diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 595bde8ed..9aed03c17 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,10 +10,9 @@ jobs: fail-fast: false matrix: os: - - ubuntu-18.04 + - ubuntu-22.04 gcc: - - 4.8 - - 7 + - 10 plugins: - default include: @@ -22,7 +21,7 @@ jobs: plugins: all steps: - name: Set up Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3 - name: Install dependencies @@ -95,6 +94,8 @@ jobs: - name: Build DFHack run: | ninja -C build-ci install + ccache --max-size 50M + ccache --cleanup ccache --show-stats - name: Run cpp unit tests id: run_tests_cpp @@ -129,6 +130,10 @@ jobs: name: Build MSVC win64 runs-on: ubuntu-22.04 steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install ccache - name: Clone DFHack uses: actions/checkout@v3 with: @@ -148,6 +153,9 @@ jobs: run: | cd build bash -x build-win64-from-linux.sh + ccache -d win64-cross/ccache --max-size 200M + ccache -d win64-cross/ccache --cleanup + ccache -d win64-cross/ccache --show-stats - name: Format artifact name id: artifactname run: | @@ -159,10 +167,10 @@ jobs: path: build/win64-cross/output/* docs: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Set up Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3 - name: Install dependencies @@ -175,23 +183,14 @@ jobs: - name: Build docs run: | sphinx-build -W --keep-going -j auto --color . docs/html - - name: Upload docs - uses: actions/upload-artifact@v1 - with: - name: docs - path: docs/html lint: - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - name: Set up Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3 - - name: Set up Ruby 2.7 - uses: actions/setup-ruby@v1 - with: - ruby-version: 2.7 - name: Install Lua run: | sudo apt-get update @@ -216,10 +215,6 @@ jobs: if: success() || failure() run: | python ci/script-syntax.py --ext=lua --cmd="luac5.3 -p" --github-actions - - name: Check Ruby syntax - if: success() || failure() - run: | - python ci/script-syntax.py --ext=rb --cmd="ruby -c" --github-actions check-pr: runs-on: ubuntu-latest diff --git a/.github/workflows/buildmaster-rebuild.yml b/.github/workflows/buildmaster-rebuild.yml index 5a1fe9504..d4c7a70e6 100644 --- a/.github/workflows/buildmaster-rebuild.yml +++ b/.github/workflows/buildmaster-rebuild.yml @@ -14,7 +14,7 @@ jobs: name: Trigger Buildmaster steps: - name: Set up Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3 - name: Install dependencies diff --git a/.github/workflows/clean-cache.yml b/.github/workflows/clean-cache.yml new file mode 100644 index 000000000..d3e12959d --- /dev/null +++ b/.github/workflows/clean-cache.yml @@ -0,0 +1,30 @@ +name: Clean up PR caches +on: + pull_request_target: + types: + - closed + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@v3 + - name: Cleanup + run: | + gh extension install actions/gh-actions-cache + + REPO=${{ github.repository }} + BRANCH="refs/pull/${{ github.event.pull_request.number }}/merge" + + echo "Fetching list of cache keys" + cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1) + + set +e + echo "Deleting caches..." + for cacheKey in $cacheKeysForPR; do + gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm + done + echo "Done" + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/steam.yml b/.github/workflows/steam.yml new file mode 100644 index 000000000..46dd27270 --- /dev/null +++ b/.github/workflows/steam.yml @@ -0,0 +1,77 @@ +name: Deploy to Steam + +on: + workflow_dispatch: + inputs: + commit_hash: + description: Commit hash + type: string + required: true + version: + description: Version + type: string + required: true + release_channel: + description: Release channel + type: string + required: true + default: staging + +jobs: + deploy-to-steam: + name: Deploy to Steam + runs-on: ubuntu-22.04 + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install ccache + - name: Clone DFHack + uses: actions/checkout@v3 + with: + submodules: true + fetch-depth: 0 + ref: ${{ github.event.inputs.commit_hash }} + - name: Get 3rd party SDKs + uses: actions/checkout@v3 + with: + repository: DFHack/3rdparty + ref: main + ssh-key: ${{ secrets.DFHACK_3RDPARTY_TOKEN }} + path: depends/steam + - name: Fetch ccache + uses: actions/cache@v3 + with: + path: build/win64-cross/ccache + key: ccache-win64-cross-msvc-${{ github.event.inputs.commit_hash }} + restore-keys: | + ccache-win64-cross-msvc-${{ github.event.inputs.commit_hash }} + ccache-win64-cross-msvc + - name: Cross-compile win64 artifacts + env: + CMAKE_EXTRA_ARGS: '-DBUILD_STONESENSE:BOOL=1 -DBUILD_DFLAUNCH:BOOL=1' + steam_username: ${{ secrets.STEAM_SDK_USERNAME }} + steam_password: ${{ secrets.STEAM_SDK_PASSWORD }} + run: | + echo "commit: ${{ github.event.inputs.commit_hash }}" + echo "version: ${{ github.event.inputs.version }}" + echo "release_channel: ${{ github.event.inputs.release_channel }}" + echo + cd build + bash -x build-win64-from-linux.sh + ccache -d win64-cross/ccache --max-size 200M + ccache -d win64-cross/ccache --cleanup + ccache -d win64-cross/ccache --show-stats + - name: Steam deploy + uses: game-ci/steam-deploy@v2 + with: + username: ${{ secrets.STEAM_USERNAME }} + password: ${{ secrets.STEAM_PASSWORD }} + configVdf: ${{ secrets.STEAM_CONFIG_VDF}} + ssfnFileName: ${{ secrets.STEAM_SSFN_FILE_NAME }} + ssfnFileContents: ${{ secrets.STEAM_SSFN_FILE_CONTENTS }} + appId: 2346660 + buildDescription: ${{ github.event.inputs.version }} + rootPath: build + depot1Path: win64-cross/output + releaseBranch: ${{ github.event.inputs.release_channel }} diff --git a/.gitignore b/.gitignore index 9b78fa31f..a386b260a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,6 @@ build*/ nix buntu -build/VC2010 #except for the real one !build/ @@ -37,6 +36,7 @@ build/lua build/bin build/depends build/library +build/package build/plugins build/scripts build/install_manifest.txt @@ -80,3 +80,6 @@ tags # external plugins /plugins/CMakeLists.custom.txt + +# steam api +depends/steam diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a4068bc44..c305de56c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.21.0 + rev: 0.22.0 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.4.2 + rev: v1.5.1 hooks: - id: forbid-tabs exclude_types: @@ -41,4 +41,4 @@ repos: entry: python3 ci/authors-rst.py files: docs/about/Authors\.rst pass_filenames: false -exclude: '^(depends/|data/.*\.json$|.*\.diff$)' +exclude: '^(depends/|data/.*\.json$|.*\.diff$|.*\.dfstock$)' diff --git a/CMakeLists.txt b/CMakeLists.txt index 56c81ba72..c1560c630 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -191,8 +191,8 @@ if(NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl endif() # set up versioning. -set(DF_VERSION "50.07") -set(DFHACK_RELEASE "alpha2") +set(DF_VERSION "50.08") +set(DFHACK_RELEASE "r2rc1") set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") @@ -300,18 +300,22 @@ if(WIN32) file(COPY ${ZLIB_DOWNLOAD_DIR}/zlib.lib DESTINATION ${CMAKE_BINARY_DIR}/depends/zlib/lib/) - # Do the same for SDLreal.dll + # Do the same for SDL.dll # (DFHack doesn't require this at build time, so no need to move it to the build folder) - set(SDLREAL_DOWNLOAD_DIR ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) + # TODO: remove SDL.dll from our distribution once DF moves to SDL2. we only + # continue to include it so we don't break Steam players on update by removing + # the SDL.dll that DF needs. + set(SDL_DOWNLOAD_DIR ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}) if(${DFHACK_BUILD_ARCH} STREQUAL "64") download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win64-SDL.dll" - ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + ${SDL_DOWNLOAD_DIR}/SDL.dll "1ae242c4b94cb03756a1288122a66faf") else() download_file("https://github.com/DFHack/dfhack-bin/releases/download/0.44.09/win32-SDL.dll" - ${SDLREAL_DOWNLOAD_DIR}/SDLreal.dll + ${SDL_DOWNLOAD_DIR}/SDL.dll "5a09604daca6b2b5ce049d79af935d6a") endif() + endif() if(APPLE) @@ -372,6 +376,9 @@ endif() #### expose depends #### +# fix for pyenv: default to `python3` before `python3.x` +set(Python_FIND_UNVERSIONED_NAMES FIRST) + if(UNIX) # Rescan for pthread and zlib if the build arch changed if(NOT "${DFHACK_BUILD_ARCH}" STREQUAL "${DFHACK_BUILD_ARCH_PREV}") @@ -615,3 +622,5 @@ if(BUILD_SIZECHECK) add_subdirectory(depends/sizecheck) add_dependencies(dfhack sizecheck) endif() + +add_subdirectory(package/windows) diff --git a/build/build-win64-from-linux.sh b/build/build-win64-from-linux.sh index 11f4dbbc5..c0559a999 100755 --- a/build/build-win64-from-linux.sh +++ b/build/build-win64-from-linux.sh @@ -41,6 +41,8 @@ fi if ! docker run --rm -i -v "$srcdir":/src -v "$srcdir/build/win64-cross/":/src/build \ -e BUILDER_UID=$builder_uid \ -e CCACHE_DIR=/src/build/ccache \ + -e steam_username \ + -e steam_password \ --name dfhack-win \ ghcr.io/dfhack/build-env:msvc \ bash -c "cd /src/build && dfhack-configure windows 64 Release -DCMAKE_INSTALL_PREFIX=/src/build/output cmake .. -DBUILD_DOCS=1 $CMAKE_EXTRA_ARGS && dfhack-make -j$jobs install" \ diff --git a/build/win64/generate-MSVC-steam.bat b/build/win64/generate-MSVC-steam.bat new file mode 100644 index 000000000..007bb1c08 --- /dev/null +++ b/build/win64/generate-MSVC-steam.bat @@ -0,0 +1,4 @@ +IF EXIST DF_PATH.txt SET /P _DF_PATH= + +,,,,,,,,,,,,,or,or,or,,or,,or,or,or +,,,,,,,,,,,,,or,or,or,or,or,or,or,or,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,or,or,or,,or,,or,or,or +,,,,,,,,,,,,,or,or,or,or,or,or,or,or,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or +,,,,,,,,,,,,,,,,,or,or,or,or,or,or,or,or,or,or +,,,,,,,,,,,,,,,,,or,,,,,,,,,or +,,,,,,,,,,,,,,,,,or,,,,,,,,,or +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,or +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,or,or,or +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,or,or,or +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#> + +,,,,,,,,,,,,,or,or,or,,,,or,or,or + + + +,,,,,,,,,,,,,or,or,or,,,,or,or,or + + + + + + + + + +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,or,or,or +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#> + +,,,,,,,,,,,,,,or,or,,,,or,or + + + +,,,,,,,,,,,,,,or,or,,,,or,or + + + + + + + + + +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,or,or +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + "#zone label(services_zones) start(18; 18) hidden() message(If you'd like to fill your wells via bucket brigade, activate the inactive pond zones one level down from where the wells will be built.) garbage dump, hospital, and pond zones" ,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` @@ -2575,42 +2715,6 @@ query_jail/services_query_jail ,,,,,,,,,,,,,,,`,`,`,`,` ,,,,,,,,,,,,,,,`,`,`,`,` -#dig label(services_traffic) start(18; 18) hidden() promote the tavern as the place to eat - -,ol,ol,ol,,ol,ol,ol,,ol,ol,ol,,or,or,or,,or,,or,or,or -,ol,ol,ol,,ol,ol,ol,,ol,ol,ol,,or,or,or,or,or,or,or,or,or -,ol,ol,ol,,ol,ol,ol,,ol,ol,ol,,or,or,or,,or,,or,or,or -,,ol,,,,ol,,,,ol,,,,,,,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,,or,,or,or,or,,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,,or,,or,or,or,,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,,,,,or,,,,,,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,,,,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,,or,,or,,or,,or -,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,,,,or,,or,,,,,or,,or,,or,,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,,,,oh,oh,oh,oh,oh,,,,or,or,or,or,or,or,or,or,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,`,`,`,oh,oh,oh,oh,oh,oh,oh,oh,oh,or,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,,`,,oh,`,`,`,oh,,`,,oh,oh,oh,`,oh,or,or,or,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,`,`,`,oh,oh,oh,oh,oh,oh,oh,oh,oh,or,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,,,,oh,oh,oh,oh,oh,,,,or,or,or,or,or,or,or,or,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,,,,,,`,,,,,,or,,or,,or,,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,,,,,,,,,,,,or,,or,,or,,or -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh,oh -,,,,oh,oh,,oh,oh -,oh,oh,oh,oh,oh,,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,,oh,oh,oh,oh,oh -,oh,oh,oh,oh,oh,,oh,oh,oh,oh,oh - "#build label(services_build3) start(18; 18) hidden() jail, statues" ,~,~,~,,~,~,~,,~,~,~,,t,l,b,,`,,t,l,b @@ -2946,8 +3050,47 @@ Apartments Walkthrough: ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d ,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d -"#build label(suites2) start(18; 18; central ramp) message(Remember to enqueue manager orders for this blueprint. -bedrooms are left unconfigured so you can assign them to specific nobles)" +"#meta label(suites2) start(central ramp) message(Remember to enqueue manager orders for this blueprint. +bedrooms are left unconfigured so you can assign them to specific nobles.) build furniture and set traffic patterns" +traffic_suites/suites_traffic +build_suites/suites_build +#dig label(suites_traffic) start(18; 18; central ramp) hidden() + +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,,,,or,,,,,,or,,,,`,,`,,,,or,,,,,,or,,,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,,,,,,,,,,,,,`,`,`,,,,,,,,,,,,,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,,,or,,,,,,or,,,,`,`,`,,,,or,,,,,,or,,,,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,,`,`,`,`,`,`,`,`,`,`,,`,`,~,`,`,,`,`,`,`,`,`,`,`,`,`,,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,,,,or,,,,,,or,,,,`,`,`,,,,or,,,,,,or,,,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,,,,,,,,,,,,,`,`,`,,,,,,,,,,,,,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,`,`,or,`,`,`,`,`,,`,`,`,`,`,or,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,`,`,,`,`,`,`,`,,`,`,`,`,`,,`,` +,`,`,,,,or,,,,,,or,,,,`,,`,,,,or,,,,,,or,,,,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` + +#build label(suites_build) start(18; 18; central ramp) hidden() ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` diff --git a/data/blueprints/library/embark.csv b/data/blueprints/embark.csv similarity index 100% rename from data/blueprints/library/embark.csv rename to data/blueprints/embark.csv diff --git a/data/blueprints/library/exploratory-mining/connected-mineshafts.csv b/data/blueprints/exploratory-mining/connected-mineshafts.csv similarity index 100% rename from data/blueprints/library/exploratory-mining/connected-mineshafts.csv rename to data/blueprints/exploratory-mining/connected-mineshafts.csv diff --git a/data/blueprints/library/exploratory-mining/tunnels.csv b/data/blueprints/exploratory-mining/tunnels.csv similarity index 100% rename from data/blueprints/library/exploratory-mining/tunnels.csv rename to data/blueprints/exploratory-mining/tunnels.csv diff --git a/data/blueprints/library/exploratory-mining/vertical-mineshafts.csv b/data/blueprints/exploratory-mining/vertical-mineshafts.csv similarity index 100% rename from data/blueprints/library/exploratory-mining/vertical-mineshafts.csv rename to data/blueprints/exploratory-mining/vertical-mineshafts.csv diff --git a/data/blueprints/library/layout-helpers/mark_down_left.csv b/data/blueprints/layout-helpers/mark_down_left.csv similarity index 100% rename from data/blueprints/library/layout-helpers/mark_down_left.csv rename to data/blueprints/layout-helpers/mark_down_left.csv diff --git a/data/blueprints/library/layout-helpers/mark_down_right.csv b/data/blueprints/layout-helpers/mark_down_right.csv similarity index 100% rename from data/blueprints/library/layout-helpers/mark_down_right.csv rename to data/blueprints/layout-helpers/mark_down_right.csv diff --git a/data/blueprints/library/layout-helpers/mark_up_left.csv b/data/blueprints/layout-helpers/mark_up_left.csv similarity index 100% rename from data/blueprints/library/layout-helpers/mark_up_left.csv rename to data/blueprints/layout-helpers/mark_up_left.csv diff --git a/data/blueprints/library/layout-helpers/mark_up_right.csv b/data/blueprints/layout-helpers/mark_up_right.csv similarity index 100% rename from data/blueprints/library/layout-helpers/mark_up_right.csv rename to data/blueprints/layout-helpers/mark_up_right.csv diff --git a/data/blueprints/library/pump_stack.csv b/data/blueprints/pump_stack.csv similarity index 100% rename from data/blueprints/library/pump_stack.csv rename to data/blueprints/pump_stack.csv diff --git a/data/blueprints/library/quickfortress.csv b/data/blueprints/quickfortress.csv similarity index 100% rename from data/blueprints/library/quickfortress.csv rename to data/blueprints/quickfortress.csv diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-2-build.csv b/data/blueprints/test/ecosystem/golden/gui_quantum-2-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/gui_quantum-2-build.csv rename to data/blueprints/test/ecosystem/golden/gui_quantum-2-build.csv diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-3-place.csv b/data/blueprints/test/ecosystem/golden/gui_quantum-3-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/gui_quantum-3-place.csv rename to data/blueprints/test/ecosystem/golden/gui_quantum-3-place.csv diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-4-query.csv b/data/blueprints/test/ecosystem/golden/gui_quantum-4-query.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/gui_quantum-4-query.csv rename to data/blueprints/test/ecosystem/golden/gui_quantum-4-query.csv diff --git a/data/blueprints/library/test/ecosystem/golden/meta-1-dig.csv b/data/blueprints/test/ecosystem/golden/meta-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/meta-1-dig.csv rename to data/blueprints/test/ecosystem/golden/meta-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/golden/tracks-2-carve.csv b/data/blueprints/test/ecosystem/golden/tracks-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/tracks-2-carve.csv rename to data/blueprints/test/ecosystem/golden/tracks-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/golden/transform-1-dig.csv b/data/blueprints/test/ecosystem/golden/transform-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/transform-1-dig.csv rename to data/blueprints/test/ecosystem/golden/transform-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/golden/transform-2-construct.csv b/data/blueprints/test/ecosystem/golden/transform-2-construct.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/transform-2-construct.csv rename to data/blueprints/test/ecosystem/golden/transform-2-construct.csv diff --git a/data/blueprints/library/test/ecosystem/golden/transform-3-build.csv b/data/blueprints/test/ecosystem/golden/transform-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/transform-3-build.csv rename to data/blueprints/test/ecosystem/golden/transform-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-1-dig.csv b/data/blueprints/test/ecosystem/in/basic-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-1-dig.csv rename to data/blueprints/test/ecosystem/in/basic-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-2-carve.csv b/data/blueprints/test/ecosystem/in/basic-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-2-carve.csv rename to data/blueprints/test/ecosystem/in/basic-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-3-build.csv b/data/blueprints/test/ecosystem/in/basic-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-3-build.csv rename to data/blueprints/test/ecosystem/in/basic-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-4-place.csv b/data/blueprints/test/ecosystem/in/basic-4-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-4-place.csv rename to data/blueprints/test/ecosystem/in/basic-4-place.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-5-zone.csv b/data/blueprints/test/ecosystem/in/basic-5-zone.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-5-zone.csv rename to data/blueprints/test/ecosystem/in/basic-5-zone.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-spec.csv b/data/blueprints/test/ecosystem/in/basic-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-spec.csv rename to data/blueprints/test/ecosystem/in/basic-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-1-dig.csv b/data/blueprints/test/ecosystem/in/buildings-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/buildings-1-dig.csv rename to data/blueprints/test/ecosystem/in/buildings-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-2-construct.csv b/data/blueprints/test/ecosystem/in/buildings-2-construct.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/buildings-2-construct.csv rename to data/blueprints/test/ecosystem/in/buildings-2-construct.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-3-build.csv b/data/blueprints/test/ecosystem/in/buildings-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/buildings-3-build.csv rename to data/blueprints/test/ecosystem/in/buildings-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-spec.csv b/data/blueprints/test/ecosystem/in/buildings-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/buildings-spec.csv rename to data/blueprints/test/ecosystem/in/buildings-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-1-dig.csv b/data/blueprints/test/ecosystem/in/fortifications-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-1-dig.csv rename to data/blueprints/test/ecosystem/in/fortifications-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-2-smooth.csv b/data/blueprints/test/ecosystem/in/fortifications-2-smooth.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-2-smooth.csv rename to data/blueprints/test/ecosystem/in/fortifications-2-smooth.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-3-carve.csv b/data/blueprints/test/ecosystem/in/fortifications-3-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-3-carve.csv rename to data/blueprints/test/ecosystem/in/fortifications-3-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-spec.csv b/data/blueprints/test/ecosystem/in/fortifications-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-spec.csv rename to data/blueprints/test/ecosystem/in/fortifications-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-2-build.csv b/data/blueprints/test/ecosystem/in/gui_quantum-2-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/gui_quantum-2-build.csv rename to data/blueprints/test/ecosystem/in/gui_quantum-2-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-3-place.csv b/data/blueprints/test/ecosystem/in/gui_quantum-3-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/gui_quantum-3-place.csv rename to data/blueprints/test/ecosystem/in/gui_quantum-3-place.csv diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-4-query.csv b/data/blueprints/test/ecosystem/in/gui_quantum-4-query.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/gui_quantum-4-query.csv rename to data/blueprints/test/ecosystem/in/gui_quantum-4-query.csv diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv b/data/blueprints/test/ecosystem/in/gui_quantum-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv rename to data/blueprints/test/ecosystem/in/gui_quantum-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/meta-1-dig.csv b/data/blueprints/test/ecosystem/in/meta-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/meta-1-dig.csv rename to data/blueprints/test/ecosystem/in/meta-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/meta-spec.csv b/data/blueprints/test/ecosystem/in/meta-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/meta-spec.csv rename to data/blueprints/test/ecosystem/in/meta-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/stockpiles-2-place.csv b/data/blueprints/test/ecosystem/in/stockpiles-2-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/stockpiles-2-place.csv rename to data/blueprints/test/ecosystem/in/stockpiles-2-place.csv diff --git a/data/blueprints/library/test/ecosystem/in/stockpiles-spec.csv b/data/blueprints/test/ecosystem/in/stockpiles-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/stockpiles-spec.csv rename to data/blueprints/test/ecosystem/in/stockpiles-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-1-dig.csv b/data/blueprints/test/ecosystem/in/tracks-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-1-dig.csv rename to data/blueprints/test/ecosystem/in/tracks-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-2-carve.csv b/data/blueprints/test/ecosystem/in/tracks-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-2-carve.csv rename to data/blueprints/test/ecosystem/in/tracks-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-3-build.csv b/data/blueprints/test/ecosystem/in/tracks-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-3-build.csv rename to data/blueprints/test/ecosystem/in/tracks-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-spec.csv b/data/blueprints/test/ecosystem/in/tracks-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-spec.csv rename to data/blueprints/test/ecosystem/in/tracks-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-1-dig.csv b/data/blueprints/test/ecosystem/in/transform-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/transform-1-dig.csv rename to data/blueprints/test/ecosystem/in/transform-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-2-construct.csv b/data/blueprints/test/ecosystem/in/transform-2-construct.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/transform-2-construct.csv rename to data/blueprints/test/ecosystem/in/transform-2-construct.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-3-build.csv b/data/blueprints/test/ecosystem/in/transform-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/transform-3-build.csv rename to data/blueprints/test/ecosystem/in/transform-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-spec.csv b/data/blueprints/test/ecosystem/in/transform-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/transform-spec.csv rename to data/blueprints/test/ecosystem/in/transform-spec.csv diff --git a/data/blueprints/library/test/ecosystem/in/zones-2-zone.csv b/data/blueprints/test/ecosystem/in/zones-2-zone.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/zones-2-zone.csv rename to data/blueprints/test/ecosystem/in/zones-2-zone.csv diff --git a/data/blueprints/library/test/ecosystem/in/zones-spec.csv b/data/blueprints/test/ecosystem/in/zones-spec.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/zones-spec.csv rename to data/blueprints/test/ecosystem/in/zones-spec.csv diff --git a/data/blueprints/library/test/quickfort/list/all_modes.csv b/data/blueprints/test/quickfort/list/all_modes.csv similarity index 100% rename from data/blueprints/library/test/quickfort/list/all_modes.csv rename to data/blueprints/test/quickfort/list/all_modes.csv diff --git a/data/blueprints/library/test/quickfort/list/all_modes_separate_sheets.xlsx b/data/blueprints/test/quickfort/list/all_modes_separate_sheets.xlsx similarity index 100% rename from data/blueprints/library/test/quickfort/list/all_modes_separate_sheets.xlsx rename to data/blueprints/test/quickfort/list/all_modes_separate_sheets.xlsx diff --git a/data/blueprints/library/test/quickfort/list/all_modes_single_sheet.xlsx b/data/blueprints/test/quickfort/list/all_modes_single_sheet.xlsx similarity index 100% rename from data/blueprints/library/test/quickfort/list/all_modes_single_sheet.xlsx rename to data/blueprints/test/quickfort/list/all_modes_single_sheet.xlsx diff --git a/data/blueprints/library/tombs/Mini_Saracen.csv b/data/blueprints/tombs/Mini_Saracen.csv similarity index 100% rename from data/blueprints/library/tombs/Mini_Saracen.csv rename to data/blueprints/tombs/Mini_Saracen.csv diff --git a/data/blueprints/library/tombs/The_Saracen_Crypts.csv b/data/blueprints/tombs/The_Saracen_Crypts.csv similarity index 100% rename from data/blueprints/library/tombs/The_Saracen_Crypts.csv rename to data/blueprints/tombs/The_Saracen_Crypts.csv diff --git a/data/blueprints/README.md b/data/dfhack-config/blueprints/README.md similarity index 100% rename from data/blueprints/README.md rename to data/dfhack-config/blueprints/README.md diff --git a/data/dfhack-config/buildingplan.json b/data/dfhack-config/buildingplan.json new file mode 100644 index 000000000..9bb3052b7 --- /dev/null +++ b/data/dfhack-config/buildingplan.json @@ -0,0 +1,5 @@ +{ + "planner": { + "minimized": true + } +} \ No newline at end of file diff --git a/data/dfhack-config/init/dfhack.control-panel-system.init b/data/dfhack-config/init/dfhack.control-panel-system.init new file mode 100644 index 000000000..c1ad6a679 --- /dev/null +++ b/data/dfhack-config/init/dfhack.control-panel-system.init @@ -0,0 +1,5 @@ +# DO NOT EDIT THIS FILE +# Please use gui/control-panel to edit this file + +enable faststart +enable work-now diff --git a/data/dfhack-config/init/onMapLoad.control-panel-new-fort.init b/data/dfhack-config/init/onMapLoad.control-panel-new-fort.init new file mode 100644 index 000000000..66a07d14f --- /dev/null +++ b/data/dfhack-config/init/onMapLoad.control-panel-new-fort.init @@ -0,0 +1,4 @@ +# DO NOT EDIT THIS FILE +# Please use gui/control-panel to edit this file + +on-new-fortress enable fix/protect-nicks diff --git a/data/dfhack-config/init/onMapLoad.control-panel-repeats.init b/data/dfhack-config/init/onMapLoad.control-panel-repeats.init new file mode 100644 index 000000000..cbed00b67 --- /dev/null +++ b/data/dfhack-config/init/onMapLoad.control-panel-repeats.init @@ -0,0 +1,5 @@ +# DO NOT EDIT THIS FILE +# Please use gui/control-panel to edit this file + +repeat --name general-strike --time 1 --timeUnits days --command [ fix/general-strike -q ] +repeat --name warn-starving --time 10 --timeUnits days --command [ warn-starving ] diff --git a/data/dfhack-config/quickfort/aliases.txt b/data/dfhack-config/quickfort/aliases.txt deleted file mode 100644 index a3e52052d..000000000 --- a/data/dfhack-config/quickfort/aliases.txt +++ /dev/null @@ -1,17 +0,0 @@ -# Custom aliases for quickfort query mode blueprints -# -# This file defines custom key sequence shortcuts for query mode blueprints. -# Definitions in this file take precedence over any definitions in the -# baseline aliases configuration file in -# hack/data/quickfort/aliases-common.txt -# -# Please see -# https://docs.dfhack.org/en/latest/docs/guides/quickfort-alias-guide.html -# or -# hack/docs/docs/guides/quickfort-alias-guide.html -# in your DF installation directory for alias syntax documentation and an -# overview of the DFHack alias standard library. -# -# -# Add your custom aliases here. Example: -# food_stash: {foodprefix}b{Right}{Down 11}p^{permitplants} diff --git a/data/dfhack-config/quickfort/quickfort.txt b/data/dfhack-config/quickfort/quickfort.txt deleted file mode 100644 index 92af2909e..000000000 --- a/data/dfhack-config/quickfort/quickfort.txt +++ /dev/null @@ -1,37 +0,0 @@ -# quickfort main configuration file -# -# Set startup defaults for the quickfort script in this file. Settings can be -# temporarily overridden in the active session with the `quickfort set` command. -# -# If you have edited this file but want to revert to "factory defaults", delete -# this file and a default one will be regenerated for you the next time you -# start DFHack. - -# Directory tree to search for blueprints. Can be set to an absolute or relative -# path. If set to a relative path, resolves to a directory under the DF folder. -# Note that if you change this directory, you will not automatically pick up -# blueprints written by the DFHack "blueprint" plugin (which always writes to -# the "blueprints" dir). -blueprints_dir=blueprints - -# Set to "true" or "false". If true, will designate all dig blueprints in marker -# mode. If false, only cells with dig codes explicitly prefixed with an "m" will -# be designated in marker mode. -force_marker_mode=false - -# Skip query blueprint sanity checks that detect common blueprint errors and -# halt or skip keycode playback. Checks include ensuring a configurable building -# exists at the designated cursor position and verifying the active UI screen is -# the same before and after sending keys for the cursor position. If you find -# you need to enable this for one of your own blueprints, you should probably be -# using a config blueprint, not a query blueprint. -query_unsafe=false - -# Set to the maximum number of resources you want assigned to stockpiles of the -# relevant types. Set to -1 for DF defaults (number of stockpile tiles for -# stockpiles that take barrels and bins, 1 wheelbarrow for stone stockpiles). -# The default here for wheelbarrows is 0 since restricting stockpiles to a -# single wheelbarrow can drastically *decrease* the efficiency of your fort. -stockpiles_max_barrels=-1 -stockpiles_max_bins=-1 -stockpiles_max_wheelbarrows=0 diff --git a/data/dfhack-config/stockpiles/README.md b/data/dfhack-config/stockpiles/README.md new file mode 100644 index 000000000..593d45fb6 --- /dev/null +++ b/data/dfhack-config/stockpiles/README.md @@ -0,0 +1,5 @@ +This folder contains stockpile settings that can be applied by `stockpiles` and +`quickfort` tools. For more information, see: + +* [stockpiles documentation](https://docs.dfhack.org/en/latest/docs/tools/stockpiles.html) +* [quickfort documentation](https://docs.dfhack.org/en/latest/docs/guides/quickfort-user-guide.html) diff --git a/data/init/dfhack.keybindings.init b/data/init/dfhack.keybindings.init index 3a94aea2a..f677ca301 100644 --- a/data/init/dfhack.keybindings.init +++ b/data/init/dfhack.keybindings.init @@ -43,12 +43,15 @@ keybinding add Ctrl-C spotclean # destroy the selected item keybinding add Ctrl-K@dwarfmode autodump-destroy-item -# destroy items designated for dump in the selected tile -keybinding add Ctrl-H@dwarfmode autodump-destroy-here +# bring up the autodump UI +keybinding add Ctrl-H@dwarfmode gui/autodump # apply blueprints to the map keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort +# toggle keyboard cursor +keybinding add Alt-K@dwarfmode toggle-kbd-cursor + # show information collected by dwarfmonitor #keybinding add Alt-M@dwarfmode/Default "dwarfmonitor prefs" #keybinding add Ctrl-F@dwarfmode/Default "dwarfmonitor stats" @@ -153,6 +156,10 @@ keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort #keybinding add Ctrl-Shift-N@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit|buildinglist gui/rename #keybinding add Ctrl-Shift-T@dwarfmode|unit|unitlist|joblist|dungeon_monsterstatus|layer_unit_relationship|item|workshop_profile|layer_noblelist|locations|pets|layer_overall_health|textviewer|reportlist|announcelist|layer_military|layer_unit_health|customize_unit "gui/rename unit-profession" +# gui/design +keybinding add Ctrl-D@dwarfmode gui/design + + ##################### # adv mode bindings # diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init index 50272cfa2..9d8c0dd33 100644 --- a/data/init/dfhack.tools.init +++ b/data/init/dfhack.tools.init @@ -137,3 +137,11 @@ enable \ # a replacement for the "load game" screen #gui/load-screen enable + +################### +# Default Aliases # +################### + +alias add autounsuspend suspendmanager +alias add gui/dig gui/design +alias add version help diff --git a/data/orders/basic.json b/data/orders/basic.json index 16b05e0ba..8e271ba04 100644 --- a/data/orders/basic.json +++ b/data/orders/basic.json @@ -1,31 +1,9 @@ [ - { - "amount_left" : 150, - "amount_total" : 150, - "frequency" : "Monthly", - "id" : 0, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "LessThan", - "flags" : - [ - "unrotten" - ], - "item_type" : "FOOD", - "value" : 400 - } - ], - "job" : "PrepareMeal", - "meal_ingredients" : 2 - }, { "amount_left" : 10, "amount_total" : 10, "frequency" : "Daily", - "id" : 1, + "id" : 0, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -47,7 +25,7 @@ "unrotten", "cookable" ], - "value" : 500 + "value" : 80 }, { "condition" : "AtMost", @@ -57,15 +35,6 @@ ], "item_type" : "FOOD", "value" : 3500 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "unrotten" - ], - "item_type" : "FOOD", - "value" : 400 } ], "job" : "PrepareMeal", @@ -75,7 +44,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 2, + "id" : 1, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -112,7 +81,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 3, + "id" : 2, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -149,7 +118,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 4, + "id" : 3, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -170,7 +139,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 5, + "id" : 4, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -205,7 +174,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 6, + "id" : 5, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -237,7 +206,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 7, + "id" : 6, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -268,7 +237,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 8, + "id" : 7, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -290,7 +259,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 9, + "id" : 8, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -322,7 +291,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 10, + "id" : 9, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -353,7 +322,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 11, + "id" : 10, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -386,7 +355,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 12, + "id" : 11, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -426,7 +395,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 13, + "id" : 12, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -452,7 +421,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 14, + "id" : 13, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -487,7 +456,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 15, + "id" : 14, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -522,7 +491,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 16, + "id" : 15, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -557,7 +526,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 17, + "id" : 16, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -587,7 +556,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 18, + "id" : 17, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -618,6 +587,36 @@ "job" : "MakeTool", "material" : "INORGANIC" }, + { + "amount_left" : 1, + "amount_total" : 1, + "frequency" : "Daily", + "id" : 18, + "is_active" : false, + "is_validated" : false, + "item_conditions" : + [ + { + "condition" : "AtLeast", + "item_type" : "WOOD", + "value" : 50 + }, + { + "condition" : "AtMost", + "flags" : + [ + "empty" + ], + "item_type" : "BIN", + "value" : 5 + } + ], + "job" : "ConstructBin", + "material_category" : + [ + "wood" + ] + }, { "amount_left" : 1, "amount_total" : 1, diff --git a/data/orders/military.json b/data/orders/military.json index 0e53747b6..abf2675f9 100644 --- a/data/orders/military.json +++ b/data/orders/military.json @@ -92,15 +92,16 @@ [ { "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", + "flags" : + [ + "silk" + ], + "item_type" : "CLOTH", + "min_dimension" : 10000, "value" : 10 }, { "condition" : "AtMost", - "flags" : - [ - "leather" - ], "item_subtype" : "ITEM_ARMOR_CLOAK", "item_type" : "ARMOR", "value" : 10 @@ -110,7 +111,7 @@ "job" : "MakeArmor", "material_category" : [ - "leather" + "silk" ] }, { @@ -152,25 +153,21 @@ [ { "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 + "item_type" : "WOOD", + "value" : 50 }, { "condition" : "AtMost", - "flags" : - [ - "leather" - ], "item_subtype" : "ITEM_SHIELD_SHIELD", "item_type" : "SHIELD", - "value" : 1 + "value" : 10 } ], "item_subtype" : "ITEM_SHIELD_SHIELD", "job" : "MakeShield", "material_category" : [ - "leather" + "wood" ] }, { @@ -191,7 +188,7 @@ "condition" : "AtMost", "item_subtype" : "ITEM_ARMOR_LEATHER", "item_type" : "ARMOR", - "value" : 1 + "value" : 10 } ], "item_subtype" : "ITEM_ARMOR_LEATHER", @@ -223,7 +220,7 @@ ], "item_subtype" : "ITEM_HELM_HELM", "item_type" : "HELM", - "value" : 1 + "value" : 10 } ], "item_subtype" : "ITEM_HELM_HELM", @@ -255,7 +252,7 @@ ], "item_subtype" : "ITEM_SHOES_BOOTS", "item_type" : "SHOES", - "value" : 2 + "value" : 20 } ], "item_subtype" : "ITEM_SHOES_BOOTS", @@ -287,7 +284,7 @@ ], "item_subtype" : "ITEM_PANTS_LEGGINGS", "item_type" : "PANTS", - "value" : 1 + "value" : 10 } ], "item_subtype" : "ITEM_PANTS_LEGGINGS", @@ -319,7 +316,7 @@ ], "item_subtype" : "ITEM_GLOVES_GLOVES", "item_type" : "GLOVES", - "value" : 2 + "value" : 20 } ], "item_subtype" : "ITEM_GLOVES_GLOVES", @@ -571,37 +568,6 @@ "is_active" : false, "is_validated" : false, "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:NATIVE_PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 10 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:NATIVE_PLATINUM" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 19, - "is_active" : false, - "is_validated" : false, - "item_conditions" : [ { "condition" : "AtLeast", @@ -629,7 +595,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 20, + "id" : 19, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -660,7 +626,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 21, + "id" : 20, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -691,7 +657,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 22, + "id" : 21, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -722,7 +688,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 23, + "id" : 22, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -759,7 +725,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 24, + "id" : 23, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -808,7 +774,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 25, + "id" : 24, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -845,7 +811,7 @@ "amount_left" : 4, "amount_total" : 4, "frequency" : "Daily", - "id" : 26, + "id" : 25, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -888,7 +854,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 27, + "id" : 26, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -924,7 +890,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 28, + "id" : 27, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -967,7 +933,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 29, + "id" : 28, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1015,7 +981,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 30, + "id" : 29, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1070,79 +1036,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 35, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:SILVER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 35, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:SILVER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 37, + "id" : 30, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1175,7 +1069,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 38, + "id" : 31, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1208,7 +1102,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 39, + "id" : 32, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1241,7 +1135,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 40, + "id" : 33, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1274,7 +1168,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 41, + "id" : 34, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1307,7 +1201,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 42, + "id" : 35, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1347,7 +1241,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 43, + "id" : 36, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1387,7 +1281,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 44, + "id" : 37, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1404,12 +1298,6 @@ "material" : "COAL", "value" : 100 }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, { "condition" : "AtMost", "item_subtype" : "ITEM_WEAPON_MACE", @@ -1426,7 +1314,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 45, + "id" : 38, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1443,12 +1331,6 @@ "material" : "COAL", "value" : 100 }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, { "condition" : "AtMost", "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", @@ -1465,7 +1347,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 46, + "id" : 39, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1498,7 +1380,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 47, + "id" : 40, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1531,7 +1413,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 48, + "id" : 41, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1564,7 +1446,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 49, + "id" : 42, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1597,7 +1479,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 50, + "id" : 43, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1630,7 +1512,103 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 51, + "id" : 44, + "is_active" : false, + "is_validated" : false, + "item_conditions" : + [ + { + "condition" : "AtLeast", + "item_type" : "BAR", + "material" : "INORGANIC:SILVER", + "value" : 5 + }, + { + "condition" : "AtLeast", + "item_type" : "BAR", + "material" : "COAL", + "value" : 100 + }, + { + "condition" : "LessThan", + "item_type" : "BOULDER", + "reaction_class" : "FLUX", + "value" : 5 + }, + { + "condition" : "AtMost", + "flags" : + [ + "metal" + ], + "item_subtype" : "ITEM_WEAPON_MACE", + "item_type" : "WEAPON", + "value" : 10 + }, + { + "condition" : "LessThan", + "item_type" : "BAR", + "material" : "INORGANIC:STEEL", + "value" : 10 + } + ], + "item_subtype" : "ITEM_WEAPON_MACE", + "job" : "MakeWeapon", + "material" : "INORGANIC:SILVER" + }, + { + "amount_left" : 1, + "amount_total" : 1, + "frequency" : "Daily", + "id" : 45, + "is_active" : false, + "is_validated" : false, + "item_conditions" : + [ + { + "condition" : "AtLeast", + "item_type" : "BAR", + "material" : "INORGANIC:SILVER", + "value" : 5 + }, + { + "condition" : "AtLeast", + "item_type" : "BAR", + "material" : "COAL", + "value" : 100 + }, + { + "condition" : "LessThan", + "item_type" : "BOULDER", + "reaction_class" : "FLUX", + "value" : 5 + }, + { + "condition" : "AtMost", + "flags" : + [ + "metal" + ], + "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", + "item_type" : "WEAPON", + "value" : 10 + }, + { + "condition" : "LessThan", + "item_type" : "BAR", + "material" : "INORGANIC:STEEL", + "value" : 10 + } + ], + "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", + "job" : "MakeWeapon", + "material" : "INORGANIC:SILVER" + }, + { + "amount_left" : 1, + "amount_total" : 1, + "frequency" : "Daily", + "id" : 46, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1678,7 +1656,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 52, + "id" : 47, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1726,7 +1704,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 53, + "id" : 48, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1774,7 +1752,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 54, + "id" : 49, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1822,7 +1800,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 55, + "id" : 50, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1870,7 +1848,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 56, + "id" : 51, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1928,7 +1906,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 57, + "id" : 52, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -1986,7 +1964,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 58, + "id" : 53, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2040,7 +2018,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 59, + "id" : 54, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2094,7 +2072,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 60, + "id" : 55, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2142,7 +2120,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 61, + "id" : 56, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2190,7 +2168,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 62, + "id" : 57, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2238,7 +2216,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 63, + "id" : 58, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2286,7 +2264,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 64, + "id" : 59, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2334,7 +2312,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 79, + "id" : 74, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2382,7 +2360,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 80, + "id" : 75, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2430,7 +2408,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 81, + "id" : 76, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2478,7 +2456,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 82, + "id" : 77, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2526,7 +2504,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 83, + "id" : 78, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2574,7 +2552,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 84, + "id" : 79, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2632,7 +2610,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 85, + "id" : 80, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2690,7 +2668,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 72, + "id" : 67, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2735,7 +2713,7 @@ "item_type" : "BAR", "material" : "INORGANIC:SILVER", "value" : 5 - }, + } ], "item_subtype" : "ITEM_WEAPON_MACE", "job" : "MakeWeapon", @@ -2745,7 +2723,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 87, + "id" : 82, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2799,7 +2777,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 88, + "id" : 83, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2847,7 +2825,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 89, + "id" : 84, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2895,7 +2873,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 90, + "id" : 85, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2943,7 +2921,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 91, + "id" : 86, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -2991,7 +2969,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 92, + "id" : 87, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3039,7 +3017,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 79, + "id" : 74, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3093,7 +3071,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 80, + "id" : 75, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3147,7 +3125,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 81, + "id" : 76, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3201,7 +3179,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 82, + "id" : 77, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3255,7 +3233,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 83, + "id" : 78, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3309,7 +3287,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 84, + "id" : 79, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3373,7 +3351,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 85, + "id" : 80, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3437,7 +3415,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 86, + "id" : 81, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3497,7 +3475,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 87, + "id" : 82, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3557,7 +3535,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 88, + "id" : 83, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3611,7 +3589,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 89, + "id" : 84, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3665,7 +3643,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 90, + "id" : 85, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3719,7 +3697,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 91, + "id" : 86, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3773,7 +3751,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 92, + "id" : 87, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3827,7 +3805,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 93, + "id" : 88, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3887,7 +3865,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 94, + "id" : 89, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -3947,7 +3925,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 95, + "id" : 90, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4007,7 +3985,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 96, + "id" : 91, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4067,7 +4045,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 97, + "id" : 92, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4127,7 +4105,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 98, + "id" : 93, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4197,7 +4175,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 99, + "id" : 94, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4267,7 +4245,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 100, + "id" : 95, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4333,7 +4311,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 101, + "id" : 96, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4399,7 +4377,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 102, + "id" : 97, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4459,7 +4437,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 103, + "id" : 98, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4519,7 +4497,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 104, + "id" : 99, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4579,7 +4557,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 105, + "id" : 100, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -4639,7 +4617,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 106, + "id" : 101, "is_active" : false, "is_validated" : false, "item_conditions" : diff --git a/data/orders/military_include_artifact_materials.json b/data/orders/military_include_artifact_materials.json deleted file mode 100644 index 536b2cd7a..000000000 --- a/data/orders/military_include_artifact_materials.json +++ /dev/null @@ -1,4971 +0,0 @@ -[ - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 0, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 10 - }, - { - "condition" : "AtMost", - "item_type" : "BACKPACK", - "value" : 10 - } - ], - "job" : "MakeBackpack", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 1, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 10 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_type" : "FLASK", - "value" : 10 - } - ], - "job" : "MakeFlask", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 2, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 10 - }, - { - "condition" : "AtMost", - "item_type" : "QUIVER", - "value" : 10 - } - ], - "job" : "MakeQuiver", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 3, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 10 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_ARMOR_CLOAK", - "item_type" : "ARMOR", - "value" : 10 - } - ], - "item_subtype" : "ITEM_ARMOR_CLOAK", - "job" : "MakeArmor", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 4, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "WOOD", - "value" : 50 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material_category" : - [ - "wood" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 5, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "value" : 1 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 6, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_ARMOR_LEATHER", - "item_type" : "ARMOR", - "value" : 1 - } - ], - "item_subtype" : "ITEM_ARMOR_LEATHER", - "job" : "MakeArmor", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 7, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "value" : 1 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 8, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "value" : 2 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 9, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_PANTS_LEGGINGS", - "item_type" : "PANTS", - "value" : 1 - } - ], - "item_subtype" : "ITEM_PANTS_LEGGINGS", - "job" : "MakePants", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 10, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "SKIN_TANNED", - "value" : 25 - }, - { - "condition" : "AtMost", - "flags" : - [ - "leather" - ], - "item_subtype" : "ITEM_GLOVES_GLOVES", - "item_type" : "GLOVES", - "value" : 2 - } - ], - "item_subtype" : "ITEM_GLOVES_GLOVES", - "job" : "MakeGloves", - "material_category" : - [ - "leather" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 11, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "flags" : - [ - "unrotten", - "bone", - "body_part" - ], - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "bone" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material_category" : - [ - "bone" - ] - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 12, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "WOOD", - "value" : 150 - }, - { - "condition" : "AtMost", - "flags" : - [ - "bone" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 200 - }, - { - "condition" : "AtMost", - "flags" : - [ - "plant" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material_category" : - [ - "wood" - ] - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 13, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:CASSITERITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:TIN", - "value" : 20 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:CASSITERITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 14, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:HEMATITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:HEMATITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 15, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:HORN_SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 10 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:HORN_SILVER" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 16, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:LIMONITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:LIMONITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 17, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:NATIVE_COPPER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:NATIVE_COPPER" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 18, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:NATIVE_PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 10 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:NATIVE_PLATINUM" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 19, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:NATIVE_SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 10 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:NATIVE_SILVER" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 20, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:MAGNETITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:MAGNETITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 21, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:MALACHITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:MALACHITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 22, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "material" : "INORGANIC:TETRAHEDRITE", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 40 - } - ], - "job" : "SmeltOre", - "material" : "INORGANIC:TETRAHEDRITE" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 23, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "bearing" : "TIN", - "condition" : "AtLeast", - "item_type" : "BOULDER", - "value" : 5 - }, - { - "bearing" : "COPPER", - "condition" : "AtLeast", - "item_type" : "BOULDER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 40 - } - ], - "job" : "CustomReaction", - "reaction" : "BRONZE_MAKING" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 24, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:TIN", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 40 - }, - { - "bearing" : "TIN", - "condition" : "AtMost", - "item_type" : "BOULDER", - "value" : 5 - }, - { - "bearing" : "COPPER", - "condition" : "AtMost", - "item_type" : "BOULDER", - "value" : 5 - } - ], - "job" : "CustomReaction", - "reaction" : "BRONZE_MAKING2" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 25, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:PIG_IRON", - "value" : 10 - } - ], - "job" : "CustomReaction", - "reaction" : "PIG_IRON_MAKING" - }, - { - "amount_left" : 4, - "amount_total" : 4, - "frequency" : "Daily", - "id" : 26, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:PIG_IRON", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 40 - } - ], - "job" : "CustomReaction", - "reaction" : "STEEL_MAKING" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 27, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 28, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "min_dimension" : 150, - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 29, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 30, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "min_dimension" : 150, - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "item_type" : "AMMO", - "value" : 1000 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - } - ], - "item_subtype" : "ITEM_AMMO_BOLTS", - "job" : "MakeAmmo", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 31, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "material" : "INORGANIC:PLATINUM", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:PLATINUM" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 32, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "material" : "INORGANIC:PLATINUM", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:PLATINUM" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 64, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "material" : "INORGANIC:PLATINUM", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:PLATINUM" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 35, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:SILVER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 35, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:SILVER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 64, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:SILVER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 37, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 38, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "job" : "MakeArmor", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 39, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 40, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 41, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "item_type" : "GLOVES", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "job" : "MakeGloves", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 42, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_PANTS_GREAVES", - "item_type" : "PANTS", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "material" : "INORGANIC:STEEL", - "value" : 5 - } - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "job" : "MakePants", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 43, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "item_type" : "ARMOR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "material" : "INORGANIC:STEEL", - "value" : 5 - } - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "job" : "MakeArmor", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 44, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 45, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 46, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_SPEAR", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 47, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 48, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 49, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_PICK", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 50, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:STEEL" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 51, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 52, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "job" : "MakeArmor", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 53, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 54, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 55, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "item_type" : "GLOVES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "job" : "MakeGloves", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 56, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "item_type" : "PANTS", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "job" : "MakePants", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 57, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "job" : "MakeArmor", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 58, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 59, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 60, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 61, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 62, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 63, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 64, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BOULDER", - "reaction_class" : "FLUX", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:IRON" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 79, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 80, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "job" : "MakeArmor", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 81, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 82, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 83, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "item_type" : "GLOVES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - } - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "job" : "MakeGloves", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 84, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "item_type" : "PANTS", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "job" : "MakePants", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 85, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "job" : "MakeArmor", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 72, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "min_dimension" : 150, - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 87, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 88, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 89, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 90, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 91, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 92, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:BISMUTH_BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 79, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 80, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "job" : "MakeArmor", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 81, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 82, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 83, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "item_type" : "GLOVES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "job" : "MakeGloves", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 84, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "item_type" : "PANTS", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "job" : "MakePants", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 85, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "job" : "MakeArmor", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 86, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 87, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 88, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 89, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 90, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 91, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 92, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:BRONZE" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 93, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "item_type" : "SHIELD", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHIELD_SHIELD", - "job" : "MakeShield", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 94, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "job" : "MakeArmor", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 95, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_HELM_HELM", - "item_type" : "HELM", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_HELM_HELM", - "job" : "MakeHelm", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 96, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "item_type" : "SHOES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_SHOES_BOOTS", - "job" : "MakeShoes", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 97, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "item_type" : "GLOVES", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - } - ], - "item_subtype" : "ITEM_GLOVES_GAUNTLETS", - "job" : "MakeGloves", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 98, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "item_type" : "PANTS", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_PANTS_GREAVES", - "job" : "MakePants", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 99, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 20 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "item_type" : "ARMOR", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 20 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 20 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_ARMOR_MAIL_SHIRT", - "item_type" : "ARMOR", - "value" : 5 - } - ], - "item_subtype" : "ITEM_ARMOR_BREASTPLATE", - "job" : "MakeArmor", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 100, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_MACE", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 101, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "item_type" : "WEAPON", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_HAMMER_WAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 102, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SPEAR", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 103, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_SWORD_SHORT", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 104, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 10 - } - ], - "item_subtype" : "ITEM_WEAPON_AXE_BATTLE", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 105, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 10 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_PICK", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 106, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "INORGANIC:COPPER", - "value" : 30 - }, - { - "condition" : "AtLeast", - "item_type" : "BAR", - "material" : "COAL", - "value" : 100 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BRONZE", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:IRON", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:STEEL", - "value" : 30 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:SILVER", - "value" : 5 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:PLATINUM", - "value" : 5 - }, - { - "condition" : "AtMost", - "flags" : - [ - "metal" - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "item_type" : "WEAPON", - "value" : 10 - }, - { - "condition" : "LessThan", - "item_type" : "BAR", - "material" : "INORGANIC:BISMUTH_BRONZE", - "value" : 30 - } - ], - "item_subtype" : "ITEM_WEAPON_CROSSBOW", - "job" : "MakeWeapon", - "material" : "INORGANIC:COPPER" - } -] diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt deleted file mode 100644 index f000b5bd1..000000000 --- a/data/quickfort/aliases-common.txt +++ /dev/null @@ -1,509 +0,0 @@ -# Standard library of aliases for quickfort query mode blueprints. -# -# Please DO NOT EDIT this file directly. It will get overwritten when DFHack -# is updated. Instead, custom aliases should be added to -# dfhack-config/quickfort/aliases.txt -# Custom alias definitions will take precedence over aliases in this file. -# -# Please see -# https://docs.dfhack.org/en/latest/docs/guides/quickfort-alias-guide.html -# or -# hack/docs/docs/guides/quickfort-alias-guide.html -# in your DF installation directory for alias syntax documentation and -# documentation for the aliases in this file. - -################################## -# naming aliases -################################## - -name: {Empty} -givename: !n{name}& -namezone: ^i{givename}^q - - -################################## -# quantum stockpile aliases -################################## - -# Allows the standard stockpile config aliases to also be used to configure -# hauling routes. -enter_sp_config: {enter_sp_config_default} -enter_sp_config_default: s -enter_sp_config_hauling: & - -quantum_enable: {enableanimals}{enablefood}{enablefurniture}{enablestone}{enableammo}{enablecoins}{enablebars}{enablegems}{enablefinishedgoods}{enableleather}{enablecloth}{enablewood}{enableweapons}{enablearmor}{enablesheet} -quantum: {linksonly}{nocontainers}{quantum_enable}{givename} - -stop_name: {Empty} -route_enable: {quantum_enable}{enablecorpses}{enablerefuse} -sp_link: s{move}p{move_back} -sp_links: {sp_link} -quantumstop: ^hrn{name}&sn{stop_name}&&xxx{route_enable enter_sp_config={enter_sp_config_hauling}}{sp_links}^^q -quantumstopfromeast: {quantumstop move={Right} move_back={Left}} -quantumstopfromsouth: {quantumstop move={Down} move_back={Up}} -quantumstopfromwest: {quantumstop move={Left} move_back={Right}} -quantumstopfromnorth: {quantumstop move={Up} move_back={Down}} - - -################################## -# farm plots -################################## - -growlastcropall: a/&b/&c/&d/& -growfirstcropall: a&b&c&d& - - -######################################## -# stockpile utility aliases -######################################## - -linksonly: a -maxbins: V -maxbarrels: R -nobins: C -nobarrels: E -nocontainers: {nobins}{nobarrels} - -give: g{move}& -give2up: {give move={Up 2}} -give2down: {give move={Down 2}} -give2left: {give move={Left 2}} -give2right: {give move={Right 2}} -give10up: {give move={Up 10}} -give10down: {give move={Down 10}} -give10left: {give move={Left 10}} -give10right: {give move={Right 10}} - -togglesequence: &{Down} -togglesequence2: &{Down 2} - -# these aliases use the DFHack "search" plugin to filter the right column -forbidsearch: s{search}&f{Left}{Right} -permitsearch: s{search}&p{Left}{Right} -togglesearch: s{search}&&{Left}{Right} - -masterworkonly: {prefix}{Right}{Up 2}f{Right}{Up 2}&^ -artifactonly: {prefix}{Right}{Up 2}f{Right}{Up}&^ - -togglemasterwork: {prefix}{Right}{Up 2}{Right}{Up 2}&^ -toggleartifact: {prefix}{Right}{Up 2}{Right}{Up}&^ - - -################################## -# animal stockpile adjustments -################################## - -animalsprefix: {enter_sp_config} -enableanimals: {animalsprefix}e^ -disableanimals: {animalsprefix}d^ - -cages: {animalsprefix}bu^ -traps: {animalsprefix}bj^ - -forbidcages: {animalsprefix}u^ -forbidtraps: {animalsprefix}j^ - -permitcages: {forbidcages} -permittraps: {forbidtraps} - - -################################## -# food stockpile adjustments -################################## - -foodprefix: {enter_sp_config}{Down} -enablefood: {foodprefix}e^ -disablefood: {foodprefix}d^ - -preparedfood: {foodprefix}bu^ -unpreparedfish: {foodprefix}b{Right}{Down 2}p^ -plants: {foodprefix}b{Right}{Down 4}p^ -booze: {foodprefix}b{Right}{Down 5}p{Down}p^ -seeds: {foodprefix}b{Right}{Down 9}p^ -dye: {foodprefix}b{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ -tallow: {foodprefix}b{Right}{Down 13}{Right}{permitsearch search=tallow}^ -miscliquid: {foodprefix}b{Right}{Down 18}p^ -wax: {foodprefix}b{Right}{Down 15}{Right}{Down 6}&^ - -forbidpreparedfood: {foodprefix}u^ -forbidunpreparedfish: {foodprefix}{Right}{Down 2}f^ -forbidplants: {foodprefix}{Right}{Down 4}f^ -forbidbooze: {foodprefix}{Right}{Down 5}f{Down}f^ -forbidseeds: {foodprefix}{Right}{Down 9}f^ -forbiddye: {foodprefix}{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ -forbidtallow: {foodprefix}{Right}{Down 13}{Right}{forbidsearch search=tallow}^ -forbidmiscliquid: {foodprefix}{Right}{Down 18}f^ -forbidwax: {foodprefix}{Right}{Down 15}{Right}{Down 6}&^ - -permitpreparedfood: {forbidpreparedfood} -permitunpreparedfish: {foodprefix}{Right}{Down 2}p^ -permitplants: {foodprefix}{Right}{Down 4}p^ -permitbooze: {foodprefix}{Right}{Down 5}p{Down}p^ -permitseeds: {foodprefix}{Right}{Down 9}p^ -permitdye: {forbiddye} -permittallow: {foodprefix}{Right}{Down 13}{Right}{permitsearch search=tallow}^ -permitmiscliquid: {foodprefix}{Right}{Down 18}p^ -permitwax: {forbidwax} - -# the next two aliases are for compatibility with previous implementations of -# Quickfort and are not documented. -# enables everything but seeds -noseeds: {disablefood}{enablefood}{forbidseeds} -# enables all food except for the types listed above -food: {noseeds}{forbidpreparedfood}{forbidunpreparedfish}{forbidplants}{forbidbooze}{forbiddye}{forbidtallow}{forbidmiscliquid} - - -################################## -# furniture stockpile adjustments -################################## - -furnitureprefix: {enter_sp_config}{Down 2} -enablefurniture: {furnitureprefix}e^ -disablefurniture: {furnitureprefix}d^ - -pots: {furnitureprefix}de{Right}f{Right}{Up 5}&^ -bags: {furnitureprefix}de{Right}f{Right}{Up 10}&{Left}{Down}f{Down}f{Down}f{Right}{Down}&{Down 6}&{Down}&{Down 6}&^ -buckets: {furnitureprefix}de{Right}f{Right}{Up 12}&^ -sand: {furnitureprefix}de{Right}f{Right}{Up}&^ - -forbidpots: {furnitureprefix}{Right 2}{Up 5}&^ -forbidbuckets: {furnitureprefix}{Right 2}{Up 12}&^ -forbidsand: {furnitureprefix}{Right 2}{Up}&^ - -permitpots: {forbidpots} -permitbuckets: {forbidbuckets} -permitsand: {forbidsand} - -masterworkfurniture: {masterworkonly prefix={furnitureprefix}} -artifactfurniture: {artifactonly prefix={furnitureprefix}} - -forbidmasterworkfurniture: {togglemasterwork prefix={furnitureprefix}} -forbidartifactfurniture: {toggleartifact prefix={furnitureprefix}} - -permitmasterworkfurniture: {togglemasterwork prefix={furnitureprefix}} -permitartifactfurniture: {toggleartifact prefix={furnitureprefix}} - - -########################################### -# corpses and refuse stockpile adjustments -########################################### - -corpsesprefix: {enter_sp_config}{Down 3} -enablecorpses: {corpsesprefix}e^ -disablecorpses: {corpsesprefix}d{Up}d^ - -refuseprefix: {enter_sp_config}{Down 4} -enablerefuse: {refuseprefix}e^ -disablerefuse: {refuseprefix}d^ - -corpses: {refuseprefix}b{Right}{Down}p^ -rawhides: {refuseprefix}b{Right 2}{Down}&^ -tannedhides: {refuseprefix}b{Right 2}{Down 53}&^ -skulls: {refuseprefix}b{Right}{Down 3}p^ -bones: {refuseprefix}b{Right}{Down 4}p^ -shells: {refuseprefix}b{Right}{Down 5}p^ -teeth: {refuseprefix}b{Right}{Down 6}p^ -horns: {refuseprefix}b{Right}{Down 7}p^ -hair: {refuseprefix}b{Right}{Down 8}p^ -usablehair: {refuseprefix}b{Right}{Down 8}{Right}{togglesearch search=sheep}{togglesearch search=llama}{togglesearch search=alpaca}{togglesearch search=troll}^ -craftrefuse: {skulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permitusablehair} - -forbidcorpses: {refuseprefix}{Right}{Down}f^ -forbidrawhides: {refuseprefix}{Right 2}{Down}&^ -forbidtannedhides: {refuseprefix}{Right 2}{Down 53}&^ -forbidskulls: {refuseprefix}{Right}{Down 3}f^ -forbidbones: {refuseprefix}{Right}{Down 4}f^ -forbidshells: {refuseprefix}{Right}{Down 5}f^ -forbidteeth: {refuseprefix}{Right}{Down 6}f^ -forbidhorns: {refuseprefix}{Right}{Down 7}f^ -forbidhair: {refuseprefix}{Right}{Down 8}f^ -forbidusablehair: {refuseprefix}{Right}{Down 8}{Right}{forbidsearch search=sheep}{forbidsearch search=llama}{forbidsearch search=alpaca}{forbidsearch search=troll}^ -forbidcraftrefuse: {forbidskulls}{forbidbones}{forbidshells}{forbidteeth}{forbidhorns}{forbidusablehair} - -permitcorpses: {refuseprefix}{Right}{Down}p^ -permitrawhides: {forbidrawhides} -permittannedhides: {forbidtannedhides} -permitskulls: {refuseprefix}{Right}{Down 3}p^ -permitbones: {refuseprefix}{Right}{Down 4}p^ -permitshells: {refuseprefix}{Right}{Down 5}p^ -permitteeth: {refuseprefix}{Right}{Down 6}p^ -permithorns: {refuseprefix}{Right}{Down 7}p^ -permithair: {refuseprefix}{Right}{Down 8}p^ -permitusablehair: {refuseprefix}{Right}{Down 8}{Right}{permitsearch search=sheep}{permitsearch search=llama}{permitsearch search=alpaca}{permitsearch search=troll}^ -permitcraftrefuse: {permitskulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permitusablehair} - - -################################## -# stone stockpile adjustments -################################## - -stoneprefix: {enter_sp_config}{Down 5} -enablestone: {stoneprefix}e^ -disablestone: {stoneprefix}d^ - -metal: {stoneprefix}b{Right}p^ -iron: {stoneprefix}b{Right}{Right}&{Down}&{Down 13}&^ -economic: {stoneprefix}b{Right}{Down}p^ -flux: {stoneprefix}b{Right}{Down}{Right}{togglesequence 4}{Down 4}&^ -plaster: {stoneprefix}b{Right}{Down}{Right}{Down 6}&{Down 3}{togglesequence 3}^ -coalproducing: {stoneprefix}b{Right}{Down}{Right}{Down 4}{togglesequence 2}^ -otherstone: {stoneprefix}b{Right}{Down 2}p^ -bauxite: {stoneprefix}b{Right}{Down 2}{Right}{Down 42}&^ -clay: {stoneprefix}b{Right}{Down 3}p^ - -forbidmetal: {stoneprefix}{Right}f^ -forbidiron: {stoneprefix}{Right}{Right}&{Down}&{Down 13}&^ -forbideconomic: {stoneprefix}{Right}{Down}f^ -forbidflux: {stoneprefix}{Right}{Down}{Right}{togglesequence 4}{Down 4}&^ -forbidplaster: {stoneprefix}{Right}{Down}{Right}{Down 6}&{Down 3}{togglesequence 3}^ -forbidcoalproducing: {stoneprefix}{Right}{Down}{Right}{Down 4}{togglesequence 2}^ -forbidotherstone: {stoneprefix}{Right}{Down 2}f^ -forbidbauxite: {stoneprefix}{Right}{Down 2}{Right}{Down 42}&^ -forbidclay: {stoneprefix}{Right}{Down 3}f^ - -permitmetal: {stoneprefix}{Right}p^ -permitiron: {forbidiron} -permiteconomic: {stoneprefix}{Right}{Down}p^ -permitflux: {forbidflux} -permitplaster: {forbidplaster} -permitcoalproducing: {forbidcoalproducing} -permitotherstone: {stoneprefix}{Right}{Down 2}p^ -permitbauxite: {forbidbauxite} -permitclay: {stoneprefix}{Right}{Down 3}p^ - - -################################## -# ammo stockpile adjustments -################################## - -ammoprefix: {enter_sp_config}{Down 6} -enableammo: {ammoprefix}e^ -disableammo: {ammoprefix}d^ - -bolts: {ammoprefix}a{Right 2}f&^ - -forbidmetalbolts: {ammoprefix}{Right}{Down}f^ -forbidwoodenbolts: {ammoprefix}{Right}{Down 2}{Right}&^ -forbidbonebolts: {ammoprefix}{Right}{Down 2}{Right}{Down}&^ - -masterworkammo: {masterworkonly prefix={ammoprefix}} -artifactammo: {artifactonly prefix={ammoprefix}} - -forbidmasterworkammo: {togglemasterwork prefix={ammoprefix}} -forbidartifactammo: {toggleartifact prefix={ammoprefix}} - -permitmasterworkammo: {togglemasterwork prefix={ammoprefix}} -permitartifactammo: {toggleartifact prefix={ammoprefix}} - - -################################## -# bar stockpile adjustments -################################## - -barsprefix: {enter_sp_config}{Down 8} -enablebars: {barsprefix}e^ -disablebars: {barsprefix}d^ - -bars: {barsprefix}b{Right}p{Down}p^ -metalbars: {barsprefix}b{Right}p^ -ironbars: {barsprefix}b{Right 2}&^ -steelbars: {barsprefix}b{Right 2}{Down 8}&^ -pigironbars: {barsprefix}b{Right 2}{Down 9}&^ -otherbars: {barsprefix}b{Right}{Down}p^ -coal: {barsprefix}b{Right}{Down}{Right}&^ -potash: {barsprefix}b{Right}{Down}{Right}{Down}&^ -ash: {barsprefix}b{Right}{Down}{Right}{Down 2}&^ -pearlash: {barsprefix}b{Right}{Down}{Right}{Down 3}&^ -soap: {barsprefix}b{Right}{Down}{Right}{Down 4}&^ -blocks: {barsprefix}b{Down 2}p{Down}p{Down}p^ - -forbidbars: {barsprefix}{Right}f{Down}f^ -forbidmetalbars: {barsprefix}{Right}f^ -forbidironbars: {barsprefix}{Right 2}&^ -forbidsteelbars: {barsprefix}{Right 2}{Down 8}&^ -forbidpigironbars: {barsprefix}{Right 2}{Down 9}&^ -forbidotherbars: {barsprefix}{Right}{Down}f^ -forbidcoal: {barsprefix}{Right}{Down}{Right}&^ -forbidpotash: {barsprefix}{Right}{Down}{Right}{Down}&^ -forbidash: {barsprefix}{Right}{Down}{Right}{Down 2}&^ -forbidpearlash: {barsprefix}{Right}{Down}{Right}{Down 3}&^ -forbidsoap: {barsprefix}{Right}{Down}{Right}{Down 4}&^ -forbidblocks: {barsprefix}{Down 2}f{Down}f{Down}f^ - - -################################## -# gem stockpile adjustments -################################## - -gemsprefix: {enter_sp_config}{Down 9} -enablegems: {gemsprefix}e^ -disablegems: {gemsprefix}d^ - -roughgems: {gemsprefix}b{Right}p^ -roughglass: {gemsprefix}b{Right}{Down}p^ -cutgems: {gemsprefix}b{Right}{Down 2}p^ -cutglass: {gemsprefix}b{Right}{Down 3}p^ -cutstone: {gemsprefix}b{Right}{Down 4}p^ - -forbidroughgems: {gemsprefix}{Right}f^ -forbidroughglass: {gemsprefix}{Right}{Down}f^ -forbidcutgems: {gemsprefix}{Right}{Down 2}f^ -forbidcutglass: {gemsprefix}{Right}{Down 3}f^ -forbidcutstone: {gemsprefix}{Right}{Down 4}f^ - - -####################################### -# finished goods stockpile adjustments -####################################### - -finishedgoodsprefix: {enter_sp_config}{Down 10} -enablefinishedgoods: {finishedgoodsprefix}e^ -disablefinishedgoods: {finishedgoodsprefix}d^ - -crafts: {finishedgoodsprefix}{Right}f{Right}{Down 9}{togglesequence 9}^ -goblets: {finishedgoodsprefix}{Right}f{Right}{Down 2}&^ -jugs: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down 2}f{Down}f{Down}f^ -stonetools: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down 2}f{Down}f{Down}f^ -woodentools: {finishedgoodsprefix}{Right}f{Right}{Up 2}&{Left}{Down}f{Down}f{Down}f{Down}f{Right}&^ - -forbidcrafts: {finishedgoodsprefix}{Right 2}{Down 9}{togglesequence 9}^ -forbidgoblets: {finishedgoodsprefix}{Right 2}{Down 2}&^ - -permitcrafts: {forbidcrafts} -permitgoblets: {forbidgoblets} - -masterworkfinishedgoods: {masterworkonly prefix={finishedgoodsprefix}} -artifactfinishedgoods: {artifactonly prefix={finishedgoodsprefix}} - -forbidmasterworkfinishedgoods: {togglemasterwork prefix={finishedgoodsprefix}} -forbidartifactfinishedgoods: {toggleartifact prefix={finishedgoodsprefix}} - -permitmasterworkfinishedgoods: {togglemasterwork prefix={finishedgoodsprefix}} -permitartifactfinishedgoods: {toggleartifact prefix={finishedgoodsprefix}} - - -################################## -# cloth -################################## - -clothprefix: {enter_sp_config}{Down 12} -enablecloth: {clothprefix}e^ -disablecloth: {clothprefix}d^ - -thread: {clothprefix}b{Right}p{Down}p{Down}p^ -adamantinethread: {clothprefix}b{Right}{Down 3}p^ -cloth: {clothprefix}b{Right}{Down 4}p{Down}p{Down}p^ -adamantinecloth: {clothprefix}b{Right}{Up}p^ - -forbidthread: {clothprefix}{Right}f{Down}f{Down}f^ -forbidadamantinethread: {clothprefix}{Right}{Down 3}f^ -forbidcloth: {clothprefix}{Right}{Down 4}f{Down}f{Down}f^ -forbidadamantinecloth: {clothprefix}{Right}{Up}f^ - -permitthread: {clothprefix}{Right}p{Down}p{Down}p^ -permitadamantinethread: {clothprefix}{Right}{Down 3}p^ -permitcloth: {clothprefix}{Right}{Down 4}p{Down}p{Down}p^ -permitadamantinecloth: {clothprefix}{Right}{Up}p^ - -################################## -# weapon stockpile adjustments -################################## - -weaponsprefix: {enter_sp_config}{Down 14} -enableweapons: {weaponsprefix}e^ -disableweapons: {weaponsprefix}d^ - -metalweapons: {forbidtrapcomponents}{forbidstoneweapons}{forbidotherweapons} -ironweapons: {metalweapons}{forbidmetalweapons}{permitironweapons} -bronzeweapons: {metalweapons}{forbidmetalweapons}{permitbronzeweapons} -copperweapons: {metalweapons}{forbidmetalweapons}{permitcopperweapons} -steelweapons: {metalweapons}{forbidmetalweapons}{permitsteelweapons} - -forbidweapons: {weaponsprefix}{Right}f^ -forbidtrapcomponents: {weaponsprefix}{Right}{Down}f^ -forbidmetalweapons: {weaponsprefix}{Right}{Down 2}f^ -forbidstoneweapons: {weaponsprefix}{Right}{Down 3}f^ -forbidotherweapons: {weaponsprefix}{Right}{Down 4}f^ -forbidironweapons: {weaponsprefix}{Right}{Down 2}{Right}&^ -forbidbronzeweapons: {weaponsprefix}{Right}{Down 2}{Right}{Down 6}&^ -forbidcopperweapons: {weaponsprefix}{Right}{Down 2}{Right}{Down 3}&^ -forbidsteelweapons: {weaponsprefix}{Right}{Down 2}{Right}{Down 8}&^ - -permitweapons: {weaponsprefix}{Right}p^ -permittrapcomponents: {weaponsprefix}{Right}{Down}p^ -permitmetalweapons: {weaponsprefix}{Right}{Down 2}p^ -permitstoneweapons: {weaponsprefix}{Right}{Down 3}p^ -permitotherweapons: {weaponsprefix}{Right}{Down 4}p^ -permitironweapons: {forbidironweapons} -permitbronzeweapons: {forbidbronzeweapons} -permitcopperweapons: {forbidcopperweapons} -permitsteelweapons: {forbidsteelweapons} - -masterworkweapons: {masterworkonly prefix={weaponsprefix}} -artifactweapons: {artifactonly prefix={weaponsprefix}} - -forbidmasterworkweapons: {togglemasterwork prefix={weaponsprefix}} -forbidartifactweapons: {toggleartifact prefix={weaponsprefix}} - -permitmasterworkweapons: {togglemasterwork prefix={weaponsprefix}} -permitartifactweapons: {toggleartifact prefix={weaponsprefix}} - - -################################## -# armor stockpile adjustments -################################## - -armorprefix: {enter_sp_config}{Down 15} -enablearmor: {armorprefix}e^ -disablearmor: {armorprefix}d^ - -metalarmor: {forbidotherarmor} -otherarmor: {forbidmetalarmor} -ironarmor: {metalarmor}{forbidmetalarmor}{permitironarmor} -bronzearmor: {metalarmor}{forbidmetalarmor}{permitbronzearmor} -copperarmor: {metalarmor}{forbidmetalarmor}{permitcopperarmor} -steelarmor: {metalarmor}{forbidmetalarmor}{permitsteelarmor} - -forbidmetalarmor: {armorprefix}{Right}{Down 6}f^ -forbidotherarmor: {armorprefix}{Right}{Down 7}f^ -forbidironarmor: {armorprefix}{Right}{Down 6}{Right}&^ -forbidbronzearmor: {armorprefix}{Right}{Down 6}{Right}{Down 6}&^ -forbidcopperarmor: {armorprefix}{Right}{Down 6}{Right}{Down 3}&^ -forbidsteelarmor: {armorprefix}{Right}{Down 6}{Right}{Down 8}&^ - -permitmetalarmor: {armorprefix}{Right}{Down 6}p^ -permitotherarmor: {armorprefix}{Right}{Down 7}p^ -permitironarmor: {forbidironarmor} -permitbronzearmor: {forbidbronzearmor} -permitcopperarmor: {forbidcopperarmor} -permitsteelarmor: {forbidsteelarmor} - -masterworkarmor: {masterworkonly prefix={armorprefix}} -artifactarmor: {artifactonly prefix={armorprefix}} - -forbidmasterworkarmor: {togglemasterwork prefix={armorprefix}} -forbidartifactarmor: {toggleartifact prefix={armorprefix}} - -permitmasterworkarmor: {togglemasterwork prefix={armorprefix}} -permitartifactarmor: {toggleartifact prefix={armorprefix}} - - -################################## -# others -################################## - -coinsprefix: {enter_sp_config}{Down 7} -enablecoins: {coinsprefix}e^ -disablecoins: {coinsprefix}d^ - -leatherprefix: {enter_sp_config}{Down 11} -enableleather: {leatherprefix}e^ -disableleather: {leatherprefix}d^ - -woodprefix: {enter_sp_config}{Down 13} -enablewood: {woodprefix}e^ -disablewood: {woodprefix}d^ - -sheetprefix: {enter_sp_config}{Down 16} -enablesheet: {sheetprefix}e^ -disablesheet: {sheetprefix}d^ diff --git a/data/stockpiles/adamantinecloth.dfstock b/data/stockpiles/adamantinecloth.dfstock new file mode 100644 index 000000000..38781d313 --- /dev/null +++ b/data/stockpiles/adamantinecloth.dfstock @@ -0,0 +1 @@ +rBINORGANIC:ADAMANTINE \ No newline at end of file diff --git a/data/stockpiles/adamantinethread.dfstock b/data/stockpiles/adamantinethread.dfstock new file mode 100644 index 000000000..2231b389f --- /dev/null +++ b/data/stockpiles/adamantinethread.dfstock @@ -0,0 +1 @@ +r"INORGANIC:ADAMANTINE \ No newline at end of file diff --git a/data/stockpiles/adamantineweapons.dfstock b/data/stockpiles/adamantineweapons.dfstock new file mode 100644 index 000000000..6236196f0 Binary files /dev/null and b/data/stockpiles/adamantineweapons.dfstock differ diff --git a/data/stockpiles/ash.dfstock b/data/stockpiles/ash.dfstock new file mode 100644 index 000000000..d313e0096 --- /dev/null +++ b/data/stockpiles/ash.dfstock @@ -0,0 +1,2 @@ +R +ASH \ No newline at end of file diff --git a/data/stockpiles/bags.dfstock b/data/stockpiles/bags.dfstock new file mode 100644 index 000000000..3bfa1a1a1 --- /dev/null +++ b/data/stockpiles/bags.dfstock @@ -0,0 +1,2 @@ + +BAG \ No newline at end of file diff --git a/data/stockpiles/bars.dfstock b/data/stockpiles/bars.dfstock new file mode 100644 index 000000000..c292dcf56 --- /dev/null +++ b/data/stockpiles/bars.dfstock @@ -0,0 +1,6 @@ +Rõ +COAL +POTASH +ASH +PEARLASH +SOAPINORGANIC:IRONINORGANIC:SILVERINORGANIC:COPPERINORGANIC:NICKELINORGANIC:ZINCINORGANIC:BRONZEINORGANIC:BRASSINORGANIC:STEELINORGANIC:PIG_IRONINORGANIC:PLATINUMINORGANIC:ELECTRUM INORGANIC:TININORGANIC:PEWTER_FINEINORGANIC:PEWTER_TRIFLEINORGANIC:PEWTER_LAYINORGANIC:LEADINORGANIC:ALUMINUMINORGANIC:NICKEL_SILVERINORGANIC:BILLONINORGANIC:STERLING_SILVERINORGANIC:BLACK_BRONZEINORGANIC:ROSE_GOLDINORGANIC:BISMUTHINORGANIC:BISMUTH_BRONZEINORGANIC:ADAMANTINEINORGANIC:GOLDINORGANIC:DIVINE_1INORGANIC:DIVINE_3INORGANIC:DIVINE_5INORGANIC:DIVINE_7INORGANIC:DIVINE_9INORGANIC:DIVINE_11INORGANIC:DIVINE_13INORGANIC:DIVINE_15INORGANIC:DIVINE_17INORGANIC:DIVINE_19 \ No newline at end of file diff --git a/data/stockpiles/bauxite.dfstock b/data/stockpiles/bauxite.dfstock new file mode 100644 index 000000000..83a49cb5b --- /dev/null +++ b/data/stockpiles/bauxite.dfstock @@ -0,0 +1,2 @@ +2 +INORGANIC:BAUXITE \ No newline at end of file diff --git a/data/stockpiles/blocks.dfstock b/data/stockpiles/blocks.dfstock new file mode 100644 index 000000000..cd08d9333 --- /dev/null +++ b/data/stockpiles/blocks.dfstock @@ -0,0 +1 @@ +Rã GREEN_GLASS CLEAR_GLASS CRYSTAL_GLASSWOOD"INORGANIC:IRON"INORGANIC:SILVER"INORGANIC:COPPER"INORGANIC:NICKEL"INORGANIC:ZINC"INORGANIC:BRONZE"INORGANIC:BRASS"INORGANIC:STEEL"INORGANIC:PIG_IRON"INORGANIC:PLATINUM"INORGANIC:ELECTRUM" INORGANIC:TIN"INORGANIC:PEWTER_FINE"INORGANIC:PEWTER_TRIFLE"INORGANIC:PEWTER_LAY"INORGANIC:LEAD"INORGANIC:ALUMINUM"INORGANIC:NICKEL_SILVER"INORGANIC:BILLON"INORGANIC:STERLING_SILVER"INORGANIC:BLACK_BRONZE"INORGANIC:ROSE_GOLD"INORGANIC:BISMUTH"INORGANIC:BISMUTH_BRONZE"INORGANIC:ADAMANTINE"INORGANIC:PLASTER"INORGANIC:CERAMIC_EARTHENWARE"INORGANIC:CERAMIC_STONEWARE"INORGANIC:CERAMIC_PORCELAIN"INORGANIC:ASH_GLAZE"INORGANIC:TIN_GLAZE"INORGANIC:SANDSTONE"INORGANIC:SILTSTONE"INORGANIC:MUDSTONE"INORGANIC:SHALE"INORGANIC:CLAYSTONE"INORGANIC:ROCK_SALT"INORGANIC:LIMESTONE"INORGANIC:CONGLOMERATE"INORGANIC:DOLOMITE"INORGANIC:CHERT"INORGANIC:CHALK"INORGANIC:GRANITE"INORGANIC:DIORITE"INORGANIC:GABBRO"INORGANIC:RHYOLITE"INORGANIC:BASALT"INORGANIC:ANDESITE"INORGANIC:DACITE"INORGANIC:OBSIDIAN"INORGANIC:QUARTZITE"INORGANIC:SLATE"INORGANIC:PHYLLITE"INORGANIC:SCHIST"INORGANIC:GNEISS"INORGANIC:MARBLE"INORGANIC:HEMATITE"INORGANIC:LIMONITE"INORGANIC:GARNIERITE"INORGANIC:NATIVE_GOLD"INORGANIC:NATIVE_SILVER"INORGANIC:NATIVE_COPPER"INORGANIC:MALACHITE"INORGANIC:GALENA"INORGANIC:SPHALERITE"INORGANIC:CASSITERITE"INORGANIC:COAL_BITUMINOUS"INORGANIC:LIGNITE"INORGANIC:NATIVE_PLATINUM"INORGANIC:CINNABAR"INORGANIC:COBALTITE"INORGANIC:TETRAHEDRITE"INORGANIC:HORN_SILVER"INORGANIC:GYPSUM"INORGANIC:TALC" INORGANIC:JET"INORGANIC:PUDDINGSTONE"INORGANIC:PETRIFIED_WOOD"INORGANIC:GRAPHITE"INORGANIC:BRIMSTONE"INORGANIC:KIMBERLITE"INORGANIC:BISMUTHINITE"INORGANIC:REALGAR"INORGANIC:ORPIMENT"INORGANIC:STIBNITE"INORGANIC:MARCASITE"INORGANIC:SYLVITE"INORGANIC:CRYOLITE"INORGANIC:PERICLASE"INORGANIC:ILMENITE"INORGANIC:RUTILE"INORGANIC:MAGNETITE"INORGANIC:CHROMITE"INORGANIC:PYROLUSITE"INORGANIC:PITCHBLENDE"INORGANIC:BAUXITE"INORGANIC:NATIVE_ALUMINUM"INORGANIC:BORAX"INORGANIC:OLIVINE"INORGANIC:HORNBLENDE"INORGANIC:KAOLINITE"INORGANIC:SERPENTINE"INORGANIC:ORTHOCLASE"INORGANIC:MICROCLINE"INORGANIC:MICA"INORGANIC:CALCITE"INORGANIC:SALTPETER"INORGANIC:ALABASTER"INORGANIC:SELENITE"INORGANIC:SATINSPAR"INORGANIC:ANHYDRITE"INORGANIC:ALUNITE"INORGANIC:RAW_ADAMANTINE"INORGANIC:SLADE" INORGANIC:BROMS_CLEAN_CORPSEDUST"INORGANIC:GOLD"INORGANIC:DIVINE_1"INORGANIC:DIVINE_3"INORGANIC:DIVINE_5"INORGANIC:DIVINE_7"INORGANIC:DIVINE_9"INORGANIC:DIVINE_11"INORGANIC:DIVINE_13"INORGANIC:DIVINE_15"INORGANIC:DIVINE_17"INORGANIC:DIVINE_19 \ No newline at end of file diff --git a/data/stockpiles/bolts.dfstock b/data/stockpiles/bolts.dfstock new file mode 100644 index 000000000..2e5bd4b4b --- /dev/null +++ b/data/stockpiles/bolts.dfstock @@ -0,0 +1,2 @@ +B +AMMO:ITEM_AMMO_BOLTS \ No newline at end of file diff --git a/data/stockpiles/boneammo.dfstock b/data/stockpiles/boneammo.dfstock new file mode 100644 index 000000000..909c67d1d --- /dev/null +++ b/data/stockpiles/boneammo.dfstock @@ -0,0 +1 @@ +BBONE \ No newline at end of file diff --git a/data/stockpiles/booze.dfstock b/data/stockpiles/booze.dfstock new file mode 100644 index 000000000..736560f98 Binary files /dev/null and b/data/stockpiles/booze.dfstock differ diff --git a/data/stockpiles/bronzearmor.dfstock b/data/stockpiles/bronzearmor.dfstock new file mode 100644 index 000000000..22dda4155 Binary files /dev/null and b/data/stockpiles/bronzearmor.dfstock differ diff --git a/data/stockpiles/bronzeweapons.dfstock b/data/stockpiles/bronzeweapons.dfstock new file mode 100644 index 000000000..54cd87d11 Binary files /dev/null and b/data/stockpiles/bronzeweapons.dfstock differ diff --git a/data/stockpiles/buckets.dfstock b/data/stockpiles/buckets.dfstock new file mode 100644 index 000000000..cb6b8c4bb --- /dev/null +++ b/data/stockpiles/buckets.dfstock @@ -0,0 +1,2 @@ + +BUCKET \ No newline at end of file diff --git a/data/stockpiles/cages.dfstock b/data/stockpiles/cages.dfstock new file mode 100644 index 000000000..0ca677985 Binary files /dev/null and b/data/stockpiles/cages.dfstock differ diff --git a/data/stockpiles/cat_ammo.dfstock b/data/stockpiles/cat_ammo.dfstock new file mode 100644 index 000000000..55a20e7e6 --- /dev/null +++ b/data/stockpiles/cat_ammo.dfstock @@ -0,0 +1 @@ +B0 \ No newline at end of file diff --git a/data/stockpiles/cat_animals.dfstock b/data/stockpiles/cat_animals.dfstock new file mode 100644 index 000000000..9796e4535 --- /dev/null +++ b/data/stockpiles/cat_animals.dfstock @@ -0,0 +1,2 @@ + +  \ No newline at end of file diff --git a/data/stockpiles/cat_armor.dfstock b/data/stockpiles/cat_armor.dfstock new file mode 100644 index 000000000..54a2d54ee --- /dev/null +++ b/data/stockpiles/cat_armor.dfstock @@ -0,0 +1 @@ +Šh \ No newline at end of file diff --git a/data/stockpiles/cat_bars_blocks.dfstock b/data/stockpiles/cat_bars_blocks.dfstock new file mode 100644 index 000000000..b7ae54bb1 --- /dev/null +++ b/data/stockpiles/cat_bars_blocks.dfstock @@ -0,0 +1 @@ +R( \ No newline at end of file diff --git a/data/stockpiles/cat_cloth.dfstock b/data/stockpiles/cat_cloth.dfstock new file mode 100644 index 000000000..d88106c6b --- /dev/null +++ b/data/stockpiles/cat_cloth.dfstock @@ -0,0 +1 @@ +rH \ No newline at end of file diff --git a/data/stockpiles/cat_coins.dfstock b/data/stockpiles/cat_coins.dfstock new file mode 100644 index 000000000..a48133ac4 --- /dev/null +++ b/data/stockpiles/cat_coins.dfstock @@ -0,0 +1 @@ +J \ No newline at end of file diff --git a/data/stockpiles/cat_corpses.dfstock b/data/stockpiles/cat_corpses.dfstock new file mode 100644 index 000000000..f4edc7ff8 --- /dev/null +++ b/data/stockpiles/cat_corpses.dfstock @@ -0,0 +1 @@ +Ê \ No newline at end of file diff --git a/data/stockpiles/cat_finished_goods.dfstock b/data/stockpiles/cat_finished_goods.dfstock new file mode 100644 index 000000000..68a2d9053 --- /dev/null +++ b/data/stockpiles/cat_finished_goods.dfstock @@ -0,0 +1 @@ +b0 \ No newline at end of file diff --git a/data/stockpiles/cat_food.dfstock b/data/stockpiles/cat_food.dfstock new file mode 100644 index 000000000..32829f677 --- /dev/null +++ b/data/stockpiles/cat_food.dfstock @@ -0,0 +1 @@ +¨ \ No newline at end of file diff --git a/data/stockpiles/cat_furniture.dfstock b/data/stockpiles/cat_furniture.dfstock new file mode 100644 index 000000000..fdec022ed --- /dev/null +++ b/data/stockpiles/cat_furniture.dfstock @@ -0,0 +1 @@ +8 \ No newline at end of file diff --git a/data/stockpiles/cat_gems.dfstock b/data/stockpiles/cat_gems.dfstock new file mode 100644 index 000000000..8a6483e26 --- /dev/null +++ b/data/stockpiles/cat_gems.dfstock @@ -0,0 +1 @@ +Z( \ No newline at end of file diff --git a/data/stockpiles/cat_leather.dfstock b/data/stockpiles/cat_leather.dfstock new file mode 100644 index 000000000..357287897 --- /dev/null +++ b/data/stockpiles/cat_leather.dfstock @@ -0,0 +1 @@ +j \ No newline at end of file diff --git a/data/stockpiles/cat_refuse.dfstock b/data/stockpiles/cat_refuse.dfstock new file mode 100644 index 000000000..a6219a81a --- /dev/null +++ b/data/stockpiles/cat_refuse.dfstock @@ -0,0 +1 @@ +*` \ No newline at end of file diff --git a/data/stockpiles/cat_sheets.dfstock b/data/stockpiles/cat_sheets.dfstock new file mode 100644 index 000000000..0230086d8 --- /dev/null +++ b/data/stockpiles/cat_sheets.dfstock @@ -0,0 +1 @@ +Ò \ No newline at end of file diff --git a/data/stockpiles/cat_stone.dfstock b/data/stockpiles/cat_stone.dfstock new file mode 100644 index 000000000..29bcd227e --- /dev/null +++ b/data/stockpiles/cat_stone.dfstock @@ -0,0 +1 @@ +2 \ No newline at end of file diff --git a/data/stockpiles/cat_weapons.dfstock b/data/stockpiles/cat_weapons.dfstock new file mode 100644 index 000000000..8f46ee619 --- /dev/null +++ b/data/stockpiles/cat_weapons.dfstock @@ -0,0 +1 @@ +‚H \ No newline at end of file diff --git a/data/stockpiles/cat_wood.dfstock b/data/stockpiles/cat_wood.dfstock new file mode 100644 index 000000000..5613a0f07 --- /dev/null +++ b/data/stockpiles/cat_wood.dfstock @@ -0,0 +1 @@ +z \ No newline at end of file diff --git a/data/stockpiles/clay.dfstock b/data/stockpiles/clay.dfstock new file mode 100644 index 000000000..e01c7abaf --- /dev/null +++ b/data/stockpiles/clay.dfstock @@ -0,0 +1,6 @@ +2f +INORGANIC:CLAY +INORGANIC:SILTY_CLAY +INORGANIC:SANDY_CLAY +INORGANIC:CLAY_LOAM +INORGANIC:FIRE_CLAY \ No newline at end of file diff --git a/data/stockpiles/cloth.dfstock b/data/stockpiles/cloth.dfstock new file mode 100644 index 000000000..26c4c52f3 --- /dev/null +++ b/data/stockpiles/cloth.dfstock @@ -0,0 +1 @@ +rŽ'*"CREATURE:SPIDER_BROWN_RECLUSE:SILK*&CREATURE:BROWN_RECLUSE_SPIDER_MAN:SILK*(CREATURE:GIANT_BROWN_RECLUSE_SPIDER:SILK*CREATURE:SPIDER_PHANTOM:SILK*CREATURE:SPIDER_CAVE_GIANT:SILK*CREATURE:SPIDER_CAVE:SILK*INORGANIC:DIVINE_2*INORGANIC:DIVINE_4*INORGANIC:DIVINE_6*INORGANIC:DIVINE_8*INORGANIC:DIVINE_10*INORGANIC:DIVINE_12*INORGANIC:DIVINE_14*INORGANIC:DIVINE_16*INORGANIC:DIVINE_18*INORGANIC:DIVINE_20*CREATURE:FORGOTTEN_BEAST_4:SILK* CREATURE:FORGOTTEN_BEAST_22:SILK* CREATURE:FORGOTTEN_BEAST_33:SILK* CREATURE:FORGOTTEN_BEAST_51:SILK* CREATURE:FORGOTTEN_BEAST_56:SILK* CREATURE:FORGOTTEN_BEAST_58:SILK* CREATURE:FORGOTTEN_BEAST_62:SILK* CREATURE:FORGOTTEN_BEAST_74:SILK* CREATURE:FORGOTTEN_BEAST_80:SILK* CREATURE:FORGOTTEN_BEAST_87:SILK* CREATURE:FORGOTTEN_BEAST_90:SILK*!CREATURE:FORGOTTEN_BEAST_106:SILK*!CREATURE:FORGOTTEN_BEAST_109:SILK*!CREATURE:FORGOTTEN_BEAST_127:SILK*!CREATURE:FORGOTTEN_BEAST_131:SILK*!CREATURE:FORGOTTEN_BEAST_143:SILK*!CREATURE:FORGOTTEN_BEAST_145:SILK*!CREATURE:FORGOTTEN_BEAST_163:SILK*!CREATURE:FORGOTTEN_BEAST_177:SILK*!CREATURE:FORGOTTEN_BEAST_189:SILK*!CREATURE:FORGOTTEN_BEAST_194:SILK*!CREATURE:FORGOTTEN_BEAST_197:SILK*!CREATURE:FORGOTTEN_BEAST_200:SILK*!CREATURE:FORGOTTEN_BEAST_201:SILK*!CREATURE:FORGOTTEN_BEAST_202:SILK*!CREATURE:FORGOTTEN_BEAST_217:SILK*!CREATURE:FORGOTTEN_BEAST_224:SILK*!CREATURE:FORGOTTEN_BEAST_234:SILK*!CREATURE:FORGOTTEN_BEAST_242:SILK*!CREATURE:FORGOTTEN_BEAST_246:SILK*!CREATURE:FORGOTTEN_BEAST_250:SILK*!CREATURE:FORGOTTEN_BEAST_270:SILK*!CREATURE:FORGOTTEN_BEAST_271:SILK*!CREATURE:FORGOTTEN_BEAST_286:SILK*!CREATURE:FORGOTTEN_BEAST_291:SILK*!CREATURE:FORGOTTEN_BEAST_296:SILK*!CREATURE:FORGOTTEN_BEAST_329:SILK*!CREATURE:FORGOTTEN_BEAST_350:SILK*!CREATURE:FORGOTTEN_BEAST_351:SILK*!CREATURE:FORGOTTEN_BEAST_359:SILK*!CREATURE:FORGOTTEN_BEAST_365:SILK*!CREATURE:FORGOTTEN_BEAST_375:SILK*!CREATURE:FORGOTTEN_BEAST_376:SILK*!CREATURE:FORGOTTEN_BEAST_377:SILK*!CREATURE:FORGOTTEN_BEAST_383:SILK*!CREATURE:FORGOTTEN_BEAST_387:SILK*!CREATURE:FORGOTTEN_BEAST_398:SILK*!CREATURE:FORGOTTEN_BEAST_400:SILK*!CREATURE:FORGOTTEN_BEAST_406:SILK*!CREATURE:FORGOTTEN_BEAST_410:SILK*!CREATURE:FORGOTTEN_BEAST_417:SILK*!CREATURE:FORGOTTEN_BEAST_428:SILK*!CREATURE:FORGOTTEN_BEAST_429:SILK*!CREATURE:FORGOTTEN_BEAST_432:SILK*!CREATURE:FORGOTTEN_BEAST_442:SILK*!CREATURE:FORGOTTEN_BEAST_448:SILK*!CREATURE:FORGOTTEN_BEAST_452:SILK*!CREATURE:FORGOTTEN_BEAST_457:SILK*!CREATURE:FORGOTTEN_BEAST_468:SILK*!CREATURE:FORGOTTEN_BEAST_472:SILK*!CREATURE:FORGOTTEN_BEAST_479:SILK*!CREATURE:FORGOTTEN_BEAST_482:SILK*!CREATURE:FORGOTTEN_BEAST_492:SILK*!CREATURE:FORGOTTEN_BEAST_504:SILK*!CREATURE:FORGOTTEN_BEAST_512:SILK*!CREATURE:FORGOTTEN_BEAST_519:SILK*!CREATURE:FORGOTTEN_BEAST_537:SILK*!CREATURE:FORGOTTEN_BEAST_550:SILK*!CREATURE:FORGOTTEN_BEAST_556:SILK*!CREATURE:FORGOTTEN_BEAST_557:SILK*!CREATURE:FORGOTTEN_BEAST_563:SILK*!CREATURE:FORGOTTEN_BEAST_573:SILK*!CREATURE:FORGOTTEN_BEAST_583:SILK*!CREATURE:FORGOTTEN_BEAST_588:SILK*!CREATURE:FORGOTTEN_BEAST_599:SILK*!CREATURE:FORGOTTEN_BEAST_602:SILK*!CREATURE:FORGOTTEN_BEAST_605:SILK*!CREATURE:FORGOTTEN_BEAST_607:SILK*!CREATURE:FORGOTTEN_BEAST_611:SILK*!CREATURE:FORGOTTEN_BEAST_618:SILK*!CREATURE:FORGOTTEN_BEAST_621:SILK*!CREATURE:FORGOTTEN_BEAST_627:SILK*!CREATURE:FORGOTTEN_BEAST_638:SILK*!CREATURE:FORGOTTEN_BEAST_639:SILK*!CREATURE:FORGOTTEN_BEAST_651:SILK*!CREATURE:FORGOTTEN_BEAST_659:SILK*!CREATURE:FORGOTTEN_BEAST_670:SILK*!CREATURE:FORGOTTEN_BEAST_686:SILK*!CREATURE:FORGOTTEN_BEAST_692:SILK*!CREATURE:FORGOTTEN_BEAST_695:SILK*!CREATURE:FORGOTTEN_BEAST_697:SILK*!CREATURE:FORGOTTEN_BEAST_699:SILK*!CREATURE:FORGOTTEN_BEAST_707:SILK*!CREATURE:FORGOTTEN_BEAST_713:SILK*!CREATURE:FORGOTTEN_BEAST_723:SILK*!CREATURE:FORGOTTEN_BEAST_733:SILK*!CREATURE:FORGOTTEN_BEAST_740:SILK*!CREATURE:FORGOTTEN_BEAST_741:SILK*!CREATURE:FORGOTTEN_BEAST_747:SILK*!CREATURE:FORGOTTEN_BEAST_758:SILK*!CREATURE:FORGOTTEN_BEAST_766:SILK*!CREATURE:FORGOTTEN_BEAST_769:SILK*!CREATURE:FORGOTTEN_BEAST_771:SILK*!CREATURE:FORGOTTEN_BEAST_790:SILK*!CREATURE:FORGOTTEN_BEAST_794:SILK*!CREATURE:FORGOTTEN_BEAST_826:SILK*!CREATURE:FORGOTTEN_BEAST_827:SILK*!CREATURE:FORGOTTEN_BEAST_837:SILK*!CREATURE:FORGOTTEN_BEAST_839:SILK*!CREATURE:FORGOTTEN_BEAST_846:SILK*!CREATURE:FORGOTTEN_BEAST_847:SILK*!CREATURE:FORGOTTEN_BEAST_849:SILK*!CREATURE:FORGOTTEN_BEAST_850:SILK*!CREATURE:FORGOTTEN_BEAST_855:SILK*CREATURE:TITAN_1:SILK*CREATURE:TITAN_5:SILK*CREATURE:TITAN_18:SILK*CREATURE:TITAN_24:SILK*CREATURE:TITAN_28:SILK*CREATURE:TITAN_29:SILK*CREATURE:TITAN_30:SILK*CREATURE:TITAN_31:SILK*CREATURE:DEMON_13:SILK*CREATURE:DEMON_15:SILK*CREATURE:DEMON_43:SILK*CREATURE:DEMON_46:SILK*CREATURE:DEMON_49:SILK*CREATURE:DEMON_50:SILK*CREATURE:DEMON_52:SILK2PLANT:FLAX:THREAD2PLANT:JUTE:THREAD2PLANT:HEMP:THREAD2PLANT:COTTON:THREAD2PLANT:RAMIE:THREAD2PLANT:KENAF:THREAD2PLANT:GRASS_TAIL_PIG:THREAD2PLANT:REED_ROPE:THREAD:CREATURE:SHEEP:HAIR:CREATURE:LLAMA:HAIR:CREATURE:ALPACA:HAIR:CREATURE:TROLL:HAIR \ No newline at end of file diff --git a/data/stockpiles/coal.dfstock b/data/stockpiles/coal.dfstock new file mode 100644 index 000000000..8e9ec16e0 --- /dev/null +++ b/data/stockpiles/coal.dfstock @@ -0,0 +1,2 @@ +R +COAL \ No newline at end of file diff --git a/data/stockpiles/coalproducing.dfstock b/data/stockpiles/coalproducing.dfstock new file mode 100644 index 000000000..30d6f7adf --- /dev/null +++ b/data/stockpiles/coalproducing.dfstock @@ -0,0 +1,3 @@ +2. +INORGANIC:COAL_BITUMINOUS +INORGANIC:LIGNITE \ No newline at end of file diff --git a/data/stockpiles/copperarmor.dfstock b/data/stockpiles/copperarmor.dfstock new file mode 100644 index 000000000..bc7b8ec58 Binary files /dev/null and b/data/stockpiles/copperarmor.dfstock differ diff --git a/data/stockpiles/copperweapons.dfstock b/data/stockpiles/copperweapons.dfstock new file mode 100644 index 000000000..a30c53107 Binary files /dev/null and b/data/stockpiles/copperweapons.dfstock differ diff --git a/data/stockpiles/crafts.dfstock b/data/stockpiles/crafts.dfstock new file mode 100644 index 000000000..633dd9f1f --- /dev/null +++ b/data/stockpiles/crafts.dfstock @@ -0,0 +1,10 @@ +bG +FIGURINE +AMULET +SCEPTER +CROWN +RING +EARRING +BRACELET +GEM +TOTEM \ No newline at end of file diff --git a/data/stockpiles/cutgems.dfstock b/data/stockpiles/cutgems.dfstock new file mode 100644 index 000000000..1049acd67 --- /dev/null +++ b/data/stockpiles/cutgems.dfstock @@ -0,0 +1 @@ +ZÃ"INORGANIC:ONYX"INORGANIC:MORION"INORGANIC:SCHORL"INORGANIC:LACE AGATE"INORGANIC:BLUE JADE"INORGANIC:LAPIS LAZULI"INORGANIC:PRASE"INORGANIC:PRASE OPAL"INORGANIC:BLOODSTONE"INORGANIC:MOSS AGATE"INORGANIC:MOSS OPAL"INORGANIC:VARISCITE"INORGANIC:CHRYSOPRASE"INORGANIC:CHRYSOCOLLA"INORGANIC:SARD"INORGANIC:CARNELIAN"INORGANIC:BANDED AGATE"INORGANIC:SARDONYX"INORGANIC:CHERRY OPAL"INORGANIC:LAVENDER JADE"INORGANIC:PINK JADE"INORGANIC:TUBE AGATE"INORGANIC:FIRE AGATE"INORGANIC:PLUME AGATE"INORGANIC:BROWN JASPER"INORGANIC:PICTURE JASPER"INORGANIC:SMOKY QUARTZ"INORGANIC:WAX OPAL"INORGANIC:WOOD OPAL"INORGANIC:AMBER OPAL"INORGANIC:GOLD OPAL"INORGANIC:CITRINE"INORGANIC:YELLOW JASPER"INORGANIC:TIGEREYE"INORGANIC:TIGER IRON"INORGANIC:SUNSTONE"INORGANIC:RESIN OPAL"INORGANIC:PYRITE"INORGANIC:CLEAR TOURMALINE"INORGANIC:GRAY CHALCEDONY"INORGANIC:DENDRITIC AGATE"INORGANIC:SHELL OPAL"INORGANIC:BONE OPAL"INORGANIC:WHITE CHALCEDONY"INORGANIC:FORTIFICATION AGATE"INORGANIC:MILK QUARTZ"INORGANIC:MOONSTONE"INORGANIC:WHITE JADE"INORGANIC:JASPER OPAL"INORGANIC:PINEAPPLE OPAL"INORGANIC:ONYX OPAL"INORGANIC:MILK OPAL"INORGANIC:PIPE OPAL"INORGANIC:AVENTURINE"INORGANIC:TURQUOISE"INORGANIC:QUARTZ_ROSE"INORGANIC:CRYSTAL_ROCK"INORGANIC:BLACK ZIRCON"INORGANIC:BLACK PYROPE"INORGANIC:MELANITE"INORGANIC:INDIGO TOURMALINE"INORGANIC:BLUE GARNET"INORGANIC:TSAVORITE"INORGANIC:GREEN TOURMALINE"INORGANIC:DEMANTOID"INORGANIC:GREEN ZIRCON"INORGANIC:GREEN JADE"INORGANIC:HELIODOR"INORGANIC:PERIDOT"INORGANIC:RED ZIRCON"INORGANIC:RED TOURMALINE"INORGANIC:RED PYROPE"INORGANIC:ALMANDINE"INORGANIC:RED GROSSULAR"INORGANIC:PINK TOURMALINE"INORGANIC:RED BERYL"INORGANIC:FIRE OPAL"INORGANIC:RHODOLITE"INORGANIC:SPINEL_PURPLE"INORGANIC:ALEXANDRITE"INORGANIC:TANZANITE"INORGANIC:MORGANITE"INORGANIC:VIOLET SPESSARTINE"INORGANIC:PINK GARNET"INORGANIC:KUNZITE"INORGANIC:CINNAMON GROSSULAR"INORGANIC:HONEY YELLOW BERYL"INORGANIC:JELLY OPAL"INORGANIC:BROWN ZIRCON"INORGANIC:YELLOW ZIRCON"INORGANIC:GOLDEN BERYL"INORGANIC:YELLOW SPESSARTINE"INORGANIC:TOPAZ"INORGANIC:TOPAZOLITE"INORGANIC:YELLOW GROSSULAR"INORGANIC:RUBICELLE"INORGANIC:CLEAR GARNET"INORGANIC:GOSHENITE"INORGANIC:CAT'S EYE"INORGANIC:CLEAR ZIRCON"INORGANIC:AMETHYST"INORGANIC:AQUAMARINE"INORGANIC:SPINEL_RED"INORGANIC:CHRYSOBERYL"INORGANIC:OPAL_PFIRE"INORGANIC:OPAL_REDFLASH"INORGANIC:OPAL_BLACK"INORGANIC:OPAL_WHITE"INORGANIC:OPAL_CRYSTAL"INORGANIC:OPAL_CLARO"INORGANIC:OPAL_LEVIN"INORGANIC:OPAL_HARLEQUIN"INORGANIC:OPAL_PINFIRE"INORGANIC:OPAL_BANDFIRE"INORGANIC:DIAMOND_LY"INORGANIC:DIAMOND_FY"INORGANIC:EMERALD"INORGANIC:RUBY"INORGANIC:SAPPHIRE"INORGANIC:DIAMOND_CLEAR"INORGANIC:DIAMOND_RED"INORGANIC:DIAMOND_GREEN"INORGANIC:DIAMOND_BLUE"INORGANIC:DIAMOND_YELLOW"INORGANIC:DIAMOND_BLACK"INORGANIC:SAPPHIRE_STAR"INORGANIC:RUBY_STAR \ No newline at end of file diff --git a/data/stockpiles/cutglass.dfstock b/data/stockpiles/cutglass.dfstock new file mode 100644 index 000000000..6997d42ac --- /dev/null +++ b/data/stockpiles/cutglass.dfstock @@ -0,0 +1 @@ +Z) GLASS_GREEN GLASS_CLEAR GLASS_CRYSTAL \ No newline at end of file diff --git a/data/stockpiles/cutstone.dfstock b/data/stockpiles/cutstone.dfstock new file mode 100644 index 000000000..5e0f3f881 --- /dev/null +++ b/data/stockpiles/cutstone.dfstock @@ -0,0 +1 @@ +Zâ"INORGANIC:PLASTER"INORGANIC:CERAMIC_EARTHENWARE"INORGANIC:CERAMIC_STONEWARE"INORGANIC:CERAMIC_PORCELAIN"INORGANIC:ASH_GLAZE"INORGANIC:TIN_GLAZE"INORGANIC:SANDSTONE"INORGANIC:SILTSTONE"INORGANIC:MUDSTONE"INORGANIC:SHALE"INORGANIC:CLAYSTONE"INORGANIC:ROCK_SALT"INORGANIC:LIMESTONE"INORGANIC:CONGLOMERATE"INORGANIC:DOLOMITE"INORGANIC:CHERT"INORGANIC:CHALK"INORGANIC:GRANITE"INORGANIC:DIORITE"INORGANIC:GABBRO"INORGANIC:RHYOLITE"INORGANIC:BASALT"INORGANIC:ANDESITE"INORGANIC:DACITE"INORGANIC:OBSIDIAN"INORGANIC:QUARTZITE"INORGANIC:SLATE"INORGANIC:PHYLLITE"INORGANIC:SCHIST"INORGANIC:GNEISS"INORGANIC:MARBLE"INORGANIC:HEMATITE"INORGANIC:LIMONITE"INORGANIC:GARNIERITE"INORGANIC:NATIVE_GOLD"INORGANIC:NATIVE_SILVER"INORGANIC:NATIVE_COPPER"INORGANIC:MALACHITE"INORGANIC:GALENA"INORGANIC:SPHALERITE"INORGANIC:CASSITERITE"INORGANIC:COAL_BITUMINOUS"INORGANIC:LIGNITE"INORGANIC:NATIVE_PLATINUM"INORGANIC:CINNABAR"INORGANIC:COBALTITE"INORGANIC:TETRAHEDRITE"INORGANIC:HORN_SILVER"INORGANIC:GYPSUM"INORGANIC:TALC" INORGANIC:JET"INORGANIC:PUDDINGSTONE"INORGANIC:PETRIFIED_WOOD"INORGANIC:GRAPHITE"INORGANIC:BRIMSTONE"INORGANIC:KIMBERLITE"INORGANIC:BISMUTHINITE"INORGANIC:REALGAR"INORGANIC:ORPIMENT"INORGANIC:STIBNITE"INORGANIC:MARCASITE"INORGANIC:SYLVITE"INORGANIC:CRYOLITE"INORGANIC:PERICLASE"INORGANIC:ILMENITE"INORGANIC:RUTILE"INORGANIC:MAGNETITE"INORGANIC:CHROMITE"INORGANIC:PYROLUSITE"INORGANIC:PITCHBLENDE"INORGANIC:BAUXITE"INORGANIC:NATIVE_ALUMINUM"INORGANIC:BORAX"INORGANIC:OLIVINE"INORGANIC:HORNBLENDE"INORGANIC:KAOLINITE"INORGANIC:SERPENTINE"INORGANIC:ORTHOCLASE"INORGANIC:MICROCLINE"INORGANIC:MICA"INORGANIC:CALCITE"INORGANIC:SALTPETER"INORGANIC:ALABASTER"INORGANIC:SELENITE"INORGANIC:SATINSPAR"INORGANIC:ANHYDRITE"INORGANIC:ALUNITE"INORGANIC:RAW_ADAMANTINE"INORGANIC:SLADE" INORGANIC:BROMS_CLEAN_CORPSEDUST \ No newline at end of file diff --git a/data/stockpiles/dye.dfstock b/data/stockpiles/dye.dfstock new file mode 100644 index 000000000..67b785d20 Binary files /dev/null and b/data/stockpiles/dye.dfstock differ diff --git a/data/stockpiles/economic.dfstock b/data/stockpiles/economic.dfstock new file mode 100644 index 000000000..976f4cc11 --- /dev/null +++ b/data/stockpiles/economic.dfstock @@ -0,0 +1,13 @@ +2ò +INORGANIC:LIMESTONE +INORGANIC:DOLOMITE +INORGANIC:CHALK +INORGANIC:MARBLE +INORGANIC:COAL_BITUMINOUS +INORGANIC:LIGNITE +INORGANIC:GYPSUM +INORGANIC:KAOLINITE +INORGANIC:CALCITE +INORGANIC:ALABASTER +INORGANIC:SELENITE +INORGANIC:SATINSPAR \ No newline at end of file diff --git a/data/stockpiles/flux.dfstock b/data/stockpiles/flux.dfstock new file mode 100644 index 000000000..ec86187db --- /dev/null +++ b/data/stockpiles/flux.dfstock @@ -0,0 +1,6 @@ +2_ +INORGANIC:LIMESTONE +INORGANIC:DOLOMITE +INORGANIC:CHALK +INORGANIC:MARBLE +INORGANIC:CALCITE \ No newline at end of file diff --git a/data/stockpiles/goblets.dfstock b/data/stockpiles/goblets.dfstock new file mode 100644 index 000000000..bff4f8a6d --- /dev/null +++ b/data/stockpiles/goblets.dfstock @@ -0,0 +1,2 @@ +b +GOBLET \ No newline at end of file diff --git a/data/stockpiles/ironarmor.dfstock b/data/stockpiles/ironarmor.dfstock new file mode 100644 index 000000000..9474bd69f Binary files /dev/null and b/data/stockpiles/ironarmor.dfstock differ diff --git a/data/stockpiles/ironbars.dfstock b/data/stockpiles/ironbars.dfstock new file mode 100644 index 000000000..2cb77c0e3 --- /dev/null +++ b/data/stockpiles/ironbars.dfstock @@ -0,0 +1 @@ +RINORGANIC:IRON \ No newline at end of file diff --git a/data/stockpiles/ironore.dfstock b/data/stockpiles/ironore.dfstock new file mode 100644 index 000000000..2108b60ac --- /dev/null +++ b/data/stockpiles/ironore.dfstock @@ -0,0 +1,4 @@ +2= +INORGANIC:HEMATITE +INORGANIC:LIMONITE +INORGANIC:MAGNETITE \ No newline at end of file diff --git a/data/stockpiles/ironweapons.dfstock b/data/stockpiles/ironweapons.dfstock new file mode 100644 index 000000000..8c0afecd1 Binary files /dev/null and b/data/stockpiles/ironweapons.dfstock differ diff --git a/data/stockpiles/metalammo.dfstock b/data/stockpiles/metalammo.dfstock new file mode 100644 index 000000000..f1d9cdee9 --- /dev/null +++ b/data/stockpiles/metalammo.dfstock @@ -0,0 +1 @@ +BÒINORGANIC:IRONINORGANIC:SILVERINORGANIC:COPPERINORGANIC:NICKELINORGANIC:ZINCINORGANIC:BRONZEINORGANIC:BRASSINORGANIC:STEELINORGANIC:PIG_IRONINORGANIC:PLATINUMINORGANIC:ELECTRUM INORGANIC:TININORGANIC:PEWTER_FINEINORGANIC:PEWTER_TRIFLEINORGANIC:PEWTER_LAYINORGANIC:LEADINORGANIC:ALUMINUMINORGANIC:NICKEL_SILVERINORGANIC:BILLONINORGANIC:STERLING_SILVERINORGANIC:BLACK_BRONZEINORGANIC:ROSE_GOLDINORGANIC:BISMUTHINORGANIC:BISMUTH_BRONZEINORGANIC:ADAMANTINEINORGANIC:GOLDINORGANIC:DIVINE_1INORGANIC:DIVINE_3INORGANIC:DIVINE_5INORGANIC:DIVINE_7INORGANIC:DIVINE_9INORGANIC:DIVINE_11INORGANIC:DIVINE_13INORGANIC:DIVINE_15INORGANIC:DIVINE_17INORGANIC:DIVINE_19 \ No newline at end of file diff --git a/data/stockpiles/metalarmor.dfstock b/data/stockpiles/metalarmor.dfstock new file mode 100644 index 000000000..f3a00646a Binary files /dev/null and b/data/stockpiles/metalarmor.dfstock differ diff --git a/data/stockpiles/metalbars.dfstock b/data/stockpiles/metalbars.dfstock new file mode 100644 index 000000000..103619c50 --- /dev/null +++ b/data/stockpiles/metalbars.dfstock @@ -0,0 +1 @@ +RÒINORGANIC:IRONINORGANIC:SILVERINORGANIC:COPPERINORGANIC:NICKELINORGANIC:ZINCINORGANIC:BRONZEINORGANIC:BRASSINORGANIC:STEELINORGANIC:PIG_IRONINORGANIC:PLATINUMINORGANIC:ELECTRUM INORGANIC:TININORGANIC:PEWTER_FINEINORGANIC:PEWTER_TRIFLEINORGANIC:PEWTER_LAYINORGANIC:LEADINORGANIC:ALUMINUMINORGANIC:NICKEL_SILVERINORGANIC:BILLONINORGANIC:STERLING_SILVERINORGANIC:BLACK_BRONZEINORGANIC:ROSE_GOLDINORGANIC:BISMUTHINORGANIC:BISMUTH_BRONZEINORGANIC:ADAMANTINEINORGANIC:GOLDINORGANIC:DIVINE_1INORGANIC:DIVINE_3INORGANIC:DIVINE_5INORGANIC:DIVINE_7INORGANIC:DIVINE_9INORGANIC:DIVINE_11INORGANIC:DIVINE_13INORGANIC:DIVINE_15INORGANIC:DIVINE_17INORGANIC:DIVINE_19 \ No newline at end of file diff --git a/data/stockpiles/metalore.dfstock b/data/stockpiles/metalore.dfstock new file mode 100644 index 000000000..8cbb60959 --- /dev/null +++ b/data/stockpiles/metalore.dfstock @@ -0,0 +1,17 @@ +2í +INORGANIC:HEMATITE +INORGANIC:LIMONITE +INORGANIC:GARNIERITE +INORGANIC:NATIVE_GOLD +INORGANIC:NATIVE_SILVER +INORGANIC:NATIVE_COPPER +INORGANIC:MALACHITE +INORGANIC:GALENA +INORGANIC:SPHALERITE +INORGANIC:CASSITERITE +INORGANIC:NATIVE_PLATINUM +INORGANIC:TETRAHEDRITE +INORGANIC:HORN_SILVER +INORGANIC:BISMUTHINITE +INORGANIC:MAGNETITE +INORGANIC:NATIVE_ALUMINUM \ No newline at end of file diff --git a/data/stockpiles/metalweapons.dfstock b/data/stockpiles/metalweapons.dfstock new file mode 100644 index 000000000..b9cd66511 Binary files /dev/null and b/data/stockpiles/metalweapons.dfstock differ diff --git a/data/stockpiles/miscliquid.dfstock b/data/stockpiles/miscliquid.dfstock new file mode 100644 index 000000000..dbf75a072 Binary files /dev/null and b/data/stockpiles/miscliquid.dfstock differ diff --git a/data/stockpiles/otherarmor.dfstock b/data/stockpiles/otherarmor.dfstock new file mode 100644 index 000000000..ec857e0cc Binary files /dev/null and b/data/stockpiles/otherarmor.dfstock differ diff --git a/data/stockpiles/otherbars.dfstock b/data/stockpiles/otherbars.dfstock new file mode 100644 index 000000000..4cbcc1c8f --- /dev/null +++ b/data/stockpiles/otherbars.dfstock @@ -0,0 +1,6 @@ +R# +COAL +POTASH +ASH +PEARLASH +SOAP \ No newline at end of file diff --git a/data/stockpiles/otherstone.dfstock b/data/stockpiles/otherstone.dfstock new file mode 100644 index 000000000..1f9f867ed --- /dev/null +++ b/data/stockpiles/otherstone.dfstock @@ -0,0 +1,56 @@ +2Ë +INORGANIC:SANDSTONE +INORGANIC:SILTSTONE +INORGANIC:MUDSTONE +INORGANIC:SHALE +INORGANIC:CLAYSTONE +INORGANIC:ROCK_SALT +INORGANIC:CONGLOMERATE +INORGANIC:CHERT +INORGANIC:GRANITE +INORGANIC:DIORITE +INORGANIC:GABBRO +INORGANIC:RHYOLITE +INORGANIC:BASALT +INORGANIC:ANDESITE +INORGANIC:DACITE +INORGANIC:OBSIDIAN +INORGANIC:QUARTZITE +INORGANIC:SLATE +INORGANIC:PHYLLITE +INORGANIC:SCHIST +INORGANIC:GNEISS +INORGANIC:CINNABAR +INORGANIC:COBALTITE +INORGANIC:TALC + INORGANIC:JET +INORGANIC:PUDDINGSTONE +INORGANIC:PETRIFIED_WOOD +INORGANIC:GRAPHITE +INORGANIC:BRIMSTONE +INORGANIC:KIMBERLITE +INORGANIC:REALGAR +INORGANIC:ORPIMENT +INORGANIC:STIBNITE +INORGANIC:MARCASITE +INORGANIC:SYLVITE +INORGANIC:CRYOLITE +INORGANIC:PERICLASE +INORGANIC:ILMENITE +INORGANIC:RUTILE +INORGANIC:CHROMITE +INORGANIC:PYROLUSITE +INORGANIC:PITCHBLENDE +INORGANIC:BAUXITE +INORGANIC:BORAX +INORGANIC:OLIVINE +INORGANIC:HORNBLENDE +INORGANIC:SERPENTINE +INORGANIC:ORTHOCLASE +INORGANIC:MICROCLINE +INORGANIC:MICA +INORGANIC:SALTPETER +INORGANIC:ANHYDRITE +INORGANIC:ALUNITE +INORGANIC:RAW_ADAMANTINE +INORGANIC:SLADE \ No newline at end of file diff --git a/data/stockpiles/otherweapons.dfstock b/data/stockpiles/otherweapons.dfstock new file mode 100644 index 000000000..f1127a784 Binary files /dev/null and b/data/stockpiles/otherweapons.dfstock differ diff --git a/data/stockpiles/pearlash.dfstock b/data/stockpiles/pearlash.dfstock new file mode 100644 index 000000000..3cea5164d --- /dev/null +++ b/data/stockpiles/pearlash.dfstock @@ -0,0 +1,3 @@ +R + +PEARLASH \ No newline at end of file diff --git a/data/stockpiles/pigironbars.dfstock b/data/stockpiles/pigironbars.dfstock new file mode 100644 index 000000000..f07b1def2 --- /dev/null +++ b/data/stockpiles/pigironbars.dfstock @@ -0,0 +1 @@ +RINORGANIC:PIG_IRON \ No newline at end of file diff --git a/data/stockpiles/plants.dfstock b/data/stockpiles/plants.dfstock new file mode 100644 index 000000000..ca55f19f9 Binary files /dev/null and b/data/stockpiles/plants.dfstock differ diff --git a/data/stockpiles/plasterproducing.dfstock b/data/stockpiles/plasterproducing.dfstock new file mode 100644 index 000000000..2764150a3 --- /dev/null +++ b/data/stockpiles/plasterproducing.dfstock @@ -0,0 +1,5 @@ +2P +INORGANIC:GYPSUM +INORGANIC:ALABASTER +INORGANIC:SELENITE +INORGANIC:SATINSPAR \ No newline at end of file diff --git a/data/stockpiles/platinumweapons.dfstock b/data/stockpiles/platinumweapons.dfstock new file mode 100644 index 000000000..162865512 Binary files /dev/null and b/data/stockpiles/platinumweapons.dfstock differ diff --git a/data/stockpiles/potash.dfstock b/data/stockpiles/potash.dfstock new file mode 100644 index 000000000..8a09e446f --- /dev/null +++ b/data/stockpiles/potash.dfstock @@ -0,0 +1,2 @@ +R +POTASH \ No newline at end of file diff --git a/data/stockpiles/pots.dfstock b/data/stockpiles/pots.dfstock new file mode 100644 index 000000000..2c1fe0e5c --- /dev/null +++ b/data/stockpiles/pots.dfstock @@ -0,0 +1,2 @@ + + FOOD_STORAGE \ No newline at end of file diff --git a/data/stockpiles/preparedmeals.dfstock b/data/stockpiles/preparedmeals.dfstock new file mode 100644 index 000000000..657c93f38 --- /dev/null +++ b/data/stockpiles/preparedmeals.dfstock @@ -0,0 +1 @@ +˜ \ No newline at end of file diff --git a/data/stockpiles/rawhides.dfstock b/data/stockpiles/rawhides.dfstock new file mode 100644 index 000000000..eedc71760 Binary files /dev/null and b/data/stockpiles/rawhides.dfstock differ diff --git a/data/stockpiles/roughgems.dfstock b/data/stockpiles/roughgems.dfstock new file mode 100644 index 000000000..fdad4be1a --- /dev/null +++ b/data/stockpiles/roughgems.dfstock @@ -0,0 +1 @@ +ZÃINORGANIC:ONYXINORGANIC:MORIONINORGANIC:SCHORLINORGANIC:LACE AGATEINORGANIC:BLUE JADEINORGANIC:LAPIS LAZULIINORGANIC:PRASEINORGANIC:PRASE OPALINORGANIC:BLOODSTONEINORGANIC:MOSS AGATEINORGANIC:MOSS OPALINORGANIC:VARISCITEINORGANIC:CHRYSOPRASEINORGANIC:CHRYSOCOLLAINORGANIC:SARDINORGANIC:CARNELIANINORGANIC:BANDED AGATEINORGANIC:SARDONYXINORGANIC:CHERRY OPALINORGANIC:LAVENDER JADEINORGANIC:PINK JADEINORGANIC:TUBE AGATEINORGANIC:FIRE AGATEINORGANIC:PLUME AGATEINORGANIC:BROWN JASPERINORGANIC:PICTURE JASPERINORGANIC:SMOKY QUARTZINORGANIC:WAX OPALINORGANIC:WOOD OPALINORGANIC:AMBER OPALINORGANIC:GOLD OPALINORGANIC:CITRINEINORGANIC:YELLOW JASPERINORGANIC:TIGEREYEINORGANIC:TIGER IRONINORGANIC:SUNSTONEINORGANIC:RESIN OPALINORGANIC:PYRITEINORGANIC:CLEAR TOURMALINEINORGANIC:GRAY CHALCEDONYINORGANIC:DENDRITIC AGATEINORGANIC:SHELL OPALINORGANIC:BONE OPALINORGANIC:WHITE CHALCEDONYINORGANIC:FORTIFICATION AGATEINORGANIC:MILK QUARTZINORGANIC:MOONSTONEINORGANIC:WHITE JADEINORGANIC:JASPER OPALINORGANIC:PINEAPPLE OPALINORGANIC:ONYX OPALINORGANIC:MILK OPALINORGANIC:PIPE OPALINORGANIC:AVENTURINEINORGANIC:TURQUOISEINORGANIC:QUARTZ_ROSEINORGANIC:CRYSTAL_ROCKINORGANIC:BLACK ZIRCONINORGANIC:BLACK PYROPEINORGANIC:MELANITEINORGANIC:INDIGO TOURMALINEINORGANIC:BLUE GARNETINORGANIC:TSAVORITEINORGANIC:GREEN TOURMALINEINORGANIC:DEMANTOIDINORGANIC:GREEN ZIRCONINORGANIC:GREEN JADEINORGANIC:HELIODORINORGANIC:PERIDOTINORGANIC:RED ZIRCONINORGANIC:RED TOURMALINEINORGANIC:RED PYROPEINORGANIC:ALMANDINEINORGANIC:RED GROSSULARINORGANIC:PINK TOURMALINEINORGANIC:RED BERYLINORGANIC:FIRE OPALINORGANIC:RHODOLITEINORGANIC:SPINEL_PURPLEINORGANIC:ALEXANDRITEINORGANIC:TANZANITEINORGANIC:MORGANITEINORGANIC:VIOLET SPESSARTINEINORGANIC:PINK GARNETINORGANIC:KUNZITEINORGANIC:CINNAMON GROSSULARINORGANIC:HONEY YELLOW BERYLINORGANIC:JELLY OPALINORGANIC:BROWN ZIRCONINORGANIC:YELLOW ZIRCONINORGANIC:GOLDEN BERYLINORGANIC:YELLOW SPESSARTINEINORGANIC:TOPAZINORGANIC:TOPAZOLITEINORGANIC:YELLOW GROSSULARINORGANIC:RUBICELLEINORGANIC:CLEAR GARNETINORGANIC:GOSHENITEINORGANIC:CAT'S EYEINORGANIC:CLEAR ZIRCONINORGANIC:AMETHYSTINORGANIC:AQUAMARINEINORGANIC:SPINEL_REDINORGANIC:CHRYSOBERYLINORGANIC:OPAL_PFIREINORGANIC:OPAL_REDFLASHINORGANIC:OPAL_BLACKINORGANIC:OPAL_WHITEINORGANIC:OPAL_CRYSTALINORGANIC:OPAL_CLAROINORGANIC:OPAL_LEVININORGANIC:OPAL_HARLEQUININORGANIC:OPAL_PINFIREINORGANIC:OPAL_BANDFIREINORGANIC:DIAMOND_LYINORGANIC:DIAMOND_FYINORGANIC:EMERALDINORGANIC:RUBYINORGANIC:SAPPHIREINORGANIC:DIAMOND_CLEARINORGANIC:DIAMOND_REDINORGANIC:DIAMOND_GREENINORGANIC:DIAMOND_BLUEINORGANIC:DIAMOND_YELLOWINORGANIC:DIAMOND_BLACKINORGANIC:SAPPHIRE_STARINORGANIC:RUBY_STAR \ No newline at end of file diff --git a/data/stockpiles/roughglass.dfstock b/data/stockpiles/roughglass.dfstock new file mode 100644 index 000000000..23b95b81a --- /dev/null +++ b/data/stockpiles/roughglass.dfstock @@ -0,0 +1,4 @@ +Z) + GLASS_GREEN + GLASS_CLEAR + GLASS_CRYSTAL \ No newline at end of file diff --git a/data/stockpiles/sand.dfstock b/data/stockpiles/sand.dfstock new file mode 100644 index 000000000..b97921e81 --- /dev/null +++ b/data/stockpiles/sand.dfstock @@ -0,0 +1,3 @@ + + +SAND_BAG \ No newline at end of file diff --git a/data/stockpiles/seeds.dfstock b/data/stockpiles/seeds.dfstock new file mode 100644 index 000000000..ce9d0a4c2 Binary files /dev/null and b/data/stockpiles/seeds.dfstock differ diff --git a/data/stockpiles/silverweapons.dfstock b/data/stockpiles/silverweapons.dfstock new file mode 100644 index 000000000..de517d0ab Binary files /dev/null and b/data/stockpiles/silverweapons.dfstock differ diff --git a/data/stockpiles/soap.dfstock b/data/stockpiles/soap.dfstock new file mode 100644 index 000000000..0436f1e7a --- /dev/null +++ b/data/stockpiles/soap.dfstock @@ -0,0 +1,2 @@ +R +SOAP \ No newline at end of file diff --git a/data/stockpiles/steelarmor.dfstock b/data/stockpiles/steelarmor.dfstock new file mode 100644 index 000000000..c2ba86fcb Binary files /dev/null and b/data/stockpiles/steelarmor.dfstock differ diff --git a/data/stockpiles/steelbars.dfstock b/data/stockpiles/steelbars.dfstock new file mode 100644 index 000000000..888f32e53 --- /dev/null +++ b/data/stockpiles/steelbars.dfstock @@ -0,0 +1 @@ +RINORGANIC:STEEL \ No newline at end of file diff --git a/data/stockpiles/steelweapons.dfstock b/data/stockpiles/steelweapons.dfstock new file mode 100644 index 000000000..fc5233925 Binary files /dev/null and b/data/stockpiles/steelweapons.dfstock differ diff --git a/data/stockpiles/stonetools.dfstock b/data/stockpiles/stonetools.dfstock new file mode 100644 index 000000000..dee961a74 --- /dev/null +++ b/data/stockpiles/stonetools.dfstock @@ -0,0 +1,2 @@ +bÆ +TOOLINORGANIC:PLASTERINORGANIC:CERAMIC_EARTHENWAREINORGANIC:CERAMIC_STONEWAREINORGANIC:CERAMIC_PORCELAININORGANIC:ASH_GLAZEINORGANIC:TIN_GLAZEINORGANIC:SANDSTONEINORGANIC:SILTSTONEINORGANIC:MUDSTONEINORGANIC:SHALEINORGANIC:CLAYSTONEINORGANIC:ROCK_SALTINORGANIC:LIMESTONEINORGANIC:CONGLOMERATEINORGANIC:DOLOMITEINORGANIC:CHERTINORGANIC:CHALKINORGANIC:GRANITEINORGANIC:DIORITEINORGANIC:GABBROINORGANIC:RHYOLITEINORGANIC:BASALTINORGANIC:ANDESITEINORGANIC:DACITEINORGANIC:OBSIDIANINORGANIC:QUARTZITEINORGANIC:SLATEINORGANIC:PHYLLITEINORGANIC:SCHISTINORGANIC:GNEISSINORGANIC:MARBLEINORGANIC:HEMATITEINORGANIC:LIMONITEINORGANIC:GARNIERITEINORGANIC:NATIVE_GOLDINORGANIC:NATIVE_SILVERINORGANIC:NATIVE_COPPERINORGANIC:MALACHITEINORGANIC:GALENAINORGANIC:SPHALERITEINORGANIC:CASSITERITEINORGANIC:COAL_BITUMINOUSINORGANIC:LIGNITEINORGANIC:NATIVE_PLATINUMINORGANIC:CINNABARINORGANIC:COBALTITEINORGANIC:TETRAHEDRITEINORGANIC:HORN_SILVERINORGANIC:GYPSUMINORGANIC:TALC INORGANIC:JETINORGANIC:PUDDINGSTONEINORGANIC:PETRIFIED_WOODINORGANIC:GRAPHITEINORGANIC:BRIMSTONEINORGANIC:KIMBERLITEINORGANIC:BISMUTHINITEINORGANIC:REALGARINORGANIC:ORPIMENTINORGANIC:STIBNITEINORGANIC:MARCASITEINORGANIC:SYLVITEINORGANIC:CRYOLITEINORGANIC:PERICLASEINORGANIC:ILMENITEINORGANIC:RUTILEINORGANIC:MAGNETITEINORGANIC:CHROMITEINORGANIC:PYROLUSITEINORGANIC:PITCHBLENDEINORGANIC:BAUXITEINORGANIC:NATIVE_ALUMINUMINORGANIC:BORAXINORGANIC:OLIVINEINORGANIC:HORNBLENDEINORGANIC:KAOLINITEINORGANIC:SERPENTINEINORGANIC:ORTHOCLASEINORGANIC:MICROCLINEINORGANIC:MICAINORGANIC:CALCITEINORGANIC:SALTPETERINORGANIC:ALABASTERINORGANIC:SELENITEINORGANIC:SATINSPARINORGANIC:ANHYDRITEINORGANIC:ALUNITEINORGANIC:RAW_ADAMANTINEINORGANIC:SLADE \ No newline at end of file diff --git a/data/stockpiles/stoneweapons.dfstock b/data/stockpiles/stoneweapons.dfstock new file mode 100644 index 000000000..acba2374c Binary files /dev/null and b/data/stockpiles/stoneweapons.dfstock differ diff --git a/data/stockpiles/tannedhides.dfstock b/data/stockpiles/tannedhides.dfstock new file mode 100644 index 000000000..9c58d41ce Binary files /dev/null and b/data/stockpiles/tannedhides.dfstock differ diff --git a/data/stockpiles/thread.dfstock b/data/stockpiles/thread.dfstock new file mode 100644 index 000000000..2774f50e8 --- /dev/null +++ b/data/stockpiles/thread.dfstock @@ -0,0 +1,146 @@ +rŽ' +"CREATURE:SPIDER_BROWN_RECLUSE:SILK +&CREATURE:BROWN_RECLUSE_SPIDER_MAN:SILK +(CREATURE:GIANT_BROWN_RECLUSE_SPIDER:SILK +CREATURE:SPIDER_PHANTOM:SILK +CREATURE:SPIDER_CAVE_GIANT:SILK +CREATURE:SPIDER_CAVE:SILK +INORGANIC:DIVINE_2 +INORGANIC:DIVINE_4 +INORGANIC:DIVINE_6 +INORGANIC:DIVINE_8 +INORGANIC:DIVINE_10 +INORGANIC:DIVINE_12 +INORGANIC:DIVINE_14 +INORGANIC:DIVINE_16 +INORGANIC:DIVINE_18 +INORGANIC:DIVINE_20 +CREATURE:FORGOTTEN_BEAST_4:SILK + CREATURE:FORGOTTEN_BEAST_22:SILK + CREATURE:FORGOTTEN_BEAST_33:SILK + CREATURE:FORGOTTEN_BEAST_51:SILK + CREATURE:FORGOTTEN_BEAST_56:SILK + CREATURE:FORGOTTEN_BEAST_58:SILK + CREATURE:FORGOTTEN_BEAST_62:SILK + CREATURE:FORGOTTEN_BEAST_74:SILK + CREATURE:FORGOTTEN_BEAST_80:SILK + CREATURE:FORGOTTEN_BEAST_87:SILK + CREATURE:FORGOTTEN_BEAST_90:SILK +!CREATURE:FORGOTTEN_BEAST_106:SILK +!CREATURE:FORGOTTEN_BEAST_109:SILK +!CREATURE:FORGOTTEN_BEAST_127:SILK +!CREATURE:FORGOTTEN_BEAST_131:SILK +!CREATURE:FORGOTTEN_BEAST_143:SILK +!CREATURE:FORGOTTEN_BEAST_145:SILK +!CREATURE:FORGOTTEN_BEAST_163:SILK +!CREATURE:FORGOTTEN_BEAST_177:SILK +!CREATURE:FORGOTTEN_BEAST_189:SILK +!CREATURE:FORGOTTEN_BEAST_194:SILK +!CREATURE:FORGOTTEN_BEAST_197:SILK +!CREATURE:FORGOTTEN_BEAST_200:SILK +!CREATURE:FORGOTTEN_BEAST_201:SILK +!CREATURE:FORGOTTEN_BEAST_202:SILK +!CREATURE:FORGOTTEN_BEAST_217:SILK +!CREATURE:FORGOTTEN_BEAST_224:SILK +!CREATURE:FORGOTTEN_BEAST_234:SILK +!CREATURE:FORGOTTEN_BEAST_242:SILK +!CREATURE:FORGOTTEN_BEAST_246:SILK +!CREATURE:FORGOTTEN_BEAST_250:SILK +!CREATURE:FORGOTTEN_BEAST_270:SILK +!CREATURE:FORGOTTEN_BEAST_271:SILK +!CREATURE:FORGOTTEN_BEAST_286:SILK +!CREATURE:FORGOTTEN_BEAST_291:SILK +!CREATURE:FORGOTTEN_BEAST_296:SILK +!CREATURE:FORGOTTEN_BEAST_329:SILK +!CREATURE:FORGOTTEN_BEAST_350:SILK +!CREATURE:FORGOTTEN_BEAST_351:SILK +!CREATURE:FORGOTTEN_BEAST_359:SILK +!CREATURE:FORGOTTEN_BEAST_365:SILK +!CREATURE:FORGOTTEN_BEAST_375:SILK +!CREATURE:FORGOTTEN_BEAST_376:SILK +!CREATURE:FORGOTTEN_BEAST_377:SILK +!CREATURE:FORGOTTEN_BEAST_383:SILK +!CREATURE:FORGOTTEN_BEAST_387:SILK +!CREATURE:FORGOTTEN_BEAST_398:SILK +!CREATURE:FORGOTTEN_BEAST_400:SILK +!CREATURE:FORGOTTEN_BEAST_406:SILK +!CREATURE:FORGOTTEN_BEAST_410:SILK +!CREATURE:FORGOTTEN_BEAST_417:SILK +!CREATURE:FORGOTTEN_BEAST_428:SILK +!CREATURE:FORGOTTEN_BEAST_429:SILK +!CREATURE:FORGOTTEN_BEAST_432:SILK +!CREATURE:FORGOTTEN_BEAST_442:SILK +!CREATURE:FORGOTTEN_BEAST_448:SILK +!CREATURE:FORGOTTEN_BEAST_452:SILK +!CREATURE:FORGOTTEN_BEAST_457:SILK +!CREATURE:FORGOTTEN_BEAST_468:SILK +!CREATURE:FORGOTTEN_BEAST_472:SILK +!CREATURE:FORGOTTEN_BEAST_479:SILK +!CREATURE:FORGOTTEN_BEAST_482:SILK +!CREATURE:FORGOTTEN_BEAST_492:SILK +!CREATURE:FORGOTTEN_BEAST_504:SILK +!CREATURE:FORGOTTEN_BEAST_512:SILK +!CREATURE:FORGOTTEN_BEAST_519:SILK +!CREATURE:FORGOTTEN_BEAST_537:SILK +!CREATURE:FORGOTTEN_BEAST_550:SILK +!CREATURE:FORGOTTEN_BEAST_556:SILK +!CREATURE:FORGOTTEN_BEAST_557:SILK +!CREATURE:FORGOTTEN_BEAST_563:SILK +!CREATURE:FORGOTTEN_BEAST_573:SILK +!CREATURE:FORGOTTEN_BEAST_583:SILK +!CREATURE:FORGOTTEN_BEAST_588:SILK +!CREATURE:FORGOTTEN_BEAST_599:SILK +!CREATURE:FORGOTTEN_BEAST_602:SILK +!CREATURE:FORGOTTEN_BEAST_605:SILK +!CREATURE:FORGOTTEN_BEAST_607:SILK +!CREATURE:FORGOTTEN_BEAST_611:SILK +!CREATURE:FORGOTTEN_BEAST_618:SILK +!CREATURE:FORGOTTEN_BEAST_621:SILK +!CREATURE:FORGOTTEN_BEAST_627:SILK +!CREATURE:FORGOTTEN_BEAST_638:SILK +!CREATURE:FORGOTTEN_BEAST_639:SILK +!CREATURE:FORGOTTEN_BEAST_651:SILK +!CREATURE:FORGOTTEN_BEAST_659:SILK +!CREATURE:FORGOTTEN_BEAST_670:SILK +!CREATURE:FORGOTTEN_BEAST_686:SILK +!CREATURE:FORGOTTEN_BEAST_692:SILK +!CREATURE:FORGOTTEN_BEAST_695:SILK +!CREATURE:FORGOTTEN_BEAST_697:SILK +!CREATURE:FORGOTTEN_BEAST_699:SILK +!CREATURE:FORGOTTEN_BEAST_707:SILK +!CREATURE:FORGOTTEN_BEAST_713:SILK +!CREATURE:FORGOTTEN_BEAST_723:SILK +!CREATURE:FORGOTTEN_BEAST_733:SILK +!CREATURE:FORGOTTEN_BEAST_740:SILK +!CREATURE:FORGOTTEN_BEAST_741:SILK +!CREATURE:FORGOTTEN_BEAST_747:SILK +!CREATURE:FORGOTTEN_BEAST_758:SILK +!CREATURE:FORGOTTEN_BEAST_766:SILK +!CREATURE:FORGOTTEN_BEAST_769:SILK +!CREATURE:FORGOTTEN_BEAST_771:SILK +!CREATURE:FORGOTTEN_BEAST_790:SILK +!CREATURE:FORGOTTEN_BEAST_794:SILK +!CREATURE:FORGOTTEN_BEAST_826:SILK +!CREATURE:FORGOTTEN_BEAST_827:SILK +!CREATURE:FORGOTTEN_BEAST_837:SILK +!CREATURE:FORGOTTEN_BEAST_839:SILK +!CREATURE:FORGOTTEN_BEAST_846:SILK +!CREATURE:FORGOTTEN_BEAST_847:SILK +!CREATURE:FORGOTTEN_BEAST_849:SILK +!CREATURE:FORGOTTEN_BEAST_850:SILK +!CREATURE:FORGOTTEN_BEAST_855:SILK +CREATURE:TITAN_1:SILK +CREATURE:TITAN_5:SILK +CREATURE:TITAN_18:SILK +CREATURE:TITAN_24:SILK +CREATURE:TITAN_28:SILK +CREATURE:TITAN_29:SILK +CREATURE:TITAN_30:SILK +CREATURE:TITAN_31:SILK +CREATURE:DEMON_13:SILK +CREATURE:DEMON_15:SILK +CREATURE:DEMON_43:SILK +CREATURE:DEMON_46:SILK +CREATURE:DEMON_49:SILK +CREATURE:DEMON_50:SILK +CREATURE:DEMON_52:SILKPLANT:FLAX:THREADPLANT:JUTE:THREADPLANT:HEMP:THREADPLANT:COTTON:THREADPLANT:RAMIE:THREADPLANT:KENAF:THREADPLANT:GRASS_TAIL_PIG:THREADPLANT:REED_ROPE:THREADCREATURE:SHEEP:HAIRCREATURE:LLAMA:HAIRCREATURE:ALPACA:HAIRCREATURE:TROLL:HAIR \ No newline at end of file diff --git a/data/stockpiles/toys.dfstock b/data/stockpiles/toys.dfstock new file mode 100644 index 000000000..a90eab591 --- /dev/null +++ b/data/stockpiles/toys.dfstock @@ -0,0 +1,2 @@ +b +TOY \ No newline at end of file diff --git a/data/stockpiles/trapcomponents.dfstock b/data/stockpiles/trapcomponents.dfstock new file mode 100644 index 000000000..475b530d4 Binary files /dev/null and b/data/stockpiles/trapcomponents.dfstock differ diff --git a/data/stockpiles/traps.dfstock b/data/stockpiles/traps.dfstock new file mode 100644 index 000000000..ed1ba6776 Binary files /dev/null and b/data/stockpiles/traps.dfstock differ diff --git a/data/stockpiles/unpreparedfish.dfstock b/data/stockpiles/unpreparedfish.dfstock new file mode 100644 index 000000000..13841da90 Binary files /dev/null and b/data/stockpiles/unpreparedfish.dfstock differ diff --git a/data/stockpiles/unusablearmor.dfstock b/data/stockpiles/unusablearmor.dfstock new file mode 100644 index 000000000..d5b2e9c64 Binary files /dev/null and b/data/stockpiles/unusablearmor.dfstock differ diff --git a/data/stockpiles/unusableweapons.dfstock b/data/stockpiles/unusableweapons.dfstock new file mode 100644 index 000000000..6af8d1b5a Binary files /dev/null and b/data/stockpiles/unusableweapons.dfstock differ diff --git a/data/stockpiles/usablearmor.dfstock b/data/stockpiles/usablearmor.dfstock new file mode 100644 index 000000000..58c9296de Binary files /dev/null and b/data/stockpiles/usablearmor.dfstock differ diff --git a/data/stockpiles/usablehair.dfstock b/data/stockpiles/usablehair.dfstock new file mode 100644 index 000000000..2082d7dcf Binary files /dev/null and b/data/stockpiles/usablehair.dfstock differ diff --git a/data/stockpiles/usableweapons.dfstock b/data/stockpiles/usableweapons.dfstock new file mode 100644 index 000000000..248610ae9 Binary files /dev/null and b/data/stockpiles/usableweapons.dfstock differ diff --git a/data/stockpiles/wax.dfstock b/data/stockpiles/wax.dfstock new file mode 100644 index 000000000..35c69f6ca Binary files /dev/null and b/data/stockpiles/wax.dfstock differ diff --git a/data/stockpiles/woodammo.dfstock b/data/stockpiles/woodammo.dfstock new file mode 100644 index 000000000..6bec8b1e5 --- /dev/null +++ b/data/stockpiles/woodammo.dfstock @@ -0,0 +1 @@ +BWOOD \ No newline at end of file diff --git a/data/stockpiles/woodtools.dfstock b/data/stockpiles/woodtools.dfstock new file mode 100644 index 000000000..86eb0d0c8 --- /dev/null +++ b/data/stockpiles/woodtools.dfstock @@ -0,0 +1,2 @@ +b +TOOLWOOD \ No newline at end of file diff --git a/depends/clsocket b/depends/clsocket index 6ed8aa464..d5e17c601 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit 6ed8aa46462ea01a1122fc49422840a2facc9757 +Subproject commit d5e17c6012e7eefb0cbe3e130a56c24bd11f0094 diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index c3ff0c16f..efded915f 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -1,5 +1,5 @@ project(lua CXX) -cmake_minimum_required(VERSION 2.8) +cmake_minimum_required(VERSION 3.21) set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -DLUA_USE_APICHECK") diff --git a/depends/lua/src/ldo.c b/depends/lua/src/ldo.c index 316e45c8f..65158df0b 100644 --- a/depends/lua/src/ldo.c +++ b/depends/lua/src/ldo.c @@ -767,14 +767,14 @@ static void f_parser (lua_State *L, void *ud) { LClosure *cl; struct SParser *p = cast(struct SParser *, ud); int c = zgetc(p->z); /* read first character */ - if (c == LUA_SIGNATURE[0]) { - checkmode(L, p->mode, "binary"); - cl = luaU_undump(L, p->z, p->name); - } - else { + // if (c == LUA_SIGNATURE[0]) { + // checkmode(L, p->mode, "binary"); + // cl = luaU_undump(L, p->z, p->name); + // } + // else { checkmode(L, p->mode, "text"); cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c); - } + // } lua_assert(cl->nupvalues == cl->p->sizeupvalues); luaF_initupvals(L, cl); } diff --git a/depends/xlsxio b/depends/xlsxio index 439fdbc25..0a9945266 160000 --- a/depends/xlsxio +++ b/depends/xlsxio @@ -1 +1 @@ -Subproject commit 439fdbc259c13f23a3122e68ba35ad5a13bcd97c +Subproject commit 0a994526622c2201756e386ef98b44b193e25f06 diff --git a/docs/Core.rst b/docs/Core.rst index 86e357b27..763858b61 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -53,7 +53,7 @@ double quotes. To include a double quote character, use ``\"``. If the first non-whitespace character is ``:``, the command is parsed in an alternative mode. The non-whitespace characters following the ``:`` are the command name, and the remaining part of the line is used verbatim as -the first argument. This is very useful for the `lua` and `rb` commands. +the first argument. This is very useful for the `lua` command. As an example, the following two command lines are exactly equivalent:: :foo a b "c d" e f @@ -235,10 +235,57 @@ root DF folder): #. :file:`dfhack-config/scripts` #. :file:`save/{world}/scripts` (only if a save is loaded) #. :file:`hack/scripts` +#. :file:`data/installed_mods/...` (see below) For example, if ``teleport`` is run, these folders are searched in order for ``teleport.lua``, and the first matching file is run. +Scripts in installed mods +......................... + +Scripts in mods are automatically added to the script path. The following +directories are searched for mods:: + + ../../workshop/content/975370/ (the DF Steam workshop directory) + mods/ + data/installed_mods/ + +Each mod can have two directories that contain scripts: + +- ``scripts_modactive/`` is added to the script path if and only if the mod is + active in the loaded world. +- ``scripts_modinstalled/`` is added to the script path as long as the mod is + installed in one of the searched mod directories. + +Multiple versions of a mod may be installed at the same time. If a mod is +active in a loaded world, then the scripts for the version of the mod that is +active will be added to the script path. Otherwise, the latest version of each +mod is added to the script path. + +Scripts for active mods take precedence according to their load order when you +generated the current world. + +Scripts for non-active mods are ordered by their containing mod's ID. + +For example, the search paths for mods might look like this:: + + activemod_last_in_load_order/scripts_modactive + activemod_last_in_load_order/scripts_modinstalled + activemod_second_to_last_in_load_order/scripts_modactive + activemod_second_to_last_in_load_order/scripts_modinstalled + ... + inactivemod1/scripts_modinstalled + inactivemod2/scripts_modinstalled + ... + +Not all mods will have script directories, of course, and those mods will not be +added to the script search path. Mods are re-scanned whenever a world is loaded +or unloaded. For more information on scripts and mods, check out the +`modding-guide`. + +Custom script paths +................... + Script paths can be added by modifying :file:`dfhack-config/script-paths.txt`. Each line should start with one of these characters: @@ -259,6 +306,23 @@ the root DF folder. Note that ``script-paths.txt`` is only read at startup, but the paths can also be modified programmatically at any time through the `Lua API `. +Commandline options +=================== + +In addition to `Using an OS terminal`_ to execute commands on startup, DFHack +also recognizes a single commandline option that can be specified on the +commandline: + +- ``--disable-dfhack``: If this option is passed on the Dwarf Fortress + commandline, then DFHack will be disabled for the session. You will have to + restart Dwarf Fortress without specifying this option in order to use DFHack. + If you are launching Dwarf Fortress from Steam, you can enter the option in + the "Launch Options" text box in the properties for the Dwarf Fortress app. + Note that if you do this, DFHack will be disabled regardless of whether you + run Dwarf Fortress from its own app or DFHack's. You will have to clear the + DF Launch Options in order to use DFHack again. Note that even if DFHack is + disabled, :file:`stdout.txt` and :file:`stderr.txt` will still be redirected + to :file:`stdout.log` and :file:`stderr.log`, respectively. .. _env-vars: @@ -272,6 +336,11 @@ on UNIX-like systems: DFHACK_SOME_VAR=1 ./dfhack +- ``DFHACK_DISABLE``: if set, DFHack will not initialize, not even to redirect + standard output or standard error. This is provided as an alternative + to the ``--disable-dfhack`` commandline parameter above for when environment + variables are more convenient. + - ``DFHACK_PORT``: the port to use for the RPC server (used by ``dfhack-run`` and `remotefortressreader` among others) instead of the default ``5000``. As with the default, if this port cannot be used, the server is not started. diff --git a/docs/Installing.rst b/docs/Installing.rst index bc58a39b7..8a04b7cb9 100644 --- a/docs/Installing.rst +++ b/docs/Installing.rst @@ -7,78 +7,50 @@ Installing DFHack .. contents:: :local: - Requirements ============ -DFHack supports Windows, Linux, and macOS, and both 64-bit and 32-bit builds -of Dwarf Fortress. +DFHack supports all operating systems and platforms that Dwarf Fortress itself +supports, which at the moment is just 64-bit Windows. However, the Windows +build of DFHack works well under ``wine`` (or ``Proton``, Steam's fork of +``wine``) on other operating systems. .. _installing-df-version: DFHack releases generally only support the version of Dwarf Fortress that they -are named after. For example, DFHack 0.40.24-r5 only supported DF 0.40.24. -DFHack releases *never* support newer versions of DF, because DFHack requires -data about DF that is only possible to obtain after DF has been released. -Occasionally, DFHack releases will be able to maintain support for older -versions of DF - for example, DFHack 0.34.11-r5 supported both DF 0.34.11 and -0.34.10. For maximum stability, you should usually use the latest versions of -both DF and DFHack. - -Windows -------- - -* DFHack only supports the SDL version of Dwarf Fortress. The "legacy" version - will *not* work with DFHack (the "small" SDL version is acceptable, however). -* Windows XP and older are *not* supported, due in part to a - `Visual C++ 2015 bug `_ - -The Windows build of DFHack should work under Wine on other operating systems, -although this is not tested very often. It is recommended to use the native -build for your operating system instead. - -.. _installing-reqs-linux: - -Linux ------ - -Generally, DFHack should work on any modern Linux distribution. There are -multiple release binaries provided - as of DFHack 0.47.04-r1, there are built -with GCC 7 and GCC 4.8 (as indicated by the ``gcc`` component of their -filenames). Using the newest build that works on your system is recommended. -The GCC 4.8 build is built on Ubuntu 14.04 and targets an older glibc, so it -should work on older distributions. - -In the event that none of the provided binaries work on your distribution, -you may need to `compile DFHack from source `. - -macOS ------ - -OS X 10.6.8 or later is required. - +are named after. For example, DFHack 50.05 only supported DF 50.05. DFHack +releases *never* support newer versions of DF -- DFHack requires data about DF +that is only possible to obtain after DF has been released. Occasionally, +DFHack releases will be able to maintain support for older versions of DF - for +example, DFHack 0.34.11-r5 supported both DF 0.34.11 and 0.34.10. For maximum +stability, you should usually use the latest versions of both DF and DFHack. .. _downloading: Downloading DFHack ================== -Stable builds of DFHack are available on `GitHub `_. -GitHub has been known to change their layout periodically, but as of July 2020, -downloads are available at the bottom of the release notes for each release, under a section -named "Assets" (which you may have to expand). The name of the file indicates -which DF version, platform, and architecture the build supports - the platform -and architecture (64-bit or 32-bit) **must** match your build of DF. The DF -version should also match your DF version - see `above ` -for details. For example: +Stable builds of DFHack are available on +`Steam `__ +or from our `GitHub `__. Either +location will give you exactly the same package. + +On Steam, note that DFHack is a separate app, not a DF Steam Workshop mod. You +can run DF with DFHack by launching either the DFHack app or the original Dwarf +Fortress app. + +If you download from GitHub, downloads are available at the bottom of the +release notes for each release, under a section named "Assets" (which you may +have to expand). The name of the file indicates which DF version, platform, and +architecture the build supports - the platform and architecture (64-bit or +32-bit) **must** match your build of DF. The DF version should also match your +DF version - see `above ` for details. For example: -* ``dfhack-0.47.04-r1-Windows-64bit.zip`` supports 64-bit DF on Windows -* ``dfhack-0.47.04-r1-Linux-32bit-gcc-7.tar.bz2`` supports 32-bit DF on Linux - (see `installing-reqs-linux` for details on the GCC version indicator) +* ``dfhack-50.07-r1-Windows-64bit.zip`` supports 64-bit DF on Windows -The `DFHack website `_ also provides links to -unstable builds. These files have a different naming scheme, but the same -restrictions apply (e.g. a file named ``Windows64`` is for 64-bit Windows DF). +In between stable releases, we may create beta releases to test new features. +These are available via the beta release channel on Steam or from our regular +Github page as a pre-release tagged with a "beta" suffix. .. warning:: @@ -91,79 +63,46 @@ restrictions apply (e.g. a file named ``Windows64`` is for 64-bit Windows DF). Installing DFHack ================= +If you are installing from Steam, this is handled for you automatically. The +instructions here are for manual installs. + When you `download DFHack `, you will end up with a release archive (a ``.zip`` file on Windows, or a ``.tar.bz2`` file on other platforms). Your operating system should have built-in utilities capable of extracting files from these archives. -The release archives contain several folders, including a ``hack`` folder where -DFHack binary and system data is stored, a ``dfhack-config`` folder where user -data and configuration is stored, and a ``blueprints`` folder where `quickfort` -blueprints are stored. To install DFHack, copy all of the files from the DFHack -archive into the root DF folder, which should already include a ``data`` folder -and a ``raw`` folder, among other things. Some packs and other redistributions -of Dwarf Fortress may place DF in another folder, so ensure that the ``hack`` -folder ends up next to the ``data`` folder. - -.. note:: - - On Windows, installing DFHack will overwrite ``SDL.dll``. This is - intentional and necessary for DFHack to work, so be sure to choose to - overwrite ``SDL.dll`` if prompted. (If you are not prompted, you may be - installing DFHack in the wrong place.) - +The release archives contain a ``hack`` folder where DFHack binary and system +data is stored, a ``stonesense`` folder that contains data specific to the +`stonesense` 3d renderer, and various libraries and executable files. To +install DFHack, copy all of the files from the DFHack archive into the root DF +folder, which should already include a ``data`` folder and a ``save`` folder, +among other things. Some redistributions of Dwarf Fortress may place DF in +another folder, so ensure that the ``hack`` folder ends up next to the ``data`` +folder, and you'll be fine. Uninstalling DFHack =================== -Uninstalling DFHack essentially involves reversing what you did to install -DFHack. On Windows, replace ``SDL.dll`` with ``SDLreal.dll`` first. Then, you -can remove any files that were part of the DFHack archive. DFHack does not -currently maintain a list of these files, so if you want to completely remove -them, you should consult the DFHack archive that you installed for a full list. -Generally, any files left behind should not negatively affect DF. +Just renaming or removing the ``dfhooks`` library file is enough to disable +DFHack. If you would like to remove all DFHack files, consult the DFHack install +archive to see the list of files and remove the corresponding files in the Dwarf +Fortress folder. Any DFHack files left behind will not negatively affect DF. +On Steam, uninstalling DFHack will cleanly remove everything that was installed +with DFHack, so there is nothing else for you to do. + +Note that Steam will leave behind the ``dfhack-config`` folder, which contains +all your personal DFHack-related settings and data. If you keep this folder, +all your settings will be restored when you reinstall DFHack later. Upgrading DFHack ================ -The recommended approach to upgrade DFHack is to uninstall DFHack first, then -install the new version. This will ensure that any files that are only part -of the older DFHack installation do not affect the new DFHack installation -(although this is unlikely to occur). - -It is also possible to overwrite an existing DFHack installation in-place. -To do this, follow the installation instructions above, but overwrite all files -that exist in the new DFHack archive (on Windows, this includes ``SDL.dll`` again). - -.. note:: - - You may wish to make a backup of your ``dfhack-config`` folder first if you - have made changes to it. Some archive managers (e.g. Archive Utility on macOS) - will overwrite the entire folder, removing any files that you have added. - - -Pre-packaged DFHack installations -================================= - -There are :wiki:`several packs available ` that include -DF, DFHack, and other utilities. If you are new to Dwarf Fortress and DFHack, -these may be easier to set up. Note that these packs are not maintained by the -DFHack team and vary in their release schedules and contents. Some may make -significant configuration changes, and some may not include DFHack at all. - -Linux packages -============== - -Third-party DFHack packages are available for some Linux distributions, -including in: +Again, if you have installed from Steam, your copy of DFHack will automatically be kept up to date. This section is for manual installers. -* `AUR `__, for Arch and related - distributions -* `RPM Fusion `__, - for Fedora and related distributions +First, remove the ``hack`` and ``stonesense`` folders in their entirety. This +ensures that files that don't exist in the latest version are properly removed +and don't affect your new installation. -Note that these may lag behind DFHack releases. If you want to use a newer -version of DFHack, we generally recommended installing it in a clean copy of DF -in your home folder. Attempting to upgrade an installation of DFHack from a -package manager may break it. +Then, extract the DFHack release archive into your Dwarf Fortress folder, +overwriting any remaining top-level files. diff --git a/docs/Quickstart.rst b/docs/Quickstart.rst index 55cfe6f36..f4a022a9c 100644 --- a/docs/Quickstart.rst +++ b/docs/Quickstart.rst @@ -5,19 +5,19 @@ Quickstart guide Welcome to DFHack! This guide will help get you oriented with the DFHack system and teach you how to find and use the tools productively. If you're reading this -in `quickstart-guide`, hit the right arrow key or click on the hotkey hint in -the lower right corner of the window to go to the next page. +in the in-game `quickstart-guide` reader, hit the right arrow key or click on +the hotkey hint in the lower right corner of the window to go to the next page. What is DFHack? --------------- -DFHack is a framework for Dwarf Fortress that provides a unified, cross-platform -environment that enables mods and tools to significantly extend the game. The -default DFHack distribution contains a wide variety of tools, including bugfixes, -interface improvements, automation agents, design blueprints, modding building -blocks, and more. Third-party tools (e.g. mods downloaded from Steam Workshop or -the forums) can also seamlessly integrate with the DFHack framework and extend -the game far beyond what can be done by just modding the raws. +DFHack is an add-on for Dwarf Fortress that enables mods and tools to +significantly extend the game. The default DFHack distribution contains a wide +variety of tools, including bugfixes, interface improvements, automation agents, +design blueprints, modding building blocks, and more. Third-party tools (e.g. +mods downloaded from Steam Workshop or the forums) can also seamlessly integrate +with the DFHack framework and extend the game far beyond what can be done by +just modding the raws. DFHack's mission is to provide tools and interfaces for players and modders to: @@ -162,7 +162,7 @@ You can run them all from the launcher. First, let's import some useful manager orders to keep your fort stocked with basic necessities. Run ``orders import library/basic``. If you go to your -mangager orders screen, you can see all the orders that have been created for you. +manager orders screen, you can see all the orders that have been created for you. Note that you could have imported the orders directly from this screen as well, using the DFHack `overlay` widget at the bottom of the manager orders panel. diff --git a/docs/Tags.rst b/docs/Tags.rst index 60a6a6813..ded15c4c2 100644 --- a/docs/Tags.rst +++ b/docs/Tags.rst @@ -21,7 +21,9 @@ for the tag assignment spreadsheet. "why" tags ---------- -- `armok `: Tools that give you complete control over an aspect of the game or provide access to information that the game intentionally keeps hidden. +- `armok `: Tools which give the player god-like powers of any variety, such as control over game events, creating items from thin air, or viewing information the game intentionally keeps hidden. Players that do not wish to see these tools listed in DFHack command lists can hide them in the ``Preferences`` tab of `gui/control-panel`. + + - `auto `: Tools that run in the background and automatically manage routine, toilsome aspects of your fortress. - `bugfix `: Tools that fix specific bugs, either permanently or on-demand. - `design `: Tools that help you design your fort. @@ -49,4 +51,4 @@ for the tag assignment spreadsheet. "misc" tags ----------- -- `untested `: Tools that are not yet tested with the current release. +- `unavailable `: Tools that are not yet available for the current release. diff --git a/docs/about/Authors.rst b/docs/about/Authors.rst index 75b9eb4f7..cf74c6412 100644 --- a/docs/about/Authors.rst +++ b/docs/about/Authors.rst @@ -12,6 +12,7 @@ Name Github Other 8Z 8Z Abel abstern acwatkins acwatkins +Alex Blamey Cubittus Alexander Collins gearsix Alexander Gavrilov angavrilov ag Amber Brown hawkowl @@ -133,6 +134,7 @@ Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo mizipzor mizipzor moversti moversti +mrrho mrrho Murad Beybalaev Erquint Myk Taylor myk002 napagokc napagokc @@ -158,6 +160,7 @@ Petr Mrázek peterix Pfhreak Pfhreak Pierre Lulé plule Pierre-David Bélanger pierredavidbelanger +PopnROFL PopnROFL potato ppaawwll ppaawwll ðŸ‡ðŸ‡ðŸ‡ðŸ‡ Priit Laes plaes @@ -201,6 +204,7 @@ SeerSkye SeerSkye seishuuu seishuuu Seth Woodworth sethwoodworth Shim Panze Shim-Panze +Silver silverflyone simon Simon Jackson sizeak Simon Lees simotek @@ -235,6 +239,7 @@ ViTuRaS ViTuRaS Vjek vjek Warmist warmist Wes Malone wesQ3 +Will H TSM-EVO Will Rogers wjrogers WoosterUK WoosterUK XianMaeve XianMaeve diff --git a/docs/about/Removed.rst b/docs/about/Removed.rst index 954aa2ce2..174fc56c4 100644 --- a/docs/about/Removed.rst +++ b/docs/about/Removed.rst @@ -18,6 +18,20 @@ An automated labor management tool that only addressed hauling labors, leaving t of skilled labors entirely up to the player. Fundamentally incompatible with the work detail system of labor management in v50 of Dwarf Fortress. +.. _automaterial: + +automaterial +============ +Moved frequently used materials to the top of the materials list when building +buildings. Also offered extended options when building constructions. All +functionality has been merged into `buildingplan`. + +.. _autounsuspend: + +autounsuspend +============= +Replaced by `suspendmanager`. + .. _combine-drinks: combine-drinks @@ -138,6 +152,12 @@ This script is no longer useful in current DF versions. The script required a binpatch `, which has not been available since DF 0.34.11. +.. _gui/dig: + +gui/dig +======= +Renamed to gui/design + .. _gui/hack-wish: gui/hack-wish @@ -179,6 +199,19 @@ show-unit-syndromes =================== Replaced with a GUI version: `gui/unit-syndromes`. +.. _stocksettings: + +stocksettings +============= +Along with ``copystock``, ``loadstock`` and ``savestock``, replaced with the new +`stockpiles` API. + +.. _title-version: + +title-version +============= +Replaced with an `overlay`. + .. _warn-stuck-trees: warn-stuck-trees diff --git a/docs/changelog.txt b/docs/changelog.txt index 4a3428031..60d858551 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -26,7 +26,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ]]] ================================================================================ -======== IMPORTANT: rename this, and add a new "future" section, BEFORE ======== +======== IMPORTANT: rename this, and add a new "Future" section, BEFORE ======== ======== making a new DFHack release, even if the only changes made ======== ======== were in submodules with their own changelogs! ======== ================================================================================ @@ -34,17 +34,188 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins +- `add-spatter`: reinstated: allow mods to add poisons and magical effects to weapons +- `changeitem`: reinstated: change item material, quality, and subtype +- `createitem`: reinstated: create arbitrary items, from the command line +- `deramp`: reinstated: removes all ramps designated for removal from the map +- `flows`: reinstated: counts map blocks with flowing liquids +- `lair`: reinstated: mark the map as a monster lair (this avoids item scatter when the fortress is abandoned) +- `luasocket`: reinstated: provides a Lua API for accessing network sockets +- `work-now`: reinstated, renamed from ``workNow``: reduce the time that dwarves are left without a task after completing a job ## Fixes --@ ``widgets.HotkeyLabel``: don't trigger on click if the widget is disabled -- ``dfhack.job.isSuitableMaterial``: now properly detects lack of fire and magma safety for vulnerable materials with high melting points +- DFHack screen backgrounds now use appropriate tiles in DF Classic +- RemoteServer: fix crash on malformed json in ``dfhack-config/remote-server.json`` +- `autolabor`: work detail override warning now only appears on the work details screen +- `RemoteFortressReader`: ensured names are transmitted in UTF-8 instead of CP437 ## Misc Improvements +- `autodump`: no longer checks for a keyboard cursor before executing, so ``autodump destroy`` (which doesn't require a cursor) can still function +- `orders`: update orders in orders library for prepared meals, bins, archer uniforms, and weapons +- Terminal console no longer appears in front of the game window on startup +- `gui/control-panel`: new preference for whether filters in lists search for substrings in the middle of words (e.g. if set to true, then "ee" will match "steel") +- `gui/design`: Improved performance for drawing shapes +- Dreamfort: improve traffic patterns throughout the fortress (stockpiles and zones are still not working, pending updates in `quickfort`) +- Core: For debugging purposes, you can now pass ``--disable-dfhack`` on the Dwarf Fortress commandline or specify ``DFHACK_DISABLE=1`` in the environment to disable DFHack for the current session. +- `overlay`: added links to the quickstart guide and the control panel on the DF title screen +- `gui/autodump`: fort-mode keybinding: Ctrl-H ## Documentation +## API + +## Lua +- ``overlay.reload()``: has been renamed to ``overlay.rescan()`` so as not to conflict with the global ``reload()`` function. If you are developing an overlay, please take note of the new function name for reloading your overlay during development. +- ``gui``: changed frame naming scheme to ``FRAME_X`` rather than ``X_FRAME``, and added aliases for backwards compatibility. (for example ``BOLD_FRAME`` is now called ``FRAME_BOLD``) + +## Removed +- `orders`: ``library/military_include_artifact_materials`` library file removed since recent research indicates that platinum blunt weapons and silver crossbows are not more effective than standard steel. the alternate military orders file was also causing unneeded confusion. + +# 50.08-r1 + +## Fixes +- `autoclothing`: eliminate game lag when there are many inventory items in the fort +- `buildingplan`: fixed size limit calculations for rollers +- `buildingplan`: fixed items not being checked for accessibility in the filter and item selection dialogs +- `dig-now`: properly detect and complete smoothing designations that have been converted into active jobs + +## Misc Improvements +- `buildingplan`: planner panel is minimized by default and now remembers minimized state +- `buildingplan`: can now filter by gems (for gem windows) and yarn (for ropes in wells) +- ``toggle-kbd-cursor``: add hotkey for toggling the keyboard cursor (Alt-K) +- ``version``: add alias to display the DFHack help (including the version number) so something happens when players try to run "version" +- `gui/control-panel`: add preference option for hiding the terminal console on startup +- `gui/control-panel`: add preference option for hiding "armok" tools in command lists +- ``Dwarf Therapist``: add a warning to the Labors screen when Dwarf Therapist is active so players know that changes they make to that screen will have no effect. If you're starting a new embark and nobody seems to be doing anything, check your Labors tab for this warning to see if Dwarf Therapist thinks it is in control (even if it's not running). +- `overlay`: add the DFHack version string to the DF title screen + +## Lua +- ``widgets.RangeSlider``: new mouse-controlled two-headed slider widget +- ``gui.ZScreenModal``: ZScreen subclass for modal dialogs +- ``widgets.CycleHotkeyLabel``: exposed "key_sep" and "option_gap" attributes for improved stylistic control. + +## Removed +- `title-version`: replaced by an `overlay` widget + +# 50.07-r1 + +## New Plugins +- `faststart`: speeds up the "Loading..." screen so the Main Menu appears faster + +## Fixes +-@ `hotkeys`: hotkey hints on menu popup will no longer get their last character cut off by the scrollbar +-@ ``launchdf``: launch Dwarf Fortress via the Steam client so Steam Workshop is functional +- `blueprint`: interpret saplings, shrubs, and twigs as floors instead of walls +- `combine`: fix error processing stockpiles with boundaries that extend outside of the map +-@ `prospector`: display both "raw" Z levels and "cooked" elevations +- `stockpiles`: fix crash when importing settings for gems from other worlds +-@ `stockpiles`: allow numbers in saved stockpile filenames + +## Misc Improvements +-@ `buildingplan`: items in the item selection dialog should now use the same item quality symbols as the base game +-@ `buildingplan`: hide planner overlay while the DF tutorial is active so that it can detect when you have placed the carpenter's workshop and bed and allow you to finish the tutorial +- `buildingplan`: can now filter by cloth and silk materials (for ropes) +-@ `buildingplan`: rearranged elements of ``planneroverlay`` interface +-@ `buildingplan`: rearranged elements of ``itemselection`` interface +-@ Mods: scripts in mods that are only in the steam workshop directory are now accessible. this means that a script-only mod that you never mark as "active" when generating a world will still receive automatic updates and be usable from in-game +-@ Mods: scripts from only the most recent version of an installed mod are added to the script path +-@ Mods: give active mods a chance to reattach their load hooks when a world is reloaded +- `gui/control-panel`: bugfix services are now enabled by default +- Core: hide DFHack terminal console by default when running on Steam Deck + +## Documentation +- `installing`: updated to include Steam installation instructions + +## Lua +- added two new window borders: ``gui.BOLD_FRAME`` for accented elements and ``gui.INTERIOR_MEDIUM_FRAME`` for a signature-less frame that's thicker than the existing ``gui.INTERIOR_FRAME`` + +# 50.07-beta2 + +## New Plugins +- `getplants`: reinstated: designate trees for chopping and shrubs for gathering according to type +- `prospector`: reinstated: get stone, ore, gem, and other tile property counts in fort mode. + +## Fixes +-@ `buildingplan`: filters are now properly applied to planned stairs +-@ `buildingplan`: existing carved up/down stairs are now taken into account when determining which stair shape to construct +- `buildingplan`: upright spike traps are now placed extended rather than retracted +- `buildingplan`: you can no longer designate constructions on tiles with magma or deep water, mirroring the vanilla restrictions +-@ `buildingplan`: fixed material filters getting lost for planning buildings on save/reload +-@ `buildingplan`: respect building size limits (e.g. roads and bridges cannot be more than 31 tiles in any dimension) +- `tailor`: properly discriminate between dyed and undyed cloth +-@ `tailor`: no longer default to using adamantine cloth for producing clothes +- `tailor`: take queued orders into account when calculating available materials +- `tailor`: skip units who can't wear clothes +- `tailor`: identify more available items as available, solving issues with over-production + +## Misc Improvements +- `buildingplan`: filters and global settings are now ignored when manually choosing items for a building, allowing you to make custom choices independently of the filters that would otherwise be used +- `buildingplan`: if `suspendmanager` is running, then planned buildings will be left suspended when their items are all attached. `suspendmanager` will unsuspsend them for construction when it is safe to do so. +- `buildingplan`: add option for autoselecting the last manually chosen item (like `automaterial` used to do) +- `confirm`: adds confirmation for removing burrows via the repaint menu +- `stockpiles`: support applying stockpile configurations with fully enabled categories to stockpiles in worlds other than the one where the configuration was exported from +- `stockpiles`: support partial application of a saved config based on dynamic filtering (e.g. disable all tallow in a food stockpile, even tallow from world-specific generated creatures) +- `stockpiles`: additive and subtractive modes when applying a second stockpile configuration on top of a first +- `stockpiles`: write player-exported stockpile configurations to the ``dfhack-config/stockpiles`` folder. If you have any stockpile configs in other directories, please move them to that folder. +- `stockpiles`: now includes a library of useful stockpile configs (see docs for details) +- `automelt`: now allows metal chests to be melted (workaround for DF bug 2493 is no longer needed) +- `orders`: add minimize button to overlay panel so you can get it out of the way to read long statue descriptions when choosing a subject in the details screen +- `orders`: add option to delete exported files from the import dialog +- `enable`: can now interpret aliases defined with the `alias` command +- Mods: scripts in mods are now automatically added to the DFHack script path. DFHack recognizes two directories in a mod's folder: ``scripts_modinstalled/`` and ``scripts_modactive/``. ``scripts_modinstalled/`` folders will always be added the script path, regardless of whether the mod is active in a world. ``scripts_modactive/`` folders will only be added to the script path when the mod is active in the current loaded world. + +## Documentation +- `modding-guide`: guide updated to include information for 3rd party script developers +- the ``untested`` tag has been renamed to ``unavailable`` to better reflect the status of the remaining unavaialable tools. most of the simply "untested" tools have now been tested and marked as working. the remaining tools are known to need development work before they are available again. + +## Lua +- ``widgets.Label``: tokens can now specify a ``htile`` property to indicate the tile that should be shown when the Label is hovered over with the mouse +- ``widgets.Label``: click handlers no longer get the label itself as the first param to the click handler +- ``widgets.CycleHotkeyLabel``: options that are bare integers will no longer be interpreted as the pen color in addition to being the label and value +- ``widgets.CycleHotkeyLabel``: option labels and pens can now be functions that return a label or pen + +# 50.07-beta1 + +## Fixes +-@ `buildingplan`: items are now attached correctly to screw pumps and other multi-item buildings +-@ `buildingplan`: buildings with different material filters will no longer get "stuck" if one of the filters currently matches no items +- `showmood` properly count required number of bars and cloth when they aren't the main item for the strange mood + +## Misc Improvements +-@ `buildingplan`: can now filter by clay materials +-@ `buildingplan`: remember choice per building type for whether the player wants to choose specific items +-@ `buildingplan`: you can now attach multiple weapons to spike traps +-@ `buildingplan`: can now filter by whether a slab is engraved +-@ `buildingplan`: add "minimize" button to temporarily get the planner overlay out of the way if you would rather use the vanilla UI for placing the current building +-@ `buildingplan`: add ``buildingplan reset`` command for resetting all filters to defaults +-@ `buildingplan`: rename "Build" button to "Confirm" on the item selection dialog and change the hotkey from "B" to "C" +- `blueprint`: now writes blueprints to the ``dfhack-config/blueprints`` directory +- `blueprint-library-guide`: library blueprints have moved from ``blueprints`` to ``hack/data/blueprints`` +- `blueprint-library-guide`: player-created blueprints should now go in the ``dfhack-config/blueprints`` folder. please move your existing blueprints from ``blueprints`` to ``dfhack-config/blueprints``. you don't need to move the library blueprints -- those can be safely deleted from the old ``blueprints`` directory. +-@ `showmood`: clarify how many bars and/or cloth items are actually needed for the mood + +## Removed +-@ `buildingplan`: "heat safety" setting is temporarily removed while we investigate incorrect item matching + +# 50.07-alpha3 + +## Fixes +-@ ``widgets.HotkeyLabel``: don't trigger on click if the widget is disabled +- ``dfhack.job.isSuitableMaterial``: now properly detects lack of fire and magma safety for vulnerable materials with high melting points +- `dig-now`: fixed multi-layer channel designations only channeling every second layer + +## Misc Improvements +- `dig-now`: added handling of dig designations that have been converted into active jobs +- `buildingplan`: entirely new UI for building placement, item selection, and materials filtering! + ## API - Gui focus strings will no longer get the "dfhack/" prefix if the string "dfhack/" already exists in the focus string +- ``Military``: New module for military functionality +- ``Military``: new ``makeSquad`` to create a squad +- ``Military``: changed ``getSquadName`` to take a squad identifier +- ``Military``: new ``updateRoomAssignments`` for assigning a squad to a barracks and archery range +- ``Maps::GetBiomeType`` renamed to ``Maps::getBiomeType`` for consistency +- ``Maps::GetBiomeTypeRef`` renamed to ``Maps::getBiomeTypeRef`` for consistency ## Lua - ``dfhack.job.attachJobItem()``: allows you to attach specific items to a job @@ -53,22 +224,24 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - ``widgets.CycleHotkeyLabel``: add ``label_below`` attribute for compact 2-line output -@ ``widgets.FilteredList``: search key matching is now case insensitive by default -@ ``gui.INTERIOR_FRAME``: a panel frame style for use in highlighting off interior areas of a UI +- ``maps.getBiomeType``: exposed preexisting function to Lua ## Removed -@ ``gui.THIN_FRAME``: replaced by ``gui.INTERIOR_FRAME`` +- `automaterial`: all functionality has been merged into `buildingplan` # 50.07-alpha2 ## Fixes -@ `nestboxes`: fixed bug causing nestboxes themselves to be forbidden, which prevented citizens from using them to lay eggs. Now only eggs are forbidden. -- `autobutcher`: implemented work-around for Dwarf Fortress not setting nicknames properly, so that nicknames created in the in-game interface are detected & protect animals from being butchered properly. Note that nicknames for unnamed units are not currently saved by dwarf fortress - use ``enable fix/protect-nicks`` to fix any nicknames created/removed within dwarf fortress so they can be saved/reloaded when you reload the game. +-@ `autobutcher`: implemented work-around for Dwarf Fortress not setting nicknames properly, so that nicknames created in the in-game interface are detected & protect animals from being butchered properly. Note that nicknames for unnamed units are not currently saved by dwarf fortress - use ``enable fix/protect-nicks`` to fix any nicknames created/removed within dwarf fortress so they can be saved/reloaded when you reload the game. -@ `seedwatch`: fix saving and loading of seed stock targets - `autodump`: changed behaviour to only change ``dump`` and ``forbid`` flags if an item is successfully dumped. -@ `autochop`: generate default names for burrows with no assigned names - ``Buildings::StockpileIterator``: fix check for stockpile items on block boundary. - `tailor`: block making clothing sized for toads; make replacement clothing orders use the size of the wearer, not the size of the garment -@ `confirm`: fix fps drop when enabled -- `channel-safely`: fix an out of bounds error regarding the REPORT event listener receiving (presumably) stale id's +-@ `channel-safely`: fix an out of bounds error regarding the REPORT event listener receiving (presumably) stale id's ## Misc Improvements - `autobutcher`: logs activity to the console terminal instead of making disruptive in-game announcements @@ -81,31 +254,24 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system - Units module: added new predicates for ``isGeldable()``, ``isMarkedForGelding()``, and ``isPet()`` -- ``Military``: New module for military functionality -- ``Military``: new ``makeSquad`` to create a squad -- ``Military``: changed ``getSquadName`` to take a squad identifier -- ``Military``: new ``updateRoomAssignments`` for assigning a squad to a barracks and archery range -- ``Maps::GetBiomeType`` renamed to ``Maps::getBiomeType`` for consistency -- ``Maps::GetBiomeTypeRef`` renamed to ``Maps::getBiomeTypeRef`` for consistency ## Lua - ``dfhack.gui.getSelectedCivZone``: returns the Zone that the user has selected currently - ``widgets.FilteredList``: Added ``edit_on_change`` optional parameter to allow a custom callback on filter edit change. - ``widgets.TabBar``: new library widget (migrated from control-panel.lua) -- ``maps.getBiomeType``: exposed preexisting function to Lua # 50.07-alpha1 ## Fixes - ``Units::isFortControlled``: Account for agitated wildlife -@ Fix right click sometimes closing both a DFHack window and a vanilla panel -- Fixed issue with scrollable lists having some data off-screen if they were scrolled before being made visible -- `channel-safely`: fixed bug resulting in marker mode never being set for any designation +-@ Fixed issue with scrollable lists having some data off-screen if they were scrolled before being made visible +-@ `channel-safely`: fixed bug resulting in marker mode never being set for any designation -@ `automelt`: fixed bug related to lua stack smashing behavior in returned stockpile configs -@ `autochop`: fixed bug related to lua stack smashing behavior in returned stockpile configs -- `nestboxes`: now cancels any in-progress hauling jobs when it protects a fertile egg +-@ `nestboxes`: now cancels any in-progress hauling jobs when it protects a fertile egg -@ Fix persisted data not being written on manual save -- `nestboxes`: now scans for eggs more frequently and cancels any in-progress hauling jobs when it protects a fertile egg +-@ `nestboxes`: now scans for eggs more frequently and cancels any in-progress hauling jobs when it protects a fertile egg ## Misc Improvements -@ `automelt`: is now more resistent to vanilla savegame corruption @@ -197,7 +363,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # 50.05-alpha2 ## Fixes -- `autofarm`: don't duplicate status line entries for crops with no current supply +-@ `autofarm`: don't duplicate status line entries for crops with no current supply -@ `orders`: allow the orders library to be listed and imported properly (if you previously copied the orders library into your ``dfhack-config/orders`` directory to work around this bug, you can remove those files now) - `tailor`: now respects the setting of the "used dyed clothing" standing order toggle diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index b5e66ef8b..1c1d75358 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -4371,6 +4371,13 @@ Here is an example skeleton for a ZScreen tool window:: view = view and view:raise() or MyScreen{}:show() +ZScreenModal class +------------------ + +A ZScreen convenience subclass that sets the attributes to something +appropriate for modal dialogs. The game is force paused, and no input is passed +through to the underlying viewscreens. + FramedScreen class ------------------ @@ -4388,23 +4395,34 @@ A framed screen has the following attributes: There are the following predefined frame style tables: -* ``WINDOW_FRAME`` +* ``FRAME_WINDOW`` A frame suitable for a draggable, optionally resizable window. -* ``PANEL_FRAME`` +* ``FRAME_PANEL`` A frame suitable for a static (non-draggable, non-resizable) panel. -* ``MEDIUM_FRAME`` +* ``FRAME_MEDIUM`` A frame suitable for overlay widget panels. -* ``INTERIOR_FRAME`` +* ``FRAME_BOLD`` + + A frame suitable for a non-draggable panel meant to capture the user's focus, + like an important notification, confirmation dialog or error message. - A frame suitable for light interior accent elements. This frame does *not* have - a visible ``DFHack`` signature on it, so it must not be used as the most external - frame for a DFHack-owned UI. +* ``FRAME_INTERIOR`` + + A frame suitable for light interior accent elements. This frame does *not* + have a visible ``DFHack`` signature on it, so it must not be used as the most + external frame for a DFHack-owned UI. + +* ``FRAME_INTERIOR_MEDIUM`` + + A copy of ``FRAME_MEDIUM`` that lacks the ``DFHack`` signature. Suitable for + panels that are part of a larger widget cluster. Must *not* be used as the + most external frame for a DFHack-owned UI. gui.widgets =========== @@ -4779,10 +4797,12 @@ containing newlines, or a table with the following possible fields: Specifies the number of character positions to advance on the line before rendering the token. -* ``token.tile = pen`` +* ``token.tile``, ``token.htile`` Specifies a pen or texture index (or a function that returns a pen or texture - index) to paint as one tile before the main part of the token. + index) to paint as one tile before the main part of the token. If ``htile`` + is specified, that is used instead of ``tile`` when the Label is hovered over + with the mouse. * ``token.width = ...`` @@ -4810,10 +4830,10 @@ containing newlines, or a table with the following possible fields: Same as the attributes of the label itself, but applies only to the token. -* ``token.pen``, ``token.dpen`` +* ``token.pen``, ``token.dpen``, ``token.hpen`` - Specify the pen and disabled pen to be used for the token's text. - The field may be either the pen itself, or a callback that returns it. + Specify the pen, disabled pen, and hover pen to be used for the token's text. + The fields may be either the pen itself, or a callback that returns it. * ``token.on_activate`` @@ -4940,15 +4960,20 @@ It has the following attributes: :key: The hotkey keycode to display, e.g. ``'CUSTOM_A'``. :key_back: Similar to ``key``, but will cycle backwards (optional) +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation. :label: The string (or a function that returns a string) to display after the hotkey. :label_width: The number of spaces to allocate to the ``label`` (for use in aligning a column of ``CycleHotkeyLabel`` labels). -:label_below: If ``true``, then the option value will apear below the label +:label_below: If ``true``, then the option value will appear below the label instead of to the right of it. Defaults to ``false``. +:option_gap: The size of the gap between the label text and the option value. + Default is ``1``. If set to ``0``, there'll be no gap between the strings. + If ``label_below`` == ``true``, negative values will shift the value leftwards. :options: A list of strings or tables of - ``{label=string, value=string[, pen=pen]}``. String options use the same - string for the label and value and the default pen. The optional ``pen`` + ``{label=string or fn, value=val[, pen=pen]}``. String options use the same + string for the label and value and use the default pen. The optional ``pen`` element could be a color like ``COLOR_RED``. :initial_option: The value or numeric index of the initial option. :on_change: The callback to call when the selected option changes. It is called @@ -5129,6 +5154,14 @@ The widget implements: Same as with an ordinary list. +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 with a command like:: + + :lua require('gui.widgets').FILTER_FULL_TEXT=true + TabBar class ------------ @@ -5136,7 +5169,9 @@ This widget implements a set of one or more tabs to allow navigation between gro the width of the window and will continue rendering on the next line(s) if all tabs cannot fit on a single line. :key: Specifies a keybinding that can be used to switch to the next tab. -:key_back: Specifies a keybinding that can be used to switch to the previous tab. + Defaults to ``CUSTOM_CTRL_T``. +:key_back: Specifies a keybinding that can be used to switch to the previous + tab. Defaults to ``CUSTOM_CTRL_Y``. :labels: A table of strings; entry representing the label text for a single tab. The order of the entries determines the order the tabs will appear in. :on_select: Callback executed when a tab is selected. It receives the selected tab index as an argument. The provided function @@ -5165,6 +5200,20 @@ widget does not require direct usage of ``Tab``. usage of ``Tab`` in ``TabBar:init()`` for an example. See the default value of ``active_tab_pens`` or ``inactive_tab_pens`` in ``TabBar`` for an example of how to construct pens. +RangeSlider class +----------------- + +This widget implements a mouse-interactable range-slider. The player can move its two handles to set minimum and maximum values +to define a range, or they can drag the bar itself to move both handles at once. +The parent widget owns the range values, and can control them independently (e.g. with ``CycleHotkeyLabels``). If the range values change, the ``RangeSlider`` appearance will adjust automatically. + +:num_stops: Used to specify the number of "notches" in the range slider, the places where handles can stop. + (this should match the parents' number of options) +:get_left_idx_fn: The function used by the RangeSlider to get the notch index on which to display the left handle. +:get_right_idx_fn: The function used by the RangeSlider to get the notch index on which to display the right handle. +:on_left_change: Callback executed when moving the left handle. +:on_right_change: Callback executed when moving the right handle. + .. _lua-plugins: ======= diff --git a/docs/dev/overlay-dev-guide.rst b/docs/dev/overlay-dev-guide.rst index 1a53e7f65..4ec226d30 100644 --- a/docs/dev/overlay-dev-guide.rst +++ b/docs/dev/overlay-dev-guide.rst @@ -183,7 +183,7 @@ Scripts #. If the script is not in your `script-paths`, install your script (see the `modding-guide` for help setting up a dev environment so that you don't need to reinstall your scripts after every edit). -#. Call ``:lua require('plugins.overlay').reload()`` to reload your overlay +#. Call ``:lua require('plugins.overlay').rescan()`` to reload your overlay widget Plugins @@ -194,7 +194,7 @@ Plugins :file:`hack/lua/plugins/` #. If you have changed the compiled plugin, `reload` it #. If you have changed the lua code, run ``:lua reload('plugins.mypluginname')`` -#. Call ``:lua require('plugins.overlay').reload()`` to reload your overlay +#. Call ``:lua require('plugins.overlay').rescan()`` to reload your overlay widget Troubleshooting diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst index 73d9407d6..38117503c 100644 --- a/docs/guides/modding-guide.rst +++ b/docs/guides/modding-guide.rst @@ -8,70 +8,128 @@ DFHack modding guide What is the difference between a script and a mod? -------------------------------------------------- -A script is a single file that can be run as a command in DFHack, like something -that modifies or displays game data on request. A mod is something you install -to get persistent behavioural changes in the game and/or add new content. Mods -can contain and use scripts in addition to (or instead of) modifications to the -DF game raws. - -DFHack scripts are written in Lua. If you don't already know Lua, there's a -great primer at `lua.org `__. +Well, sometimes there is no difference. A mod is anything you add to the game, +which can be graphics overrides, content in the raws, DFHack scripts, any, or +all. There are already resources out there for +`raws modding `__, so this +guide will focus more on scripts, both standalone and as an extension to +raws-based mods. + +A DFHack script is a Lua file that can be run as a command in +DFHack. Scripts can do pretty much anything, from displaying information to +enforcing new game mechanics. If you don't already know Lua, there's a great +primer at `lua.org `__. Why not just mod the raws? -------------------------- It depends on what you want to do. Some mods *are* better to do in just the -raws. You don't need DFHack to add a new race or modify attributes, for example. -However, DFHack scripts can do many things that you just can't do in the raws, -like make a creature that trails smoke. Some things *could* be done in the raws, -but writing a script is less hacky, easier to maintain, easier to extend, and is -not prone to side-effects. A great example is adding a syndrome when a reaction -is performed. If done in the raws, you have to create an exploding boulder to -apply the syndrome. DFHack scripts can add the syndrome directly and with much -more flexibility. In the end, complex mods will likely require a mix of raw -modding and DFHack scripting. +raws. You don't need DFHack to add a new race or modify attributes. However, +DFHack scripts can do many things that you just can't do in the raws, like make +a creature that trails smoke or launch a unit into the air when they are hit +with a certain type of projectile. Some things *could* be done in the raws, but +a script is better (e.g. easier to maintain, easier to extend, and/or not prone +to side-effects). A great example is adding a syndrome when a reaction +is performed. If done in the raws, you have to create an exploding boulder as +an intermediary to apply the syndrome. DFHack scripts can add the syndrome +directly and with much more flexibility. In the end, complex mods will likely +require a mix of raw modding and DFHack scripting. + +The structure of a mod +---------------------- + +In the example below, we'll use a mod name of ``example-mod``. I'm sure your +mods will have more creative names! Mods have a basic structure that looks like +this:: + + info.txt + graphics/... + objects/... + scripts_modactive/example-mod.lua + scripts_modactive/internal/example-mod/... + scripts_modinstalled/... + README.md (optional) + +Let's go through that line by line. + +- The :file:`info.txt` file contains metadata about your mod that DF will + display in-game. You can read more about this file in the + `Official DF Modding Guide `__. +- Modifications to the game raws (potentially with custom raw tokens) go in + the :file:`graphics/` and :file:`objects/` folders. You can read more about + the files that go in these directories on the :wiki:`Modding` wiki page. +- A control script in :file:`scripts_modactive/` directory that handles + system-level event hooks (e.g. reloading state when a world is loaded), + registering `overlays `, and + `enabling/disabling ` your mod. You can put other + scripts in this directory as well if you want them to appear as runnable + DFHack commands when your mod is active for the current world. Lua modules + that your main scripts use, but which don't need to be directly runnable by + the player, should go in a subdirectory under + :file:`scripts_modactive/internal/` so they don't show up in the DFHack + `launcher ` command autocomplete lists. +- Scripts that you want to be available before a world is loaded (i.e. on the + DF title screen) or that you want to be runnable in any world, regardless + of whether your mod is active, should go in the + :file:`scripts_modinstalled/` folder. You can also have an :file:`internal/` + subfolder in here for private modules if you like. +- Finally, a :file:`README.md` file that has more information about your mod. + If you develop your mod using version control (recommended!), that + :file:`README.md` file can also serve as your git repository documentation. + +These files end up in a subdirectory under :file:`mods/` when players copy them +in or install them from the +`Steam Workshop `__, and in +:file:`data/installed_mods/` when the mod is selected as "active" for the first +time. + +What if I just want to distribute a simple script? +-------------------------------------------------- + +If your mod is just a script with no raws modifications, things get a bit +simpler. All you need is:: + + info.txt + scripts_modinstalled/yourscript.lua + README.md (optional) + +Adding your script to the :file:`scripts_modinstalled/` folder will allow +DFHack to find it and add your mod to the `script-paths`. Your script will be +runnable from the title screen and in any loaded world, regardless of whether +your mod is explicitly "active". A mod-maker's development environment ------------------------------------- -While you're writing your mod, you need a place to store your in-development -scripts that will: +Create a folder for development somewhere outside your Dwarf Fortress +installation directory (e.g. ``/path/to/mymods/``). If you work on multiple +mods, you might want to make a subdirectory for each mod. -- be directly runnable by DFHack -- not get lost when you upgrade DFHack +If you have changes to the raws, you'll have to copy them into DF's +``data/installed_mods/`` folder to have them take effect, but you can set +things up so that scripts are run directly from your dev directory. This way, +you can edit your scripts and have the changes available in the game +immediately: no copying, no restarting. -The recommended approach is to create a directory somewhere outside of your DF -installation (let's call it "/path/to/own-scripts") and do all your script -development in there. +How does this magic work? Just add a line like this to your +``dfhack-config/script-paths.txt`` file:: -Inside your DF installation folder, there is a file named -:file:`dfhack-config/script-paths.txt`. If you add a line like this to that -file:: - - +/path/to/own-scripts + +/path/to/mymods/example-mod/scripts_modinstalled Then that directory will be searched when you run DFHack commands from inside the game. The ``+`` at the front of the path means to search that directory -first, before any other script directory (like :file:`hack/scripts` or -:file:`raw/scripts`). That way, your latest changes will always be used instead -of older copies that you may have installed in a DF directory. - -For scripts with the same name, the `order of precedence ` will -be: - -1. ``own-scripts/`` -2. ``dfhack-config/scripts/`` -3. ``save/*/scripts/`` -4. ``hack/scripts/`` +first, before any other script directory (like :file:`hack/scripts` or other +versions of your mod in the DF mod folders). The structure of the game ------------------------- -"The game" is in the global variable `df `. The game's memory can be -found in ``df.global``, containing things like the list of all items, whether to -reindex pathfinding, et cetera. Also relevant to us in ``df`` are the various -types found in the game, e.g. ``df.pronoun_type`` which we will be using in this -guide. We'll explore more of the game structures below. +"The game" is in the global variable `df `. Most of the information +relevant to a script is found in ``df.global.world``, which contains things +like the list of all items, whether to reindex pathfinding, et cetera. Also +relevant to us are the various data types found in the game, e.g. +``df.pronoun_type`` which we will be using in this guide. We'll explore more of +the game structures below. Your first script ----------------- @@ -83,8 +141,8 @@ First line, we get the unit:: local unit = dfhack.gui.getSelectedUnit() -If no unit is selected, an error message will be printed (which can be silenced -by passing ``true`` to ``getSelectedUnit``) and ``unit`` will be ``nil``. +If no unit is selected, ``unit`` will be ``nil`` and an error message will be +printed (which can be silenced by passing ``true`` to ``getSelectedUnit``). If ``unit`` is ``nil``, we don't want the script to run anymore:: @@ -94,33 +152,32 @@ If ``unit`` is ``nil``, we don't want the script to run anymore:: Now, the field ``sex`` in a unit is an integer, but each integer corresponds to a string value ("it", "she", or "he"). We get this value by indexing the -bidirectional map ``df.pronoun_type``. Indexing the other way, incidentally, -with one of the strings, will yield its corresponding number. So:: +bidirectional map ``df.pronoun_type``. Indexing the other way, with one of the +strings, will yield its corresponding number. So:: local pronounTypeString = df.pronoun_type[unit.sex] print(pronounTypeString) -Simple. Save this as a Lua file in your own scripts directory and run it as -shown before when a unit is selected in the Dwarf Fortress UI. +Simple. Save this as a Lua file in your own scripts directory and run it from +`gui/launcher` when a unit is selected in the Dwarf Fortress UI. -Exploring DF structures ------------------------ +Exploring DF state +------------------ So how could you have known about the field and type we just used? Well, there are two main tools for discovering the various fields in the game's data structures. The first is the ``df-structures`` `repository `__ that contains XML files -describing the contents of the game's structures. These are complete, but +describing the layouts of the game's structures. These are complete, but difficult to read (for a human). The second option is the `gui/gm-editor` -script, an interactive data explorer. You can run the script while objects like -units are selected to view the data within them. You can also run -``gui/gm-editor scr`` to view the data for the current screen. Press :kbd:`?` -while the script is active to view help. +interface, an interactive data explorer. You can run the script while objects +like units are selected to view the data within them. Press :kbd:`?` while the +script is active to view help. Familiarising yourself with the many structs of the game will help with ideas immensely, and you can always ask for help in the `right places `. -Detecting triggers +Reacting to events ------------------ The common method for injecting new behaviour into the game is to define a @@ -130,7 +187,7 @@ provides two libraries for this, ``repeat-util`` and `eventful `. frames (paused or unpaused), ticks (unpaused), in-game days, months, or years. If you need to be aware the instant something happens, you'll need to run a check once a tick. Be careful not to do this gratuitously, though, since -running that often can slow down the game! +running callbacks too often can slow down the game! ``eventful``, on the other hand, is much more performance-friendly since it will only call your callback when a relevant event happens, like a reaction or job @@ -327,7 +384,8 @@ Then, let's make a ``repeat-util`` callback for once a tick:: repeatUtil.scheduleEvery(modId, 1, "ticks", function() Let's iterate over every active unit, and for every unit, iterate over their -worn items to calculate how much we are going to take from their on-foot movement timers:: +worn items to calculate how much we are going to take from their on-foot +movement timers:: for _, unit in ipairs(df.global.world.units.active) do local amount = 0 @@ -341,82 +399,89 @@ worn items to calculate how much we are going to take from their on-foot movemen end -- Subtract amount from on-foot movement timers if not on ground if not unit.flags1.on_ground then - dfhack.units.subtractActionTimers(unit, amount, df.unit_action_type_group.MovementFeet) + dfhack.units.subtractActionTimers(unit, amount, + df.unit_action_type_group.MovementFeet) end end -The structure of a full mod ---------------------------- - -For reference, `Tachy Guns `__ is a -full mod that conforms to this guide. - -Create a folder for mod projects somewhere outside your Dwarf Fortress -installation directory (e.g. ``/path/to/mymods/``) and use your mod IDs as the -names for the mod folders within it. In the example below, we'll use a mod ID of -``example-mod``. I'm sure your mods will have more creative names! The -``example-mod`` mod will be developed in the ``/path/to/mymods/example-mod/`` -directory and has a basic structure that looks like this:: - - init.d/example-mod.lua - raw/objects/... - raw/scripts/example-mod.lua - raw/scripts/example-mod/... - README.md +Putting it all together +----------------------- -Let's go through that line by line. +Ok, you're all set up! Now, let's take a look at an example +``scripts_modinstalled/example-mod.lua`` file:: -* A short (one-line) script in ``init.d/`` to initialise your - mod when a save is loaded. -* Modifications to the game raws (potentially with custom raw tokens) go in - ``raw/objects/``. -* A control script in ``scripts/`` that handles enabling and disabling your - mod. -* A subfolder for your mod under ``scripts/`` will contain all the internal - scripts and/or modules used by your mod. + -- main file for example-mod -It is a good idea to use a version control system to organize changes to your -mod code. You can create a separate Git repository for each of your mods. The -``README.md`` file will be your mod help text when people browse to your online -repository. + -- these lines indicate that the script supports the "enable" + -- API so you can start it by running "enable example-mod" and + -- stop it by running "disable example-mod" + --@module = true + --@enable = true -Unless you want to install your ``raw/`` folder into your DF game folder every -time you make a change to your scripts, you should add your development scripts -directory to your script paths in ``dfhack-config/script-paths.txt``:: + -- this is the help text that will appear in `help` and + -- `gui/launcher`. see possible tags here: + -- https://docs.dfhack.org/en/latest/docs/Tags.html + --[====[ + example-mod + =========== - +/path/to/mymods/example-mod/scripts/ + Tags: fort | gameplay -Ok, you're all set up! Now, let's take a look at an example -``scripts/example-mod.lua`` file:: + Short one-sentence description ... - -- main setup and teardown for example-mod - -- this next line indicates that the script supports the "enable" - -- API so you can start it by running "enable example-mod" and stop - -- it by running "disable example-mod" - --@ enable = true + Longer description ... - local usage = [[ Usage ----- enable example-mod disable example-mod - ]] + ]====] + local repeatUtil = require('repeat-util') local eventful = require('plugins.eventful') -- you can reference global values or functions declared in any of -- your internal scripts - local moduleA = reqscript('example-mod/module-a') - local moduleB = reqscript('example-mod/module-b') - local moduleC = reqscript('example-mod/module-c') - local moduleD = reqscript('example-mod/module-d') + local moduleA = reqscript('internal/example-mod/module-a') + local moduleB = reqscript('internal/example-mod/module-b') + local moduleC = reqscript('internal/example-mod/module-c') + local moduleD = reqscript('internal/example-mod/module-d') + + local GLOBAL_KEY = 'example-mod' enabled = enabled or false - local modId = 'example-mod' + + function isEnabled() + -- this function is for the enabled API, the script won't show up on the + -- control panel without it + return enabled + end + + dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc == SC_MAP_UNLOADED then + dfhack.run_command('disable', 'example-mod') + + -- ensure our mod doesn't try to enable itself when a different + -- world is loaded where we are *not* active + dfhack.onStateChange[GLOBAL_KEY] = nil + + return + end + + if sc ~= SC_MAP_LOADED or df.global.gamemode ~= df.game_mode.DWARF then + return + end + + dfhack.run_command('enable', 'example-mod') + end + + if dfhack_flags.module then + return + end if not dfhack_flags.enable then - print(usage) + print(dfhack.script_help()) print() print(('Example mod is currently '):format( enabled and 'enabled' or 'disabled')) @@ -472,23 +537,17 @@ Ok, you're all set up! Now, let's take a look at an example enabled = false end -You can call ``enable example-mod`` and ``disable example-mod`` yourself while -developing, but for end users you can start your mod automatically from -``init.d/example-mod.lua``:: - - dfhack.run_command('enable example-mod') - -Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this:: +Inside ``scripts_modinstalled/internal/example-mod/module-a.lua`` you could +have code like this:: --@ module = true - -- The above line is required for reqscript to work function onLoad() -- global variables are exported -- do initialization here end - -- this is an internal function: local functions/variables - -- are not exported + -- this is a local function: local functions/variables + -- are not accessible to other scripts. local function usedByOnTick(unit) -- ... end @@ -499,6 +558,6 @@ Inside ``raw/scripts/example-mod/module-a.lua`` you could have code like this:: end end -The `reqscript ` function reloads scripts that have changed, so you can modify -your scripts while DF is running and just disable/enable your mod to load the -changes into your ongoing game! +The `reqscript ` function reloads scripts that have changed, so you +can modify your scripts while DF is running and just disable/enable your mod to +load the changes into your ongoing game! diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 74b384cc0..47d86cd4f 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -5,7 +5,7 @@ Quickfort blueprint library =========================== This guide contains a high-level overview of the blueprints available in the -:source:`quickfort blueprint library `. +:source:`quickfort blueprint library `. Each file is hyperlinked to its online version so you can see exactly what the blueprints do before you run them. Also, if you use `gui/quickfort`, you will @@ -18,8 +18,8 @@ Whole fort blueprint sets These files contain the plans for entire fortresses. Each file has one or more help sections that walk you through how to build the fort, step by step. -- :source:`library/dreamfort.csv ` -- :source:`library/quickfortress.csv ` +- :source:`library/dreamfort.csv ` +- :source:`library/quickfortress.csv ` .. _dreamfort: @@ -149,10 +149,10 @@ these ``#dig`` blueprints can only mark undug wall tiles for mining, they are best used underground. They won't do much on the surface, where there aren't many walls. -- :source:`library/layout-helpers/mark_up_left.csv ` -- :source:`library/layout-helpers/mark_up_right.csv ` -- :source:`library/layout-helpers/mark_down_right.csv ` -- :source:`library/layout-helpers/mark_down_left.csv ` +- :source:`library/layout-helpers/mark_up_left.csv ` +- :source:`library/layout-helpers/mark_up_right.csv ` +- :source:`library/layout-helpers/mark_down_right.csv ` +- :source:`library/layout-helpers/mark_down_left.csv ` Bedrooms -------- @@ -161,9 +161,9 @@ These are popular bedroom layouts from the :wiki:`Bedroom design` page on the wiki. Each file has ``#dig``, ``#build``, and ``#query`` blueprints to dig the rooms, build the furniture, and configure the beds as bedrooms, respectively. -- :source:`library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv ` -- :source:`library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv ` -- :source:`library/bedrooms/28-3-Modified_Windmill_Villas.csv ` +- :source:`library/bedrooms/48-4-Raynard_Whirlpool_Housing.csv ` +- :source:`library/bedrooms/95-9-Hactar1_3_Branch_Tree.csv ` +- :source:`library/bedrooms/28-3-Modified_Windmill_Villas.csv ` Tombs ----- @@ -171,8 +171,8 @@ Tombs These blueprints have burial plot layouts for fortress that expect a lot of casualties. -- :source:`library/tombs/Mini_Saracen.csv ` -- :source:`library/tombs/The_Saracen_Crypts.csv ` +- :source:`library/tombs/Mini_Saracen.csv ` +- :source:`library/tombs/The_Saracen_Crypts.csv ` Exploratory mining ------------------ @@ -181,18 +181,18 @@ Several mining patterns to choose from when searching for gems or ores. The patterns can be repeated up or down z-levels (via quickfort's ``--repeat`` commandline option) for exploring through the depths. -- :source:`library/exploratory-mining/tunnels.csv ` -- :source:`library/exploratory-mining/vertical-mineshafts.csv ` -- :source:`library/exploratory-mining/connected-mineshafts.csv ` +- :source:`library/exploratory-mining/tunnels.csv ` +- :source:`library/exploratory-mining/vertical-mineshafts.csv ` +- :source:`library/exploratory-mining/connected-mineshafts.csv ` Miscellaneous ------------- Extra blueprints that are useful in specific situations. -- :source:`library/aquifer_tap.csv ` -- :source:`library/embark.csv ` -- :source:`library/pump_stack.csv ` +- :source:`library/aquifer_tap.csv ` +- :source:`library/embark.csv ` +- :source:`library/pump_stack.csv ` Light aquifer tap ~~~~~~~~~~~~~~~~~ diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index ecee46918..ec416f18f 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -27,9 +27,9 @@ written by Joel Thornton, reused here with his permission. For those just looking to apply existing blueprints, check out the `quickfort command's documentation ` for syntax. There are also many -ready-to-use blueprints available in the ``blueprints/library`` subfolder in +ready-to-use blueprints available in the ``hack/data/blueprints`` subfolder in your DFHack installation. Browse them on your computer or -:source:`online `, or run `gui/quickfort` to browse +:source:`online `, or run `gui/quickfort` to browse and apply them to your fort! Before you become an expert at writing blueprints, though, you should know that @@ -248,7 +248,7 @@ You can save a lot of time and effort by using aliases instead of adding all key sequences directly to your blueprints. For more details, check out the `quickfort-alias-guide`. You can also see examples of aliases being used in the query blueprints in the -:source:`DFHack blueprint library `. You can create +:source:`DFHack blueprint library `. You can create your own aliases by adding them to :source:`dfhack-config/quickfort/aliases.txt` in your DFHack folder or you can package them `together with your blueprint files `. @@ -1467,7 +1467,7 @@ Dreamfort organization and packaging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The Dreamfort blueprints are distributed with DFHack as -:source:`one large .csv file `, but +:source:`one large .csv file `, but editing in that format would be frustrating. Instead, the blueprints are edited `online as Google drive spreadsheets `__. @@ -1825,7 +1825,7 @@ Links - `blueprint-library-guide` - :forums:`Quickfort forum thread <176889>` - :issue:`DFHack issue tracker <>` -- :source:`Blueprint library source ` +- :source:`Blueprint library source ` - :source-scripts:`Quickfort source code ` **Related tools:** diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst index 1d7acd94f..3112934ab 100644 --- a/docs/plugins/3dveins.rst +++ b/docs/plugins/3dveins.rst @@ -3,7 +3,7 @@ .. dfhack-tool:: :summary: Rewrite layer veins to expand in 3D space. - :tags: untested fort gameplay map + :tags: unavailable fort gameplay map Existing, flat veins are removed and new 3D veins that naturally span z-levels are generated in their place. The transformation preserves the mineral counts diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst index fc3c507c2..e8a39f781 100644 --- a/docs/plugins/add-spatter.rst +++ b/docs/plugins/add-spatter.rst @@ -2,8 +2,8 @@ add-spatter =========== .. dfhack-tool:: - :summary: Make tagged reactions produce contaminants. - :tags: untested adventure fort gameplay items + :summary: Add poisons and magical effects to weapons. + :tags: adventure fort gameplay items :no-command: Give some use to all those poisons that can be bought from caravans! The plugin diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst index f18b671a1..6135b39d2 100644 --- a/docs/plugins/autogems.rst +++ b/docs/plugins/autogems.rst @@ -3,7 +3,7 @@ autogems .. dfhack-tool:: :summary: Automatically cut rough gems. - :tags: untested fort auto workorders + :tags: unavailable fort auto workorders :no-command: .. dfhack-command:: autogems-reload diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst deleted file mode 100644 index 54653a685..000000000 --- a/docs/plugins/automaterial.rst +++ /dev/null @@ -1,53 +0,0 @@ -automaterial -============ - -.. dfhack-tool:: - :summary: Sorts building materials by recent usage. - :tags: untested fort design productivity buildings map - :no-command: - -This plugin makes building constructions (walls, floors, fortifications, etc) -much easier by saving you from having to trawl through long lists of materials -each time you place one. - -It moves the last used material for a given construction type to the top of the -list, if there are any left. So if you build a wall with chalk blocks, the next -time you place a wall the chalk blocks will be at the top of the list, -regardless of distance (it only does this in "grouped" mode, as individual item -lists could be huge). This means you can place most constructions without having -to search for your preferred material type. - -Usage ------ - -:: - - enable automaterial - -.. image:: ../images/automaterial-mat.png - -Pressing :kbd:`a` while highlighting any material will enable that material for -"auto select" for this construction type. You can enable multiple materials. Now -the next time you place this type of construction, the plugin will automatically -choose materials for you from the kinds you enabled. If there is enough to -satisfy the whole placement, you won't be prompted with the material screen at -all -- the construction will be placed and you will be back in the construction -menu. - -When choosing the construction placement, you will see a couple of options: - -.. image:: ../images/automaterial-pos.png - -Use :kbd:`a` here to temporarily disable the material autoselection, e.g. if you -need to go to the material selection screen so you can toggle some materials on -or off. - -The other option (auto type selection, off by default) can be toggled on with -:kbd:`t`. If you toggle this option on, instead of returning you to the main -construction menu after selecting materials, it returns you back to this screen. -If you use this along with several autoselect enabled materials, you should be -able to place complex constructions more conveniently. - -The ``automaterial`` plugin also enables extra construction placement modes, -such as designating areas larger than 10x10 and allowing you to designate hollow -rectangles instead of the default filled ones. diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst index 439eead40..24c1b42fa 100644 --- a/docs/plugins/autotrade.rst +++ b/docs/plugins/autotrade.rst @@ -3,7 +3,7 @@ autotrade .. dfhack-tool:: :summary: Quickly designate items to be traded. - :tags: untested fort productivity items stockpiles + :tags: unavailable fort productivity items stockpiles :no-command: When `enabled `, this plugin adds an option to the :kbd:`q` menu for diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst index b10921d30..6d2e9154f 100644 --- a/docs/plugins/blueprint.rst +++ b/docs/plugins/blueprint.rst @@ -9,7 +9,7 @@ With ``blueprint``, you can export the structure of a portion of your fortress in a blueprint file that you (or anyone else) can later play back with `gui/quickfort`. -Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` +Blueprints are ``.csv`` or ``.xlsx`` files created in the ``dfhack-config/blueprints`` subdirectory of your DF folder. The map area to turn into a blueprint is either selected interactively with the ``gui/blueprint`` command or, if the GUI is not used, starts at the active cursor location and extends right and down for the diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst index cce3479d8..b45b23be4 100644 --- a/docs/plugins/building-hacks.rst +++ b/docs/plugins/building-hacks.rst @@ -3,7 +3,7 @@ building-hacks .. dfhack-tool:: :summary: Provides a Lua API for creating powered workshops. - :tags: untested fort gameplay buildings + :tags: unavailable fort gameplay buildings :no-command: See `building-hacks-api` for more details. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index fe3683f77..9cc7e68a2 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -3,7 +3,7 @@ buildingplan .. dfhack-tool:: :summary: Plan building layouts with or without materials. - :tags: fort design buildings + :tags: fort design productivity buildings Buildingplan allows you to place furniture, constructions, and other buildings, regardless of whether the required materials are available. This allows you to @@ -20,7 +20,10 @@ building. Once all items are attached, the construction job will be unsuspended and a dwarf will come and build the building. If you have the `unsuspend` overlay enabled (it is enabled by default), then buildingplan-suspended buildings will appear with a ``P`` marker on the main map, as opposed to the -usual ``x`` marker for "regular" suspended buildings. +usual ``x`` marker for "regular" suspended buildings. If you have +`suspendmanager` running, then buildings will be left suspended when their +items are all attached and ``suspendmanager`` will unsuspend them for +construction when it is safe to do so. If you want to impose restrictions on which items are chosen for the buildings, buildingplan has full support for quality and material filters (see `below @@ -47,11 +50,14 @@ to build the planned buildings as they are produced, with minimal space dedicated to stockpiles. The DFHack `orders` library can help with setting these manager workorders up for you. -If you do not wish to use the ``buildingplan`` interface, you can turn off the +If you don't want to use the ``buildingplan`` interface for the building you're +currently trying to place, you can hit :kbd:`Alt`:kbd:`M` or click on the +minimize toggle in the upper right corner of the panel. If you do not wish to +ever use the ``buildingplan`` interface, you can turn off the ``buildingplan.planner`` overlay in `gui/control-panel` (on the "Overlays" -tab). You should not disable the ``buildingplan`` "System service" in -`gui/control-panel` since existing planned buildings in loaded forts will stop -functioning. +tab). Be sure to keep the ``buildingplan`` "System service" itself enabled in +`gui/control-panel` since if you turn it off, existing planned buildings in +saved forts will stop functioning. Usage ----- @@ -60,6 +66,7 @@ Usage buildingplan [status] buildingplan set (true|false) + buildingplan reset Examples -------- @@ -72,6 +79,10 @@ Examples When finding items to satisfy "building materials" requirements, don't select boulders. Use blocks or logs (if enabled) instead. +``buildingplan reset`` + Reset all settings and filters to their defaults. This command does not affect + existing planned buildings. + .. _buildingplan-settings: Global settings @@ -114,12 +125,8 @@ tiles selected in the construction area are not appropriate for building. For example, if you want to fill an area with flooring, you can select the entire area, and any tiles with existing buildings or walls will simply be skipped. -Setting heat safety filters -+++++++++++++++++++++++++++ - -If you specifically need the building to be magma- or fire-safe, click on the -"Building safety" button or hit :kbd:`g` until the desired heat safety is -displayed. This filter applies to all items used to construct the building. +For weapon and spike traps, you can choose how many weapons will be included +on this panel. Setting quality and material filters ++++++++++++++++++++++++++++++++++++ @@ -151,15 +158,17 @@ or hit :kbd:`i` before placing the building. When you click to place the building, a dialog will come up that allows you choose which items to use. The list is sorted by most recently used materials for that building type by default, but you can change to sort by name or by available quantity by -clicking on the "Sort by" selector or hitting :kbd:`R`. +clicking on the "Sort by" selector or hitting :kbd:`R`. The configuration for +whether you would like to choose specific items is saved per building type and +will be restored when you plan more of that building type. You can select the maximum quantity of a specified item by clicking on the item name or selecting it with the arrow keys and hitting :kbd:`Enter`. You can instead select items one at a time by Ctrl-clicking (:kbd:`Shift`:kbd:`Right`) to increment or Ctrl-Shift-clicking (:kbd:`Shift`:kbd:`Left`) to decrement. -Once you are satisfied with your choices, click on the "Build" button or hit -:kbd:`B` to continue building. Note that you don't have to select all the items +Once you are satisfied with your choices, click on the "Confirm" button or hit +:kbd:`C` to continue building. Note that you don't have to select all the items that the building needs. Any remaining items will be automatically chosen from other available items (or future items if not all items are available yet). If there are multiple item types to choose for the current building, one dialog diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst index 54812ece5..299656cf5 100644 --- a/docs/plugins/burrows.rst +++ b/docs/plugins/burrows.rst @@ -3,7 +3,7 @@ burrows .. dfhack-tool:: :summary: Auto-expand burrows as you dig. - :tags: untested fort auto design productivity map units + :tags: unavailable fort auto design productivity map units :no-command: .. dfhack-command:: burrow diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst index fe9229a09..f452f6a74 100644 --- a/docs/plugins/changeitem.rst +++ b/docs/plugins/changeitem.rst @@ -2,8 +2,8 @@ changeitem ========== .. dfhack-tool:: - :summary: Change item material or base quality. - :tags: untested adventure fort armok items + :summary: Change item material, quality, and subtype. + :tags: adventure fort armok items By default, a change is only allowed if the existing and desired item materials are of the same subtype (for example wood -> wood, stone -> stone, etc). But diff --git a/docs/plugins/channel-safely.rst b/docs/plugins/channel-safely.rst index 3acbe66cd..c5dbc37f6 100644 --- a/docs/plugins/channel-safely.rst +++ b/docs/plugins/channel-safely.rst @@ -3,7 +3,7 @@ channel-safely .. dfhack-tool:: :summary: Auto-manage channel designations to keep dwarves safe. - :tags: untested fort auto + :tags: fort auto Multi-level channel projects can be dangerous, and managing the safety of your dwarves throughout the completion of such projects can be difficult and time diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst index f0a34b909..1e6270187 100644 --- a/docs/plugins/confirm.rst +++ b/docs/plugins/confirm.rst @@ -5,8 +5,9 @@ confirm :summary: Adds confirmation dialogs for destructive actions. :tags: fort interface -Now you can get the chance to avoid accidentally disbanding a squad or deleting a -hauling route in case you hit the key accidentally. +In the base game, it is frightenly easy to destroy hours of work with a single +misclick. Now you can avoid the consequences of accidentally disbanding a squad +(for example), or deleting a hauling route. Usage ----- diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst index 7abe06cff..b29e41521 100644 --- a/docs/plugins/createitem.rst +++ b/docs/plugins/createitem.rst @@ -3,7 +3,7 @@ createitem .. dfhack-tool:: :summary: Create arbitrary items. - :tags: untested adventure fort armok items + :tags: adventure fort armok items You can create new items of any type and made of any material. A unit must be selected in-game to use this command. By default, items created are spawned at diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst index 641f370a6..fdc0f7619 100644 --- a/docs/plugins/deramp.rst +++ b/docs/plugins/deramp.rst @@ -3,7 +3,7 @@ deramp .. dfhack-tool:: :summary: Removes all ramps designated for removal from the map. - :tags: untested fort armok map + :tags: fort armok map It also removes any "floating" down ramps that can remain after a cave-in. diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst index 70dd9b388..ee502371a 100644 --- a/docs/plugins/digFlood.rst +++ b/docs/plugins/digFlood.rst @@ -3,7 +3,7 @@ digFlood .. dfhack-tool:: :summary: Digs out veins as they are discovered. - :tags: untested fort auto map + :tags: unavailable fort auto map Once you register specific vein types, this tool will automatically designate tiles of those types of veins for digging as your miners complete adjacent diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst index 4ea52013e..001636709 100644 --- a/docs/plugins/diggingInvaders.rst +++ b/docs/plugins/diggingInvaders.rst @@ -3,7 +3,7 @@ diggingInvaders .. dfhack-tool:: :summary: Invaders dig and destroy to get to your dwarves. - :tags: untested fort gameplay military units + :tags: unavailable fort gameplay military units Usage ----- diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst index 47d49d1e0..45a5d8f85 100644 --- a/docs/plugins/dwarfmonitor.rst +++ b/docs/plugins/dwarfmonitor.rst @@ -3,7 +3,7 @@ dwarfmonitor .. dfhack-tool:: :summary: Report on dwarf preferences and efficiency. - :tags: untested fort inspection jobs units + :tags: unavailable fort inspection jobs units It can also show heads-up display widgets with live fort statistics. diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst index 7480c10a6..b4dfe0ada 100644 --- a/docs/plugins/dwarfvet.rst +++ b/docs/plugins/dwarfvet.rst @@ -3,7 +3,7 @@ dwarfvet .. dfhack-tool:: :summary: Allows animals to be treated at animal hospitals. - :tags: untested fort gameplay animals + :tags: unavailable fort gameplay animals Annoyed that your dragons become useless after a minor injury? Well, with dwarfvet, injured animals will be treated at an animal hospital, which is simply diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst index bb74f221d..282d4b122 100644 --- a/docs/plugins/embark-assistant.rst +++ b/docs/plugins/embark-assistant.rst @@ -3,7 +3,7 @@ embark-assistant .. dfhack-tool:: :summary: Embark site selection support. - :tags: untested embark fort interface + :tags: unavailable embark fort interface Run this command while the pre-embark screen is displayed to show extended (and reasonably correct) resource information for the embark rectangle as well as diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst index 6a600019b..f320706ff 100644 --- a/docs/plugins/embark-tools.rst +++ b/docs/plugins/embark-tools.rst @@ -3,7 +3,7 @@ embark-tools .. dfhack-tool:: :summary: Extend the embark screen functionality. - :tags: untested embark fort interface + Usage ----- diff --git a/docs/plugins/faststart.rst b/docs/plugins/faststart.rst new file mode 100644 index 000000000..b39269e01 --- /dev/null +++ b/docs/plugins/faststart.rst @@ -0,0 +1,18 @@ +faststart +========= + +.. dfhack-tool:: + :summary: Makes the main menu appear sooner. + :tags: dfhack interface + :no-command: + +This plugin accelerates the initial "Loading..." screen that appears when the +game first starts, so you don't have to wait as long before the Main Menu +appears and you can start playing. + +Usage +----- + +:: + + enable faststart diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst index 8589f765a..6ba01b712 100644 --- a/docs/plugins/fix-unit-occupancy.rst +++ b/docs/plugins/fix-unit-occupancy.rst @@ -3,7 +3,7 @@ fix-unit-occupancy .. dfhack-tool:: :summary: Fix phantom unit occupancy issues. - :tags: untested fort bugfix map + :tags: unavailable fort bugfix map If you see "unit blocking tile" messages that you can't account for (:bug:`3499`), this tool can help. diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst index c13bbe9a1..552c5f8c3 100644 --- a/docs/plugins/fixveins.rst +++ b/docs/plugins/fixveins.rst @@ -3,7 +3,7 @@ fixveins .. dfhack-tool:: :summary: Restore missing mineral inclusions. - :tags: untested fort bugfix map + :tags: unavailable fort bugfix map This tool can also remove invalid references to mineral inclusions if you broke your embark with tools like `tiletypes`. diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst index 209bb577c..bbb2c6661 100644 --- a/docs/plugins/flows.rst +++ b/docs/plugins/flows.rst @@ -3,7 +3,7 @@ flows .. dfhack-tool:: :summary: Counts map blocks with flowing liquids. - :tags: untested fort inspection map + :tags: fort inspection map If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map. diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst index 47db40bd8..e72f79ce0 100644 --- a/docs/plugins/follow.rst +++ b/docs/plugins/follow.rst @@ -3,7 +3,7 @@ follow .. dfhack-tool:: :summary: Make the screen follow the selected unit. - :tags: untested fort interface units + :tags: unavailable fort interface units Once you exit from the current menu or cursor mode, the screen will stay centered on the unit. Handy for watching dwarves running around. Deactivated by diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst index d4c9a6c4a..2565d12c3 100644 --- a/docs/plugins/forceequip.rst +++ b/docs/plugins/forceequip.rst @@ -3,7 +3,7 @@ forceequip .. dfhack-tool:: :summary: Move items into a unit's inventory. - :tags: untested adventure fort animals items military units + :tags: unavailable adventure fort animals items military units This tool is typically used to equip specific clothing/armor items onto a dwarf, but can also be used to put armor onto a war animal or to add unusual items diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst index ba1761a1b..ea386eacf 100644 --- a/docs/plugins/generated-creature-renamer.rst +++ b/docs/plugins/generated-creature-renamer.rst @@ -3,7 +3,7 @@ generated-creature-renamer .. dfhack-tool:: :summary: Automatically renames generated creatures. - :tags: untested adventure fort legends units + :tags: unavailable adventure fort legends units :no-command: .. dfhack-command:: list-generated diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst index 756fa1aaa..05bac71c9 100644 --- a/docs/plugins/getplants.rst +++ b/docs/plugins/getplants.rst @@ -3,7 +3,7 @@ getplants .. dfhack-tool:: :summary: Designate trees for chopping and shrubs for gathering. - :tags: untested fort productivity plants + :tags: fort productivity plants Specify the types of trees to cut down and/or shrubs to gather by their plant names. diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst index 7e8ec2f31..789709c9f 100644 --- a/docs/plugins/infiniteSky.rst +++ b/docs/plugins/infiniteSky.rst @@ -3,7 +3,7 @@ infiniteSky .. dfhack-tool:: :summary: Automatically allocate new z-levels of sky - :tags: untested fort auto design map + :tags: unavailable fort auto design map If enabled, this plugin will automatically allocate new z-levels of sky at the top of the map as you build up. Or it can allocate one or many additional levels diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst index 2442f70ef..b792cb649 100644 --- a/docs/plugins/isoworldremote.rst +++ b/docs/plugins/isoworldremote.rst @@ -3,7 +3,7 @@ isoworldremote .. dfhack-tool:: :summary: Provides a remote API used by Isoworld. - :tags: untested dev graphics + :tags: unavailable dev graphics :no-command: See `remote` for related remote APIs. diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst index f68c200b2..674c6897a 100644 --- a/docs/plugins/jobutils.rst +++ b/docs/plugins/jobutils.rst @@ -5,7 +5,7 @@ jobutils .. dfhack-tool:: :summary: Provides commands for interacting with jobs. - :tags: untested fort inspection jobs + :tags: unavailable fort inspection jobs :no-command: .. dfhack-command:: job diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst index f808fcb59..731f706b8 100644 --- a/docs/plugins/labormanager.rst +++ b/docs/plugins/labormanager.rst @@ -3,7 +3,7 @@ labormanager .. dfhack-tool:: :summary: Automatically manage dwarf labors. - :tags: untested fort auto labors + :tags: unavailable fort auto labors Labormanager is derived from `autolabor` but uses a completely different approach to assigning jobs to dwarves. While autolabor tries to keep as many diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst index 77ad0a045..9bded57ab 100644 --- a/docs/plugins/lair.rst +++ b/docs/plugins/lair.rst @@ -3,7 +3,7 @@ lair .. dfhack-tool:: :summary: Mark the map as a monster lair. - :tags: untested fort armok map + :tags: fort armok map This avoids item scatter when the fortress is abandoned. diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst index 69f566e24..4b5b18540 100644 --- a/docs/plugins/luasocket.rst +++ b/docs/plugins/luasocket.rst @@ -3,7 +3,7 @@ luasocket .. dfhack-tool:: :summary: Provides a Lua API for accessing network sockets. - :tags: untested dev + :tags: dev :no-command: See `luasocket-api` for details. diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst index b4c0d6160..734800e77 100644 --- a/docs/plugins/manipulator.rst +++ b/docs/plugins/manipulator.rst @@ -3,7 +3,7 @@ manipulator .. dfhack-tool:: :summary: An in-game labor management interface. - :tags: untested fort productivity labors + :tags: unavailable fort productivity labors :no-command: It is equivalent to the popular Dwarf Therapist utility. diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst index bbd01aa97..4f3c6ba72 100644 --- a/docs/plugins/map-render.rst +++ b/docs/plugins/map-render.rst @@ -3,7 +3,7 @@ map-render .. dfhack-tool:: :summary: Provides a Lua API for re-rendering portions of the map. - :tags: untested dev graphics + :tags: unavailable dev graphics :no-command: See `map-render-api` for details. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst index 42c7f6fcc..061e3a548 100644 --- a/docs/plugins/mode.rst +++ b/docs/plugins/mode.rst @@ -3,7 +3,7 @@ mode .. dfhack-tool:: :summary: See and change the game mode. - :tags: untested armok dev gameplay + :tags: unavailable armok dev gameplay .. warning:: diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst index 0e49d3924..bff110f0e 100644 --- a/docs/plugins/mousequery.rst +++ b/docs/plugins/mousequery.rst @@ -3,7 +3,7 @@ mousequery .. dfhack-tool:: :summary: Adds mouse controls to the DF interface. - :tags: untested fort productivity interface + :tags: unavailable fort productivity interface Adds mouse controls to the DF interface. For example, with ``mousequery`` you can click on buildings to configure them, hold the mouse button to draw dig diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 6fe159899..46a004081 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -48,8 +48,11 @@ is open via an `overlay` widget. There are hotkeys assigned to export, import, sort, and clear. You can also click on the hotkey hints as if they were buttons. Clearing will ask for confirmation before acting. -If you want to change where the hotkey hints appear, you can move them via -`gui/overlay`. +If you want to change where the overlay panel appears, you can move it via +`gui/overlay`. If you just need to get the overlay out of the way temporarily, +for example to read a long description of a historical figure when choosing a +subject for a statue, click on the small arrow in the upper right corner of the +overlay panel. Click on the arrow again to restore the panel. The orders library ------------------ @@ -64,7 +67,7 @@ This collection of orders handles basic fort necessities: - prepared meals and food products (and by-products like oil) - booze/mead - thread/cloth/dye -- pots/jugs/buckets/mugs +- pots/bins/jugs/buckets/mugs - bags of leather, cloth, silk, and yarn - crafts, totems, and shleggings from otherwise unusable by-products - mechanisms/cages @@ -77,7 +80,10 @@ This collection of orders handles basic fort necessities: You should import it as soon as you have enough dwarves to perform the tasks. Right after the first migration wave is usually a good time. -Armok's note: shleggings? Yes, `shleggings `__. +Note that the jugs are specifically made out of wood. This is so, as long as you don't may any other "Tools" out of wood, you can have a stockpile just for jugs by restricting a finished goods stockpile to only take wooden tools. + +Armok's additional note: "shleggings? Yes, +`shleggings `__." :source:`library/furnace ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -101,13 +107,15 @@ Orders are missing for plaster powder until DF :bug:`11803` is fixed. This collection adds high-volume smelting jobs for military-grade metal ores and produces weapons and armor: -- leather backpacks/waterskins/cloaks/quivers/armor +- leather backpacks/waterskins/quivers/armor +- silk cloaks - bone/wooden bolts - smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and their dependencies) - bronze/bismuth bronze/copper bolts -- silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, +- steel/silver/iron/bismuth bronze/bronze/copper weapons and armor, with checks to ensure only the best available materials are being used +- wooden shields (if metal isn't available) If you set a stockpile to take weapons and armor of less than masterwork quality and turn on `automelt` (like what `dreamfort` provides on its industry level), @@ -117,16 +125,6 @@ Make sure you have a lot of fuel (or magma forges and furnaces) before you turn This file should only be imported, of course, if you need to equip a military. -:source:`library/military_include_artifact_materials ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -As above, but this collection will also allow creation of platinum blunt weapons. -Normally these are only created by artifact moods, work orders can't be created -manually for them. - -- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, - with checks to ensure only the best available materials are being used - :source:`library/smelting ` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/plugins/overlay.rst b/docs/plugins/overlay.rst index 313220fd4..fd196158f 100644 --- a/docs/plugins/overlay.rst +++ b/docs/plugins/overlay.rst @@ -66,3 +66,10 @@ For easy reference, the corners can be found at the following coordinates: :(-1, 1): top right corner :(1, -1): lower left corner :(-1, -1): lower right corner + +Overlay +------- + +The `overlay` plugin also provides a standard overlay itself: +``title_version``, which displays the DFHack version on the DF title screen, +along with quick links to `quickstart-guide` and `gui/control-panel`. diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst index 798e39f32..4f6ea4160 100644 --- a/docs/plugins/petcapRemover.rst +++ b/docs/plugins/petcapRemover.rst @@ -3,7 +3,7 @@ petcapRemover .. dfhack-tool:: :summary: Modify the pet population cap. - :tags: untested fort auto animals + :tags: unavailable fort auto animals In vanilla DF, pets will not reproduce unless the population is below 50 and the number of children of that species is below a certain percentage. This plugin diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst index cdcc0daac..281b295cf 100644 --- a/docs/plugins/plants.rst +++ b/docs/plugins/plants.rst @@ -5,7 +5,7 @@ plants .. dfhack-tool:: :summary: Provides commands that interact with plants. - :tags: untested adventure fort armok map plants + :tags: unavailable adventure fort armok map plants :no-command: .. dfhack-command:: plant diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst index 8ffc79e3e..f3a76c60a 100644 --- a/docs/plugins/power-meter.rst +++ b/docs/plugins/power-meter.rst @@ -3,7 +3,7 @@ power-meter .. dfhack-tool:: :summary: Allow pressure plates to measure power. - :tags: untested fort gameplay buildings + :tags: unavailable fort gameplay buildings :no-command: If you run `gui/power-meter` while building a pressure plate, the pressure diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst index 3fb47cc57..4628d11c8 100644 --- a/docs/plugins/prospector.rst +++ b/docs/plugins/prospector.rst @@ -5,7 +5,7 @@ prospector .. dfhack-tool:: :summary: Provides commands that help you analyze natural resources. - :tags: untested embark fort armok inspection map + :tags: embark fort armok inspection map :no-command: .. dfhack-command:: prospect diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst index 3c6ed4a77..903a1a1d4 100644 --- a/docs/plugins/rename.rst +++ b/docs/plugins/rename.rst @@ -3,7 +3,7 @@ rename .. dfhack-tool:: :summary: Easily rename things. - :tags: untested adventure fort productivity buildings stockpiles units + :tags: unavailable adventure fort productivity buildings stockpiles units Use `gui/rename` for an in-game interface. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst index 370771d6b..91faa6f5c 100644 --- a/docs/plugins/rendermax.rst +++ b/docs/plugins/rendermax.rst @@ -3,7 +3,7 @@ rendermax .. dfhack-tool:: :summary: Modify the map lighting. - :tags: untested adventure fort gameplay graphics + :tags: unavailable adventure fort gameplay graphics This plugin provides a collection of OpenGL lighting filters that affect how the map is drawn to the screen. diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index bbcc07b6b..2f4ebb5d8 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -17,7 +17,7 @@ reveal :summary: Switch between reveal and unreveal. .. dfhack-command:: revflood - :summary: Hide everything, then reveal tiles with a path to the cursor. + :summary: Hide everything, then reveal tiles with a path to a unit. .. dfhack-command:: nopause :summary: Disable pausing. @@ -44,9 +44,10 @@ Usage where (for example) you abandoned with the fort revealed and no longer need the saved map data when you load a new fort. ``revflood`` - Hide everything, then reveal tiles with a path to the cursor. This allows - reparing maps that you accidentally saved while they were revealed. Note - that tiles behind constructed walls are also revealed as a workaround for + Hide everything, then reveal tiles with a path to the keyboard cursor (if + enabled) or the selected unit (if a unit is selected) or else a random citizen. + This allows reparing maps that you accidentally saved while they were revealed. + Note that tiles behind constructed walls are also revealed as a workaround for :bug:`1871`. ``nopause 1|0`` Disables pausing (both manual and automatic) with the exception of the pause diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst index 8e1a93143..c1493f992 100644 --- a/docs/plugins/search.rst +++ b/docs/plugins/search.rst @@ -5,7 +5,7 @@ search .. dfhack-tool:: :summary: Adds search capabilities to the UI. - :tags: untested fort productivity interface + :tags: unavailable fort productivity interface :no-command: Search options are added to the Stocks, Animals, Trading, Stockpile, Noble diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst index fa18dcd48..57693fa11 100644 --- a/docs/plugins/siege-engine.rst +++ b/docs/plugins/siege-engine.rst @@ -3,7 +3,7 @@ siege-engine .. dfhack-tool:: :summary: Extend the functionality and usability of siege engines. - :tags: untested fort gameplay buildings + :tags: unavailable fort gameplay buildings :no-command: Siege engines in DF haven't been updated since the game was 2D, and can only aim diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index 3a8c7c0f5..067e188fd 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -3,7 +3,7 @@ sort .. dfhack-tool:: :summary: Sort lists shown in the DF interface. - :tags: untested fort productivity interface + :tags: unavailable fort productivity interface :no-command: .. dfhack-command:: sort-items diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst index 448369cd8..95ce852ae 100644 --- a/docs/plugins/spectate.rst +++ b/docs/plugins/spectate.rst @@ -3,7 +3,7 @@ spectate .. dfhack-tool:: :summary: Automatically follow productive dwarves. - :tags: untested fort interface + :tags: unavailable fort interface Usage ----- diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst index 11d0145f3..532b311d0 100644 --- a/docs/plugins/steam-engine.rst +++ b/docs/plugins/steam-engine.rst @@ -3,7 +3,7 @@ steam-engine .. dfhack-tool:: :summary: Allow modded steam engine buildings to function. - :tags: untested fort gameplay buildings + :tags: unavailable fort gameplay buildings :no-command: The steam-engine plugin detects custom workshops with the string diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst index f501fc904..29b7838fc 100644 --- a/docs/plugins/stockflow.rst +++ b/docs/plugins/stockflow.rst @@ -3,7 +3,7 @@ stockflow .. dfhack-tool:: :summary: Queue manager jobs based on free space in stockpiles. - :tags: untested fort auto stockpiles workorders + :tags: unavailable fort auto stockpiles workorders With this plugin, the fortress bookkeeper can tally up free space in specific stockpiles and queue jobs through the manager to produce items to fill the free diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst index 01cd3159b..560ae2497 100644 --- a/docs/plugins/stockpiles.rst +++ b/docs/plugins/stockpiles.rst @@ -1,41 +1,477 @@ -.. _stocksettings: - stockpiles ========== .. dfhack-tool:: - :summary: Import and export stockpile settings. + :summary: Import, export, or modify stockpile settings and features. :tags: fort design productivity stockpiles - :no-command: - -.. dfhack-command:: savestock - :summary: Exports the configuration of the selected stockpile. -.. dfhack-command:: loadstock - :summary: Imports configuration for the selected stockpile. - -Select a stockpile in the UI first to use these commands. +If you are importing or exporting setting and don't want to specify a building +ID, select a stockpile in the UI before running the command. Usage ----- -``savestock `` - Saves the currently highlighted stockpile's settings to a file in your - Dwarf Fortress folder. This file can be used to copy settings between game - saves or players. -``loadstock `` - Loads a saved stockpile settings file and applies it to the currently - selected stockpile. +:: + + stockpiles [status] + stockpiles list [] + stockpiles export [] + stockpiles import [] -Filenames with spaces are not supported. Generated materials, divine metals, -etc. are not saved as they are different in every world. +Exported stockpile settings are saved in the ``dfhack-config/stockpiles`` +folder, where you can view and delete them, if desired. Names can only +contain numbers, letters, periods, and underscores. + +The names of library settings files are all prefixed by the string ``library/``. +You can specify library files explicitly by including the prefix, or you can +just write the short name to use a player-exported file by that name if it +exists, and the library file if it doesn't. Examples -------- -``savestock food`` - Export the stockpile settings for the currently selected stockpile to a - file named ``food.dfstock``. -``loadstock food`` - Set the selected stockpile settings to those saved in the ``food.dfstock`` - file. +``stockpiles`` + Shows the list of all your stockpiles and some relevant statistics. +``stockpiles list`` + Shows the list of previously exported stockpile settings files, including + the stockpile configuration library. +``stockpiles list plants`` + Shows the list of exported stockpile settings files that include the + substring ``plants``. +``stockpiles import library/plants`` + Imports the library ``plants`` settings file into the currently selected + stockpile. +``stockpiles import plants`` + Imports a player-exported settings file named ``plants``, or the library + ``plants`` settings file if a player-exported file by that name doesn't + exist. +``stockpiles import -m enable plants`` + Enables plants in the selected stockpile. +``stockpiles import -m disable cat_food -f tallow`` + Disables all tallow in the selected food stockpile. +``stockpiles export mysettings`` + Export the settings for the currently selected stockpile to a file named + ``dfhack-config/stockpiles/mysettings.dfstock``. +``stockpiles export mysettings -i categories,types`` + Export the stockpile category and item settings, but ignore the container + and general settings. This allows you to import the configuration later + without touching the container and general settings of the target + stockpile. + +Options +------- + +``-s``, ``--stockpile `` + Specify a specific stockpile ID instead of using the one currently selected + in the UI. +``-i``, ``--include `` + When exporting, you can include this option to select only specific elements + of the stockpile to record. If not specified, everything is included. When + the file is later imported, only the included settings will be modified. The + options are explained below in the next section. +``-m``, ``--mode (set|enable|disable)`` + When importing, choose the algorithm used to apply the settings. In ``set`` + mode (the default), the stockpile is cleared and the settings in the file + are enabled. In ``enable`` mode, enabled settings in the file are *added* + to the stockpile, but no other settings are changed. In ``disable`` mode, + enabled settings in the file are *removed* from the current stockpile + configuration, and nothing else is changed. +``-f``, ``--filter [,...]`` + When importing, only modify the settings that contain at least one of the + given substrings. + +Configuration elements +---------------------- + +The different configuration elements you can include in an exported settings +file are: + +:containers: Max bins, max barrels, and num wheelbarrows. +:general: Whether the stockpile takes from links only and whether organic + and/or inorganic materials are allowed. +:categories: The top-level categories of items that are enabled for the + stockpile, like Ammo, Finished goods, or Stone. +:types: The elements below the categories, which include the sub-categories, the + specific item types, and any toggles the category might have (like Prepared + meals for the Food category). + +.. _stockpiles-library: + +The stockpiles settings library +------------------------------- + +DFHack comes with a library of useful stockpile settings files that are ready +for import. If the stockpile configuration that you need isn't directly +represented, you can often use the ``enable`` and ``disable`` modes and/or +the ``filter`` option to transform an existing saved stockpile setting. Some +stockpile configurations can only be achieved with filters since the stockpile +lists are different for each world. For example, to disable all tallow in your +main food stockpile, you'd run this command:: + + stockpiles import cat_food -m disable -f tallow + +Top-level categories +~~~~~~~~~~~~~~~~~~~~ + +Each stockpile category has a file that allows you to enable or disable the +entire category, or with a filter, any matchable subset thereof:: + + cat_ammo + cat_animals + cat_armor + cat_bars_blocks + cat_cloth + cat_coins + cat_corpses + cat_finished_goods + cat_food + cat_furniture + cat_gems + cat_leather + cat_refuse + cat_sheets + cat_stone + cat_weapons + cat_wood + +For many of the categories, there are also flags and subcategory prefixes that +you can match with filters and convenient pre-made settings files that +manipulate interesting category subsets. + +Ammo stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + type/ + mats/ + other/ + core/ + total/ + +Settings files:: + + bolts + metalammo + boneammo + woodammo + +Example commands for a stockpile of metal bolts:: + + stockpiles import cat_ammo -f mats/,core/,total/ + stockpiles import -m enable bolts + +Animal stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flags:: + + cages + traps + +Settings files:: + + cages + traps + +Example commands for a stockpile of empty cages:: + + stockpiles import cages + +Or, using the flag for the same effect:: + + stockpiles import cat_animals -f cages + +Armor stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flags and subcategory prefixes:: + + nouse + canuse + body/ + head/ + feet/ + hands/ + legs/ + shield/ + mats/ + other/ + core/ + total/ + +Settings files:: + + metalarmor + otherarmor + ironarmor + bronzearmor + copperarmor + steelarmor + usablearmor + unusablearmor + +Example commands for a stockpile of sub-masterwork meltable armor:: + + stockpiles import cat_armor + stockpiles import -m disable -f other/,core/mas,core/art cat_armor + +Bar stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + mats/bars/ + other/bars/ + mats/blocks/ + other/blocks/ + +Settings files:: + + bars + metalbars + ironbars + pigironbars + steelbars + otherbars + coal + potash + ash + pearlash + soap + blocks + +Example commands for a stockpile of blocks:: + + stockpiles import blocks + +Cloth stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + thread/silk/ + thread/plant/ + thread/yarn/ + thread/metal/ + cloth/silk/ + cloth/plant/ + cloth/yarn/ + cloth/metal/ + +Settings files:: + + thread + adamantinethread + cloth + adamantinecloth + +Notes: + +* ``thread`` and ``cloth`` settings files set all materials that are not + adamantine. + +Finished goods stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + type/ + mats/ + other/ + core/ + total/ + +Settings files:: + + stonetools + woodtools + crafts + goblets + toys + +Example commands for a toy stockpile:: + + stockpiles import cat_furniture -f mats/,other/,core/,total/ + stockpiles import -m enable toys + +Food stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flags and subcategory prefixes:: + + preparedmeals + meat/ + fish/prepared/ + fish/unprepared/ + egg/ + plants/ + drink/plant/ + drink/animal/ + cheese/plant/ + cheese/animal/ + seeds/ + leaves/ + powder/plant/ + powder/animal/ + glob/ + liquid/plant/ + liquid/animal/ + liquid/misc/ + paste/ + pressed/ + +Settings files:: + + preparedmeals + unpreparedfish + plants + booze + seeds + dye + miscliquid + wax + +Example commands for a kitchen ingredients stockpile:: + + stockpiles import cat_food -f meat/,fish/prepared/,egg/,cheese/,leaves/,powder/,glob/,liquid/plant/,paste/,pressed/ + stockpiles import cat_food -m enable -f milk,royal_jelly + stockpiles import dye -m disable + stockpiles import cat_food -m disable -f tallow,thread,liquid/misc/ + +Furniture stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + type/ + mats/ + other/ + core/ + total/ + +Settings files:: + + pots + bags + buckets + sand + +* Because of the limitations of Dwarf Fortress, ``bags`` cannot distinguish + between empty bags and bags filled with gypsum powder. + +Example commands for a sand bag stockpile:: + + stockpiles import cat_furniture + stockpiles import cat_furniture -m disable -f type/ + stockpiles import sand -m enable + +Gem stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + mats/rough/ + mats/cut/ + other/rough/ + other/cut/ + +Settings files:: + + roughgems + roughglass + cutgems + cutglass + cutstone + +Refuse stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flags and subcategory prefixes:: + + rawhide/fresh + rawhide/rotten + type/ + corpses/ + bodyparts/ + skulls/ + bones/ + hair/ + shells/ + teeth/ + horns/ + +Settings files:: + + rawhides + tannedhides + usablehair + +Notes: + +* ``usablehair`` Only hair and wool that can make usable clothing is included, + i.e. from sheep, llamas, alpacas, and trolls. + +Example commands for a craftable refuse stockpile:: + + stockpiles import cat_refuse -f skulls/,bones/,shells',teeth/,horns/ + stockpiles import usablehair -m enable + +Sheet stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Subcategory prefixes:: + + paper/ + parchment/ + +Stone stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Settings files:: + + metalore + ironore + economic + flux + plasterproducing + coalproducing + otherstone + bauxite + clay + +Weapon stockpile adjustments +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Flags and subcategory prefixes:: + + nouse + canuse + type/weapon/ + type/trapcomp/ + mats/ + other/ + core/ + total/ + +Settings files:: + + metalweapons + stoneweapons + otherweapons + trapcomponents + ironweapons + silverweapons + bronzeweapons + copperweapons + steelweapons + platinumweapons + adamantineweapons + usableweapons + unusableweapons + +Example commands for a non-metallic trap components stockpile:: + + stockpiles import cat_weapons + stockpiles import cat_weapons -m disable -f type/weapon/ + stockpiles metalweapons -m disable diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst index c924b748c..f8db4ee67 100644 --- a/docs/plugins/stocks.rst +++ b/docs/plugins/stocks.rst @@ -3,7 +3,7 @@ stocks .. dfhack-tool:: :summary: Enhanced fortress stock management interface. - :tags: untested fort productivity items + :tags: unavailable fort productivity items When the plugin is enabled, two new hotkeys become available: diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst index a863943e9..12e814c55 100644 --- a/docs/plugins/strangemood.rst +++ b/docs/plugins/strangemood.rst @@ -10,7 +10,7 @@ Usage :: - stangemood [] + strangemood [] Examples -------- diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst index 32818071b..ee4068547 100644 --- a/docs/plugins/title-folder.rst +++ b/docs/plugins/title-folder.rst @@ -3,7 +3,7 @@ title-folder .. dfhack-tool:: :summary: Displays the DF folder name in the window title bar. - :tags: untested interface + :tags: unavailable interface :no-command: Usage diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst deleted file mode 100644 index 18c7c3ba8..000000000 --- a/docs/plugins/title-version.rst +++ /dev/null @@ -1,14 +0,0 @@ -title-version -============= - -.. dfhack-tool:: - :summary: Displays the DFHack version on DF's title screen. - :tags: untested interface - :no-command: - -Usage ------ - -:: - - enable title-version diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst index 6ae23dcd0..041bcac2c 100644 --- a/docs/plugins/trackstop.rst +++ b/docs/plugins/trackstop.rst @@ -3,7 +3,7 @@ trackstop .. dfhack-tool:: :summary: Add dynamic configuration options for track stops. - :tags: untested fort gameplay buildings + :tags: unavailable fort gameplay buildings :no-command: When enabled, this plugin adds a :kbd:`q` menu for track stops, which is diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst index eb568ef03..a8684c765 100644 --- a/docs/plugins/tubefill.rst +++ b/docs/plugins/tubefill.rst @@ -3,7 +3,7 @@ tubefill .. dfhack-tool:: :summary: Replenishes mined-out adamantine. - :tags: untested fort armok map + :tags: unavailable fort armok map Veins that were originally hollow will be left alone. diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst index dc5ed19a8..f9467e333 100644 --- a/docs/plugins/tweak.rst +++ b/docs/plugins/tweak.rst @@ -3,7 +3,7 @@ tweak .. dfhack-tool:: :summary: A collection of tweaks and bugfixes. - :tags: untested adventure fort armok bugfix fps interface + :tags: unavailable adventure fort armok bugfix fps interface Usage ----- diff --git a/docs/plugins/work-now.rst b/docs/plugins/work-now.rst new file mode 100644 index 000000000..5eddf7c9f --- /dev/null +++ b/docs/plugins/work-now.rst @@ -0,0 +1,19 @@ +.. _worknow: + +work-now +======== + +.. dfhack-tool:: + :summary: Reduce the time that dwarves idle after completing a job. + :tags: fort auto jobs + +After finishing a job, dwarves will wander away for a while before picking up a +new job. This plugin will automatically poke them to pick up a new task quicker. + +Usage +----- + +:: + + enable work-now + work-now [status] diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst deleted file mode 100644 index 66bc822ae..000000000 --- a/docs/plugins/workNow.rst +++ /dev/null @@ -1,22 +0,0 @@ -workNow -======= - -.. dfhack-tool:: - :summary: Reduce the time that dwarves idle after completing a job. - :tags: untested fort auto labors - -After finishing a job, dwarves will wander away for a while before picking up a -new job. This plugin will automatically poke the game to assign dwarves to new -tasks. - -Usage ------ - -``workNow`` - Print current plugin status. -``workNow 0`` - Stop monitoring and poking. -``workNow 1`` - Poke the game to assign dwarves to tasks whenever the game is paused. -``workNow 2`` - Poke the game to assign dwarves to tasks whenever a dwarf finishes a job. diff --git a/docs/plugins/workflow.rst b/docs/plugins/workflow.rst index 72b7a8e2b..c95054c0e 100644 --- a/docs/plugins/workflow.rst +++ b/docs/plugins/workflow.rst @@ -3,7 +3,7 @@ workflow .. dfhack-tool:: :summary: Manage automated item production rules. - :tags: untested fort auto jobs + :tags: unavailable fort auto jobs Manage repeat jobs according to stock levels. `gui/workflow` provides a simple front-end integrated in the game UI. diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 9d9991dde..af6ee2b5f 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -3,7 +3,7 @@ zone .. dfhack-tool:: :summary: Manage activity zones, cages, and the animals therein. - :tags: untested fort productivity animals buildings + :tags: unavailable fort productivity animals buildings Usage ----- diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 92db43563..371d3cd14 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -1,5 +1,5 @@ project(dfapi) -cmake_minimum_required(VERSION 2.8.12) +cmake_minimum_required(VERSION 3.21) # prevent CMake warnings about INTERFACE_LINK_LIBRARIES vs LINK_INTERFACE_LIBRARIES cmake_policy(SET CMP0022 NEW) @@ -95,7 +95,6 @@ endif() set(MAIN_SOURCES_WINDOWS ${CONSOLE_SOURCES} - Hooks-windows.cpp Hooks.cpp PlugLoad-windows.cpp Process-windows.cpp @@ -108,14 +107,12 @@ endif() set(MAIN_SOURCES_LINUX ${CONSOLE_SOURCES} - Hooks-linux.cpp PlugLoad-posix.cpp Process-linux.cpp ) set(MAIN_SOURCES_DARWIN ${CONSOLE_SOURCES} - Hooks-darwin.cpp PlugLoad-posix.cpp Process-darwin.cpp ) @@ -125,6 +122,7 @@ set(MODULE_HEADERS include/modules/Burrows.h include/modules/Constructions.h include/modules/DFSDL.h + include/modules/DFSteam.h include/modules/Designations.h include/modules/EventManager.h include/modules/Filesystem.h @@ -154,6 +152,7 @@ set(MODULE_SOURCES modules/Burrows.cpp modules/Constructions.cpp modules/DFSDL.cpp + modules/DFSteam.cpp modules/Designations.cpp modules/EventManager.cpp modules/Filesystem.cpp @@ -374,8 +373,7 @@ add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) if(WIN32) - # name the resulting library SDL.dll on Windows - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) + set_target_properties(dfhack PROPERTIES OUTPUT_NAME "dfhooks" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() @@ -435,8 +433,8 @@ if(UNIX) LIBRARY DESTINATION . RUNTIME DESTINATION .) else() - # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll + # On windows, copy SDL.dll so DF can still run. + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDL.dll DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 515d89911..20d943b18 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.cpp @@ -508,6 +508,7 @@ bool Console::init(bool) inited = true; // DOESN'T WORK - locks up DF! // ForceForegroundWindow(d->MainWindow); + hide(); return true; } // FIXME: looks awfully empty, doesn't it? diff --git a/library/Core.cpp b/library/Core.cpp index 01bc89480..256493dce 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -49,6 +49,7 @@ distribution. #include "PluginManager.h" #include "ModuleFactory.h" #include "modules/DFSDL.h" +#include "modules/DFSteam.h" #include "modules/EventManager.h" #include "modules/Filesystem.h" #include "modules/Gui.h" @@ -272,7 +273,7 @@ static std::string dfhack_version_desc() if (Version::is_release()) s << "(release)"; else - s << "(development build " << Version::git_description() << ")"; + s << "(git: " << Version::git_commit(true) << ")"; s << " on " << (sizeof(void*) == 8 ? "x86_64" : "x86"); if (strlen(Version::dfhack_build_id())) s << " [build ID: " << Version::dfhack_build_id() << "]"; @@ -440,6 +441,12 @@ bool Core::addScriptPath(std::string path, bool search_before) return true; } +bool Core::setModScriptPaths(const std::vector &mod_script_paths) { + std::lock_guard lock(script_path_mutex); + script_paths[2] = mod_script_paths; + return true; +} + bool Core::removeScriptPath(std::string path) { std::lock_guard lock(script_path_mutex); @@ -464,20 +471,21 @@ void Core::getScriptPaths(std::vector *dest) std::lock_guard lock(script_path_mutex); dest->clear(); std::string df_path = this->p->getPath() + "/"; - for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) - dest->push_back(*it); + for (auto & path : script_paths[0]) + dest->emplace_back(path); dest->push_back(df_path + CONFIG_PATH + "scripts"); if (df::global::world && isWorldLoaded()) { std::string save = World::ReadWorldFolder(); if (save.size()) - dest->push_back(df_path + "/save/" + save + "/scripts"); + dest->emplace_back(df_path + "save/" + save + "/scripts"); } - dest->push_back(df_path + "/hack/scripts"); - for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it) - dest->push_back(*it); + dest->emplace_back(df_path + "hack/scripts"); + for (auto & path : script_paths[2]) + dest->emplace_back(path); + for (auto & path : script_paths[1]) + dest->emplace_back(path); } - std::string Core::findScript(std::string name) { std::vector paths; @@ -526,6 +534,21 @@ bool loadScriptPaths(color_ostream &out, bool silent = false) return true; } +static void loadModScriptPaths(color_ostream &out) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + std::vector mod_script_paths; + Lua::CallLuaModuleFunction(out, L, "script-manager", "get_mod_script_paths", 0, 1, + Lua::DEFAULT_LUA_LAMBDA, + [&](lua_State *L) { + Lua::GetVector(L, mod_script_paths); + }); + DEBUG(script,out).print("final mod script paths:\n"); + for (auto & path : mod_script_paths) + DEBUG(script,out).print(" %s\n", path.c_str()); + Core::getInstance().setModScriptPaths(mod_script_paths); +} + static std::map state_change_event_map; static void sc_event_map_init() { if (!state_change_event_map.size()) @@ -763,6 +786,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, s } } + part = GetAliasCommand(part, true); + Plugin * plug = (*plug_mgr)[part]; if(!plug) @@ -1285,6 +1310,15 @@ static void run_dfhack_init(color_ostream &out, Core *core) // load user overrides std::vector prefixes(1, "dfhack"); loadScriptFiles(core, out, prefixes, CONFIG_PATH + "init"); + + // show the terminal if requested + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(out, L, "dfhack", "getHideConsoleOnStartup", 0, 1, + Lua::DEFAULT_LUA_LAMBDA, [&](lua_State* L) { + if (!lua_toboolean(L, -1)) + core->getConsole().show(); + }, false); } // Load dfhack.init in a dedicated thread (non-interactive console mode) @@ -1437,16 +1471,8 @@ std::string Core::getHackPath() #endif } -bool Core::Init() -{ - if(started) - return true; - if(errorstate) - return false; - - // Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown - // Core::Update will temporary unlock when there is any commands queued - MainThread::suspend().lock(); +bool Core::InitMainThread() { + Filesystem::init(); // 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 @@ -1462,8 +1488,6 @@ bool Core::Init() if (!freopen("stderr.log", "w", stderr)) std::cerr << "Could not redirect stderr to stderr.log" << std::endl; - Filesystem::init(); - std::cerr << "DFHack build: " << Version::git_description() << "\n" << "Starting with working directory: " << Filesystem::getcwd() << std::endl; @@ -1532,6 +1556,20 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); + return true; +} + +bool Core::InitSimulationThread() +{ + if(started) + return true; + if(errorstate) + return false; + + // Lock the CoreSuspendMutex until the thread exits or call Core::Shutdown + // Core::Update will temporary unlock when there is any commands queued + MainThread::suspend().lock(); + std::cerr << "Initializing Console.\n"; // init the console. bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT)); @@ -1644,6 +1682,8 @@ bool Core::Init() fatal("cannot bind SDL libraries"); return false; } + if (DFSteam::init(con)) + std::cerr << "Found Steam.\n"; std::cerr << "Initializing textures.\n"; Textures::init(con); // create mutex for syncing with interactive tasks @@ -1929,7 +1969,7 @@ int Core::Update() if(!started) { // Initialize the core - Init(); + InitSimulationThread(); if(errorstate) return -1; } @@ -2052,7 +2092,8 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve if (!df::global::world) return; - std::string rawFolder = "save/" + (df::global::world->cur_savegame.save_dir) + "/init"; + + std::string rawFolder = !isWorldLoaded() ? "" : "save/" + World::ReadWorldFolder() + "/init"; auto i = table.find(event); if ( i != table.end() ) { @@ -2111,14 +2152,25 @@ void Core::onStateChange(color_ostream &out, state_change_event event) switch (event) { case SC_CORE_INITIALIZED: - { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - Lua::CallLuaModuleFunction(con, L, "helpdb", "refresh"); - Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); - } + { + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "helpdb", "refresh"); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); break; + } case SC_WORLD_LOADED: + { + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload", 1, 0, + [](lua_State* L) { + Lua::Push(L, true); + }); + // fallthrough + } case SC_WORLD_UNLOADED: case SC_MAP_LOADED: case SC_MAP_UNLOADED: @@ -2183,6 +2235,10 @@ void Core::onStateChange(color_ostream &out, state_change_event event) if (event == SC_WORLD_UNLOADED) { Persistence::Internal::clear(); + loadModScriptPaths(out); + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + Lua::CallLuaModuleFunction(con, L, "script-manager", "reload"); } } @@ -2235,6 +2291,7 @@ int Core::Shutdown ( void ) allModules.clear(); Textures::cleanup(); DFSDL::cleanup(); + DFSteam::cleanup(); memset(&(s_mods), 0, sizeof(s_mods)); d.reset(); return -1; @@ -2245,12 +2302,13 @@ int Core::Shutdown ( void ) #define KEY_F0 0410 /* Function keys. Space for 64 */ #define KEY_F(n) (KEY_F0+(n)) /* Value of function key n */ +// returns true if the event has been handled bool Core::ncurses_wgetch(int in, int & out) { if(!started) { out = in; - return true; + return false; } if(in >= KEY_F(1) && in <= KEY_F(8)) { @@ -2265,18 +2323,18 @@ bool Core::ncurses_wgetch(int in, int & out) df::global::plotinfo->main.hotkeys[idx].cmd == df::ui_hotkey::T_cmd::None) { setHotkeyCmd(df::global::plotinfo->main.hotkeys[idx].name); - return false; + return true; } else { out = in; - return true; + return false; } } */ } out = in; - return true; + return false; } bool Core::DFH_ncurses_key(int key) @@ -2284,7 +2342,7 @@ bool Core::DFH_ncurses_key(int key) if (getenv("DFHACK_HEADLESS")) return true; int dummy; - return !ncurses_wgetch(key, dummy); + return ncurses_wgetch(key, dummy); } int UnicodeAwareSym(const SDL::KeyboardEvent& ke) @@ -2335,21 +2393,19 @@ int UnicodeAwareSym(const SDL::KeyboardEvent& ke) return unicode; } - -//MEMO: return false if event is consumed -int Core::DFH_SDL_Event(SDL::Event* ev) +// returns true if the event is handled +bool Core::DFH_SDL_Event(SDL::Event* ev) { // do NOT process events before we are ready. - if(!started) return true; - if(!ev) - return true; + if(!started || !ev) + return false; if(ev->type == SDL::ET_ACTIVEEVENT && ev->active.gain) { // clear modstate when gaining focus in case alt-tab was used when // losing focus and modstate is now incorrectly set modstate = 0; - return true; + return false; } if(ev->type == SDL::ET_KEYDOWN || ev->type == SDL::ET_KEYUP) @@ -2384,8 +2440,7 @@ int Core::DFH_SDL_Event(SDL::Event* ev) hotkey_states[ke->ksym.sym] = false; } } - return true; - // do stuff with the events... + return false; } bool Core::SelectHotkey(int sym, int modifiers) @@ -2657,13 +2712,14 @@ std::map> Core::ListAliases() return aliases; } -std::string Core::GetAliasCommand(const std::string &name, const std::string &default_) +std::string Core::GetAliasCommand(const std::string &name, bool ignore_params) { std::lock_guard lock(alias_mutex); - if (IsAlias(name)) - return join_strings(" ", aliases[name]); - else - return default_; + if (!IsAlias(name) || aliases[name].empty()) + return name; + if (ignore_params) + return aliases[name][0]; + return join_strings(" ", aliases[name]); } ///////////////// diff --git a/library/DFHackVersion.cpp b/library/DFHackVersion.cpp index 7746ebece..42aac251e 100644 --- a/library/DFHackVersion.cpp +++ b/library/DFHackVersion.cpp @@ -1,6 +1,8 @@ #define NO_DFHACK_VERSION_MACROS #include "DFHackVersion.h" #include "git-describe.h" +#include + namespace DFHack { namespace Version { int dfhack_abi_version() @@ -27,9 +29,10 @@ namespace DFHack { { return DFHACK_GIT_DESCRIPTION; } - const char *git_commit() + const char* git_commit(bool short_hash) { - return DFHACK_GIT_COMMIT; + static std::string shorty(DFHACK_GIT_COMMIT, 0, 7); + return short_hash ? shorty.c_str() : DFHACK_GIT_COMMIT; } const char *git_xml_commit() { diff --git a/library/Hooks-darwin.cpp b/library/Hooks-darwin.cpp deleted file mode 100644 index 418cf5472..000000000 --- a/library/Hooks-darwin.cpp +++ /dev/null @@ -1,318 +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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -typedef struct interpose_s -{ - void *new_func; - void *orig_func; -} interpose_t; - -#include "DFHack.h" -#include "Core.h" -#include "Hooks.h" -#include "SDL_events.h" -#include - -/*static const interpose_t interposers[] __attribute__ ((section("__DATA, __interpose"))) = -{ - { (void *)DFH_SDL_Init, (void *)SDL_Init }, - { (void *)DFH_SDL_PollEvent, (void *)SDL_PollEvent }, - { (void *)DFH_SDL_Quit, (void *)SDL_Quit }, - { (void *)DFH_SDL_NumJoysticks, (void *)SDL_NumJoysticks }, - -};*/ - -#define DYLD_INTERPOSE(_replacement,_replacee) \ - __attribute__((used)) static struct{ const void* replacment; const void* replacee; } \ - _interpose_##_replacee __attribute__ ((section ("__DATA,__interpose"))) = \ - { (const void*)(unsigned long)&_replacement, (const void*)(unsigned long)&_replacee }; - -DYLD_INTERPOSE(DFH_SDL_Init,SDL_Init); -DYLD_INTERPOSE(DFH_SDL_PollEvent,SDL_PollEvent); -DYLD_INTERPOSE(DFH_SDL_Quit,SDL_Quit); -DYLD_INTERPOSE(DFH_SDL_NumJoysticks,SDL_NumJoysticks); -DYLD_INTERPOSE(DFH_wgetch,wgetch); - -/******************************************************************************* -* SDL part starts here * -*******************************************************************************/ - -#define SDL_APPMOUSEFOCUS 0x01 /**< The app has mouse coverage */ -#define SDL_APPINPUTFOCUS 0x02 /**< The app has input focus */ -#define SDL_APPACTIVE 0x04 /**< The application is active */ -static uint8_t (*_SDL_GetAppState)(void) = 0; -DFhackCExport uint8_t SDL_GetAppState(void) -{ - return _SDL_GetAppState(); -} - -// hook - called for each game tick (or more often) -DFhackCExport int DFH_SDL_NumJoysticks(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} - -// hook - called at program exit -static void (*_SDL_Quit)(void) = 0; -DFhackCExport void DFH_SDL_Quit(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - c.Shutdown(); - - SDL_Quit(); -} - -// called by DF to check input events -static int (*_SDL_PollEvent)(SDL::Event* event) = 0; -DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event) -{ - pollevent_again: - // if SDL returns 0 here, it means there are no more events. return 0 - int orig_return = SDL_PollEvent(event); - if(!orig_return || (!(SDL_GetAppState() & SDL_APPINPUTFOCUS) && - (event->type == SDL::ET_KEYDOWN || event->type == SDL::ET_KEYUP))) - return 0; - // otherwise we have an event to filter - else if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - // if we consume the event, ask SDL for more. - if(!c.DFH_SDL_Event(event)) - goto pollevent_again; - } - return orig_return; -} - -static int (*_SDL_PushEvent)(SDL::Event* event) = 0; -DFhackCExport int SDL_PushEvent(SDL::Event* event) -{ - return _SDL_PushEvent(event); -} - -struct WINDOW; -DFhackCExport int DFH_wgetch(WINDOW *win) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - wgetch_again: - int in = wgetch(win); - int out; - if(c.ncurses_wgetch(in, out)) - { - // not consumed, give to DF - return out; - } - else - { - // consumed, repeat - goto wgetch_again; - } -} - -void dlsym_bind_or_exit(void **target, const char *name) -{ - void *sym = dlsym(RTLD_NEXT, name); - if (sym) - { - if (*target && *target != sym) - { - fprintf(stderr, "warning: rebinding symbol %s from %p to %p\n", - name, *target, sym); - } - *target = sym; - } - else - { - fprintf(stderr, "Fatal: Could not find symbol: %s\n", name); - fprintf(stdout, "dfhack: something went horribly wrong\n" - "Check stderr.log for details\n"); - exit(1); - } -} - - -// New SDL functions starting in r5 -static vPtr (*_SDL_CreateRGBSurface)(uint32_t flags, int width, int height, int depth, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) = 0; -DFhackCExport vPtr SDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) -{ - return _SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask); -} - -static vPtr (*_SDL_CreateRGBSurfaceFrom)(vPtr pixels, int width, int height, int depth, int pitch, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) = 0; -DFhackCExport vPtr SDL_CreateRGBSurfaceFrom(vPtr pixels, int width, int height, int depth, int pitch, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) -{ - return _SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask); -} - -static void (*_SDL_FreeSurface)(vPtr surface) = 0; -DFhackCExport void SDL_FreeSurface(vPtr surface) -{ - _SDL_FreeSurface(surface); -} - -static vPtr (*_SDL_ConvertSurface)(vPtr surface, vPtr format, uint32_t flags) = 0; -DFhackCExport vPtr SDL_ConvertSurface(vPtr surface, vPtr format, uint32_t flags) -{ - return _SDL_ConvertSurface(surface, format, flags); -} - -static int (*_SDL_LockSurface)(vPtr surface) = 0; -DFhackCExport int SDL_LockSurface(vPtr surface) -{ - return _SDL_LockSurface(surface); -} - -static void (*_SDL_UnlockSurface)(vPtr surface) = 0; -DFhackCExport void SDL_UnlockSurface(vPtr surface) -{ - _SDL_UnlockSurface(surface); -} - -static uint8_t (*_SDL_GetMouseState)(int *, int *) = 0; -DFhackCExport uint8_t SDL_GetMouseState(int *x, int *y) -{ - return _SDL_GetMouseState(x,y); -} - -static void * (*_SDL_GetVideoSurface)( void ) = 0; -DFhackCExport void * SDL_GetVideoSurface(void) -{ - return _SDL_GetVideoSurface(); -} - -static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; -DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - if ( c.isValid() && dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) - { - DFHack::Graphic* g = c.getGraphic(); - DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); - - if ( ov != NULL ) - { - if ( ov->paintOver ) - { - _SDL_UpperBlit(src, srcrect, dst, dstrect); - } - - DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; - dstrect2->x = dstrect->x; - dstrect2->y = dstrect->y; - dstrect2->w = dstrect->w; - dstrect2->h = dstrect->h; - - if ( ov->dstResize != NULL ) - { - DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; - dstrect2->x += r->x; - dstrect2->y += r->y; - dstrect2->w += r->w; - dstrect2->h += r->h; - } - - int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); - delete dstrect2; - return result; - } - } - - return _SDL_UpperBlit(src, srcrect, dst, dstrect); -} - -static int (*_SDL_SemWait)(vPtr) = 0; -DFhackCExport int SDL_SemWait(vPtr sem) -{ - return _SDL_SemWait(sem); -} - -static int (*_SDL_SemPost)(vPtr) = 0; -DFhackCExport int SDL_SemPost(vPtr sem) -{ - return _SDL_SemPost(sem); -} - -// hook - called at program start, initialize some stuffs we'll use later -static int (*_SDL_Init)(uint32_t flags) = 0; -DFhackCExport int DFH_SDL_Init(uint32_t flags) -{ - // reroute stderr - fprintf(stderr,"dfhack: attempting to hook in\n"); - // we don't reroute stdout until we figure out if this should be done at all - // See: Console-posix.cpp - - // find real functions - fprintf(stderr,"dfhack: saving real SDL functions\n"); - - #define bind(sym) dlsym_bind_or_exit((void**)&_##sym, #sym) - bind(SDL_Init); - bind(SDL_Quit); - bind(SDL_PollEvent); - bind(SDL_PushEvent); - - bind(SDL_UpperBlit); - bind(SDL_CreateRGBSurface); - bind(SDL_CreateRGBSurfaceFrom); - bind(SDL_FreeSurface); - bind(SDL_ConvertSurface); - bind(SDL_LockSurface); - bind(SDL_UnlockSurface); - bind(SDL_GetMouseState); - bind(SDL_GetVideoSurface); - - bind(SDL_SemWait); - bind(SDL_SemPost); - bind(SDL_GetAppState); - #undef bind - - fprintf(stderr, "dfhack: saved real SDL functions\n"); - assert(_SDL_Init && _SDL_Quit && _SDL_PollEvent); - fprintf(stderr, "dfhack: hooking successful\n"); - - // prevent any subprocesses from trying to load libdfhack.dylib - setenv("DYLD_INSERT_LIBRARIES", "", 1); - - int ret = SDL_Init(flags); - return ret; -} diff --git a/library/Hooks-linux.cpp b/library/Hooks-linux.cpp deleted file mode 100644 index 7a0cdf947..000000000 --- a/library/Hooks-linux.cpp +++ /dev/null @@ -1,139 +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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "DFHack.h" -#include "Core.h" -#include "Hooks.h" -#include - -/******************************************************************************* -* SDL part starts here * -*******************************************************************************/ -// hook - called for each game tick (or more often) -DFhackCExport int SDL_NumJoysticks(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} - -// hook - called at program exit -static void (*_SDL_Quit)(void) = 0; -DFhackCExport void SDL_Quit(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - c.Shutdown(); - if(_SDL_Quit) - { - _SDL_Quit(); - } -} - -// called by DF to check input events -static int (*_SDL_PollEvent)(SDL::Event* event) = 0; -DFhackCExport int SDL_PollEvent(SDL::Event* event) -{ - pollevent_again: - // if SDL returns 0 here, it means there are no more events. return 0 - int orig_return = _SDL_PollEvent(event); - if(!orig_return) - return 0; - // otherwise we have an event to filter - else if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - // if we consume the event, ask SDL for more. - if(!c.DFH_SDL_Event(event)) - goto pollevent_again; - } - return orig_return; -} - -struct WINDOW; -DFhackCExport int wgetch(WINDOW *win) -{ - if (getenv("DFHACK_HEADLESS")) - { - return 0; - } - static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); - if(!_wgetch) - { - exit(EXIT_FAILURE); - } - DFHack::Core & c = DFHack::Core::getInstance(); - wgetch_again: - int in = _wgetch(win); - int out; - if(c.ncurses_wgetch(in, out)) - { - // not consumed, give to DF - return out; - } - else - { - // consumed, repeat - goto wgetch_again; - } -} - -// hook - called at program start, initialize some stuffs we'll use later -static int (*_SDL_Init)(uint32_t flags) = 0; -DFhackCExport int SDL_Init(uint32_t flags) -{ - // find real functions - _SDL_Init = (int (*)( uint32_t )) dlsym(RTLD_NEXT, "SDL_Init"); - _SDL_Quit = (void (*)( void )) dlsym(RTLD_NEXT, "SDL_Quit"); - _SDL_PollEvent = (int (*)(SDL::Event*))dlsym(RTLD_NEXT,"SDL_PollEvent"); - - // check if we got them - if(_SDL_Init && _SDL_Quit && _SDL_PollEvent) - { - fprintf(stderr,"dfhack: hooking successful\n"); - } - else - { - // bail, this would be a disaster otherwise - fprintf(stderr,"dfhack: something went horribly wrong\n"); - exit(1); - } - - int ret = _SDL_Init(flags); - return ret; -} diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp deleted file mode 100644 index d3f39b969..000000000 --- a/library/Hooks-windows.cpp +++ /dev/null @@ -1,838 +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. -*/ - -#define DFhackCExport extern "C" __declspec(dllexport) - -#include -#include -#include -#include -#include -#include "Core.h" -#include "Hooks.h" -#include - -#include "tinythread.h" -#include "modules/Graphic.h" - -/*************************************************************************/ -// extremely boring wrappers beyond this point. Only fix when broken - -// we don't know which of the SDL functions will be called first... so we -// just catch the first one and init all our function pointers at that time -static void InitSDLPointers(void); -static std::once_flag inited; - -/// wrappers for SDL 1.2 functions used in 40d16 -/***** Condition variables - -SDL_CreateCond - SDL_cond * SDLCALL SDL_CreateCond(void); -SDL_CondSignal - int SDLCALL SDL_CondSignal(SDL_cond *cond); -SDL_CondWait - int SDLCALL SDL_CondWait(SDL_cond *cond, SDL_mutex *mut); -SDL_DestroyCond - void SDLCALL SDL_DestroyCond(SDL_cond *cond); -*/ -static vPtr (*_SDL_CreateCond)() = 0; -DFhackCExport vPtr SDL_CreateCond() -{ - return _SDL_CreateCond(); -} - -static int (*_SDL_CondSignal)( vPtr ) = 0; -DFhackCExport int SDL_CondSignal( vPtr cond ) -{ - return _SDL_CondSignal(cond); -} - -static int (*_SDL_CondWait)( vPtr,vPtr ) = 0; -DFhackCExport int SDL_CondWait( vPtr cond, vPtr mutex ) -{ - return _SDL_CondWait(cond, mutex); -} - -static void (*_SDL_DestroyCond)( vPtr ) = 0; -DFhackCExport void SDL_DestroyCond( vPtr cond ) -{ - _SDL_DestroyCond(cond); -} - -/***** mutexes - -SDL_CreateMutex - SDL_mutex * SDLCALL SDL_CreateMutex(void); -SDL_mutexP - int SDLCALL SDL_mutexP(SDL_mutex *mutex); -SDL_DestroyMutex - void SDLCALL SDL_DestroyMutex(SDL_mutex *mutex); -*/ -static vPtr (*_SDL_CreateMutex)(void) = 0; -DFhackCExport vPtr SDL_CreateMutex(void) -{ - return _SDL_CreateMutex(); -} - -static int (*_SDL_mutexP)(vPtr mutex) = 0; -DFhackCExport int SDL_mutexP(vPtr mutex) -{ - return _SDL_mutexP(mutex); -} - -static int (*_SDL_mutexV)(vPtr mutex) = 0; -DFhackCExport int SDL_mutexV(vPtr mutex) -{ - return _SDL_mutexV(mutex); -} - -static void (*_SDL_DestroyMutex)(vPtr mutex) = 0; -DFhackCExport void SDL_DestroyMutex(vPtr mutex) -{ - _SDL_DestroyMutex(mutex); -} - - -/***** timers - -SDL_AddTimer - SDL_TimerID SDLCALL SDL_AddTimer(Uint32 interval, SDL_NewTimerCallback callback, void *param); -SDL_RemoveTimer - SDL_bool SDLCALL SDL_RemoveTimer(SDL_TimerID t); -SDL_GetTicks - Uint32 SDLCALL SDL_GetTicks(void); -*/ -static vPtr (*_SDL_AddTimer)(uint32_t interval, fPtr callback, vPtr param) = 0; -DFhackCExport vPtr SDL_AddTimer(uint32_t interval, fPtr callback, vPtr param) -{ - return _SDL_AddTimer(interval, callback, param); -} - -static bool (*_SDL_RemoveTimer)(vPtr timer) = 0; -DFhackCExport bool SDL_RemoveTimer(vPtr timer) -{ - return _SDL_RemoveTimer(timer); -} - -static uint32_t (*_SDL_GetTicks)(void) = 0; -DFhackCExport uint32_t SDL_GetTicks(void) -{ - return _SDL_GetTicks(); -} - -/***** Surfaces -SDL_CreateRGBSurface - SDL_Surface * SDLCALL SDL_CreateRGBSurface - (Uint32 flags, int width, int height, int depth, - Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); - -SDL_CreateRGBSurfaceFrom - SDL_Surface * SDLCALL SDL_CreateRGBSurfaceFrom - (void *pixels, int width, int height, int depth, int pitch, - Uint32 Rmask, Uint32 Gmask, Uint32 Bmask, Uint32 Amask); - -SDL_FreeSurface - void SDLCALL SDL_FreeSurface(SDL_Surface *surface); - -SDL_ConvertSurface - SDL_Surface * SDLCALL SDL_ConvertSurface - (SDL_Surface *src, SDL_PixelFormat *fmt, Uint32 flags); - -SDL_LockSurface - int SDLCALL SDL_LockSurface(SDL_Surface *surface); - -SDL_UnlockSurface - void SDLCALL SDL_UnlockSurface(SDL_Surface *surface); -*/ - -static vPtr (*_SDL_CreateRGBSurface)(uint32_t flags, int width, int height, int depth, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) = 0; -DFhackCExport vPtr SDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) -{ - return _SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask); -} - -static vPtr (*_SDL_CreateRGBSurfaceFrom)(vPtr pixels, int width, int height, int depth, int pitch, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) = 0; -DFhackCExport vPtr SDL_CreateRGBSurfaceFrom(vPtr pixels, int width, int height, int depth, int pitch, - uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) -{ - return _SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask); -} - -static void (*_SDL_FreeSurface)(vPtr surface) = 0; -DFhackCExport void SDL_FreeSurface(vPtr surface) -{ - _SDL_FreeSurface(surface); -} - -static vPtr (*_SDL_ConvertSurface)(vPtr surface, vPtr format, uint32_t flags) = 0; -DFhackCExport vPtr SDL_ConvertSurface(vPtr surface, vPtr format, uint32_t flags) -{ - return _SDL_ConvertSurface(surface, format, flags); -} - -static int (*_SDL_LockSurface)(vPtr surface) = 0; -DFhackCExport int SDL_LockSurface(vPtr surface) -{ - return _SDL_LockSurface(surface); -} - -static void (*_SDL_UnlockSurface)(vPtr surface) = 0; -DFhackCExport void SDL_UnlockSurface(vPtr surface) -{ - _SDL_UnlockSurface(surface); -} - -/***** More surface stuff -SDL_MapRGB - Uint32 SDLCALL SDL_MapRGB - (const SDL_PixelFormat * const format, const Uint8 r, const Uint8 g, const Uint8 b); - -SDL_SaveBMP_RW - int SDLCALL SDL_SaveBMP_RW - (SDL_Surface *surface, SDL_RWops *dst, int freedst); - -SDL_SetAlpha - int SDLCALL SDL_SetAlpha(SDL_Surface *surface, Uint32 flag, Uint8 alpha); - -SDL_SetColorKey - int SDLCALL SDL_SetColorKey(SDL_Surface *surface, Uint32 flag, Uint32 key); - -SDL_GetVideoInfo - const SDL_VideoInfo * SDLCALL SDL_GetVideoInfo(void); - -SDL_SetVideoMode - SDL_Surface * SDLCALL SDL_SetVideoMode - (int width, int height, int bpp, Uint32 flags); - -SDL_UpperBlit - int SDLCALL SDL_UpperBlit - (SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect); -*/ - -static uint32_t (*_SDL_MapRGB)(vPtr pixelformat, uint8_t r, uint8_t g, uint8_t b) = 0; -DFhackCExport uint32_t SDL_MapRGB(vPtr pixelformat, uint8_t r, uint8_t g, uint8_t b) -{ - return _SDL_MapRGB(pixelformat,r,g,b); -} - -static int (*_SDL_SaveBMP_RW)(vPtr surface, vPtr dst, int freedst) = 0; -DFhackCExport int SDL_SaveBMP_RW(vPtr surface, vPtr dst, int freedst) -{ - return _SDL_SaveBMP_RW(surface,dst,freedst); -} - -static int (*_SDL_SetAlpha)(vPtr surface, uint32_t flag, uint8_t alpha) = 0; -DFhackCExport int SDL_SetAlpha(vPtr surface, uint32_t flag, uint8_t alpha) -{ - return _SDL_SetAlpha(surface,flag,alpha); -} - -static int (*_SDL_SetColorKey)(vPtr surface, uint32_t flag, uint32_t key) = 0; -DFhackCExport int SDL_SetColorKey(vPtr surface, uint32_t flag, uint32_t key) -{ - return _SDL_SetColorKey(surface,flag,key); -} - -static vPtr (*_SDL_GetVideoInfo)(void) = 0; -DFhackCExport vPtr SDL_GetVideoInfo(void) -{ - return _SDL_GetVideoInfo(); -} - -static vPtr (*_SDL_SetVideoMode)(int width, int height, int bpp, uint32_t flags) = 0; -DFhackCExport vPtr SDL_SetVideoMode(int width, int height, int bpp, uint32_t flags) -{ - return _SDL_SetVideoMode(width, height, bpp, flags); -} - -static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; -DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - if ( c.isValid() && dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) - { - DFHack::Graphic* g = c.getGraphic(); - DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); - - if ( ov != NULL ) - { - if ( ov->paintOver ) - { - _SDL_UpperBlit(src, srcrect, dst, dstrect); - } - - DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; - dstrect2->x = dstrect->x; - dstrect2->y = dstrect->y; - dstrect2->w = dstrect->w; - dstrect2->h = dstrect->h; - - if ( ov->dstResize != NULL ) - { - DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; - dstrect2->x += r->x; - dstrect2->y += r->y; - dstrect2->w += r->w; - dstrect2->h += r->h; - } - - int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); - delete dstrect2; - return result; - } - } - - return _SDL_UpperBlit(src, srcrect, dst, dstrect); -} - -/***** Even more surface -SDL_GL_GetAttribute - int SDLCALL SDL_GL_GetAttribute(SDL_GLattr attr, int* value); - -SDL_GL_SetAttribute - int SDLCALL SDL_GL_SetAttribute(SDL_GLattr attr, int value); - -SDL_WM_SetCaption - void SDLCALL SDL_WM_SetCaption(const char *title, const char *icon); - -SDL_WM_SetIcon - void SDLCALL SDL_WM_SetIcon(SDL_Surface *icon, Uint8 *mask); - -SDL_FillRect - int SDLCALL SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, Uint32 color); -*/ - - -static void * (*_SDL_GetVideoSurface)( void ) = 0; -DFhackCExport void * SDL_GetVideoSurface(void) -{ - return _SDL_GetVideoSurface(); -} - -static void * (*_SDL_DisplayFormat)( void * surface ) = 0; -DFhackCExport void * SDL_DisplayFormat(void *surface) -{ - return _SDL_DisplayFormat(surface); -} - -// SDL_Surface *SDL_DisplayFormatAlpha(SDL_Surface *surface); -static void * (*_SDL_DisplayFormatAlpha)( void * surface ) = 0; -DFhackCExport void * SDL_DisplayFormatAlpha(void *surface) -{ - return _SDL_DisplayFormatAlpha(surface); -} - -//void SDL_GetRGBA(Uint32 pixel, SDL_PixelFormat *fmt, Uint8 *r, Uint8 *g, Uint8 *b, Uint8 *a); - -static void (*_SDL_GetRGBA)(uint32_t pixel, void * fmt, uint8_t * r, uint8_t * g, uint8_t * b, uint8_t *a) = 0; -DFhackCExport void SDL_GetRGBA(uint32_t pixel, void * fmt, uint8_t * r, uint8_t * g, uint8_t * b, uint8_t *a) -{ - return _SDL_GetRGBA(pixel, fmt, r, g, b, a); -} - -static int (*_SDL_GL_GetAttribute)(int attr, int * value) = 0; -DFhackCExport int SDL_GL_GetAttribute(int attr, int * value) -{ - return _SDL_GL_GetAttribute(attr,value); -} - -static int (*_SDL_GL_SetAttribute)(int attr, int value) = 0; -DFhackCExport int SDL_GL_SetAttribute(int attr, int value) -{ - return _SDL_GL_SetAttribute(attr,value); -} - -static void (*_SDL_WM_SetCaption)(const char *title, const char *icon) = 0; -DFhackCExport void SDL_WM_SetCaption(const char *title, const char *icon) -{ - //_SDL_WM_SetCaption("DwarfHacked the Fortress of Hacks",icon); - _SDL_WM_SetCaption(title,icon); -} - -static void (*_SDL_WM_SetIcon)(vPtr icon, uint8_t *mask) = 0; -DFhackCExport void SDL_WM_SetIcon(vPtr icon, uint8_t *mask) -{ - _SDL_WM_SetIcon(icon, mask); -} - -static int (*_SDL_FillRect)(vPtr dst, vPtr dstrect, uint32_t color) = 0; -DFhackCExport int SDL_FillRect(vPtr dst, vPtr dstrect, uint32_t color) -{ - return _SDL_FillRect(dst,dstrect,color); -} - -/***** Events and input -SDL_EnableKeyRepeat - int SDLCALL SDL_EnableKeyRepeat(int delay, int interval); -SDL_EnableUNICODE - int SDLCALL SDL_EnableUNICODE(int enable); -SDL_GetKeyState - Uint8 * SDLCALL SDL_GetKeyState(int *numkeys); -SDL_PollEvent - int SDLCALL SDL_PollEvent(SDL_Event *event); -SDL_PushEvent - int SDLCALL SDL_PushEvent(SDL_Event *event); -*/ - -static int (*_SDL_EnableKeyRepeat)(int delay, int interval) = 0; -DFhackCExport int SDL_EnableKeyRepeat(int delay, int interval) -{ - return _SDL_EnableKeyRepeat(delay, interval); -} - -static int (*_SDL_EnableUNICODE)(int enable) = 0; -DFhackCExport int SDL_EnableUNICODE(int enable) -{ - if(!enable) - { - fprintf(stderr, "SDL_EnableUNICODE turned off. Keybindings may break.\n"); - } - return _SDL_EnableUNICODE(enable); -} - -static uint8_t * (*_SDL_GetKeyState)(int* numkeys) = 0; -DFhackCExport uint8_t * SDL_GetKeyState(int* numkeys) -{ - return _SDL_GetKeyState(numkeys); -} - -// called by DF to check input events -static int (*_SDL_PollEvent)(SDL::Event* event) = 0; -DFhackCExport int SDL_PollEvent(SDL::Event* event) -{ - pollevent_again: - // if SDL returns 0 here, it means there are no more events. return 0 - int orig_return = _SDL_PollEvent(event); - if(!orig_return) - return 0; - // otherwise we have an event to filter - else if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - // if we consume the event, ask SDL for more. - if(!c.DFH_SDL_Event(event)) - goto pollevent_again; - } - return orig_return; -} - -static int (*_SDL_PushEvent)(SDL::Event* event) = 0; -DFhackCExport int SDL_PushEvent(SDL::Event* event) -{ - return _SDL_PushEvent(event); -} - -/***** error handling -SDL_GetError - char * SDLCALL SDL_GetError(void); -SDL_SetError - extern DECLSPEC void SDLCALL SDL_SetError(const char *fmt, ...); -SDL_ClearError - extern DECLSPEC void SDLCALL SDL_ClearError(void); -SDL_Error - extern DECLSPEC void SDLCALL SDL_Error(SDL_errorcode code); -*/ - -static char * (*_SDL_GetError)(void) = 0; -DFhackCExport char * SDL_GetError(void) -{ - return _SDL_GetError(); -} - -static void (*_SDL_SetError)(const char *fmt, ...) = 0; -DFhackCExport void SDL_SetError(const char *fmt, ...) Wformat(printf,1,2) -{ - char buf[1024]; - va_list args; - va_start(args,fmt); - vsnprintf(buf, sizeof(buf) - 1 ,fmt,args); - va_end(args); - _SDL_SetError(buf); -} - -static void (*_SDL_ClearError)(void) = 0; -DFhackCExport void SDL_ClearError(void) -{ - _SDL_ClearError(); -} - -static void (*_SDL_Error)(int code) = 0; -DFhackCExport void SDL_Error(int code) -{ - _SDL_Error(code); -} - -/***** symbol resolution -SDL_LoadFunction - extern DECLSPEC void * SDLCALL SDL_LoadFunction(void *handle, const char *name); -SDL_LoadObject - extern DECLSPEC void * SDLCALL SDL_LoadObject(const char *sofile); -SDL_UnloadObject - extern DECLSPEC void SDLCALL SDL_UnloadObject(void *handle); -*/ - -static void * (*_SDL_LoadFunction)(vPtr handle, const char *name) = 0; -DFhackCExport void * SDL_LoadFunction(vPtr handle, const char *name) -{ - return _SDL_LoadFunction(handle, name); -} - -extern "C" static vPtr (*_SDL_LoadObject)(const char *sofile) = 0; -DFhackCExport vPtr SDL_LoadObject(const char *sofile) -{ - return _SDL_LoadObject(sofile); -} - -static void (*_SDL_UnloadObject)(vPtr handle) = 0; -DFhackCExport void SDL_UnloadObject(vPtr handle) -{ - _SDL_UnloadObject(handle); -} - -/***** r/w -SDL_ReadBE32 - extern DECLSPEC Uint32 SDLCALL SDL_ReadBE32(SDL_RWops *src); -SDL_ReadLE16 - extern DECLSPEC Uint16 SDLCALL SDL_ReadLE16(SDL_RWops *src); -SDL_ReadLE32 - extern DECLSPEC Uint32 SDLCALL SDL_ReadLE32(SDL_RWops *src); -*/ - -static uint32_t (*_SDL_ReadBE32)(vPtr src) = 0; -DFhackCExport uint32_t SDL_ReadBE32(vPtr src) -{ - return _SDL_ReadBE32(src); -} - -static uint16_t (*_SDL_ReadLE16)(vPtr src) = 0; -DFhackCExport uint16_t SDL_ReadLE16(vPtr src) -{ - return _SDL_ReadLE16(src); -} - -static uint32_t (*_SDL_ReadLE32)(vPtr src) = 0; -DFhackCExport uint32_t SDL_ReadLE32(vPtr src) -{ - return _SDL_ReadLE32(src); -} - -/***** Misc -SDL_RWFromFile - SDL_RWops * SDLCALL SDL_RWFromFile(const char *file, const char *mode); -SDL_SetModuleHandle - void SDLCALL SDL_SetModuleHandle(void *hInst); -SDL_ShowCursor - int SDLCALL SDL_ShowCursor(int toggle); -SDL_strlcpy - size_t SDLCALL SDL_strlcpy(char *dst, const char *src, size_t maxlen); -*/ - -static vPtr (*_SDL_RWFromFile)(const char* file, const char *mode) = 0; -DFhackCExport vPtr SDL_RWFromFile(const char* file, const char *mode) -{ - return _SDL_RWFromFile(file, mode); -} - -static void (*_SDL_SetModuleHandle)(vPtr hInst) = 0; -DFhackCExport void SDL_SetModuleHandle(vPtr hInst) -{ - _SDL_SetModuleHandle(hInst); -} - -static int (*_SDL_ShowCursor)(int toggle) = 0; -DFhackCExport int SDL_ShowCursor(int toggle) -{ - return _SDL_ShowCursor(toggle); -} - -static size_t (*_SDL_strlcpy)(char *dst, const char *src, size_t maxlen) = 0; -DFhackCExport size_t SDL_strlcpy(char *dst, const char *src, size_t maxlen) -{ - if(!_SDL_strlcpy) - { - HMODULE realSDLlib = LoadLibrary("SDLreal.dll"); - if(!realSDLlib) - { - exit(-111); - } - _SDL_strlcpy = (size_t (*)(char*, const char*, size_t))GetProcAddress(realSDLlib,"SDL_strlcpy"); - } - return _SDL_strlcpy(dst,src,maxlen); -} - -/***** The real meat of this -SDL_Init -SDL_Quit -SDL_GL_SwapBuffers - void SDLCALL SDL_GL_SwapBuffers(void); -*/ - - -// hook - called at program exit -static void (*_SDL_Quit)(void) = 0; -DFhackCExport void SDL_Quit(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - c.Shutdown(); - if(_SDL_Quit) - { - _SDL_Quit(); - } -} -// this is supported from 0.31.04 forward -DFhackCExport int SDL_NumJoysticks(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} - -static void (*_SDL_GL_SwapBuffers)(void) = 0; -DFhackCExport void SDL_GL_SwapBuffers(void) -{ - InitSDLPointers(); - _SDL_GL_SwapBuffers(); -} - -// hook - called every tick in the 2D mode of DF -static int (*_SDL_Flip)(void * some_ptr) = 0; -DFhackCExport int SDL_Flip(void * some_ptr) -{ - InitSDLPointers(); - return _SDL_Flip(some_ptr); -} - -static int (*_SDL_Init)(uint32_t flags) = 0; -DFhackCExport int SDL_Init(uint32_t flags) -{ - InitSDLPointers(); - return _SDL_Init(flags); -} - -/* -MORE CRAP -*/ -static void * (*_SDL_CreateSemaphore)(uint32_t initial_value) = 0; -DFhackCExport void *SDL_CreateSemaphore(uint32_t initial_value) -{ - InitSDLPointers(); - return _SDL_CreateSemaphore(initial_value); -} - -static vPtr (*_SDL_CreateThread)(int (*fn)(void *), void *data) = 0; -DFhackCExport vPtr SDL_CreateThread(int (*fn)(void *), void *data) -{ - InitSDLPointers(); - return _SDL_CreateThread(fn,data); -} - - -static void (*_SDL_Delay)(uint32_t ms) = 0; -DFhackCExport void SDL_Delay(uint32_t ms) -{ - InitSDLPointers(); - _SDL_Delay(ms); -} - -static void (*_SDL_DestroySemaphore)(void *sem) = 0; -DFhackCExport void SDL_DestroySemaphore(void *sem) -{ - InitSDLPointers(); - _SDL_DestroySemaphore(sem); -} - -static vPtr (*_SDL_ListModes)(vPtr format, uint32_t flags) = 0; -DFhackCExport vPtr SDL_ListModes(vPtr format, uint32_t flags) -{ - InitSDLPointers(); - return _SDL_ListModes(format, flags); -} - -static uint8_t (*_SDL_GetAppState)(void) = 0; -DFhackCExport uint8_t SDL_GetAppState(void) -{ - InitSDLPointers(); - return _SDL_GetAppState(); -} - -static uint8_t (*_SDL_GetMouseState)(int *, int *) = 0; -DFhackCExport uint8_t SDL_GetMouseState(int *x, int *y) -{ - InitSDLPointers(); - return _SDL_GetMouseState(x,y); -} - -static int (*_SDL_InitSubSystem)(uint32_t flags) = 0; -DFhackCExport int SDL_InitSubSystem(uint32_t flags) -{ - InitSDLPointers(); - return _SDL_InitSubSystem(flags); -} - -static int (*_SDL_SemPost)(void *sem) = 0; -DFhackCExport int SDL_SemPost(void *sem) -{ - InitSDLPointers(); - return _SDL_SemPost(sem); -} - -static int (*_SDL_SemTryWait)(void *sem) = 0; -DFhackCExport int SDL_SemTryWait(void *sem) -{ - InitSDLPointers(); - return _SDL_SemTryWait(sem); -} - -static int (*_SDL_SemWait)(void *sem) = 0; -DFhackCExport int SDL_SemWait(void *sem) -{ - InitSDLPointers(); - return _SDL_SemWait(sem); -} - -static uint32_t (*_SDL_ThreadID)(void) = 0; -DFhackCExport uint32_t SDL_ThreadID(void) -{ - InitSDLPointers(); - return _SDL_ThreadID(); -} - -static char* (*_SDL_getenv)(const char *name) = 0; -DFhackCExport char* SDL_getenv(const char *name) -{ - InitSDLPointers(); - return _SDL_getenv(name); -} - -static size_t (*_SDL_strlcat)(char *dst, const char *src, size_t maxlen) = 0; -DFhackCExport size_t SDL_strlcat(char *dst, const char *src, size_t maxlen) -{ - InitSDLPointers(); - return _SDL_strlcat(dst, src, maxlen); -} - -void FirstCall() -{ - // reroute stdout and stderr - freopen("stdout.log", "w", stdout); - freopen("stderr.log", "w", stderr); - HMODULE realSDLlib = LoadLibrary("SDLreal.dll"); - if(!realSDLlib) - { - MessageBox(0,"Can't load SDLreal.dll\n","Error", MB_OK); - fprintf(stderr, "Can't load SDLreal.dll\n"); - return; - } - fprintf(stderr, "FirstCall()\n"); - // stuff for DF - _SDL_AddTimer = (void*(*)(uint32_t, void*, void*)) GetProcAddress(realSDLlib,"SDL_AddTimer"); - _SDL_CondSignal = (int (*)(vPtr))GetProcAddress(realSDLlib,"SDL_CondSignal"); - _SDL_CondWait = (int (*)(vPtr, vPtr))GetProcAddress(realSDLlib,"SDL_CondWait"); - _SDL_ConvertSurface = (void*(*)(void*, void*, uint32_t))GetProcAddress(realSDLlib,"SDL_ConvertSurface"); - _SDL_CreateCond = (vPtr(*)())GetProcAddress(realSDLlib,"SDL_CreateCond"); - _SDL_CreateMutex = (vPtr(*)())GetProcAddress(realSDLlib,"SDL_CreateMutex"); - _SDL_CreateRGBSurface = (void*(*)(uint32_t, int, int, int, uint32_t, uint32_t, uint32_t, uint32_t))GetProcAddress(realSDLlib,"SDL_CreateRGBSurface"); - _SDL_CreateRGBSurfaceFrom = (void*(*)(void*, int, int, int, int, uint32_t, uint32_t, uint32_t, uint32_t))GetProcAddress(realSDLlib,"SDL_CreateRGBSurfaceFrom"); - _SDL_DestroyCond = (void (*)(vPtr))GetProcAddress(realSDLlib,"SDL_DestroyCond"); - _SDL_DestroyMutex = (void (*)(vPtr))GetProcAddress(realSDLlib,"SDL_DestroyMutex"); - _SDL_EnableKeyRepeat = (int (*)(int, int))GetProcAddress(realSDLlib,"SDL_EnableKeyRepeat"); - _SDL_EnableUNICODE = (int (*)(int))GetProcAddress(realSDLlib,"SDL_EnableUNICODE"); - _SDL_GetVideoSurface = (void*(*)())GetProcAddress(realSDLlib,"SDL_GetVideoSurface"); - _SDL_DisplayFormat = (void * (*) (void *))GetProcAddress(realSDLlib,"SDL_DisplayFormat"); - _SDL_DisplayFormatAlpha = (void * (*) (void *))GetProcAddress(realSDLlib,"SDL_DisplayFormatAlpha"); - _SDL_GetRGBA = (void (*) (uint32_t, void *, uint8_t *, uint8_t *, uint8_t *, uint8_t *))GetProcAddress(realSDLlib,"SDL_GetRGBA"); - _SDL_FreeSurface = (void (*)(void*))GetProcAddress(realSDLlib,"SDL_FreeSurface"); - _SDL_GL_GetAttribute = (int (*)(int, int*))GetProcAddress(realSDLlib,"SDL_GL_GetAttribute"); - _SDL_GL_SetAttribute = (int (*)(int, int))GetProcAddress(realSDLlib,"SDL_GL_SetAttribute"); - _SDL_GL_SwapBuffers = (void (*)())GetProcAddress(realSDLlib,"SDL_GL_SwapBuffers"); - _SDL_GetError = (char*(*)())GetProcAddress(realSDLlib,"SDL_GetError"); - _SDL_GetKeyState = (uint8_t*(*)(int*))GetProcAddress(realSDLlib,"SDL_GetKeyState"); - _SDL_GetTicks = (uint32_t (*)())GetProcAddress(realSDLlib,"SDL_GetTicks"); - _SDL_GetVideoInfo = (void*(*)())GetProcAddress(realSDLlib,"SDL_GetVideoInfo"); - _SDL_Init = (int (*)(uint32_t))GetProcAddress(realSDLlib,"SDL_Init"); - _SDL_Flip = (int (*)( void * )) GetProcAddress(realSDLlib, "SDL_Flip"); - _SDL_LockSurface = (int (*)(void*))GetProcAddress(realSDLlib,"SDL_LockSurface"); - _SDL_MapRGB = (uint32_t (*)(void*, uint8_t, uint8_t, uint8_t))GetProcAddress(realSDLlib,"SDL_MapRGB"); - _SDL_PollEvent = (int (*)(SDL::Event*))GetProcAddress(realSDLlib,"SDL_PollEvent"); - _SDL_PushEvent = (int (*)(SDL::Event*))GetProcAddress(realSDLlib,"SDL_PushEvent"); - _SDL_Quit = (void (*)())GetProcAddress(realSDLlib,"SDL_Quit"); - _SDL_RWFromFile = (void*(*)(const char*, const char*))GetProcAddress(realSDLlib,"SDL_RWFromFile"); - _SDL_RemoveTimer = (bool (*)(void*))GetProcAddress(realSDLlib,"SDL_RemoveTimer"); - _SDL_SaveBMP_RW = (int (*)(void*, void*, int))GetProcAddress(realSDLlib,"SDL_SaveBMP_RW"); - _SDL_SetAlpha = (int (*)(void*, uint32_t, uint8_t))GetProcAddress(realSDLlib,"SDL_SetAlpha"); - _SDL_SetColorKey = (int (*)(void*, uint32_t, uint32_t))GetProcAddress(realSDLlib,"SDL_SetColorKey"); - _SDL_SetModuleHandle = (void (*)(void*))GetProcAddress(realSDLlib,"SDL_SetModuleHandle"); - _SDL_SetVideoMode = (void*(*)(int, int, int, uint32_t))GetProcAddress(realSDLlib,"SDL_SetVideoMode"); - _SDL_ShowCursor = (int (*)(int))GetProcAddress(realSDLlib,"SDL_ShowCursor"); - _SDL_UnlockSurface = (void (*)(void*))GetProcAddress(realSDLlib,"SDL_UnlockSurface"); - _SDL_UpperBlit = (int (*)(DFHack::DFSDL_Surface*, DFHack::DFSDL_Rect*, DFHack::DFSDL_Surface*, DFHack::DFSDL_Rect*))GetProcAddress(realSDLlib,"SDL_UpperBlit"); - _SDL_WM_SetCaption = (void (*)(const char*, const char*))GetProcAddress(realSDLlib,"SDL_WM_SetCaption"); - _SDL_WM_SetIcon = (void (*)(void*, uint8_t*))GetProcAddress(realSDLlib,"SDL_WM_SetIcon"); - _SDL_mutexP = (int (*)(vPtr))GetProcAddress(realSDLlib,"SDL_mutexP"); - _SDL_mutexV = (int (*)(vPtr))GetProcAddress(realSDLlib,"SDL_mutexV"); - _SDL_strlcpy = (size_t (*)(char*, const char*, size_t))GetProcAddress(realSDLlib,"SDL_strlcpy"); - - // stuff for SDL_Image - _SDL_ClearError = (void (*)())GetProcAddress(realSDLlib,"SDL_ClearError"); - _SDL_Error = (void (*)(int))GetProcAddress(realSDLlib,"SDL_Error"); - _SDL_LoadFunction = (void*(*)(vPtr, const char*))GetProcAddress(realSDLlib,"SDL_LoadFunction"); - _SDL_LoadObject = (vPtr(*)(const char*))GetProcAddress(realSDLlib,"SDL_LoadObject"); - _SDL_ReadBE32 = (uint32_t (*)(void*))GetProcAddress(realSDLlib,"SDL_ReadBE32"); - _SDL_ReadLE16 = (uint16_t (*)(void*))GetProcAddress(realSDLlib,"SDL_ReadLE16"); - _SDL_ReadLE32 = (uint32_t (*)(void*))GetProcAddress(realSDLlib,"SDL_ReadLE32"); - _SDL_SetError = (void (*)(const char*, ...))GetProcAddress(realSDLlib,"SDL_SetError"); - _SDL_UnloadObject = (void (*)(vPtr))GetProcAddress(realSDLlib,"SDL_UnloadObject"); - _SDL_FillRect = (int (*)(void*,void*,uint32_t))GetProcAddress(realSDLlib,"SDL_FillRect"); - - // new in DF 0.31.04 - _SDL_CreateSemaphore = (void* (*)(uint32_t))GetProcAddress(realSDLlib,"SDL_CreateSemaphore"); - _SDL_CreateThread = (vPtr (*)(int (*fn)(void *), void *data))GetProcAddress(realSDLlib,"SDL_CreateThread"); - _SDL_Delay = (void (*)(uint32_t))GetProcAddress(realSDLlib,"SDL_Delay"); - _SDL_DestroySemaphore = (void (*)(void *))GetProcAddress(realSDLlib,"SDL_DestroySemaphore"); - _SDL_GetAppState = (uint8_t (*)(void))GetProcAddress(realSDLlib,"SDL_GetAppState"); - _SDL_GetMouseState = (uint8_t (*)(int *, int *))GetProcAddress(realSDLlib,"SDL_GetMouseState"); - _SDL_InitSubSystem = (int (*)(uint32_t))GetProcAddress(realSDLlib,"SDL_InitSubSystem"); - _SDL_SemPost = (int (*)(void *))GetProcAddress(realSDLlib,"SDL_SemPost"); - _SDL_SemTryWait = (int (*)(void *))GetProcAddress(realSDLlib,"SDL_SemTryWait"); - _SDL_SemWait = (int (*)(void *))GetProcAddress(realSDLlib,"SDL_SemWait"); - _SDL_ThreadID = (uint32_t (*)(void))GetProcAddress(realSDLlib,"SDL_ThreadID"); - - // new in DF 0.43.05 - _SDL_getenv = (char* (*)(const char*))GetProcAddress(realSDLlib,"SDL_getenv"); - _SDL_strlcat = (size_t (*)(char*, const char*, size_t))GetProcAddress(realSDLlib,"SDL_strlcat"); - - // new in DF v50.01 - _SDL_ListModes = (void *(*)(void*, uint32_t))GetProcAddress(realSDLlib,"SDL_ListModes"); - - _SDL_EnableUNICODE(1); - - fprintf(stderr,"Initized HOOKS!\n"); -} - -void InitSDLPointers() -{ - std::call_once(inited, [](){ FirstCall(); }); -} diff --git a/library/Hooks.cpp b/library/Hooks.cpp index 36af2617c..4e339e768 100644 --- a/library/Hooks.cpp +++ b/library/Hooks.cpp @@ -1,33 +1,65 @@ #include "Core.h" #include "Export.h" -// called before main event loop starts +#include "df/gamest.h" + +static bool disabled = false; + +// called from the main thread before the simulation thread is started +// and the main event loop is initiated DFhackCExport void dfhooks_init() { - DFHack::Core::getInstance().Init(); + if (getenv("DFHACK_DISABLE")) { + fprintf(stdout, "dfhack: DFHACK_DISABLE detected in environment; disabling\n"); + disabled = true; + return; + } + + // we need to init DF globals before we can check the commandline + if (!DFHack::Core::getInstance().InitMainThread() || !df::global::game) + return; + const std::string & cmdline = df::global::game->command_line.original; + if (cmdline.find("--disable-dfhack") != std::string::npos) { + fprintf(stdout, "dfhack: --disable-dfhack specified on commandline; disabling\n"); + disabled = true; + } } -// called after main event loops exits +// called from the main thread after the main event loops exits DFhackCExport void dfhooks_shutdown() { + if (disabled) + return; DFHack::Core::getInstance().Shutdown(); } -// called in the main event loop +// called from the simulation thread in the main event loop DFhackCExport void dfhooks_update() { + if (disabled) + return; DFHack::Core::getInstance().Update(); } -// called just before adding the macro recording/playback overlay +// called from the simulation thread just before adding the macro +// recording/playback overlay DFhackCExport void dfhooks_prerender() { + if (disabled) + return; // TODO: render overlay widgets that are not attached to a viewscreen } -// called for each SDL event, if true is returned, then the event has been -// consumed and further processing shouldn't happen +// called from the main thread for each SDL event. if true is returned, then +// the event has been consumed and further processing shouldn't happen DFhackCExport bool dfhooks_sdl_event(SDL::Event* event) { + if (disabled) + return false; return DFHack::Core::getInstance().DFH_SDL_Event(event); } -// called for each utf-8 char read from the ncurses input + +// called from the main thread for each utf-8 char read from the ncurses input // key is positive for ncurses keys and negative for everything else +// if true is returned, then the event has been consumed and further processing +// shouldn't happen DFhackCExport bool dfhooks_ncurses_key(int key) { + if (disabled) + return false; return DFHack::Core::getInstance().DFH_ncurses_key(key); } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5e5fa7ff3..0a737875a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1718,6 +1718,7 @@ static const LuaWrapper::FunctionReg dfhack_textures_module[] = { WRAPM(Textures, getControlPanelTexposStart), WRAPM(Textures, getThinBordersTexposStart), WRAPM(Textures, getMediumBordersTexposStart), + WRAPM(Textures, getBoldBordersTexposStart), WRAPM(Textures, getPanelBordersTexposStart), WRAPM(Textures, getWindowBordersTexposStart), { NULL, NULL } @@ -2728,13 +2729,9 @@ static int filesystem_listdir_recursive(lua_State *L) include_prefix = lua_toboolean(L, 3); std::map files; int err = DFHack::Filesystem::listdir_recursive(dir, files, depth, include_prefix); - if (err) - { + if (err != 0 && err != -1) { lua_pushnil(L); - if (err == -1) - lua_pushfstring(L, "max depth exceeded: %d", depth); - else - lua_pushstring(L, strerror(err)); + lua_pushstring(L, strerror(err)); lua_pushinteger(L, err); return 3; } diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 6a8becaae..0aa68eb51 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -150,7 +150,11 @@ int RemoteClient::GetDefaultPort() if (in_file) { Json::Value config; - in_file >> config; + try { + in_file >> config; + } catch (const std::exception & e) { + std::cerr << "Error reading remote server config file: " << filename << ": " << e.what() << std::endl; + } in_file.close(); if (config.isMember("port")) { port = config["port"].asInt(); diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 734b80702..77510d63a 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -420,17 +420,20 @@ ServerMainImpl::ServerMainImpl(std::promise promise, int port) : Json::Value configJson; - std::ifstream inFile(filename, std::ios_base::in); - bool allow_remote = false; - if (inFile.is_open()) - { - inFile >> configJson; - inFile.close(); - - allow_remote = configJson.get("allow_remote", "false").asBool(); + std::ifstream inFile(filename, std::ios_base::in); + try { + if (inFile.is_open()) + { + inFile >> configJson; + allow_remote = configJson.get("allow_remote", "false").asBool(); + } + } catch (const std::exception & e) { + std::cerr << "Error reading remote server config file: " << filename << ": " << e.what() << std::endl; + std::cerr << "Reverting to remote server config to defaults" << std::endl; } + inFile.close(); // rewrite/normalize config file configJson["allow_remote"] = allow_remote; diff --git a/library/include/Core.h b/library/include/Core.h index 6c99e62be..696be4ead 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -108,19 +108,6 @@ namespace DFHack // Better than tracking some weird variables all over the place. class DFHACK_EXPORT Core { -#ifdef _DARWIN - friend int ::DFH_SDL_NumJoysticks(void); - friend void ::DFH_SDL_Quit(void); - friend int ::DFH_SDL_PollEvent(SDL::Event *); - friend int ::DFH_SDL_Init(uint32_t flags); - friend int ::DFH_wgetch(WINDOW * w); -#else - friend int ::SDL_NumJoysticks(void); - friend void ::SDL_Quit(void); - friend int ::SDL_PollEvent(SDL::Event *); - friend int ::SDL_Init(uint32_t flags); - friend int ::wgetch(WINDOW * w); -#endif friend void ::dfhooks_init(); friend void ::dfhooks_shutdown(); friend void ::dfhooks_update(); @@ -158,6 +145,7 @@ namespace DFHack bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); bool addScriptPath(std::string path, bool search_before = false); + bool setModScriptPaths(const std::vector &mod_script_paths); bool removeScriptPath(std::string path); std::string findScript(std::string name); void getScriptPaths(std::vector *dest); @@ -173,7 +161,7 @@ namespace DFHack bool RunAlias(color_ostream &out, const std::string &name, const std::vector ¶meters, command_result &result); std::map> ListAliases(); - std::string GetAliasCommand(const std::string &name, const std::string &default_ = ""); + std::string GetAliasCommand(const std::string &name, bool ignore_params = false); std::string getHackPath(); @@ -203,10 +191,11 @@ namespace DFHack struct Private; std::unique_ptr d; - bool Init(); + bool InitMainThread(); + bool InitSimulationThread(); int Update (void); int Shutdown (void); - int DFH_SDL_Event(SDL::Event* event); + bool DFH_SDL_Event(SDL::Event* event); bool ncurses_wgetch(int in, int & out); bool DFH_ncurses_key(int key); @@ -239,7 +228,7 @@ namespace DFHack std::vector> allModules; DFHack::PluginManager * plug_mgr; - std::vector script_paths[2]; + std::vector script_paths[3]; std::mutex script_path_mutex; // hotkey-related stuff diff --git a/library/include/DFHackVersion.h b/library/include/DFHackVersion.h index 1b69dfe55..fbf2539bf 100644 --- a/library/include/DFHackVersion.h +++ b/library/include/DFHackVersion.h @@ -8,7 +8,7 @@ namespace DFHack { int dfhack_abi_version(); const char *git_description(); - const char *git_commit(); + const char* git_commit(bool short_hash = false); const char *git_xml_commit(); const char *git_xml_expected_commit(); bool git_xml_match(); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 689d82260..cab3ee9cc 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -31,6 +31,7 @@ distribution. #include #include #include +#include #include "df/interfacest.h" @@ -341,29 +342,29 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void PushInterfaceKeys(lua_State *L, const std::set &keys); - template - void PushVector(lua_State *state, const T &pvec, bool addn = false) - { - lua_createtable(state,pvec.size(), addn?1:0); + DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos); + DFHACK_EXPORT int PushPosXY(lua_State *state, const df::coord2d &pos); - if (addn) - { - lua_pushinteger(state, pvec.size()); - lua_setfield(state, -2, "n"); + template + void Push(lua_State *L, const std::set &pset) { + lua_createtable(L, 0, pset.size()); + for (auto &entry : pset) { + Lua::Push(L, entry); + Lua::Push(L, true); + lua_settable(L, -3); } + } - for (size_t i = 0; i < pvec.size(); i++) - { - Push(state, pvec[i]); - lua_rawseti(state, -2, i+1); + template + void Push(lua_State *L, const std::unordered_set &pset) { + lua_createtable(L, 0, pset.size()); + for (auto &entry : pset) { + Lua::Push(L, entry); + Lua::Push(L, true); + lua_settable(L, -3); } } - DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec, int idx = 1); - - DFHACK_EXPORT int PushPosXYZ(lua_State *state, const df::coord &pos); - DFHACK_EXPORT int PushPosXY(lua_State *state, const df::coord2d &pos); - template void Push(lua_State *L, const std::map &pmap) { lua_createtable(L, 0, pmap.size()); @@ -391,6 +392,26 @@ namespace DFHack {namespace Lua { lua_settable(state, -3); } + template + void PushVector(lua_State *state, const T &pvec, bool addn = false) + { + lua_createtable(state,pvec.size(), addn?1:0); + + if (addn) + { + lua_pushinteger(state, pvec.size()); + lua_setfield(state, -2, "n"); + } + + for (size_t i = 0; i < pvec.size(); i++) + { + Push(state, pvec[i]); + lua_rawseti(state, -2, i+1); + } + } + + DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec, int idx = 1); + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); DFHACK_EXPORT bool IsCoreContext(lua_State *state); diff --git a/library/include/df/custom/enabler.methods.inc b/library/include/df/custom/enabler.methods.inc index 26a23a4dd..b06af2345 100644 --- a/library/include/df/custom/enabler.methods.inc +++ b/library/include/df/custom/enabler.methods.inc @@ -1,6 +1,6 @@ void zoom_display(df::zoom_commands command) { - SDL_SemWait(async_zoom.sem); + DFHack::DFSDL::DFSDL_SemWait(async_zoom.sem); async_zoom.queue.push_back(command); - SDL_SemPost(async_zoom.sem); - SDL_SemPost(async_zoom.sem_fill); + DFHack::DFSDL::DFSDL_SemPost(async_zoom.sem); + DFHack::DFSDL::DFSDL_SemPost(async_zoom.sem_fill); } diff --git a/library/include/modules/DFSteam.h b/library/include/modules/DFSteam.h new file mode 100644 index 000000000..3144830da --- /dev/null +++ b/library/include/modules/DFSteam.h @@ -0,0 +1,32 @@ +#pragma once + +#include "ColorText.h" +#include "Export.h" + +namespace DFHack +{ + +/** + * The DFSteam module - provides access to Steam functions without actually + * requiring build-time linkage to Steam + * \ingroup grp_modules + * \ingroup grp_dfsdl + */ +namespace DFSteam +{ + +/** + * Call this on DFHack init so we can load the function pointers. Returns false on + * failure. + */ +bool init(DFHack::color_ostream& out); + +/** + * Call this when DFHack is being unloaded. + */ +void cleanup(); + +DFHACK_EXPORT bool DFIsSteamRunningOnSteamDeck(); + +} +} diff --git a/library/include/modules/Textures.h b/library/include/modules/Textures.h index a438f01f4..95e628d5a 100644 --- a/library/include/modules/Textures.h +++ b/library/include/modules/Textures.h @@ -56,6 +56,7 @@ DFHACK_EXPORT long getControlPanelTexposStart(); */ DFHACK_EXPORT long getThinBordersTexposStart(); DFHACK_EXPORT long getMediumBordersTexposStart(); +DFHACK_EXPORT long getBoldBordersTexposStart(); DFHACK_EXPORT long getPanelBordersTexposStart(); DFHACK_EXPORT long getWindowBordersTexposStart(); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 2ee03dfed..78d978147 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -51,6 +51,18 @@ if dfhack.is_core_context then SC_UNPAUSED = 8 end +-- User-changeable options + +dfhack.HIDE_CONSOLE_ON_STARTUP = true +function dfhack.getHideConsoleOnStartup() + return dfhack.HIDE_CONSOLE_ON_STARTUP +end + +dfhack.HIDE_ARMOK_TOOLS = false +function dfhack.getHideArmokTools() + return dfhack.HIDE_ARMOK_TOOLS +end + -- Error handling safecall = dfhack.safecall @@ -702,7 +714,7 @@ local warned_scripts = {} function dfhack.run_script(name,...) if not warned_scripts[name] then local helpdb = require('helpdb') - if helpdb.is_entry(name) and helpdb.get_entry_tags(name).untested then + if helpdb.is_entry(name) and helpdb.get_entry_tags(name).unavailable then warned_scripts[name] = true dfhack.printerr(('UNTESTED WARNING: the "%s" script has not been validated to work well with this version of DF.'):format(name)) dfhack.printerr('It may not work as expected, or it may corrupt your game.') @@ -770,7 +782,7 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) elseif ((type(v.required) == 'boolean' and v.required) or (type(v.required) == 'function' and v.required(flags))) then if not script_flags[flag] then - local msg = v.error or 'Flag "' .. flag .. '" not recognized' + local msg = v.error or ('Flag "' .. flag .. '" not recognized') error(name .. ': ' .. msg) end end diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 7791f3685..d511cd51f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -9,7 +9,7 @@ local getval = utils.getval local to_pen = dfhack.pen.parse -CLEAR_PEN = to_pen{tile=909, ch=32, fg=0, bg=0, write_to_lower=true} +CLEAR_PEN = to_pen{tile=df.global.init.texpos_border_interior, ch=32, fg=0, bg=0, write_to_lower=true} TRANSPARENT_PEN = to_pen{tile=0, ch=0} KEEP_LOWER_PEN = to_pen{ch=32, fg=0, bg=0, keep_lower=true} @@ -745,6 +745,7 @@ end local NO_LOGIC_SCREENS = { 'viewscreen_loadgamest', + 'viewscreen_adopt_regionst', 'viewscreen_export_regionst', 'viewscreen_choose_game_typest', 'viewscreen_worldst', @@ -866,8 +867,17 @@ function ZScreen:onGetSelectedPlant() return zscreen_get_any(self, 'Plant') end --------------------------- --- Framed screen object -- +-- convenience subclass for modal dialogs +ZScreenModal = defclass(ZScreenModal, ZScreen) +ZScreenModal.ATTRS{ + defocusable = false, + force_pause = true, + pass_pause = false, + pass_movement_keys = false, + pass_mouse_clicks = false, +} + +-- Framed screen object -------------------------- -- Plain grey-colored frame. @@ -913,14 +923,26 @@ local function make_frame(name, double_line) return frame end -WINDOW_FRAME = make_frame('Window', true) -PANEL_FRAME = make_frame('Panel', false) -MEDIUM_FRAME = make_frame('Medium', false) -INTERIOR_FRAME = make_frame('Thin', false) -INTERIOR_FRAME.signature_pen = false +FRAME_WINDOW = make_frame('Window', true) +FRAME_PANEL = make_frame('Panel', false) +FRAME_MEDIUM = make_frame('Medium', false) +FRAME_BOLD = make_frame('Bold', true) +FRAME_INTERIOR = make_frame('Thin', false) +FRAME_INTERIOR.signature_pen = false +FRAME_INTERIOR_MEDIUM = copyall(FRAME_MEDIUM) +FRAME_INTERIOR_MEDIUM.signature_pen = false -- for compatibility with pre-steam code -GREY_LINE_FRAME = WINDOW_FRAME +GREY_LINE_FRAME = FRAME_PANEL + +-- for compatibility with deprecated frame naming scheme +WINDOW_FRAME = FRAME_WINDOW +PANEL_FRAME = FRAME_PANEL +MEDIUM_FRAME = FRAME_MEDIUM +BOLD_FRAME = FRAME_BOLD +INTERIOR_FRAME = FRAME_INTERIOR +INTERIOR_MEDIUM_FRAME = FRAME_INTERIOR_MEDIUM + function paint_frame(dc,rect,style,title,inactive,pause_forced,resizable) local pen = style.frame_pen @@ -929,8 +951,8 @@ function paint_frame(dc,rect,style,title,inactive,pause_forced,resizable) dscreen.paintTile(style.rt_frame_pen or pen, x2, y1) dscreen.paintTile(style.lb_frame_pen or pen, x1, y2) local rb_frame_pen = style.rb_frame_pen - if rb_frame_pen == WINDOW_FRAME.rb_frame_pen and not resizable then - rb_frame_pen = PANEL_FRAME.rb_frame_pen + if rb_frame_pen == FRAME_WINDOW.rb_frame_pen and not resizable then + rb_frame_pen = FRAME_PANEL.rb_frame_pen end dscreen.paintTile(rb_frame_pen or pen, x2, y2) dscreen.fillRect(style.t_frame_pen or style.h_frame_pen or pen,x1+1,y1,x2-1,y1) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 3b2470114..1b7858416 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -4,9 +4,6 @@ local _ENV = mkmodule('gui.dialogs') local gui = require('gui') local widgets = require('gui.widgets') -local utils = require('utils') - -local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) @@ -66,6 +63,7 @@ function MessageBox:onInput(keys) elseif (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) and self.on_cancel then self.on_cancel() end + gui.markMouseClicksHandled(keys) return true end return self:inputToSubviews(keys) @@ -135,6 +133,7 @@ function InputBox:onInput(keys) if self.on_cancel then self.on_cancel() end + gui.markMouseClicksHandled(keys) return true end return self:inputToSubviews(keys) @@ -218,9 +217,9 @@ end function ListBox:onRenderFrame(dc,rect) ListBox.super.onRenderFrame(self,dc,rect) - --if self.select2_hint then - -- dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_GREY) - --end + if self.select2_hint then + dc:seek(rect.x1+2,rect.y2):string('Shift-Click', COLOR_LIGHTGREEN):string(': '..self.select2_hint, COLOR_GREY) + end end function ListBox:getWantedFrameSize() @@ -236,6 +235,7 @@ function ListBox:onInput(keys) if self.on_cancel then self.on_cancel() end + gui.markMouseClicksHandled(keys) return true end return self:inputToSubviews(keys) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index b86e31710..5c8094d28 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -836,7 +836,9 @@ function Scrollbar:update(top_elem, elems_per_page, num_elems) end local function scrollbar_do_drag(scrollbar) - local _,y = scrollbar.frame_body:localXY(dfhack.screen.getMousePos()) + local x,y = dfhack.screen.getMousePos() + if not y then return end + x,y = scrollbar.frame_body:localXY(x, y) local cur_pos = y - scrollbar.is_dragging local max_top = scrollbar.num_elems - scrollbar.elems_per_page + 1 local max_pos = scrollbar_get_max_pos_and_height(scrollbar) @@ -851,7 +853,7 @@ local function scrollbar_is_visible(scrollbar) return scrollbar.elems_per_page < scrollbar.num_elems end -local SBSO = 922 --Scroll Bar Spritesheet Offset / change this to point to a different spritesheet (ui themes, anyone? :p) +local SBSO = df.global.init.scrollbar_texpos[0] --Scroll Bar Spritesheet Offset / change this to point to a different spritesheet (ui themes, anyone? :p) local SCROLLBAR_UP_LEFT_PEN = to_pen{tile=SBSO+0, ch=47, fg=COLOR_CYAN, bg=COLOR_BLACK} local SCROLLBAR_UP_RIGHT_PEN = to_pen{tile=SBSO+1, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK} local SCROLLBAR_DOWN_LEFT_PEN = to_pen{tile=SBSO+24, ch=92, fg=COLOR_CYAN, bg=COLOR_BLACK} @@ -1121,10 +1123,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled,hpen,hovered) end end - if token.tile then + if token.tile or (hovered and token.htile) then x = x + 1 if dc then - local tile = getval(token.tile) + local tile = hovered and getval(token.htile or token.tile) or getval(token.tile) local tile_pen = tonumber(tile) and to_pen{tile=tile} or tile dc:char(nil, tile_pen) if token.width then @@ -1361,11 +1363,11 @@ function Label:onInput(keys) return true end if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then - self:on_click() + self.on_click() return true end if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then - self:on_rclick() + self.on_rclick() return true end for k,v in pairs(self.scroll_keys) do @@ -1488,6 +1490,8 @@ CycleHotkeyLabel = defclass(CycleHotkeyLabel, Label) CycleHotkeyLabel.ATTRS{ key=DEFAULT_NIL, key_back=DEFAULT_NIL, + key_sep=': ', + option_gap=1, label=DEFAULT_NIL, label_width=DEFAULT_NIL, label_below=false, @@ -1499,17 +1503,16 @@ CycleHotkeyLabel.ATTRS{ function CycleHotkeyLabel:init() self:setOption(self.initial_option) - local val_gap = 1 if self.label_below then - val_gap = 0 + (self.key_back and 1 or 0) + (self.key and 3 or 0) + self.option_gap = self.option_gap + (self.key_back and 1 or 0) + (self.key and 2 or 0) end self:setText{ self.key_back ~= nil and {key=self.key_back, key_sep='', width=0, on_activate=self:callback('cycle', true)} or {}, - {key=self.key, key_sep=': ', text=self.label, width=self.label_width, + {key=self.key, key_sep=self.key_sep, text=self.label, width=self.label_width, on_activate=self:callback('cycle')}, self.label_below and NEWLINE or '', - {gap=val_gap, text=self:callback('getOptionLabel'), + {gap=self.option_gap, text=self:callback('getOptionLabel'), pen=self:callback('getOptionPen')}, } end @@ -1560,17 +1563,17 @@ function CycleHotkeyLabel:setOption(value_or_index, call_on_change) end end -local function cyclehotkeylabel_getOptionElem(self, option_idx, key) +local function cyclehotkeylabel_getOptionElem(self, option_idx, key, require_key) option_idx = option_idx or self.option_idx local option = self.options[option_idx] if type(option) == 'table' then return option[key] end - return option + return not require_key and option or nil end function CycleHotkeyLabel:getOptionLabel(option_idx) - return cyclehotkeylabel_getOptionElem(self, option_idx, 'label') + return getval(cyclehotkeylabel_getOptionElem(self, option_idx, 'label')) end function CycleHotkeyLabel:getOptionValue(option_idx) @@ -1578,7 +1581,7 @@ function CycleHotkeyLabel:getOptionValue(option_idx) end function CycleHotkeyLabel:getOptionPen(option_idx) - local pen = cyclehotkeylabel_getOptionElem(self, option_idx, 'pen') + local pen = getval(cyclehotkeylabel_getOptionElem(self, option_idx, 'pen', true)) if type(pen) == 'string' then return nil end return pen end @@ -1657,6 +1660,13 @@ function List:setChoices(choices, selected) end self:setSelected(selected) + + -- Check if page_top needs to be adjusted + if #self.choices - self.page_size < 0 then + self.page_top = 1 + elseif self.page_top > #self.choices - self.page_size + 1 then + self.page_top = #self.choices - self.page_size + 1 + end end function List:setSelected(selected) @@ -1697,10 +1707,20 @@ local function update_list_scrollbar(list) end function List:postComputeFrame(body) - self.page_size = math.max(1, math.floor(body.height / self.row_height)) - if #self.choices - self.page_size < 0 then + local row_count = body.height // self.row_height + self.page_size = math.max(1, row_count) + + local num_choices = #self.choices + if num_choices == 0 then self.page_top = 1 + update_list_scrollbar(self) + return end + + if self.page_top > num_choices - self.page_size + 1 then + self.page_top = math.max(1, num_choices - self.page_size + 1) + end + update_list_scrollbar(self) end @@ -1929,6 +1949,8 @@ end -- Filtered List -- ------------------- +FILTER_FULL_TEXT = false + FilteredList = defclass(FilteredList, Widget) FilteredList.ATTRS { @@ -2099,19 +2121,22 @@ function FilteredList:setFilter(filter, pos) end for _,key in ipairs(tokens) do key = key:escape_pattern() - -- start matches at non-space or non-punctuation. this allows - -- punctuation itself to be matched if that is useful (e.g. - -- filenames or parameter names) if key ~= '' then if not self.case_sensitive then search_key = string.lower(search_key) key = string.lower(key) end - if not search_key:match('%f[^%p\x00]'..key) and - not search_key:match('%f[^%s\x00]'..key) then - ok = false - break + -- the separate checks for non-space or non-punctuation allows + -- punctuation itself to be matched if that is useful (e.g. + -- filenames or parameter names) + if not FILTER_FULL_TEXT and not search_key:match('%f[^%p\x00]'..key) + and not search_key:match('%f[^%s\x00]'..key) then + ok = false + break + elseif FILTER_FULL_TEXT and not search_key:find(key) then + ok = false + break end end end @@ -2141,34 +2166,35 @@ function FilteredList:onFilterChar(char, text) return true end +local TSO = df.global.init.tabs_texpos[0] -- tab spritesheet offset local DEFAULT_ACTIVE_TAB_PENS = { text_mode_tab_pen=to_pen{fg=COLOR_YELLOW}, text_mode_label_pen=to_pen{fg=COLOR_WHITE}, - lt=to_pen{tile=1005, write_to_lower=true}, - lt2=to_pen{tile=1006, write_to_lower=true}, - t=to_pen{tile=1007, fg=COLOR_BLACK, write_to_lower=true, top_of_text=true}, - rt2=to_pen{tile=1008, write_to_lower=true}, - rt=to_pen{tile=1009, write_to_lower=true}, - lb=to_pen{tile=1015, write_to_lower=true}, - lb2=to_pen{tile=1016, write_to_lower=true}, - b=to_pen{tile=1017, fg=COLOR_BLACK, write_to_lower=true, bottom_of_text=true}, - rb2=to_pen{tile=1018, write_to_lower=true}, - rb=to_pen{tile=1019, write_to_lower=true}, + lt=to_pen{tile=TSO+5, write_to_lower=true}, + lt2=to_pen{tile=TSO+6, write_to_lower=true}, + t=to_pen{tile=TSO+7, fg=COLOR_BLACK, write_to_lower=true, top_of_text=true}, + rt2=to_pen{tile=TSO+8, write_to_lower=true}, + rt=to_pen{tile=TSO+9, write_to_lower=true}, + lb=to_pen{tile=TSO+15, write_to_lower=true}, + lb2=to_pen{tile=TSO+16, write_to_lower=true}, + b=to_pen{tile=TSO+17, fg=COLOR_BLACK, write_to_lower=true, bottom_of_text=true}, + rb2=to_pen{tile=TSO+18, write_to_lower=true}, + rb=to_pen{tile=TSO+19, write_to_lower=true}, } local DEFAULT_INACTIVE_TAB_PENS = { text_mode_tab_pen=to_pen{fg=COLOR_BROWN}, text_mode_label_pen=to_pen{fg=COLOR_DARKGREY}, - lt=to_pen{tile=1000, write_to_lower=true}, - lt2=to_pen{tile=1001, write_to_lower=true}, - t=to_pen{tile=1002, fg=COLOR_WHITE, write_to_lower=true, top_of_text=true}, - rt2=to_pen{tile=1003, write_to_lower=true}, - rt=to_pen{tile=1004, write_to_lower=true}, - lb=to_pen{tile=1010, write_to_lower=true}, - lb2=to_pen{tile=1011, write_to_lower=true}, - b=to_pen{tile=1012, fg=COLOR_WHITE, write_to_lower=true, bottom_of_text=true}, - rb2=to_pen{tile=1013, write_to_lower=true}, - rb=to_pen{tile=1014, write_to_lower=true}, + lt=to_pen{tile=TSO+0, write_to_lower=true}, + lt2=to_pen{tile=TSO+1, write_to_lower=true}, + t=to_pen{tile=TSO+2, fg=COLOR_WHITE, write_to_lower=true, top_of_text=true}, + rt2=to_pen{tile=TSO+3, write_to_lower=true}, + rt=to_pen{tile=TSO+4, write_to_lower=true}, + lb=to_pen{tile=TSO+10, write_to_lower=true}, + lb2=to_pen{tile=TSO+11, write_to_lower=true}, + b=to_pen{tile=TSO+12, fg=COLOR_WHITE, write_to_lower=true, bottom_of_text=true}, + rb2=to_pen{tile=TSO+13, write_to_lower=true}, + rb=to_pen{tile=TSO+14, write_to_lower=true}, } --------- @@ -2238,8 +2264,8 @@ TabBar.ATTRS{ active_tab_pens=DEFAULT_ACTIVE_TAB_PENS, inactive_tab_pens=DEFAULT_INACTIVE_TAB_PENS, get_pens=DEFAULT_NIL, - key=DEFAULT_NIL, - key_back=DEFAULT_NIL, + key='CUSTOM_CTRL_T', + key_back='CUSTOM_CTRL_Y', } function TabBar:init() @@ -2293,4 +2319,141 @@ function TabBar:onInput(keys) end end +-------------------------------- +-- RangeSlider +-- + +RangeSlider = defclass(RangeSlider, Widget) +RangeSlider.ATTRS{ + num_stops=DEFAULT_NIL, + get_left_idx_fn=DEFAULT_NIL, + get_right_idx_fn=DEFAULT_NIL, + on_left_change=DEFAULT_NIL, + on_right_change=DEFAULT_NIL, +} + +function RangeSlider:preinit(init_table) + init_table.frame = init_table.frame or {} + init_table.frame.h = init_table.frame.h or 1 +end + +function RangeSlider:init() + if self.num_stops < 2 then error('too few RangeSlider stops') end + self.is_dragging_target = nil -- 'left', 'right', or 'both' + self.is_dragging_idx = nil -- offset from leftmost dragged tile +end + +local function rangeslider_get_width_per_idx(self) + return math.max(5, (self.frame_body.width-7) // (self.num_stops-1)) +end + +function RangeSlider:onInput(keys) + if not keys._MOUSE_L_DOWN then return false end + local x = self:getMousePos() + if not x then return false end + local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() + local width_per_idx = rangeslider_get_width_per_idx(self) + local left_pos = width_per_idx*(left_idx-1) + local right_pos = width_per_idx*(right_idx-1) + 4 + if x < left_pos then + self.on_left_change(self.get_left_idx_fn() - 1) + elseif x < left_pos+3 then + self.is_dragging_target = 'left' + self.is_dragging_idx = x - left_pos + elseif x < right_pos then + self.is_dragging_target = 'both' + self.is_dragging_idx = x - left_pos + elseif x < right_pos+3 then + self.is_dragging_target = 'right' + self.is_dragging_idx = x - right_pos + else + self.on_right_change(self.get_right_idx_fn() + 1) + end + return true +end + +local function rangeslider_do_drag(self, width_per_idx) + local x = self.frame_body:localXY(dfhack.screen.getMousePos()) + local cur_pos = x - self.is_dragging_idx + cur_pos = math.max(0, cur_pos) + cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos) + local offset = self.is_dragging_target == 'right' and -2 or 1 + local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1 + local new_left_idx, new_right_idx + if self.is_dragging_target == 'right' then + new_right_idx = new_idx + else + new_left_idx = new_idx + if self.is_dragging_target == 'both' then + new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn() + if new_right_idx > self.num_stops then + return + end + end + end + if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then + self.on_left_change(new_left_idx) + end + if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then + self.on_right_change(new_right_idx) + end +end + +local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK} +local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK} +local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK} +local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK} +local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK} +local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK} +local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW} +local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW} +local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW} + +function RangeSlider:onRenderBody(dc, rect) + local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() + local width_per_idx = rangeslider_get_width_per_idx(self) + -- draw track + dc:seek(1,0) + dc:char(nil, SLIDER_LEFT_END) + dc:char(nil, SLIDER_TRACK) + for stop_idx=1,self.num_stops-1 do + local track_stop_pen = SLIDER_TRACK_STOP_SELECTED + local track_pen = SLIDER_TRACK_SELECTED + if left_idx > stop_idx or right_idx < stop_idx then + track_stop_pen = SLIDER_TRACK_STOP + track_pen = SLIDER_TRACK + elseif right_idx == stop_idx then + track_pen = SLIDER_TRACK + end + dc:char(nil, track_stop_pen) + for i=2,width_per_idx do + dc:char(nil, track_pen) + end + end + if right_idx >= self.num_stops then + dc:char(nil, SLIDER_TRACK_STOP_SELECTED) + else + dc:char(nil, SLIDER_TRACK_STOP) + end + dc:char(nil, SLIDER_TRACK) + dc:char(nil, SLIDER_RIGHT_END) + -- draw tabs + dc:seek(width_per_idx*(left_idx-1)) + dc:char(nil, SLIDER_TAB_LEFT) + dc:char(nil, SLIDER_TAB_CENTER) + dc:char(nil, SLIDER_TAB_RIGHT) + dc:seek(width_per_idx*(right_idx-1)+4) + dc:char(nil, SLIDER_TAB_LEFT) + dc:char(nil, SLIDER_TAB_CENTER) + dc:char(nil, SLIDER_TAB_RIGHT) + -- manage dragging + if self.is_dragging_target then + rangeslider_do_drag(self, width_per_idx) + end + if df.global.enabler.mouse_lbut == 0 then + self.is_dragging_target = nil + self.is_dragging_idx = nil + end +end + return _ENV diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua index df5482aec..4ce078a22 100644 --- a/library/lua/helpdb.lua +++ b/library/lua/helpdb.lua @@ -12,6 +12,8 @@ local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' local SCRIPT_DOC_BEGIN = '[====[' local SCRIPT_DOC_END = ']====]' +local GLOBAL_KEY = 'HELPDB' + -- enums local ENTRY_TYPES = { BUILTIN='builtin', @@ -423,6 +425,14 @@ function refresh() ensure_db() end +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc ~= SC_WORLD_LOADED then + return + end + -- pick up widgets from active mods + refresh() +end + local function parse_blocks(text) local blocks = {} for line in text:gmatch('[^\n]*') do @@ -778,7 +788,11 @@ function ls(filter_str, skip_tags, show_dev_commands, exclude_strs) table.insert(excludes, {str=argparse.stringList(exclude_strs)}) end if not show_dev_commands then - table.insert(excludes, {tag='dev'}) + local dev_tags = {'dev', 'unavailable'} + if dfhack.getHideArmokTools() then + table.insert(dev_tags, 'armok') + end + table.insert(excludes, {tag=dev_tags}) end list_entries(skip_tags, include, excludes) end @@ -803,7 +817,16 @@ function tags(tag) local skip_tags = true local include = {entry_type={ENTRY_TYPES.COMMAND}, tag=tag} - list_entries(skip_tags, include) + + local excludes = {tag={}} + if tag ~= 'unavailable' then + table.insert(excludes.tag, 'unavailable') + end + if tag ~= 'armok' and dfhack.getHideArmokTools() then + table.insert(excludes.tag, 'armok') + end + + list_entries(skip_tags, include, excludes) end return _ENV diff --git a/library/lua/script-manager.lua b/library/lua/script-manager.lua index cc5dd9fe3..450012357 100644 --- a/library/lua/script-manager.lua +++ b/library/lua/script-manager.lua @@ -2,23 +2,31 @@ local _ENV = mkmodule('script-manager') local utils = require('utils') +--------------------- +-- enabled API + -- for each script that can be loaded as a module, calls cb(script_name, env) -function foreach_module_script(cb) +function foreach_module_script(cb, preprocess_script_file_fn) for _,script_path in ipairs(dfhack.internal.getScriptPaths()) do local files = dfhack.filesystem.listdir_recursive( script_path, nil, false) if not files then goto skip_path end for _,f in ipairs(files) do - if not f.isdir and - f.path:endswith('.lua') and - not f.path:startswith('test/') and - not f.path:startswith('internal/') then - local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' - local ok, script_env = pcall(reqscript, script_name) - if ok then - cb(script_name, script_env) - end + if f.isdir or not f.path:endswith('.lua') or + f.path:startswith('.git') or + f.path:startswith('test/') or + f.path:startswith('internal/') then + goto continue + end + if preprocess_script_file_fn then + preprocess_script_file_fn(script_path, f.path) + end + local script_name = f.path:sub(1, #f.path - 4) -- remove '.lua' + local ok, script_env = pcall(reqscript, script_name) + if ok then + cb(script_name, script_env) end + ::continue:: end ::skip_path:: end @@ -39,9 +47,17 @@ local function process_script(env_name, env) enabled_map[env_name] = fn end -function reload() +function reload(refresh_active_mod_scripts) enabled_map = utils.OrderedTable() - foreach_module_script(process_script) + local force_refresh_fn = refresh_active_mod_scripts and function(script_path, script_name) + if script_path:find('scripts_modactive') then + internal_script = dfhack.internal.scripts[script_path..'/'..script_name] + if internal_script then + internal_script.env = nil + end + end + end or nil + foreach_module_script(process_script, force_refresh_fn) end local function ensure_loaded() @@ -57,4 +73,107 @@ function list() end end +--------------------- +-- mod script paths + +-- this perhaps could/should be queried from the Steam API +-- are there any installation configurations where this will be wrong, though? +local WORKSHOP_MODS_PATH = '../../workshop/content/975370/' +local MODS_PATH = 'mods/' +local INSTALLED_MODS_PATH = 'data/installed_mods/' + +-- last instance of the same version of the same mod wins, so read them in this +-- order (in increasing order of liklihood that players may have made custom +-- changes to the files) +local MOD_PATH_ROOTS = {WORKSHOP_MODS_PATH, MODS_PATH, INSTALLED_MODS_PATH} + +local function get_mod_id_and_version(path) + local idfile = path .. '/info.txt' + local ok, lines = pcall(io.lines, idfile) + if not ok then return end + local id, version + for line in lines do + if not id then + _,_,id = line:find('^%[ID:([^%]]+)%]') + end + if not version then + -- note this doesn't include the closing brace since some people put + -- non-number characters in here, and DF only reads the digits as the + -- numeric version + _,_,version = line:find('^%[NUMERIC_VERSION:(%d+)') + end + -- note that we do *not* want to break out of this loop early since + -- lines has to hit EOF to close the file + end + return id, version +end + +local function add_script_path(mod_script_paths, path) + if dfhack.filesystem.isdir(path) then + print('indexing mod scripts: ' .. path) + table.insert(mod_script_paths, path) + end +end + +local function add_script_paths(mod_script_paths, base_path, include_modactive) + if not base_path:endswith('/') then + base_path = base_path .. '/' + end + if include_modactive then + add_script_path(mod_script_paths, base_path..'scripts_modactive') + end + add_script_path(mod_script_paths, base_path..'scripts_modinstalled') +end + +function get_mod_script_paths() + -- ordered map of mod id -> {handled=bool, versions=map of version -> path} + local mods = utils.OrderedTable() + local mod_script_paths = {} + + -- 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 = tostring(path.value) + if not path:startswith(INSTALLED_MODS_PATH) then goto continue end + local id = get_mod_id_and_version(path) + if not id then goto continue end + mods[id] = {handled=true} + add_script_paths(mod_script_paths, path, true) + ::continue:: + end + end + + -- assemble version -> path maps for all (non-handled) mod source dirs + for _,mod_path_root in ipairs(MOD_PATH_ROOTS) do + local files = dfhack.filesystem.listdir_recursive(mod_path_root, 0) + if not files then goto skip_path_root end + for _,f in ipairs(files) do + if not f.isdir then goto continue end + local id, version = get_mod_id_and_version(f.path) + if not id or not version then goto continue end + local mod = ensure_key(mods, id) + if mod.handled then goto continue end + ensure_key(mod, 'versions')[version] = f.path + ::continue:: + end + ::skip_path_root:: + end + + -- add script paths from most recent version of all not-yet-handled mods + for _,v in pairs(mods) do + if v.handled then goto continue end + local max_version, path + for version,mod_path in pairs(v.versions) do + if not max_version or max_version < version then + path = mod_path + max_version = version + end + end + add_script_paths(mod_script_paths, path) + ::continue:: + end + + return mod_script_paths +end + return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 2e03a8616..3883439f1 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -2,9 +2,9 @@ local _ENV = mkmodule('utils') local df = df -function getval(obj) +function getval(obj, ...) if type(obj) == 'function' then - return obj() + return obj(...) else return obj end diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 2b6fc8ec8..b9e61c863 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -68,6 +68,7 @@ using namespace DFHack; #include "df/building_stockpilest.h" #include "df/building_trapst.h" #include "df/building_water_wheelst.h" +#include "df/building_weaponst.h" #include "df/building_wellst.h" #include "df/building_workshopst.h" #include "df/buildings_other_id.h" @@ -591,6 +592,12 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in obj->gate_flags.bits.closed = false; break; } + case building_type::Weapon: + { + if (VIRTUAL_CAST_VAR(obj, df::building_weaponst, bld)) + obj->gate_flags.bits.closed = false; + break; + } default: break; } @@ -792,10 +799,13 @@ bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, if (!allow_occupied && block->occupancy[btile.x][btile.y].bits.building) allowed = false; - else + else if (!allow_wall) { - auto tile = block->tiletype[btile.x][btile.y]; - if (!allow_wall && !HighPassable(tile)) + auto &tt = block->tiletype[btile.x][btile.y]; + auto &des = block->designation[btile.x][btile.y]; + if (!HighPassable(tt) || + des.bits.flow_size > 1 || + (des.bits.flow_size >= 1 && des.bits.liquid_type == df::tile_liquid::Magma)) allowed = false; } @@ -1105,31 +1115,17 @@ static void createDesign(df::building *bld, bool rough) static int getMaxStockpileId() { - auto &vec = world->buildings.other[buildings_other_id::STOCKPILE]; int max_id = 0; - - for (size_t i = 0; i < vec.size(); i++) - { - auto bld = strict_virtual_cast(vec[i]); - if (bld) - max_id = std::max(max_id, bld->stockpile_number); - } - + for (auto bld : world->buildings.other.STOCKPILE) + max_id = std::max(max_id, bld->stockpile_number); return max_id; } static int getMaxCivzoneId() { - auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; int max_id = 0; - - for (size_t i = 0; i < vec.size(); i++) - { - auto bld = strict_virtual_cast(vec[i]); - if (bld) - max_id = std::max(max_id, bld->zone_num); - } - + for (auto bld : world->buildings.other.ANY_ZONE) + max_id = std::max(max_id, bld->zone_num); return max_id; } @@ -1635,19 +1631,25 @@ StockpileIterator& StockpileIterator::operator++() { if (block) { // Check the next item in the current block. ++current; + } + else if (stockpile->x2 < 0 || stockpile->y2 < 0 || stockpile->z < 0 || stockpile->x1 > world->map.x_count - 1 || stockpile->y1 > world->map.y_count - 1 || stockpile->z > world->map.z_count - 1) { + // if the stockpile bounds exist outside of valid map plane then no items can be in the stockpile + block = NULL; + item = NULL; + return *this; } else { // Start with the top-left block covering the stockpile. - block = Maps::getTileBlock(stockpile->x1, stockpile->y1, stockpile->z); + block = Maps::getTileBlock(std::min(std::max(stockpile->x1, 0), world->map.x_count-1), std::min(std::max(stockpile->y1, 0), world->map.y_count-1), stockpile->z); current = 0; } while (current >= block->items.size()) { // Out of items in this block; find the next block to search. - if (block->map_pos.x + 16 <= stockpile->x2) { + if (block->map_pos.x + 16 <= std::min(stockpile->x2, world->map.x_count-1)) { block = Maps::getTileBlock(block->map_pos.x + 16, block->map_pos.y, stockpile->z); current = 0; - } else if (block->map_pos.y + 16 <= stockpile->y2) { - block = Maps::getTileBlock(stockpile->x1, block->map_pos.y + 16, stockpile->z); + } else if (block->map_pos.y + 16 <= std::min(stockpile->y2, world->map.y_count-1)) { + block = Maps::getTileBlock(std::max(stockpile->x1, 0), block->map_pos.y + 16, stockpile->z); current = 0; } else { // All items in all blocks have been checked. diff --git a/library/modules/DFSDL.cpp b/library/modules/DFSDL.cpp index 6a3e6af2f..b95b6302a 100644 --- a/library/modules/DFSDL.cpp +++ b/library/modules/DFSDL.cpp @@ -14,7 +14,7 @@ using namespace DFHack; static DFLibrary *g_sdl_handle = nullptr; static DFLibrary *g_sdl_image_handle = nullptr; static const std::vector SDL_LIBS { - "SDLreal.dll", // TODO: change to SDL.dll once we move to dfhooks + "SDL.dll", "SDL.framework/Versions/A/SDL", "SDL.framework/SDL", "libSDL-1.2.so.0" diff --git a/library/modules/DFSteam.cpp b/library/modules/DFSteam.cpp new file mode 100644 index 000000000..b27cc1744 --- /dev/null +++ b/library/modules/DFSteam.cpp @@ -0,0 +1,81 @@ +#include "Internal.h" + +#include "modules/DFSteam.h" + +#include "Debug.h" +#include "PluginManager.h" + +namespace DFHack +{ +DBG_DECLARE(core, dfsteam, DebugCategory::LINFO); +} + +using namespace DFHack; + +static DFLibrary* g_steam_handle = nullptr; +static const std::vector STEAM_LIBS { + "steam_api64.dll", + "steam_api", // TODO: validate this on OSX + "libsteam_api.so" // TODO: validate this on Linux +}; + +bool (*g_SteamAPI_Init)() = nullptr; +void (*g_SteamAPI_Shutdown)() = nullptr; +void* (*g_SteamInternal_FindOrCreateUserInterface)(int, const char*) = nullptr; +bool (*g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck)(void*) = nullptr; + +bool DFSteam::init(color_ostream& out) { + for (auto& lib_str : STEAM_LIBS) { + if ((g_steam_handle = OpenPlugin(lib_str.c_str()))) + break; + } + if (!g_steam_handle) { + DEBUG(dfsteam, out).print("steam library not found; stubbing calls\n"); + return false; + } + +#define bind(handle, name) \ + g_##name = (decltype(g_##name))LookupPlugin(handle, #name); \ + if (!g_##name) { \ + WARN(dfsteam, out).print("steam library function not found: " #name "\n"); \ + } + + bind(g_steam_handle, SteamAPI_Init); + bind(g_steam_handle, SteamAPI_Shutdown); + + // TODO: can we remove this initialization of the Steam API once we move to dfhooks? + if (!g_SteamAPI_Init || !g_SteamAPI_Shutdown || !g_SteamAPI_Init()) { + DEBUG(dfsteam, out).print("steam detected but cannot be initialized\n"); + return false; + } + + bind(g_steam_handle, SteamInternal_FindOrCreateUserInterface); + bind(g_steam_handle, SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck); +#undef bind + + DEBUG(dfsteam, out).print("steam library linked\n"); + return true; +} + +void DFSteam::cleanup() { + if (!g_steam_handle) + return; + + if (g_SteamAPI_Shutdown) + g_SteamAPI_Shutdown(); + + ClosePlugin(g_steam_handle); + g_steam_handle = nullptr; +} + +bool DFSteam::DFIsSteamRunningOnSteamDeck() { + if (!g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck) + return false; + + if (!g_SteamInternal_FindOrCreateUserInterface) + return false; + + void* SteamUtils = g_SteamInternal_FindOrCreateUserInterface(0, "SteamUtils010"); + + return g_SteamAPI_ISteamUtils_IsSteamRunningOnSteamDeck(SteamUtils); +} diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index a182ac062..2c275ceba 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -255,13 +255,14 @@ static int listdir_recursive_impl (std::string prefix, std::string path, std::string path_file = path + *file; if (Filesystem::isdir(prefixed_file)) { + files.insert(std::pair(include_prefix ? prefixed_file : path_file, true)); + if (depth == 0) { out_of_depth = true; continue; } - files.insert(std::pair(include_prefix ? prefixed_file : path_file, true)); err = listdir_recursive_impl(prefix, path_file + "/", files, depth - 1, include_prefix); if (err) return err; @@ -277,5 +278,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, int Filesystem::listdir_recursive (std::string dir, std::map &files, int depth /* = 10 */, bool include_prefix /* = true */) { + if (dir.size() && dir[dir.size()-1] == '/') + dir.resize(dir.size()-1); return listdir_recursive_impl(dir, "", files, depth, include_prefix); } diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 6ea0a5ff0..d03bcce09 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -84,6 +84,8 @@ using namespace DFHack; #include "df/unit.h" #include "df/unit_inventory_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_new_regionst.h" +#include "df/viewscreen_titlest.h" #include "df/world.h" const size_t MAX_REPORTS_SIZE = 3000; // DF clears old reports to maintain this vector size @@ -144,6 +146,30 @@ static std::map getFocusStringsHandle ); \ static void getFocusStrings_##screen_type(std::string &baseFocus, std::vector &focusStrings, VIEWSCREEN(screen_type) *screen) +DEFINE_GET_FOCUS_STRING_HANDLER(title) +{ + if (screen->managing_mods) + focusStrings.push_back(baseFocus + "/Mods"); + else if (game->main_interface.settings.open) + focusStrings.push_back(baseFocus + "/Settings"); + + if (focusStrings.empty()) + focusStrings.push_back(baseFocus + "/Default"); +} + +DEFINE_GET_FOCUS_STRING_HANDLER(new_region) +{ + if (screen->doing_mods) + focusStrings.push_back(baseFocus + "/Mods"); + else if (screen->doing_simple_params) + focusStrings.push_back(baseFocus + "/Basic"); + else if (screen->doing_params) + focusStrings.push_back(baseFocus + "/Advanced"); + + if (focusStrings.empty()) + focusStrings.push_back(baseFocus); +} + DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) { std::string newFocusString; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index bfdd525be..03d6cda4a 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -720,7 +720,14 @@ df::coord Items::getPosition(df::item *item) return item->pos; } -static char quality_table[] = { 0, '-', '+', '*', '=', '@' }; +static const char quality_table[] = { + '\0', // (base) + '-', // well-crafted + '+', // finely-crafted + '*', // superior quality + '\xF0', // (≡) exceptional + '\x0F' // (☼) masterful +}; static void addQuality(std::string &tmp, int quality) { @@ -825,7 +832,7 @@ std::string Items::getDescription(df::item *item, int type, bool decorate) addQuality(tmp, item->getQuality()); if (item->isImproved()) { - tmp = "<" + tmp + ">"; + tmp = '\xAE' + tmp + '\xAF'; // («) + tmp + (») addQuality(tmp, item->getImprovementQuality()); } } diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index b88a078b1..c329220c9 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -60,6 +60,7 @@ using namespace std; #include "df/flow_info.h" #include "df/job.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/region_map_entry.h" @@ -805,14 +806,16 @@ void MapExtras::BlockInfo::prepare(Block *mblock) // If the block is at or above the plant's base level, we use the body array // otherwise we use the roots. // TODO: verify that the tree bounds intersect the block. - df::plant_tree_tile tile; + bool has_tree_tile = false; int z_diff = block->map_pos.z - pp->pos.z; - if (z_diff >= 0) - tile = info->body[z_diff][xx + (yy*info->dim_x)]; - else - tile = info->roots[-1 - z_diff][xx + (yy*info->dim_x)]; - if (tile.whole && !(tile.bits.blocked)) - { + if (z_diff >= 0) { + df::plant_tree_tile tile = info->body[z_diff][xx + (yy * info->dim_x)]; + has_tree_tile = tile.whole && !(tile.bits.blocked); + } else { + df::plant_root_tile tile = info->roots[-1 - z_diff][xx + (yy * info->dim_x)]; + has_tree_tile = tile.whole && !(tile.bits.blocked); + } + if (has_tree_tile) { df::coord pos = pp->pos; pos.x = pos.x - (info->dim_x / 2) + xx; pos.y = pos.y - (info->dim_y / 2) + yy; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index ddcd6078f..2e831075d 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -57,6 +57,7 @@ using namespace std; #include "df/flow_info.h" #include "df/map_block_column.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/region_map_entry.h" diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index d73b59922..7a1ef249f 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -439,6 +439,7 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) const return true; if (cat.bits.milk && linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0) return true; + TEST(gem, IS_GEM); return false; } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index dcb18dd91..6ccf246aa 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -209,7 +209,7 @@ static bool doSetTile_default(const Pen &pen, int x, int y, bool map) } } else if (pen.ch) { screen[0] = uint8_t(pen.ch); - *texpos_lower = 909; // basic black background + *texpos_lower = df::global::init->texpos_border_interior; // basic black background } auto rgb_fg = &gps->uccolor[fg][0]; diff --git a/library/modules/Textures.cpp b/library/modules/Textures.cpp index 25d44695a..a30f879a0 100644 --- a/library/modules/Textures.cpp +++ b/library/modules/Textures.cpp @@ -26,6 +26,7 @@ static long g_on_off_texpos_start = -1; static long g_control_panel_texpos_start = -1; static long g_thin_borders_texpos_start = -1; static long g_medium_borders_texpos_start = -1; +static long g_bold_borders_texpos_start = -1; static long g_panel_borders_texpos_start = -1; static long g_window_borders_texpos_start = -1; @@ -134,6 +135,8 @@ void Textures::init(color_ostream &out) { &g_thin_borders_texpos_start); g_num_dfhack_textures += load_textures(out, "hack/data/art/border-medium.png", &g_medium_borders_texpos_start); + g_num_dfhack_textures += load_textures(out, "hack/data/art/border-bold.png", + &g_bold_borders_texpos_start); g_num_dfhack_textures += load_textures(out, "hack/data/art/border-panel.png", &g_panel_borders_texpos_start); g_num_dfhack_textures += load_textures(out, "hack/data/art/border-window.png", @@ -202,6 +205,10 @@ long Textures::getMediumBordersTexposStart() { return g_medium_borders_texpos_start; } +long Textures::getBoldBordersTexposStart() { + return g_bold_borders_texpos_start; +} + long Textures::getPanelBordersTexposStart() { return g_panel_borders_texpos_start; } diff --git a/library/xml b/library/xml index 8ae81f8d8..9891be326 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 8ae81f8d8f1f96d82b9074b205073bb8e8d29f96 +Subproject commit 9891be32663435f2fe875e27c70010d5618de735 diff --git a/package/windows/CMakeLists.txt b/package/windows/CMakeLists.txt new file mode 100644 index 000000000..ad5469c6b --- /dev/null +++ b/package/windows/CMakeLists.txt @@ -0,0 +1,32 @@ +project(package_windows) + +option(BUILD_DFLAUNCH "Whether to build the Steam launcher exectuable (requires Steam SDK)." OFF) + +if(WIN32 AND BUILD_DFLAUNCH) + # builder must manually download Steam SDK + set (STEAMAPI_DIR ${dfhack_SOURCE_DIR}/depends/steam) + set (STEAMAPI_VER 156) + set (STEAMAPI_ZIP_EXPECTED_HASH af5a579990dbe5ae4c1b0689260d001b) + set (STEAMSDK_ZIP ${STEAMAPI_DIR}/steamworks_sdk_${STEAMAPI_VER}.zip) + + set (STEAM_SDK_HASH "NOT FOUND") + file(MD5 ${STEAMSDK_ZIP} STEAM_SDK_HASH) + if (NOT (${STEAM_SDK_HASH} STREQUAL ${STEAMAPI_ZIP_EXPECTED_HASH})) + message(FATAL_ERROR "You need the Steamworks SDK at ${STEAMSDK_ZIP} to build launchdf.exe. Please disable the BUILD_DFLAUNCH CMake option or download the Steam SDK from: https://partner.steamgames.com/downloads/steamworks_sdk_${STEAMAPI_VER}.zip") + endif() + if (${STEAMSDK_ZIP} IS_NEWER_THAN ${STEAMAPI_DIR}/sdk) + file(ARCHIVE_EXTRACT + INPUT ${STEAMSDK_ZIP} + DESTINATION ${STEAMAPI_DIR}) + endif() + + set(STEAMAPI_LIBRARY "${STEAMAPI_DIR}/sdk/redistributable_bin/win64/steam_api64.lib") + set(STEAMAPI_SOURCE_DIR "${STEAMAPI_DIR}/sdk/public/steam") + set(STEAMAPI_SHARED_LIBRARY "${STEAMAPI_DIR}/sdk/redistributable_bin/win64/steam_api64.dll") + + include_directories(${STEAMAPI_SOURCE_DIR}) + link_libraries(${STEAMAPI_LIBRARY}) + add_executable(launchdf WIN32 launchdf.cpp) + install(TARGETS launchdf DESTINATION ${DFHACK_DATA_DESTINATION}) + install(FILES ${STEAMAPI_SHARED_LIBRARY} DESTINATION ${DFHACK_DATA_DESTINATION}) +endif() diff --git a/package/windows/launchdf.cpp b/package/windows/launchdf.cpp new file mode 100644 index 000000000..a84465f53 --- /dev/null +++ b/package/windows/launchdf.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include "steam_api.h" + +#include + +const uint32 DFHACK_STEAM_APPID = 2346660; +const uint32 DF_STEAM_APPID = 975370; + +static BOOL is_running_on_wine() { + typedef const char* (CDECL wine_get_version)(void); + static wine_get_version* pwine_get_version; + HMODULE hntdll = GetModuleHandle("ntdll.dll"); + if(!hntdll) + return FALSE; + + pwine_get_version = (wine_get_version*) GetProcAddress(hntdll, "wine_get_version"); + return !!pwine_get_version; +} + +static LPCWSTR launch_via_steam_posix() { + const char* argv[] = { "/bin/sh", "-c", "\"steam -applaunch 975370\"", NULL }; + + // does not return on success + _execv(argv[0], argv); + + return L"Could not launch Dwarf Fortress"; +} + +static LPCWSTR launch_via_steam_windows() { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + WCHAR steamPath[1024] = L""; + DWORD datasize = 1024; + + LONG retCode = RegGetValueW(HKEY_CURRENT_USER, L"SOFTWARE\\Valve\\Steam", + L"SteamExe", RRF_RT_REG_SZ, NULL, &steamPath, &datasize); + + if (retCode != ERROR_SUCCESS) + return L"Could not find Steam client executable"; + + WCHAR commandLine[1024] = L"steam.exe -applaunch 975370"; + + BOOL res = CreateProcessW(steamPath, commandLine, + NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + + if (res) + { + WaitForSingleObject(pi.hProcess, INFINITE); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return NULL; + } + else + { + return L"Could not launch Dwarf Fortress"; + } +} + +static LPCWSTR launch_direct() { + STARTUPINFOW si; + PROCESS_INFORMATION pi; + + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); + + BOOL res = CreateProcessW(L"Dwarf Fortress.exe", + NULL, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi); + + if (res) + { + WaitForSingleObject(pi.hProcess, INFINITE); + + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return NULL; + } + + return L"Could not launch via non-steam fallback method"; +} + +DWORD findDwarfFortressProcess() +{ + PROCESSENTRY32W entry; + entry.dwSize = sizeof(PROCESSENTRY32W); + + const auto snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + + if (!Process32FirstW(snapshot, &entry)) + { + CloseHandle(snapshot); + return -1; + } + + do { + std::wstring executableName(entry.szExeFile); + if (executableName == L"Dwarf Fortress.exe") + { + CloseHandle(snapshot); + return entry.th32ProcessID; + } + } while (Process32NextW(snapshot, &entry)); + + CloseHandle(snapshot); + return -1; +} + +int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nShowCmd) { + + // initialize steam context + if (SteamAPI_RestartAppIfNecessary(DFHACK_STEAM_APPID)) + { + exit(0); + } + + if (!SteamAPI_Init()) + { + // could not initialize steam context, attempt fallback launch + LPCWSTR err = launch_direct(); + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + exit(0); + } + + bool wine = is_running_on_wine(); + + if (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); + } + + // steam detected and not running in wine + + bool df_installed = SteamApps()->BIsAppInstalled(DF_STEAM_APPID); + + if (!df_installed) + { + // Steam DF is not installed. Assume DF is installed in same directory as DFHack and do a fallback launch + LPCWSTR err = launch_direct(); + + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + exit(0); + } + + // obtain DF app path + + char buf[2048] = ""; + + int b1 = SteamApps()->GetAppInstallDir(DFHACK_STEAM_APPID, (char*)&buf, 2048); + std::string dfhack_install_folder = (b1 != -1) ? std::string(buf) : ""; + + int b2 = SteamApps()->GetAppInstallDir(DF_STEAM_APPID, (char*)&buf, 2048); + std::string df_install_folder = (b2 != -1) ? std::string(buf) : ""; + + + if (df_install_folder != dfhack_install_folder) + { + // DF and DFHack are not installed in the same library + MessageBoxW(NULL, L"DFHack and Dwarf Fortress must be installed in the same Steam library.\nAborting.", NULL, 0); + exit(1); + } + + DWORD df_pid = findDwarfFortressProcess(); + + if (df_pid == -1) + { + LPCWSTR err = launch_via_steam_windows(); + if (err != NULL) + { + MessageBoxW(NULL, err, NULL, 0); + exit(1); + } + int counter = 0; + + do { + if (counter++ > 60) + { + MessageBoxW(NULL, L"Dwarf Fortress took too long to launch, aborting", NULL, 0); + exit(1); + } + Sleep(1000); + df_pid = findDwarfFortressProcess(); + } while (df_pid == -1); + } + + HANDLE hDF = OpenProcess(PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, df_pid); + + // in the future open an IPC connection so that we can proxy SteamAPI calls for the DFSteam module + + // this will eventuallyh need to become a loop with a WaitForMultipleObjects call + WaitForSingleObject(hDF, INFINITE); + + CloseHandle(hDF); + + exit(0); +} diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index d87b41978..3ae78d320 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -74,15 +74,15 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE ) # see instructions for adding "external" plugins at the end of this file. #dfhack_plugin(3dveins 3dveins.cpp) -#dfhack_plugin(add-spatter add-spatter.cpp) +dfhack_plugin(add-spatter add-spatter.cpp) dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua) -dfhack_plugin(autoclothing autoclothing.cpp) +dfhack_plugin(autoclothing autoclothing.cpp LINK_LIBRARIES lua) +dfhack_plugin(design design.cpp LINK_LIBRARIES lua) dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autofarm autofarm.cpp) #dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) add_subdirectory(autolabor) -#dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp LINK_LIBRARIES lua) dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) #dfhack_plugin(autotrade autotrade.cpp) @@ -90,7 +90,7 @@ dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) #dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) #dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua) add_subdirectory(buildingplan) -#dfhack_plugin(changeitem changeitem.cpp) +dfhack_plugin(changeitem changeitem.cpp) dfhack_plugin(changelayer changelayer.cpp) dfhack_plugin(changevein changevein.cpp) add_subdirectory(channel-safely) @@ -98,10 +98,10 @@ dfhack_plugin(cleanconst cleanconst.cpp) dfhack_plugin(cleaners cleaners.cpp) dfhack_plugin(cleanowned cleanowned.cpp) dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua) -#dfhack_plugin(createitem createitem.cpp) +dfhack_plugin(createitem createitem.cpp) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) -#dfhack_plugin(deramp deramp.cpp) +dfhack_plugin(deramp deramp.cpp) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp) dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) @@ -113,21 +113,22 @@ dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) #dfhack_plugin(embark-tools embark-tools.cpp) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) dfhack_plugin(fastdwarf fastdwarf.cpp) +dfhack_plugin(faststart faststart.cpp) dfhack_plugin(filltraffic filltraffic.cpp) #dfhack_plugin(fix-unit-occupancy fix-unit-occupancy.cpp) #dfhack_plugin(fixveins fixveins.cpp) -#dfhack_plugin(flows flows.cpp) +dfhack_plugin(flows flows.cpp) #dfhack_plugin(follow follow.cpp) #dfhack_plugin(forceequip forceequip.cpp) #dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) -#dfhack_plugin(getplants getplants.cpp) +dfhack_plugin(getplants getplants.cpp) dfhack_plugin(hotkeys hotkeys.cpp LINK_LIBRARIES lua) #dfhack_plugin(infiniteSky infiniteSky.cpp) #dfhack_plugin(isoworldremote isoworldremote.cpp PROTOBUFS isoworldremote) #dfhack_plugin(jobutils jobutils.cpp) -#dfhack_plugin(lair lair.cpp) +dfhack_plugin(lair lair.cpp) dfhack_plugin(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) -#dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) +dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) #dfhack_plugin(manipulator manipulator.cpp) #dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua) dfhack_plugin(misery misery.cpp LINK_LIBRARIES lua) @@ -141,7 +142,7 @@ dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) #dfhack_plugin(petcapRemover petcapRemover.cpp) #dfhack_plugin(plants plants.cpp) dfhack_plugin(probe probe.cpp) -#dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) +dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) #dfhack_plugin(power-meter power-meter.cpp LINK_LIBRARIES lua) dfhack_plugin(regrass regrass.cpp) add_subdirectory(remotefortressreader) @@ -162,12 +163,11 @@ dfhack_plugin(strangemood strangemood.cpp) dfhack_plugin(tailor tailor.cpp LINK_LIBRARIES lua) dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua) #dfhack_plugin(title-folder title-folder.cpp) -#dfhack_plugin(title-version title-version.cpp) #dfhack_plugin(trackstop trackstop.cpp) #dfhack_plugin(tubefill tubefill.cpp) #add_subdirectory(tweak) #dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) -#dfhack_plugin(workNow workNow.cpp) +dfhack_plugin(work-now work-now.cpp) dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read_STATIC zip expat) #dfhack_plugin(zone zone.cpp) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index db7582c09..a0cea765f 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -6,11 +6,6 @@ if(UNIX) endif() endif() -include_directories("${dfhack_SOURCE_DIR}/library/include") -include_directories("${dfhack_SOURCE_DIR}/library/proto") -include_directories("${CMAKE_CURRENT_SOURCE_DIR}/proto") -include_directories("${dfhack_SOURCE_DIR}/library/depends/xgetopt") - macro(car var) set(${var} ${ARGV1}) endmacro() @@ -123,6 +118,11 @@ macro(dfhack_plugin) add_library(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES}) ide_folder(${PLUGIN_NAME} "Plugins") + target_include_directories(${PLUGIN_NAME} PRIVATE "${dfhack_SOURCE_DIR}/library/include") + target_include_directories(${PLUGIN_NAME} PRIVATE "${dfhack_SOURCE_DIR}/library/proto") + target_include_directories(${PLUGIN_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/proto") + target_include_directories(${PLUGIN_NAME} PRIVATE "${dfhack_SOURCE_DIR}/library/depends/xgetopt") + if(NUM_PROTO) add_dependencies(${PLUGIN_NAME} generate_proto_${PLUGIN_NAME}) target_link_libraries(${PLUGIN_NAME} dfhack protobuf-lite dfhack-version ${PLUGIN_LINK_LIBRARIES}) diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 451fffabf..3cfefce39 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -1,51 +1,26 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "df/item_liquid_miscst.h" +#include "PluginManager.h" +#include "VTableInterpose.h" + +#include "modules/Items.h" +#include "modules/Units.h" + #include "df/item_constructed.h" -#include "df/builtin_mats.h" -#include "df/world.h" #include "df/job.h" #include "df/job_item.h" #include "df/job_item_ref.h" -#include "df/plotinfost.h" -#include "df/report.h" #include "df/reaction.h" #include "df/reaction_reagent_itemst.h" #include "df/reaction_product_item_improvementst.h" -#include "df/reaction_product_improvement_flags.h" -#include "df/matter_state.h" #include "df/spatter.h" -#include "MiscUtils.h" - using std::vector; using std::string; -using std::stack; + using namespace DFHack; using namespace df::enums; DFHACK_PLUGIN("add-spatter"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(plotinfo); typedef df::reaction_product_item_improvementst improvement_product; @@ -397,18 +372,20 @@ static void enable_hooks(bool enable) INTERPOSE_HOOK(product_hook, produce).apply(enable); } +DFhackCExport command_result plugin_load_data (color_ostream &out) { + if (find_reactions(out)) { + out.print("Detected spatter add reactions - enabling plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + + return CR_OK; +} + DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { - case SC_WORLD_LOADED: - if (find_reactions(out)) - { - out.print("Detected spatter add reactions - enabling plugin.\n"); - enable_hooks(true); - } - else - enable_hooks(false); - break; case SC_WORLD_UNLOADED: enable_hooks(false); reactions.clear(); @@ -423,9 +400,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - if (Core::getInstance().isWorldLoaded()) - plugin_onstatechange(out, SC_WORLD_LOADED); - return CR_OK; } diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index fc8b1e43b..d404960b4 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -61,7 +61,25 @@ namespace DFHack { } static const string CONFIG_KEY = string(plugin_name) + "/config"; +enum ConfigValues { + CONFIG_IS_ENABLED = 0, +}; +//static int get_config_val(PersistentDataItem& c, int index) { +// if (!c.isValid()) +// return -1; +// return c.ival(index); +//} +//static bool get_config_bool(PersistentDataItem& c, int index) { +// return get_config_val(c, index) == 1; +//} +static void set_config_val(PersistentDataItem& c, int index, int value) { + if (c.isValid()) + c.ival(index) = value; +} +static void set_config_bool(PersistentDataItem& c, int index, bool value) { + set_config_val(c, index, value ? 1 : 0); +} // Here go all the command declarations... // mostly to allow having the mandatory stuff on top of the file and commands on the bottom @@ -250,6 +268,29 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan } +DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot enable %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + if (enable != autoclothing_enabled) { + auto enabled = World::GetPersistentData("autoclothing/enabled"); + autoclothing_enabled = enable; + DEBUG(report, out).print("%s from the API; persisting\n", + autoclothing_enabled ? "enabled" : "disabled"); + set_config_bool(enabled, CONFIG_IS_ENABLED, autoclothing_enabled); + if (enable) + do_autoclothing(); + } + else { + DEBUG(report, out).print("%s from the API, but already %s; no action\n", + autoclothing_enabled ? "enabled" : "disabled", + autoclothing_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + // Whatever you put here will be done in each game step. Don't abuse it. // It's optional, so you can just comment it out like this if you don't need it. @@ -389,6 +430,7 @@ command_result autoclothing(color_ostream &out, vector & parameters) if (parameters.size() == 0) { CoreSuspender suspend; + out << "Automatic clothing management is currently " << (autoclothing_enabled ? "enabled" : "disabled") << "." << endl; out << "Currently set " << clothingOrders.size() << " automatic clothing orders" << endl; for (size_t i = 0; i < clothingOrders.size(); i++) { @@ -523,7 +565,7 @@ static void find_needed_clothing_items() if (!item) { - WARN(cycle).print("Invalid inventory item ID: %d\n", ownedItem); + DEBUG(cycle).print("autoclothing: Invalid inventory item ID: %d\n", ownedItem); continue; } @@ -728,34 +770,28 @@ static void list_unit_counts(color_ostream& out, map& unitList) static bool isAvailableItem(df::item* item) { - if (item->flags.bits.in_job) - return false; - if (item->flags.bits.hostile) - return false; - if (item->flags.bits.in_building) - return false; - if (item->flags.bits.in_building) - return false; - if (item->flags.bits.encased) - return false; - if (item->flags.bits.foreign) - return false; - if (item->flags.bits.trader) - return false; - if (item->flags.bits.owned) - return false; - if (item->flags.bits.artifact) - return false; - if (item->flags.bits.forbid) - return false; - if (item->flags.bits.dump) - return false; - if (item->flags.bits.on_fire) - return false; - if (item->flags.bits.melt) - return false; - if (item->flags.bits.hidden) + static struct BadFlags { + uint32_t whole; + + BadFlags() { + df::item_flags flags; +#define F(x) flags.bits.x = true; + F(in_job); F(hostile); F(in_building); F(encased); + F(foreign); F(trader); F(owned); F(forbid); + F(dump); F(on_fire); F(melt); F(hidden); + + F(garbage_collect); F(rotten); F(construction); + F(in_chest); F(removed); F(spider_web); + + // F(artifact); -- TODO: should this be included? +#undef F + whole = flags.whole; + } + } badFlags; + + if ((item->flags.whole & badFlags.whole) != 0) return false; + if (item->getWear() > 1) return false; if (!item->isClothing()) @@ -782,7 +818,7 @@ static void generate_report(color_ostream& out) auto item = Items::findItemByID(itemId); if (!item) { - WARN(cycle,out).print("Invalid inventory item ID: %d\n", itemId); + DEBUG(cycle, out).print("autoclothing: Invalid inventory item ID: %d\n", itemId); continue; } if (item->getWear() >= 1) @@ -915,3 +951,23 @@ static void generate_report(color_ostream& out) } } + +///////////////////////////////////////////////////// +// Lua API +// TODO: implement Lua hooks to manipulate the persistent order configuration +// + +static void autoclothing_doCycle(color_ostream& out) { + DEBUG(report, out).print("entering autoclothing_doCycle\n"); + do_autoclothing(); +} + + +DFHACK_PLUGIN_LUA_FUNCTIONS{ + DFHACK_LUA_FUNCTION(autoclothing_doCycle), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS{ + DFHACK_LUA_END +}; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 2514dd91a..839b3b727 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -280,8 +280,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector #include #include +#include #include -#include #include @@ -51,7 +51,7 @@ using namespace df::enums; DFHACK_PLUGIN("autolabor"); REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(game_extra); +REQUIRE_GLOBAL(game); #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) @@ -414,7 +414,7 @@ static void enable_plugin(color_ostream &out) cleanup_state(); init_state(); - df::global::game_extra->external_flag |= 1; // shut down DF's work detail system + game->external_flag |= 1; // shut down DF's work detail system } DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) @@ -1084,7 +1084,7 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) enable_autolabor = false; setOptionEnabled(CF_ENABLED, false); - df::global::game_extra->external_flag &= ~1; // reenable DF's work detail system + game->external_flag &= ~1; // reenable DF's work detail system out << "Autolabor is disabled." << std::endl; } diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp deleted file mode 100644 index 9783d28dc..000000000 --- a/plugins/automaterial.cpp +++ /dev/null @@ -1,1340 +0,0 @@ -// Auto Material Select - -#include -#include -#include - -#include "Core.h" -#include "LuaTools.h" -#include -#include -#include -#include - - -// DF data structure definition headers -#include "DataDefs.h" -#include "Debug.h" -#include "MiscUtils.h" -#include "TileTypes.h" -#include "df/build_req_choice_genst.h" -#include "df/build_req_choice_specst.h" -#include "df/construction_type.h" -#include "df/item.h" -#include "df/plotinfost.h" -#include "df/buildreq.h" -#include "df/viewscreen_dwarfmodest.h" -#include "df/items_other_id.h" -#include "df/job.h" -#include "df/world.h" -#include "df/building_constructionst.h" -#include "df/job_item.h" - -#include "modules/Gui.h" -#include "modules/Screen.h" -#include "modules/Items.h" -#include "modules/Constructions.h" -#include "modules/Buildings.h" -#include "modules/Maps.h" -#include "modules/MapCache.h" - -#include "uicommon.h" - -using namespace std; -using std::map; -using std::string; -using std::vector; - -using namespace DFHack; -using namespace df::enums; - -DFHACK_PLUGIN("automaterial"); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(plotinfo); -REQUIRE_GLOBAL(ui_build_selector); - -namespace DFHack { - DBG_DECLARE(automaterial,log,DebugCategory::LINFO); -} - -struct MaterialDescriptor -{ - df::item_type item_type; - int16_t item_subtype; - int16_t type; - int32_t index; - bool valid; - - bool matches(const MaterialDescriptor &a) const - { - return a.valid && valid && - a.type == type && - a.index == index && - a.item_type == item_type && - a.item_subtype == item_subtype; - } -}; - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -void AMOutputToggleString(int &x, int &y, const char *text, const char *hotkey, bool state, bool newline = true, int left_margin = 0, int8_t color = COLOR_WHITE) -{ - OutputHotkeyString(x, y, text, hotkey); - OutputString(COLOR_WHITE, x, y, ": "); - if (state) - OutputString(COLOR_GREEN, x, y, "Enabled", newline, left_margin); - else - OutputString(COLOR_GREY, x, y, "Disabled", newline, left_margin); -} - -//START UI Functions - -static enum t_box_select_mode {SELECT_FIRST, SELECT_SECOND, SELECT_MATERIALS, AUTOSELECT_MATERIALS} box_select_mode = SELECT_FIRST; -static coord32_t box_first, box_second; -static bool box_select_enabled = false; -static bool show_box_selection = true; -static bool hollow_selection = false; -static deque box_select_materials; - -#define SELECTION_IGNORE_TICKS 1 -static int ignore_selection = SELECTION_IGNORE_TICKS; - -static map last_used_material; -static map last_moved_material; -static map< int16_t, vector > preferred_materials; -static map< int16_t, df::interface_key > hotkeys; -static bool last_used_moved = false; -static bool auto_choose_materials = true; -static bool revert_to_last_used_type = false; -static bool allow_future_placement = false; - -static inline bool in_material_choice_stage() -{ - return Gui::build_selector_hotkey(Core::getTopViewscreen()) && - ui_build_selector->building_type == df::building_type::Construction && - plotinfo->main.mode == ui_sidebar_mode::Build && - ui_build_selector->stage == 2; -} - -static inline bool in_placement_stage() -{ - return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && - plotinfo->main.mode == ui_sidebar_mode::Build && - ui_build_selector && - ui_build_selector->building_type == df::building_type::Construction && - ui_build_selector->stage == 1; -} - -static inline bool in_type_choice_stage() -{ - return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && - plotinfo->main.mode == ui_sidebar_mode::Build && - ui_build_selector && - ui_build_selector->building_type < 0; -} - -static inline vector &get_curr_constr_prefs() -{ - if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end()) - preferred_materials[ui_build_selector->building_subtype] = vector(); - - return preferred_materials[ui_build_selector->building_subtype]; -} - -static inline MaterialDescriptor &get_last_used_material() -{ - if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end()) - last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor(); - - return last_used_material[ui_build_selector->building_subtype]; -} - -static void set_last_used_material(const MaterialDescriptor &matetial) -{ - last_used_material[ui_build_selector->building_subtype] = matetial; -} - -static MaterialDescriptor &get_last_moved_material() -{ - if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end()) - last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor(); - - return last_moved_material[ui_build_selector->building_subtype]; -} - -static void set_last_moved_material(const MaterialDescriptor &matetial) -{ - last_moved_material[ui_build_selector->building_subtype] = matetial; -} - -static MaterialDescriptor get_material_in_list(size_t i) -{ - MaterialDescriptor result; - result.valid = false; - - if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i])) - { - result.item_type = gen->item_type; - result.item_subtype = gen->item_subtype; - result.type = gen->mat_type; - result.index = gen->mat_index; - result.valid = true; - } - else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) - { - result.item_type = spec->candidate->getType(); - result.item_subtype = spec->candidate->getSubtype(); - result.type = spec->candidate->getActualMaterial(); - result.index = spec->candidate->getActualMaterialIndex(); - result.valid = true; - } - - return result; -} - -static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material) -{ - for (i = 0; i < get_curr_constr_prefs().size(); i++) - { - if (get_curr_constr_prefs()[i].matches(material)) - return true; - } - - return false; -} - -static bool is_material_in_list(size_t &i, MaterialDescriptor &material) -{ - const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big - for (i = 0; i < size; i++) - { - if (get_material_in_list(i).matches(material)) - return true; - } - - return false; -} - -static bool move_material_to_top(MaterialDescriptor &material) -{ - size_t i; - if (is_material_in_list(i, material)) - { - auto sel_item = ui_build_selector->choices[i]; - ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i); - ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item); - - ui_build_selector->sel_index = 0; - set_last_moved_material(material); - return true; - } - - set_last_moved_material(MaterialDescriptor()); - return false; -} - -static bool check_autoselect(MaterialDescriptor &material, bool toggle) -{ - size_t idx; - if (is_material_in_autoselect(idx, material)) - { - if (toggle) - vector_erase_at(get_curr_constr_prefs(), idx); - - return true; - } - else - { - if (toggle) - get_curr_constr_prefs().push_back(material); - - return false; - } -} - -static void cancel_box_selection() -{ - if (box_select_mode == SELECT_FIRST) - return; - - box_select_mode = SELECT_FIRST; - box_select_materials.clear(); - if (!show_box_selection) - Gui::setDesignationCoords(-1, -1, -1); -} -//END UI Functions - - -//START Building and Verification -struct building_site -{ - df::coord pos; - bool in_open_air; - - building_site(df::coord pos, bool in_open_air) - { - this->pos = pos; - this->in_open_air = in_open_air; - } - - building_site() {} -}; - -static deque valid_building_sites; -static deque open_air_sites; -static building_site anchor; - -static bool is_orthogonal_to_pending_construction(building_site &site) -{ - for (deque::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++) - { - if ((it->pos.x == site.pos.x && abs(it->pos.y - site.pos.y) == 1) || (it->pos.y == site.pos.y && abs(it->pos.x - site.pos.x) == 1)) - { - site.in_open_air = true; - return true; - } - } - - return false; -} - -static df::building_constructionst *get_construction_on_tile(const df::coord &pos) -{ - auto current = Buildings::findAtTile(pos); - if (current) - return strict_virtual_cast(current); - - return NULL; -} - -static df::tiletype *read_tile_shapes(const df::coord &pos, df::tiletype_shape &shape, df::tiletype_shape_basic &shape_basic) -{ - if (!Maps::isValidTilePos(pos)) - return NULL; - - auto ttype = Maps::getTileType(pos); - - if (!ttype) - return NULL; - - shape = tileShape(*ttype); - shape_basic = tileShapeBasic(shape); - - return ttype; -} - -static bool is_valid_building_site(building_site &site, bool orthogonal_check, bool check_placed_constructions, bool in_future_placement_mode) -{ - df::tiletype_shape shape; - df::tiletype_shape_basic shape_basic; - - auto ttype = read_tile_shapes(site.pos, shape, shape_basic); - if (!ttype) - return false; - - if (shape_basic == tiletype_shape_basic::Open) - { - if (orthogonal_check) - { - // Check if this is a valid tile to have a construction placed orthogonally to it - if (!in_future_placement_mode) - return false; - - df::building_constructionst *cons = get_construction_on_tile(site.pos); - if (cons && cons->type == construction_type::Floor) - { - site.in_open_air = true; - return true; - } - - return false; - } - - // Stairs can be placed in open space, if they can connect to other stairs - df::tiletype_shape shape_s; - df::tiletype_shape_basic shape_basic_s; - - if (ui_build_selector->building_subtype == construction_type::DownStair || - ui_build_selector->building_subtype == construction_type::UpDownStair) - { - df::coord below(site.pos.x, site.pos.y, site.pos.z - 1); - auto ttype_s = read_tile_shapes(below, shape_s, shape_basic_s); - if (ttype_s) - { - if (shape_s == tiletype_shape::STAIR_UP || shape_s == tiletype_shape::STAIR_UPDOWN) - return true; - } - } - - if (ui_build_selector->building_subtype == construction_type::UpStair || - ui_build_selector->building_subtype == construction_type::UpDownStair) - { - df::coord above(site.pos.x, site.pos.y, site.pos.z + 1); - auto ttype_s = read_tile_shapes(above, shape_s, shape_basic_s); - if (ttype_s) - { - if (shape_s == tiletype_shape::STAIR_DOWN || shape_s == tiletype_shape::STAIR_UPDOWN) - return true; - } - } - - // Check if there is a valid tile orthogonally adjacent - bool valid_orthogonal_tile_found = false; - df::coord orthagonal_pos; - orthagonal_pos.z = site.pos.z; - for (orthagonal_pos.x = site.pos.x-1; orthagonal_pos.x <= site.pos.x+1 && !valid_orthogonal_tile_found; orthagonal_pos.x++) - { - for (orthagonal_pos.y = site.pos.y-1; orthagonal_pos.y <= site.pos.y+1; orthagonal_pos.y++) - { - if ((site.pos.x == orthagonal_pos.x) == (site.pos.y == orthagonal_pos.y)) - continue; - - building_site orthogonal_site(orthagonal_pos, false); - if (is_valid_building_site(orthogonal_site, true, check_placed_constructions, in_future_placement_mode)) - { - valid_orthogonal_tile_found = true; - if (orthogonal_site.in_open_air) - site.in_open_air = true; - break; - } - - } - } - - if (!(valid_orthogonal_tile_found || (check_placed_constructions && is_orthogonal_to_pending_construction(site)))) - { - site.in_open_air = true; - return false; - } - } - else if (orthogonal_check) - { - if (shape != tiletype_shape::RAMP && - shape_basic != tiletype_shape_basic::Floor && - shape_basic != tiletype_shape_basic::Stair) - return false; - } - else - { - auto material = tileMaterial(*ttype); - if (shape == tiletype_shape::RAMP) - { - if (material == tiletype_material::CONSTRUCTION) - return false; - } - else - { - if (shape != tiletype_shape::STAIR_DOWN && shape_basic != tiletype_shape_basic::Floor) - return false; - - // Can build on top of a wall, but not on other construction - auto construction = Constructions::findAtTile(site.pos); - if (construction) - { - if (construction->flags.bits.top_of_wall==0) - return false; - } - - if (material == tiletype_material::FIRE || - material == tiletype_material::POOL || - material == tiletype_material::BROOK || - material == tiletype_material::RIVER || - material == tiletype_material::MAGMA || - material == tiletype_material::DRIFTWOOD || - material == tiletype_material::CAMPFIRE - ) - - return false; - } - } - - if (orthogonal_check) - return true; - - auto designation = Maps::getTileDesignation(site.pos); - if (designation->bits.flow_size > 2) - return false; - - auto current = Buildings::findAtTile(site.pos); - if (current) - return false; - - df::coord2d size(1,1); - return Buildings::checkFreeTiles(site.pos, size, NULL, false, false); -} - - -static bool find_anchor_in_spiral(const df::coord &start) -{ - bool found = false; - - for (anchor.pos.z = start.z; anchor.pos.z > start.z - 4; anchor.pos.z--) - { - int x, y, dx, dy; - x = y = dx = 0; - dy = -1; - const int side = 11; - const int maxI = side*side; - for (int i = 0; i < maxI; i++) - { - if (-side/2 < x && x <= side/2 && -side/2 < y && y <= side/2) - { - anchor.pos.x = start.x + x; - anchor.pos.y = start.y + y; - if (is_valid_building_site(anchor, false, false, false)) - { - found = true; - break; - } - } - - if ((x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1-y))) - { - int tmp = dx; - dx = -dy; - dy = tmp; - } - - x += dx; - y += dy; - } - - if (found) - break; - } - - return found; -} - -static bool find_valid_building_sites(bool in_future_placement_mode, bool use_buildingplan) -{ - valid_building_sites.clear(); - open_air_sites.clear(); - - int xD = (box_second.x > box_first.x) ? 1 : -1; - int yD = (box_second.y > box_first.y) ? 1 : -1; - for (int32_t xB = box_first.x; (xD > 0) ? (xB <= box_second.x) : (xB >= box_second.x); xB += xD) - { - for (int32_t yB = box_first.y; (yD > 0) ? (yB <= box_second.y) : (yB >= box_second.y); yB += yD) - { - if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) - continue; - - building_site site(df::coord(xB, yB, box_second.z), false); - // if we're using buildingplan, it will take care of filtering out bad tiles - if (use_buildingplan || is_valid_building_site(site, false, true, in_future_placement_mode)) - valid_building_sites.push_back(site); - else if (site.in_open_air) - { - if (in_future_placement_mode) - valid_building_sites.push_back(site); - else - open_air_sites.push_back(site); - } - } - } - - if (!use_buildingplan) - { - size_t last_open_air_count = 0; - while (valid_building_sites.size() > 0 && open_air_sites.size() != last_open_air_count) - { - last_open_air_count = open_air_sites.size(); - deque current_open_air_list = open_air_sites; - open_air_sites.clear(); - for (deque::iterator it = current_open_air_list.begin(); it != current_open_air_list.end(); it++) - { - if (is_orthogonal_to_pending_construction(*it)) - valid_building_sites.push_back(*it); - else - open_air_sites.push_back(*it); - } - - } - } - - return valid_building_sites.size() > 0; -} - -static bool is_buildingplan_enabled() -{ - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - Lua::StackUnwinder top(L); - - if (!(lua_checkstack(L, 1) && - Lua::PushModulePublic(out, L, "plugins.buildingplan", "isEnabled") && - Lua::SafeCall(out, L, 0, 1))) - { - return false; - } - - return lua_toboolean(L, -1); -} - -static bool is_buildingplan_planmode_enabled( - df::building_type type, int16_t subtype, int32_t custom) -{ - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 4) || - !Lua::PushModulePublic( - out, L, "plugins.buildingplan", "isPlanModeEnabled")) - return false; - - Lua::Push(L, type); - Lua::Push(L, subtype); - Lua::Push(L, custom); - - if (!Lua::SafeCall(out, L, 3, 1)) - return false; - - return lua_toboolean(L, -1); -} - -static bool is_buildingplan_managed() -{ - return is_buildingplan_enabled() && - is_buildingplan_planmode_enabled(ui_build_selector->building_type, - ui_build_selector->building_subtype, - ui_build_selector->custom_type); -} - -static bool build_with_buildingplan_box_select(const df::coord &pos) -{ - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - - CoreSuspendClaimer suspend; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 5) || - !Lua::PushModulePublic( - out, L, "plugins.automaterial", - "build_with_buildingplan_box_select")) - { - return false; - } - - Lua::Push(L, ui_build_selector->building_subtype); - Lua::Push(L, pos.x); - Lua::Push(L, pos.y); - Lua::Push(L, pos.z); - - if (!Lua::SafeCall(out, L, 4, 1)) - return false; - - return lua_toboolean(L, -1); -} - -static bool build_with_buildingplan_ui() -{ - auto L = Lua::Core::State; - color_ostream_proxy out(Core::getInstance().getConsole()); - - CoreSuspendClaimer suspend; - Lua::StackUnwinder top(L); - - return lua_checkstack(L, 1) && - Lua::PushModulePublic(out, L, "plugins.automaterial", - "build_with_buildingplan_ui") && - Lua::SafeCall(out, L, 0, 1); -} - -static bool designate_new_construction(df::coord &pos, df::construction_type &type, df::item *item) -{ - auto newinst = Buildings::allocInstance(pos, building_type::Construction, type); - if (!newinst) - return false; - - vector items; - items.push_back(item); - Maps::ensureTileBlock(pos); - - if (!Buildings::constructWithItems(newinst, items)) - { - delete newinst; - return false; - } - - return true; -} -//END Building and Verification - - -//START Viewscreen Hook -struct jobutils_hook : public df::viewscreen_dwarfmodest -{ - //START UI Methods - typedef df::viewscreen_dwarfmodest interpose_base; - - void send_key(const df::interface_key &key) - { - set< df::interface_key > keys; - keys.insert(key); - this->feed(&keys); - } - - bool select_material_at_index(size_t i) - { - ui_build_selector->sel_index = i; - std::set< df::interface_key > keys; - keys.insert(df::interface_key::SELECT_ALL); - this->feed(&keys); - return !in_material_choice_stage(); - } - - bool choose_materials() - { - if (!auto_choose_materials || get_curr_constr_prefs().size() == 0) - return false; - - size_t size = ui_build_selector->choices.size(); - for (size_t i = 0; i < size; i++) - { - MaterialDescriptor material = get_material_in_list(i); - size_t j; - if (is_material_in_autoselect(j, material)) - { - return select_material_at_index(i); - } - } - - return false; - } - - void draw_box_selection() - { - if (!box_select_enabled) - return; - - if (plotinfo->main.mode != df::ui_sidebar_mode::Build || - ui_build_selector->building_type != df::building_type::Construction) - return; - - df::coord vport = Gui::getViewportPos(); - - //Even if selection drawing is disabled, paint a green cursor as we can place box selection anywhere - - if (box_select_mode == SELECT_FIRST || (!show_box_selection && box_select_mode == SELECT_SECOND)) - { - int32_t x, y, z; - if (!Gui::getCursorCoords(x, y, z)) - return; - - x = x - vport.x + 1; - y = y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); - } - else if (show_box_selection && box_select_mode == SELECT_SECOND) - { - if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) - return; - - Gui::DwarfmodeDims dims = Gui::getDwarfmodeViewDims(); - int32_t startx = std::max((int32_t)vport.x, std::min(box_first.x, box_second.x)); - int32_t endx = std::min(vport.x + dims.map_x2 - dims.map_x1, std::max(box_first.x, box_second.x)); - int32_t starty = std::max((int32_t)vport.y, std::min(box_first.y, box_second.y)); - int32_t endy = std::min(vport.y + dims.map_y2 - dims.map_y1, std::max(box_first.y, box_second.y)); - for (int32_t yB = starty; yB <= endy; ++yB) { - for (int32_t xB = startx; xB <= endx; ++xB) { - if (hollow_selection && !(xB == box_first.x || xB == box_second.x || yB == box_first.y || yB == box_second.y)) - continue; - - int8_t color = (xB == box_second.x && yB == box_second.y) ? COLOR_GREEN : COLOR_BROWN; - - int32_t x = xB - vport.x + 1; - int32_t y = yB - vport.y + 1; - OutputString(color, x, y, "X", false, 0, 0, true /* map */); - } - } - } - else if (show_box_selection && box_select_mode == SELECT_MATERIALS) - { - for (deque::iterator it = valid_building_sites.begin(); it != valid_building_sites.end(); it++) - { - int32_t x = it->pos.x - vport.x + 1; - int32_t y = it->pos.y - vport.y + 1; - OutputString(COLOR_GREEN, x, y, "X", false, 0, 0, true /* map */); - } - } - } - - void reset_existing_selection() - { - for (int i = 0; i < 10; i++) - { - send_key(df::interface_key::BUILDING_DIM_Y_DOWN); - send_key(df::interface_key::BUILDING_DIM_X_DOWN); - } - } - - void handle_input(set *input) - { - if (ui_build_selector->building_subtype >= 7) - return; - - if (in_material_choice_stage()) - { - if (input->count(interface_key::LEAVESCREEN)) - { - box_select_mode = SELECT_FIRST; - } - - MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); - if (material.valid) - { - if (input->count(interface_key::SELECT) || input->count(interface_key::SELECT_ALL)) - { - if (get_last_moved_material().matches(material)) - last_used_moved = false; //Keep selected material on top - - set_last_used_material(material); - - if (box_select_enabled) - { - auto curr_index = ui_build_selector->sel_index; - vector gen_material; - gen_material.push_back(get_material_in_list(curr_index)); - box_select_materials.clear(); - // Populate material list with selected material - populate_box_materials(gen_material, ((input->count(interface_key::SELECT_ALL) && ui_build_selector->is_grouped) ? -1 : 1)); - - input->clear(); // Let the apply_box_selection routine allocate the construction - input->insert(interface_key::LEAVESCREEN); - } - } - else if (input->count(interface_key::CUSTOM_A)) - { - check_autoselect(material, true); - input->clear(); - } - } - } - else if (in_placement_stage()) - { - bool use_buildingplan = is_buildingplan_managed(); - - if (!use_buildingplan && input->count(interface_key::CUSTOM_A)) - { - auto_choose_materials = !auto_choose_materials; - } - else if (!use_buildingplan && input->count(interface_key::CUSTOM_T)) - { - revert_to_last_used_type = !revert_to_last_used_type; - } - else if (input->count(interface_key::CUSTOM_B)) - { - reset_existing_selection(); - box_select_enabled = !box_select_enabled; - if (!box_select_enabled) - cancel_box_selection(); - - return; - } - else if (!use_buildingplan && input->count(interface_key::CUSTOM_O)) - { - allow_future_placement = !allow_future_placement; - } - else if (input->count(interface_key::LEAVESCREEN)) - { - switch (box_select_mode) - { - case SELECT_FIRST: - case SELECT_SECOND: - cancel_box_selection(); - - default: - break; - } - } - else if (box_select_enabled) - { - if (input->count(interface_key::SELECT)) - { - switch (box_select_mode) - { - case SELECT_FIRST: - if (!Gui::getCursorCoords(box_first.x, box_first.y, box_first.z)) - { - cancel_box_selection(); - return; - } - box_select_mode = SELECT_SECOND; - if (!show_box_selection) - Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z); - input->clear(); - return; - - case SELECT_SECOND: - if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) - { - cancel_box_selection(); - return; - } - cancel_box_selection(); - input->clear(); - apply_box_selection(true); - return; - - default: - break; - } - } - else if (input->count(interface_key::CUSTOM_X)) - { - show_box_selection = !show_box_selection; - if (box_select_mode == SELECT_SECOND) - { - if (show_box_selection) - { - Gui::setDesignationCoords(-1, -1, -1); - } - else - { - Gui::setDesignationCoords(box_first.x, box_first.y, box_first.z); - } - } - } - else if (input->count(interface_key::CUSTOM_H)) - { - hollow_selection = !hollow_selection; - } - else if (input->count(interface_key::BUILDING_DIM_Y_UP) || - input->count(interface_key::BUILDING_DIM_Y_DOWN) || - input->count(interface_key::BUILDING_DIM_X_UP) || - input->count(interface_key::BUILDING_DIM_X_DOWN)) - { - input->clear(); - return; - } - } - else if (use_buildingplan - && ui_build_selector->errors.size() == 0 - && input->count(interface_key::SELECT)) - { - build_with_buildingplan_ui(); - Gui::refreshSidebar(); - input->clear(); - return; - } - } - } - //END UI Methods - - //START Building Application - bool populate_box_materials(vector &gen_materials, int32_t count = -1) - { - bool result = false; - - if (gen_materials.size() == 0) - return result; - - if (ui_build_selector->is_grouped) - send_key(interface_key::BUILDING_EXPAND_CONTRACT); - - size_t size = ui_build_selector->choices.size(); - vector::iterator gen_material; - for (size_t i = 0; i < size; i++) - { - if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) - { - for (gen_material = gen_materials.begin(); gen_material != gen_materials.end(); gen_material++) - { - if (gen_material->item_type == spec->candidate->getType() && - gen_material->item_subtype == spec->candidate->getSubtype() && - gen_material->type == spec->candidate->getActualMaterial() && - gen_material->index == spec->candidate->getActualMaterialIndex()) - { - box_select_materials.push_back(spec->candidate); - if (count > -1) - return true; // Right now we only support 1 or all materials - - result = true; - break; - } - } - } - } - send_key(interface_key::BUILDING_EXPAND_CONTRACT); - - return result; - } - - void move_cursor(df::coord &pos) - { - int32_t x, y, z; - Gui::getCursorCoords(x, y, z); - DEBUG(log).print("moving cursor from %d, %d, %d to %d, %d, %d\n", - x, y, z, pos.x, pos.y, pos.z); - - Gui::setCursorCoords(pos.x, pos.y, pos.z); - Gui::refreshSidebar(); - } - - void move_cursor(coord32_t &pos) - { - df::coord c((int16_t) pos.x, (int16_t) pos.y, (int16_t) pos.z); - move_cursor(c); - } - - void apply_box_selection(bool new_start) - { - static bool saved_revert_setting = false; - static bool auto_select_applied = false; - - bool use_buildingplan = is_buildingplan_managed(); - box_select_mode = SELECT_MATERIALS; - if (new_start) - { - bool ok_to_continue = false; - bool in_future_placement_mode = false; - if (!find_valid_building_sites(false, use_buildingplan)) - { - if (allow_future_placement) - { - in_future_placement_mode = find_valid_building_sites(true, use_buildingplan); - } - } - else - { - ok_to_continue = true; - } - - // if using buildingplan, we don't need an anchor - if (!use_buildingplan) - { - if (in_future_placement_mode) - { - ok_to_continue = - find_anchor_in_spiral(valid_building_sites[0].pos); - } - else if (ok_to_continue) - { - // First valid site is guaranteed to be anchored, either on - // a tile or against a valid orthogonal tile - // Use it as an anchor point to generate materials list - anchor = valid_building_sites.front(); - valid_building_sites.pop_front(); - valid_building_sites.push_back(anchor); - } - } - - if (!ok_to_continue) - { - cancel_box_selection(); - hollow_selection = false; - return; - } - - saved_revert_setting = revert_to_last_used_type; - revert_to_last_used_type = true; - auto_select_applied = false; - box_select_materials.clear(); - - } - - while (valid_building_sites.size() > 0) - { - building_site site = valid_building_sites.front(); - valid_building_sites.pop_front(); - - if (use_buildingplan) - { - // we don't actually care if this fails. buildingplan will return - // false when it filters out bad tiles, and that's ok. - build_with_buildingplan_box_select(site.pos); - continue; - } - - if (box_select_materials.size() > 0) - { - df::construction_type type = (df::construction_type) ui_build_selector->building_subtype; - df::item *item = NULL; - while (box_select_materials.size() > 0) - { - item = box_select_materials.front(); - if (!item->flags.bits.in_job) - break; - box_select_materials.pop_front(); - item = NULL; - } - - if (item != NULL) - { - if (designate_new_construction(site.pos, type, item)) - { - box_select_materials.pop_front(); - box_select_mode = AUTOSELECT_MATERIALS; - send_key(interface_key::LEAVESCREEN); //Must do this to register items in use - send_key(hotkeys[type]); - box_select_mode = SELECT_MATERIALS; - } - continue; - } - } - - // Generate material list using regular construction placement routine - - if (site.in_open_air) - { - // Cannot invoke material selection on an unconnected tile, use anchor instead - move_cursor(anchor.pos); - send_key(df::interface_key::SELECT); - } - - move_cursor(site.pos); - - if (!site.in_open_air) - send_key(df::interface_key::SELECT); - - if (in_material_choice_stage()) - { - valid_building_sites.push_front(site); //Redo current tile with whatever gets selected - if (!auto_select_applied) - { - // See if any auto select materials are available - auto_select_applied = true; - if (auto_choose_materials && populate_box_materials(preferred_materials[ui_build_selector->building_subtype])) - { - continue; - } - } - - last_used_moved = false; - return; // No auto select materials left, ask user - } - } - - // Allocation done, reset - move_cursor(box_second); - - // if we're using buildingplan, we never actually leave the placement - // screen, so there's no need to re-enter the screen - revert_to_last_used_type = saved_revert_setting; - if (!use_buildingplan && !revert_to_last_used_type) - { - send_key(df::interface_key::LEAVESCREEN); - } - - cancel_box_selection(); - hollow_selection = false; - ignore_selection = 0; - } - //END Building Application - - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - if (ignore_selection < SELECTION_IGNORE_TICKS) - { - //FIXME: Sometimes there's an extra ENTER key left over after box selection - ignore_selection = SELECTION_IGNORE_TICKS; - return; - } - - if (box_select_mode != AUTOSELECT_MATERIALS) - handle_input(input); - - int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1; - INTERPOSE_NEXT(feed)(input); - - if (revert_to_last_used_type && - last_used_constr_subtype >= 0 && - in_type_choice_stage() && - hotkeys.find(last_used_constr_subtype) != hotkeys.end()) - { - input->clear(); - input->insert(hotkeys[last_used_constr_subtype]); - INTERPOSE_NEXT(feed)(input); - - if (box_select_mode == SELECT_MATERIALS) - { - apply_box_selection(false); - } - } - } - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - if (ignore_selection < SELECTION_IGNORE_TICKS) - { - ++ignore_selection; - } - - if (in_material_choice_stage()) - { - if (!last_used_moved && ui_build_selector->is_grouped) - { - last_used_moved = true; - if (!box_select_enabled && choose_materials()) - { - return; - } - else - { - move_material_to_top(get_last_used_material()); - } - } - else if (!ui_build_selector->is_grouped) - { - last_used_moved = false; - } - } - else - { - last_used_moved = false; - } - - INTERPOSE_NEXT(render)(); - - draw_box_selection(); - - if (in_type_choice_stage()) - { - cancel_box_selection(); - return; - } - - auto dims = Gui::getDwarfmodeViewDims(); - int left_margin = dims.menu_x1 + 1; - int x = left_margin; - int y = 25; - if (in_material_choice_stage()) - { - MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); - if (material.valid) - { - AMOutputToggleString(x, y, "Autoselect", "a", check_autoselect(material, false), true, left_margin); - - if (box_select_mode == SELECT_MATERIALS) - { - ++y; - OutputString(COLOR_BROWN, x, y, "Construction:", true, left_margin); - OutputString(COLOR_WHITE, x, y, int_to_string(valid_building_sites.size()) + " tiles to fill", true, left_margin); - } - } - } - else if (in_placement_stage() && ui_build_selector->building_subtype < 7) - { - bool use_buildingplan = is_buildingplan_managed(); - - OutputString(COLOR_BROWN, x, y, "DFHack Automaterial Options", true, left_margin); - if (use_buildingplan) - { - y += 2; - } - else - { - AMOutputToggleString(x, y, "Auto Mat-select", "a", auto_choose_materials, true, left_margin); - AMOutputToggleString(x, y, "Reselect Type", "t", revert_to_last_used_type, true, left_margin); - } - - ++y; - AMOutputToggleString(x, y, "Box Select", "b", box_select_enabled, true, left_margin); - if (box_select_enabled) - { - AMOutputToggleString(x, y, "Show Box Mask", "x", show_box_selection, true, left_margin); - OutputHotkeyString(x, y, (hollow_selection) ? "Make Solid" : "Make Hollow", "h", true, left_margin); - - if (use_buildingplan) - ++y; - else - AMOutputToggleString(x, y, "Open Placement", "o", allow_future_placement, true, left_margin); - } - else - { - y += 3; - } - y += 2; - if (is_buildingplan_enabled()) - OutputString(COLOR_BROWN, x, y, "DFHack Buildingplan Options", true, left_margin); - - if (box_select_enabled) - { - Screen::Pen pen(' ',COLOR_BLACK); - y = dims.y1 + 2; - Screen::fillRect(pen, x, y, dims.menu_x2, y + 17); - - y += 2; - switch (box_select_mode) - { - case SELECT_FIRST: - OutputString(COLOR_BROWN, x, y, "Choose first corner", true, left_margin); - break; - - case SELECT_SECOND: - { - OutputString(COLOR_GREEN, x, y, "Choose second corner", true, left_margin); - - int32_t curr_x, curr_y, curr_z; - Gui::getCursorCoords(curr_x, curr_y, curr_z); - int dX = abs(box_first.x - curr_x) + 1; - int dY = abs(box_first.y - curr_y) + 1; - stringstream label; - label << "Selection: " << dX << "x" << dY; - OutputString(COLOR_WHITE, x, ++y, label.str(), true, left_margin); - - df::coord vport = Gui::getViewportPos(); - int cx = box_first.x - vport.x + 1; - int cy = box_first.y - vport.y + 1; - - Gui::DwarfmodeDims dims = Gui::getDwarfmodeViewDims(); - if (cx >= 1 && cx <= dims.map_x2 && cy >= 1 && cy <= dims.map_y2) - OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */); - break; - } - - default: - break; - } - - OutputString(COLOR_BROWN, x, ++y, "Ignore Building Restrictions", true, left_margin); - } - } - } -}; -//END Viewscreen Hook - -color_ostream_proxy console_out(Core::getInstance().getConsole()); - - -IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render); - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable) -{ - if (!gps) - return CR_FAILURE; - - if (enable != is_enabled) - { - if (!INTERPOSE_HOOK(jobutils_hook, feed).apply(enable) || - !INTERPOSE_HOOK(jobutils_hook, render).apply(enable)) - return CR_FAILURE; - - is_enabled = enable; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL; - hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR; - hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP; - hotkeys[construction_type::UpStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP; - hotkeys[construction_type::DownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN; - hotkeys[construction_type::UpDownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN; - hotkeys[construction_type::Fortification] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION; - //Ignore tracks, DF already returns to track menu - - return CR_OK; -} diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 0cd9e8131..dc0bc0f79 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -172,20 +172,6 @@ DFhackCExport command_result plugin_load_data(color_ostream &out) return CR_OK; } -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - if (event == DFHack::SC_WORLD_UNLOADED) - { - if (is_enabled) - { - DEBUG(status, out).print("world unloaded; disabling %s\n", plugin_name); - is_enabled = false; - } - } - - return CR_OK; -} - DFhackCExport command_result plugin_onupdate(color_ostream &out) { if (!Core::getInstance().isWorldLoaded()) @@ -293,7 +279,7 @@ static inline bool can_melt(df::item *item) df::item_type t = item->getType(); - if (t == df::enums::item_type::BOX || t == df::enums::item_type::BAR) + if (t == df::enums::item_type::BAR) return false; for (auto &g : item->general_refs) diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 3d34f592e..39847935d 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -46,6 +46,8 @@ using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); +static const string BLUEPRINT_USER_DIR = "dfhack-config/blueprints/"; + namespace DFHack { DBG_DECLARE(blueprint,log); } @@ -227,6 +229,9 @@ static const char * get_tile_dig(const df::coord &pos, const tile_context &) { case tiletype_shape::BOULDER: case tiletype_shape::PEBBLES: case tiletype_shape::BROOK_TOP: + case tiletype_shape::SAPLING: + case tiletype_shape::SHRUB: + case tiletype_shape::TWIG: return "d"; case tiletype_shape::STAIR_UP: return "u"; @@ -1091,7 +1096,7 @@ static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) { static bool create_output_dir(color_ostream &out, const blueprint_options &opts) { - string basename = "blueprints/" + opts.name; + string basename = BLUEPRINT_USER_DIR + opts.name; size_t last_slash = basename.find_last_of("/"); string parent_path = basename.substr(0, last_slash); diff --git a/plugins/buildingplan/buildingplan.cpp b/plugins/buildingplan/buildingplan.cpp index ec85d7952..ce424f9c6 100644 --- a/plugins/buildingplan/buildingplan.cpp +++ b/plugins/buildingplan/buildingplan.cpp @@ -9,8 +9,10 @@ #include "modules/World.h" +#include "df/construction_type.h" #include "df/item.h" #include "df/job_item.h" +#include "df/organic_mat_category.h" #include "df/world.h" using std::map; @@ -71,7 +73,7 @@ static Tasks tasks; void PlannedBuilding::remove(color_ostream &out) { DEBUG(status,out).print("removing persistent data for building %d\n", id); World::DeletePersistentData(bld_config); - if (planned_buildings.count(id) > 0) + if (planned_buildings.count(id)) planned_buildings.erase(id); } @@ -130,7 +132,7 @@ static const vector & get_job_items(color_ostream &out, Bu }, [&](lua_State *L) { df::job_item *jitem = Lua::GetDFObject(L, -1); - DEBUG(status,out).print("retrieving job_item for (%d, %d, %d) index=%d: %p\n", + DEBUG(status,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); if (!jitem) failed = true; @@ -148,27 +150,56 @@ static const df::dfhack_material_category stone_cat(df::dfhack_material_category static const df::dfhack_material_category wood_cat(df::dfhack_material_category::mask_wood); static const df::dfhack_material_category metal_cat(df::dfhack_material_category::mask_metal); static const df::dfhack_material_category glass_cat(df::dfhack_material_category::mask_glass); +static const df::dfhack_material_category gem_cat(df::dfhack_material_category::mask_gem); +static const df::dfhack_material_category clay_cat(df::dfhack_material_category::mask_clay); +static const df::dfhack_material_category cloth_cat(df::dfhack_material_category::mask_cloth); +static const df::dfhack_material_category silk_cat(df::dfhack_material_category::mask_silk); +static const df::dfhack_material_category yarn_cat(df::dfhack_material_category::mask_yarn); static void cache_matched(int16_t type, int32_t index) { MaterialInfo mi; mi.decode(type, index); if (mi.matches(stone_cat)) { - DEBUG(status).print("cached stone material: %s\n", mi.toString().c_str()); + DEBUG(status).print("cached stone material: %s (%d, %d)\n", mi.toString().c_str(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "stone")); } else if (mi.matches(wood_cat)) { - DEBUG(status).print("cached wood material: %s\n", mi.toString().c_str()); + DEBUG(status).print("cached wood material: %s (%d, %d)\n", mi.toString().c_str(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "wood")); } else if (mi.matches(metal_cat)) { - DEBUG(status).print("cached metal material: %s\n", mi.toString().c_str()); + DEBUG(status).print("cached metal material: %s (%d, %d)\n", mi.toString().c_str(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "metal")); } else if (mi.matches(glass_cat)) { - DEBUG(status).print("cached glass material: %s\n", mi.toString().c_str()); + DEBUG(status).print("cached glass material: %s (%d, %d)\n", mi.toString().c_str(), type, index); mat_cache.emplace(mi.toString(), std::make_pair(mi, "glass")); + } else if (mi.matches(gem_cat)) { + DEBUG(status).print("cached gem material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "gem")); + } else if (mi.matches(clay_cat)) { + DEBUG(status).print("cached clay material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "clay")); + } else if (mi.matches(cloth_cat)) { + DEBUG(status).print("cached cloth material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "cloth")); + } else if (mi.matches(silk_cat)) { + DEBUG(status).print("cached silk material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "silk")); + } else if (mi.matches(yarn_cat)) { + DEBUG(status).print("cached yarn material: %s (%d, %d)\n", mi.toString().c_str(), type, index); + mat_cache.emplace(mi.toString(), std::make_pair(mi, "yarn")); } else TRACE(status).print("not matched: %s\n", mi.toString().c_str()); } +static void load_organic_material_cache(df::organic_mat_category cat) { + auto& mat_tab = world->raws.mat_table; + auto& cat_vec = mat_tab.organic_types[cat]; + auto& cat_indices = mat_tab.organic_indexes[cat]; + for (size_t i = 0; i < cat_vec.size(); i++) { + cache_matched(cat_vec[i], cat_indices[i]); + } +} + static void load_material_cache() { df::world_raws &raws = world->raws; for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; ++i) @@ -178,22 +209,16 @@ static void load_material_cache() { for (size_t i = 0; i < raws.inorganics.size(); i++) cache_matched(0, i); - for (size_t i = 0; i < raws.plants.all.size(); i++) { - df::plant_raw *p = raws.plants.all[i]; - if (p->material.size() <= 1) - continue; - for (size_t j = 0; j < p->material.size(); j++) { - if (p->material[j]->id == "WOOD") { - cache_matched(DFHack::MaterialInfo::PLANT_BASE+j, i); - break; - } - } - } + load_organic_material_cache(df::organic_mat_category::Wood); + load_organic_material_cache(df::organic_mat_category::PlantFiber); + load_organic_material_cache(df::organic_mat_category::Silk); + load_organic_material_cache(df::organic_mat_category::Yarn); } static HeatSafety get_heat_safety_filter(const BuildingTypeKey &key) { - if (cur_heat_safety.count(key)) - return cur_heat_safety.at(key); + // comment out until we can get heat safety working as intended + // if (cur_heat_safety.count(key)) + // return cur_heat_safety.at(key); return HEAT_SAFETY_ANY; } @@ -206,9 +231,9 @@ static DefaultItemFilters & get_item_filters(color_ostream &out, const BuildingT static command_result do_command(color_ostream &out, vector ¶meters); void buildingplan_cycle(color_ostream &out, Tasks &tasks, - unordered_map &planned_buildings); + unordered_map &planned_buildings, bool unsuspend_on_finalize); -static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb); +static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bool unsuspend_on_finalize); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { DEBUG(status,out).print("initializing %s\n", plugin_name); @@ -257,13 +282,15 @@ static void validate_config(color_ostream &out, bool verbose = false) { set_config_bool(config, CONFIG_BARS, false); } -static void clear_state(color_ostream &out) { +static void reset_filters(color_ostream &out) { + cur_heat_safety.clear(); + cur_item_filters.clear(); call_buildingplan_lua(&out, "signal_reset"); - call_buildingplan_lua(&out, "reload_cursors"); +} + +static void clear_state(color_ostream &out) { planned_buildings.clear(); tasks.clear(); - cur_heat_safety.clear(); - cur_item_filters.clear(); for (auto &entry : job_item_cache ) { for (auto &jitem : entry.second) { delete jitem; @@ -271,6 +298,30 @@ static void clear_state(color_ostream &out) { } job_item_cache.clear(); mat_cache.clear(); + reset_filters(out); + call_buildingplan_lua(&out, "reload_pens"); +} + +static int16_t get_subtype(df::building *bld) { + if (!bld) + return -1; + + int16_t subtype = bld->getSubtype(); + if (bld->getType() == df::building_type::Construction && + subtype >= df::construction_type::UpStair && + subtype <= df::construction_type::UpDownStair) + subtype = df::construction_type::UpDownStair; + return subtype; +} + +static bool is_suspendmanager_enabled(color_ostream &out) { + bool suspendmanager_enabled = false; + call_buildingplan_lua(&out, "is_suspendmanager_enabled", 0, 1, + Lua::DEFAULT_LUA_LAMBDA, + [&](lua_State *L){ + suspendmanager_enabled = lua_toboolean(L, -1); + }); + return suspendmanager_enabled; } DFhackCExport command_result plugin_load_data (color_ostream &out) { @@ -298,34 +349,27 @@ DFhackCExport command_result plugin_load_data (color_ostream &out) { vector building_configs; World::GetPersistentData(&building_configs, BLD_CONFIG_KEY); const size_t num_building_configs = building_configs.size(); + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); for (size_t idx = 0; idx < num_building_configs; ++idx) { PlannedBuilding pb(out, building_configs[idx]); df::building *bld = df::building::find(pb.id); if (!bld) { - INFO(status,out).print("building %d no longer exists; skipping\n", pb.id); + DEBUG(status,out).print("building %d no longer exists; skipping\n", pb.id); pb.remove(out); continue; } - BuildingTypeKey key(bld->getType(), bld->getSubtype(), bld->getCustomType()); + BuildingTypeKey key(bld->getType(), get_subtype(bld), bld->getCustomType()); if (pb.item_filters.size() != get_item_filters(out, key).getItemFilters().size()) { WARN(status).print("loaded state for building %d doesn't match world\n", pb.id); pb.remove(out); continue; } - registerPlannedBuilding(out, pb); + registerPlannedBuilding(out, pb, unsuspend_on_finalize); } return CR_OK; } -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - if (event == SC_WORLD_UNLOADED) { - DEBUG(status,out).print("world unloaded; clearing state for %s\n", plugin_name); - clear_state(out); - } - return CR_OK; -} - static bool cycle_requested = false; static void do_cycle(color_ostream &out) { @@ -333,7 +377,8 @@ static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; cycle_requested = false; - buildingplan_cycle(out, tasks, planned_buildings); + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); + buildingplan_cycle(out, tasks, planned_buildings, unsuspend_on_finalize); call_buildingplan_lua(&out, "signal_reset"); } @@ -376,7 +421,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) // static string getBucket(const df::job_item & ji, const PlannedBuilding & pb, int idx) { - if (idx < 0 || (size_t)idx < pb.item_filters.size()) + if (idx < 0 || (size_t)idx >= pb.item_filters.size()) return "INVALID"; std::ostringstream ser; @@ -398,7 +443,7 @@ static string getBucket(const df::job_item & ji, const PlannedBuilding & pb, int ser << "Hc"; size_t num_materials = item_filter.getMaterials().size(); - if (num_materials == 0 || num_materials >= 9 || item_filter.getMaterialMask().whole) + if (num_materials == 0 || num_materials >= 9 || !item_filter.getMaterialMask().whole) ser << "M9"; else ser << "M" << num_materials; @@ -416,11 +461,14 @@ static string getBucket(const df::job_item & ji, const PlannedBuilding & pb, int ser << ':' << item_filter.serialize(); + for (auto &special : pb.specials) + ser << ':' << special; + return ser.str(); } // get a list of item vectors that we should search for matches -vector getVectorIds(color_ostream &out, const df::job_item *job_item) { +vector getVectorIds(color_ostream &out, const df::job_item *job_item, bool ignore_filters) { std::vector ret; // if the filter already has the vector_id set to something specific, use it @@ -432,17 +480,17 @@ vector getVectorIds(color_ostream &out, const df::job_it return ret; } - // if the filer is for building material, refer to our global settings for + // if the filter is for building material, refer to our global settings for // which vectors to search if (job_item->flags2.bits.building_material) { - if (get_config_bool(config, CONFIG_BLOCKS)) + if (ignore_filters || get_config_bool(config, CONFIG_BLOCKS)) ret.push_back(df::job_item_vector_id::BLOCKS); - if (get_config_bool(config, CONFIG_BOULDERS)) + if (ignore_filters || get_config_bool(config, CONFIG_BOULDERS)) ret.push_back(df::job_item_vector_id::BOULDER); - if (get_config_bool(config, CONFIG_LOGS)) + if (ignore_filters || get_config_bool(config, CONFIG_LOGS)) ret.push_back(df::job_item_vector_id::WOOD); - if (get_config_bool(config, CONFIG_BARS)) + if (ignore_filters || get_config_bool(config, CONFIG_BARS)) ret.push_back(df::job_item_vector_id::BAR); } @@ -452,7 +500,7 @@ vector getVectorIds(color_ostream &out, const df::job_it return ret; } -static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { +static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb, bool unsuspend_on_finalize) { df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out); if (!bld) return false; @@ -462,17 +510,23 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { return false; } + // suspend jobs + for (auto job : bld->jobs) + job->flags.bits.suspend = true; + auto job_items = bld->jobs[0]->job_items; if (isJobReady(out, job_items)) { // all items are already attached - finalizeBuilding(out, bld); + finalizeBuilding(out, bld, unsuspend_on_finalize); + pb.remove(out); return true; } - int num_job_items = job_items.size(); + int num_job_items = (int)job_items.size(); int32_t id = bld->id; for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) { - auto job_item = job_items[job_item_idx]; + int rev_jitem_index = num_job_items - (job_item_idx+1); + auto job_item = job_items[rev_jitem_index]; auto bucket = getBucket(*job_item, pb, job_item_idx); // if there are multiple vector_ids, schedule duplicate tasks. after @@ -480,20 +534,16 @@ static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) { // as invalid 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, job_item_idx); + tasks[vector_id][bucket].emplace_back(id, rev_jitem_index); DEBUG(status,out).print("added task: %s/%s/%d,%d; " - "%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket", + "%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, job_item_idx, tasks.size(), + bucket.c_str(), id, rev_jitem_index, tasks.size(), tasks[vector_id].size(), tasks[vector_id][bucket].size()); } } } - // suspend jobs - for (auto job : bld->jobs) - job->flags.bits.suspend = true; - // add the planned buildings to our register planned_buildings.emplace(bld->id, pb); @@ -524,30 +574,36 @@ static void printStatus(color_ostream &out) { out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no"); out.print("\n"); + size_t bld_count = 0; map counts; int32_t total = 0; for (auto &entry : planned_buildings) { auto &pb = entry.second; - auto bld = pb.getBuildingIfValidOrRemoveIfNot(out); + // don't actually remove bad buildings from the list while we're + // actively iterating through that list + auto bld = pb.getBuildingIfValidOrRemoveIfNot(out, true); if (!bld || bld->jobs.size() != 1) continue; auto &job_items = bld->jobs[0]->job_items; - if (job_items.size() != pb.vector_ids.size()) + const size_t num_job_items = job_items.size(); + if (num_job_items != pb.vector_ids.size()) continue; + ++bld_count; int job_item_idx = 0; for (auto &vec_ids : pb.vector_ids) { - auto &jitem = job_items[job_item_idx++]; + auto &jitem = job_items[num_job_items - (job_item_idx+1)]; int32_t quantity = jitem->quantity; if (quantity) { counts[get_desc_string(out, jitem, vec_ids)] += quantity; total += quantity; } + ++job_item_idx; } } - if (planned_buildings.size()) { + if (bld_count) { out.print("Waiting for %d item(s) to be produced for %zd building(s):\n", - total, planned_buildings.size()); + total, bld_count); for (auto &count : counts) out.print(" %3d %s%s\n", count.second, count.first.c_str(), count.second == 1 ? "" : "s"); } else { @@ -576,6 +632,11 @@ static bool setSetting(color_ostream &out, string name, bool value) { return true; } +static void resetFilters(color_ostream &out) { + DEBUG(status,out).print("entering resetFilters\n"); + reset_filters(out); +} + static bool isPlannableBuilding(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom) { DEBUG(status,out).print("entering isPlannableBuilding\n"); return get_num_filters(out, BuildingTypeKey(type, subtype, custom)) >= 1; @@ -588,13 +649,19 @@ static bool isPlannedBuilding(color_ostream &out, df::building *bld) { static bool addPlannedBuilding(color_ostream &out, df::building *bld) { DEBUG(status,out).print("entering addPlannedBuilding\n"); - if (!bld || planned_buildings.count(bld->id) - || !isPlannableBuilding(out, bld->getType(), bld->getSubtype(), - bld->getCustomType())) + if (!bld || planned_buildings.count(bld->id)) return false; - BuildingTypeKey key(bld->getType(), bld->getSubtype(), bld->getCustomType()); - PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key).getItemFilters()); - return registerPlannedBuilding(out, pb); + + int16_t subtype = get_subtype(bld); + + if (!isPlannableBuilding(out, bld->getType(), subtype, bld->getCustomType())) + return false; + + BuildingTypeKey key(bld->getType(), subtype, bld->getCustomType()); + PlannedBuilding pb(out, bld, get_heat_safety_filter(key), get_item_filters(out, key)); + + bool unsuspend_on_finalize = !is_suspendmanager_enabled(out); + return registerPlannedBuilding(out, pb, unsuspend_on_finalize); } static void doCycle(color_ostream &out) { @@ -608,7 +675,7 @@ static void scheduleCycle(color_ostream &out) { } static int scanAvailableItems(color_ostream &out, df::building_type type, int16_t subtype, - int32_t custom, int index, vector *item_ids = NULL, + int32_t custom, int index, bool ignore_filters, vector *item_ids = NULL, map *counts = NULL) { DEBUG(status,out).print( "entering countAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", @@ -618,22 +685,26 @@ static int scanAvailableItems(color_ostream &out, df::building_type type, int16_ auto &job_items = get_job_items(out, key); if (index < 0 || job_items.size() <= (size_t)index) return 0; - auto &item_filters = get_item_filters(out, key).getItemFilters(); + auto &item_filters = get_item_filters(out, key); + auto &filters = item_filters.getItemFilters(); + auto &specials = item_filters.getSpecials(); auto &jitem = job_items[index]; - auto vector_ids = getVectorIds(out, jitem); + auto vector_ids = getVectorIds(out, jitem, ignore_filters); int count = 0; for (auto vector_id : vector_ids) { auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); for (auto &item : df::global::world->items.other[other_id]) { - ItemFilter filter = item_filters[index]; - if (counts) { + ItemFilter filter = filters[index]; + set special = specials; + if (ignore_filters || counts) { // don't filter by material; we want counts for all materials filter.setMaterialMask(0); filter.setMaterials(set()); + special.clear(); } - if (itemPassesScreen(item) && matchesFilters(item, jitem, heat, filter)) { + if (itemPassesScreen(out, item) && matchesFilters(item, jitem, heat, filter, special)) { if (item_ids) item_ids->emplace_back(item->id); if (counts) { @@ -662,7 +733,7 @@ static int getAvailableItems(lua_State *L) { "entering getAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); vector item_ids; - scanAvailableItems(*out, type, subtype, custom, index, &item_ids); + scanAvailableItems(*out, type, subtype, custom, index, true, &item_ids); Lua::PushVector(L, item_ids); return 1; } @@ -685,18 +756,18 @@ static int countAvailableItems(color_ostream &out, df::building_type type, int16 DEBUG(status,out).print( "entering countAvailableItems building_type=%d subtype=%d custom=%d index=%d\n", type, subtype, custom, index); - return scanAvailableItems(out, type, subtype, custom, index); + return scanAvailableItems(out, type, subtype, custom, index, false); } static bool hasFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) { TRACE(status,out).print("entering hasFilter\n"); + if (!Core::getInstance().isWorldLoaded()) + return false; BuildingTypeKey key(type, subtype, custom); auto &filters = get_item_filters(out, key); - for (auto &filter : filters.getItemFilters()) { - if (!filter.isEmpty()) - return true; - } - return false; + if (index < 0 || filters.getItemFilters().size() <= (size_t)index) + return false; + return !filters.getItemFilters()[index].isEmpty(); } static void clearFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index) { @@ -738,6 +809,16 @@ static int setMaterialMaskFilter(lua_State *L) { mask |= metal_cat.whole; else if (cat == "glass") mask |= glass_cat.whole; + else if (cat == "gem") + mask |= gem_cat.whole; + else if (cat == "clay") + mask |= clay_cat.whole; + else if (cat == "cloth") + mask |= cloth_cat.whole; + else if (cat == "silk") + mask |= silk_cat.whole; + else if (cat == "yarn") + mask |= yarn_cat.whole; } DEBUG(status,*out).print( "setting material mask filter for building_type=%d subtype=%d custom=%d index=%d to %x\n", @@ -782,6 +863,11 @@ static int getMaterialMaskFilter(lua_State *L) { ret.emplace("wood", !bits || bits & wood_cat.whole); ret.emplace("metal", !bits || bits & metal_cat.whole); ret.emplace("glass", !bits || bits & glass_cat.whole); + ret.emplace("gem", !bits || bits & gem_cat.whole); + ret.emplace("clay", !bits || bits & clay_cat.whole); + ret.emplace("cloth", !bits || bits & cloth_cat.whole); + ret.emplace("silk", !bits || bits & silk_cat.whole); + ret.emplace("yarn", !bits || bits & yarn_cat.whole); Lua::Push(L, ret); return 1; } @@ -826,6 +912,16 @@ static int setMaterialFilter(lua_State *L) { mask.whole |= metal_cat.whole; else if (mat.matches(glass_cat)) mask.whole |= glass_cat.whole; + else if (mat.matches(gem_cat)) + mask.whole |= gem_cat.whole; + else if (mat.matches(clay_cat)) + mask.whole |= clay_cat.whole; + else if (mat.matches(cloth_cat)) + mask.whole |= cloth_cat.whole; + else if (mat.matches(silk_cat)) + mask.whole |= silk_cat.whole; + else if (mat.matches(yarn_cat)) + mask.whole |= yarn_cat.whole; } filter.setMaterialMask(mask.whole); get_item_filters(*out, key).setItemFilter(*out, filter, index); @@ -850,12 +946,26 @@ static int getMaterialFilter(lua_State *L) { return 0; const auto &mat_filter = filters[index].getMaterials(); map counts; - scanAvailableItems(*out, type, subtype, custom, index, NULL, &counts); - // name -> {count=int, enabled=bool, category=string} + scanAvailableItems(*out, type, subtype, custom, index, false, NULL, &counts); + HeatSafety heat = get_heat_safety_filter(key); + df::job_item jitem_cur_heat = getJobItemWithHeatSafety( + get_job_items(*out, key)[index], heat); + df::job_item jitem_fire = getJobItemWithHeatSafety( + get_job_items(*out, key)[index], HEAT_SAFETY_FIRE); + df::job_item jitem_magma = getJobItemWithHeatSafety( + get_job_items(*out, key)[index], HEAT_SAFETY_MAGMA); + // name -> {count=int, enabled=bool, category=string, heat=string} map> ret; for (auto & entry : mat_cache) { - auto &name = entry.first; auto &mat = entry.second.first; + if (!mat.matches(jitem_cur_heat)) + continue; + string heat_safety = ""; + if (mat.matches(jitem_magma)) + heat_safety = "magma-safe"; + else if (mat.matches(jitem_fire)) + heat_safety = "fire-safe"; + auto &name = entry.first; auto &cat = entry.second.second; map props; string count = "0"; @@ -870,6 +980,31 @@ static int getMaterialFilter(lua_State *L) { return 1; } +static void setChooseItems(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int choose) { + DEBUG(status,out).print( + "entering setChooseItems building_type=%d subtype=%d custom=%d choose=%d\n", + type, subtype, custom, choose); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(out, key); + filters.setChooseItems(choose); + // no need to reset signal; no change to the state of any other UI element +} + +static int getChooseItems(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + DEBUG(status,*out).print( + "entering getChooseItems building_type=%d subtype=%d custom=%d\n", + type, subtype, custom); + BuildingTypeKey key(type, subtype, custom); + Lua::Push(L, get_item_filters(*out, key).getChooseItems()); + return 1; +} + static void setHeatSafetyFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int heat) { DEBUG(status,out).print("entering setHeatSafetyFilter\n"); BuildingTypeKey key(type, subtype, custom); @@ -896,6 +1031,29 @@ static int getHeatSafetyFilter(lua_State *L) { return 1; } +static void setSpecial(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, string special, bool val) { + DEBUG(status,out).print("entering setSpecial\n"); + BuildingTypeKey key(type, subtype, custom); + auto &filters = get_item_filters(out, key); + filters.setSpecial(special, val); + call_buildingplan_lua(&out, "signal_reset"); +} + +static int getSpecials(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + df::building_type type = (df::building_type)luaL_checkint(L, 1); + int16_t subtype = luaL_checkint(L, 2); + int32_t custom = luaL_checkint(L, 3); + DEBUG(status,*out).print( + "entering getSpecials building_type=%d subtype=%d custom=%d\n", + type, subtype, custom); + BuildingTypeKey key(type, subtype, custom); + Lua::Push(L, get_item_filters(*out, key).getSpecials()); + return 1; +} + static void setQualityFilter(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom, int index, int decorated, int min_quality, int max_quality) { DEBUG(status,out).print("entering setQualityFilter\n"); @@ -953,20 +1111,26 @@ static bool validate_pb(color_ostream &out, df::building *bld, int index) { static string getDescString(color_ostream &out, df::building *bld, int index) { DEBUG(status,out).print("entering getDescString\n"); if (!validate_pb(out, bld, index)) - return 0; + return "INVALID"; PlannedBuilding &pb = planned_buildings.at(bld->id); - auto &jitem = bld->jobs[0]->job_items[index]; - return get_desc_string(out, jitem, pb.vector_ids[index]); + auto & jitems = bld->jobs[0]->job_items; + const size_t num_job_items = jitems.size(); + int rev_index = num_job_items - (index + 1); + auto &jitem = jitems[rev_index]; + return int_to_string(jitem->quantity) + " " + get_desc_string(out, jitem, pb.vector_ids[index]); } static int getQueuePosition(color_ostream &out, df::building *bld, int index) { - DEBUG(status,out).print("entering getQueuePosition\n"); + TRACE(status,out).print("entering getQueuePosition\n"); if (!validate_pb(out, bld, index)) return 0; PlannedBuilding &pb = planned_buildings.at(bld->id); - auto &job_item = bld->jobs[0]->job_items[index]; + auto & jitems = bld->jobs[0]->job_items; + const size_t num_job_items = jitems.size(); + int rev_index = num_job_items - (index + 1); + auto &job_item = jitems[rev_index]; if (job_item->quantity <= 0) return 0; @@ -982,7 +1146,7 @@ static int getQueuePosition(color_ostream &out, df::building *bld, int index) { int bucket_pos = -1; for (auto &task : buckets.at(bucket_id)) { ++bucket_pos; - if (bld->id == task.first && index == task.second) + if (bld->id == task.first && rev_index == task.second) break; } if (bucket_pos++ >= 0) @@ -999,18 +1163,20 @@ static void makeTopPriority(color_ostream &out, df::building *bld) { PlannedBuilding &pb = planned_buildings.at(bld->id); auto &job_items = bld->jobs[0]->job_items; + const int num_job_items = (int)job_items.size(); - for (int index = 0; index < (int)job_items.size(); ++index) { + for (int index = 0; index < num_job_items; ++index) { + int rev_index = num_job_items - (index + 1); for (auto &vec_id : pb.vector_ids[index]) { if (!tasks.count(vec_id)) continue; auto &buckets = tasks.at(vec_id); - string bucket_id = getBucket(*job_items[index], pb, index); + string bucket_id = getBucket(*job_items[rev_index], pb, index); if (!buckets.count(bucket_id)) continue; auto &bucket = buckets.at(bucket_id); for (auto taskit = bucket.begin(); taskit != bucket.end(); ++taskit) { - if (bld->id == taskit->first && index == taskit->second) { + if (bld->id == taskit->first && rev_index == taskit->second) { auto task_bld_id = taskit->first; auto task_job_item_idx = taskit->second; bucket.erase(taskit); @@ -1025,6 +1191,7 @@ static void makeTopPriority(color_ostream &out, df::building *bld) { DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(printStatus), DFHACK_LUA_FUNCTION(setSetting), + DFHACK_LUA_FUNCTION(resetFilters), DFHACK_LUA_FUNCTION(isPlannableBuilding), DFHACK_LUA_FUNCTION(isPlannedBuilding), DFHACK_LUA_FUNCTION(addPlannedBuilding), @@ -1033,7 +1200,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(countAvailableItems), DFHACK_LUA_FUNCTION(hasFilter), DFHACK_LUA_FUNCTION(clearFilter), + DFHACK_LUA_FUNCTION(setChooseItems), DFHACK_LUA_FUNCTION(setHeatSafetyFilter), + DFHACK_LUA_FUNCTION(setSpecial), DFHACK_LUA_FUNCTION(setQualityFilter), DFHACK_LUA_FUNCTION(getDescString), DFHACK_LUA_FUNCTION(getQueuePosition), @@ -1048,7 +1217,9 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getMaterialMaskFilter), DFHACK_LUA_COMMAND(setMaterialFilter), DFHACK_LUA_COMMAND(getMaterialFilter), + DFHACK_LUA_COMMAND(getChooseItems), DFHACK_LUA_COMMAND(getHeatSafetyFilter), + DFHACK_LUA_COMMAND(getSpecials), DFHACK_LUA_COMMAND(getQualityFilter), DFHACK_LUA_END }; diff --git a/plugins/buildingplan/buildingplan.h b/plugins/buildingplan/buildingplan.h index eef9808e6..9bfd38731 100644 --- a/plugins/buildingplan/buildingplan.h +++ b/plugins/buildingplan/buildingplan.h @@ -9,6 +9,7 @@ #include "df/job_item_vector_id.h" #include +#include typedef std::deque> Bucket; typedef std::map> Tasks; @@ -27,6 +28,7 @@ enum FilterConfigValues { FILTER_CONFIG_TYPE = 0, FILTER_CONFIG_SUBTYPE = 1, FILTER_CONFIG_CUSTOM = 2, + FILTER_CONFIG_CHOOSE_ITEMS = 3, }; enum BuildingConfigValues { @@ -40,13 +42,20 @@ enum HeatSafety { HEAT_SAFETY_MAGMA = 2, }; +enum ItemSelectionChoice { + ITEM_SELECTION_CHOICE_FILTER = 0, + ITEM_SELECTION_CHOICE_MANUAL = 1, + ITEM_SELECTION_CHOICE_AUTOMATERIAL = 2, +}; + int get_config_val(DFHack::PersistentDataItem &c, int index); bool get_config_bool(DFHack::PersistentDataItem &c, int index); void set_config_val(DFHack::PersistentDataItem &c, int index, int value); void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value); -std::vector getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item); -bool itemPassesScreen(df::item * item); -bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter); +std::vector getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item, bool ignore_filters); +bool itemPassesScreen(DFHack::color_ostream& out, df::item* item); +df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety heat); +bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set &special); bool isJobReady(DFHack::color_ostream &out, const std::vector &jitems); -void finalizeBuilding(DFHack::color_ostream &out, df::building *bld); +void finalizeBuilding(DFHack::color_ostream &out, df::building *bld, bool unsuspend_on_finalize); diff --git a/plugins/buildingplan/buildingplan_cycle.cpp b/plugins/buildingplan/buildingplan_cycle.cpp index f401c90a8..45cafe474 100644 --- a/plugins/buildingplan/buildingplan_cycle.cpp +++ b/plugins/buildingplan/buildingplan_cycle.cpp @@ -10,6 +10,7 @@ #include "df/building_design.h" #include "df/item.h" +#include "df/item_slabst.h" #include "df/job.h" #include "df/map_block.h" #include "df/world.h" @@ -42,13 +43,40 @@ struct BadFlags { } }; -bool itemPassesScreen(df::item * item) { +// This is tricky. we want to choose an item that can be brought to the job site, but that's not +// necessarily the same as job->pos. it could be many tiles off in any direction (e.g. for bridges), or +// up or down (e.g. for stairs). For now, just return if the item is on a walkable tile. +static bool isAccessible(color_ostream& out, df::item* item) { + df::coord item_pos = Items::getPosition(item); + df::map_block* block = Maps::getTileBlock(item_pos); + bool is_walkable = false; + if (block) { + uint16_t walkability_group = index_tile(block->walkable, item_pos); + is_walkable = walkability_group != 0; + TRACE(cycle, out).print("item %d in walkability_group %u at (%d,%d,%d) is %saccessible from job site\n", + item->id, walkability_group, item_pos.x, item_pos.y, item_pos.z, is_walkable ? "(probably) " : "not "); + } + return is_walkable; +} + +bool itemPassesScreen(color_ostream& out, df::item* item) { static const BadFlags bad_flags; return !(item->flags.whole & bad_flags.whole) - && !item->isAssignedToStockpile(); + && !item->isAssignedToStockpile() + && isAccessible(out, item); +} + +df::job_item getJobItemWithHeatSafety(const df::job_item *job_item, HeatSafety heat) { + df::job_item jitem = *job_item; + if (heat >= HEAT_SAFETY_MAGMA) { + jitem.flags2.bits.magma_safe = true; + jitem.flags2.bits.fire_safe = false; + } else if (heat == HEAT_SAFETY_FIRE && !jitem.flags2.bits.magma_safe) + jitem.flags2.bits.fire_safe = true; + return jitem; } -bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) { +bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter, const std::set &specials) { // check the properties that are not checked by Job::isSuitableItem() if (job_item->item_type > -1 && job_item->item_type != item->getType()) return false; @@ -67,12 +95,11 @@ bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety h && !item->hasToolUse(job_item->has_tool_use)) return false; - df::job_item jitem = *job_item; - if (heat == HEAT_SAFETY_MAGMA) { - jitem.flags2.bits.magma_safe = true; - jitem.flags2.bits.fire_safe = false; - } else if (heat == HEAT_SAFETY_FIRE && !jitem.flags2.bits.magma_safe) - jitem.flags2.bits.fire_safe = true; + if (item->getType() == df::item_type::SLAB && specials.count("engraved") + && static_cast(item)->engraving_type != df::slab_engraving_type::Memorial) + return false; + + df::job_item jitem = getJobItemWithHeatSafety(job_item, heat); return Job::isSuitableItem( &jitem, item->getType(), item->getSubtype()) @@ -101,7 +128,7 @@ static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) { // now all at 0, so there is no risk of having extra items attached. we don't // 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) { +void finalizeBuilding(color_ostream &out, df::building *bld, bool unsuspend_on_finalize) { DEBUG(cycle,out).print("finalizing building %d\n", bld->id); auto job = bld->jobs[0]; @@ -133,8 +160,10 @@ void finalizeBuilding(color_ostream &out, df::building *bld) { } // we're good to go! - job->flags.bits.suspend = false; - Job::checkBuildingsNow(); + if (unsuspend_on_finalize) { + job->flags.bits.suspend = false; + Job::checkBuildingsNow(); + } } static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue, @@ -153,25 +182,10 @@ static df::building * popInvalidTasks(color_ostream &out, Bucket &task_queue, return NULL; } -// This is tricky. we want to choose an item that can be brought to the job site, but that's not -// necessarily the same as job->pos. it could be many tiles off in any direction (e.g. for bridges), or -// up or down (e.g. for stairs). For now, just return if the item is on a walkable tile. -static bool isAccessibleFrom(color_ostream &out, df::item *item, df::job *job) { - df::coord item_pos = Items::getPosition(item); - df::map_block *block = Maps::getTileBlock(item_pos); - bool is_walkable = false; - if (block) { - uint16_t walkability_group = index_tile(block->walkable, item_pos); - is_walkable = walkability_group != 0; - TRACE(cycle,out).print("item %d in walkability_group %u at (%d,%d,%d) is %saccessible from job site\n", - item->id, walkability_group, item_pos.x, item_pos.y, item_pos.z, is_walkable ? "" : "not "); - } - return is_walkable; -} - static void doVector(color_ostream &out, df::job_item_vector_id vector_id, map &buckets, - unordered_map &planned_buildings) { + unordered_map &planned_buildings, + bool unsuspend_on_finalize) { auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id); 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", @@ -182,9 +196,11 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, item_it != item_vector.rend(); ++item_it) { auto item = *item_it; - if (!itemPassesScreen(item)) + if (!itemPassesScreen(out, item)) continue; 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()); auto & task_queue = bucket_it->second; auto bld = popInvalidTasks(out, task_queue, planned_buildings); if (!bld) { @@ -198,11 +214,13 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, auto & task = task_queue.front(); auto id = task.first; auto job = bld->jobs[0]; + auto & jitems = job->job_items; + const size_t num_filters = jitems.size(); auto filter_idx = task.second; + const int rev_filter_idx = num_filters - (filter_idx+1); auto &pb = planned_buildings.at(id); - if (isAccessibleFrom(out, item, job) - && matchesFilters(item, job->job_items[filter_idx], pb.heat_safety, - pb.item_filters[filter_idx]) + if (matchesFilters(item, jitems[filter_idx], pb.heat_safety, + pb.item_filters[rev_filter_idx], pb.specials) && Job::attachJobItem(job, item, df::job_item_ref::Hauled, filter_idx)) { @@ -221,10 +239,10 @@ static void doVector(color_ostream &out, df::job_item_vector_id vector_id, // keep quantity aligned with the actual number of remaining // items so if buildingplan is turned off, the building will // be completed with the correct number of items. - --job->job_items[filter_idx]->quantity; + --jitems[filter_idx]->quantity; task_queue.pop_front(); - if (isJobReady(out, job->job_items)) { - finalizeBuilding(out, bld); + if (isJobReady(out, jitems)) { + finalizeBuilding(out, bld, unsuspend_on_finalize); planned_buildings.at(id).remove(out); } if (task_queue.empty()) { @@ -259,7 +277,7 @@ struct VectorsToScanLast { }; void buildingplan_cycle(color_ostream &out, Tasks &tasks, - unordered_map &planned_buildings) { + unordered_map &planned_buildings, bool unsuspend_on_finalize) { static const VectorsToScanLast vectors_to_scan_last; DEBUG(cycle,out).print( @@ -277,7 +295,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, } auto & buckets = it->second; - doVector(out, vector_id, buckets, planned_buildings); + 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(), @@ -291,7 +309,7 @@ void buildingplan_cycle(color_ostream &out, Tasks &tasks, if (tasks.count(vector_id) == 0) continue; auto & buckets = tasks[vector_id]; - doVector(out, vector_id, buckets, planned_buildings); + 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(), diff --git a/plugins/buildingplan/defaultitemfilters.cpp b/plugins/buildingplan/defaultitemfilters.cpp index 3c3b2f3a9..069459841 100644 --- a/plugins/buildingplan/defaultitemfilters.cpp +++ b/plugins/buildingplan/defaultitemfilters.cpp @@ -31,33 +31,70 @@ static int get_max_quality(const df::job_item *jitem) { return df::item_quality::Masterful; } +static string serialize(const std::vector &item_filters, const std::set &specials) { + std::ostringstream out; + out << serialize_item_filters(item_filters); + out << "|" << join_strings(",", specials); + return out.str(); +} + DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector &jitems) - : key(key) { + : key(key), choose_items(ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER) { DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n", std::get<0>(key), std::get<1>(key), std::get<2>(key)); filter_config = World::AddPersistentData(FILTER_CONFIG_KEY); set_config_val(filter_config, FILTER_CONFIG_TYPE, std::get<0>(key)); set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(key)); set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key)); + set_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose_items); item_filters.resize(jitems.size()); for (size_t idx = 0; idx < jitems.size(); ++idx) { item_filters[idx].setMaxQuality(get_max_quality(jitems[idx]), true); } - filter_config.val() = serialize_item_filters(item_filters); + filter_config.val() = serialize(item_filters, specials); } DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector &jitems) : key(getKey(filter_config)), filter_config(filter_config) { + choose_items = get_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS); + if (choose_items < ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER || + choose_items > ItemSelectionChoice::ITEM_SELECTION_CHOICE_AUTOMATERIAL) + choose_items = ItemSelectionChoice::ITEM_SELECTION_CHOICE_FILTER; auto &serialized = filter_config.val(); - DEBUG(status,out).print("deserializing item filters for key %d,%d,%d: %s\n", + DEBUG(status,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()); - std::vector filters = deserialize_item_filters(out, 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(status,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()); item_filters.resize(jitems.size()); } else item_filters = filters; + if (elems.size() > 1) { + vector specs; + split_string(&specs, elems[1], ","); + for (auto & special : specs) { + if (special.size()) + specials.emplace(special); + } + } +} + +void DefaultItemFilters::setChooseItems(int choose) { + choose_items = choose; + set_config_val(filter_config, FILTER_CONFIG_CHOOSE_ITEMS, choose); +} + +void DefaultItemFilters::setSpecial(const std::string &special, bool val) { + if (val) + specials.emplace(special); + else + specials.erase(special); + filter_config.val() = serialize(item_filters, specials); } void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) { @@ -68,7 +105,7 @@ void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFil } item_filters[index] = filter; - filter_config.val() = serialize_item_filters(item_filters); + filter_config.val() = serialize(item_filters, specials); DEBUG(status,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.val().c_str()); } diff --git a/plugins/buildingplan/defaultitemfilters.h b/plugins/buildingplan/defaultitemfilters.h index 4d1d5cbd2..7d285ce4c 100644 --- a/plugins/buildingplan/defaultitemfilters.h +++ b/plugins/buildingplan/defaultitemfilters.h @@ -14,11 +14,17 @@ public: DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector &jitems); DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector &jitems); + void setChooseItems(int choose); void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index); + void setSpecial(const std::string &special, bool val); + int getChooseItems() const { return choose_items; } const std::vector & getItemFilters() const { return item_filters; } + const std::set & getSpecials() const { return specials; } private: DFHack::PersistentDataItem filter_config; + int choose_items; std::vector item_filters; + std::set specials; }; diff --git a/plugins/buildingplan/itemfilter.cpp b/plugins/buildingplan/itemfilter.cpp index e9639f281..dac5f98d6 100644 --- a/plugins/buildingplan/itemfilter.cpp +++ b/plugins/buildingplan/itemfilter.cpp @@ -6,6 +6,7 @@ namespace DFHack { DBG_EXTERN(buildingplan, status); + DBG_EXTERN(buildingplan, cycle); } using std::set; @@ -34,7 +35,7 @@ bool ItemFilter::isEmpty() const { && materials.empty(); } -static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat_mask) { +static bool deserializeMaterialMask(const string& ser, df::dfhack_material_category& mat_mask) { if (ser.empty()) return true; @@ -45,7 +46,7 @@ static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat return true; } -static bool deserializeMaterials(string ser, set &materials) { +static bool deserializeMaterials(const string& ser, set &materials) { if (ser.empty()) return true; @@ -62,7 +63,7 @@ static bool deserializeMaterials(string ser, set &material return true; } -ItemFilter::ItemFilter(color_ostream &out, string serialized) : ItemFilter() { +ItemFilter::ItemFilter(color_ostream &out, const string& serialized) : ItemFilter() { vector tokens; split_string(&tokens, serialized, "/"); if (tokens.size() < 5) { @@ -86,11 +87,9 @@ string ItemFilter::serialize() const { std::ostringstream ser; ser << bitfield_to_string(mat_mask, ",") << "/"; vector matstrs; - if (!materials.empty()) { - for (auto &mat : materials) - matstrs.emplace_back(mat.getToken()); - ser << join_strings(",", matstrs); - } + for (auto &mat : materials) + matstrs.emplace_back(mat.getToken()); + ser << join_strings(",", matstrs); ser << "/" << static_cast(min_quality); ser << "/" << static_cast(max_quality); ser << "/" << static_cast(decorated_only); @@ -153,11 +152,19 @@ bool ItemFilter::matches(DFHack::MaterialInfo &material) const { } bool ItemFilter::matches(df::item *item) const { - if (item->getQuality() < min_quality || item->getQuality() > max_quality) + if (item->getQuality() < min_quality || item->getQuality() > max_quality) { + TRACE(cycle).print("item outside of quality range (%d not between %d and %d)\n", + item->getQuality(), min_quality, max_quality); return false; + } - if (decorated_only && !item->hasImprovements()) + if (decorated_only && !item->hasImprovements()) { + TRACE(cycle).print("item needs improvements and doesn't have any\n"); return false; + } + + if (!mat_mask.whole) + return true; auto imattype = item->getActualMaterial(); auto imatindex = item->getActualMaterialIndex(); diff --git a/plugins/buildingplan/itemfilter.h b/plugins/buildingplan/itemfilter.h index 5ae59dd4a..c74150434 100644 --- a/plugins/buildingplan/itemfilter.h +++ b/plugins/buildingplan/itemfilter.h @@ -8,7 +8,7 @@ class ItemFilter { public: ItemFilter(); - ItemFilter(DFHack::color_ostream &out, std::string serialized); + ItemFilter(DFHack::color_ostream &out, const std::string& serialized); void clear(); bool isEmpty() const; diff --git a/plugins/buildingplan/plannedbuilding.cpp b/plugins/buildingplan/plannedbuilding.cpp index a693f6fcf..a20d7b29a 100644 --- a/plugins/buildingplan/plannedbuilding.cpp +++ b/plugins/buildingplan/plannedbuilding.cpp @@ -26,9 +26,10 @@ static vector> get_vector_ids(color_ostream &out, if (!bld || bld->jobs.size() != 1) return ret; - auto &job = bld->jobs[0]; - for (auto &jitem : job->job_items) { - ret.emplace_back(getVectorIds(out, jitem)); + auto &jitems = bld->jobs[0]->job_items; + int num_job_items = (int)jitems.size(); + for (int jitem_idx = num_job_items - 1; jitem_idx >= 0; --jitem_idx) { + ret.emplace_back(getVectorIds(out, jitems[jitem_idx], false)); } return ret; } @@ -68,19 +69,35 @@ static vector get_item_filters(color_ostream &out, PersistentDataIte return deserialize_item_filters(out, rawstrs[1]); } -static string serialize(const vector> &vector_ids, const vector &item_filters) { +static set get_specials(color_ostream &out, PersistentDataItem &bld_config) { + vector rawstrs; + split_string(&rawstrs, bld_config.val(), "|"); + set ret; + if (rawstrs.size() < 3) + return ret; + vector specials; + split_string(&specials, rawstrs[2], ","); + for (auto & special : specials) + ret.emplace(special); + return ret; +} + +static string serialize(const vector> &vector_ids, const DefaultItemFilters &item_filters) { vector joined; for (auto &vec_list : vector_ids) { joined.emplace_back(join_strings(",", vec_list)); } std::ostringstream out; - out << join_strings(";", joined) << "|" << serialize_item_filters(item_filters); + out << join_strings(";", joined); + out << "|" << serialize_item_filters(item_filters.getItemFilters()); + out << "|" << join_strings(",", item_filters.getSpecials()); return out.str(); } -PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const vector &item_filters) +PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const DefaultItemFilters &item_filters) : id(bld->id), vector_ids(get_vector_ids(out, id)), heat_safety(heat), - item_filters(item_filters) { + item_filters(item_filters.getItemFilters()), + specials(item_filters.getSpecials()) { DEBUG(status,out).print("creating persistent data for building %d\n", id); bld_config = World::AddPersistentData(BLD_CONFIG_KEY); set_config_val(bld_config, BLD_CONFIG_ID, id); @@ -92,18 +109,21 @@ PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafe PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_config) : id(get_config_val(bld_config, BLD_CONFIG_ID)), vector_ids(deserialize_vector_ids(out, bld_config)), - heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)), + //heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)), // until this works + heat_safety(HEAT_SAFETY_ANY), item_filters(get_item_filters(out, bld_config)), + specials(get_specials(out, bld_config)), bld_config(bld_config) { } // Ensure the building still exists and is in a valid state. It can disappear // for lots of reasons, such as running the game with the buildingplan plugin // disabled, manually removing the building, modifying it via the API, etc. -df::building * PlannedBuilding::getBuildingIfValidOrRemoveIfNot(color_ostream &out) { +df::building * PlannedBuilding::getBuildingIfValidOrRemoveIfNot(color_ostream &out, bool skip_remove) { auto bld = df::building::find(id); bool valid = bld && bld->getBuildStage() == 0; if (!valid) { - remove(out); + if (!skip_remove) + remove(out); return NULL; } return bld; diff --git a/plugins/buildingplan/plannedbuilding.h b/plugins/buildingplan/plannedbuilding.h index 5bd09ba5a..703d4246d 100644 --- a/plugins/buildingplan/plannedbuilding.h +++ b/plugins/buildingplan/plannedbuilding.h @@ -1,6 +1,7 @@ #pragma once #include "buildingplan.h" +#include "defaultitemfilters.h" #include "itemfilter.h" #include "Core.h" @@ -21,7 +22,9 @@ public: const std::vector item_filters; - PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const std::vector &item_filters); + const std::set specials; + + PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const DefaultItemFilters &item_filters); PlannedBuilding(DFHack::color_ostream &out, DFHack::PersistentDataItem &bld_config); void remove(DFHack::color_ostream &out); @@ -29,7 +32,7 @@ public: // Ensure the building still exists and is in a valid state. It can disappear // for lots of reasons, such as running the game with the buildingplan plugin // disabled, manually removing the building, modifying it via the API, etc. - df::building * getBuildingIfValidOrRemoveIfNot(DFHack::color_ostream &out); + df::building * getBuildingIfValidOrRemoveIfNot(DFHack::color_ostream &out, bool skip_remove = false); private: DFHack::PersistentDataItem bld_config; diff --git a/plugins/design.cpp b/plugins/design.cpp new file mode 100644 index 000000000..37ee32cbd --- /dev/null +++ b/plugins/design.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include +#include +#include + +#include "ColorText.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "df/graphic_viewportst.h" +#include "df/world.h" +#include "modules/Gui.h" +#include "modules/Persistence.h" +#include "modules/Screen.h" +#include "modules/World.h" + +DFHACK_PLUGIN("design"); +using DFHack::color_value; + +REQUIRE_GLOBAL(window_x); +REQUIRE_GLOBAL(window_y); +REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(plotinfo); +using namespace DFHack; +using namespace df::enums; + +namespace DFHack { +DBG_DECLARE(design, log, DebugCategory::LINFO); +} + +DFhackCExport command_result plugin_init(color_ostream &out, + std::vector &commands) { + return CR_OK; +} + +std::map PENS; + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + DEBUG(log,out).print("clearing PENS\n"); + PENS.clear(); + } + return CR_OK; +} + +struct DrawingPoint { + uint32_t penKey = 0; + std::pair cursor_coords; + + DrawingPoint() : penKey(0), cursor_coords({-1, -1}) {} +}; + +typedef std::map> ShapeMap; +ShapeMap arr; + +bool has_point(int x, int y) { + return arr.count(x) != 0 && arr.at(x).count(y) != 0; +}; + +// Key tuple is N, W, E, S +typedef std::tuple DirectionKey; +std::map> CURSORS_MAP = { + {{false, false, false, false}, {1, 2}}, // INSIDE + {{true, true, false, false}, {0, 1}}, // NW + {{true, false, false, false}, {1, 1}}, // NORTH + {{true, false, true, false}, {2, 1}}, // NE + {{false, true, false, false}, {0, 2}}, // WEST + {{false, false, true, false}, {2, 2}}, // EAST + {{false, true, false, true}, {0, 3}}, // SW + {{false, false, false, true}, {1, 3}}, // SOUTH + {{false, false, true, true}, {2, 3}}, // SE + {{true, true, true, false}, {3, 2}}, // N_NUB + {{true, false, true, true}, {5, 1}}, // E_NUB + {{true, true, false, true}, {3, 1}}, // W_NUB + {{false, true, true, true}, {4, 2}}, // S_NUB + {{false, true, true, false}, {3, 3}}, // VERT_NS + {{true, false, false, true}, {4, 1}}, // VERT_EW + {{true, true, true, true}, {4, 3}}, // POINT +}; + +enum PenMask { + North = 0, + South, + East, + West, + DragPoint, + MouseOver, + InShape, + ExtraPoint, + NumFlags +}; + +uint32_t gen_pen_key(bool n, bool s, bool e, bool w, bool is_drag_point, + bool is_mouse_over, bool inshape, bool extra_point) { + std::bitset(PenMask::NumFlags)> ret; + ret[PenMask::North] = n; + ret[PenMask::South] = s; + ret[PenMask::East] = e; + ret[PenMask::West] = w; + ret[PenMask::DragPoint] = is_drag_point; + ret[PenMask::MouseOver] = is_mouse_over; + ret[PenMask::InShape] = inshape; + ret[PenMask::ExtraPoint] = extra_point; + + return ret.to_ulong(); +} + +Screen::Pen make_pen(const std::pair &direction, bool is_drag_point, + bool is_mouse_over, bool inshape, bool extra_point) { + color_value color = COLOR_GREEN; + int ycursor_mod = 0; + + if (!extra_point) { + if (is_drag_point) { + color = COLOR_CYAN; + ycursor_mod += 6; + if (is_mouse_over) { + color = COLOR_MAGENTA; + ycursor_mod += 3; + } + } + } else { + ycursor_mod += 15; + color = COLOR_LIGHTRED; + + if (is_mouse_over) { + color = COLOR_RED; + ycursor_mod += 3; + } + } + + Screen::Pen pen; + pen.ch = inshape ? 'X' : 'o'; + pen.fg = color; + int selected_tile_texpos = 0; + Screen::findGraphicsTile("CURSORS", direction.first, + direction.second + ycursor_mod, + &selected_tile_texpos); + pen.tile = selected_tile_texpos; + + return pen; +} + +Screen::Pen get_pen(int x, int y, ShapeMap &arr, const std::string &type = "") { + bool n = false, w = false, e = false, s = false; + if (has_point(x, y)) { + if (y == 0 || !has_point(x, y - 1)) n = true; + if (x == 0 || !has_point(x - 1, y)) w = true; + if (!has_point(x + 1, y)) e = true; // TODO check map size + if (!has_point(x, y + 1)) s = true; // TODO check map size + } + + bool is_drag_point = type == "drag_point"; + bool is_extra = type == "extra_point"; + bool is_in_shape = has_point(x, y); + auto mouse_pos = Gui::getMousePos(); + bool mouse_over = mouse_pos.x == x && mouse_pos.y == y; + + uint32_t pen_key = gen_pen_key(n, s, e, w, is_drag_point, mouse_over, + is_in_shape, is_extra); + + if (CURSORS_MAP.count({n, w, e, s}) > 0 && has_point(x, y)) { + arr[x][y].cursor_coords = CURSORS_MAP.at({n, w, e, s}); + } + + if (PENS.find(pen_key) == PENS.end()) { + std::pair cursor{-1, -1}; + + if (type != "") { + return make_pen(CURSORS_MAP.at({n, w, e, s}), is_drag_point, + mouse_over, is_in_shape, is_extra); + } + + if (CURSORS_MAP.count({n, w, e, s}) > 0) { + PENS.emplace(pen_key, + make_pen(CURSORS_MAP.at({n, w, e, s}), is_drag_point, + mouse_over, is_in_shape, is_extra)); + if (type == "" && has_point(x, y)) { + arr[x][y].penKey = pen_key; + } + } + } + + return PENS.at(pen_key); +} + +static int design_load_shape(lua_State *L) { + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int x = lua_tointeger(L, -2); + + if (lua_istable(L, -1)) { + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + int y = lua_tointeger(L, -2); + bool value = lua_toboolean(L, -1); + + if (value) { + arr[x][y] = DrawingPoint(); + } + lua_pop(L, 1); + } + } + lua_pop(L, 1); + } + } + + return 0; +} + +static int design_clear_shape(lua_State *L) { + arr.clear(); + + return 0; +} + +static int design_draw_shape(lua_State *L) { + if (arr.size() == 0) { + design_load_shape(L); + } + + for (auto x : arr) { + for (auto y : x.second) { + Screen::Pen cur_tile = Screen::readTile(x.first, y.first, true); + Screen::Pen pen = get_pen(x.first, y.first, arr); + cur_tile.tile = pen.tile; + Screen::paintTile(cur_tile, x.first - *window_x, + y.first - *window_y, true); + } + } + + return 0; +} + +static int design_draw_points(lua_State *L) { + if (lua_istable(L, -1)) { + const char *str; + lua_rawgeti(L, -1, 2); + str = lua_tostring(L, -1); + lua_pop(L, 1); + + lua_rawgeti(L, -1, 1); + int n = luaL_len(L, -1); + for (int i = 1; i <= n; i++) { + lua_rawgeti(L, -1, i); + int x, y; + lua_getfield(L, -1, "y"); + y = lua_tointeger(L, -1); + lua_getfield(L, -2, "x"); + x = lua_tointeger(L, -1); + lua_pop(L, 3); + + Screen::Pen cur_tile = Screen::readTile(x, y, true); + Screen::Pen pen = get_pen(x, y, arr, str); + cur_tile.tile = pen.tile; + Screen::paintTile(cur_tile, x - *window_x, y - *window_y, true); + } + lua_pop(L, 1); + } + + return 0; +} + +DFHACK_PLUGIN_LUA_COMMANDS{DFHACK_LUA_COMMAND(design_draw_shape), + DFHACK_LUA_COMMAND(design_draw_points), + DFHACK_LUA_COMMAND(design_clear_shape), + DFHACK_LUA_END}; diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 6227f44f5..b2af2dbca 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -6,6 +6,7 @@ #include "PluginManager.h" #include "TileTypes.h" #include "LuaTools.h" +#include "Debug.h" #include "modules/Buildings.h" #include "modules/Gui.h" @@ -14,6 +15,8 @@ #include "modules/Random.h" #include "modules/Units.h" #include "modules/World.h" +#include "modules/EventManager.h" +#include "modules/Job.h" #include #include @@ -26,12 +29,128 @@ #include #include +#include +#include +#include + DFHACK_PLUGIN("dig-now"); REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(world); +// Debugging +namespace DFHack { + DBG_DECLARE(dignow, general, DebugCategory::LINFO); + 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{ + df::coord pos; + df::tile_designation type; + df::tile_occupancy occupancy; + designation() = default; + designation(const df::coord &c, const df::tile_designation &td, const df::tile_occupancy &to) : pos(c), type(td), occupancy(to) {} + + bool operator==(const designation &rhs) const { + return pos == rhs.pos; + } + + bool operator!=(const designation &rhs) const { + return !(rhs == *this); + } +}; + +namespace std { + template<> + struct hash { + std::size_t operator()(const designation &c) const { + std::hash hash_coord; + return hash_coord(c.pos); + } + }; +} + +class DesignationJobs { +private: + std::unordered_map designations; + std::unordered_map jobs; +public: + void load(MapExtras::MapCache &map) { + designations.clear(); + DEBUG(general).print("DesignationJobs: reading jobs list\n"); + df::job_list_link* node = df::global::world->jobs.list.next; + while (node) { + df::job* job = node->item; + node = node->next; + + if(!job || !Maps::isValidTilePos(job->pos)) + continue; + + df::tile_designation td = map.designationAt(job->pos); + df::tile_occupancy to = map.occupancyAt(job->pos); + const auto ctd = td.whole; + const auto cto = to.whole; + switch (job->job_type){ + case job_type::Dig: + td.bits.dig = tile_dig_designation::Default; + break; + case job_type::DigChannel: + td.bits.dig = tile_dig_designation::Channel; + break; + case job_type::CarveRamp: + td.bits.dig = tile_dig_designation::Ramp; + break; + case job_type::CarveUpwardStaircase: + td.bits.dig = tile_dig_designation::UpStair; + break; + case job_type::CarveDownwardStaircase: + td.bits.dig = tile_dig_designation::DownStair; + break; + case job_type::CarveUpDownStaircase: + td.bits.dig = tile_dig_designation::UpDownStair; + break; + case job_type::SmoothWall: + case job_type::SmoothFloor: + td.bits.smooth = 1; + break; + case job_type::CarveTrack: + to.bits.carve_track_north = (job->item_category.whole >> 18) & 1; + to.bits.carve_track_south = (job->item_category.whole >> 19) & 1; + to.bits.carve_track_west = (job->item_category.whole >> 20) & 1; + to.bits.carve_track_east = (job->item_category.whole >> 21) & 1; + break; + default: + break; + } + if (ctd != td.whole || cto != to.whole) { + // we found a designation job + designations.emplace(job->pos, designation(job->pos, td, to)); + jobs.emplace(job->pos, job); + } + } + DEBUG(general).print("DesignationJobs: DONE reading jobs list\n"); + } + void remove(const df::coord &pos) { + if(jobs.count(pos)) { + Job::removeJob(jobs[pos]); + jobs.erase(pos); + } + } + designation get(const df::coord &pos) { + if (designations.count(pos)) { + return designations[pos]; + } + return {}; + } + bool count(const df::coord &pos) { + return jobs.count(pos); + } +}; + struct boulder_percent_options { // percent chance ([0..100]) for creating a boulder for the given rock type uint32_t layer; @@ -320,8 +439,19 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, std::vector &dug_tiles) { df::tiletype tt = map.tiletypeAt(pos); - if (!is_diggable(map, pos, tt)) + if (!is_diggable(map, pos, tt)) { + DEBUG(general).print("dig_tile: not diggable\n"); return false; + } + + /** The algorithm process seems to be: + * for each tile + * check for a designation + * if a designation exists send it to dig_tile + * + * dig_tile (below) then digs the layer below the channel designated tile + * thereby changing it and causing its designation to be lost + * */ df::tiletype target_type = df::tiletype::Void; switch(designation) { @@ -341,19 +471,22 @@ 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)); target_type = df::tiletype::OpenSpace; DFCoord pos_above(pos.x, pos.y, pos.z+1); - if (map.ensureBlockAt(pos_above)) + if (map.ensureBlockAt(pos_above)) { remove_ramp_top(map, pos_above); - df::tile_dig_designation td_below = - map.designationAt(pos_below).bits.dig; - if (dig_tile(out, map, pos_below, - df::tile_dig_designation::Ramp, dug_tiles)) { + } + df::tile_dig_designation td_below = map.designationAt(pos_below).bits.dig; + if (dig_tile(out, map, pos_below, df::tile_dig_designation::Ramp, dug_tiles)) { clean_ramps(map, pos_below); - if (td_below == df::tile_dig_designation::Default) + if (td_below == df::tile_dig_designation::Default) { dig_tile(out, map, pos_below, td_below, dug_tiles); + } return true; } + } else { + DEBUG(channels).print("dig_tile: failed to channel at (" COORD ") [can_dig_channel: false]\n", COORDARGS(pos_below)); } break; } @@ -407,7 +540,8 @@ static bool dig_tile(color_ostream &out, MapExtras::MapCache &map, if (target_type == df::tiletype::Void || target_type == tt) return false; - dug_tiles.push_back(dug_tile_info(map, pos)); + dug_tiles.emplace_back(map, pos); + TRACE(general).print("dig_tile: digging the designation tile at (" COORD ")\n",COORDARGS(pos)); dig_type(map, pos, target_type); // let light filter down to newly exposed tiles @@ -594,9 +728,14 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, item_coords_t &item_coords, const dig_now_options &options) { MapExtras::MapCache map; Random::MersenneRNG rng; + DesignationJobs jobs; + DEBUG(general).print("do_dig(): starting..\n"); + jobs.load(map); rng.init(); + DEBUG(general).print("do_dig(): reading map..\n"); + std::unordered_set buffer; // go down levels instead of up so stacked ramps behave as expected for (int16_t z = options.end.z; z >= options.start.z; --z) { for (int16_t y = options.start.y; y <= options.end.y; ++y) { @@ -609,49 +748,73 @@ static void do_dig(color_ostream &out, std::vector &dug_coords, DFCoord pos(x, y, z); df::tile_designation td = map.designationAt(pos); df::tile_occupancy to = map.occupancyAt(pos); - if (td.bits.dig != df::tile_dig_designation::No && - !to.bits.dig_marked) { - std::vector dug_tiles; - if (dig_tile(out, map, pos, td.bits.dig, dug_tiles)) { - for (auto info : dug_tiles) { - td = map.designationAt(info.pos); - td.bits.dig = df::tile_dig_designation::No; - map.setDesignationAt(info.pos, td); - - dug_coords.push_back(info.pos); - refresh_adjacent_smooth_walls(map, info.pos); - if (info.imat < 0) - continue; - if (produces_item(options.boulder_percents, - map, rng, info)) { - auto k = std::make_pair(info.itype, info.imat); - item_coords[k].push_back(info.pos); - } - } - } - } else if (td.bits.smooth == 1) { - if (smooth_tile(out, map, pos)) { - td = map.designationAt(pos); - td.bits.smooth = 0; - map.setDesignationAt(pos, td); - } - } else if (to.bits.carve_track_north == 1 - || to.bits.carve_track_east == 1 - || to.bits.carve_track_south == 1 - || to.bits.carve_track_west == 1) { - if (carve_tile(map, pos, to)) { - to = map.occupancyAt(pos); - to.bits.carve_track_north = 0; - to.bits.carve_track_east = 0; - to.bits.carve_track_south = 0; - to.bits.carve_track_west = 0; - map.setOccupancyAt(pos, to); + if (jobs.count(pos)) { + buffer.emplace(jobs.get(pos)); + jobs.remove(pos); + // if it does get removed, then we're gonna buffer the jobs info then remove the job + } else if ((td.bits.dig != df::tile_dig_designation::No && !to.bits.dig_marked) + || td.bits.smooth == 1 + || to.bits.carve_track_north == 1 + || to.bits.carve_track_east == 1 + || to.bits.carve_track_south == 1 + || to.bits.carve_track_west == 1) { + + // we're only buffering designations, so that processing doesn't affect what we're buffering + buffer.emplace(pos, td, to); + } + } + } + } + + DEBUG(general).print("do_dig(): processing designations..\n"); + // process designations + for(auto &d : buffer) { + auto pos = d.pos; + auto td = d.type; + auto to = d.occupancy; + + if (td.bits.dig != df::tile_dig_designation::No && !to.bits.dig_marked) { + std::vector dug_tiles; + + if (dig_tile(out, map, pos, td.bits.dig, dug_tiles)) { + for (auto info: dug_tiles) { + td = map.designationAt(info.pos); + td.bits.dig = df::tile_dig_designation::No; + map.setDesignationAt(info.pos, td); + + dug_coords.push_back(info.pos); + refresh_adjacent_smooth_walls(map, info.pos); + if (info.imat < 0) + continue; + if (produces_item(options.boulder_percents, + map, rng, info)) { + auto k = std::make_pair(info.itype, info.imat); + item_coords[k].push_back(info.pos); } } } + } else if (td.bits.smooth == 1) { + if (smooth_tile(out, map, pos)) { + td = map.designationAt(pos); + td.bits.smooth = 0; + map.setDesignationAt(pos, td); + } + } else if (to.bits.carve_track_north == 1 + || to.bits.carve_track_east == 1 + || to.bits.carve_track_south == 1 + || to.bits.carve_track_west == 1) { + if (carve_tile(map, pos, to)) { + to = map.occupancyAt(pos); + to.bits.carve_track_north = 0; + to.bits.carve_track_east = 0; + to.bits.carve_track_south = 0; + to.bits.carve_track_west = 0; + map.setOccupancyAt(pos, to); + } } } + DEBUG(general).print("do_dig(): write changes to map..\n"); map.WriteAll(); } diff --git a/plugins/faststart.cpp b/plugins/faststart.cpp new file mode 100644 index 000000000..de014801c --- /dev/null +++ b/plugins/faststart.cpp @@ -0,0 +1,69 @@ +// Fast Startup tweak + +#include "Core.h" +#include +#include +#include +#include +#include + +#include "df/viewscreen_initial_prepst.h" +#include + +using namespace DFHack; +using namespace df::enums; +using std::vector; + +// Uncomment this to make the Loading screen as fast as possible +// This has the side effect of removing the dwarf face animation +// (and briefly making the game become unresponsive) + +//#define REALLY_FAST + +DFHACK_PLUGIN("faststart"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +struct prep_hook : df::viewscreen_initial_prepst +{ + typedef df::viewscreen_initial_prepst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) + { +#ifdef REALLY_FAST + while (breakdown_level != interface_breakdown_types::STOPSCREEN) + { + render_count++; + INTERPOSE_NEXT(logic)(); + } +#else + render_count = 4; + INTERPOSE_NEXT(logic)(); +#endif + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(prep_hook, logic); + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) +{ + if (enable != is_enabled) + { + if (!INTERPOSE_HOOK(prep_hook, logic).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + INTERPOSE_HOOK(prep_hook, logic).remove(); + return CR_OK; +} diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 565316fa6..87f1c0e76 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -1,13 +1,6 @@ -// (un)designate matching plants for gathering/cutting -#include - -#include "Core.h" -#include "Console.h" -#include "Export.h" +#include "Debug.h" #include "PluginManager.h" -#include "DataDefs.h" #include "TileTypes.h" -#include "MiscUtils.h" #include "df/map_block.h" #include "df/map_block_column.h" @@ -32,6 +25,11 @@ using std::set; using namespace DFHack; using namespace df::enums; +namespace DFHack +{ +DBG_DECLARE(getplants, log, DebugCategory::LINFO); +} + DFHACK_PLUGIN("getplants"); REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(world); @@ -75,49 +73,38 @@ enum class selectability { // is one of the issues in bug 6940 on the bug tracker (the others cases are detected and // 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) -selectability selectablePlant(const df::plant_raw *plant, bool farming) -{ +selectability selectablePlant(color_ostream& out, const df::plant_raw* plant, bool farming) { 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)) - { -// out.print("%s is a selectable tree\n", plant->id.c_str()); - if (farming) - { + if (plant->flags.is_set(plant_raw_flags::TREE)) { + DEBUG(log, out).print("%s is a selectable tree\n", plant->id.c_str()); + if (farming) { return selectability::Nonselectable; } - else - { + else { return selectability::Selectable; } } - else if (plant->flags.is_set(plant_raw_flags::GRASS)) - { -// out.print("%s is a non selectable Grass\n", plant->id.c_str()); + 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()); return selectability::Grass; } - if (farming && plant->material_defs.type[plant_material_def::seed] == -1) - { + if (farming && plant->material_defs.type[plant_material_def::seed] == -1) { return selectability::Nonselectable; } if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || - basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) - { -// out.print("%s is edible\n", plant->id.c_str()); - if (farming) - { - if (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()); + if (farming) { + if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW)) { result = selectability::Selectable; } } - else - { + else { return selectability::Selectable; } } @@ -126,54 +113,43 @@ selectability selectablePlant(const df::plant_raw *plant, bool farming) plant->flags.is_set(plant_raw_flags::MILL) || 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)) - { -// out.print("%s is thread/mill/extract\n", plant->id.c_str()); - if (farming) - { + plant->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) { + DEBUG(log, out).print("%s is thread/mill/extract\n", plant->id.c_str()); + if (farming) { result = selectability::Selectable; } - else - { + else { return selectability::Selectable; } } if (basic_mat.material->reaction_product.id.size() > 0 || - basic_mat.material->reaction_class.size() > 0) - { -// out.print("%s has a reaction\n", plant->id.c_str()); - if (farming) - { + basic_mat.material->reaction_class.size() > 0) { + DEBUG(log, out).print("%s has a reaction\n", plant->id.c_str()); + if (farming) { result = selectability::Selectable; } - else - { + else { return selectability::Selectable; } } - for (size_t i = 0; i < plant->growths.size(); i++) - { + for (size_t i = 0; i < plant->growths.size(); i++) { if (plant->growths[i]->item_type == df::item_type::SEEDS || // Only trees have seed growths in vanilla, but raws can be modded... - plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) - { + plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) { const DFHack::MaterialInfo growth_mat = DFHack::MaterialInfo(plant->growths[i]->mat_type, plant->growths[i]->mat_index); if ((plant->growths[i]->item_type == df::item_type::SEEDS && - (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::EDIBLE_COOKED) || + growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW))) || (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH && - growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... + growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... { bool seedSource = plant->growths[i]->item_type == df::item_type::SEEDS; - if (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) - { - for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++) - { + if (plant->growths[i]->item_type == df::item_type::PLANT_GROWTH) { + for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++) { if (growth_mat.material->reaction_product.material.mat_type[k] == plant->material_defs.type[plant_material_def::seed] && - growth_mat.material->reaction_product.material.mat_index[k] == plant->material_defs.idx[plant_material_def::seed]) - { + growth_mat.material->reaction_product.material.mat_index[k] == plant->material_defs.idx[plant_material_def::seed]) { seedSource = true; break; } @@ -182,52 +158,46 @@ selectability selectablePlant(const df::plant_raw *plant, bool farming) if (*cur_year_tick >= plant->growths[i]->timing_1 && (plant->growths[i]->timing_2 == -1 || - *cur_year_tick <= plant->growths[i]->timing_2)) - { -// out.print("%s has an edible seed or a stockpile growth\n", plant->id.c_str()); - if (!farming || seedSource) - { + *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()); + if (!farming || seedSource) { return selectability::Selectable; } } - else - { - if (!farming || seedSource) - { + else { + if (!farming || seedSource) { outOfSeason = true; } } } } -/* else if (plant->growths[i]->behavior.bits.has_seed) // This code designates beans, etc. when DF doesn't, but plant gatherers still fail to collect anything, so it's useless: bug #0006940. - { - const DFHack::MaterialInfo seed_mat = DFHack::MaterialInfo(plant->material_defs.type[plant_material_def::seed], plant->material_defs.idx[plant_material_def::seed]); - - if (seed_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || - seed_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) - { - if (*cur_year_tick >= plant->growths[i]->timing_1 && - (plant->growths[i]->timing_2 == -1 || - *cur_year_tick <= plant->growths[i]->timing_2)) + /* else if (plant->growths[i]->behavior.bits.has_seed) // This code designates beans, etc. when DF doesn't, but plant gatherers still fail to collect anything, so it's useless: bug #0006940. { - return selectability::Selectable; - } - else - { - outOfSeason = true; - } - } - } */ + const DFHack::MaterialInfo seed_mat = DFHack::MaterialInfo(plant->material_defs.type[plant_material_def::seed], plant->material_defs.idx[plant_material_def::seed]); + + if (seed_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || + seed_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) + { + if (*cur_year_tick >= plant->growths[i]->timing_1 && + (plant->growths[i]->timing_2 == -1 || + *cur_year_tick <= plant->growths[i]->timing_2)) + { + return selectability::Selectable; + } + else + { + outOfSeason = true; + } + } + } */ } - if (outOfSeason) - { -// out.print("%s has an out of season growth\n", plant->id.c_str()); + if (outOfSeason) { + DEBUG(log, out).print("%s has an out of season growth\n", plant->id.c_str()); return selectability::OutOfSeason; } - else - { -// out.printerr("%s cannot be gathered\n", plant->id.c_str()); + else { + DEBUG(log, out).print("%s cannot be gathered\n", plant->id.c_str()); return result; } } @@ -241,17 +211,17 @@ bool ripe(int32_t x, int32_t y, int32_t start, int32_t end) { } // Looks in the picked growths vector to see if a matching growth has been marked as picked. -bool picked(const df::plant *plant, int32_t growth_subtype) { - df::world_data *world_data = world->world_data; - df::world_site *site = df::world_site::find(plotinfo->site_id); +bool picked(const df::plant* plant, int32_t growth_subtype) { + df::world_data* world_data = world->world_data; + df::world_site* site = df::world_site::find(plotinfo->site_id); int32_t pos_x = site->global_min_x + plant->pos.x / 48; int32_t pos_y = site->global_min_y + plant->pos.y / 48; size_t id = pos_x + pos_y * 16 * world_data->world_width; - df::world_object_data *object_data = df::world_object_data::find(id); + df::world_object_data* object_data = df::world_object_data::find(id); if (!object_data) { return false; } - df::map_block_column *column = world->map.map_block_columns[(plant->pos.x / 16) * world->map.x_count_block + (plant->pos.y / 16)]; + df::map_block_column* column = world->map.map_block_columns[(plant->pos.x / 16) * world->map.x_count_block + (plant->pos.y / 16)]; for (size_t i = 0; i < object_data->picked_growths.x.size(); i++) { if (object_data->picked_growths.x[i] == plant->pos.x && @@ -266,13 +236,20 @@ bool picked(const df::plant *plant, int32_t growth_subtype) { return false; } -bool designate(const df::plant *plant, bool farming) { - df::plant_raw *plant_raw = world->raws.plants.all[plant->material]; +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); + + if (!farming) { + bool istree = (tileMaterial(Maps::getTileBlock(plant->pos)->tiletype[plant->pos.x % 16][plant->pos.y % 16]) == tiletype_material::TREE); + if (istree) + return Designations::markPlant(plant); + } + + df::plant_raw* plant_raw = world->raws.plants.all[plant->material]; const DFHack::MaterialInfo basic_mat = DFHack::MaterialInfo(plant_raw->material_defs.type[plant_material_def::basic_mat], plant_raw->material_defs.idx[plant_material_def::basic_mat]); if (basic_mat.material->flags.is_set(material_flags::EDIBLE_RAW) || - basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) - { + basic_mat.material->flags.is_set(material_flags::EDIBLE_COOKED)) { return Designations::markPlant(plant); } @@ -280,63 +257,60 @@ bool designate(const df::plant *plant, bool farming) { plant_raw->flags.is_set(plant_raw_flags::MILL) || plant_raw->flags.is_set(plant_raw_flags::EXTRACT_VIAL) || plant_raw->flags.is_set(plant_raw_flags::EXTRACT_BARREL) || - plant_raw->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) - { + plant_raw->flags.is_set(plant_raw_flags::EXTRACT_STILL_VIAL)) { if (!farming) { return Designations::markPlant(plant); } } if (basic_mat.material->reaction_product.id.size() > 0 || - basic_mat.material->reaction_class.size() > 0) - { + basic_mat.material->reaction_class.size() > 0) { if (!farming) { return Designations::markPlant(plant); } } - for (size_t i = 0; i < plant_raw->growths.size(); i++) - { - if (plant_raw->growths[i]->item_type == df::item_type::SEEDS || // Only trees have seed growths in vanilla, but raws can be modded... - plant_raw->growths[i]->item_type == df::item_type::PLANT_GROWTH) - { - const DFHack::MaterialInfo growth_mat = DFHack::MaterialInfo(plant_raw->growths[i]->mat_type, plant_raw->growths[i]->mat_index); - if ((plant_raw->growths[i]->item_type == df::item_type::SEEDS && - (growth_mat.material->flags.is_set(material_flags::EDIBLE_COOKED) || - growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW))) || - (plant_raw->growths[i]->item_type == df::item_type::PLANT_GROWTH && - growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... - { - bool seedSource = plant_raw->growths[i]->item_type == df::item_type::SEEDS; + 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); + // 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; - if (plant_raw->growths[i]->item_type == df::item_type::PLANT_GROWTH) - { - for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++) - { - if (growth_mat.material->reaction_product.material.mat_type[k] == plant_raw->material_defs.type[plant_material_def::seed] && - growth_mat.material->reaction_product.material.mat_index[k] == plant_raw->material_defs.idx[plant_material_def::seed]) - { - seedSource = true; - break; - } - } - } + 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", + 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::LEAF_MAT)); + if (!(plant_raw->growths[i]->item_type == df::item_type::SEEDS && + (growth_mat.material->flags.is_set(material_flags::EDIBLE_COOKED) || + growth_mat.material->flags.is_set(material_flags::EDIBLE_RAW))) && + !(plant_raw->growths[i]->item_type == df::item_type::PLANT_GROWTH && + growth_mat.material->flags.is_set(material_flags::LEAF_MAT))) // Will change name to STOCKPILE_PLANT_GROWTH any day now... + continue; - bool istree = (tileMaterial(Maps::getTileBlock(plant->pos)->tiletype[plant->pos.x % 16][plant->pos.y % 16]) == tiletype_material::TREE); - bool isripe = ripe(plant->pos.x, plant->pos.y, plant_raw->growths[i]->timing_1, plant_raw->growths[i]->timing_2); - if ((!farming || seedSource) && (istree || isripe) && !picked(plant, i)) - { - return Designations::markPlant(plant); + bool seedSource = plant_raw->growths[i]->item_type == df::item_type::SEEDS; + + if (plant_raw->growths[i]->item_type == df::item_type::PLANT_GROWTH) { + for (size_t k = 0; growth_mat.material->reaction_product.material.mat_type.size(); k++) { + if (growth_mat.material->reaction_product.material.mat_type[k] == plant_raw->material_defs.type[plant_material_def::seed] && + growth_mat.material->reaction_product.material.mat_index[k] == plant_raw->material_defs.idx[plant_material_def::seed]) { + seedSource = true; + break; } } } + + if ((!farming || seedSource) && + ripe(plant->pos.x, plant->pos.y, plant_raw->growths[i]->timing_1, plant_raw->growths[i]->timing_2) && + !picked(plant, i)) + return Designations::markPlant(plant); } return false; } -command_result df_getplants (color_ostream &out, vector & parameters) -{ +command_result df_getplants(color_ostream& out, vector & parameters) { string plantMatStr = ""; std::vector plantSelections; std::vector collectionCount; @@ -348,16 +322,14 @@ command_result df_getplants (color_ostream &out, vector & parameters) plantSelections.resize(world->raws.plants.all.size()); collectionCount.resize(world->raws.plants.all.size()); - for (size_t i = 0; i < plantSelections.size(); i++) - { + for (size_t i = 0; i < plantSelections.size(); i++) { plantSelections[i] = selectability::Unselected; collectionCount[i] = 0; } bool anyPlantsSelected = false; - for (size_t i = 0; i < parameters.size(); i++) - { + for (size_t i = 0; i < parameters.size(); i++) { if (parameters[i] == "help" || parameters[i] == "?") return CR_WRONG_USAGE; else if (parameters[i] == "-t") @@ -374,23 +346,18 @@ command_result df_getplants (color_ostream &out, vector & parameters) verbose = true; else if (parameters[i] == "-f") farming = true; - else if (parameters[i] == "-n") - { - if (parameters.size() > i + 1) - { + else if (parameters[i] == "-n") { + if (parameters.size() > i + 1) { maxCount = atoi(parameters[i + 1].c_str()); - if (maxCount >= 1) - { + if (maxCount >= 1) { i++; // We've consumed the next parameter, so we need to progress the iterator. } - else - { + else { out.printerr("-n requires a positive integer parameter!\n"); return CR_WRONG_USAGE; } } - else - { + else { out.printerr("-n requires a positive integer parameter!\n"); return CR_WRONG_USAGE; } @@ -398,55 +365,43 @@ command_result df_getplants (color_ostream &out, vector & parameters) else plantNames.insert(toUpper(parameters[i])); } - if (treesonly && shrubsonly) - { + if (treesonly && shrubsonly) { out.printerr("Cannot specify both -t and -s at the same time!\n"); return CR_WRONG_USAGE; } - if (treesonly && farming) - { + if (treesonly && farming) { out.printerr("Cannot specify both -t and -f at the same time!\n"); return CR_WRONG_USAGE; } - if (all && exclude) - { + if (all && exclude) { out.printerr("Cannot specify both -a and -x at the same time!\n"); return CR_WRONG_USAGE; } - if (all && plantNames.size()) - { + if (all && plantNames.size()) { out.printerr("Cannot specify -a along with plant IDs!\n"); return CR_WRONG_USAGE; } CoreSuspender suspend; - for (size_t i = 0; i < world->raws.plants.all.size(); i++) - { - df::plant_raw *plant = world->raws.plants.all[i]; - if (all) - { -// plantSelections[i] = selectablePlant(out, plant, farming); - plantSelections[i] = selectablePlant(plant, farming); + for (size_t i = 0; i < world->raws.plants.all.size(); i++) { + df::plant_raw* plant = world->raws.plants.all[i]; + if (all) { + plantSelections[i] = selectablePlant(out, plant, farming); } - else if (plantNames.find(plant->id) != plantNames.end()) - { + else if (plantNames.find(plant->id) != plantNames.end()) { plantNames.erase(plant->id); -// plantSelections[i] = selectablePlant(out, plant, farming); - plantSelections[i] = selectablePlant(plant, farming); - switch (plantSelections[i]) - { + 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()); break; case selectability::Nonselectable: - if (farming) - { + if (farming) { out.printerr("%s does not have any parts that can be gathered for seeds for farming\n", plant->id.c_str()); } - else - { + else { out.printerr("%s does not have any parts that can be gathered\n", plant->id.c_str()); } break; @@ -463,8 +418,7 @@ command_result df_getplants (color_ostream &out, vector & parameters) } } } - if (plantNames.size() > 0) - { + 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()); @@ -472,33 +426,26 @@ command_result df_getplants (color_ostream &out, vector & parameters) return CR_FAILURE; } - for (size_t i = 0; i < plantSelections.size(); i++) - { + for (size_t i = 0; i < plantSelections.size(); i++) { if (plantSelections[i] == selectability::OutOfSeason || - plantSelections[i] == selectability::Selectable) - { + plantSelections[i] == selectability::Selectable) { anyPlantsSelected = true; break; } } - if (!anyPlantsSelected) - { + if (!anyPlantsSelected) { out.print("Valid plant IDs:\n"); - for (size_t i = 0; i < world->raws.plants.all.size(); i++) - { - df::plant_raw *plant = world->raws.plants.all[i]; -// switch (selectablePlant(out, plant, farming)) - switch (selectablePlant(plant, farming)) - { + for (size_t i = 0; i < world->raws.plants.all.size(); i++) { + df::plant_raw* plant = world->raws.plants.all[i]; + switch (selectablePlant(out, plant, farming)) { case selectability::Grass: case selectability::Nonselectable: continue; case selectability::OutOfSeason: { - if (!treesonly) - { + if (!treesonly) { out.print("* (shrub) %s - %s is out of season\n", plant->id.c_str(), plant->name.c_str()); } break; @@ -523,22 +470,21 @@ 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]; - df::map_block *cur = Maps::getTileBlock(plant->pos); + for (size_t i = 0; i < world->plants.all.size(); i++) { + const df::plant* plant = world->plants.all[i]; + 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); int x = plant->pos.x % 16; int y = plant->pos.y % 16; if (plantSelections[plant->material] == selectability::OutOfSeason || - plantSelections[plant->material] == selectability::Selectable) - { + plantSelections[plant->material] == selectability::Selectable) { if (exclude || plantSelections[plant->material] == selectability::OutOfSeason) continue; } - else - { + else { if (!exclude) continue; } @@ -553,37 +499,29 @@ command_result df_getplants (color_ostream &out, vector & parameters) continue; if (collectionCount[plant->material] >= maxCount) continue; - if (deselect && Designations::unmarkPlant(plant)) - { + if (deselect && Designations::unmarkPlant(plant)) { collectionCount[plant->material]++; ++count; } - if (!deselect && designate(plant, farming)) - { -// 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); + 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]++; ++count; } } - if (count) - { - if (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("\n"); + 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("\n"); } out.print("Updated %d plant designations.\n", (int)count); return CR_OK; } -DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) -{ +DFhackCExport command_result plugin_init(color_ostream& out, vector & commands) { commands.push_back(PluginCommand( "getplants", "Designate trees for chopping and shrubs for gathering.", @@ -591,7 +529,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector find(":"); // colons at location 0 are for commands like ":lua" if (colon_pos == string::npos || colon_pos == 0) { - add_binding_if_valid(sym, *invoke_cmd, screen, filtermenu); + add_binding_if_valid(out, sym, *invoke_cmd, screen, filtermenu); } else { vector tokens; @@ -111,7 +131,7 @@ static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) { string focus = tokens[0].substr(1); if(Gui::matchFocusString(focus)) { auto cmdline = trim(tokens[1]); - add_binding_if_valid(sym, cmdline, screen, filtermenu); + add_binding_if_valid(out, sym, cmdline, screen, filtermenu); } } } @@ -124,7 +144,10 @@ static void find_active_keybindings(df::viewscreen *screen, bool filtermenu) { } static int getHotkeys(lua_State *L) { - find_active_keybindings(Gui::getCurViewscreen(true), true); + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + find_active_keybindings(*out, Gui::getCurViewscreen(true), true); Lua::PushVector(L, sorted_keys); Lua::Push(L, current_bindings); return 2; @@ -140,7 +163,7 @@ static void list(color_ostream &out) { DEBUG(log).print("listing active hotkeys\n"); bool was_valid = valid; if (!valid) - find_active_keybindings(Gui::getCurViewscreen(true), false); + 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()); @@ -176,6 +199,8 @@ static command_result hotkeys_cmd(color_ostream &out, vector & paramete return Core::getInstance().runCommand(out, INVOKE_MENU_COMMAND ); } + CoreSuspender guard; + if (parameters[0] == "list") { list(out); return CR_OK; @@ -185,8 +210,6 @@ static command_result hotkeys_cmd(color_ostream &out, vector & paramete if (parameters.size() != 2 || parameters[0] != "invoke") return CR_WRONG_USAGE; - CoreSuspender guard; - int index = string_to_int(parameters[1], -1); if (index < 0) return CR_WRONG_USAGE; diff --git a/plugins/lua/autolabor.lua b/plugins/lua/autolabor.lua index 6ef4d7279..a2c0e88cd 100644 --- a/plugins/lua/autolabor.lua +++ b/plugins/lua/autolabor.lua @@ -8,7 +8,7 @@ AutolaborOverlay = defclass(AutolaborOverlay, overlay.OverlayWidget) AutolaborOverlay.ATTRS{ default_pos={x=7,y=-13}, default_enabled=true, - viewscreens='dwarfmode/Info/LABOR', + viewscreens='dwarfmode/Info/LABOR/WORK_DETAILS', frame={w=29, h=5}, frame_style=gui.MEDIUM_FRAME, frame_background=gui.CLEAR_PEN, @@ -18,9 +18,20 @@ function AutolaborOverlay:init() self:addviews{ widgets.Label{ frame={t=0, l=0}, - text_pen=COLOR_RED, + text_pen=COLOR_LIGHTRED, + text='DFHack autolabor is active!', + visible=isEnabled, + }, + widgets.Label{ + frame={t=0, l=0}, + text_pen=COLOR_LIGHTRED, + text='Dwarf Therapist is active!', + visible=function() return not isEnabled() end, + }, + widgets.Label{ + frame={t=1, l=0}, + text_pen=COLOR_WHITE, text={ - 'DFHack autolabor is active!', NEWLINE, 'Any changes made on this', NEWLINE, 'screen will have no effect.' }, @@ -29,7 +40,7 @@ function AutolaborOverlay:init() end function AutolaborOverlay:render(dc) - if not isEnabled() then return false end + if df.global.game.external_flag ~= 1 then return end AutolaborOverlay.super.render(self, dc) end diff --git a/plugins/lua/automaterial.lua b/plugins/lua/automaterial.lua deleted file mode 100644 index 1cd7e9faf..000000000 --- a/plugins/lua/automaterial.lua +++ /dev/null @@ -1,23 +0,0 @@ -local _ENV = mkmodule('plugins.automaterial') - -local buildingplan = require('plugins.buildingplan') - --- construct the building and register it with buildingplan for item selection -function build_with_buildingplan_box_select(subtype, x, y, z) - local pos = xyz2pos(x, y, z) - local bld, err = dfhack.buildings.constructBuilding{ - type=df.building_type.Construction, subtype=subtype, pos=pos} - -- it's not a user error if we can't place a building here; just indicate - -- that no building was placed by returning false. - if err then return false end - buildingplan.addPlannedBuilding(bld) - return true -end - -function build_with_buildingplan_ui() - for _,bld in ipairs(buildingplan.construct_buildings_from_ui_state()) do - buildingplan.addPlannedBuilding(bld) - end -end - -return _ENV diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 718b211e8..375cfc1b4 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -206,7 +206,7 @@ end -- returns the name of the output file for the given context function get_filename(opts, phase, ordinal) - local fullname = 'blueprints/' .. opts.name + local fullname = 'dfhack-config/blueprints/' .. opts.name local _,_,basename = fullname:find('/([^/]+)/?$') if not basename then -- should not happen since opts.name should already be validated diff --git a/plugins/lua/buildingplan.lua b/plugins/lua/buildingplan.lua index a666103e5..d64317eb0 100644 --- a/plugins/lua/buildingplan.lua +++ b/plugins/lua/buildingplan.lua @@ -2,7 +2,7 @@ local _ENV = mkmodule('plugins.buildingplan') --[[ - Native functions: + Public native functions: * bool isPlannableBuilding(df::building_type type, int16_t subtype, int32_t custom) * bool isPlannedBuilding(df::building *bld) @@ -13,15 +13,11 @@ local _ENV = mkmodule('plugins.buildingplan') --]] local argparse = require('argparse') -local gui = require('gui') -local guidm = require('gui.dwarfmode') -local overlay = require('plugins.overlay') -local utils = require('utils') -local widgets = require('gui.widgets') +local inspector = require('plugins.buildingplan.inspectoroverlay') +local pens = require('plugins.buildingplan.pens') +local planner = require('plugins.buildingplan.planneroverlay') require('dfhack.buildings') -local uibs = df.global.buildreq - local function process_args(opts, args) if args[1] == 'help' then opts.help = true @@ -44,8 +40,10 @@ function parse_commandline(...) local command = table.remove(positionals, 1) if not command or command == 'status' then printStatus() - elseif command == 'set' then + elseif command == 'set' and positionals then setSetting(positionals[1], positionals[2] == 'true') + elseif command == 'reset' then + resetFilters() else return false end @@ -53,6 +51,11 @@ function parse_commandline(...) return true end +function is_suspendmanager_enabled() + local ok, sm = pcall(reqscript, 'suspendmanager') + return ok and sm.isEnabled() +end + function get_num_filters(btype, subtype, custom) local filters = dfhack.buildings.getFiltersByType({}, btype, subtype, custom) return filters and #filters or 0 @@ -66,36 +69,6 @@ function get_job_item(btype, subtype, custom, index) return obj end -local function get_cur_filters() - return dfhack.buildings.getFiltersByType({}, uibs.building_type, - uibs.building_subtype, uibs.custom_type) -end - -local function is_choosing_area() - return uibs.selection_pos.x >= 0 -end - -local function get_cur_area_dims(placement_data) - if not placement_data and not is_choosing_area() then return 1, 1, 1 end - local selection_pos = placement_data and placement_data.p1 or uibs.selection_pos - local pos = placement_data and placement_data.p2 or uibs.pos - return math.abs(selection_pos.x - pos.x) + 1, - math.abs(selection_pos.y - pos.y) + 1, - math.abs(selection_pos.z - pos.z) + 1 -end - -local function get_quantity(filter, hollow, placement_data) - local quantity = filter.quantity or 1 - local dimx, dimy, dimz = get_cur_area_dims(placement_data) - if quantity < 1 then - return (((dimx * dimy) // 4) + 1) * dimz - end - if hollow and dimx > 2 and dimy > 2 then - return quantity * (2*dimx + 2*dimy - 4) * dimz - end - return quantity * dimx * dimy * dimz -end - local function to_title_case(str) str = str:gsub('(%a)([%w_]*)', function (first, rest) return first:upper()..rest:lower() end) @@ -114,12 +87,12 @@ function get_desc(filter) elseif filter.vector_id and filter.vector_id > -1 then desc = to_title_case(df.job_item_vector_id[filter.vector_id]) elseif filter.flags2 and filter.flags2.building_material then - desc = 'Building material'; - if filter.flags2.fire_safe then - desc = 'Fire-safe material'; - end if filter.flags2.magma_safe then - desc = 'Magma-safe material'; + desc = 'Magma-safe material' + elseif filter.flags2.fire_safe then + desc = 'Fire-safe material' + else + desc = 'Building material' end end @@ -130,1934 +103,43 @@ function get_desc(filter) desc = 'Mechanism' elseif desc == 'Wood' then desc = 'Log' - end - return desc -end - -local BUTTON_START_PEN, BUTTON_END_PEN, SELECTED_ITEM_PEN = nil, nil, nil -local reset_counts_flag = false -local reset_inspector_flag = false -function signal_reset() - BUTTON_START_PEN = nil - BUTTON_END_PEN = nil - SELECTED_ITEM_PEN = nil - reset_counts_flag = true - reset_inspector_flag = true -end - -local to_pen = dfhack.pen.parse -local function get_button_start_pen() - if not BUTTON_START_PEN then - local texpos_base = dfhack.textures.getControlPanelTexposStart() - BUTTON_START_PEN = to_pen{ch='[', fg=COLOR_YELLOW, - tile=texpos_base > 0 and texpos_base + 13 or nil} - end - return BUTTON_START_PEN -end -local function get_button_end_pen() - if not BUTTON_END_PEN then - local texpos_base = dfhack.textures.getControlPanelTexposStart() - BUTTON_END_PEN = to_pen{ch=']', fg=COLOR_YELLOW, - tile=texpos_base > 0 and texpos_base + 15 or nil} - end - return BUTTON_END_PEN -end -local function get_selected_item_pen() - if not SELECTED_ITEM_PEN then - local texpos_base = dfhack.textures.getControlPanelTexposStart() - SELECTED_ITEM_PEN = to_pen{ch='x', fg=COLOR_GREEN, - tile=texpos_base > 0 and texpos_base + 9 or nil} - end - return SELECTED_ITEM_PEN -end - -BuildingplanScreen = defclass(BuildingplanScreen, gui.ZScreen) -BuildingplanScreen.ATTRS { - pass_movement_keys=true, - pass_mouse_clicks=false, - defocusable=false, -} - --------------------------------- --- ItemSelection --- - -local BUILD_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREEN, keep_lower=true} -local BUILD_TEXT_HPEN = to_pen{fg=COLOR_WHITE, bg=COLOR_GREEN, keep_lower=true} - --- map of building type -> {set=set of recently used, list=list of recently used} --- most recent entries are at the *end* of the list -local recently_used = {} - -local function sort_by_type(a, b) - local ad, bd = a.data, b.data - return ad.item_type < bd.item_type or - (ad.item_type == bd.item_type and ad.item_subtype < bd.item_subtype) or - (ad.item_type == bd.item_type and ad.item_subtype == bd.item_subtype and a.search_key < b.search_key) or - (ad.item_type == bd.item_type and ad.item_subtype == bd.item_subtype and a.search_key == b.search_key and ad.quality > bd.quality) -end - -local function sort_by_recency(a, b) - local tracker = recently_used[uibs.building_type] - if not tracker then return sort_by_type(a, b) end - local recent_a, recent_b = tracker.set[a.search_key], tracker.set[b.search_key] - -- if they're both in the set, return the one with the greater index, - -- indicating more recent - if recent_a and recent_b then return recent_a > recent_b end - if recent_a and not recent_b then return true end - if not recent_a and recent_b then return false end - return sort_by_type(a, b) -end - -local function sort_by_name(a, b) - return a.search_key < b.search_key or - (a.search_key == b.search_key and sort_by_type(a, b)) -end - -local function sort_by_quantity(a, b) - local ad, bd = a.data, b.data - return ad.quantity > bd.quantity or - (ad.quantity == bd.quantity and sort_by_type(a, b)) -end - -ItemSelection = defclass(ItemSelection, widgets.Window) -ItemSelection.ATTRS{ - frame_title='Choose items', - frame={w=56, h=20, l=4, t=8}, - resizable=true, - index=DEFAULT_NIL, - quantity=DEFAULT_NIL, - on_submit=DEFAULT_NIL, - on_cancel=DEFAULT_NIL, -} - -function ItemSelection:init() - local filter = get_cur_filters()[self.index] - self.num_selected = 0 - self.selected_set = {} - local plural = self.quantity == 1 and '' or 's' - - self:addviews{ - widgets.Label{ - frame={t=0, l=0, r=10}, - text={ - get_desc(filter), - plural, - NEWLINE, - ('Select up to %d item%s ('):format(self.quantity, plural), - {text=function() return self.num_selected end}, - ' selected)', - }, - }, - widgets.Label{ - frame={r=0, w=9, t=0, h=3}, - text_pen=BUILD_TEXT_PEN, - text_hpen=BUILD_TEXT_HPEN, - text={ - ' ', NEWLINE, - ' Build ', NEWLINE, - ' ', - }, - on_click=self:callback('submit'), - }, - widgets.FilteredList{ - view_id='flist', - frame={t=3, l=0, r=0, b=4}, - case_sensitive=false, - choices=self:get_choices(sort_by_recency), - icon_width=2, - on_submit=self:callback('toggle_group'), - edit_on_char=function(ch) return ch:match('[%l -]') end, - }, - widgets.CycleHotkeyLabel{ - frame={l=0, b=2}, - key='CUSTOM_SHIFT_R', - label='Sort by:', - options={ - {label='Recently used', value=sort_by_recency}, - {label='Name', value=sort_by_name}, - {label='Amount', value=sort_by_quantity}, - }, - on_change=self:callback('on_sort'), - }, - widgets.HotkeyLabel{ - frame={l=0, b=1}, - key='SELECT', - label='Use all/none', - auto_width=true, - on_activate=function() self:toggle_group(self.subviews.flist.list:getSelected()) end, - }, - widgets.HotkeyLabel{ - frame={l=22, b=1}, - key='CUSTOM_SHIFT_B', - label='Build', - auto_width=true, - on_activate=self:callback('submit'), - }, - widgets.HotkeyLabel{ - frame={l=38, b=1}, - key='LEAVESCREEN', - label='Go back', - auto_width=true, - on_activate=self:callback('on_cancel'), - }, - widgets.HotkeyLabel{ - frame={l=0, b=0}, - key='KEYBOARD_CURSOR_RIGHT_FAST', - key_sep=' : ', - label='Use one', - auto_width=true, - on_activate=function() self:increment_group(self.subviews.flist.list:getSelected()) end, - }, - widgets.Label{ - frame={l=6, b=0, w=5}, - text_pen=COLOR_LIGHTGREEN, - text='Right', - }, - widgets.HotkeyLabel{ - frame={l=23, b=0}, - key='KEYBOARD_CURSOR_LEFT_FAST', - key_sep=' : ', - label='Use one fewer', - auto_width=true, - on_activate=function() self:decrement_group(self.subviews.flist.list:getSelected()) end, - }, - widgets.Label{ - frame={l=29, b=0, w=4}, - text_pen=COLOR_LIGHTGREEN, - text='Left', - }, - } -end - --- resort and restore selection -function ItemSelection:on_sort(sort_fn) - local flist = self.subviews.flist - local saved_filter = flist:getFilter() - flist:setFilter('') - flist:setChoices(self:get_choices(sort_fn), flist:getSelected()) - flist:setFilter(saved_filter) -end - -local function make_search_key(str) - local out = '' - for c in str:gmatch("[%w%s]") do - out = out .. c - end - return out -end - -function ItemSelection:get_choices(sort_fn) - local item_ids = getAvailableItems(uibs.building_type, - uibs.building_subtype, uibs.custom_type, self.index-1) - local buckets = {} - for _,item_id in ipairs(item_ids) do - local item = df.item.find(item_id) - if not item then goto continue end - local desc = dfhack.items.getDescription(item, 0, true) - if buckets[desc] then - local bucket = buckets[desc] - table.insert(bucket.data.item_ids, item_id) - bucket.data.quantity = bucket.data.quantity + 1 - else - local entry = { - search_key=make_search_key(desc), - icon=self:callback('get_entry_icon', item_id), - data={ - item_ids={item_id}, - item_type=item:getType(), - item_subtype=item:getSubtype(), - quantity=1, - quality=item:getQuality(), - selected=0, - }, - } - buckets[desc] = entry - end - ::continue:: - end - local choices = {} - for desc,choice in pairs(buckets) do - local data = choice.data - choice.text = { - {width=10, text=function() return ('[%d/%d]'):format(data.selected, data.quantity) end}, - {gap=2, text=desc}, - } - table.insert(choices, choice) - end - table.sort(choices, sort_fn) - return choices -end - -function ItemSelection:increment_group(idx, choice) - local data = choice.data - if self.quantity <= self.num_selected then return false end - if data.selected >= data.quantity then return false end - data.selected = data.selected + 1 - self.num_selected = self.num_selected + 1 - local item_id = data.item_ids[data.selected] - self.selected_set[item_id] = true - return true -end - -function ItemSelection:decrement_group(idx, choice) - local data = choice.data - if data.selected <= 0 then return false end - local item_id = data.item_ids[data.selected] - self.selected_set[item_id] = nil - self.num_selected = self.num_selected - 1 - data.selected = data.selected - 1 - return true -end - -function ItemSelection:toggle_group(idx, choice) - local data = choice.data - if data.selected > 0 then - while self:decrement_group(idx, choice) do end - else - while self:increment_group(idx, choice) do end - end -end - -function ItemSelection:get_entry_icon(item_id) - return self.selected_set[item_id] and get_selected_item_pen() or nil -end - -local function track_recently_used(choices) - -- use same set for all subtypes - local tracker = ensure_key(recently_used, uibs.building_type) - for _,choice in ipairs(choices) do - local data = choice.data - if data.selected <= 0 then goto continue end - local key = choice.search_key - local recent_set = ensure_key(tracker, 'set') - local recent_list = ensure_key(tracker, 'list') - if recent_set[key] then - if recent_list[#recent_list] ~= key then - for i,v in ipairs(recent_list) do - if v == key then - table.remove(recent_list, i) - table.insert(recent_list, key) - break - end - end - tracker.set = utils.invert(recent_list) - end - else - -- only keep most recent 10 - if #recent_list >= 10 then - -- remove least recently used from list and set - recent_set[table.remove(recent_list, 1)] = nil - end - table.insert(recent_list, key) - recent_set[key] = #recent_list - end - ::continue:: - end -end - -function ItemSelection:submit() - local selected_items = {} - for item_id in pairs(self.selected_set) do - table.insert(selected_items, item_id) - end - if #selected_items > 0 then - track_recently_used(self.subviews.flist:getChoices()) - end - self.on_submit(selected_items) -end - -function ItemSelection:onInput(keys) - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then - self.on_cancel() - return true - elseif keys._MOUSE_L_DOWN then - local list = self.subviews.flist.list - local idx = list:getIdxUnderMouse() - if idx then - list:setSelected(idx) - local modstate = dfhack.internal.getModstate() - if modstate & 2 > 0 then -- ctrl - local choice = list:getChoices()[idx] - if modstate & 1 > 0 then -- shift - self:decrement_group(idx, choice) - else - self:increment_group(idx, choice) - end - return true - end - end - end - return ItemSelection.super.onInput(self, keys) -end - -ItemSelectionScreen = defclass(ItemSelectionScreen, BuildingplanScreen) -ItemSelectionScreen.ATTRS { - focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/itemselection', - force_pause=true, - pass_pause=false, - index=DEFAULT_NIL, - quantity=DEFAULT_NIL, - on_submit=DEFAULT_NIL, - on_cancel=DEFAULT_NIL, -} - -function ItemSelectionScreen:init() - self:addviews{ - ItemSelection{ - index=self.index, - quantity=self.quantity, - on_submit=self.on_submit, - on_cancel=self.on_cancel, - } - } -end - --------------------------------- --- Slider --- - -Slider = defclass(Slider, widgets.Widget) -Slider.ATTRS{ - num_stops=DEFAULT_NIL, - get_left_idx_fn=DEFAULT_NIL, - get_right_idx_fn=DEFAULT_NIL, - on_left_change=DEFAULT_NIL, - on_right_change=DEFAULT_NIL, -} - -function Slider:preinit(init_table) - init_table.frame = init_table.frame or {} - init_table.frame.h = init_table.frame.h or 1 -end - -function Slider:init() - if self.num_stops < 2 then error('too few Slider stops') end - self.is_dragging_target = nil -- 'left', 'right', or 'both' - self.is_dragging_idx = nil -- offset from leftmost dragged tile -end - -local function slider_get_width_per_idx(self) - return math.max(5, (self.frame_body.width-7) // (self.num_stops-1)) -end - -function Slider:onInput(keys) - if not keys._MOUSE_L_DOWN then return false end - local x = self:getMousePos() - if not x then return false end - local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() - local width_per_idx = slider_get_width_per_idx(self) - local left_pos = width_per_idx*(left_idx-1) - local right_pos = width_per_idx*(right_idx-1) + 4 - if x < left_pos then - self.on_left_change(self.get_left_idx_fn() - 1) - elseif x < left_pos+3 then - self.is_dragging_target = 'left' - self.is_dragging_idx = x - left_pos - elseif x < right_pos then - self.is_dragging_target = 'both' - self.is_dragging_idx = x - left_pos - elseif x < right_pos+3 then - self.is_dragging_target = 'right' - self.is_dragging_idx = x - right_pos - else - self.on_right_change(self.get_right_idx_fn() + 1) - end - return true -end - -local function slider_do_drag(self, width_per_idx) - local x = self.frame_body:localXY(dfhack.screen.getMousePos()) - local cur_pos = x - self.is_dragging_idx - cur_pos = math.max(0, cur_pos) - cur_pos = math.min(width_per_idx*(self.num_stops-1)+7, cur_pos) - local offset = self.is_dragging_target == 'right' and -2 or 1 - local new_idx = math.max(0, cur_pos+offset)//width_per_idx + 1 - local new_left_idx, new_right_idx - if self.is_dragging_target == 'right' then - new_right_idx = new_idx - else - new_left_idx = new_idx - if self.is_dragging_target == 'both' then - new_right_idx = new_left_idx + self.get_right_idx_fn() - self.get_left_idx_fn() - if new_right_idx > self.num_stops then - return - end - end - end - if new_left_idx and new_left_idx ~= self.get_left_idx_fn() then - self.on_left_change(new_left_idx) - end - if new_right_idx and new_right_idx ~= self.get_right_idx_fn() then - self.on_right_change(new_right_idx) - end -end - -local SLIDER_LEFT_END = to_pen{ch=198, fg=COLOR_GREY, bg=COLOR_BLACK} -local SLIDER_TRACK = to_pen{ch=205, fg=COLOR_GREY, bg=COLOR_BLACK} -local SLIDER_TRACK_SELECTED = to_pen{ch=205, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK} -local SLIDER_TRACK_STOP = to_pen{ch=216, fg=COLOR_GREY, bg=COLOR_BLACK} -local SLIDER_TRACK_STOP_SELECTED = to_pen{ch=216, fg=COLOR_LIGHTGREEN, bg=COLOR_BLACK} -local SLIDER_RIGHT_END = to_pen{ch=181, fg=COLOR_GREY, bg=COLOR_BLACK} -local SLIDER_TAB_LEFT = to_pen{ch=60, fg=COLOR_BLACK, bg=COLOR_YELLOW} -local SLIDER_TAB_CENTER = to_pen{ch=9, fg=COLOR_BLACK, bg=COLOR_YELLOW} -local SLIDER_TAB_RIGHT = to_pen{ch=62, fg=COLOR_BLACK, bg=COLOR_YELLOW} - -function Slider:onRenderBody(dc, rect) - local left_idx, right_idx = self.get_left_idx_fn(), self.get_right_idx_fn() - local width_per_idx = slider_get_width_per_idx(self) - -- draw track - dc:seek(1,0) - dc:char(nil, SLIDER_LEFT_END) - dc:char(nil, SLIDER_TRACK) - for stop_idx=1,self.num_stops-1 do - local track_stop_pen = SLIDER_TRACK_STOP_SELECTED - local track_pen = SLIDER_TRACK_SELECTED - if left_idx > stop_idx or right_idx < stop_idx then - track_stop_pen = SLIDER_TRACK_STOP - track_pen = SLIDER_TRACK - elseif right_idx == stop_idx then - track_pen = SLIDER_TRACK - end - dc:char(nil, track_stop_pen) - for i=2,width_per_idx do - dc:char(nil, track_pen) - end - end - if right_idx >= self.num_stops then - dc:char(nil, SLIDER_TRACK_STOP_SELECTED) - else - dc:char(nil, SLIDER_TRACK_STOP) - end - dc:char(nil, SLIDER_TRACK) - dc:char(nil, SLIDER_RIGHT_END) - -- draw tabs - dc:seek(width_per_idx*(left_idx-1)) - dc:char(nil, SLIDER_TAB_LEFT) - dc:char(nil, SLIDER_TAB_CENTER) - dc:char(nil, SLIDER_TAB_RIGHT) - dc:seek(width_per_idx*(right_idx-1)+4) - dc:char(nil, SLIDER_TAB_LEFT) - dc:char(nil, SLIDER_TAB_CENTER) - dc:char(nil, SLIDER_TAB_RIGHT) - -- manage dragging - if self.is_dragging_target then - slider_do_drag(self, width_per_idx) - end - if df.global.enabler.mouse_lbut == 0 then - self.is_dragging_target = nil - self.is_dragging_idx = nil - end -end - --------------------------------- --- QualityAndMaterialsPage --- - -QualityAndMaterialsPage = defclass(QualityAndMaterialsPage, widgets.Panel) -QualityAndMaterialsPage.ATTRS{ - frame={t=0, l=0}, - index=DEFAULT_NIL, -} - -local TYPE_COL_WIDTH = 20 -local HEADER_HEIGHT = 7 -local QUALITY_HEIGHT = 9 -local FOOTER_HEIGHT = 4 - --- returns whether the items matched by the specified filter can have a quality --- rating. This also conveniently indicates whether an item can be decorated. -local function can_be_improved(idx) - local filter = get_cur_filters()[idx] - if filter.flags2 and filter.flags2.building_material then - return false; - end - return filter.item_type ~= df.item_type.WOOD and - filter.item_type ~= df.item_type.BLOCKS and - filter.item_type ~= df.item_type.BAR and - filter.item_type ~= df.item_type.BOULDER -end - -local function mat_sort_by_name(a, b) - return a.name < b.name -end - -local function mat_sort_by_quantity(a, b) - return a.quantity > b.quantity or - (a.quantity == b.quantity and mat_sort_by_name(a, b)) -end - -function QualityAndMaterialsPage:init() - self.dirty = true - self.summary = '' - - local enable_item_quality = can_be_improved(self.index) - - self:addviews{ - widgets.Panel{ - view_id='header', - frame={l=0, t=0, h=HEADER_HEIGHT, r=0}, - frame_inset={l=1}, - subviews={ - widgets.Label{ - frame={l=0, t=0}, - text='Current filter:', - }, - widgets.WrappedLabel{ - frame={l=16, t=0, h=2, r=0}, - text_pen=COLOR_LIGHTCYAN, - text_to_wrap=function() return self.summary end, - auto_height=false, - }, - widgets.CycleHotkeyLabel{ - view_id='mat_sort', - frame={l=0, t=3, w=21}, - label='Sort by:', - key='CUSTOM_SHIFT_R', - options={ - {label='name', value=mat_sort_by_name}, - {label='available', value=mat_sort_by_quantity} - }, - on_change=function() self.dirty = true end, - }, - widgets.ToggleHotkeyLabel{ - view_id='hide_zero', - frame={l=0, t=4, w=24}, - label='Hide unavailable:', - key='CUSTOM_SHIFT_H', - initial_option=false, - on_change=function() self.dirty = true end, - }, - widgets.EditField{ - view_id='search', - frame={l=26, t=3}, - label_text='Search: ', - on_char=function(ch) return ch:match('[%l -]') end, - }, - widgets.Label{ - frame={l=1, b=0}, - text='Type', - text_pen=COLOR_LIGHTRED, - }, - widgets.Label{ - frame={l=TYPE_COL_WIDTH, b=0}, - text='Material', - text_pen=COLOR_LIGHTRED, - }, - }, - }, - widgets.Panel{ - view_id='materials_lists', - frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT}, - frame_style=gui.INTERIOR_FRAME, - subviews={ - widgets.List{ - view_id='materials_categories', - frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3}, - scroll_keys={}, - icon_width=2, - cursor_pen=COLOR_CYAN, - on_submit=self:callback('toggle_category'), - }, - widgets.FilteredList{ - view_id='materials_mats', - frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0}, - icon_width=2, - on_submit=self:callback('toggle_material'), - }, - }, - }, - widgets.Panel{ - view_id='divider', - frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=FOOTER_HEIGHT+QUALITY_HEIGHT, w=1}, - on_render=self:callback('draw_divider'), - }, - widgets.Panel{ - view_id='quality_panel', - frame={l=0, r=0, h=QUALITY_HEIGHT, b=FOOTER_HEIGHT}, - frame_style=gui.INTERIOR_FRAME, - frame_title='Item quality', - subviews={ - widgets.CycleHotkeyLabel{ - view_id='decorated', - frame={l=0, t=1, w=23}, - key='CUSTOM_SHIFT_D', - label='Decorated only:', - options={ - {label='No', value=false}, - {label='Yes', value=true}, - }, - enabled=enable_item_quality, - on_change=self:callback('set_decorated'), - }, - widgets.CycleHotkeyLabel{ - view_id='min_quality', - frame={l=0, t=3, w=18}, - label='Min quality:', - label_below=true, - key_back='CUSTOM_SHIFT_Z', - key='CUSTOM_SHIFT_X', - options={ - {label='Ordinary', value=0}, - {label='Well Crafted', value=1}, - {label='Finely Crafted', value=2}, - {label='Superior', value=3}, - {label='Exceptional', value=4}, - {label='Masterful', value=5}, - {label='Artifact', value=6}, - }, - enabled=enable_item_quality, - on_change=function(val) self:set_min_quality(val+1) end, - }, - widgets.CycleHotkeyLabel{ - view_id='max_quality', - frame={r=1, t=3, w=18}, - label='Max quality:', - label_below=true, - key_back='CUSTOM_SHIFT_Q', - key='CUSTOM_SHIFT_W', - options={ - {label='Ordinary', value=0}, - {label='Well Crafted', value=1}, - {label='Finely Crafted', value=2}, - {label='Superior', value=3}, - {label='Exceptional', value=4}, - {label='Masterful', value=5}, - {label='Artifact', value=6}, - }, - enabled=enable_item_quality, - on_change=function(val) self:set_max_quality(val+1) end, - }, - Slider{ - frame={l=0, t=6}, - num_stops=7, - get_left_idx_fn=function() - return self.subviews.min_quality:getOptionValue() + 1 - end, - get_right_idx_fn=function() - return self.subviews.max_quality:getOptionValue() + 1 - end, - on_left_change=self:callback('set_min_quality'), - on_right_change=self:callback('set_max_quality'), - active=enable_item_quality, - }, - }, - }, - widgets.Panel{ - view_id='footer', - frame={l=0, r=0, b=0, h=FOOTER_HEIGHT}, - frame_inset={t=1, l=1}, - subviews={ - widgets.HotkeyLabel{ - frame={l=0, t=0}, - label='Toggle', - auto_width=true, - key='SELECT', - }, - widgets.HotkeyLabel{ - frame={l=0, t=2}, - label='Done', - auto_width=true, - key='LEAVESCREEN', - }, - widgets.HotkeyLabel{ - frame={l=30, t=0}, - label='Invert selection', - auto_width=true, - key='CUSTOM_SHIFT_I', - on_activate=self:callback('invert_materials'), - }, - widgets.HotkeyLabel{ - frame={l=30, t=2}, - label='Reset filter', - auto_width=true, - key='CUSTOM_SHIFT_X', - on_activate=self:callback('clear_filter'), - }, - }, - } - } - - -- replace the FilteredList's built-in EditField with our own - self.subviews.materials_mats.list.frame.t = 0 - self.subviews.materials_mats.edit.visible = false - self.subviews.materials_mats.edit = self.subviews.search - self.subviews.search.on_change = self.subviews.materials_mats:callback('onFilterChange') -end - -local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN} -local MAT_DISABLED_PEN = to_pen{ch='x', fg=COLOR_RED} - -local function make_cat_choice(label, cat, key, cats) - local enabled = cats[cat] - local icon = nil - if not cats.unset then - icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN - end - return { - text=label, - key=key, - enabled=enabled, - cat=cat, - icon=icon, - } -end - -local function make_mat_choice(name, props, enabled, cats) - local quantity = tonumber(props.count) - local text = ('%5d - %s'):format(quantity, name) - local icon = nil - if not cats.unset then - icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN - end - return { - text=text, - enabled=enabled, - icon=icon, - name=name, - cat=props.category, - quantity=quantity, - } -end - -function QualityAndMaterialsPage:refresh() - local summary = get_desc(get_cur_filters()[self.index]) - local subviews = self.subviews - - local heat = getHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type) - if heat >= 2 then summary = 'Magma safe ' .. summary - elseif heat == 1 then summary = 'Fire safe ' .. summary - else summary = 'Any ' .. summary - end - - local quality = getQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) - subviews.decorated:setOption(quality.decorated ~= 0) - subviews.min_quality:setOption(quality.min_quality) - subviews.max_quality:setOption(quality.max_quality) - - local cats = getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) - local category_choices={ - make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', cats), - make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', cats), - make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', cats), - make_cat_choice('Glass', 'glass', 'CUSTOM_SHIFT_G', cats), - } - self.subviews.materials_categories:setChoices(category_choices) - - local mats = getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) - local mat_choices = {} - local hide_zero = self.subviews.hide_zero:getOptionValue() - local enabled_mat_names = {} - for name,props in pairs(mats) do - local enabled = props.enabled == 'true' and cats[props.category] - if not cats.unset and enabled then - table.insert(enabled_mat_names, name) - end - if not hide_zero or tonumber(props.count) > 0 then - table.insert(mat_choices, make_mat_choice(name, props, enabled, cats)) - end - end - table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) - - local prev_filter = self.subviews.search.text - self.subviews.materials_mats:setChoices(mat_choices) - self.subviews.materials_mats:setFilter(prev_filter) - - if #enabled_mat_names > 0 then - table.sort(enabled_mat_names) - summary = summary .. (' of %s'):format(table.concat(enabled_mat_names, ', ')) - end - - self.summary = summary - self.dirty = false - self:updateLayout() -end - -function QualityAndMaterialsPage:toggle_category(_, choice) - local cats = {} - if not choice.icon then - -- toggling from unset to something is set - table.insert(cats, choice.cat) - else - choice.enabled = not choice.enabled - for _,c in ipairs(self.subviews.materials_categories:getChoices()) do - if c.enabled then - table.insert(cats, c.cat) - end - end - end - setMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, cats) - self.dirty = true -end - -function QualityAndMaterialsPage:toggle_material(_, choice) - local mats = {} - if not choice.icon then - -- toggling from unset to something is set - table.insert(mats, choice.name) - else - for _,c in ipairs(self.subviews.materials_mats:getChoices()) do - local enabled = c.enabled - if choice.name == c.name then - enabled = not c.enabled - end - if enabled then - table.insert(mats, c.name) - end - end - end - setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) - self.dirty = true -end - -function QualityAndMaterialsPage:invert_materials() - local mats = {} - for _,c in ipairs(self.subviews.materials_mats:getChoices()) do - if not c.icon then return end - if not c.enabled then - table.insert(mats, c.name) - end - end - setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) - self.dirty = true -end - -function QualityAndMaterialsPage:clear_filter() - clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) - self.dirty = true -end - -function QualityAndMaterialsPage:set_decorated(decorated) - local subviews = self.subviews - setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, - decorated and 1 or 0, subviews.min_quality:getOptionValue(), subviews.max_quality:getOptionValue()) - self.dirty = true -end - -function QualityAndMaterialsPage:set_min_quality(idx) - idx = math.min(6, math.max(0, idx-1)) - local subviews = self.subviews - subviews.min_quality:setOption(idx) - if subviews.max_quality:getOptionValue() < idx then - subviews.max_quality:setOption(idx) - end - setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, - subviews.decorated:getOptionValue() and 1 or 0, idx, subviews.max_quality:getOptionValue()) - self.dirty = true -end - -function QualityAndMaterialsPage:set_max_quality(idx) - idx = math.min(6, math.max(0, idx-1)) - local subviews = self.subviews - subviews.max_quality:setOption(idx) - if subviews.min_quality:getOptionValue() > idx then - subviews.min_quality:setOption(idx) - end - setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, - subviews.decorated:getOptionValue() and 1 or 0, subviews.min_quality:getOptionValue(), idx) - self.dirty = true -end - -local texpos = dfhack.textures.getThinBordersTexposStart() -local tp = function(offset) - if texpos == -1 then return nil end - return texpos + offset -end - -local TOP_PEN = to_pen{tile=tp(10), ch=194, fg=COLOR_GREY, bg=COLOR_BLACK} -local MID_PEN = to_pen{tile=tp(4), ch=192, fg=COLOR_GREY, bg=COLOR_BLACK} -local BOT_PEN = to_pen{tile=tp(11), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK} - -function QualityAndMaterialsPage:draw_divider(dc) - local y2 = dc.height - 1 - for y=0,y2 do - dc:seek(0, y) - if y == 0 then - dc:char(nil, TOP_PEN) - elseif y == y2 then - dc:char(nil, BOT_PEN) - else - dc:char(nil, MID_PEN) - end - end -end - -function QualityAndMaterialsPage:onRenderFrame(dc, rect) - QualityAndMaterialsPage.super.onRenderFrame(self, dc, rect) - if self.dirty then - self:refresh() - end -end - --------------------------------- --- GlobalSettingsPage --- - -GlobalSettingsPage = defclass(GlobalSettingsPage, widgets.ResizingPanel) -GlobalSettingsPage.ATTRS{ - autoarrange_subviews=true, - frame={t=0, l=0}, - frame_style=gui.INTERIOR_FRAME, -} - -function GlobalSettingsPage:init() - self:addviews{ - widgets.WrappedLabel{ - frame={l=0}, - text_to_wrap='These options will affect the selection of "Generic Materials" for all future buildings.', - }, - widgets.Panel{ - frame={h=1}, - }, - widgets.ToggleHotkeyLabel{ - view_id='blocks', - frame={l=0}, - key='CUSTOM_B', - label='Blocks', - label_width=8, - on_change=self:callback('update_setting', 'blocks'), - }, - widgets.ToggleHotkeyLabel{ - view_id='logs', - frame={l=0}, - key='CUSTOM_L', - label='Logs', - label_width=8, - on_change=self:callback('update_setting', 'logs'), - }, - widgets.ToggleHotkeyLabel{ - view_id='boulders', - frame={l=0}, - key='CUSTOM_O', - label='Boulders', - label_width=8, - on_change=self:callback('update_setting', 'boulders'), - }, - widgets.ToggleHotkeyLabel{ - view_id='bars', - frame={l=0}, - key='CUSTOM_R', - label='Bars', - label_width=8, - on_change=self:callback('update_setting', 'bars'), - }, - } - - self:init_settings() -end - -function GlobalSettingsPage:init_settings() - local settings = getGlobalSettings() - local subviews = self.subviews - subviews.blocks:setOption(settings.blocks) - subviews.logs:setOption(settings.logs) - subviews.boulders:setOption(settings.boulders) - subviews.bars:setOption(settings.bars) -end - -function GlobalSettingsPage:update_setting(setting, val) - dfhack.run_command('buildingplan', 'set', setting, tostring(val)) - self:init_settings() -end - --------------------------------- --- FilterSelection --- - -FilterSelection = defclass(FilterSelection, widgets.Window) -FilterSelection.ATTRS{ - frame_title='Choose filters', - frame={w=55, h=53, l=30, t=8}, - frame_inset={t=1}, - resizable=true, - index=DEFAULT_NIL, - autoarrange_subviews=true, -} - -function FilterSelection:init() - self:addviews{ - widgets.TabBar{ - frame={t=0}, - labels={ - 'Quality and materials', - 'Global settings', - }, - on_select=function(idx) - self.subviews.pages:setSelected(idx) - self:updateLayout() - end, - get_cur_page=function() return self.subviews.pages:getSelected() end, - key='CUSTOM_CTRL_T', - }, - widgets.Widget{ - frame={h=1}, - }, - widgets.Pages{ - view_id='pages', - frame={t=5, l=0, b=0, r=0}, - subviews={ - QualityAndMaterialsPage{index=self.index}, - GlobalSettingsPage{}, - }, - }, - } -end - -FilterSelectionScreen = defclass(FilterSelectionScreen, BuildingplanScreen) -FilterSelectionScreen.ATTRS { - focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection', - index=DEFAULT_NIL, -} - -function FilterSelectionScreen:init() - self:addviews{ - FilterSelection{index=self.index} - } -end - -function FilterSelectionScreen:onShow() - -- don't let the building "shadow" follow the mouse cursor while this screen is open - df.global.game.main_interface.bottom_mode_selected = -1 -end - -function FilterSelectionScreen:onDismiss() - -- re-enable building shadow - df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT -end - --------------------------------- --- ItemLine --- - -local function cur_building_has_no_area() - if uibs.building_type == df.building_type.Construction then return false end - local filters = dfhack.buildings.getFiltersByType({}, - uibs.building_type, uibs.building_subtype, uibs.custom_type) - -- this works because all variable-size buildings have either no item - -- filters or a quantity of -1 for their first (and only) item - return filters and filters[1] and (not filters[1].quantity or filters[1].quantity > 0) -end - -local function is_plannable() - return get_cur_filters() and - not (uibs.building_type == df.building_type.Construction - and uibs.building_subtype == df.construction_type.TrackNSEW) -end - -local function is_construction() - return uibs.building_type == df.building_type.Construction -end - -local function is_stairs() - return is_construction() - and uibs.building_subtype == df.construction_type.UpDownStair -end - -local direction_panel_frame = {t=4, h=13, w=46, r=28} - -local direction_panel_types = utils.invert{ - df.building_type.Bridge, - df.building_type.ScrewPump, - df.building_type.WaterWheel, - df.building_type.AxleHorizontal, - df.building_type.Rollers, -} - -local function has_direction_panel() - return direction_panel_types[uibs.building_type] - or (uibs.building_type == df.building_type.Trap - and uibs.building_subtype == df.trap_type.TrackStop) -end - -local pressure_plate_panel_frame = {t=4, h=37, w=46, r=28} - -local function has_pressure_plate_panel() - return uibs.building_type == df.building_type.Trap - and uibs.building_subtype == df.trap_type.PressurePlate -end - -local function is_over_options_panel() - local frame = nil - if has_direction_panel() then - frame = direction_panel_frame - elseif has_pressure_plate_panel() then - frame = pressure_plate_panel_frame - else - return false - end - local v = widgets.Widget{frame=frame} - local rect = gui.mkdims_wh(0, 0, dfhack.screen.getWindowSize()) - v:updateLayout(gui.ViewRect{rect=rect}) - return v:getMousePos() -end - -ItemLine = defclass(ItemLine, widgets.Panel) -ItemLine.ATTRS{ - idx=DEFAULT_NIL, - is_selected_fn=DEFAULT_NIL, - is_hollow_fn=DEFAULT_NIL, - on_select=DEFAULT_NIL, - on_filter=DEFAULT_NIL, - on_clear_filter=DEFAULT_NIL, -} - -function ItemLine:init() - self.frame.h = 1 - self.visible = function() return #get_cur_filters() >= self.idx end - self:addviews{ - widgets.Label{ - frame={t=0, l=0}, - text='*', - auto_width=true, - visible=self.is_selected_fn, - }, - widgets.Label{ - frame={t=0, l=25}, - text={ - {tile=get_button_start_pen}, - {gap=6, tile=get_button_end_pen}, - }, - auto_width=true, - on_click=function() self.on_filter(self.idx) end, - }, - widgets.Label{ - frame={t=0, l=33}, - text={ - {tile=get_button_start_pen}, - {gap=1, tile=get_button_end_pen}, - }, - auto_width=true, - on_click=function() self.on_clear_filter(self.idx) end, - }, - widgets.Label{ - frame={t=0, l=2}, - text={ - {width=21, text=self:callback('get_item_line_text')}, - {gap=3, text='filter', pen=COLOR_GREEN}, - {gap=2, text='x', pen=self:callback('get_x_pen')}, - {gap=3, text=function() return self.note end, - pen=function() return self.note_pen end}, - }, - }, - } -end - -function ItemLine:reset() - self.desc = nil - self.available = nil -end - -function ItemLine:onInput(keys) - if keys._MOUSE_L_DOWN and self:getMousePos() then - self.on_select(self.idx) - end - return ItemLine.super.onInput(self, keys) -end - -function ItemLine:get_x_pen() - return hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.idx - 1) and - COLOR_GREEN or COLOR_GREY -end - -function ItemLine:get_item_line_text() - local idx = self.idx - local filter = get_cur_filters()[idx] - local quantity = get_quantity(filter, self.is_hollow_fn()) - - self.desc = self.desc or get_desc(filter) - - self.available = self.available or countAvailableItems(uibs.building_type, - uibs.building_subtype, uibs.custom_type, idx - 1) - if self.available >= quantity then - self.note_pen = COLOR_GREEN - self.note = 'Available now' - else - self.note_pen = COLOR_YELLOW - self.note = 'Will link later' - end - - return ('%d %s%s'):format(quantity, self.desc, quantity == 1 and '' or 's') -end - -function ItemLine:reduce_quantity(used_quantity) - if not self.available then return end - local filter = get_cur_filters()[self.idx] - used_quantity = used_quantity or get_quantity(filter, self.is_hollow_fn()) - self.available = math.max(0, self.available - used_quantity) -end - -local function get_placement_errors() - local out = '' - for _,str in ipairs(uibs.errors) do - if #out > 0 then out = out .. NEWLINE end - out = out .. str.value - end - return out -end - --------------------------------- --- PlannerOverlay --- - -PlannerOverlay = defclass(PlannerOverlay, overlay.OverlayWidget) -PlannerOverlay.ATTRS{ - default_pos={x=5,y=9}, - default_enabled=true, - viewscreens='dwarfmode/Building/Placement', - frame={w=56, h=20}, -} - -function PlannerOverlay:init() - self.selected = 1 - - local main_panel = widgets.Panel{ - view_id='main', - frame={t=0, l=0, r=0, h=14}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, - } - - local function make_is_selected_fn(idx) - return function() return self.selected == idx end + elseif desc == 'Any weapon' then + desc = 'Weapon' + elseif desc == 'Any spike' then + desc = 'Spike' + elseif desc == 'Ballistapart' then + desc = 'Ballista part' + elseif desc == 'Catapultpart' then + desc = 'Catapult part' + elseif desc == 'Smallgem' then + desc = 'Small, cut gem' end - local function on_select_fn(idx) - self.selected = idx - end - - local function is_hollow_fn() - return self.subviews.hollow:getOptionValue() - end - - main_panel:addviews{ - widgets.Label{ - frame={}, - auto_width=true, - text='No items required.', - visible=function() return #get_cur_filters() == 0 end, - }, - ItemLine{view_id='item1', frame={t=0, l=0, r=0}, idx=1, - is_selected_fn=make_is_selected_fn(1), is_hollow_fn=is_hollow_fn, - on_select=on_select_fn, on_filter=self:callback('set_filter'), - on_clear_filter=self:callback('clear_filter')}, - ItemLine{view_id='item2', frame={t=2, l=0, r=0}, idx=2, - is_selected_fn=make_is_selected_fn(2), is_hollow_fn=is_hollow_fn, - on_select=on_select_fn, on_filter=self:callback('set_filter'), - on_clear_filter=self:callback('clear_filter')}, - ItemLine{view_id='item3', frame={t=4, l=0, r=0}, idx=3, - is_selected_fn=make_is_selected_fn(3), is_hollow_fn=is_hollow_fn, - on_select=on_select_fn, on_filter=self:callback('set_filter'), - on_clear_filter=self:callback('clear_filter')}, - ItemLine{view_id='item4', frame={t=6, l=0, r=0}, idx=4, - is_selected_fn=make_is_selected_fn(4), is_hollow_fn=is_hollow_fn, - on_select=on_select_fn, on_filter=self:callback('set_filter'), - on_clear_filter=self:callback('clear_filter')}, - widgets.CycleHotkeyLabel{ - view_id='hollow', - frame={t=3, l=4}, - key='CUSTOM_H', - label='Hollow area:', - visible=is_construction, - options={ - {label='No', value=false}, - {label='Yes', value=true}, - }, - }, - widgets.CycleHotkeyLabel{ - view_id='stairs_top_subtype', - frame={t=4, l=4}, - key='CUSTOM_R', - label='Top Stair Type: ', - visible=is_stairs, - options={ - {label='Auto', value='auto'}, - {label='UpDown', value=df.construction_type.UpDownStair}, - {label='Down', value=df.construction_type.DownStair}, - }, - }, - widgets.CycleHotkeyLabel { - view_id='stairs_bottom_subtype', - frame={t=5, l=4}, - key='CUSTOM_B', - label='Bottom Stair Type: ', - visible=is_stairs, - options={ - {label='Auto', value='auto'}, - {label='UpDown', value=df.construction_type.UpDownStair}, - {label='Up', value=df.construction_type.UpStair}, - }, - }, - widgets.Label{ - frame={b=3, l=17}, - text={ - 'Selected area: ', - {text=function() - return ('%dx%dx%d'):format(get_cur_area_dims(self.saved_placement)) - end - }, - }, - visible=function() - return not cur_building_has_no_area() and (self.saved_placement or is_choosing_area()) - end, - }, - widgets.Panel{ - visible=function() return #get_cur_filters() > 0 end, - subviews={ - widgets.HotkeyLabel{ - frame={b=1, l=0}, - key='STRING_A042', - auto_width=true, - enabled=function() return #get_cur_filters() > 1 end, - on_activate=function() self.selected = ((self.selected - 2) % #get_cur_filters()) + 1 end, - }, - widgets.HotkeyLabel{ - frame={b=1, l=1}, - key='STRING_A047', - label='Prev/next item', - auto_width=true, - enabled=function() return #get_cur_filters() > 1 end, - on_activate=function() self.selected = (self.selected % #get_cur_filters()) + 1 end, - }, - widgets.HotkeyLabel{ - frame={b=1, l=21}, - key='CUSTOM_F', - label='Set filter', - auto_width=true, - on_activate=function() self:set_filter(self.selected) end, - }, - widgets.HotkeyLabel{ - frame={b=1, l=37}, - key='CUSTOM_X', - label='Clear filter', - auto_width=true, - on_activate=function() self:clear_filter(self.selected) end, - enabled=function() - return hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1) - end - }, - widgets.CycleHotkeyLabel{ - view_id='choose', - frame={b=0, l=0, w=25}, - key='CUSTOM_I', - label='Choose from items:', - options={{label='Yes', value=true}, - {label='No', value=false}}, - initial_option=false, - enabled=function() - for idx = 1,4 do - if (self.subviews['item'..idx].available or 0) > 0 then - return true - end - end - end, - }, - widgets.CycleHotkeyLabel{ - view_id='safety', - frame={b=0, l=29, w=25}, - key='CUSTOM_G', - label='Building safety:', - options={ - {label='Any', value=0}, - {label='Magma', value=2, pen=COLOR_RED}, - {label='Fire', value=1, pen=COLOR_LIGHTRED}, - }, - initial_option=0, - on_change=function(heat) - setHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, heat) - end, - }, - }, - }, - } - - local error_panel = widgets.ResizingPanel{ - view_id='errors', - frame={t=14, l=0, r=0}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, - } - - error_panel:addviews{ - widgets.WrappedLabel{ - frame={t=0, l=0, r=0}, - text_pen=COLOR_LIGHTRED, - text_to_wrap=get_placement_errors, - visible=function() return #uibs.errors > 0 end, - }, - widgets.Label{ - frame={t=0, l=0, r=0}, - text_pen=COLOR_GREEN, - text='OK to build', - visible=function() return #uibs.errors == 0 end, - }, - } - - self:addviews{ - main_panel, - error_panel, - } -end - -function PlannerOverlay:reset() - self.subviews.item1:reset() - self.subviews.item2:reset() - self.subviews.item3:reset() - self.subviews.item4:reset() - reset_counts_flag = false -end - -function PlannerOverlay:set_filter(idx) - FilterSelectionScreen{index=idx}:show() -end - -function PlannerOverlay:clear_filter(idx) - clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx-1) -end - -local function get_placement_data() - local pos = uibs.pos - local direction = uibs.direction - local width, height, depth = get_cur_area_dims() - local _, adjusted_width, adjusted_height = dfhack.buildings.getCorrectSize( - width, height, uibs.building_type, uibs.building_subtype, - uibs.custom_type, direction) - -- get the upper-left corner of the building/area at min z-level - local has_selection = is_choosing_area() - local start_pos = xyz2pos( - has_selection and math.min(uibs.selection_pos.x, pos.x) or pos.x - adjusted_width//2, - has_selection and math.min(uibs.selection_pos.y, pos.y) or pos.y - adjusted_height//2, - has_selection and math.min(uibs.selection_pos.z, pos.z) or pos.z - ) - if uibs.building_type == df.building_type.ScrewPump then - if direction == df.screw_pump_direction.FromSouth then - start_pos.y = start_pos.y + 1 - elseif direction == df.screw_pump_direction.FromEast then - start_pos.x = start_pos.x + 1 - end - end - local min_x, max_x = start_pos.x, start_pos.x - local min_y, max_y = start_pos.y, start_pos.y - local min_z, max_z = start_pos.z, start_pos.z - if adjusted_width == 1 and adjusted_height == 1 - and (width > 1 or height > 1 or depth > 1) then - max_x = min_x + width - 1 - max_y = min_y + height - 1 - max_z = math.max(uibs.selection_pos.z, pos.z) - end - return { - p1=xyz2pos(min_x, min_y, min_z), - p2=xyz2pos(max_x, max_y, max_z), - width=adjusted_width, - height=adjusted_height - } -end - -function PlannerOverlay:save_placement() - self.saved_placement = get_placement_data() - if (uibs.selection_pos:isValid()) then - self.saved_selection_pos_valid = true - self.saved_selection_pos = copyall(uibs.selection_pos) - self.saved_pos = copyall(uibs.pos) - uibs.selection_pos:clear() - else - self.saved_selection_pos = copyall(self.saved_placement.p1) - self.saved_pos = copyall(self.saved_placement.p2) - self.saved_pos.x = self.saved_pos.x + self.saved_placement.width - 1 - self.saved_pos.y = self.saved_pos.y + self.saved_placement.height - 1 - end -end - -function PlannerOverlay:restore_placement() - if self.saved_selection_pos_valid then - uibs.selection_pos = self.saved_selection_pos - self.saved_selection_pos_valid = nil - else - uibs.selection_pos:clear() - end - self.saved_selection_pos = nil - self.saved_pos = nil - local placement_data = self.saved_placement - self.saved_placement = nil - return placement_data -end - -function PlannerOverlay:onInput(keys) - if not is_plannable() then return false end - if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then - if uibs.selection_pos:isValid() then - uibs.selection_pos:clear() - return true - end - self.selected = 1 - self.subviews.hollow:setOption(false) - self.subviews.choose:setOption(false) - self:reset() - reset_counts_flag = true - return false - end - if PlannerOverlay.super.onInput(self, keys) then - return true - end - if keys._MOUSE_L_DOWN then - if is_over_options_panel() then return false end - local detect_rect = copyall(self.frame_rect) - detect_rect.height = self.subviews.main.frame_rect.height + - self.subviews.errors.frame_rect.height - detect_rect.y2 = detect_rect.y1 + detect_rect.height - 1 - if self.subviews.main:getMousePos(gui.ViewRect{rect=detect_rect}) - or self.subviews.errors:getMousePos() then - return true - end - if not is_construction() and #uibs.errors > 0 then return true end - if dfhack.gui.getMousePos() then - if is_choosing_area() or cur_building_has_no_area() then - local filters = get_cur_filters() - local num_filters = #filters - if num_filters == 0 then - return false -- we don't add value; let the game place it - end - local choose = self.subviews.choose - if choose.enabled() and choose:getOptionValue() then - self:save_placement() - local is_hollow = self.subviews.hollow:getOptionValue() - local chosen_items, active_screens = {}, {} - local pending = num_filters - df.global.game.main_interface.bottom_mode_selected = -1 - for idx = num_filters,1,-1 do - chosen_items[idx] = {} - if (self.subviews['item'..idx].available or 0) > 0 then - active_screens[idx] = ItemSelectionScreen{ - index=idx, - quantity=get_quantity(filters[idx], is_hollow, - self.saved_placement), - on_submit=function(items) - chosen_items[idx] = items - active_screens[idx]:dismiss() - active_screens[idx] = nil - pending = pending - 1 - if pending == 0 then - df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT - self:place_building(self:restore_placement(), chosen_items) - end - end, - on_cancel=function() - for i,scr in pairs(active_screens) do - scr:dismiss() - end - df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT - self:restore_placement() - end, - }:show() - else - pending = pending - 1 - end - end - else - self:place_building(get_placement_data()) - end - return true - elseif not is_choosing_area() then - return false - end - end - end - return keys._MOUSE_L or keys.SELECT -end - -function PlannerOverlay:render(dc) - if not is_plannable() then return end - self.subviews.errors:updateLayout() - PlannerOverlay.super.render(self, dc) -end - -local GOOD_PEN, BAD_PEN -function reload_cursors() - GOOD_PEN = to_pen{ch='o', fg=COLOR_GREEN, tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} - BAD_PEN = to_pen{ch='X', fg=COLOR_RED, tile=dfhack.screen.findGraphicsTile('CURSORS', 3, 0)} -end -reload_cursors() - -local ONE_BY_ONE = xy2pos(1, 1) - -function PlannerOverlay:onRenderFrame(dc, rect) - PlannerOverlay.super.onRenderFrame(self, dc, rect) - - if reset_counts_flag then - self:reset() - self.subviews.safety:setOption(getHeatSafetyFilter( - uibs.building_type, uibs.building_subtype, uibs.custom_type)) - end - - local selection_pos = self.saved_selection_pos or uibs.selection_pos - if not selection_pos or selection_pos.x < 0 then return end - - local pos = self.saved_pos or uibs.pos - local bounds = { - x1 = math.max(0, math.min(selection_pos.x, pos.x)), - x2 = math.min(df.global.world.map.x_count-1, math.max(selection_pos.x, pos.x)), - y1 = math.max(0, math.min(selection_pos.y, pos.y)), - y2 = math.min(df.global.world.map.y_count-1, math.max(selection_pos.y, pos.y)), - } - - local hollow = self.subviews.hollow:getOptionValue() - local default_pen = (self.saved_selection_pos or #uibs.errors == 0) and GOOD_PEN or BAD_PEN - - local get_pen_fn = is_construction() and function(pos) - return dfhack.buildings.checkFreeTiles(pos, ONE_BY_ONE) and GOOD_PEN or BAD_PEN - end or function() - return default_pen - end - - local function get_overlay_pen(pos) - if not hollow then return get_pen_fn(pos) end - if pos.x == bounds.x1 or pos.x == bounds.x2 or - pos.y == bounds.y1 or pos.y == bounds.y2 then - return get_pen_fn(pos) - end - return gui.TRANSPARENT_PEN - end - - guidm.renderMapOverlay(get_overlay_pen, bounds) -end - -function PlannerOverlay:get_stairs_subtype(pos, corner1, corner2) - local subtype = uibs.building_subtype - if pos.z == corner1.z then - local opt = self.subviews.stairs_bottom_subtype:getOptionValue() - if opt == 'auto' then - local tt = dfhack.maps.getTileType(pos) - local shape = df.tiletype.attrs[tt].shape - if shape ~= df.tiletype_shape.STAIR_DOWN then - subtype = df.construction_type.UpStair - end - else - subtype = opt - end - elseif pos.z == corner2.z then - local opt = self.subviews.stairs_top_subtype:getOptionValue() - if opt == 'auto' then - local tt = dfhack.maps.getTileType(pos) - local shape = df.tiletype.attrs[tt].shape - if shape ~= df.tiletype_shape.STAIR_UP then - subtype = df.construction_type.DownStair - end - else - subtype = opt - end - end - return subtype -end - -function PlannerOverlay:place_building(placement_data, chosen_items) - local p1, p2 = placement_data.p1, placement_data.p2 - local blds = {} - local hollow = self.subviews.hollow:getOptionValue() - local subtype = uibs.building_subtype - for z=p1.z,p2.z do for y=p1.y,p2.y do for x=p1.x,p2.x do - if hollow and x ~= p1.x and x ~= p2.x and y ~= p1.y and y ~= p2.y then - goto continue - end - local pos = xyz2pos(x, y, z) - if is_stairs() then - subtype = self:get_stairs_subtype(pos, p1, p2) - end - local bld, err = dfhack.buildings.constructBuilding{pos=pos, - type=uibs.building_type, subtype=subtype, custom=uibs.custom_type, - width=placement_data.width, height=placement_data.height, - direction=uibs.direction} - if err then - -- it's ok if some buildings fail to build - goto continue - end - -- assign fields for the types that need them. we can't pass them all in - -- to the call to constructBuilding since attempting to assign unrelated - -- fields to building types that don't support them causes errors. - for k,v in pairs(bld) do - if k == 'friction' then bld.friction = uibs.friction end - if k == 'use_dump' then bld.use_dump = uibs.use_dump end - if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end - if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end - if k == 'speed' then bld.speed = uibs.speed end - end - table.insert(blds, bld) - ::continue:: - end end end - local used_quantity = is_construction() and #blds or false - self.subviews.item1:reduce_quantity(used_quantity) - self.subviews.item2:reduce_quantity(used_quantity) - self.subviews.item3:reduce_quantity(used_quantity) - self.subviews.item4:reduce_quantity(used_quantity) - for _,bld in ipairs(blds) do - -- attach chosen items and reduce job_item quantity - if chosen_items then - local job = bld.jobs[0] - local jitems = job.job_items - for idx=1,#get_cur_filters() do - local item_ids = chosen_items[idx] - while jitems[idx-1].quantity > 0 and #item_ids > 0 do - local item_id = item_ids[#item_ids] - local item = df.item.find(item_id) - if not item then - dfhack.printerr(('item no longer available: %d'):format(item_id)) - break - end - if not dfhack.job.attachJobItem(job, item, df.job_item_ref.T_role.Hauled, idx-1, -1) then - dfhack.printerr(('cannot attach item: %d'):format(item_id)) - break - end - jitems[idx-1].quantity = jitems[idx-1].quantity - 1 - item_ids[#item_ids] = nil - end - end - end - addPlannedBuilding(bld) - end - scheduleCycle() - uibs.selection_pos:clear() -end - --------------------------------- --- InspectorLine --- - -local function get_building_filters() - local bld = dfhack.gui.getSelectedBuilding() - return dfhack.buildings.getFiltersByType({}, - bld:getType(), bld:getSubtype(), bld:getCustomType()) -end - -InspectorLine = defclass(InspectorLine, widgets.Panel) -InspectorLine.ATTRS{ - idx=DEFAULT_NIL, -} - -function InspectorLine:init() - self.frame.h = 2 - self.visible = function() return #get_building_filters() >= self.idx end - self:addviews{ - widgets.Label{ - frame={t=0, l=0}, - text={{text=self:callback('get_desc_string')}}, - }, - widgets.Label{ - frame={t=1, l=2}, - text={{text=self:callback('get_status_line')}}, - }, - } -end - -function InspectorLine:get_desc_string() - if self.desc then return self.desc end - self.desc = getDescString(dfhack.gui.getSelectedBuilding(), self.idx-1) - return self.desc -end - -function InspectorLine:get_status_line() - if self.status then return self.status end - local queue_pos = getQueuePosition(dfhack.gui.getSelectedBuilding(), self.idx-1) - if queue_pos <= 0 then - return 'Item attached' - end - self.status = ('Position in line: %d'):format(queue_pos) - return self.status -end - -function InspectorLine:reset() - self.desc = nil - self.status = nil -end - --------------------------------- --- InspectorOverlay --- - -InspectorOverlay = defclass(InspectorOverlay, overlay.OverlayWidget) -InspectorOverlay.ATTRS{ - default_pos={x=-41,y=14}, - default_enabled=true, - viewscreens='dwarfmode/ViewSheets/BUILDING', - frame={w=30, h=15}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, -} - -function InspectorOverlay:init() - self:addviews{ - widgets.Label{ - frame={t=0, l=0}, - text='Waiting for items:', - }, - InspectorLine{view_id='item1', frame={t=2, l=0}, idx=1}, - InspectorLine{view_id='item2', frame={t=4, l=0}, idx=2}, - InspectorLine{view_id='item3', frame={t=6, l=0}, idx=3}, - InspectorLine{view_id='item4', frame={t=8, l=0}, idx=4}, - widgets.HotkeyLabel{ - frame={t=11, l=0}, - label='adjust filters', - key='CUSTOM_CTRL_F', - visible=false, -- until implemented - }, - widgets.HotkeyLabel{ - frame={t=12, l=0}, - label='make top priority', - key='CUSTOM_CTRL_T', - on_activate=self:callback('make_top_priority'), - }, - } -end - -function InspectorOverlay:reset() - self.subviews.item1:reset() - self.subviews.item2:reset() - self.subviews.item3:reset() - self.subviews.item4:reset() - reset_inspector_flag = false -end - -function InspectorOverlay:make_top_priority() - makeTopPriority(dfhack.gui.getSelectedBuilding()) - self:reset() + return desc end -local RESUME_BUTTON_FRAME = {t=15, h=3, r=73, w=25} - -local function mouse_is_over_resume_button(rect) - local x,y = dfhack.screen.getMousePos() - if not x then return false end - if y < RESUME_BUTTON_FRAME.t or y > RESUME_BUTTON_FRAME.t + RESUME_BUTTON_FRAME.h - 1 then - return false - end - if x > rect.x2 - RESUME_BUTTON_FRAME.r + 1 or x < rect.x2 - RESUME_BUTTON_FRAME.r - RESUME_BUTTON_FRAME.w + 2 then - return false - end - return true +function reload_pens() + pens.reload_pens() end -function InspectorOverlay:onInput(keys) - if not isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then - return false - end - if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then - return true - elseif keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or keys.LEAVESCREEN then - self:reset() - end - return InspectorOverlay.super.onInput(self, keys) +function signal_reset() + planner.reset_counts_flag = true + inspector.reset_inspector_flag = true end -function InspectorOverlay:render(dc) - if not isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then - return - end - if reset_inspector_flag then - self:reset() - end - InspectorOverlay.super.render(self, dc) +-- for use during development to reload all buildingplan modules +function reload_modules() + reload('plugins.buildingplan.pens') + reload('plugins.buildingplan.filterselection') + reload('plugins.buildingplan.itemselection') + reload('plugins.buildingplan.planneroverlay') + reload('plugins.buildingplan.inspectoroverlay') + reload('plugins.buildingplan') end OVERLAY_WIDGETS = { - planner=PlannerOverlay, - inspector=InspectorOverlay, + planner=planner.PlannerOverlay, + inspector=inspector.InspectorOverlay, } return _ENV diff --git a/plugins/lua/buildingplan/filterselection.lua b/plugins/lua/buildingplan/filterselection.lua new file mode 100644 index 000000000..e25b58eec --- /dev/null +++ b/plugins/lua/buildingplan/filterselection.lua @@ -0,0 +1,608 @@ +local _ENV = mkmodule('plugins.buildingplan.filterselection') + +local gui = require('gui') +local pens = require('plugins.buildingplan.pens') +local widgets = require('gui.widgets') + +local uibs = df.global.buildreq +local to_pen = dfhack.pen.parse + +local function get_cur_filters() + return dfhack.buildings.getFiltersByType({}, uibs.building_type, + uibs.building_subtype, uibs.custom_type) +end + +-------------------------------- +-- QualityAndMaterialsPage +-- + +QualityAndMaterialsPage = defclass(QualityAndMaterialsPage, widgets.Panel) +QualityAndMaterialsPage.ATTRS{ + frame={t=0, l=0}, + index=DEFAULT_NIL, + desc=DEFAULT_NIL, +} + +local TYPE_COL_WIDTH = 20 +local HEADER_HEIGHT = 7 +local QUALITY_HEIGHT = 9 +local FOOTER_HEIGHT = 4 + +-- returns whether the items matched by the specified filter can have a quality +-- rating. This also conveniently indicates whether an item can be decorated. +local function can_be_improved(idx) + local filter = get_cur_filters()[idx] + if filter.flags2 and filter.flags2.building_material then + return false; + end + return filter.item_type ~= df.item_type.WOOD and + filter.item_type ~= df.item_type.BLOCKS and + filter.item_type ~= df.item_type.BAR and + filter.item_type ~= df.item_type.BOULDER +end + +local function mat_sort_by_name(a, b) + return a.name < b.name +end + +local function mat_sort_by_quantity(a, b) + return a.quantity > b.quantity or + (a.quantity == b.quantity and mat_sort_by_name(a, b)) +end + +function QualityAndMaterialsPage:init() + self.dirty = true + self.summary = '' + + local enable_item_quality = can_be_improved(self.index) + + self:addviews{ + widgets.Panel{ + view_id='header', + frame={l=0, t=0, h=HEADER_HEIGHT, r=0}, + frame_inset={l=1}, + subviews={ + widgets.Label{ + frame={l=0, t=0}, + text='Current filter:', + }, + widgets.WrappedLabel{ + frame={l=16, t=0, h=2, r=0}, + text_pen=COLOR_LIGHTCYAN, + text_to_wrap=function() return self.summary end, + auto_height=false, + }, + widgets.CycleHotkeyLabel{ + view_id='mat_sort', + frame={l=0, t=3, w=21}, + label='Sort by:', + key='CUSTOM_SHIFT_R', + options={ + {label='name', value=mat_sort_by_name}, + {label='available', value=mat_sort_by_quantity} + }, + on_change=function() self.dirty = true end, + }, + widgets.ToggleHotkeyLabel{ + view_id='hide_zero', + frame={l=0, t=4, w=24}, + label='Hide unavailable:', + key='CUSTOM_SHIFT_H', + initial_option=false, + on_change=function() self.dirty = true end, + }, + widgets.EditField{ + view_id='search', + frame={l=26, t=3}, + label_text='Search: ', + on_char=function(ch) return ch:match('[%l -]') end, + }, + widgets.Label{ + frame={l=1, b=0}, + text='Type', + text_pen=COLOR_LIGHTRED, + }, + widgets.Label{ + frame={l=TYPE_COL_WIDTH, b=0}, + text='Material', + text_pen=COLOR_LIGHTRED, + }, + }, + }, + widgets.Panel{ + view_id='materials_lists', + frame={l=0, t=HEADER_HEIGHT, r=0, b=FOOTER_HEIGHT+QUALITY_HEIGHT}, + frame_style=gui.INTERIOR_FRAME, + subviews={ + widgets.List{ + view_id='materials_categories', + frame={l=1, t=0, b=0, w=TYPE_COL_WIDTH-3}, + scroll_keys={}, + icon_width=2, + cursor_pen=COLOR_CYAN, + on_submit=self:callback('toggle_category'), + }, + widgets.FilteredList{ + view_id='materials_mats', + frame={l=TYPE_COL_WIDTH, t=0, r=0, b=0}, + icon_width=2, + on_submit=self:callback('toggle_material'), + }, + }, + }, + widgets.Panel{ + view_id='divider', + frame={l=TYPE_COL_WIDTH-1, t=HEADER_HEIGHT, b=FOOTER_HEIGHT+QUALITY_HEIGHT, w=1}, + on_render=self:callback('draw_divider'), + }, + widgets.Panel{ + view_id='quality_panel', + frame={l=0, r=0, h=QUALITY_HEIGHT, b=FOOTER_HEIGHT}, + frame_style=gui.INTERIOR_FRAME, + frame_title='Item quality', + subviews={ + widgets.CycleHotkeyLabel{ + view_id='decorated', + frame={l=0, t=1, w=23}, + key='CUSTOM_SHIFT_D', + label='Decorated only:', + options={ + {label='No', value=false}, + {label='Yes', value=true}, + }, + enabled=enable_item_quality, + on_change=self:callback('set_decorated'), + }, + widgets.CycleHotkeyLabel{ + view_id='min_quality', + frame={l=0, t=3, w=18}, + label='Min quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Z', + key='CUSTOM_SHIFT_X', + options={ + {label='Ordinary', value=0}, + {label='Well Crafted', value=1}, + {label='Finely Crafted', value=2}, + {label='Superior', value=3}, + {label='Exceptional', value=4}, + {label='Masterful', value=5}, + {label='Artifact', value=6}, + }, + enabled=enable_item_quality, + on_change=function(val) self:set_min_quality(val+1) end, + }, + widgets.CycleHotkeyLabel{ + view_id='max_quality', + frame={r=1, t=3, w=18}, + label='Max quality:', + label_below=true, + key_back='CUSTOM_SHIFT_Q', + key='CUSTOM_SHIFT_W', + options={ + {label='Ordinary', value=0}, + {label='Well Crafted', value=1}, + {label='Finely Crafted', value=2}, + {label='Superior', value=3}, + {label='Exceptional', value=4}, + {label='Masterful', value=5}, + {label='Artifact', value=6}, + }, + enabled=enable_item_quality, + on_change=function(val) self:set_max_quality(val+1) end, + }, + widgets.RangeSlider{ + frame={l=0, t=6}, + num_stops=7, + get_left_idx_fn=function() + return self.subviews.min_quality:getOptionValue() + 1 + end, + get_right_idx_fn=function() + return self.subviews.max_quality:getOptionValue() + 1 + end, + on_left_change=self:callback('set_min_quality'), + on_right_change=self:callback('set_max_quality'), + active=enable_item_quality, + }, + }, + }, + widgets.Panel{ + view_id='footer', + frame={l=0, r=0, b=0, h=FOOTER_HEIGHT}, + frame_inset={t=1, l=1}, + subviews={ + widgets.HotkeyLabel{ + frame={l=0, t=0}, + label='Toggle', + auto_width=true, + key='SELECT', + }, + widgets.HotkeyLabel{ + frame={l=0, t=2}, + label='Done', + auto_width=true, + key='LEAVESCREEN', + }, + widgets.HotkeyLabel{ + frame={l=30, t=0}, + label='Invert selection', + auto_width=true, + key='CUSTOM_SHIFT_I', + on_activate=self:callback('invert_materials'), + }, + widgets.HotkeyLabel{ + frame={l=30, t=2}, + label='Reset filter', + auto_width=true, + key='CUSTOM_SHIFT_X', + on_activate=self:callback('clear_filter'), + }, + }, + } + } + + -- replace the FilteredList's built-in EditField with our own + self.subviews.materials_mats.list.frame.t = 0 + self.subviews.materials_mats.edit.visible = false + self.subviews.materials_mats.edit = self.subviews.search + self.subviews.search.on_change = self.subviews.materials_mats:callback('onFilterChange') +end + +local MAT_ENABLED_PEN = to_pen{ch=string.char(251), fg=COLOR_LIGHTGREEN} +local MAT_DISABLED_PEN = to_pen{ch='x', fg=COLOR_RED} + +local function make_cat_choice(label, cat, key, cats) + local enabled = cats[cat] + local icon = nil + if not cats.unset then + icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + end + return { + text=label, + key=key, + enabled=enabled, + cat=cat, + icon=icon, + } +end + +local function make_mat_choice(name, props, enabled, cats) + local quantity = tonumber(props.count) + local text = ('%5d - %s'):format(quantity, name) + local icon = nil + if not cats.unset then + icon = enabled and MAT_ENABLED_PEN or MAT_DISABLED_PEN + end + return { + text=text, + enabled=enabled, + icon=icon, + name=name, + cat=props.category, + quantity=quantity, + } +end + +function QualityAndMaterialsPage:refresh() + local summary = self.desc + local subviews = self.subviews + + local buildingplan = require('plugins.buildingplan') + + local heat = buildingplan.getHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type) + if heat >= 2 then summary = 'Magma safe ' .. summary + elseif heat == 1 then summary = 'Fire safe ' .. summary + else summary = 'Any ' .. summary + end + + local specials = buildingplan.getSpecials(uibs.building_type, uibs.building_subtype, uibs.custom_type) + if next(specials) then + local specials_list = {} + for special in pairs(specials) do + table.insert(specials_list, special) + end + summary = summary .. ' [' .. table.concat(specials_list, ', ') .. ']' + end + + local quality = buildingplan.getQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + subviews.decorated:setOption(quality.decorated ~= 0) + subviews.min_quality:setOption(quality.min_quality) + subviews.max_quality:setOption(quality.max_quality) + + local cats = buildingplan.getMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + local category_choices={ + make_cat_choice('Stone', 'stone', 'CUSTOM_SHIFT_S', cats), + make_cat_choice('Wood', 'wood', 'CUSTOM_SHIFT_O', cats), + make_cat_choice('Metal', 'metal', 'CUSTOM_SHIFT_M', cats), + make_cat_choice('Glass', 'glass', 'CUSTOM_SHIFT_G', cats), + make_cat_choice('Gem', 'gem', 'CUSTOM_SHIFT_E', cats), + make_cat_choice('Clay', 'clay', 'CUSTOM_SHIFT_C', cats), + make_cat_choice('Cloth', 'cloth', 'CUSTOM_SHIFT_L', cats), + make_cat_choice('Silk', 'silk', 'CUSTOM_SHIFT_K', cats), + make_cat_choice('Yarn', 'yarn', 'CUSTOM_SHIFT_Y', cats), + } + self.subviews.materials_categories:setChoices(category_choices) + + local mats = buildingplan.getMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + local mat_choices = {} + local hide_zero = self.subviews.hide_zero:getOptionValue() + local enabled_mat_names = {} + for name,props in pairs(mats) do + local enabled = props.enabled == 'true' and cats[props.category] + if not cats.unset and enabled then + table.insert(enabled_mat_names, name) + end + if not hide_zero or tonumber(props.count) > 0 then + table.insert(mat_choices, make_mat_choice(name, props, enabled, cats)) + end + end + table.sort(mat_choices, self.subviews.mat_sort:getOptionValue()) + + local prev_filter = self.subviews.search.text + self.subviews.materials_mats:setChoices(mat_choices) + self.subviews.materials_mats:setFilter(prev_filter) + + if #enabled_mat_names > 0 then + table.sort(enabled_mat_names) + summary = summary .. (' of %s'):format(table.concat(enabled_mat_names, ', ')) + end + + self.summary = summary + self.dirty = false + self:updateLayout() +end + +function QualityAndMaterialsPage:toggle_category(_, choice) + local cats = {} + if not choice.icon then + -- toggling from unset to something is set + table.insert(cats, choice.cat) + else + choice.enabled = not choice.enabled + for _,c in ipairs(self.subviews.materials_categories:getChoices()) do + if c.enabled then + table.insert(cats, c.cat) + end + end + end + require('plugins.buildingplan').setMaterialMaskFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, cats) + self.dirty = true +end + +function QualityAndMaterialsPage:toggle_material(_, choice) + local mats = {} + if not choice.icon then + -- toggling from unset to something is set + table.insert(mats, choice.name) + else + for _,c in ipairs(self.subviews.materials_mats:getChoices()) do + local enabled = c.enabled + if choice.name == c.name then + enabled = not c.enabled + end + if enabled then + table.insert(mats, c.name) + end + end + end + require('plugins.buildingplan').setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) + self.dirty = true +end + +function QualityAndMaterialsPage:invert_materials() + local mats = {} + for _,c in ipairs(self.subviews.materials_mats:getChoices()) do + if not c.icon then return end + if not c.enabled then + table.insert(mats, c.name) + end + end + require('plugins.buildingplan').setMaterialFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, mats) + self.dirty = true +end + +function QualityAndMaterialsPage:clear_filter() + require('plugins.buildingplan').clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1) + self.dirty = true +end + +function QualityAndMaterialsPage:set_decorated(decorated) + local subviews = self.subviews + require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, + decorated and 1 or 0, subviews.min_quality:getOptionValue(), subviews.max_quality:getOptionValue()) + self.dirty = true +end + +function QualityAndMaterialsPage:set_min_quality(idx) + idx = math.min(6, math.max(0, idx-1)) + local subviews = self.subviews + subviews.min_quality:setOption(idx) + if subviews.max_quality:getOptionValue() < idx then + subviews.max_quality:setOption(idx) + end + require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, + subviews.decorated:getOptionValue() and 1 or 0, idx, subviews.max_quality:getOptionValue()) + self.dirty = true +end + +function QualityAndMaterialsPage:set_max_quality(idx) + idx = math.min(6, math.max(0, idx-1)) + local subviews = self.subviews + subviews.max_quality:setOption(idx) + if subviews.min_quality:getOptionValue() > idx then + subviews.min_quality:setOption(idx) + end + require('plugins.buildingplan').setQualityFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.index-1, + subviews.decorated:getOptionValue() and 1 or 0, subviews.min_quality:getOptionValue(), idx) + self.dirty = true +end + +function QualityAndMaterialsPage:draw_divider(dc) + local y2 = dc.height - 1 + for y=0,y2 do + dc:seek(0, y) + if y == 0 then + dc:char(nil, pens.VERT_TOP_PEN) + elseif y == y2 then + dc:char(nil, pens.VERT_BOT_PEN) + else + dc:char(nil, pens.VERT_MID_PEN) + end + end +end + +function QualityAndMaterialsPage:onRenderFrame(dc, rect) + QualityAndMaterialsPage.super.onRenderFrame(self, dc, rect) + if self.dirty then + self:refresh() + end +end + +-------------------------------- +-- GlobalSettingsPage +-- + +GlobalSettingsPage = defclass(GlobalSettingsPage, widgets.ResizingPanel) +GlobalSettingsPage.ATTRS{ + autoarrange_subviews=true, + frame={t=0, l=0}, + frame_style=gui.INTERIOR_FRAME, +} + +function GlobalSettingsPage:init() + self:addviews{ + widgets.WrappedLabel{ + frame={l=0}, + text_to_wrap='These options will affect the selection of "Generic Materials" for all future buildings.', + }, + widgets.Panel{ + frame={h=1}, + }, + widgets.ToggleHotkeyLabel{ + view_id='blocks', + frame={l=0}, + key='CUSTOM_B', + label='Blocks', + label_width=8, + on_change=self:callback('update_setting', 'blocks'), + }, + widgets.ToggleHotkeyLabel{ + view_id='logs', + frame={l=0}, + key='CUSTOM_L', + label='Logs', + label_width=8, + on_change=self:callback('update_setting', 'logs'), + }, + widgets.ToggleHotkeyLabel{ + view_id='boulders', + frame={l=0}, + key='CUSTOM_O', + label='Boulders', + label_width=8, + on_change=self:callback('update_setting', 'boulders'), + }, + widgets.ToggleHotkeyLabel{ + view_id='bars', + frame={l=0}, + key='CUSTOM_R', + label='Bars', + label_width=8, + on_change=self:callback('update_setting', 'bars'), + }, + } + + self:init_settings() +end + +function GlobalSettingsPage:init_settings() + local settings = require('plugins.buildingplan').getGlobalSettings() + local subviews = self.subviews + subviews.blocks:setOption(settings.blocks) + subviews.logs:setOption(settings.logs) + subviews.boulders:setOption(settings.boulders) + subviews.bars:setOption(settings.bars) +end + +function GlobalSettingsPage:update_setting(setting, val) + dfhack.run_command('buildingplan', 'set', setting, tostring(val)) + self:init_settings() +end + +-------------------------------- +-- FilterSelection +-- + +FilterSelection = defclass(FilterSelection, widgets.Window) +FilterSelection.ATTRS{ + frame_title='Choose filters', + frame={w=55, h=53, l=30, t=8}, + frame_inset={t=1}, + resizable=true, + index=DEFAULT_NIL, + desc=DEFAULT_NIL, + autoarrange_subviews=true, +} + +function FilterSelection:init() + self:addviews{ + widgets.TabBar{ + frame={t=0}, + labels={ + 'Quality and materials', + 'Global settings', + }, + on_select=function(idx) + self.subviews.pages:setSelected(idx) + self:updateLayout() + end, + get_cur_page=function() return self.subviews.pages:getSelected() end, + key='CUSTOM_CTRL_T', + }, + widgets.Widget{ + frame={h=1}, + }, + widgets.Pages{ + view_id='pages', + frame={t=5, l=0, b=0, r=0}, + subviews={ + QualityAndMaterialsPage{ + index=self.index, + desc=self.desc + }, + GlobalSettingsPage{}, + }, + }, + } +end + +FilterSelectionScreen = defclass(FilterSelectionScreen, gui.ZScreen) +FilterSelectionScreen.ATTRS { + focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/filterselection', + pass_movement_keys=true, + pass_mouse_clicks=false, + defocusable=false, + index=DEFAULT_NIL, + desc=DEFAULT_NIL, +} + +function FilterSelectionScreen:init() + self:addviews{ + FilterSelection{ + index=self.index, + desc=self.desc + } + } +end + +function FilterSelectionScreen:onShow() + -- don't let the building "shadow" follow the mouse cursor while this screen is open + df.global.game.main_interface.bottom_mode_selected = -1 +end + +function FilterSelectionScreen:onDismiss() + -- re-enable building shadow + df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT +end + +return _ENV diff --git a/plugins/lua/buildingplan/inspectoroverlay.lua b/plugins/lua/buildingplan/inspectoroverlay.lua new file mode 100644 index 000000000..5262eccc8 --- /dev/null +++ b/plugins/lua/buildingplan/inspectoroverlay.lua @@ -0,0 +1,148 @@ +local _ENV = mkmodule('plugins.buildingplan.inspectoroverlay') + +local gui = require('gui') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +reset_inspector_flag = false + +local function get_building_filters() + local bld = dfhack.gui.getSelectedBuilding() + return dfhack.buildings.getFiltersByType({}, + bld:getType(), bld:getSubtype(), bld:getCustomType()) +end + +-------------------------------- +-- InspectorLine +-- + +InspectorLine = defclass(InspectorLine, widgets.Panel) +InspectorLine.ATTRS{ + idx=DEFAULT_NIL, +} + +function InspectorLine:init() + self.frame.h = 2 + self.visible = function() return #get_building_filters() >= self.idx end + self:addviews{ + widgets.Label{ + frame={t=0, l=0}, + text={{text=self:callback('get_desc_string')}}, + }, + widgets.Label{ + frame={t=1, l=2}, + text={{text=self:callback('get_status_line')}}, + }, + } +end + +function InspectorLine:get_desc_string() + if self.desc then return self.desc end + self.desc = require('plugins.buildingplan').getDescString(dfhack.gui.getSelectedBuilding(), self.idx-1) + return self.desc +end + +function InspectorLine:get_status_line() + if self.status then return self.status end + local queue_pos = require('plugins.buildingplan').getQueuePosition(dfhack.gui.getSelectedBuilding(), self.idx-1) + if queue_pos <= 0 then + return 'Item attached' + end + self.status = ('Position in line: %d'):format(queue_pos) + return self.status +end + +function InspectorLine:reset() + self.desc = nil + self.status = nil +end + +-------------------------------- +-- InspectorOverlay +-- + +InspectorOverlay = defclass(InspectorOverlay, overlay.OverlayWidget) +InspectorOverlay.ATTRS{ + default_pos={x=-41,y=14}, + default_enabled=true, + viewscreens='dwarfmode/ViewSheets/BUILDING', + frame={w=30, h=15}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, +} + +function InspectorOverlay:init() + self:addviews{ + widgets.Label{ + frame={t=0, l=0}, + text='Waiting for items:', + }, + InspectorLine{view_id='item1', frame={t=2, l=0}, idx=1}, + InspectorLine{view_id='item2', frame={t=4, l=0}, idx=2}, + InspectorLine{view_id='item3', frame={t=6, l=0}, idx=3}, + InspectorLine{view_id='item4', frame={t=8, l=0}, idx=4}, + widgets.HotkeyLabel{ + frame={t=11, l=0}, + label='adjust filters', + key='CUSTOM_CTRL_F', + visible=false, -- until implemented + }, + widgets.HotkeyLabel{ + frame={t=12, l=0}, + label='make top priority', + key='CUSTOM_CTRL_T', + on_activate=self:callback('make_top_priority'), + }, + } +end + +function InspectorOverlay:reset() + self.subviews.item1:reset() + self.subviews.item2:reset() + self.subviews.item3:reset() + self.subviews.item4:reset() + reset_inspector_flag = false +end + +function InspectorOverlay:make_top_priority() + require('plugins.buildingplan').makeTopPriority(dfhack.gui.getSelectedBuilding()) + self:reset() +end + +local RESUME_BUTTON_FRAME = {t=15, h=3, r=73, w=25} + +local function mouse_is_over_resume_button(rect) + local x,y = dfhack.screen.getMousePos() + if not x then return false end + if y < RESUME_BUTTON_FRAME.t or y > RESUME_BUTTON_FRAME.t + RESUME_BUTTON_FRAME.h - 1 then + return false + end + if x > rect.x2 - RESUME_BUTTON_FRAME.r + 1 or x < rect.x2 - RESUME_BUTTON_FRAME.r - RESUME_BUTTON_FRAME.w + 2 then + return false + end + return true +end + +function InspectorOverlay:onInput(keys) + if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then + return false + end + if keys._MOUSE_L_DOWN and mouse_is_over_resume_button(self.frame_parent_rect) then + return true + elseif keys._MOUSE_L_DOWN or keys._MOUSE_R_DOWN or keys.LEAVESCREEN then + self:reset() + end + return InspectorOverlay.super.onInput(self, keys) +end + +function InspectorOverlay:render(dc) + if not require('plugins.buildingplan').isPlannedBuilding(dfhack.gui.getSelectedBuilding()) then + return + end + if reset_inspector_flag then + self:reset() + end + InspectorOverlay.super.render(self, dc) +end + +return _ENV diff --git a/plugins/lua/buildingplan/itemselection.lua b/plugins/lua/buildingplan/itemselection.lua new file mode 100644 index 000000000..84e866502 --- /dev/null +++ b/plugins/lua/buildingplan/itemselection.lua @@ -0,0 +1,421 @@ +local _ENV = mkmodule('plugins.buildingplan.itemselection') + +local gui = require('gui') +local pens = require('plugins.buildingplan.pens') +local utils = require('utils') +local widgets = require('gui.widgets') + +local uibs = df.global.buildreq +local to_pen = dfhack.pen.parse + +local BUILD_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREEN, keep_lower=true} +local BUILD_TEXT_HPEN = to_pen{fg=COLOR_WHITE, bg=COLOR_GREEN, keep_lower=true} + +-- map of building type -> {set=set of recently used, list=list of recently used} +-- most recent entries are at the *end* of the list +local recently_used = {} + +function get_automaterial_selection(building_type) + local tracker = recently_used[building_type] + if not tracker or not tracker.list then return end + return tracker.list[#tracker.list] +end + +local function sort_by_type(a, b) + local ad, bd = a.data, b.data + return ad.item_type < bd.item_type or + (ad.item_type == bd.item_type and ad.item_subtype < bd.item_subtype) or + (ad.item_type == bd.item_type and ad.item_subtype == bd.item_subtype and a.search_key < b.search_key) or + (ad.item_type == bd.item_type and ad.item_subtype == bd.item_subtype and a.search_key == b.search_key and ad.quality > bd.quality) +end + +local function sort_by_recency(a, b) + local tracker = recently_used[uibs.building_type] + if not tracker then return sort_by_type(a, b) end + local recent_a, recent_b = tracker.set[a.search_key], tracker.set[b.search_key] + -- if they're both in the set, return the one with the greater index, + -- indicating more recent + if recent_a and recent_b then return recent_a > recent_b end + if recent_a and not recent_b then return true end + if not recent_a and recent_b then return false end + return sort_by_type(a, b) +end + +local function sort_by_name(a, b) + return a.search_key < b.search_key or + (a.search_key == b.search_key and sort_by_type(a, b)) +end + +local function sort_by_quantity(a, b) + local ad, bd = a.data, b.data + return ad.quantity > bd.quantity or + (ad.quantity == bd.quantity and sort_by_type(a, b)) +end + +ItemSelection = defclass(ItemSelection, widgets.Window) +ItemSelection.ATTRS{ + frame_title='Choose items', + frame={w=56, h=24, l=4, t=7}, + resizable=true, + index=DEFAULT_NIL, + desc=DEFAULT_NIL, + quantity=DEFAULT_NIL, + autoselect=DEFAULT_NIL, + on_submit=DEFAULT_NIL, + on_cancel=DEFAULT_NIL, +} + +function ItemSelection:init() + self.num_selected = 0 + self.selected_set = {} + local plural = self.quantity == 1 and '' or 's' + + local choices = self:get_choices(sort_by_recency) + + if self.autoselect then + self:do_autoselect(choices) + if self.num_selected >= self.quantity then + self:submit(choices) + return + end + end + + self:addviews{ + widgets.Panel{ + view_id='header', + frame={t=0, h=3}, + subviews={ + widgets.Label{ + frame={t=0, l=0, r=16}, + text={ + self.desc, plural, NEWLINE, + ('Select up to %d item%s ('):format(self.quantity, plural), + {text=function() return self.num_selected end}, + ' selected)', + }, + }, + widgets.Label{ + frame={r=0, w=15, t=0, h=3}, + text_pen=BUILD_TEXT_PEN, + text_hpen=BUILD_TEXT_HPEN, + text={ + ' Use filter ', NEWLINE, + ' for remaining ', NEWLINE, + ' items ', + }, + on_click=self:callback('submit'), + visible=function() return self.num_selected < self.quantity end, + }, + widgets.Label{ + frame={r=0, w=15, t=0, h=3}, + text_pen=BUILD_TEXT_PEN, + text_hpen=BUILD_TEXT_HPEN, + text={ + ' ', NEWLINE, + ' Continue ', NEWLINE, + ' ', + }, + on_click=self:callback('submit'), + visible=function() return self.num_selected >= self.quantity end, + }, + }, + }, + } + + self:addviews{ + widgets.Panel{ + view_id='body', + frame={t=self.subviews.header.frame.h, b=4}, + subviews={ + widgets.EditField{ + view_id='search', + frame={l=1, t=0}, + label_text='Search: ', + on_char=function(ch) return ch:match('[%l -]') end, + }, + widgets.CycleHotkeyLabel{ + frame={l=1, t=2}, + key='CUSTOM_SHIFT_R', + label='Sort by:', + options={ + {label='Recently used', value=sort_by_recency}, + {label='Name', value=sort_by_name}, + {label='Amount', value=sort_by_quantity}, + }, + on_change=self:callback('on_sort'), + }, + widgets.Panel{ + frame={l=0, t=3, r=0, b=0}, + frame_style=gui.INTERIOR_FRAME, + subviews={ + widgets.FilteredList{ + view_id='flist', + frame={t=0, b=0}, + case_sensitive=false, + choices=choices, + icon_width=2, + on_submit=self:callback('toggle_group'), + }, + }, + }, + }, + }, + widgets.Panel{ + view_id='footer', + frame={l=1, r=1, b=0, h=3}, + subviews={ + widgets.HotkeyLabel{ + frame={l=0, h=1, t=0}, + key='KEYBOARD_CURSOR_RIGHT_FAST', + key_sep='----: ', -- these hypens function as "padding" to be overwritten by the next Label + label='Use one', + auto_width=true, + on_activate=function() self:increment_group(self.subviews.flist.list:getSelected()) end, + }, + widgets.Label{ + frame={l=6, w=5, t=0}, + text_pen=COLOR_LIGHTGREEN, + text='Right', -- this overrides the "6----" characters from the previous HotkeyLabel + }, + widgets.HotkeyLabel{ + frame={l=1, h=1, t=1}, + key='KEYBOARD_CURSOR_LEFT_FAST', + key_sep='---: ', -- these hypens function as "padding" to be overwritten by the next Label + label='Use one fewer', + auto_width=true, + on_activate=function() self:decrement_group(self.subviews.flist.list:getSelected()) end, + }, + widgets.Label{ + frame={l=7, w=4, t=1}, + text_pen=COLOR_LIGHTGREEN, + text='Left', -- this overrides the "4---" characters from the previous HotkeyLabel + }, + widgets.HotkeyLabel{ + frame={l=6, t=2, h=2}, + key='SELECT', + label='Use all/none', + auto_width=true, + on_activate=function() self:toggle_group(self.subviews.flist.list:getSelected()) end, + }, + widgets.HotkeyLabel{ + frame={r=5, t=0}, + key='LEAVESCREEN', + label='Go back', + auto_width=true, + on_activate=self:callback('on_cancel'), + }, + widgets.HotkeyLabel{ + frame={r=4, t=2}, + key='CUSTOM_SHIFT_C', + label='Continue', + auto_width=true, + on_activate=self:callback('submit'), + }, + }, + }, + } + + self.subviews.flist.list.frame.t = 0 + self.subviews.flist.edit.visible = false + self.subviews.flist.edit = self.subviews.search + self.subviews.search.on_change = self.subviews.flist:callback('onFilterChange') +end + +-- resort and restore selection +function ItemSelection:on_sort(sort_fn) + local flist = self.subviews.flist + local saved_filter = flist:getFilter() + flist:setFilter('') + flist:setChoices(self:get_choices(sort_fn), flist:getSelected()) + flist:setFilter(saved_filter) +end + +local function make_search_key(str) + local out = '' + for c in str:gmatch("[%w%s]") do + out = out .. c + end + return out +end + +function ItemSelection:get_choices(sort_fn) + local item_ids = require('plugins.buildingplan').getAvailableItems(uibs.building_type, + uibs.building_subtype, uibs.custom_type, self.index-1) + local buckets = {} + for _,item_id in ipairs(item_ids) do + local item = df.item.find(item_id) + if not item then goto continue end + local desc = dfhack.items.getDescription(item, 0, true) + if buckets[desc] then + local bucket = buckets[desc] + table.insert(bucket.data.item_ids, item_id) + bucket.data.quantity = bucket.data.quantity + 1 + else + local entry = { + search_key=make_search_key(desc), + icon=self:callback('get_entry_icon', item_id), + data={ + item_ids={item_id}, + item_type=item:getType(), + item_subtype=item:getSubtype(), + quantity=1, + quality=item:getQuality(), + selected=0, + }, + } + buckets[desc] = entry + end + ::continue:: + end + local choices = {} + for desc,choice in pairs(buckets) do + local data = choice.data + choice.text = { + {width=10, text=function() return ('%d/%d'):format(data.selected, data.quantity) end}, + {gap=2, text=desc}, + } + table.insert(choices, choice) + end + table.sort(choices, sort_fn) + return choices +end + +function ItemSelection:do_autoselect(choices) + if #choices == 0 then return end + local desired = get_automaterial_selection(uibs.building_type) + if choices[1].search_key ~= desired then return end + self:toggle_group(1, choices[1]) +end + +function ItemSelection:increment_group(idx, choice) + local data = choice.data + if self.quantity <= self.num_selected then return false end + if data.selected >= data.quantity then return false end + data.selected = data.selected + 1 + self.num_selected = self.num_selected + 1 + local item_id = data.item_ids[data.selected] + self.selected_set[item_id] = true + return true +end + +function ItemSelection:decrement_group(idx, choice) + local data = choice.data + if data.selected <= 0 then return false end + local item_id = data.item_ids[data.selected] + self.selected_set[item_id] = nil + self.num_selected = self.num_selected - 1 + data.selected = data.selected - 1 + return true +end + +function ItemSelection:toggle_group(idx, choice) + local data = choice.data + if data.selected > 0 then + while self:decrement_group(idx, choice) do end + else + while self:increment_group(idx, choice) do end + end +end + +function ItemSelection:get_entry_icon(item_id) + return self.selected_set[item_id] and pens.SELECTED_ITEM_PEN or nil +end + +local function track_recently_used(choices) + -- use same set for all subtypes + local tracker = ensure_key(recently_used, uibs.building_type) + for _,choice in ipairs(choices) do + local data = choice.data + if data.selected <= 0 then goto continue end + local key = choice.search_key + local recent_set = ensure_key(tracker, 'set') + local recent_list = ensure_key(tracker, 'list') + if recent_set[key] then + if recent_list[#recent_list] ~= key then + for i,v in ipairs(recent_list) do + if v == key then + table.remove(recent_list, i) + table.insert(recent_list, key) + break + end + end + tracker.set = utils.invert(recent_list) + end + else + -- only keep most recent 10 + if #recent_list >= 10 then + -- remove least recently used from list and set + recent_set[table.remove(recent_list, 1)] = nil + end + table.insert(recent_list, key) + recent_set[key] = #recent_list + end + ::continue:: + end +end + +function ItemSelection:submit(choices) + local selected_items = {} + for item_id in pairs(self.selected_set) do + table.insert(selected_items, item_id) + end + if #selected_items > 0 then + track_recently_used(choices or self.subviews.flist:getChoices()) + end + self.on_submit(selected_items) +end + +function ItemSelection:onInput(keys) + if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + self.on_cancel() + return true + elseif keys._MOUSE_L_DOWN then + local list = self.subviews.flist.list + local idx = list:getIdxUnderMouse() + if idx then + list:setSelected(idx) + local modstate = dfhack.internal.getModstate() + if modstate & 2 > 0 then -- ctrl + local choice = list:getChoices()[idx] + if modstate & 1 > 0 then -- shift + self:decrement_group(idx, choice) + else + self:increment_group(idx, choice) + end + return true + end + end + end + return ItemSelection.super.onInput(self, keys) +end + +ItemSelectionScreen = defclass(ItemSelectionScreen, gui.ZScreen) +ItemSelectionScreen.ATTRS { + focus_path='dwarfmode/Building/Placement/dfhack/lua/buildingplan/itemselection', + force_pause=true, + pass_movement_keys=true, + pass_pause=false, + pass_mouse_clicks=false, + defocusable=false, + index=DEFAULT_NIL, + desc=DEFAULT_NIL, + quantity=DEFAULT_NIL, + autoselect=DEFAULT_NIL, + on_submit=DEFAULT_NIL, + on_cancel=DEFAULT_NIL, +} + +function ItemSelectionScreen:init() + self:addviews{ + ItemSelection{ + index=self.index, + desc=self.desc, + quantity=self.quantity, + autoselect=self.autoselect, + on_submit=self.on_submit, + on_cancel=self.on_cancel, + } + } +end + +return _ENV diff --git a/plugins/lua/buildingplan/pens.lua b/plugins/lua/buildingplan/pens.lua new file mode 100644 index 000000000..e69a4c210 --- /dev/null +++ b/plugins/lua/buildingplan/pens.lua @@ -0,0 +1,43 @@ +local _ENV = mkmodule('plugins.buildingplan.pens') + +GOOD_TILE_PEN, BAD_TILE_PEN = nil, nil +VERT_TOP_PEN, VERT_MID_PEN, VERT_BOT_PEN = nil, nil, nil +HORI_LEFT_PEN, HORI_MID_PEN, HORI_RIGHT_PEN = nil, nil, nil +BUTTON_START_PEN, BUTTON_END_PEN = nil, nil +SELECTED_ITEM_PEN = nil +MINI_TEXT_PEN, MINI_TEXT_HPEN, MINI_BUTT_PEN, MINI_BUTT_HPEN = nil, nil, nil, nil + +local to_pen = dfhack.pen.parse + +local tp = function(base, offset) + if base == -1 then return nil end + return base + offset +end + +function reload_pens() + GOOD_TILE_PEN = to_pen{ch='o', fg=COLOR_GREEN, tile=dfhack.screen.findGraphicsTile('CURSORS', 1, 2)} + BAD_TILE_PEN = to_pen{ch='X', fg=COLOR_RED, tile=dfhack.screen.findGraphicsTile('CURSORS', 3, 0)} + + local tb_texpos = dfhack.textures.getThinBordersTexposStart() + VERT_TOP_PEN = to_pen{tile=tp(tb_texpos, 10), ch=194, fg=COLOR_GREY, bg=COLOR_BLACK} + VERT_MID_PEN = to_pen{tile=tp(tb_texpos, 4), ch=179, fg=COLOR_GREY, bg=COLOR_BLACK} + VERT_BOT_PEN = to_pen{tile=tp(tb_texpos, 11), ch=193, fg=COLOR_GREY, bg=COLOR_BLACK} + + local mb_texpos = dfhack.textures.getMediumBordersTexposStart() + HORI_LEFT_PEN = to_pen{tile=tp(mb_texpos, 12), ch=195, fg=COLOR_GREY, bg=COLOR_BLACK} + HORI_MID_PEN = to_pen{tile=tp(mb_texpos, 5), ch=196, fg=COLOR_GREY, bg=COLOR_BLACK} + HORI_RIGHT_PEN = to_pen{tile=tp(mb_texpos, 13), ch=180, fg=COLOR_GREY, bg=COLOR_BLACK} + + local cp_texpos = dfhack.textures.getControlPanelTexposStart() + BUTTON_START_PEN = to_pen{tile=tp(cp_texpos, 13), ch='[', fg=COLOR_YELLOW} + BUTTON_END_PEN = to_pen{tile=tp(cp_texpos, 15), ch=']', fg=COLOR_YELLOW} + SELECTED_ITEM_PEN = to_pen{tile=tp(cp_texpos, 9), ch=string.char(251), fg=COLOR_YELLOW} + + MINI_TEXT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_GREY} + MINI_TEXT_HPEN = to_pen{fg=COLOR_BLACK, bg=COLOR_WHITE} + MINI_BUTT_PEN = to_pen{fg=COLOR_BLACK, bg=COLOR_LIGHTRED} + MINI_BUTT_HPEN = to_pen{fg=COLOR_WHITE, bg=COLOR_RED} +end +reload_pens() + +return _ENV diff --git a/plugins/lua/buildingplan/planneroverlay.lua b/plugins/lua/buildingplan/planneroverlay.lua new file mode 100644 index 000000000..803e9ae99 --- /dev/null +++ b/plugins/lua/buildingplan/planneroverlay.lua @@ -0,0 +1,989 @@ +local _ENV = mkmodule('plugins.buildingplan.planneroverlay') + +local itemselection = require('plugins.buildingplan.itemselection') +local filterselection = require('plugins.buildingplan.filterselection') +local gui = require('gui') +local guidm = require('gui.dwarfmode') +local json = require('json') +local overlay = require('plugins.overlay') +local pens = require('plugins.buildingplan.pens') +local utils = require('utils') +local widgets = require('gui.widgets') +require('dfhack.buildings') + +config = config or json.open('dfhack-config/buildingplan.json') + +local uibs = df.global.buildreq + +reset_counts_flag = false + +local function get_cur_filters() + return dfhack.buildings.getFiltersByType({}, uibs.building_type, + uibs.building_subtype, uibs.custom_type) +end + +local function is_choosing_area() + return uibs.selection_pos.x >= 0 +end + +-- TODO: reuse data in quickfort database +local function get_selection_size_limits() + local btype = uibs.building_type + if btype == df.building_type.Bridge + or btype == df.building_type.FarmPlot + or btype == df.building_type.RoadPaved + or btype == df.building_type.RoadDirt then + return {w=31, h=31} + elseif btype == df.building_type.AxleHorizontal then + return uibs.direction == 1 and {w=1, h=31} or {w=31, h=1} + elseif btype == df.building_type.Rollers then + return (uibs.direction == 1 or uibs.direction == 3) and {w=31, h=1} or {w=1, h=31} + end +end + +local function get_selected_bounds(selection_pos, pos) + selection_pos = selection_pos or uibs.selection_pos + if not is_choosing_area() then return end + + pos = pos or uibs.pos + + local bounds = { + x1=math.min(selection_pos.x, pos.x), + x2=math.max(selection_pos.x, pos.x), + y1=math.min(selection_pos.y, pos.y), + y2=math.max(selection_pos.y, pos.y), + z1=math.min(selection_pos.z, pos.z), + z2=math.max(selection_pos.z, pos.z), + } + + -- clamp to map edges + bounds = { + x1=math.max(0, bounds.x1), + x2=math.min(df.global.world.map.x_count-1, bounds.x2), + y1=math.max(0, bounds.y1), + y2=math.min(df.global.world.map.y_count-1, bounds.y2), + z1=math.max(0, bounds.z1), + z2=math.min(df.global.world.map.z_count-1, bounds.z2), + } + + local limits = get_selection_size_limits() + if limits then + -- clamp to building type area limit + bounds = { + x1=math.max(selection_pos.x - (limits.w-1), bounds.x1), + x2=math.min(selection_pos.x + (limits.w-1), bounds.x2), + y1=math.max(selection_pos.y - (limits.h-1), bounds.y1), + y2=math.min(selection_pos.y + (limits.h-1), bounds.y2), + z1=bounds.z1, + z2=bounds.z2, + } + end + + return bounds +end + +local function get_cur_area_dims(bounds) + if not bounds and not is_choosing_area() then return 1, 1, 1 end + bounds = bounds or get_selected_bounds() + if not bounds then return 1, 1, 1 end + return bounds.x2 - bounds.x1 + 1, + bounds.y2 - bounds.y1 + 1, + bounds.z2 - bounds.z1 + 1 +end + +local function is_pressure_plate() + return uibs.building_type == df.building_type.Trap + and uibs.building_subtype == df.trap_type.PressurePlate +end + +local function is_weapon_trap() + return uibs.building_type == df.building_type.Trap + and uibs.building_subtype == df.trap_type.WeaponTrap +end + +local function is_spike_trap() + return uibs.building_type == df.building_type.Weapon +end + +local function is_weapon_or_spike_trap() + return is_weapon_trap() or is_spike_trap() +end + +-- adjusted from CycleHotkeyLabel on the planner panel +local weapon_quantity = 1 + +-- TODO: this should account for erroring constructions +local function get_quantity(filter, hollow, bounds) + if is_pressure_plate() then + local flags = uibs.plate_info.flags + return (flags.units and 1 or 0) + (flags.water and 1 or 0) + + (flags.magma and 1 or 0) + (flags.track and 1 or 0) + elseif (is_weapon_trap() and filter.vector_id == df.job_item_vector_id.ANY_WEAPON) or is_spike_trap() then + return weapon_quantity + end + local quantity = filter.quantity or 1 + local dimx, dimy, dimz = get_cur_area_dims(bounds) + if quantity < 1 then + return (((dimx * dimy) // 4) + 1) * dimz + end + if hollow and dimx > 2 and dimy > 2 then + return quantity * (2*dimx + 2*dimy - 4) * dimz + end + return quantity * dimx * dimy * dimz +end + +local function cur_building_has_no_area() + if uibs.building_type == df.building_type.Construction then return false end + local filters = dfhack.buildings.getFiltersByType({}, + uibs.building_type, uibs.building_subtype, uibs.custom_type) + -- this works because all variable-size buildings have either no item + -- filters or a quantity of -1 for their first (and only) item + return filters and filters[1] and (not filters[1].quantity or filters[1].quantity > 0) +end + +local function is_construction() + return uibs.building_type == df.building_type.Construction +end + +local function is_tutorial_open() + local help = df.global.game.main_interface.help + return help.open and + help.context == df.help_context_type.START_TUTORIAL_WORKSHOPS_AND_TASKS +end + +local function is_plannable() + return not is_tutorial_open() and + get_cur_filters() and + not (is_construction() and + uibs.building_subtype == df.construction_type.TrackNSEW) +end + +local function is_slab() + return uibs.building_type == df.building_type.Slab +end + +local function is_stairs() + return is_construction() + and uibs.building_subtype == df.construction_type.UpDownStair +end + +local direction_panel_frame = {t=4, h=13, w=46, r=28} + +local direction_panel_types = utils.invert{ + df.building_type.Bridge, + df.building_type.ScrewPump, + df.building_type.WaterWheel, + df.building_type.AxleHorizontal, + df.building_type.Rollers, +} + +local function has_direction_panel() + return direction_panel_types[uibs.building_type] + or (uibs.building_type == df.building_type.Trap + and uibs.building_subtype == df.trap_type.TrackStop) +end + +local pressure_plate_panel_frame = {t=4, h=37, w=46, r=28} + +local function has_pressure_plate_panel() + return is_pressure_plate() +end + +local function is_over_options_panel() + local frame = nil + if has_direction_panel() then + frame = direction_panel_frame + elseif has_pressure_plate_panel() then + frame = pressure_plate_panel_frame + else + return false + end + local v = widgets.Widget{frame=frame} + local rect = gui.mkdims_wh(0, 0, dfhack.screen.getWindowSize()) + v:updateLayout(gui.ViewRect{rect=rect}) + return v:getMousePos() +end + +-------------------------------- +-- ItemLine +-- + +ItemLine = defclass(ItemLine, widgets.Panel) +ItemLine.ATTRS{ + idx=DEFAULT_NIL, + is_selected_fn=DEFAULT_NIL, + is_hollow_fn=DEFAULT_NIL, + on_select=DEFAULT_NIL, + on_filter=DEFAULT_NIL, + on_clear_filter=DEFAULT_NIL, +} + +function ItemLine:init() + self.frame.h = 2 + self.visible = function() return #get_cur_filters() >= self.idx end + self:addviews{ + widgets.Label{ + view_id='item_symbol', + frame={t=0, l=0}, + text=string.char(16), -- this is the "â–º" character + text_pen=COLOR_YELLOW, + auto_width=true, + visible=self.is_selected_fn, + }, + widgets.Label{ + view_id='item_desc', + frame={t=0, l=2}, + text={ + {text=self:callback('get_item_line_text'), + pen=function() return gui.invert_color(COLOR_WHITE, self.is_selected_fn()) end}, + }, + }, + widgets.Label{ + view_id='item_filter', + frame={t=0, l=28}, + text={ + {text=self:callback('get_filter_text'), + pen=function() return gui.invert_color(COLOR_LIGHTCYAN, self.is_selected_fn()) end}, + }, + auto_width=true, + on_click=function() self.on_filter(self.idx) end, + }, + widgets.Label{ + frame={t=0, l=42}, + text='[clear]', + text_pen=COLOR_LIGHTRED, + auto_width=true, + visible=self:callback('has_filter'), + on_click=function() self.on_clear_filter(self.idx) end, + }, + widgets.Label{ + frame={t=1, l=2}, + text={ + {gap=2, text=function() return self.note end, + pen=function() return self.note_pen end}, + }, + }, + } +end + +function ItemLine:reset() + self.desc = nil + self.available = nil +end + +function ItemLine:onInput(keys) + if keys._MOUSE_L_DOWN and self:getMousePos() then + self.on_select(self.idx) + end + return ItemLine.super.onInput(self, keys) +end + +function ItemLine:get_item_line_text() + local idx = self.idx + local filter = get_cur_filters()[idx] + local quantity = get_quantity(filter, self.is_hollow_fn()) + + local buildingplan = require('plugins.buildingplan') + self.desc = self.desc or buildingplan.get_desc(filter) + + self.available = self.available or buildingplan.countAvailableItems( + uibs.building_type, uibs.building_subtype, uibs.custom_type, idx - 1) + if self.available >= quantity then + self.note_pen = COLOR_GREEN + self.note = ' Available now' + else + self.note_pen = COLOR_BROWN + self.note = ' Will link later' + end + self.note = string.char(192) .. self.note -- character 192 is "â””" + + return ('%d %s%s'):format(quantity, self.desc, quantity == 1 and '' or 's') +end + +function ItemLine:has_filter() + return require('plugins.buildingplan').hasFilter( + uibs.building_type, uibs.building_subtype, uibs.custom_type, self.idx-1) +end + +function ItemLine:get_filter_text() + -- TODO: make this show the filter's materials instead of "edit filters" + return self:has_filter() and '[edit filters]' or '[any material]' +end + +function ItemLine:reduce_quantity(used_quantity) + if not self.available then return end + local filter = get_cur_filters()[self.idx] + used_quantity = used_quantity or get_quantity(filter, self.is_hollow_fn()) + self.available = math.max(0, self.available - used_quantity) +end + +local function get_placement_errors() + local out = '' + for _,str in ipairs(uibs.errors) do + if #out > 0 then out = out .. NEWLINE end + out = out .. str.value + end + return out +end + +-------------------------------- +-- PlannerOverlay +-- + +PlannerOverlay = defclass(PlannerOverlay, overlay.OverlayWidget) +PlannerOverlay.ATTRS{ + default_pos={x=5,y=9}, + default_enabled=true, + viewscreens='dwarfmode/Building/Placement', + frame={w=56, h=22}, +} + +function PlannerOverlay:init() + self.selected = 1 + self.state = ensure_key(config.data, 'planner') + + local main_panel = widgets.Panel{ + view_id='main', + frame={t=1, l=0, r=0, h=14}, + frame_style=gui.INTERIOR_MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, + visible=self:callback('is_not_minimized'), + } + + local minimized_panel = widgets.Panel{ + frame={t=0, r=1, w=17, h=1}, + subviews={ + widgets.Label{ + frame={t=0, r=0, h=1}, + text={ + {text=' show Planner ', pen=pens.MINI_TEXT_PEN, hpen=pens.MINI_TEXT_HPEN}, + {text='['..string.char(31)..']', pen=pens.MINI_BUTT_PEN, hpen=pens.MINI_BUTT_HPEN}, + }, + visible=self:callback('is_minimized'), + on_click=self:callback('toggle_minimized'), + }, + widgets.Label{ + frame={t=0, r=0, h=1}, + text={ + {text=' hide Planner ', pen=pens.MINI_TEXT_PEN, hpen=pens.MINI_TEXT_HPEN}, + {text='['..string.char(30)..']', pen=pens.MINI_BUTT_PEN, hpen=pens.MINI_BUTT_HPEN}, + }, + visible=self:callback('is_not_minimized'), + on_click=self:callback('toggle_minimized'), + }, + }, + } + + local function make_is_selected_fn(idx) + return function() return self.selected == idx end + end + + local function on_select_fn(idx) + self.selected = idx + end + + local function is_hollow_fn() + return self.subviews.hollow:getOptionValue() + end + + local buildingplan = require('plugins.buildingplan') + + main_panel:addviews{ + widgets.Label{ + frame={}, + auto_width=true, + text='No items required.', + visible=function() return #get_cur_filters() == 0 end, + }, + ItemLine{view_id='item1', frame={t=0, l=0, r=0}, idx=1, + is_selected_fn=make_is_selected_fn(1), is_hollow_fn=is_hollow_fn, + on_select=on_select_fn, on_filter=self:callback('set_filter'), + on_clear_filter=self:callback('clear_filter')}, + ItemLine{view_id='item2', frame={t=2, l=0, r=0}, idx=2, + is_selected_fn=make_is_selected_fn(2), is_hollow_fn=is_hollow_fn, + on_select=on_select_fn, on_filter=self:callback('set_filter'), + on_clear_filter=self:callback('clear_filter')}, + ItemLine{view_id='item3', frame={t=4, l=0, r=0}, idx=3, + is_selected_fn=make_is_selected_fn(3), is_hollow_fn=is_hollow_fn, + on_select=on_select_fn, on_filter=self:callback('set_filter'), + on_clear_filter=self:callback('clear_filter')}, + ItemLine{view_id='item4', frame={t=6, l=0, r=0}, idx=4, + is_selected_fn=make_is_selected_fn(4), is_hollow_fn=is_hollow_fn, + on_select=on_select_fn, on_filter=self:callback('set_filter'), + on_clear_filter=self:callback('clear_filter')}, + widgets.CycleHotkeyLabel{ + view_id='hollow', + frame={b=4, l=1, w=21}, + key='CUSTOM_H', + label='Hollow area:', + visible=is_construction, + options={ + {label='No', value=false}, + {label='Yes', value=true, pen=COLOR_GREEN}, + }, + }, + widgets.CycleHotkeyLabel{ + view_id='stairs_top_subtype', + frame={b=5, l=23, w=30}, + key='CUSTOM_R', + label='Top Stair Type: ', + visible=is_stairs, + options={ + {label='Auto', value='auto'}, + {label='UpDown', value=df.construction_type.UpDownStair}, + {label='Down', value=df.construction_type.DownStair}, + }, + }, + widgets.CycleHotkeyLabel { + view_id='stairs_bottom_subtype', + frame={b=4, l=23, w=30}, + key='CUSTOM_B', + label='Bottom Stair Type:', + visible=is_stairs, + options={ + {label='Auto', value='auto'}, + {label='UpDown', value=df.construction_type.UpDownStair}, + {label='Up', value=df.construction_type.UpStair}, + }, + }, + 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={ + {label='(1)', value=1, pen=COLOR_YELLOW}, + {label='(2)', value=2, pen=COLOR_YELLOW}, + {label='(3)', value=3, pen=COLOR_YELLOW}, + {label='(4)', value=4, pen=COLOR_YELLOW}, + {label='(5)', value=5, pen=COLOR_YELLOW}, + {label='(6)', value=6, pen=COLOR_YELLOW}, + {label='(7)', value=7, pen=COLOR_YELLOW}, + {label='(8)', value=8, pen=COLOR_YELLOW}, + {label='(9)', value=9, pen=COLOR_YELLOW}, + {label='(10)', value=10, pen=COLOR_YELLOW}, + }, + on_change=function(val) weapon_quantity = val end, + }, + widgets.ToggleHotkeyLabel { + view_id='engraved', + frame={b=4, l=1, w=22}, + key='CUSTOM_T', + label='Engraved only:', + visible=is_slab, + on_change=function(val) + buildingplan.setSpecial(uibs.building_type, uibs.building_subtype, uibs.custom_type, 'engraved', val) + end, + }, + widgets.Label{ + frame={b=2, l=23}, + text_pen=COLOR_DARKGREY, + text={ + 'Selected area: ', + {text=function() + return ('%dx%dx%d'):format(get_cur_area_dims(self.saved_placement)) + end + }, + }, + visible=function() + return not cur_building_has_no_area() and (self.saved_placement or is_choosing_area()) + end, + }, + widgets.Panel{ + visible=function() return #get_cur_filters() > 0 end, + subviews={ + widgets.HotkeyLabel{ + frame={b=1, l=1, w=22}, + key='CUSTOM_F', + label=function() + return buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1) + and 'Edit filter' or 'Set filter' + end, + on_activate=function() self:set_filter(self.selected) end, + }, + widgets.HotkeyLabel{ + frame={b=0, l=1, w=22}, + key='CUSTOM_X', + label='Clear filter', + on_activate=function() self:clear_filter(self.selected) end, + enabled=function() + return buildingplan.hasFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, self.selected - 1) + end + }, + widgets.CycleHotkeyLabel{ + view_id='choose', + frame={b=0, l=23}, + key='CUSTOM_Z', + label='Choose items:', + label_below=true, + options={ + {label='With filters', value=0}, + { + label=function() + local automaterial = itemselection.get_automaterial_selection(uibs.building_type) + return ('Last used (%s)'):format(automaterial or 'pick manually') + end, + value=2, + }, + {label='Manually', value=1}, + }, + initial_option=0, + on_change=function(choose) + buildingplan.setChooseItems(uibs.building_type, uibs.building_subtype, uibs.custom_type, choose) + end, + }, + widgets.CycleHotkeyLabel{ + view_id='safety', + frame={b=2, l=23, w=25}, + key='CUSTOM_G', + label='Building safety:', + options={ + {label='Any', value=0}, + {label='Magma', value=2, pen=COLOR_RED}, + {label='Fire', value=1, pen=COLOR_LIGHTRED}, + }, + initial_option=0, + on_change=function(heat) + buildingplan.setHeatSafetyFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, heat) + end, + visible=false, -- until we can make this work the way it's intended + }, + }, + }, + } + + local divider_widget = widgets.Panel{ + view_id='divider', + frame={t=10, l=0, r=0, h=1}, + on_render=self:callback('draw_divider_h'), + visible=self:callback('is_not_minimized'), + } + + local error_panel = widgets.ResizingPanel{ + view_id='errors', + frame={t=15, l=0, r=0}, + frame_style=gui.BOLD_FRAME, + frame_background=gui.CLEAR_PEN, + visible=self:callback('is_not_minimized'), + } + + error_panel:addviews{ + widgets.WrappedLabel{ + frame={t=0, l=1, r=0}, + text_pen=COLOR_LIGHTRED, + text_to_wrap=get_placement_errors, + visible=function() return #uibs.errors > 0 end, + }, + widgets.Label{ + frame={t=0, l=1, r=0}, + text_pen=COLOR_GREEN, + text='OK to build', + visible=function() return #uibs.errors == 0 end, + }, + } + + local prev_next_selector = widgets.Panel{ + frame={h=1}, + auto_width=true, + subviews={ + widgets.HotkeyLabel{ + frame={t=0, l=1, w=9}, + key='CUSTOM_SHIFT_Q', + key_sep='\0', + label=': Prev/', + on_activate=function() self.selected = ((self.selected - 2) % #get_cur_filters()) + 1 end, + }, + widgets.HotkeyLabel{ + frame={t=0, l=2, w=1}, + key='CUSTOM_Q', + on_activate=function() self.selected = (self.selected % #get_cur_filters()) + 1 end, + }, + widgets.Label{ + frame={t=0,l=10}, + text='next item', + on_click=function() self.selected = (self.selected % #get_cur_filters()) + 1 end, + }, + }, + visible=function() return #get_cur_filters() > 1 end, + } + + local black_bar = widgets.Panel{ + frame={t=0, l=1, w=37, h=1}, + frame_inset=0, + frame_background=gui.CLEAR_PEN, + visible=self:callback('is_not_minimized'), + subviews={ + prev_next_selector, + }, + } + + self:addviews{ + black_bar, + minimized_panel, + main_panel, + divider_widget, + error_panel, + } +end + +function PlannerOverlay:is_minimized() + return self.state.minimized +end + +function PlannerOverlay:is_not_minimized() + return not self.state.minimized +end + +function PlannerOverlay:toggle_minimized() + self.state.minimized = not self.state.minimized + config:write() +end + +function PlannerOverlay:draw_divider_h(dc) + local x2 = dc.width -1 + for x=0,x2 do + dc:seek(x, 0) + if x == 0 then + dc:char(nil, pens.HORI_LEFT_PEN) + elseif x == x2 then + dc:char(nil, pens.HORI_RIGHT_PEN) + else + dc:char(nil, pens.HORI_MID_PEN) + end + end +end + +function PlannerOverlay:reset() + self.subviews.item1:reset() + self.subviews.item2:reset() + self.subviews.item3:reset() + self.subviews.item4:reset() + reset_counts_flag = false +end + +function PlannerOverlay:set_filter(idx) + filterselection.FilterSelectionScreen{index=idx, desc=require('plugins.buildingplan').get_desc(get_cur_filters()[idx])}:show() +end + +function PlannerOverlay:clear_filter(idx) + desc=require('plugins.buildingplan').clearFilter(uibs.building_type, uibs.building_subtype, uibs.custom_type, idx-1) +end + +local function get_placement_data() + local direction = uibs.direction + local bounds = get_selected_bounds() + local width, height, depth = get_cur_area_dims(bounds) + local _, adjusted_width, adjusted_height = dfhack.buildings.getCorrectSize( + width, height, uibs.building_type, uibs.building_subtype, + uibs.custom_type, direction) + -- get the upper-left corner of the building/area at min z-level + local start_pos = bounds and xyz2pos(bounds.x1, bounds.y1, bounds.z1) or + xyz2pos( + uibs.pos.x - adjusted_width//2, + uibs.pos.y - adjusted_height//2, + uibs.pos.z) + if uibs.building_type == df.building_type.ScrewPump then + if direction == df.screw_pump_direction.FromSouth then + start_pos.y = start_pos.y + 1 + elseif direction == df.screw_pump_direction.FromEast then + start_pos.x = start_pos.x + 1 + end + end + local min_x, max_x = start_pos.x, start_pos.x + local min_y, max_y = start_pos.y, start_pos.y + local min_z, max_z = start_pos.z, start_pos.z + if adjusted_width == 1 and adjusted_height == 1 + and (width > 1 or height > 1 or depth > 1) then + max_x = min_x + width - 1 + max_y = min_y + height - 1 + max_z = math.max(uibs.selection_pos.z, uibs.pos.z) + end + return { + x1=min_x, y1=min_y, z1=min_z, + x2=max_x, y2=max_y, z2=max_z, + width=adjusted_width, + height=adjusted_height + } +end + +function PlannerOverlay:save_placement() + self.saved_placement = get_placement_data() + if (uibs.selection_pos:isValid()) then + self.saved_selection_pos_valid = true + self.saved_selection_pos = copyall(uibs.selection_pos) + self.saved_pos = copyall(uibs.pos) + uibs.selection_pos:clear() + else + local sp = self.saved_placement + self.saved_selection_pos = xyz2pos(sp.x1, sp.y1, sp.z1) + self.saved_pos = xyz2pos(sp.x2, sp.y2, sp.z2) + self.saved_pos.x = self.saved_pos.x + sp.width - 1 + self.saved_pos.y = self.saved_pos.y + sp.height - 1 + end +end + +function PlannerOverlay:restore_placement() + if self.saved_selection_pos_valid then + uibs.selection_pos = self.saved_selection_pos + self.saved_selection_pos_valid = nil + else + uibs.selection_pos:clear() + end + self.saved_selection_pos = nil + self.saved_pos = nil + local placement_data = self.saved_placement + self.saved_placement = nil + return placement_data +end + +function PlannerOverlay:onInput(keys) + if not is_plannable() then return false end + if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then + if uibs.selection_pos:isValid() then + uibs.selection_pos:clear() + return true + end + self.selected = 1 + self.subviews.hollow:setOption(false) + self:reset() + reset_counts_flag = true + return false + end + if keys.CUSTOM_ALT_M then + self:toggle_minimized() + return true + end + if PlannerOverlay.super.onInput(self, keys) then + return true + end + if self:is_minimized() then return false end + if keys._MOUSE_L_DOWN then + if is_over_options_panel() then return false end + local detect_rect = copyall(self.frame_rect) + detect_rect.height = self.subviews.main.frame_rect.height + + self.subviews.errors.frame_rect.height + detect_rect.y2 = detect_rect.y1 + detect_rect.height - 1 + if self.subviews.main:getMousePos(gui.ViewRect{rect=detect_rect}) + or self.subviews.errors:getMousePos() then + return true + end + if not is_construction() and #uibs.errors > 0 then return true end + if dfhack.gui.getMousePos() then + if is_choosing_area() or cur_building_has_no_area() then + local filters = get_cur_filters() + local num_filters = #filters + local choose = self.subviews.choose:getOptionValue() + if choose > 0 then + local bounds = get_selected_bounds() + self:save_placement() + local autoselect = choose == 2 + local is_hollow = self.subviews.hollow:getOptionValue() + local chosen_items, active_screens = {}, {} + local pending = num_filters + df.global.game.main_interface.bottom_mode_selected = -1 + for idx = num_filters,1,-1 do + chosen_items[idx] = {} + local filter = filters[idx] + local selection_screen = itemselection.ItemSelectionScreen{ + index=idx, + desc=require('plugins.buildingplan').get_desc(filter), + quantity=get_quantity(filter, is_hollow, bounds), + autoselect=autoselect, + on_submit=function(items) + chosen_items[idx] = items + if active_screens[idx] then + active_screens[idx]:dismiss() + active_screens[idx] = nil + else + active_screens[idx] = true + end + pending = pending - 1 + if pending == 0 then + df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT + self:place_building(self:restore_placement(), chosen_items) + end + end, + on_cancel=function() + for i,scr in pairs(active_screens) do + scr:dismiss() + end + df.global.game.main_interface.bottom_mode_selected = df.main_bottom_mode_type.BUILDING_PLACEMENT + self:restore_placement() + end, + } + if active_screens[idx] then + -- we've already returned via autoselect + active_screens[idx] = nil + else + active_screens[idx] = selection_screen:show() + end + end + else + self:place_building(get_placement_data()) + end + return true + elseif not is_choosing_area() then + return false + end + end + end + return keys._MOUSE_L or keys.SELECT +end + +function PlannerOverlay:render(dc) + if not is_plannable() then return end + self.subviews.errors:updateLayout() + PlannerOverlay.super.render(self, dc) +end + +local ONE_BY_ONE = xy2pos(1, 1) + +function PlannerOverlay:onRenderFrame(dc, rect) + PlannerOverlay.super.onRenderFrame(self, dc, rect) + + if reset_counts_flag then + self:reset() + local buildingplan = require('plugins.buildingplan') + self.subviews.engraved:setOption(buildingplan.getSpecials( + uibs.building_type, uibs.building_subtype, uibs.custom_type).engraved or false) + self.subviews.choose:setOption(buildingplan.getChooseItems( + uibs.building_type, uibs.building_subtype, uibs.custom_type)) + self.subviews.safety:setOption(buildingplan.getHeatSafetyFilter( + uibs.building_type, uibs.building_subtype, uibs.custom_type)) + end + + if self:is_minimized() then return end + + local bounds = get_selected_bounds(self.saved_selection_pos, self.saved_pos) + if not bounds then return end + + local hollow = self.subviews.hollow:getOptionValue() + local default_pen = (self.saved_selection_pos or #uibs.errors == 0) and pens.GOOD_TILE_PEN or pens.BAD_TILE_PEN + + local get_pen_fn = is_construction() and function(pos) + return dfhack.buildings.checkFreeTiles(pos, ONE_BY_ONE) and pens.GOOD_TILE_PEN or pens.BAD_TILE_PEN + end or function() + return default_pen + end + + local function get_overlay_pen(pos) + if not hollow then return get_pen_fn(pos) end + if pos.x == bounds.x1 or pos.x == bounds.x2 or + pos.y == bounds.y1 or pos.y == bounds.y2 then + return get_pen_fn(pos) + end + return gui.TRANSPARENT_PEN + end + + guidm.renderMapOverlay(get_overlay_pen, bounds) +end + +function PlannerOverlay:get_stairs_subtype(pos, bounds) + local subtype = uibs.building_subtype + if pos.z == bounds.z1 then + local opt = self.subviews.stairs_bottom_subtype:getOptionValue() + if opt == 'auto' then + local tt = dfhack.maps.getTileType(pos) + local shape = df.tiletype.attrs[tt].shape + if shape ~= df.tiletype_shape.STAIR_DOWN and shape ~= df.tiletype_shape.STAIR_UPDOWN then + subtype = df.construction_type.UpStair + end + else + subtype = opt + end + elseif pos.z == bounds.z2 then + local opt = self.subviews.stairs_top_subtype:getOptionValue() + if opt == 'auto' then + local tt = dfhack.maps.getTileType(pos) + local shape = df.tiletype.attrs[tt].shape + if shape ~= df.tiletype_shape.STAIR_UP and shape ~= df.tiletype_shape.STAIR_UPDOWN then + subtype = df.construction_type.DownStair + end + else + subtype = opt + end + end + return subtype +end + +function PlannerOverlay:place_building(placement_data, chosen_items) + local pd = placement_data + local blds = {} + local hollow = self.subviews.hollow:getOptionValue() + local subtype = uibs.building_subtype + local filters = get_cur_filters() + if is_pressure_plate() or is_spike_trap() then + filters[1].quantity = get_quantity(filters[1]) + elseif is_weapon_trap() then + filters[2].quantity = get_quantity(filters[2]) + end + for z=pd.z1,pd.z2 do for y=pd.y1,pd.y2 do for x=pd.x1,pd.x2 do + if hollow and x ~= pd.x1 and x ~= pd.x2 and y ~= pd.y1 and y ~= pd.y2 then + goto continue + end + local pos = xyz2pos(x, y, z) + if is_stairs() then + subtype = self:get_stairs_subtype(pos, pd) + 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} + if err then + -- it's ok if some buildings fail to build + goto continue + end + -- assign fields for the types that need them. we can't pass them all in + -- to the call to constructBuilding since attempting to assign unrelated + -- fields to building types that don't support them causes errors. + for k,v in pairs(bld) do + if k == 'friction' then bld.friction = uibs.friction end + if k == 'use_dump' then bld.use_dump = uibs.use_dump end + if k == 'dump_x_shift' then bld.dump_x_shift = uibs.dump_x_shift end + if k == 'dump_y_shift' then bld.dump_y_shift = uibs.dump_y_shift end + if k == 'speed' then bld.speed = uibs.speed end + if k == 'plate_info' then utils.assign(bld.plate_info, uibs.plate_info) end + end + table.insert(blds, bld) + ::continue:: + end end end + local used_quantity = is_construction() and #blds or false + self.subviews.item1:reduce_quantity(used_quantity) + self.subviews.item2:reduce_quantity(used_quantity) + self.subviews.item3:reduce_quantity(used_quantity) + self.subviews.item4:reduce_quantity(used_quantity) + local buildingplan = require('plugins.buildingplan') + for _,bld in ipairs(blds) do + -- attach chosen items and reduce job_item quantity + if chosen_items then + local job = bld.jobs[0] + local jitems = job.job_items + local num_filters = #get_cur_filters() + for idx=1,num_filters do + local item_ids = chosen_items[idx] + local jitem = jitems[num_filters-idx] + while jitem.quantity > 0 and #item_ids > 0 do + local item_id = item_ids[#item_ids] + local item = df.item.find(item_id) + if not item then + dfhack.printerr(('item no longer available: %d'):format(item_id)) + break + end + if not dfhack.job.attachJobItem(job, item, df.job_item_ref.T_role.Hauled, idx-1, -1) then + dfhack.printerr(('cannot attach item: %d'):format(item_id)) + break + end + jitem.quantity = jitem.quantity - 1 + item_ids[#item_ids] = nil + end + end + end + buildingplan.addPlannedBuilding(bld) + end + buildingplan.scheduleCycle() + uibs.selection_pos:clear() +end + +return _ENV diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index 13964645b..1ddf190b4 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -12,10 +12,11 @@ setmetatable(keys, { -- Mouse keys will be sent as a string instead of interface_key local MOUSE_LEFT = "MOUSE_LEFT" local MOUSE_RIGHT = "MOUSE_RIGHT" + --[[ The screen where a confirmation has been triggered Note that this is *not* necessarily the topmost viewscreen, so do not use gui.getCurViewscreen() or related functions. ]] -screen = nil +--screen = nil function if_nil(obj, default) if obj == nil then @@ -118,7 +119,9 @@ zone_remove.message = "Are you sure you want to remove this zone?" burrow_remove = defconf('burrow-remove') function burrow_remove.intercept_key(key) - return key == MOUSE_LEFT and df.global.game.main_interface.current_hover == 171 + return key == MOUSE_LEFT and + (df.global.game.main_interface.current_hover == 171 or + df.global.game.main_interface.current_hover == 168) end burrow_remove.title = "Remove burrow" burrow_remove.message = "Are you sure you want to remove this burrow?" diff --git a/plugins/lua/design.lua b/plugins/lua/design.lua new file mode 100644 index 000000000..ec3b4c79b --- /dev/null +++ b/plugins/lua/design.lua @@ -0,0 +1,3 @@ +local _ENV = mkmodule('plugins.design') + +return _ENV diff --git a/plugins/lua/hotkeys.lua b/plugins/lua/hotkeys.lua index 0eb09d244..fca0f1f27 100644 --- a/plugins/lua/hotkeys.lua +++ b/plugins/lua/hotkeys.lua @@ -5,6 +5,19 @@ local helpdb = require('helpdb') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') +local function get_command(cmdline) + local first_word = cmdline:trim():split(' +')[1] + if first_word:startswith(':') then first_word = first_word:sub(2) end + return first_word +end + +function should_hide_armok(cmdline) + local command = get_command(cmdline) + return dfhack.getHideArmokTools() and + helpdb.is_entry(command) and + helpdb.get_entry_tags(command).armok +end + -- ----------------- -- -- HotspotMenuWidget -- -- ----------------- -- @@ -26,7 +39,7 @@ HotspotMenuWidget.ATTRS{ -- 'new_region', -- conflicts with vanilla panel layouts 'savegame', 'setupdwarfgame', - 'title', + 'title/Default', 'update_region', 'world' }, @@ -106,8 +119,8 @@ local function get_bindings_to_hotkeys(hotkeys, bindings) return bindings_to_hotkeys end --- number of non-text tiles: icon, space, space between cmd and hk, scrollbar -local LIST_BUFFER = 2 + 1 + 1 +-- number of non-text tiles: icon, space between cmd and hk, scrollbar+margin +local LIST_BUFFER = 2 + 1 + 3 local function get_choices(hotkeys, bindings, is_inverted) local choices, max_width, seen = {}, 0, {} @@ -143,7 +156,7 @@ local function get_choices(hotkeys, bindings, is_inverted) -- adjust width of command fields so the hotkey tokens are right justified for _,choice in ipairs(choices) do local command_token = choice.text[1] - command_token.width = max_width - choice.hk_width - 3 + command_token.width = max_width - choice.hk_width - (LIST_BUFFER - 1) end return choices, max_width @@ -232,10 +245,9 @@ end function Menu:onSelect(_, choice) if not choice or #self.subviews == 0 then return end - local first_word = choice.command:trim():split(' +')[1] - if first_word:startswith(':') then first_word = first_word:sub(2) end - self.subviews.help.text_to_wrap = helpdb.is_entry(first_word) and - helpdb.get_entry_short_help(first_word) or 'Command not found' + local command = get_command(choice.command) + self.subviews.help.text_to_wrap = helpdb.is_entry(command) and + helpdb.get_entry_short_help(command) or 'Command not found' self.subviews.help_panel:updateLayout() end diff --git a/plugins/lua/orders.lua b/plugins/lua/orders.lua index 72bb6185e..50197fce4 100644 --- a/plugins/lua/orders.lua +++ b/plugins/lua/orders.lua @@ -19,15 +19,37 @@ local function do_clear() function() dfhack.run_command('orders', 'clear') end) end +local function get_import_choices() + return dfhack.run_command_silent('orders', 'list'):split('\n') +end + local function do_import() - local output = dfhack.run_command_silent('orders', 'list') - dialogs.ListBox{ - frame_title='Import Manager Orders', + local dlg + local function get_dlg() return dlg end + dlg = dialogs.ListBox{ + frame_title='Import/Delete Manager Orders', with_filter=true, - choices=output:split('\n'), - on_select=function(idx, choice) + choices=get_import_choices(), + on_select=function(_, choice) dfhack.run_command('orders', 'import', choice.text) end, + dismiss_on_select2=false, + on_select2=function(_, choice) + if choice.text:startswith('library/') then return end + local fname = 'dfhack-config/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, + function() + print('deleting ' .. fname) + os.remove(fname) + local list = get_dlg().subviews.list + local filter = list:getFilter() + list:setChoices(get_import_choices(), list:getSelected()) + list:setFilter(filter) + end) + end, + select2_hint='Delete file', }:show() end @@ -46,37 +68,87 @@ OrdersOverlay.ATTRS{ default_enabled=true, viewscreens='dwarfmode/Info/WORK_ORDERS', frame={w=30, h=4}, - frame_style=gui.MEDIUM_FRAME, - frame_background=gui.CLEAR_PEN, } function OrdersOverlay:init() - self:addviews{ - widgets.HotkeyLabel{ - frame={t=0, l=0}, - label='import', - key='CUSTOM_CTRL_I', - on_activate=do_import, - }, - widgets.HotkeyLabel{ - frame={t=1, l=0}, - label='export', - key='CUSTOM_CTRL_E', - on_activate=do_export, - }, - widgets.HotkeyLabel{ - frame={t=0, l=15}, - label='sort', - key='CUSTOM_CTRL_O', - on_activate=do_sort, + self.minimized = false + + local main_panel = widgets.Panel{ + frame={t=0, l=0, r=0, h=4}, + frame_style=gui.MEDIUM_FRAME, + frame_background=gui.CLEAR_PEN, + visible=function() return not self.minimized end, + subviews={ + widgets.HotkeyLabel{ + frame={t=0, l=0}, + label='import', + key='CUSTOM_CTRL_I', + auto_width=true, + on_activate=do_import, + }, + widgets.HotkeyLabel{ + frame={t=1, l=0}, + label='export', + key='CUSTOM_CTRL_E', + auto_width=true, + on_activate=do_export, + }, + widgets.HotkeyLabel{ + frame={t=0, l=15}, + label='sort', + key='CUSTOM_CTRL_O', + auto_width=true, + on_activate=do_sort, + }, + widgets.HotkeyLabel{ + frame={t=1, l=15}, + label='clear', + key='CUSTOM_CTRL_C', + auto_width=true, + on_activate=do_clear, + }, }, - widgets.HotkeyLabel{ - frame={t=1, l=15}, - label='clear', - key='CUSTOM_CTRL_C', - on_activate=do_clear, + } + + 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, + } +end + +function OrdersOverlay:onInput(keys) + if keys.CUSTOM_ALT_M then + self.minimized = not self.minimized + return true + end + if OrdersOverlay.super.onInput(self, keys) then + return true + end end OVERLAY_WIDGETS = { diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index 3d476bf0d..fda1edc95 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -8,6 +8,7 @@ local widgets = require('gui.widgets') local OVERLAY_CONFIG_FILE = 'dfhack-config/overlay.json' local OVERLAY_WIDGETS_VAR = 'OVERLAY_WIDGETS' +local GLOBAL_KEY = 'OVERLAY' local DEFAULT_X_POS, DEFAULT_Y_POS = -2, -2 @@ -291,7 +292,7 @@ local function load_widgets(env_name, env) end -- called directly from cpp on plugin enable -function reload() +function rescan() reset() for _,plugin in ipairs(dfhack.internal.listPlugins()) do @@ -311,6 +312,14 @@ function reload() reposition_widgets() end +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc ~= SC_WORLD_LOADED then + return + end + -- pick up widgets from active mods + rescan() +end + local function dump_widget_config(name, widget) local pos = overlay_config[name].pos print(('widget %s is positioned at x=%d, y=%d'):format(name, pos.x, pos.y)) @@ -438,7 +447,7 @@ function update_hotspot_widgets() end end -local function matches_focus_strings(db_entry, vs_name) +local function matches_focus_strings(db_entry, vs_name, vs) if not db_entry.focus_strings then return true end local matched = true local simple_vs_name = simplify_viewscreen_name(vs_name) @@ -456,9 +465,10 @@ end local function _update_viewscreen_widgets(vs_name, vs, now_ms) local vs_widgets = active_viewscreen_widgets[vs_name] if not vs_widgets then return end + local is_all = vs_name == 'all' now_ms = now_ms or dfhack.getTickCount() for name,db_entry in pairs(vs_widgets) do - if matches_focus_strings(db_entry, vs_name) and + if (is_all or matches_focus_strings(db_entry, vs_name, vs)) and do_update(name, db_entry, now_ms, vs) then return end @@ -474,12 +484,12 @@ function update_viewscreen_widgets(vs_name, vs) end end -local function _feed_viewscreen_widgets(vs_name, keys) +local function _feed_viewscreen_widgets(vs_name, vs, keys) local vs_widgets = active_viewscreen_widgets[vs_name] if not vs_widgets then return false end for _,db_entry in pairs(vs_widgets) do local w = db_entry.widget - if matches_focus_strings(db_entry, vs_name) and + if (not vs or matches_focus_strings(db_entry, vs_name, vs)) and detect_frame_change(w, function() return w:onInput(keys) end) then return true end @@ -487,9 +497,9 @@ local function _feed_viewscreen_widgets(vs_name, keys) return false end -function feed_viewscreen_widgets(vs_name, keys) - if not _feed_viewscreen_widgets(vs_name, keys) and - not _feed_viewscreen_widgets('all', keys) then +function feed_viewscreen_widgets(vs_name, vs, keys) + if not _feed_viewscreen_widgets(vs_name, vs, keys) and + not _feed_viewscreen_widgets('all', nil, keys) then return false end gui.markMouseClicksHandled(keys) @@ -499,21 +509,28 @@ function feed_viewscreen_widgets(vs_name, keys) return true end -local function _render_viewscreen_widgets(vs_name, dc) +local function _render_viewscreen_widgets(vs_name, vs, dc) local vs_widgets = active_viewscreen_widgets[vs_name] - if not vs_widgets then return false end + if not vs_widgets then return end dc = dc or gui.Painter.new() for _,db_entry in pairs(vs_widgets) do local w = db_entry.widget - if matches_focus_strings(db_entry, vs_name) then + if not vs or matches_focus_strings(db_entry, vs_name, vs) then detect_frame_change(w, function() w:render(dc) end) end end + return dc end -function render_viewscreen_widgets(vs_name) - local dc = _render_viewscreen_widgets(vs_name, nil) - _render_viewscreen_widgets('all', dc) +local force_refresh + +function render_viewscreen_widgets(vs_name, vs) + local dc = _render_viewscreen_widgets(vs_name, vs, nil) + _render_viewscreen_widgets('all', nil, dc) + if force_refresh then + force_refresh = nil + df.global.gps.force_full_display_count = 1 + end end -- called when the DF window is resized @@ -522,6 +539,7 @@ function reposition_widgets() for _,db_entry in pairs(widget_db) do db_entry.widget:updateLayout(sr) end + force_refresh = true end -- ------------------------------------------------- -- @@ -553,4 +571,57 @@ function OverlayWidget:init() self.frame.h = self.frame.h or 1 end +-- ------------------- -- +-- TitleVersionOverlay -- +-- ------------------- -- + +TitleVersionOverlay = defclass(TitleVersionOverlay, OverlayWidget) +TitleVersionOverlay.ATTRS{ + default_pos={x=7, y=2}, + default_enabled=true, + viewscreens='title/Default', + frame={w=35, h=5}, + autoarrange_subviews=1, +} + +function TitleVersionOverlay:init() + local text = {} + table.insert(text, 'DFHack ' .. dfhack.getDFHackVersion() .. + (dfhack.isRelease() and '' or (' (git: %s)'):format(dfhack.getGitCommit(true)))) + if #dfhack.getDFHackBuildID() > 0 then + table.insert(text, NEWLINE) + table.insert(text, 'Build ID: ' .. dfhack.getDFHackBuildID()) + end + if dfhack.isPrerelease() then + table.insert(text, NEWLINE) + table.insert(text, {text='Pre-release build', pen=COLOR_LIGHTRED}) + end + + self:addviews{ + widgets.Label{ + frame={t=0, l=0}, + text=text, + text_pen=COLOR_WHITE, + }, + widgets.HotkeyLabel{ + frame={l=0}, + label='Quickstart guide', + auto_width=true, + key='STRING_A063', + on_activate=function() dfhack.run_script('quickstart-guide') end, + }, + widgets.HotkeyLabel{ + frame={l=0}, + label='Control panel', + auto_width=true, + key='STRING_A047', + on_activate=function() dfhack.run_script('gui/control-panel') end, + }, + } +end + +OVERLAY_WIDGETS = { + title_version = TitleVersionOverlay, +} + return _ENV diff --git a/plugins/lua/stockpiles.lua b/plugins/lua/stockpiles.lua index ca8c28cd4..9e17cbe2b 100644 --- a/plugins/lua/stockpiles.lua +++ b/plugins/lua/stockpiles.lua @@ -1,244 +1,187 @@ local _ENV = mkmodule('plugins.stockpiles') ---[[ +local argparse = require('argparse') - Native functions: +local STOCKPILES_DIR = "dfhack-config/stockpiles"; +local STOCKPILES_LIBRARY_DIR = "hack/data/stockpiles"; - * stockpiles_list_settings(dir_path), list files in directory - * stockpiles_load(file), with full path - * stockpiles_save(file), with full path - * isEnabled() - ---]] --- -function safe_require(module) - local status, module = pcall(require, module) - return status and module or nil +local function get_sp_name(name, num) + if #name > 0 then return name end + return ('Stockpile %d'):format(num) end - -local gui = require 'gui' -local widgets = require('gui.widgets') -local dlg = require('gui.dialogs') -local script = require 'gui.script' -local persist = safe_require('persist-table') - - -function ListFilterDialog(args) - args.text = args.prompt or 'Type or select an option' - args.text_pen = COLOR_WHITE - args.with_filter = true - args.icon_width = 2 - - local choices = {} - - if not args.hide_none then - table.insert(choices, { - icon = '?', text = args.none_caption or 'none', - index = -1, name = -1 - }) +local STATUS_FMT = '%6s %s' +local function print_status() + local sps = df.global.world.buildings.other.STOCKPILE + print(('Current stockpiles: %d'):format(#sps)) + if #sps > 0 then + print() + print(STATUS_FMT:format('ID', 'Name')) + print(STATUS_FMT:format('------', '----------')) end - - local filter = args.item_filter - - for i,v in ipairs(args.items) do - if not filter or filter(v,-1) then - local name = v - local icon - table.insert(choices, { - icon = icon, search_key = string.lower(name), text = name, index = i - }) - end + for _,sp in ipairs(sps) do + print(STATUS_FMT:format(sp.id, get_sp_name(sp.name, sp.stockpile_number))) end +end - args.choices = choices - - if args.on_select then - local cb = args.on_select - args.on_select = function(idx, obj) - return cb(obj.index, args.items[obj.index]) +local function list_dir(path, prefix, filters) + local paths = dfhack.filesystem.listdir_recursive(path, 0, false) + if not paths then + dfhack.printerr(('Cannot find stockpile settings directory: "%s"'):format(path)) + return + end + local normalized_filters = {} + for _,filter in ipairs(filters or {}) do + table.insert(normalized_filters, filter:lower()) + end + for _,v in ipairs(paths) do + local normalized_path = prefix .. v.path:lower() + if v.isdir or not normalized_path:endswith('.dfstock') then goto continue end + normalized_path = normalized_path:sub(1, -9) + if #normalized_filters > 0 then + local matched = false + for _,filter in ipairs(normalized_filters) do + if normalized_path:find(filter, 1, true) then + matched = true + break + end + end + if not matched then goto continue end end + print(('%s%s'):format(prefix, v.path:sub(1, -9))) + ::continue:: end - - return dlg.ListBox(args) end -function showFilterPrompt(title, list, text,item_filter,hide_none) - ListFilterDialog{ - frame_title=title, - items=list, - prompt=text, - item_filter=item_filter, - hide_none=hide_none, - on_select=script.mkresume(true), - on_cancel=script.mkresume(false), - on_close=script.qresume(nil) - }:show() - - return script.wait() +local function list_settings_files(filters) + list_dir(STOCKPILES_DIR, '', filters) + list_dir(STOCKPILES_LIBRARY_DIR, 'library/', filters) end -function init() - if persist == nil then return end - if dfhack.isMapLoaded() then - if persist.GlobalTable.stockpiles == nil then - persist.GlobalTable.stockpiles = {} - persist.GlobalTable.stockpiles['settings_path'] = './stocksettings' - end +local function assert_safe_name(name) + if not name or #name == 0 then + qerror('name missing or empty') + end + if name:find('[^%w._]') then + qerror('name can only contain numbers, letters, periods, and underscores') end end -function tablify(iterableObject) - t={} - for k,v in ipairs(iterableObject) do - t[k] = v~=nil and v or 'nil' - end - return t +local function get_sp_id(opts) + if opts.id then return opts.id end + local sp = dfhack.gui.getSelectedStockpile() + if sp then return sp.id end + return nil end -local filename_invalid_regex = '[^A-Za-z0-9 ._-]' +local included_elements = { + containers=1, + general=2, + categories=4, + types=8, +} -function valid_filename(filename) - return not filename:match(filename_invalid_regex) -end +local function export_stockpile(name, opts) + assert_safe_name(name) + name = STOCKPILES_DIR .. '/' .. name -function sanitize_filename(filename) - local ret = '' - for i = 1, #filename do - local ch = filename:sub(i, i) - if valid_filename(ch) then - ret = ret .. ch - else - ret = ret .. '-' + local includedElements = 0 + for _,inc in ipairs(opts.includes) do + if included_elements[inc] then + includedElements = includedElements | included_elements[inc] end end - return ret -end -FilenameInputBox = defclass(FilenameInputBox, dlg.InputBox) -function FilenameInputBox:onInput(keys) - if not valid_filename(string.char(keys._STRING or 0)) and not keys.STRING_A000 then - keys._STRING = nil + if includedElements == 0 then + for _,v in pairs(included_elements) do + includedElements = includedElements | v + end end - FilenameInputBox.super.onInput(self, keys) -end -function showFilenameInputPrompt(title, text, tcolor, input, min_width) - FilenameInputBox{ - frame_title = title, - text = text, - text_pen = tcolor, - input = input, - frame_width = min_width, - on_input = script.mkresume(true), - on_cancel = script.mkresume(false), - on_close = script.qresume(nil) - }:show() - - return script.wait() + stockpiles_export(name, get_sp_id(opts), includedElements) end -function load_settings() - init() - local path = get_path() - local ok, list = pcall(stockpiles_list_settings, path) - if not ok then - show_message_box("Stockpile Settings", "The stockpile settings folder doesn't exist.", true) - return +local function import_stockpile(name, opts) + local is_library = false + if name:startswith('library/') then + name = name:sub(9) + is_library = true end - if #list == 0 then - show_message_box("Stockpile Settings", "There are no saved stockpile settings.", true) - return + assert_safe_name(name) + if not is_library and dfhack.filesystem.exists(STOCKPILES_DIR .. '/' .. name .. '.dfstock') then + name = STOCKPILES_DIR .. '/' .. name + else + name = STOCKPILES_LIBRARY_DIR .. '/' .. name end + stockpiles_import(name, get_sp_id(opts), opts.mode, table.concat(opts.filters, ',')) +end - local choice_list = {} - for i,v in ipairs(list) do - choice_list[i] = string.gsub(v, "/", "/ ") - choice_list[i] = string.gsub(choice_list[i], "-", " - ") - choice_list[i] = string.gsub(choice_list[i], "_", " ") - end +local valid_includes = {general=true, categories=true, types=true} - script.start(function() - local ok2,index,name=showFilterPrompt('Stockpile Settings', choice_list, 'Choose a stockpile', function(item) return true end, true) - if ok2 then - local filename = list[index]; - stockpiles_load(path..'/'..filename) +local function parse_include(arg) + local includes = argparse.stringList(arg, 'include') + for _,v in ipairs(includes) do + if not valid_includes[v] then + qerror(('invalid included element: "%s"'):format(v)) end - end) + end + return includes end -function save_settings(stockpile) - init() - script.start(function() - local suggested = stockpile.name - if #suggested == 0 then - suggested = 'Stock1' - end - suggested = sanitize_filename(suggested) - local path = get_path() - local sok,filename = showFilenameInputPrompt('Stockpile Settings', 'Enter filename', COLOR_WHITE, suggested) - if sok then - if filename == nil or filename == '' or not valid_filename(filename) then - script.showMessage('Stockpile Settings', 'Invalid File Name', COLOR_RED) - else - if not dfhack.filesystem.exists(path) then - dfhack.filesystem.mkdir(path) - end - stockpiles_save(path..'/'..filename) - end - end - end) -end +local valid_modes = {set=true, enable=true, disable=true} -function manage_settings(sp) - init() - if not guard() then return false end - script.start(function() - local list = {'Load', 'Save'} - local tok,i = script.showListPrompt('Stockpile Settings','Load or Save Settings?',COLOR_WHITE,tablify(list)) - if tok then - if i == 1 then - load_settings() - else - save_settings(sp) - end - end - end) +local function parse_mode(arg) + if not valid_modes[arg] then + qerror(('invalid mode: "%s"'):format(arg)) + end + return arg end -function show_message_box(title, msg, iserror) - local color = COLOR_WHITE - if iserror then - color = COLOR_RED +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return end - script.start(function() - script.showMessage(title, msg, color) - end) + + opts.includes = {} + opts.mode = 'set' + opts.filters = {} + + return argparse.processArgsGetopt(args, { + {'f', 'filter', hasArg=true, + handler=function(arg) opts.filters = argparse.stringList(arg) end}, + {'h', 'help', handler=function() opts.help = true end}, + {'i', 'include', hasArg=true, + handler=function(arg) opts.includes = parse_include(arg) end}, + {'m', 'mode', hasArg=true, + handler=function(arg) opts.mode = parse_mode(arg) end}, + {'s', 'stockpile', hasArg=true, + handler=function(arg) opts.id = argparse.nonnegativeInt(arg, 'stockpile') end}, + }) end -function guard() - if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Stockpile') then - qerror("This script requires a stockpile selected in the 'q' mode") +function parse_commandline(args) + local opts = {} + local positionals = process_args(opts, args) + + if opts.help or not positionals then return false end - return true -end -function set_path(path) - init() - if persist == nil then - qerror("This version of DFHack doesn't support setting the stockpile settings path. Sorry.") - return + local command = table.remove(positionals, 1) + if not command or command == 'status' then + print_status() + elseif command == 'list' then + list_settings_files(positionals) + elseif command == 'export' then + export_stockpile(positionals[1], opts) + elseif command == 'import' then + import_stockpile(positionals[1], opts) + else + return false end - persist.GlobalTable.stockpiles['settings_path'] = path -end -function get_path() - init() - if persist == nil then - return "stocksettings" - end - return persist.GlobalTable.stockpiles['settings_path'] + return true end return _ENV diff --git a/plugins/lua/tailor.lua b/plugins/lua/tailor.lua index e9a88bfe1..46b3526d9 100644 --- a/plugins/lua/tailor.lua +++ b/plugins/lua/tailor.lua @@ -32,6 +32,11 @@ function setMaterials(names) idxs.adamantine or -1) end +function setDebugMode(opt) + local fl = (opt[1] == "true" or opt[1] == "on") + tailor_setDebugFlag(fl) +end + function parse_commandline(...) local args, opts = {...}, {} local positionals = process_args(opts, args) @@ -47,6 +52,8 @@ function parse_commandline(...) tailor_doCycle() elseif command == 'materials' then setMaterials(positionals) + elseif command == 'debugging' then + setDebugMode(positionals) else return false end diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp index 4c7384f3b..784e17129 100644 --- a/plugins/overlay.cpp +++ b/plugins/overlay.cpp @@ -13,6 +13,7 @@ #include "df/viewscreen_titlest.h" #include "df/viewscreen_update_regionst.h" #include "df/viewscreen_worldst.h" +#include "df/world.h" #include "Debug.h" #include "LuaTools.h" @@ -27,6 +28,8 @@ using namespace DFHack; DFHACK_PLUGIN("overlay"); DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(world); + namespace DFHack { DBG_DECLARE(overlay, control, DebugCategory::LINFO); DBG_DECLARE(overlay, event, DebugCategory::LINFO); @@ -67,13 +70,16 @@ struct viewscreen_overlay : T { } DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { bool input_is_handled = false; - call_overlay_lua(NULL, "feed_viewscreen_widgets", 2, 1, - [&](lua_State *L) { - Lua::Push(L, T::_identity.getName()); - Lua::PushInterfaceKeys(L, *input); - }, [&](lua_State *L) { - input_is_handled = lua_toboolean(L, -1); - }); + // don't send input to the overlays if there is a modal dialog up + if (!world->status.popups.size()) + call_overlay_lua(NULL, "feed_viewscreen_widgets", 3, 1, + [&](lua_State *L) { + Lua::Push(L, T::_identity.getName()); + Lua::Push(L, this); + Lua::PushInterfaceKeys(L, *input); + }, [&](lua_State *L) { + input_is_handled = lua_toboolean(L, -1); + }); if (!input_is_handled) INTERPOSE_NEXT(feed)(input); } @@ -122,7 +128,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { if (enable) { screenSize = Screen::getWindowSize(); - call_overlay_lua(&out, "reload"); + call_overlay_lua(&out, "rescan"); } DEBUG(control).print("%sing interpose hooks\n", enable ? "enabl" : "disabl"); diff --git a/plugins/pathable.cpp b/plugins/pathable.cpp index dd26a712f..06394f838 100644 --- a/plugins/pathable.cpp +++ b/plugins/pathable.cpp @@ -7,6 +7,8 @@ #include "LuaTools.h" #include "PluginManager.h" +#include "df/init.h" + using namespace DFHack; DFHACK_PLUGIN("pathable"); @@ -37,8 +39,8 @@ static void paintScreen(df::coord target, bool skip_unrevealed = false) { int selected_tile_texpos = 0; Screen::findGraphicsTile("CURSORS", 4, 3, &selected_tile_texpos); - long pathable_tile_texpos = 779; - long unpathable_tile_texpos = 782; + long pathable_tile_texpos = df::global::init->load_bar_texpos[1]; + long unpathable_tile_texpos = df::global::init->load_bar_texpos[4]; long on_off_texpos = Textures::getOnOffTexposStart(); if (on_off_texpos > 0) { pathable_tile_texpos = on_off_texpos + 0; diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index e75c967dc..d660c98b5 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -168,9 +168,11 @@ static void printMatdata(color_ostream &con, const matdata &data, bool only_z = con << std::setw(9) << int(data.count); if(data.lower_z != data.upper_z) - con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; + con <<" Elev:" << std::setw(4) << (data.lower_z) << ".." << (data.upper_z) + << std::endl; else - con <<" Z:" << std::setw(4) << data.lower_z << std::endl; + con <<" Elev:" << std::setw(4) << (data.lower_z) + << std::endl; } static int getValue(const df::inorganic_raw &info) @@ -565,6 +567,10 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, const prospect_options &options) { + out.printerr("prospector at embark is not currently available.\n"); + return CR_FAILURE; + +/* if (!world || !world->world_data) { out.printerr("World data is not available.\n"); @@ -621,6 +627,7 @@ static command_result embark_prospector(color_ostream &out, out << std::endl << "Warning: the above data is only a very rough estimate." << std::endl; return CR_OK; +*/ } static command_result map_prospector(color_ostream &con, @@ -671,7 +678,8 @@ static command_result map_prospector(color_ostream &con, b->GetGlobalFeature(&blockFeatureGlobal); b->GetLocalFeature(&blockFeatureLocal); - int global_z = world->map.region_z + z; + // the '- 100' is because DF v50 and later have a 100 offset in reported elevation + int global_z = world->map.region_z + z - 100; // Iterate over all the tiles in the block for(uint32_t y = 0; y < 16; y++) diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 55525bc21..262d163f1 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -24,23 +24,9 @@ set(PROJECT_PROTO ui_sidebar_mode ) -set(PLUGIN_PROTOS) -foreach(pbuf ${PROJECT_PROTO}) - list(APPEND PLUGIN_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/../proto/${pbuf}.proto) -endforeach() - -string(REPLACE ".proto" ".pb.cc" PLUGIN_PROTO_SRCS "${PLUGIN_PROTOS}") -string(REPLACE ".proto" ".pb.h" PLUGIN_PROTO_HDRS "${PLUGIN_PROTOS}") -set_source_files_properties(${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS} PROPERTIES GENERATED TRUE) - -set_source_files_properties( ${PROJECT_HDRS} ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) - -# mash them together (headers are marked as headers and nothing will try to compile them) -list(APPEND PROJECT_SRCS ${PROJECT_HDRS} ${PLUGIN_PROTOS} ${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS}) - if(UNIX AND NOT APPLE) set(PROJECT_LIBS ${PROJECT_LIBS} SDL) endif() # this makes sure all the stuff is put in proper places and linked to dfhack -dfhack_plugin(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES protobuf-lite ${PROJECT_LIBS} COMPILE_FLAGS_MSVC "/FI\"Export.h\"" COMPILE_FLAGS_GCC "-include Export.h -Wno-misleading-indentation" ) +dfhack_plugin(RemoteFortressReader ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS} PROTOBUFS ${PROJECT_PROTO}) diff --git a/plugins/remotefortressreader/proto/.gitignore b/plugins/remotefortressreader/proto/.gitignore new file mode 100644 index 000000000..befabf79d --- /dev/null +++ b/plugins/remotefortressreader/proto/.gitignore @@ -0,0 +1,3 @@ +*.pb.cc +*.pb.cc.rule +*.pb.h diff --git a/plugins/proto/AdventureControl.proto b/plugins/remotefortressreader/proto/AdventureControl.proto similarity index 100% rename from plugins/proto/AdventureControl.proto rename to plugins/remotefortressreader/proto/AdventureControl.proto diff --git a/plugins/proto/DwarfControl.proto b/plugins/remotefortressreader/proto/DwarfControl.proto similarity index 100% rename from plugins/proto/DwarfControl.proto rename to plugins/remotefortressreader/proto/DwarfControl.proto diff --git a/plugins/proto/ItemdefInstrument.proto b/plugins/remotefortressreader/proto/ItemdefInstrument.proto similarity index 100% rename from plugins/proto/ItemdefInstrument.proto rename to plugins/remotefortressreader/proto/ItemdefInstrument.proto diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/remotefortressreader/proto/RemoteFortressReader.proto similarity index 100% rename from plugins/proto/RemoteFortressReader.proto rename to plugins/remotefortressreader/proto/RemoteFortressReader.proto diff --git a/plugins/proto/ui_sidebar_mode.proto b/plugins/remotefortressreader/proto/ui_sidebar_mode.proto similarity index 100% rename from plugins/proto/ui_sidebar_mode.proto rename to plugins/remotefortressreader/proto/ui_sidebar_mode.proto diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index f33044877..ec478c632 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -76,7 +76,9 @@ #include "df/ocean_wave.h" #include "df/physical_attribute_type.h" #include "df/plant.h" +#include "df/plant_tree_tile.h" #include "df/plant_raw_flags.h" +#include "df/plant_root_tile.h" #include "df/projectile.h" #include "df/proj_itemst.h" #include "df/proj_unitst.h" @@ -806,8 +808,6 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage return CR_OK; } - - df::world_raws *raws = &world->raws; // df::world_history *history = &world->history; MaterialInfo mat; @@ -818,7 +818,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_type(0); mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; + mat_def->set_name(DF2UTF(mat.toString())); //find the name at cave temperature; if (size_t(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->inorganics[i]->material.state_color[GetState(&raws->inorganics[i]->material)], mat_def->mutable_state_color()); @@ -836,7 +836,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_type(i); mat_def->mutable_mat_pair()->set_mat_index(j); mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; + mat_def->set_name(DF2UTF(mat.toString())); //find the name at cave temperature; if (size_t(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(raws->mat_table.builtin[i]->state_color[GetState(raws->mat_table.builtin[i])], mat_def->mutable_state_color()); @@ -853,7 +853,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_type(j + 19); mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; + mat_def->set_name(DF2UTF(mat.toString())); //find the name at cave temperature; if (size_t(creature->material[j]->state_color[GetState(creature->material[j])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(creature->material[j]->state_color[GetState(creature->material[j])], mat_def->mutable_state_color()); @@ -870,7 +870,7 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage mat_def->mutable_mat_pair()->set_mat_type(j + 419); mat_def->mutable_mat_pair()->set_mat_index(i); mat_def->set_id(mat.getToken()); - mat_def->set_name(mat.toString()); //find the name at cave temperature; + mat_def->set_name(DF2UTF(mat.toString())); //find the name at cave temperature; if (size_t(plant->material[j]->state_color[GetState(plant->material[j])]) < raws->descriptors.colors.size()) { ConvertDFColorDescriptor(plant->material[j]->state_color[GetState(plant->material[j])], mat_def->mutable_state_color()); @@ -901,7 +901,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i continue; MaterialDefinition * basePlant = out->add_material_list(); basePlant->set_id(pp->id + ":BASE"); - basePlant->set_name(pp->name); + basePlant->set_name(DF2UTF(pp->name)); basePlant->mutable_mat_pair()->set_mat_type(-1); basePlant->mutable_mat_pair()->set_mat_index(i); #if DF_VERSION_INT > 40001 @@ -914,7 +914,7 @@ static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *i { MaterialDefinition * out_growth = out->add_material_list(); out_growth->set_id(pp->id + ":" + growth->id + +":" + growth_locations[l]); - out_growth->set_name(growth->name); + out_growth->set_name(DF2UTF(growth->name)); out_growth->mutable_mat_pair()->set_mat_type(g * 10 + l); out_growth->mutable_mat_pair()->set_mat_index(i); } @@ -971,17 +971,18 @@ void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc || yyy >= 16 ) continue; - df::plant_tree_tile tile; if (-localPos.z < 0) { - tile = tree_info->roots[-1 + localPos.z][xx + (yy*tree_info->dim_x)]; + df::plant_root_tile tile = tree_info->roots[-1 + localPos.z][xx + (yy * tree_info->dim_x)]; + if (!tile.whole || tile.bits.blocked) + continue; } else { - tile = tree_info->body[-localPos.z][xx + (yy*tree_info->dim_x)]; + df::plant_tree_tile tile = tree_info->body[-localPos.z][xx + (yy * tree_info->dim_x)]; + if (!tile.whole || tile.bits.blocked) + continue; } - if (!tile.whole || tile.bits.blocked) - continue; if (tree_info->body_height <= 1) trunk_percent[xxx][yyy] = 0; else @@ -1948,8 +1949,8 @@ static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessag out->set_center_x(pos.x); out->set_center_y(pos.y); out->set_center_z(pos.z); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_name(DF2UTF(Translation::TranslateName(&(data->name), false))); + out->set_name_english(DF2UTF(Translation::TranslateName(&(data->name), true))); out->set_cur_year(World::ReadCurrentYear()); out->set_cur_year_tick(World::ReadCurrentTick()); return CR_OK; @@ -1974,8 +1975,8 @@ static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, int height = data->world_height; out->set_world_width(width); out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_name(DF2UTF(Translation::TranslateName(&(data->name), false))); + out->set_name_english(DF2UTF(Translation::TranslateName(&(data->name), true))); auto poles = data->flip_latitude; #if DF_VERSION_INT > 34011 switch (poles) @@ -2123,8 +2124,8 @@ static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage * int height = data->world_height; out->set_world_width(width); out->set_world_height(height); - out->set_name(Translation::TranslateName(&(data->name), false)); - out->set_name_english(Translation::TranslateName(&(data->name), true)); + out->set_name(DF2UTF(Translation::TranslateName(&(data->name), false))); + out->set_name_english(DF2UTF(Translation::TranslateName(&(data->name), true))); #if DF_VERSION_INT > 34011 auto poles = data->flip_latitude; switch (poles) @@ -2794,8 +2795,8 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe auto send_tissue = send_creature->add_tissues(); send_tissue->set_id(orig_tissue->id); - send_tissue->set_name(orig_tissue->tissue_name_singular); - send_tissue->set_subordinate_to_tissue(orig_tissue->subordinate_to_tissue); + send_tissue->set_name(DF2UTF(orig_tissue->tissue_name_singular)); + send_tissue->set_subordinate_to_tissue(DF2UTF(orig_tissue->subordinate_to_tissue)); CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); } @@ -2828,7 +2829,7 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque plant_remote->set_index(i); plant_remote->set_id(plant_local->id); - plant_remote->set_name(plant_local->name); + plant_remote->set_name(DF2UTF(plant_local->name)); if (!plant_local->flags.is_set(df::plant_raw_flags::TREE)) plant_remote->set_tile(plant_local->tiles.shrub_tile); else @@ -2840,7 +2841,7 @@ static command_result GetPartialPlantRaws(color_ostream &stream, const ListReque TreeGrowth * growth_remote = plant_remote->add_growths(); growth_remote->set_index(j); growth_remote->set_id(growth_local->id); - growth_remote->set_name(growth_local->name); + growth_remote->set_name(DF2UTF(growth_local->name)); for (size_t k = 0; k < growth_local->prints.size(); k++) { df::plant_growth_print* print_local = growth_local->prints[k]; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 2820c9f54..b8a4ae902 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -12,6 +12,7 @@ #include "modules/World.h" #include "modules/MapCache.h" #include "modules/Gui.h" +#include "modules/Units.h" #include "modules/Screen.h" #include "df/block_square_event_frozen_liquidst.h" @@ -502,21 +503,32 @@ command_result revflood(color_ostream &out, vector & params) out.printerr("Only in proper dwarf mode.\n"); return CR_FAILURE; } - int32_t cx, cy, cz; + df::coord pos; Maps::getSize(x_max,y_max,z_max); - Gui::getCursorCoords(cx,cy,cz); - if(cx == -30000) - { - out.printerr("Cursor is not active. Point the cursor at some empty space you want to be unhidden.\n"); + Gui::getCursorCoords(pos); + if (!pos.isValid()) { + df::unit *unit = Gui::getSelectedUnit(out, true); + if (unit) + pos = Units::getPosition(unit); + } + + if (!pos.isValid()) { + vector citizens; + Units::getCitizens(citizens); + if (citizens.size()) + pos = Units::getPosition(citizens[0]); + } + + if(!pos.isValid()) { + out.printerr("Please select a unit or place the keyboard cursor at some empty space you want to be unhidden.\n"); return CR_FAILURE; } - DFCoord xy ((uint32_t)cx,(uint32_t)cy,cz); MapCache * MCache = new MapCache; - df::tiletype tt = MCache->tiletypeAt(xy); + df::tiletype tt = MCache->tiletypeAt(pos); if(isWallTerrain(tt)) { - out.printerr("Point the cursor at some empty space you want to be unhidden.\n"); + out.printerr("Please select a unit or place the keyboard cursor at some empty space you want to be unhidden.\n"); delete MCache; return CR_FAILURE; } @@ -534,7 +546,7 @@ command_result revflood(color_ostream &out, vector & params) } MCache->trash(); - unhideFlood_internal(MCache, xy); + unhideFlood_internal(MCache, pos); MCache->WriteAll(); delete MCache; diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 79a733b7b..3ef6a6fb2 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -275,29 +275,17 @@ command_result df_showmood (color_ostream &out, vector & parameters) // count how many items of this type the crafter already collected { int count_got = 0; - int dimension_got = 0; int divisor = 1; - bool has_dims = false; - if (item->item_type == item_type::BAR) { + if (item->item_type == item_type::BAR) divisor = 150; - has_dims = true; - } else if (item->item_type == item_type::CLOTH) { + else if (item->item_type == item_type::CLOTH) divisor = 10000; - has_dims = true; - } - for (size_t j = 0; j < job->items.size(); j++) - { - if(job->items[j]->job_item_idx == int32_t(i)) - { - if (has_dims) - dimension_got += job->items[j]->item->getTotalDimension(); + for (size_t j = 0; j < job->items.size(); j++) { + if (job->items[j]->job_item_idx == int32_t(i)) count_got += 1; - } } - out.print(", got %i of %i", count_got, item->quantity/divisor); - if (has_dims) - out.print(" (%i of %i sub-units)", dimension_got, item->quantity); - out.print("\n"); + out.print(", got %i of %i\n", count_got, + item->quantity < divisor ? item->quantity : item->quantity/divisor); } } } diff --git a/plugins/stockpiles/OrganicMatLookup.cpp b/plugins/stockpiles/OrganicMatLookup.cpp index 5167ece4a..a9e2210c9 100644 --- a/plugins/stockpiles/OrganicMatLookup.cpp +++ b/plugins/stockpiles/OrganicMatLookup.cpp @@ -1,154 +1,126 @@ #include "OrganicMatLookup.h" - #include "StockpileUtils.h" -#include "modules/Materials.h" -#include "MiscUtils.h" - -#include "df/world.h" -#include "df/world_data.h" +#include "Debug.h" #include "df/creature_raw.h" #include "df/caste_raw.h" -#include "df/material.h" +#include "df/world.h" using namespace DFHack; using namespace df::enums; using df::global::world; -using std::endl; +namespace DFHack { + DBG_EXTERN(stockpiles, log); +} /** * Helper class for mapping the various organic mats between their material indices * and their index in the stockpile_settings structures. */ -void OrganicMatLookup::food_mat_by_idx ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat & food_mat ) -{ - out << "food_lookup: food_idx(" << food_idx << ") "; - df::world_raws &raws = world->raws; +void OrganicMatLookup::food_mat_by_idx(organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat& food_mat) { + DEBUG(log).print("food_lookup: food_idx(%zd)\n", food_idx); + df::world_raws& raws = world->raws; df::special_mat_table table = raws.mat_table; int32_t main_idx = table.organic_indexes[mat_category][food_idx]; int16_t type = table.organic_types[mat_category][food_idx]; - if ( mat_category == organic_mat_category::Fish || - mat_category == organic_mat_category::UnpreparedFish || - mat_category == organic_mat_category::Eggs ) - { + if (mat_category == organic_mat_category::Fish || + mat_category == organic_mat_category::UnpreparedFish || + mat_category == organic_mat_category::Eggs) { food_mat.creature = raws.creatures.all[type]; food_mat.caste = food_mat.creature->caste[main_idx]; - out << " special creature type(" << type << ") caste("<< main_idx <<")" <::size_type idx ) -{ +std::string OrganicMatLookup::food_token_by_idx(organic_mat_category::organic_mat_category mat_category, std::vector::size_type idx) { FoodMat food_mat; - food_mat_by_idx ( out, mat_category, idx, food_mat ); - if ( food_mat.material.isValid() ) - { + food_mat_by_idx(mat_category, idx, food_mat); + if (food_mat.material.isValid()) { return food_mat.material.getToken(); } - else if ( food_mat.creature ) - { + else if (food_mat.creature) { return food_mat.creature->creature_id + ":" + food_mat.caste->caste_id; } return std::string(); } -size_t OrganicMatLookup::food_max_size ( organic_mat_category::organic_mat_category mat_category ) -{ +size_t OrganicMatLookup::food_max_size(organic_mat_category::organic_mat_category mat_category) { return world->raws.mat_table.organic_types[mat_category].size(); } -void OrganicMatLookup::food_build_map ( std::ostream &out ) -{ - if ( index_built ) +void OrganicMatLookup::food_build_map() { + if (index_built) return; - df::world_raws &raws = world->raws; + df::world_raws& raws = world->raws; df::special_mat_table table = raws.mat_table; using df::enums::organic_mat_category::organic_mat_category; using traits = df::enum_traits; - for ( int32_t mat_category = traits::first_item_value; mat_category <= traits::last_item_value; ++mat_category ) - { - for ( size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i ) - { - int16_t type = table.organic_types[mat_category].at ( i ); - int32_t index = table.organic_indexes[mat_category].at ( i ); - food_index[mat_category].insert ( std::make_pair ( std::make_pair ( type,index ), i ) ); // wtf.. only in c++ + for (int32_t mat_category = traits::first_item_value; mat_category <= traits::last_item_value; ++mat_category) { + for (size_t i = 0; i < table.organic_indexes[mat_category].size(); ++i) { + int16_t type = table.organic_types[mat_category].at(i); + int32_t index = table.organic_indexes[mat_category].at(i); + food_index[mat_category].insert(std::make_pair(std::make_pair(type, index), i)); // wtf.. only in c++ } } index_built = true; } -int16_t OrganicMatLookup::food_idx_by_token ( std::ostream &out, organic_mat_category::organic_mat_category mat_category, const std::string & token ) -{ - int16_t food_idx = -1; - df::world_raws &raws = world->raws; +int16_t OrganicMatLookup::food_idx_by_token(organic_mat_category::organic_mat_category mat_category, const std::string& token) { + df::world_raws& raws = world->raws; df::special_mat_table table = raws.mat_table; - out << "food_idx_by_token: "; - if ( mat_category == organic_mat_category::Fish || - mat_category == organic_mat_category::UnpreparedFish || - mat_category == organic_mat_category::Eggs ) - { + DEBUG(log).print("food_idx_by_token:\n"); + if (mat_category == organic_mat_category::Fish || + mat_category == organic_mat_category::UnpreparedFish || + mat_category == organic_mat_category::Eggs) { std::vector tokens; - split_string ( &tokens, token, ":" ); - if ( tokens.size() != 2 ) - { - out << "creature " << "invalid CREATURE:CASTE token: " << token << endl; + split_string(&tokens, token, ":"); + if (tokens.size() != 2) { + WARN(log).print("creature invalid CREATURE:CASTE token: %s\n", token.c_str()); + return -1; } - else - { - int16_t creature_idx = find_creature ( tokens[0] ); - if ( creature_idx < 0 ) - { - out << " creature invalid token " << tokens[0]; - } - else - { - 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 ) - out << "creature " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; - else - { - out << "ERROR creature caste not found: " << token << " caste " << tokens[1] << " creature_idx(" << creature_idx << ") food_idx("<< food_idx << ")" << endl; - food_idx = -1; - } - } + int16_t creature_idx = find_creature(tokens[0]); + if (creature_idx < 0) { + WARN(log).print("creature invalid token %s\n", tokens[0].c_str()); + return -1; } - } - else - { - if ( !index_built ) - food_build_map ( out ); - MaterialInfo mat_info = food_mat_by_token ( out, token ); - int16_t type = mat_info.type; - 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() ) - { - out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx(" << it->second << ")" << endl; - food_idx = it->second; - } - else - { - out << "matinfo: " << token << " type(" << mat_info.type << ") idx(" << mat_info.index << ") food_idx not found :(" << endl; + 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).print("creature %s caste %s creature_idx(%d) food_idx(%d)\n", token.c_str(), tokens[1].c_str(), creature_idx, food_idx); + return food_idx; } + WARN(log).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); + return -1; } - return food_idx; + + if (!index_built) + food_build_map(); + MaterialInfo mat_info = food_mat_by_token(token); + int16_t type = mat_info.type; + 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).print("matinfo: %s type(%d) idx(%d) food_idx(%zd)\n", token.c_str(), mat_info.type, mat_info.index, it->second); + return it->second; + + } + + WARN(log).print("matinfo: %s type(%d) idx(%d) food_idx not found :(\n", token.c_str(), mat_info.type, mat_info.index); + return -1; } -MaterialInfo OrganicMatLookup::food_mat_by_token ( std::ostream &out, const std::string & token ) -{ +MaterialInfo OrganicMatLookup::food_mat_by_token(const std::string& token) { MaterialInfo mat_info; - mat_info.find ( token ); + mat_info.find(token); return mat_info; } bool OrganicMatLookup::index_built = false; -std::vector OrganicMatLookup::food_index = std::vector ( df::enum_traits< df::organic_mat_category >::last_item_value + 1 ); +std::vector OrganicMatLookup::food_index = std::vector(df::enum_traits< df::organic_mat_category >::last_item_value + 1); diff --git a/plugins/stockpiles/OrganicMatLookup.h b/plugins/stockpiles/OrganicMatLookup.h index ef49f3f3c..7ae65b17e 100644 --- a/plugins/stockpiles/OrganicMatLookup.h +++ b/plugins/stockpiles/OrganicMatLookup.h @@ -5,42 +5,42 @@ #include "df/organic_mat_category.h" namespace df { -struct creature_raw; -struct caste_raw; + struct creature_raw; + struct caste_raw; } /** * Helper class for mapping the various organic mats between their material indices * and their index in the stockpile_settings structures. */ -class OrganicMatLookup -{ +class OrganicMatLookup { -// pair of material type and index + // pair of material type and index typedef std::pair FoodMatPair; -// map for using type,index pairs to find the food index + // map for using type,index pairs to find the food index typedef std::map FoodMatMap; public: - struct FoodMat - { + struct FoodMat { DFHack::MaterialInfo material; - df::creature_raw *creature; - df::caste_raw * caste; - FoodMat() : material ( -1 ), creature ( 0 ), caste ( 0 ) {} + df::creature_raw* creature; + df::caste_raw* caste; + FoodMat(): material(-1), creature(0), caste(0) { } }; - static void food_mat_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat & food_mat ); - static std::string food_token_by_idx ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type idx ); - static size_t food_max_size ( df::enums::organic_mat_category::organic_mat_category mat_category ); - static void food_build_map ( std::ostream &out ); + static void food_mat_by_idx(df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type food_idx, FoodMat& food_mat); + static std::string food_token_by_idx(df::enums::organic_mat_category::organic_mat_category mat_category, std::vector::size_type idx); - static int16_t food_idx_by_token ( std::ostream &out, df::enums::organic_mat_category::organic_mat_category mat_category, const std::string & token ); + static size_t food_max_size(df::enums::organic_mat_category::organic_mat_category mat_category); + static void food_build_map(); - static DFHack::MaterialInfo food_mat_by_token ( std::ostream &out, const std::string & token ); + static int16_t food_idx_by_token(df::enums::organic_mat_category::organic_mat_category mat_category, const std::string& token); + + static DFHack::MaterialInfo food_mat_by_token(const std::string& token); static bool index_built; static std::vector food_index; + private: OrganicMatLookup(); }; diff --git a/plugins/stockpiles/StockpileSerializer.cpp b/plugins/stockpiles/StockpileSerializer.cpp index c413613f6..529e402bd 100644 --- a/plugins/stockpiles/StockpileSerializer.cpp +++ b/plugins/stockpiles/StockpileSerializer.cpp @@ -1,38 +1,35 @@ -#include "StockpileSerializer.h" - // stockpiles plugin #include "OrganicMatLookup.h" +#include "StockpileSerializer.h" #include "StockpileUtils.h" // dfhack +#include "Debug.h" #include "MiscUtils.h" +#include "modules/Items.h" // df #include "df/building_stockpilest.h" -#include "df/inorganic_raw.h" #include "df/creature_raw.h" -#include "df/caste_raw.h" -#include "df/material.h" #include "df/inorganic_raw.h" -#include "df/plant_raw.h" -#include "df/stockpile_group_set.h" -#include -#include +#include "df/item_quality.h" #include -#include #include -#include -#include #include +#include #include #include +#include +#include +#include +#include "df/furniture_type.h" +#include "df/material.h" +#include "df/plant_raw.h" +#include "df/stockpile_group_set.h" // protobuf #include -#include - -using std::endl; using std::string; using std::vector; @@ -44,686 +41,1562 @@ using df::global::world; using std::placeholders::_1; - - -int NullBuffer::overflow ( int c ) -{ - return c; -} - -NullStream::NullStream() : std::ostream ( &m_sb ) {} - -StockpileSerializer::StockpileSerializer ( df::building_stockpilest * stockpile ) - : mDebug ( false ) - , mOut ( 0 ) - , mNull() - , mPile ( stockpile ) -{ - - // build other_mats indices - furniture_setup_other_mats(); - bars_blocks_setup_other_mats(); - finished_goods_setup_other_mats(); - weapons_armor_setup_other_mats(); -} - -StockpileSerializer::~StockpileSerializer() {} - -void StockpileSerializer::enable_debug ( std::ostream&out ) -{ - mDebug = true; - mOut = &out; -} - -bool StockpileSerializer::serialize_to_ostream ( std::ostream* output ) -{ - if ( output->fail( ) ) return false; +namespace DFHack { + DBG_EXTERN(stockpiles, log); +} + +static struct OtherMatsFurniture { + const std::map mats; + + OtherMatsFurniture() : mats(getMats()) {} + + std::map getMats() { + std::map ret; + ret.emplace(0, "WOOD"); + ret.emplace(1, "PLANT_CLOTH"); + ret.emplace(2, "BONE"); + ret.emplace(3, "TOOTH"); + ret.emplace(4, "HORN"); + ret.emplace(5, "PEARL"); + ret.emplace(6, "SHELL"); + ret.emplace(7, "LEATHER"); + ret.emplace(8, "SILK"); + ret.emplace(9, "AMBER"); + ret.emplace(10, "CORAL"); + ret.emplace(11, "GREEN_GLASS"); + ret.emplace(12, "CLEAR_GLASS"); + ret.emplace(13, "CRYSTAL_GLASS"); + ret.emplace(14, "YARN"); + return ret; + } +} mOtherMatsFurniture; + +static struct OtherMatsBars { + const std::map mats; + + OtherMatsBars() : mats(getMats()) {} + + std::map getMats() { + std::map ret; + ret.emplace(0, "COAL"); + ret.emplace(1, "POTASH"); + ret.emplace(2, "ASH"); + ret.emplace(3, "PEARLASH"); + ret.emplace(4, "SOAP"); + return ret; + } +} mOtherMatsBars; + +static struct OtherMatsBlocks { + const std::map mats; + + OtherMatsBlocks() : mats(getMats()) {} + + std::map getMats() { + std::map ret; + ret.emplace(0, "GREEN_GLASS"); + ret.emplace(1, "CLEAR_GLASS"); + ret.emplace(2, "CRYSTAL_GLASS"); + ret.emplace(3, "WOOD"); + return ret; + } +} mOtherMatsBlocks; + +static struct OtherMatsFinishedGoods { + const std::map mats; + + OtherMatsFinishedGoods() : mats(getMats()) {} + + std::map getMats() { + std::map ret; + ret.emplace(0, "WOOD"); + ret.emplace(1, "PLANT_CLOTH"); + ret.emplace(2, "BONE"); + ret.emplace(3, "TOOTH"); + ret.emplace(4, "HORN"); + ret.emplace(5, "PEARL"); + ret.emplace(6, "SHELL"); + ret.emplace(7, "LEATHER"); + ret.emplace(8, "SILK"); + ret.emplace(9, "AMBER"); + ret.emplace(10, "CORAL"); + ret.emplace(11, "GREEN_GLASS"); + ret.emplace(12, "CLEAR_GLASS"); + ret.emplace(13, "CRYSTAL_GLASS"); + ret.emplace(14, "YARN"); + ret.emplace(15, "WAX"); + return ret; + } +} mOtherMatsFinishedGoods; + +static struct OtherMatsWeaponsArmor { + const std::map mats; + + OtherMatsWeaponsArmor() : mats(getMats()) {} + + std::map getMats() { + std::map ret; + ret.emplace(0, "WOOD"); + ret.emplace(1, "PLANT_CLOTH"); + ret.emplace(2, "BONE"); + ret.emplace(3, "SHELL"); + ret.emplace(4, "LEATHER"); + ret.emplace(5, "SILK"); + ret.emplace(6, "GREEN_GLASS"); + ret.emplace(7, "CLEAR_GLASS"); + ret.emplace(8, "CRYSTAL_GLASS"); + ret.emplace(9, "YARN"); + return ret; + } +} mOtherMatsWeaponsArmor; + +StockpileSerializer::StockpileSerializer(df::building_stockpilest* stockpile) + : mPile(stockpile) { } + +StockpileSerializer::~StockpileSerializer() { } + +bool StockpileSerializer::serialize_to_ostream(std::ostream* output, uint32_t includedElements) { + if (output->fail()) + return false; mBuffer.Clear(); - write(); + write(includedElements); { - io::OstreamOutputStream zero_copy_output ( output ); - if ( !mBuffer.SerializeToZeroCopyStream ( &zero_copy_output ) ) return false; + io::OstreamOutputStream zero_copy_output(output); + if (!mBuffer.SerializeToZeroCopyStream(&zero_copy_output)) + return false; } return output->good(); } -bool StockpileSerializer::serialize_to_file ( const std::string & file ) -{ - std::fstream output ( file, std::ios::out | std::ios::binary | std::ios::trunc ); - if ( output.fail() ) - { - debug() << "ERROR: failed to open file for writing: " << file << endl; +bool StockpileSerializer::serialize_to_file(const string& file, uint32_t includedElements) { + std::fstream output(file, std::ios::out | std::ios::binary | std::ios::trunc); + if (output.fail()) { + WARN(log).print("ERROR: failed to open file for writing: '%s'\n", + file.c_str()); return false; } - return serialize_to_ostream ( &output ); + return serialize_to_ostream(&output, includedElements); } -bool StockpileSerializer::parse_from_istream ( std::istream* input ) -{ - if ( input->fail( ) ) return false; +bool StockpileSerializer::parse_from_istream(std::istream* input, DeserializeMode mode, const vector& filters) { + if (input->fail()) + return false; mBuffer.Clear(); - io::IstreamInputStream zero_copy_input ( input ); - const bool res = mBuffer.ParseFromZeroCopyStream ( &zero_copy_input ) && input->eof(); - if ( res ) read(); + io::IstreamInputStream zero_copy_input(input); + const bool res = mBuffer.ParseFromZeroCopyStream(&zero_copy_input) + && input->eof(); + if (res) + read(mode, filters); return res; } - -bool StockpileSerializer::unserialize_from_file ( const std::string & file ) -{ - std::fstream input ( file, std::ios::in | std::ios::binary ); - if ( input.fail() ) - { - debug() << "ERROR: failed to open file for reading: " << file << endl; +bool StockpileSerializer::unserialize_from_file(const string& file, DeserializeMode mode, const vector& filters) { + std::fstream input(file, std::ios::in | std::ios::binary); + if (input.fail()) { + WARN(log).print("failed to open file for reading: '%s'\n", + file.c_str()); return false; } - return parse_from_istream ( &input ); -} - -std::ostream & StockpileSerializer::debug() -{ - if ( mDebug ) - return *mOut; - return mNull; -} - - -void StockpileSerializer::write() -{ - // debug() << "GROUP SET " << bitfield_to_string(mPile->settings.flags) << endl; - write_general(); - if ( mPile->settings.flags.bits.animals ) - write_animals(); - if ( mPile->settings.flags.bits.food ) - write_food(); - if ( mPile->settings.flags.bits.furniture ) - write_furniture(); - if ( mPile->settings.flags.bits.refuse ) - write_refuse(); - if ( mPile->settings.flags.bits.stone ) - write_stone(); - if ( mPile->settings.flags.bits.ammo ) - write_ammo(); - if ( mPile->settings.flags.bits.coins ) - write_coins(); - if ( mPile->settings.flags.bits.bars_blocks ) - write_bars_blocks(); - if ( mPile->settings.flags.bits.gems ) - write_gems(); - if ( mPile->settings.flags.bits.finished_goods ) - write_finished_goods(); - if ( mPile->settings.flags.bits.leather ) - write_leather(); - if ( mPile->settings.flags.bits.cloth ) - write_cloth(); - if ( mPile->settings.flags.bits.wood ) - write_wood(); - if ( mPile->settings.flags.bits.weapons ) - write_weapons(); - if ( mPile->settings.flags.bits.armor ) - write_armor(); -} - -void StockpileSerializer::read () -{ - debug() << endl << "==READ==" << endl; - read_general(); - read_animals(); - read_food(); - read_furniture(); - read_refuse(); - read_stone(); - read_ammo(); - read_coins(); - read_bars_blocks(); - read_gems(); - read_finished_goods(); - read_leather(); - read_cloth(); - read_wood(); - read_weapons(); - read_armor(); -} - - -void StockpileSerializer::serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector * list, organic_mat_category::organic_mat_category cat ) -{ - if ( !list ) - { - debug() << "serialize_list_organic_mat: list null" << endl; + return parse_from_istream(&input, mode, filters); +} + +/** + * Find an enum's value based off the string label. + * @param traits the enum's trait struct + * @param token the string value in key_table + * @return the enum's value, -1 if not found + */ +template +static typename df::enum_traits::base_type linear_index(df::enum_traits traits, const string& token) { + auto j = traits.first_item_value; + auto limit = traits.last_item_value; + // sometimes enums start at -1, which is bad news for array indexing + if (j < 0) { + j += abs(traits.first_item_value); + limit += abs(traits.first_item_value); } - for ( size_t i = 0; i < list->size(); ++i ) - { - if ( list->at ( i ) ) - { - std::string token = OrganicMatLookup::food_token_by_idx ( debug(), cat, i ); - if ( !token.empty() ) - { - add_value ( token ); - debug() << " organic_material " << i << " is " << token << endl; - } - else - { - debug() << "food mat invalid :(" << endl; - } - } + for (; j <= limit; ++j) { + if (token.compare(traits.key_table[j]) == 0) + return j; } + return -1; } -void StockpileSerializer::unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector *pile_list, organic_mat_category::organic_mat_category cat ) -{ - pile_list->clear(); - pile_list->resize ( OrganicMatLookup::food_max_size ( cat ), '\0' ); - for ( size_t i = 0; i < list_size; ++i ) - { - std::string token = get_value ( i ); - int16_t idx = OrganicMatLookup::food_idx_by_token ( debug(), cat, token ); - debug() << " organic_material " << idx << " is " << token << endl; - if ( size_t(idx) >= pile_list->size() ) - { - debug() << "error organic mat index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; - continue; - } - pile_list->at ( idx ) = 1; +static bool matches_filter(const vector& filters, const string& name) { + for (auto & filter : filters) { + DEBUG(log).print("searching for '%s' in '%s'\n", filter.c_str(), name.c_str()); + 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()) + return true; } + return !filters.size(); } - -void StockpileSerializer::serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) -{ - using df::enums::item_type::item_type; - using type_traits = df::enum_traits; - debug() << "item_type size = " << list.size() << " size limit = " << type_traits::last_item_value << " typecasted: " << ( size_t ) type_traits::last_item_value << endl; - for ( size_t i = 0; i <= ( size_t ) type_traits::last_item_value; ++i ) - { - if ( list.at ( i ) ) - { - const item_type type = ( item_type ) ( ( df::enum_traits::base_type ) i ); - std::string r_type ( type_traits::key_table[i+1] ); - if ( !is_allowed ( type ) ) continue; - add_value ( r_type ); - debug() << "item_type key_table[" << i+1 << "] type[" << ( int16_t ) type << "] is " << r_type <& filters, bool all, char val, bool enabled, bool& elem) { + if ((all || enabled) && matches_filter(filters, name)) { + DEBUG(log).print("setting %s to %d\n", name, val); + elem = val; } } +static void set_filter_elem(const char* subcat, const vector& filters, char val, + const string& name, const string& id, char& elem) { + if (matches_filter(filters, subcat + ((*subcat ? "/" : "") + name))) { + DEBUG(log).print("setting %s (%s) to %d\n", name.c_str(), id.c_str(), val); + elem = val; + } +} -void StockpileSerializer::unserialize_list_item_type ( FuncItemAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) -{ - pile_list->clear(); - pile_list->resize ( 112, '\0' ); // TODO remove hardcoded list size value - for ( size_t i = 0; i < pile_list->size(); ++i ) - { - pile_list->at ( i ) = is_allowed ( ( item_type::item_type ) i ) ? 0 : 1; +template +static void set_filter_elem(const char* subcat, const vector& filters, T_val val, + const string& name, T_id id, T_val& elem) { + if (matches_filter(filters, subcat + ((*subcat ? "/" : "") + name))) { + DEBUG(log).print("setting %s (%d) to %d\n", name.c_str(), (int32_t)id, val); + elem = val; } - using df::enums::item_type::item_type; - df::enum_traits type_traits; - for ( int32_t i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - // subtract one because item_type starts at -1 - const df::enum_traits::base_type idx = linear_index ( debug(), type_traits, token ) - 1; - const item_type type = ( item_type ) idx; - if ( !is_allowed ( type ) ) continue; - debug() << " item_type " << idx << " is " << token << endl; - if ( size_t(idx) >= pile_list->size() ) - { - debug() << "error item_type index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; +} + +/** + * There are many repeated (un)serialization cases throughout the stockpile_settings structure, + * so the most common cases have been generalized into generic functions using lambdas. + * + * The basic process to serialize a stockpile_settings structure is: + * 1. loop through the list + * 2. for every element that is TRUE: + * 3. map the specific stockpile_settings index into a general material, creature, etc index + * 4. verify that type is allowed in the list (e.g., no stone in gems stockpiles) + * 5. add it to the protobuf using FuncWriteExport + * + * The unserialization process is the same in reverse. + */ +static bool serialize_list_itemdef(FuncWriteExport add_value, + vector list, + vector items, + item_type::item_type type) { + bool all = true; + for (size_t i = 0; i < list.size(); ++i) { + if (!list.at(i)) { + all = false; continue; } - pile_list->at ( idx ) = 1; + const df::itemdef* a = items.at(i); + // skip procedurally generated items + if (a->base_flags.is_set(df::itemdef_flags::GENERATED)) + continue; + ItemTypeInfo ii; + if (!ii.decode(type, i)) + continue; + DEBUG(log).print("adding itemdef type %s\n", ii.getToken().c_str()); + add_value(ii.getToken()); } + return all; } +static void unserialize_list_itemdef(const char* subcat, bool all, char val, const vector& filters, + FuncReadImport read_value, int32_t list_size, vector& pile_list, item_type::item_type type) { + int num_elems = Items::getSubtypeCount(type); + pile_list.resize(num_elems, '\0'); + if (all) { + for (auto idx = 0; idx < num_elems; ++idx) { + ItemTypeInfo ii; + ii.decode(type, idx); + set_filter_elem(subcat, filters, val, ii.toString(), idx, pile_list.at(idx)); + } + return; + } -void StockpileSerializer::serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ) -{ - MaterialInfo mi; - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - mi.decode ( 0, i ); - if ( !is_allowed ( mi ) ) continue; - debug() << " material " << i << " is " << mi.getToken() << endl; - add_value ( mi.getToken() ); + for (auto i = 0; i < list_size; ++i) { + string id = read_value(i); + ItemTypeInfo ii; + if (!ii.find(id)) + continue; + if (ii.subtype < 0 || size_t(ii.subtype) >= pile_list.size()) { + WARN(log).print("item type index invalid: %d\n", ii.subtype); + continue; } + set_filter_elem(subcat, filters, val, id, ii.subtype, pile_list.at(ii.subtype)); } } +static bool serialize_list_quality(FuncWriteExport add_value, + const bool(&quality_list)[7]) { + using df::enums::item_quality::item_quality; + using quality_traits = df::enum_traits; -void StockpileSerializer::unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) -{ - // we initialize all possible (allowed) values to 0, - // then all other not-allowed values to 1 - // why? because that's how the memory is in DF before - // we muck with it. - std::set idx_set; - pile_list->clear(); - pile_list->resize ( world->raws.inorganics.size(), 0 ); - for ( size_t i = 0; i < pile_list->size(); ++i ) - { - MaterialInfo mi ( 0, i ); - pile_list->at ( i ) = is_allowed ( mi ) ? 0 : 1; - } - for ( int i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !is_allowed ( mi ) ) continue; - debug() << " material " << mi.index << " is " << token << endl; - if ( size_t(mi.index) >= pile_list->size() ) - { - debug() << "error material index too large! idx[" << mi.index << "] max_size[" << pile_list->size() << "]" << endl; + bool all = true; + for (size_t i = 0; i < 7; ++i) { + if (!quality_list[i]) { + all = false; continue; } - pile_list->at ( mi.index ) = 1; + const string f_type(quality_traits::key_table[i]); + add_value(f_type); + DEBUG(log).print("adding quality %s\n", f_type.c_str()); } + return all; +} + +static void quality_clear(bool(&pile_list)[7]) { + std::fill(pile_list, pile_list + 7, false); } +static void unserialize_list_quality(const char* subcat, bool all, bool val, const vector& filters, + FuncReadImport read_value, int32_t list_size, bool(&pile_list)[7]) { + if (all) { + for (auto idx = 0; idx < 7; ++idx) { + string id = ENUM_KEY_STR(item_quality, (df::item_quality)idx); + set_filter_elem(subcat, filters, val, id, idx, pile_list[idx]); + } + return; + } -void StockpileSerializer::serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ) -{ using df::enums::item_quality::item_quality; - using quality_traits = df::enum_traits; - for ( size_t i = 0; i < 7; ++i ) - { - if ( quality_list[i] ) - { - const std::string f_type ( quality_traits::key_table[i] ); - add_value ( f_type ); - debug() << " quality: " << i << " is " << f_type < quality_traits; + for (int i = 0; i < list_size; ++i) { + const string quality = read_value(i); + df::enum_traits::base_type idx = linear_index(quality_traits, quality); + if (idx < 0) { + WARN(log).print("invalid quality token: %s\n", quality.c_str()); + continue; } + set_filter_elem(subcat, filters, val, quality, idx, pile_list[idx]); } } - -void StockpileSerializer::quality_clear ( bool ( &pile_list ) [7] ) -{ - std::fill ( pile_list, pile_list+7, false ); +static string other_mats_index(const std::map other_mats, + int idx) { + auto it = other_mats.find(idx); + if (it == other_mats.end()) + return string(); + return it->second; } +static int other_mats_token(const std::map other_mats, + const string& token) { + for (auto it = other_mats.begin(); it != other_mats.end(); ++it) { + if (it->second == token) + return it->first; + } + return -1; +} -void StockpileSerializer::unserialize_list_quality ( FuncReadImport read_value, int32_t list_size, bool ( &pile_list ) [7] ) -{ - quality_clear ( pile_list ); - if ( list_size > 0 && list_size <= 7 ) - { - using df::enums::item_quality::item_quality; - df::enum_traits quality_traits; - for ( int i = 0; i < list_size; ++i ) - { - const std::string quality = read_value ( i ); - df::enum_traits::base_type idx = linear_index ( debug(), quality_traits, quality ); - if ( idx < 0 ) - { - debug() << " invalid quality token " << quality << endl; - continue; - } - debug() << " quality: " << idx << " is " << quality << endl; - pile_list[idx] = true; +static bool serialize_list_other_mats( + const std::map other_mats, + FuncWriteExport add_value, + vector list) { + bool all = true; + for (size_t i = 0; i < list.size(); ++i) { + if (!list.at(i)) { + all = false; + continue; + } + const string token = other_mats_index(other_mats, i); + if (token.empty()) { + WARN(log).print("invalid other material with index %zd\n", i); + continue; } + add_value(token); + DEBUG(log).print("other mats %zd is %s\n", i, token.c_str()); } + return all; } +static void unserialize_list_other_mats(const char* subcat, bool all, char val, const vector& filters, + const std::map other_mats, FuncReadImport read_value, int32_t list_size, vector& pile_list) { + size_t num_elems = other_mats.size(); + pile_list.resize(num_elems, '\0'); -void StockpileSerializer::serialize_list_other_mats ( const std::map other_mats, FuncWriteExport add_value, std::vector list ) -{ - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - const std::string token = other_mats_index ( other_mats, i ); - if ( token.empty() ) - { - debug() << " invalid other material with index " << i << endl; - continue; - } - add_value ( token ); - debug() << " other mats " << i << " is " << token << endl; + if (all) { + for (auto & entry : other_mats) + set_filter_elem(subcat, filters, val, entry.second, entry.first, pile_list.at(entry.first)); + return; + } + + for (int i = 0; i < list_size; ++i) { + const string token = read_value(i); + size_t idx = other_mats_token(other_mats, token); + if (idx < 0) { + WARN(log).print("invalid other mat with token %s\n", token.c_str()); + continue; + } + if (idx >= num_elems) { + WARN(log).print("other_mats index too large! idx[%zd] max_size[%zd]\n", idx, num_elems); + continue; } + set_filter_elem(subcat, filters, val, token, idx, pile_list.at(idx)); } } - -void StockpileSerializer::unserialize_list_other_mats ( const std::map other_mats, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ) -{ - pile_list->clear(); - pile_list->resize ( other_mats.size(), '\0' ); - for ( int i = 0; i < list_size; ++i ) - { - const std::string token = read_value ( i ); - size_t idx = other_mats_token ( other_mats, token ); - if ( idx < 0 ) - { - debug() << "invalid other mat with token " << token; +static bool serialize_list_organic_mat(FuncWriteExport add_value, + const vector* list, + organic_mat_category::organic_mat_category cat) { + bool all = true; + if (!list) { + DEBUG(log).print("serialize_list_organic_mat: list null\n"); + return all; + } + for (size_t i = 0; i < list->size(); ++i) { + if (!list->at(i)) { + all = false; continue; } - debug() << " other_mats " << idx << " is " << token << endl; - if ( idx >= pile_list->size() ) - { - debug() << "error other_mats index too large! idx[" << idx << "] max_size[" << pile_list->size() << "]" << endl; + string token = OrganicMatLookup::food_token_by_idx(cat, i); + if (token.empty()) { + DEBUG(log).print("food mat invalid :(\n"); continue; } - pile_list->at ( idx ) = 1; + DEBUG(log).print("organic_material %zd is %s\n", i, token.c_str()); + add_value(token); } + return all; } +static void unserialize_list_organic_mat(const char* subcat, bool all, char val, const vector& filters, + FuncReadImport read_value, size_t list_size, vector& pile_list, + organic_mat_category::organic_mat_category cat) { + size_t num_elems = OrganicMatLookup::food_max_size(cat); + pile_list.resize(num_elems, '\0'); + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + string token = OrganicMatLookup::food_token_by_idx(cat, idx); + set_filter_elem(subcat, filters, val, token, idx, pile_list.at(idx)); + } + return; + } - -void StockpileSerializer::serialize_list_itemdef ( FuncWriteExport add_value, std::vector list, std::vector items, item_type::item_type type ) -{ - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) ) - { - const df::itemdef *a = items.at ( i ); - // skip procedurally generated items - if ( a->base_flags.is_set ( df::itemdef_flags::GENERATED ) ) continue; - ItemTypeInfo ii; - if ( !ii.decode ( type, i ) ) continue; - add_value ( ii.getToken() ); - debug() << " itemdef type" << i << " is " << ii.getToken() << endl; + for (size_t i = 0; i < list_size; ++i) { + const string token = read_value(i); + int16_t idx = OrganicMatLookup::food_idx_by_token(cat, token); + if (idx < 0 || size_t(idx) >= num_elems) { + WARN(log).print("organic mat index too large! idx[%d] max_size[%zd]\n", idx, num_elems); + continue; } + set_filter_elem(subcat, filters, val, token, idx, pile_list.at(idx)); } } +static bool serialize_list_item_type(FuncItemAllowed is_allowed, + FuncWriteExport add_value, const vector& list) { + using df::enums::item_type::item_type; + using type_traits = df::enum_traits; -void StockpileSerializer::unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector *pile_list, item_type::item_type type ) -{ - pile_list->clear(); - pile_list->resize ( Items::getSubtypeCount ( type ), '\0' ); - for ( int i = 0; i < list_size; ++i ) - { - std::string token = read_value ( i ); - ItemTypeInfo ii; - if ( !ii.find ( token ) ) continue; - debug() << " itemdef " << ii.subtype << " is " << token << endl; - if ( size_t(ii.subtype) >= pile_list->size() ) - { - debug() << "error itemdef index too large! idx[" << ii.subtype << "] max_size[" << pile_list->size() << "]" << endl; + bool all = true; + size_t num_item_types = list.size(); + DEBUG(log).print("item_type size = %zd size limit = %d typecasted: %zd\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) { + if (i < num_item_types && !list.at(i)) { + all = false; continue; } - pile_list->at ( ii.subtype ) = 1; + const item_type type = (item_type)((df::enum_traits::base_type)i); + string r_type(type_traits::key_table[i + 1]); + if (!is_allowed(type)) + continue; + add_value(r_type); + DEBUG(log).print("item_type key_table[%zd] type[%d] is %s\n", i + 1, (int16_t)type, r_type.c_str()); } + return all; } +static void unserialize_list_item_type(const char* subcat, bool all, char val, const vector& filters, + FuncItemAllowed is_allowed, FuncReadImport read_value, int32_t list_size, vector& pile_list) { + // TODO can we remove the hardcoded list size? + size_t num_elems = 112; + pile_list.resize(num_elems, '\0'); + for (size_t i = 0; i < num_elems; ++i) { + if (!is_allowed((df::item_type)i)) + pile_list.at(i) = 1; + } -std::string StockpileSerializer::other_mats_index ( const std::map other_mats, int idx ) -{ - auto it = other_mats.find ( idx ); - if ( it == other_mats.end() ) - return std::string(); - return it->second; + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + string id = ENUM_KEY_STR(item_type, (df::item_type)idx); + set_filter_elem(subcat, filters, val, id, idx, pile_list.at(idx)); + } + return; + } + + using df::enums::item_type::item_type; + df::enum_traits type_traits; + for (int i = 0; i < list_size; ++i) { + const string token = read_value(i); + // subtract one because item_type starts at -1 + const df::enum_traits::base_type idx = linear_index(type_traits, token) - 1; + if (!is_allowed((item_type)idx)) + continue; + if (idx < 0 || size_t(idx) >= num_elems) { + WARN(log).print("error item_type index too large! idx[%d] max_size[%zd]\n", idx, num_elems); + continue; + } + set_filter_elem(subcat, filters, val, token, idx, pile_list.at(idx)); + } } -int StockpileSerializer::other_mats_token ( const std::map other_mats, const std::string & token ) -{ - for ( auto it = other_mats.begin(); it != other_mats.end(); ++it ) - { - if ( it->second == token ) - return it->first; +static bool serialize_list_material(FuncMaterialAllowed is_allowed, + FuncWriteExport add_value, const vector& list) { + bool all = true; + MaterialInfo mi; + for (size_t i = 0; i < list.size(); ++i) { + if (!list.at(i)) { + all = false; + continue; + } + mi.decode(0, i); + if (!is_allowed(mi)) + continue; + DEBUG(log).print("adding material %s\n", mi.getToken().c_str()); + add_value(mi.getToken()); } - return -1; + return all; } -void StockpileSerializer::write_general() -{ - mBuffer.set_max_bins ( mPile->max_bins ); - mBuffer.set_max_wheelbarrows ( mPile->max_wheelbarrows ); - mBuffer.set_max_barrels ( mPile->max_barrels ); - mBuffer.set_use_links_only ( mPile->use_links_only ); - mBuffer.set_allow_inorganic ( mPile->settings.allow_inorganic ); - mBuffer.set_allow_organic ( mPile->settings.allow_organic ); - mBuffer.set_corpses ( mPile->settings.flags.bits.corpses ); -} - -void StockpileSerializer::read_general() -{ - if ( mBuffer.has_max_bins() ) - mPile->max_bins = mBuffer.max_bins(); - if ( mBuffer.has_max_wheelbarrows() ) - mPile->max_wheelbarrows = mBuffer.max_wheelbarrows(); - if ( mBuffer.has_max_barrels() ) - mPile->max_barrels = mBuffer.max_barrels(); - if ( mBuffer.has_use_links_only() ) - mPile->use_links_only = mBuffer.use_links_only(); - if ( mBuffer.has_allow_inorganic() ) - mPile->settings.allow_inorganic = mBuffer.allow_inorganic(); - if ( mBuffer.has_allow_organic() ) - mPile->settings.allow_organic = mBuffer.allow_organic(); - if ( mBuffer.has_corpses() ) - mPile->settings.flags.bits.corpses = mBuffer.corpses(); -} - -void StockpileSerializer::write_animals() -{ - StockpileSettings::AnimalsSet *animals= mBuffer.mutable_animals(); - animals->set_empty_cages ( mPile->settings.animals.empty_cages ); - animals->set_empty_traps ( mPile->settings.animals.empty_traps ); - for ( size_t i = 0; i < mPile->settings.animals.enabled.size(); ++i ) - { - if ( mPile->settings.animals.enabled.at ( i ) == 1 ) - { - df::creature_raw* r = find_creature ( i ); - debug() << "creature "<< r->creature_id << " " << i << endl; - animals->add_enabled ( r->creature_id ); +static void unserialize_list_material(const char* subcat, bool all, char val, const vector& filters, + FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, + vector& pile_list) { + // we initialize all disallowed values to 1 + // why? because that's how the memory is in DF before we muck with it. + size_t num_elems = world->raws.inorganics.size(); + pile_list.resize(num_elems, 0); + for (size_t i = 0; i < pile_list.size(); ++i) { + MaterialInfo mi(0, i); + if (!is_allowed(mi)) + pile_list.at(i) = 1; + } + + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + MaterialInfo mi; + mi.decode(0, idx); + set_filter_elem(subcat, filters, val, mi.toString(), idx, pile_list.at(idx)); + } + return; + } + + for (auto i = 0; i < list_size; ++i) { + string id = read_value(i); + MaterialInfo mi; + if (!mi.find(id) || !is_allowed(mi)) + continue; + if (mi.index < 0 || size_t(mi.index) >= pile_list.size()) { + WARN(log).print("material type index invalid: %d\n", mi.index); + continue; } + set_filter_elem(subcat, filters, val, id, mi.index, pile_list.at(mi.index)); } } -void StockpileSerializer::read_animals() -{ - if ( mBuffer.has_animals() ) - { - mPile->settings.flags.bits.animals = 1; - debug() << "animals:" << endl; - mPile->settings.animals.empty_cages = mBuffer.animals().empty_cages(); - mPile->settings.animals.empty_traps = mBuffer.animals().empty_traps(); - - mPile->settings.animals.enabled.clear(); - mPile->settings.animals.enabled.resize ( world->raws.creatures.all.size(), '\0' ); - debug() << " pile has " << mPile->settings.animals.enabled.size() << endl; - for ( auto i = 0; i < mBuffer.animals().enabled_size(); ++i ) - { - std::string id = mBuffer.animals().enabled ( i ); - int idx = find_creature ( id ); - debug() << id << " " << idx << endl; - if ( idx < 0 || size_t(idx) >= mPile->settings.animals.enabled.size() ) - { - debug() << "WARNING: animal index invalid: " << idx << endl; - continue; - } - mPile->settings.animals.enabled.at ( idx ) = ( char ) 1; +static bool serialize_list_creature(FuncWriteExport add_value, const vector& list) { + bool all = true; + + for (size_t i = 0; i < list.size(); ++i) { + if (!list.at(i)) { + all = false; + continue; } + df::creature_raw* r = find_creature(i); + if (r->flags.is_set(creature_raw_flags::GENERATED) + || r->creature_id == "EQUIPMENT_WAGON") + continue; + DEBUG(log).print("adding creature %s\n", r->creature_id.c_str()); + add_value(r->creature_id); } - else - { - mPile->settings.animals.enabled.clear(); - mPile->settings.flags.bits.animals = 0; - mPile->settings.animals.empty_cages = false; - mPile->settings.animals.empty_traps = false; + return all; +} + +static void unserialize_list_creature(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.creatures.all.size(); + pile_list.resize(num_elems, '\0'); + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + auto r = find_creature(idx); + set_filter_elem(subcat, filters, val, r->name[0], r->creature_id, pile_list.at(idx)); + } + return; + } + + for (auto i = 0; i < list_size; ++i) { + string id = read_value(i); + int idx = find_creature(id); + if (idx < 0 || size_t(idx) >= num_elems) { + WARN(log).print("animal index invalid: %d\n", idx); + continue; + } + auto r = find_creature(idx); + set_filter_elem(subcat, filters, val, r->name[0], r->creature_id, pile_list.at(idx)); + } +} + +template +static void write_cat(const char *name, bool include_types, uint32_t cat_flags, + enum df::stockpile_group_set::Mask cat_mask, + std::function mutable_cat_fn, + std::function write_cat_fn) { + if (!(cat_flags & cat_mask)) + return; + + T_cat_set* cat_set = mutable_cat_fn(); + + if (!include_types) { + DEBUG(log).print("including all for %s since only category is being recorded\n", name); + cat_set->set_all(true); + return; + } + + if (write_cat_fn(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).print("including all for %s since all fields were enabled\n", name); + cat_set->Clear(); + cat_set->set_all(true); + } +} + +void StockpileSerializer::write(uint32_t includedElements) { + if (includedElements & INCLUDED_ELEMENTS_CONTAINERS) + write_containers(); + if (includedElements & INCLUDED_ELEMENTS_GENERAL) + write_general(); + + if (!(includedElements & INCLUDED_ELEMENTS_CATEGORIES)) + return; + + DEBUG(log).print("GROUP SET %s\n", + bitfield_to_string(mPile->settings.flags).c_str()); + + bool include_types = 0 != (includedElements & INCLUDED_ELEMENTS_TYPES); + + write_cat("ammo", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_ammo, + std::bind(&StockpileSettings::mutable_ammo, &mBuffer), + std::bind(&StockpileSerializer::write_ammo, this, _1)); + write_cat("animals", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_animals, + std::bind(&StockpileSettings::mutable_animals, &mBuffer), + std::bind(&StockpileSerializer::write_animals, this, _1)); + write_cat("armor", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_armor, + std::bind(&StockpileSettings::mutable_armor, &mBuffer), + std::bind(&StockpileSerializer::write_armor, this, _1)); + write_cat("bars_blocks", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_bars_blocks, + std::bind(&StockpileSettings::mutable_barsblocks, &mBuffer), + std::bind(&StockpileSerializer::write_bars_blocks, this, _1)); + write_cat("cloth", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_cloth, + std::bind(&StockpileSettings::mutable_cloth, &mBuffer), + std::bind(&StockpileSerializer::write_cloth, this, _1)); + write_cat("coin", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_coins, + std::bind(&StockpileSettings::mutable_coin, &mBuffer), + std::bind(&StockpileSerializer::write_coins, this, _1)); + write_cat("finished_goods", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_finished_goods, + std::bind(&StockpileSettings::mutable_finished_goods, &mBuffer), + std::bind(&StockpileSerializer::write_finished_goods, this, _1)); + write_cat("food", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_food, + std::bind(&StockpileSettings::mutable_food, &mBuffer), + std::bind(&StockpileSerializer::write_food, this, _1)); + write_cat("furniture", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_furniture, + std::bind(&StockpileSettings::mutable_furniture, &mBuffer), + std::bind(&StockpileSerializer::write_furniture, this, _1)); + write_cat("gems", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_gems, + std::bind(&StockpileSettings::mutable_gems, &mBuffer), + std::bind(&StockpileSerializer::write_gems, this, _1)); + write_cat("leather", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_leather, + std::bind(&StockpileSettings::mutable_leather, &mBuffer), + std::bind(&StockpileSerializer::write_leather, this, _1)); + write_cat("corpses", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_corpses, + std::bind(&StockpileSettings::mutable_corpses_v50, &mBuffer), + std::bind(&StockpileSerializer::write_corpses, this, _1)); + write_cat("refuse", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_refuse, + std::bind(&StockpileSettings::mutable_refuse, &mBuffer), + std::bind(&StockpileSerializer::write_refuse, this, _1)); + write_cat("sheet", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_sheet, + std::bind(&StockpileSettings::mutable_sheet, &mBuffer), + std::bind(&StockpileSerializer::write_sheet, this, _1)); + write_cat("stone", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_stone, + std::bind(&StockpileSettings::mutable_stone, &mBuffer), + std::bind(&StockpileSerializer::write_stone, this, _1)); + write_cat("weapons", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_weapons, + std::bind(&StockpileSettings::mutable_weapons, &mBuffer), + std::bind(&StockpileSerializer::write_weapons, this, _1)); + write_cat("wood", include_types, + mPile->settings.flags.whole, + mPile->settings.flags.mask_wood, + std::bind(&StockpileSettings::mutable_wood, &mBuffer), + std::bind(&StockpileSerializer::write_wood, this, _1)); +} + +void StockpileSerializer::read(DeserializeMode mode, const vector& filters) { + DEBUG(log).print("==READ==\n"); + read_containers(mode); + read_general(mode); + read_ammo(mode, filters); + read_animals(mode, filters); + read_armor(mode, filters); + read_bars_blocks(mode, filters); + read_cloth(mode, filters); + read_coins(mode, filters); + read_finished_goods(mode, filters); + read_food(mode, filters); + read_furniture(mode, filters); + read_gems(mode, filters); + read_leather(mode, filters); + + // support for old versions before corpses had a set + if (mBuffer.has_corpses()) { + StockpileSettings::CorpsesSet* corpses = mBuffer.mutable_corpses_v50(); + corpses->set_all(true); + } + read_corpses(mode, filters); + + read_refuse(mode, filters); + read_sheet(mode, filters); + read_stone(mode, filters); + read_weapons(mode, filters); + read_wood(mode, filters); +} + +void StockpileSerializer::write_containers() { + DEBUG(log).print("writing container settings\n"); + mBuffer.set_max_bins(mPile->max_bins); + mBuffer.set_max_barrels(mPile->max_barrels); + mBuffer.set_max_wheelbarrows(mPile->max_wheelbarrows); +} + +template +static void read_elem(const char *name, DeserializeMode mode, + std::function has_elem_fn, + std::function elem_fn, + T_elem &setting) { + if (!has_elem_fn()) + return; + + 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).print("setting %s to %d\n", name, val); + setting = val; + } +} + +template +static void read_category(const char *name, DeserializeMode mode, + std::function has_cat_fn, + std::function cat_fn, + uint32_t & cat_flags, + enum df::stockpile_group_set::Mask cat_mask, + std::function clear_fn, + std::function set_fn) { + if (mode == DESERIALIZE_MODE_SET) { + DEBUG(log).print("clearing %s\n", name); + cat_flags &= ~cat_mask; + clear_fn(); + } + + if (!has_cat_fn()) + return; + + if (mode == DESERIALIZE_MODE_DISABLE && !(cat_flags & cat_mask)) + return; + + if (mode == DESERIALIZE_MODE_SET || mode == DESERIALIZE_MODE_ENABLE) + cat_flags |= cat_mask; + + bool all = cat_fn().all(); + char val = (mode == DESERIALIZE_MODE_DISABLE) ? (char)0 : (char)1; + DEBUG(log).print("setting %s %s elements to %d\n", + all ? "all" : "marked", name, val); + set_fn(all, val); +} + +void StockpileSerializer::read_containers(DeserializeMode mode) { + read_elem("max_bins", mode, + std::bind(&StockpileSettings::has_max_bins, mBuffer), + std::bind(&StockpileSettings::max_bins, mBuffer), + mPile->max_bins); + read_elem("max_barrels", mode, + std::bind(&StockpileSettings::has_max_barrels, mBuffer), + std::bind(&StockpileSettings::max_barrels, mBuffer), + mPile->max_barrels); + read_elem("max_wheelbarrows", mode, + std::bind(&StockpileSettings::has_max_wheelbarrows, mBuffer), + std::bind(&StockpileSettings::max_wheelbarrows, mBuffer), + mPile->max_wheelbarrows); +} + +void StockpileSerializer::write_general() { + DEBUG(log).print("writing general settings\n"); + mBuffer.set_use_links_only(mPile->use_links_only); + mBuffer.set_allow_inorganic(mPile->settings.allow_inorganic); + mBuffer.set_allow_organic(mPile->settings.allow_organic); +} + +void StockpileSerializer::read_general(DeserializeMode mode) { + read_elem("use_links_only", mode, + std::bind(&StockpileSettings::has_use_links_only, mBuffer), + std::bind(&StockpileSettings::use_links_only, mBuffer), + mPile->use_links_only); + read_elem("allow_inorganic", mode, + std::bind(&StockpileSettings::has_allow_inorganic, mBuffer), + std::bind(&StockpileSettings::allow_inorganic, mBuffer), + mPile->settings.allow_inorganic); + read_elem("allow_organic", mode, + std::bind(&StockpileSettings::has_allow_organic, mBuffer), + std::bind(&StockpileSettings::allow_organic, mBuffer), + mPile->settings.allow_organic); +} + +static bool ammo_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && mi.material->flags.is_set(material_flags::IS_METAL); +} + +bool StockpileSerializer::write_ammo(StockpileSettings::AmmoSet* ammo) { + bool all = serialize_list_itemdef( + [&](const string& token) { ammo->add_type(token); }, + mPile->settings.ammo.type, + vector(world->raws.itemdefs.ammo.begin(), world->raws.itemdefs.ammo.end()), + item_type::AMMO); + + all = serialize_list_material( + ammo_mat_is_allowed, + [&](const string& token) { ammo->add_mats(token); }, + mPile->settings.ammo.mats) && all; + + if (mPile->settings.ammo.other_mats.size() > 2) { + WARN(log).print("ammo other materials > 2: %zd\n", + mPile->settings.ammo.other_mats.size()); + } + + size_t num_other_mats = std::min(size_t(2), + mPile->settings.ammo.other_mats.size()); + for (size_t i = 0; i < num_other_mats; ++i) { + if (!mPile->settings.ammo.other_mats.at(i)) { + all = false; + continue; + } + const string token = i == 0 ? "WOOD" : "BONE"; + ammo->add_other_mats(token); + DEBUG(log).print("other mats %zd is %s\n", i, token.c_str()); + } + + all = serialize_list_quality( + [&](const string& token) { ammo->add_quality_core(token); }, + mPile->settings.ammo.quality_core) && all; + + all = serialize_list_quality( + [&](const string& token) { ammo->add_quality_total(token); }, + mPile->settings.ammo.quality_total) && all; + + return all; +} + +void StockpileSerializer::read_ammo(DeserializeMode mode, const vector& filters) { + auto & pammo = mPile->settings.ammo; + read_category("ammo", mode, + std::bind(&StockpileSettings::has_ammo, mBuffer), + std::bind(&StockpileSettings::ammo, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_ammo, + [&]() { + pammo.type.clear(); + pammo.mats.clear(); + pammo.other_mats.clear(); + quality_clear(pammo.quality_core); + quality_clear(pammo.quality_total); + }, + [&](bool all, char val) { + auto & bammo = mBuffer.ammo(); + + unserialize_list_itemdef("type", all, val, filters, + [&](const size_t& idx) -> const string& { return bammo.type(idx); }, + bammo.type_size(), pammo.type, item_type::AMMO); + + unserialize_list_material("mats", all, val, filters, ammo_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bammo.mats(idx); }, + bammo.mats_size(), pammo.mats); + + pammo.other_mats.resize(2, '\0'); + if (all) { + set_filter_elem("other", filters, val, "WOOD", 0, pammo.other_mats.at(0)); + set_filter_elem("other", filters, val, "BONE", 1, pammo.other_mats.at(1)); + } else { + // TODO can we un-hardcode the values? + for (int i = 0; i < bammo.other_mats_size(); ++i) { + const string id = bammo.other_mats(i); + const int32_t idx = id == "WOOD" ? 0 : id == "BONE" ? 1 : -1; + if (idx == -1) + continue; + set_filter_elem("other", filters, val, id, idx, pammo.other_mats.at(idx)); + } + } + + unserialize_list_quality("core", all, val, filters, + [&](const size_t& idx) -> const string& { return bammo.quality_core(idx); }, + bammo.quality_core_size(), pammo.quality_core); + + unserialize_list_quality("total", all, val, filters, + [&](const size_t& idx) -> const string& { return bammo.quality_total(idx); }, + bammo.quality_total_size(), pammo.quality_total); + }); +} + +bool StockpileSerializer::write_animals(StockpileSettings::AnimalsSet* animals) { + auto & panimals = mPile->settings.animals; + bool all = panimals.empty_cages && panimals.empty_traps; + + animals->set_empty_cages(panimals.empty_cages); + animals->set_empty_traps(panimals.empty_traps); + + return serialize_list_creature( + [&](const string& token) { animals->add_enabled(token); }, + panimals.enabled) && all; +} + +void StockpileSerializer::read_animals(DeserializeMode mode, const vector& filters) { + auto & panimals = mPile->settings.animals; + read_category("animals", mode, + std::bind(&StockpileSettings::has_animals, mBuffer), + std::bind(&StockpileSettings::animals, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_animals, + [&]() { + panimals.empty_cages = false; + panimals.empty_traps = false; + panimals.enabled.clear(); + }, + [&](bool all, char val) { + auto & banimals = mBuffer.animals(); + + set_flag("cages", filters, all, val, banimals.empty_cages(), panimals.empty_cages); + set_flag("traps", filters, all, val, banimals.empty_traps(), panimals.empty_traps); + + unserialize_list_creature("", all, val, filters, + [&](const size_t& idx) -> const string& { return banimals.enabled(idx); }, + banimals.enabled_size(), panimals.enabled); + }); +} + +static bool armor_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && mi.material->flags.is_set(material_flags::IS_METAL); +} + +bool StockpileSerializer::write_armor(StockpileSettings::ArmorSet* armor) { + + auto & parmor = mPile->settings.armor; + bool all = parmor.unusable && parmor.usable; + + armor->set_unusable(parmor.unusable); + armor->set_usable(parmor.usable); + + // armor type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_body(token); }, + parmor.body, + vector(world->raws.itemdefs.armor.begin(), world->raws.itemdefs.armor.end()), + item_type::ARMOR) && all; + + // helm type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_head(token); }, + parmor.head, + vector(world->raws.itemdefs.helms.begin(), world->raws.itemdefs.helms.end()), + item_type::HELM) && all; + + // shoes type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_feet(token); }, + parmor.feet, + vector(world->raws.itemdefs.shoes.begin(), world->raws.itemdefs.shoes.end()), + item_type::SHOES) && all; + + // gloves type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_hands(token); }, + parmor.hands, + vector(world->raws.itemdefs.gloves.begin(), world->raws.itemdefs.gloves.end()), + item_type::GLOVES) && all; + + // pant type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_legs(token); }, + parmor.legs, + vector(world->raws.itemdefs.pants.begin(), world->raws.itemdefs.pants.end()), + item_type::PANTS) && all; + + // shield type + all = serialize_list_itemdef( + [&](const string& token) { armor->add_shield(token); }, + parmor.shield, + vector(world->raws.itemdefs.shields.begin(), world->raws.itemdefs.shields.end()), + item_type::SHIELD) && all; + + // materials + all = serialize_list_material( + armor_mat_is_allowed, + [&](const string& token) { armor->add_mats(token); }, + parmor.mats) && all; + + // other mats + all = serialize_list_other_mats( + mOtherMatsWeaponsArmor.mats, [&](const string& token) { armor->add_other_mats(token); }, + parmor.other_mats) && all; + + // quality core + all = serialize_list_quality([&](const string& token) { armor->add_quality_core(token); }, + parmor.quality_core) && all; + + // quality total + all = serialize_list_quality([&](const string& token) { armor->add_quality_total(token); }, + parmor.quality_total) && all; + + return all; +} + +void StockpileSerializer::read_armor(DeserializeMode mode, const vector& filters) { + auto & parmor = mPile->settings.armor; + read_category("armor", mode, + std::bind(&StockpileSettings::has_armor, mBuffer), + std::bind(&StockpileSettings::armor, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_armor, + [&]() { + parmor.unusable = false; + parmor.usable = false; + parmor.body.clear(); + parmor.head.clear(); + parmor.feet.clear(); + parmor.hands.clear(); + parmor.legs.clear(); + parmor.shield.clear(); + parmor.other_mats.clear(); + parmor.mats.clear(); + quality_clear(parmor.quality_core); + quality_clear(parmor.quality_total); + }, + [&](bool all, char val) { + auto & barmor = mBuffer.armor(); + + set_flag("nouse", filters, all, val, barmor.unusable(), parmor.unusable); + set_flag("canuse", filters, all, val, barmor.usable(), parmor.usable); + + unserialize_list_itemdef("body", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.body(idx); }, + barmor.body_size(), parmor.body, item_type::ARMOR); + + unserialize_list_itemdef("head", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.head(idx); }, + barmor.head_size(), parmor.head, item_type::HELM); + + unserialize_list_itemdef("feet", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.feet(idx); }, + barmor.feet_size(), parmor.feet, item_type::SHOES); + + unserialize_list_itemdef("hands", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.hands(idx); }, + barmor.hands_size(), parmor.hands, item_type::GLOVES); + + unserialize_list_itemdef("legs", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.legs(idx); }, + barmor.legs_size(), parmor.legs, item_type::PANTS); + + unserialize_list_itemdef("shield", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.shield(idx); }, + barmor.shield_size(), parmor.shield, item_type::SHIELD); + + unserialize_list_material("mats", all, val, filters, + armor_mat_is_allowed, + [&](const size_t& idx) -> const string& { return barmor.mats(idx); }, + barmor.mats_size(), parmor.mats); + + unserialize_list_other_mats("other", all, val, filters, + mOtherMatsWeaponsArmor.mats, [&](const size_t& idx) -> const string& { return barmor.other_mats(idx); }, + barmor.other_mats_size(), parmor.other_mats); + + unserialize_list_quality("core", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.quality_core(idx); }, + barmor.quality_core_size(), parmor.quality_core); + + unserialize_list_quality("total", all, val, filters, + [&](const size_t& idx) -> const string& { return barmor.quality_total(idx); }, + barmor.quality_total_size(), parmor.quality_total); + }); +} + +static bool bars_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && mi.material->flags.is_set(material_flags::IS_METAL); +} + +static bool blocks_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && (mi.material->flags.is_set(material_flags::IS_METAL) || mi.material->flags.is_set(material_flags::IS_STONE)); +} + +bool StockpileSerializer::write_bars_blocks(StockpileSettings::BarsBlocksSet* bars_blocks) { + bool all = serialize_list_material( + bars_mat_is_allowed, + [&](const string& token) { bars_blocks->add_bars_mats(token); }, + mPile->settings.bars_blocks.bars_mats); + + all = serialize_list_material( + blocks_mat_is_allowed, + [&](const string& token) { bars_blocks->add_blocks_mats(token); }, + mPile->settings.bars_blocks.blocks_mats) && all; + + all = serialize_list_other_mats( + mOtherMatsBars.mats, [&](const string& token) { bars_blocks->add_bars_other_mats(token); }, + mPile->settings.bars_blocks.bars_other_mats) && all; + + all = serialize_list_other_mats( + mOtherMatsBlocks.mats, [&](const string& token) { bars_blocks->add_blocks_other_mats(token); }, + mPile->settings.bars_blocks.blocks_other_mats) && all; + + return all; +} + +void StockpileSerializer::read_bars_blocks(DeserializeMode mode, const vector& filters) { + auto & pbarsblocks = mPile->settings.bars_blocks; + read_category("bars_blocks", mode, + std::bind(&StockpileSettings::has_barsblocks, mBuffer), + std::bind(&StockpileSettings::barsblocks, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_bars_blocks, + [&]() { + pbarsblocks.bars_mats.clear(); + pbarsblocks.bars_other_mats.clear(); + pbarsblocks.blocks_mats.clear(); + pbarsblocks.blocks_other_mats.clear(); + }, + [&](bool all, char val) { + auto & bbarsblocks = mBuffer.barsblocks(); + + unserialize_list_material("mats/bars", all, val, filters, bars_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bbarsblocks.bars_mats(idx); }, + bbarsblocks.bars_mats_size(), pbarsblocks.bars_mats); + + unserialize_list_other_mats("other/bars", all, val, filters, + mOtherMatsBars.mats, + [&](const size_t& idx) -> const string& { return bbarsblocks.bars_other_mats(idx); }, + bbarsblocks.bars_other_mats_size(), pbarsblocks.bars_other_mats); + + unserialize_list_material("mats/blocks", all, val, filters, + blocks_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bbarsblocks.blocks_mats(idx); }, + bbarsblocks.blocks_mats_size(), pbarsblocks.blocks_mats); + + unserialize_list_other_mats("other/blocks", all, val, filters, + mOtherMatsBlocks.mats, + [&](const size_t& idx) -> const string& { return bbarsblocks.blocks_other_mats(idx); }, + bbarsblocks.blocks_other_mats_size(), pbarsblocks.blocks_other_mats); + }); +} + +bool StockpileSerializer::write_cloth(StockpileSettings::ClothSet* cloth) { + bool all = true; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_thread_silk(token); }, + &mPile->settings.cloth.thread_silk, organic_mat_category::Silk) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_thread_plant(token); }, + &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_thread_yarn(token); }, + &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_thread_metal(token); }, + &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_cloth_silk(token); }, + &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_cloth_plant(token); }, + &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_cloth_yarn(token); }, + &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn) && all; + + all = serialize_list_organic_mat( + [&](const string& token) { cloth->add_cloth_metal(token); }, + &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread) && all; + + return all; +} + +void StockpileSerializer::read_cloth(DeserializeMode mode, const vector& filters) { + auto & pcloth = mPile->settings.cloth; + read_category("cloth", mode, + std::bind(&StockpileSettings::has_cloth, mBuffer), + std::bind(&StockpileSettings::cloth, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_cloth, + [&]() { + pcloth.thread_silk.clear(); + pcloth.thread_yarn.clear(); + pcloth.thread_plant.clear(); + pcloth.thread_metal.clear(); + pcloth.cloth_silk.clear(); + pcloth.cloth_plant.clear(); + pcloth.cloth_yarn.clear(); + pcloth.cloth_metal.clear(); + }, + [&](bool all, char val) { + auto & bcloth = mBuffer.cloth(); + + unserialize_list_organic_mat("thread/silk", all, val, filters, + [&](size_t idx) -> string { return bcloth.thread_silk(idx); }, + bcloth.thread_silk_size(), pcloth.thread_silk, organic_mat_category::Silk); + + unserialize_list_organic_mat("thread/plant", all, val, filters, + [&](size_t idx) -> string { return bcloth.thread_plant(idx); }, + bcloth.thread_plant_size(), pcloth.thread_plant, organic_mat_category::PlantFiber); + + unserialize_list_organic_mat("thread/yarn", all, val, filters, + [&](size_t idx) -> string { return bcloth.thread_yarn(idx); }, + bcloth.thread_yarn_size(), pcloth.thread_yarn, organic_mat_category::Yarn); + + unserialize_list_organic_mat("thread/metal", all, val, filters, + [&](size_t idx) -> string { return bcloth.thread_metal(idx); }, + bcloth.thread_metal_size(), pcloth.thread_metal, organic_mat_category::MetalThread); + + unserialize_list_organic_mat("cloth/silk", all, val, filters, + [&](size_t idx) -> string { return bcloth.cloth_silk(idx); }, + bcloth.cloth_silk_size(), pcloth.cloth_silk, organic_mat_category::Silk); + + unserialize_list_organic_mat("cloth/plant", all, val, filters, + [&](size_t idx) -> string { return bcloth.cloth_plant(idx); }, + bcloth.cloth_plant_size(), pcloth.cloth_plant, organic_mat_category::PlantFiber); + + unserialize_list_organic_mat("cloth/yarn", all, val, filters, + [&](size_t idx) -> string { return bcloth.cloth_yarn(idx); }, + bcloth.cloth_yarn_size(), pcloth.cloth_yarn, organic_mat_category::Yarn); + + unserialize_list_organic_mat("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); + }); +} + +static bool coins_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid(); +} + +bool StockpileSerializer::write_coins(StockpileSettings::CoinSet* coins) { + return serialize_list_material( + coins_mat_is_allowed, + [&](const string& token) { coins->add_mats(token); }, + mPile->settings.coins.mats); +} + +void StockpileSerializer::read_coins(DeserializeMode mode, const vector& filters) { + auto & pcoins = mPile->settings.coins; + read_category("coin", mode, + std::bind(&StockpileSettings::has_coin, mBuffer), + std::bind(&StockpileSettings::coin, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_coins, + [&]() { + pcoins.mats.clear(); + }, + [&](bool all, char val) { + auto & bcoin = mBuffer.coin(); + + unserialize_list_material("", all, val, filters, coins_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bcoin.mats(idx); }, + bcoin.mats_size(), pcoins.mats); + }); +} + +static bool finished_goods_type_is_allowed(item_type::item_type type) { + switch (type) { + case item_type::CHAIN: + case item_type::FLASK: + case item_type::GOBLET: + case item_type::INSTRUMENT: + case item_type::TOY: + case item_type::ARMOR: + case item_type::SHOES: + case item_type::HELM: + case item_type::GLOVES: + case item_type::FIGURINE: + case item_type::AMULET: + case item_type::SCEPTER: + case item_type::CROWN: + case item_type::RING: + case item_type::EARRING: + case item_type::BRACELET: + case item_type::GEM: + case item_type::TOTEM: + case item_type::PANTS: + case item_type::BACKPACK: + case item_type::QUIVER: + case item_type::SPLINT: + case item_type::CRUTCH: + case item_type::TOOL: + case item_type::BOOK: + return true; + default: + return false; } } -StockpileSerializer::food_pair StockpileSerializer::food_map ( organic_mat_category::organic_mat_category cat ) -{ +static bool finished_goods_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && (mi.material->flags.is_set(material_flags::IS_GEM) || mi.material->flags.is_set(material_flags::IS_METAL) || mi.material->flags.is_set(material_flags::IS_STONE)); +} + +bool StockpileSerializer::write_finished_goods(StockpileSettings::FinishedGoodsSet* finished_goods) { + bool all = serialize_list_item_type( + finished_goods_type_is_allowed, + [&](const string& token) { finished_goods->add_type(token); }, + mPile->settings.finished_goods.type); + + all = serialize_list_material( + finished_goods_mat_is_allowed, + [&](const string& token) { finished_goods->add_mats(token); }, + mPile->settings.finished_goods.mats) && all; + + all = serialize_list_other_mats( + mOtherMatsFinishedGoods.mats, [&](const string& token) { finished_goods->add_other_mats(token); }, + mPile->settings.finished_goods.other_mats) && all; + + all = serialize_list_quality([&](const string& token) { finished_goods->add_quality_core(token); }, + mPile->settings.finished_goods.quality_core) && all; + + all = serialize_list_quality([&](const string& token) { finished_goods->add_quality_total(token); }, + mPile->settings.finished_goods.quality_total) && all; + + return all; +} + +void StockpileSerializer::read_finished_goods(DeserializeMode mode, const vector& filters) { + auto & pfinished_goods = mPile->settings.finished_goods; + read_category("finished_goods", mode, + std::bind(&StockpileSettings::has_finished_goods, mBuffer), + std::bind(&StockpileSettings::finished_goods, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_finished_goods, + [&]() { + pfinished_goods.type.clear(); + pfinished_goods.other_mats.clear(); + pfinished_goods.mats.clear(); + quality_clear(pfinished_goods.quality_core); + quality_clear(pfinished_goods.quality_total); + }, + [&](bool all, char val) { + auto & bfinished_goods = mBuffer.finished_goods(); + + unserialize_list_item_type("type", all, val, filters, finished_goods_type_is_allowed, + [&](const size_t& idx) -> const string& { return bfinished_goods.type(idx); }, + bfinished_goods.type_size(), pfinished_goods.type); + + unserialize_list_material("mats", all, val, filters, finished_goods_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bfinished_goods.mats(idx); }, + bfinished_goods.mats_size(), pfinished_goods.mats); + + unserialize_list_other_mats("other", all, val, filters, mOtherMatsFinishedGoods.mats, + [&](const size_t& idx) -> const string& { return bfinished_goods.other_mats(idx); }, + bfinished_goods.other_mats_size(), pfinished_goods.other_mats); + + unserialize_list_quality("core", all, val, filters, + [&](const size_t& idx) -> const string& { return bfinished_goods.quality_core(idx); }, + bfinished_goods.quality_core_size(), pfinished_goods.quality_core); + + unserialize_list_quality("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); + }); +} + +food_pair StockpileSerializer::food_map(organic_mat_category::organic_mat_category cat) { using df::enums::organic_mat_category::organic_mat_category; - using namespace std::placeholders; - switch ( cat ) - { + + switch (cat) { case organic_mat_category::Meat: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_meat ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_meat(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().meat ( idx ); }; - return food_pair ( setter, &mPile->settings.food.meat, getter, mBuffer.food().meat_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().meat(idx); }; + return food_pair("meat", setter, &mPile->settings.food.meat, getter, mBuffer.food().meat_size()); } case organic_mat_category::Fish: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_fish ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_fish(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().fish ( idx ); }; - return food_pair ( setter, &mPile->settings.food.fish, getter, mBuffer.food().fish_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().fish(idx); }; + return food_pair("fish/prepared", setter, &mPile->settings.food.fish, getter, mBuffer.food().fish_size()); } case organic_mat_category::UnpreparedFish: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_unprepared_fish ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_unprepared_fish(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().unprepared_fish ( idx ); }; - return food_pair ( setter, &mPile->settings.food.unprepared_fish, getter, mBuffer.food().unprepared_fish_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().unprepared_fish(idx); }; + return food_pair("fish/unprepared", setter, &mPile->settings.food.unprepared_fish, getter, mBuffer.food().unprepared_fish_size()); } case organic_mat_category::Eggs: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_egg ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_egg(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().egg ( idx ); }; - return food_pair ( setter, &mPile->settings.food.egg, getter, mBuffer.food().egg_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().egg(idx); }; + return food_pair("egg", setter, &mPile->settings.food.egg, getter, mBuffer.food().egg_size()); } case organic_mat_category::Plants: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_plants ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_plants(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().plants ( idx ); }; - return food_pair ( setter, &mPile->settings.food.plants, getter, mBuffer.food().plants_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().plants(idx); }; + return food_pair("plants", setter, &mPile->settings.food.plants, getter, mBuffer.food().plants_size()); } case organic_mat_category::PlantDrink: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_drink_plant ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_drink_plant(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.drink_plant, getter, mBuffer.food().drink_plant_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().drink_plant(idx); }; + return food_pair("drink/plant", setter, &mPile->settings.food.drink_plant, getter, mBuffer.food().drink_plant_size()); } case organic_mat_category::CreatureDrink: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_drink_animal ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_drink_animal(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().drink_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.drink_animal, getter, mBuffer.food().drink_animal_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().drink_animal(idx); }; + return food_pair("drink/animal", setter, &mPile->settings.food.drink_animal, getter, mBuffer.food().drink_animal_size()); } case organic_mat_category::PlantCheese: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_cheese_plant ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_cheese_plant(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.cheese_plant, getter, mBuffer.food().cheese_plant_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().cheese_plant(idx); }; + return food_pair("cheese/plant", setter, &mPile->settings.food.cheese_plant, getter, mBuffer.food().cheese_plant_size()); } case organic_mat_category::CreatureCheese: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_cheese_animal ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_cheese_animal(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().cheese_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.cheese_animal, getter, mBuffer.food().cheese_animal_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().cheese_animal(idx); }; + return food_pair("cheese/animal", setter, &mPile->settings.food.cheese_animal, getter, mBuffer.food().cheese_animal_size()); } case organic_mat_category::Seed: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_seeds ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_seeds(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().seeds ( idx ); }; - return food_pair ( setter, &mPile->settings.food.seeds, getter, mBuffer.food().seeds_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().seeds(idx); }; + return food_pair("seeds", setter, &mPile->settings.food.seeds, getter, mBuffer.food().seeds_size()); } case organic_mat_category::Leaf: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_leaves ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_leaves(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().leaves ( idx ); }; - return food_pair ( setter, &mPile->settings.food.leaves, getter, mBuffer.food().leaves_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().leaves(idx); }; + return food_pair("leaves", setter, &mPile->settings.food.leaves, getter, mBuffer.food().leaves_size()); } case organic_mat_category::PlantPowder: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_powder_plant ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_powder_plant(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.powder_plant, getter, mBuffer.food().powder_plant_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().powder_plant(idx); }; + return food_pair("powder/plant", setter, &mPile->settings.food.powder_plant, getter, mBuffer.food().powder_plant_size()); } case organic_mat_category::CreaturePowder: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_powder_creature ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_powder_creature(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().powder_creature ( idx ); }; - return food_pair ( setter, &mPile->settings.food.powder_creature, getter, mBuffer.food().powder_creature_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().powder_creature(idx); }; + return food_pair("powder/animal", setter, &mPile->settings.food.powder_creature, getter, mBuffer.food().powder_creature_size()); } case organic_mat_category::Glob: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_glob(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob, getter, mBuffer.food().glob_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().glob(idx); }; + return food_pair("glob", setter, &mPile->settings.food.glob, getter, mBuffer.food().glob_size()); } case organic_mat_category::PlantLiquid: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_plant ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_liquid_plant(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_plant ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_plant, getter, mBuffer.food().liquid_plant_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().liquid_plant(idx); }; + return food_pair("liquid/plant", setter, &mPile->settings.food.liquid_plant, getter, mBuffer.food().liquid_plant_size()); } case organic_mat_category::CreatureLiquid: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_animal ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_liquid_animal(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_animal ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_animal, getter, mBuffer.food().liquid_animal_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().liquid_animal(idx); }; + return food_pair("liquid/animal", setter, &mPile->settings.food.liquid_animal, getter, mBuffer.food().liquid_animal_size()); } case organic_mat_category::MiscLiquid: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_liquid_misc ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_liquid_misc(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().liquid_misc ( idx ); }; - return food_pair ( setter, &mPile->settings.food.liquid_misc, getter, mBuffer.food().liquid_misc_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().liquid_misc(idx); }; + return food_pair("liquid/misc", setter, &mPile->settings.food.liquid_misc, getter, mBuffer.food().liquid_misc_size()); } case organic_mat_category::Paste: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob_paste ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_glob_paste(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_paste ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob_paste, getter, mBuffer.food().glob_paste_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().glob_paste(idx); }; + return food_pair("paste", setter, &mPile->settings.food.glob_paste, getter, mBuffer.food().glob_paste_size()); } case organic_mat_category::Pressed: { - FuncWriteExport setter = [=] ( const std::string &id ) - { - mBuffer.mutable_food()->add_glob_pressed ( id ); + FuncWriteExport setter = [&](const string& id) { + mBuffer.mutable_food()->add_glob_pressed(id); }; - FuncReadImport getter = [=] ( size_t idx ) -> std::string { return mBuffer.food().glob_pressed ( idx ); }; - return food_pair ( setter, &mPile->settings.food.glob_pressed, getter, mBuffer.food().glob_pressed_size() ); + FuncReadImport getter = [&](size_t idx) -> string { return mBuffer.food().glob_pressed(idx); }; + return food_pair("pressed", setter, &mPile->settings.food.glob_pressed, getter, mBuffer.food().glob_pressed_size()); } case organic_mat_category::Leather: case organic_mat_category::Silk: @@ -750,1456 +1623,639 @@ StockpileSerializer::food_pair StockpileSerializer::food_map ( organic_mat_categ return food_pair(); } +bool StockpileSerializer::write_food(StockpileSettings::FoodSet* food) { + auto & pfood = mPile->settings.food; + bool all = pfood.prepared_meals; -void StockpileSerializer::write_food() -{ - StockpileSettings::FoodSet *food = mBuffer.mutable_food(); - debug() << " food: " << endl; - food->set_prepared_meals ( mPile->settings.food.prepared_meals ); + food->set_prepared_meals(pfood.prepared_meals); using df::enums::organic_mat_category::organic_mat_category; using traits = df::enum_traits; - for ( int32_t mat_category = traits::first_item_value; mat_category & filters) { using df::enums::organic_mat_category::organic_mat_category; using traits = df::enum_traits; - if ( mBuffer.has_food() ) - { - mPile->settings.flags.bits.food = 1; - const StockpileSettings::FoodSet food = mBuffer.food(); - debug() << "food:" <settings.food.prepared_meals = food.prepared_meals(); - else - mPile->settings.food.prepared_meals = true; - - debug() << " prepared_meals: " << mPile->settings.food.prepared_meals << endl; - - for ( int32_t mat_category = traits::first_item_value; mat_category clear(); - } - mPile->settings.flags.bits.food = 0; - mPile->settings.food.prepared_meals = false; - } -} - -void StockpileSerializer::furniture_setup_other_mats() -{ - mOtherMatsFurniture.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 3,"TOOTH" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 4,"HORN" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 5,"PEARL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 6,"SHELL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 7,"LEATHER" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 8,"SILK" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 9,"AMBER" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 10,"CORAL" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); - mOtherMatsFurniture.insert ( std::make_pair ( 14,"YARN" ) ); -} - -void StockpileSerializer::write_furniture() -{ - StockpileSettings::FurnitureSet *furniture= mBuffer.mutable_furniture(); - - // FURNITURE type - using df::enums::furniture_type::furniture_type; - using type_traits = df::enum_traits; - for ( size_t i = 0; i < mPile->settings.furniture.type.size(); ++i ) - { - if ( mPile->settings.furniture.type.at ( i ) ) - { - std::string f_type ( type_traits::key_table[i] ); - furniture->add_type ( f_type ); - debug() << "furniture_type " << i << " is " << f_type <add_mats ( token ); - }, mPile->settings.furniture.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsFurniture, [=] ( const std::string &token ) - { - furniture->add_other_mats ( token ); - }, mPile->settings.furniture.other_mats ); - - serialize_list_quality ( [=] ( const std::string &token ) - { - furniture->add_quality_core ( token ); - }, mPile->settings.furniture.quality_core ); - serialize_list_quality ( [=] ( const std::string &token ) - { - furniture->add_quality_total ( token ); - }, mPile->settings.furniture.quality_total ); -} -bool StockpileSerializer::furniture_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_METAL ) - || mi.material->flags.is_set ( material_flags::IS_STONE ) ); -} - -void StockpileSerializer::read_furniture() -{ - if ( mBuffer.has_furniture() ) - { - mPile->settings.flags.bits.furniture = 1; - const StockpileSettings::FurnitureSet furniture = mBuffer.furniture(); - debug() << "furniture:" < type_traits; - mPile->settings.furniture.type.clear(); - mPile->settings.furniture.type.resize ( type_traits.last_item_value+1, '\0' ); - if ( furniture.type_size() > 0 ) - { - for ( int i = 0; i < furniture.type_size(); ++i ) - { - const std::string type = furniture.type ( i ); - df::enum_traits::base_type idx = linear_index ( debug(), type_traits, type ); - debug() << " type " << idx << " is " << type << endl; - if ( idx < 0 || size_t(idx) >= mPile->settings.furniture.type.size() ) - { - debug() << "WARNING: furniture type index invalid " << type << ", idx=" << idx << endl; + auto & pfood = mPile->settings.food; + read_category("food", mode, + std::bind(&StockpileSettings::has_food, mBuffer), + std::bind(&StockpileSettings::food, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_food, + [&]() { + pfood.prepared_meals = false; + for (int32_t mat_category = traits::first_item_value; mat_category < traits::last_item_value; ++mat_category) { + food_pair p = food_map((organic_mat_category)mat_category); + if (!p.valid) continue; - } - mPile->settings.furniture.type.at ( idx ) = 1; + p.stockpile_values->clear(); } - } - - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::furniture_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return furniture.mats ( idx ); - }, furniture.mats_size(), &mPile->settings.furniture.mats ); - - // other materials - unserialize_list_other_mats ( mOtherMatsFurniture, [=] ( const size_t & idx ) -> const std::string& - { - return furniture.other_mats ( idx ); - }, furniture.other_mats_size(), &mPile->settings.furniture.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return furniture.quality_core ( idx ); - }, furniture.quality_core_size(), mPile->settings.furniture.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return furniture.quality_total ( idx ); - }, furniture.quality_total_size(), mPile->settings.furniture.quality_total ); - - } - else - { - mPile->settings.flags.bits.furniture = 0; - mPile->settings.furniture.type.clear(); - mPile->settings.furniture.other_mats.clear(); - mPile->settings.furniture.mats.clear(); - quality_clear ( mPile->settings.furniture.quality_core ); - quality_clear ( mPile->settings.furniture.quality_total ); - } -} - -bool StockpileSerializer::refuse_creature_is_allowed ( const df::creature_raw *raw ) -{ - if ( !raw ) return false; - // wagon and generated creatures not allowed, except angels - const bool is_wagon = raw->creature_id == "EQUIPMENT_WAGON"; - const bool is_generated = raw->flags.is_set ( creature_raw_flags::GENERATED ); - const bool is_angel = is_generated && raw->creature_id.find ( "DIVINE_" ) != std::string::npos; - return !is_wagon && ! ( is_generated && !is_angel ); -} - -void StockpileSerializer::refuse_write_helper ( std::function add_value, const vector & list ) -{ - for ( size_t i = 0; i < list.size(); ++i ) - { - if ( list.at ( i ) == 1 ) - { - df::creature_raw* r = find_creature ( i ); - // skip forgotten beasts, titans, demons, and night creatures - if ( !refuse_creature_is_allowed ( r ) ) continue; - debug() << "creature "<< r->creature_id << " " << i << endl; - add_value ( r->creature_id ); - } - } -} - -bool StockpileSerializer::refuse_type_is_allowed ( item_type::item_type type ) -{ - if ( type == item_type::NONE - || type == item_type::BAR - || type == item_type::SMALLGEM - || type == item_type::BLOCKS - || type == item_type::ROUGH - || type == item_type::BOULDER - || type == item_type::CORPSE - || type == item_type::CORPSEPIECE - || type == item_type::ROCK - || type == item_type::ORTHOPEDIC_CAST - ) return false; - return true; -} - + }, + [&](bool all, char val) { + auto & bfood = mBuffer.food(); -void StockpileSerializer::write_refuse() -{ - StockpileSettings::RefuseSet *refuse = mBuffer.mutable_refuse(); - refuse->set_fresh_raw_hide ( mPile->settings.refuse.fresh_raw_hide ); - refuse->set_rotten_raw_hide ( mPile->settings.refuse.rotten_raw_hide ); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); - serialize_list_item_type ( filter, [=] ( const std::string &token ) - { - refuse->add_type ( token ); - }, mPile->settings.refuse.type ); - - // corpses - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_corpses ( id ); - }, mPile->settings.refuse.corpses ); - // body_parts - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_body_parts ( id ); - }, mPile->settings.refuse.body_parts ); - // skulls - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_skulls ( id ); - }, mPile->settings.refuse.skulls ); - // bones - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_bones ( id ); - }, mPile->settings.refuse.bones ); - // hair - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_hair ( id ); - }, mPile->settings.refuse.hair ); - // shells - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_shells ( id ); - }, mPile->settings.refuse.shells ); - // teeth - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_teeth ( id ); - }, mPile->settings.refuse.teeth ); - // horns - refuse_write_helper ( [=] ( const std::string &id ) - { - refuse->add_horns ( id ); - }, mPile->settings.refuse.horns ); -} + set_flag("preparedmeals", filters, all, val, bfood.prepared_meals(), pfood.prepared_meals); -void StockpileSerializer::refuse_read_helper ( std::function get_value, size_t list_size, std::vector* pile_list ) -{ - pile_list->clear(); - pile_list->resize ( world->raws.creatures.all.size(), '\0' ); - if ( list_size > 0 ) - { - for ( size_t i = 0; i < list_size; ++i ) - { - const std::string creature_id = get_value ( i ); - const int idx = find_creature ( creature_id ); - const df::creature_raw* creature = find_creature ( idx ); - if ( idx < 0 || !refuse_creature_is_allowed ( creature ) || size_t(idx) >= pile_list->size() ) - { - debug() << "WARNING invalid refuse creature " << creature_id << ", idx=" << idx << endl; - continue; + for (int32_t mat_category = traits::first_item_value; mat_category < traits::last_item_value; ++mat_category) { + food_pair p = food_map((organic_mat_category)mat_category); + if (!p.valid) + continue; + unserialize_list_organic_mat(p.name, all, val, filters, + p.get_value, p.serialized_count, *p.stockpile_values, + (organic_mat_category)mat_category); } - debug() << " creature " << idx << " is " << creature_id << endl; - pile_list->at ( idx ) = 1; - } - } -} - - - -void StockpileSerializer::read_refuse() -{ - if ( mBuffer.has_refuse() ) - { - mPile->settings.flags.bits.refuse = 1; - const StockpileSettings::RefuseSet refuse = mBuffer.refuse(); - debug() << "refuse: " <settings.refuse.fresh_raw_hide = refuse.fresh_raw_hide(); - mPile->settings.refuse.rotten_raw_hide = refuse.rotten_raw_hide(); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::refuse_type_is_allowed, this, _1 ); - unserialize_list_item_type ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return refuse.type ( idx ); - }, refuse.type_size(), &mPile->settings.refuse.type ); - - // corpses - debug() << " corpses" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.corpses ( idx ); - }, refuse.corpses_size(), &mPile->settings.refuse.corpses ); - // body_parts - debug() << " body_parts" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.body_parts ( idx ); - }, refuse.body_parts_size(), &mPile->settings.refuse.body_parts ); - // skulls - debug() << " skulls" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.skulls ( idx ); - }, refuse.skulls_size(), &mPile->settings.refuse.skulls ); - // bones - debug() << " bones" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.bones ( idx ); - }, refuse.bones_size(), &mPile->settings.refuse.bones ); - // hair - debug() << " hair" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.hair ( idx ); - }, refuse.hair_size(), &mPile->settings.refuse.hair ); - // shells - debug() << " shells" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.shells ( idx ); - }, refuse.shells_size(), &mPile->settings.refuse.shells ); - // teeth - debug() << " teeth" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.teeth ( idx ); - }, refuse.teeth_size(), &mPile->settings.refuse.teeth ); - // horns - debug() << " horns" << endl; - refuse_read_helper ( [=] ( const size_t & idx ) -> const std::string& - { - return refuse.horns ( idx ); - }, refuse.horns_size(), &mPile->settings.refuse.horns ); - } - else - { - mPile->settings.flags.bits.refuse = 0; - mPile->settings.refuse.type.clear(); - mPile->settings.refuse.corpses.clear(); - mPile->settings.refuse.body_parts.clear(); - mPile->settings.refuse.skulls.clear(); - mPile->settings.refuse.bones.clear(); - mPile->settings.refuse.hair.clear(); - mPile->settings.refuse.shells.clear(); - mPile->settings.refuse.teeth.clear(); - mPile->settings.refuse.horns.clear(); - mPile->settings.refuse.fresh_raw_hide = false; - mPile->settings.refuse.rotten_raw_hide = false; - } -} - -bool StockpileSerializer::stone_is_allowed ( const MaterialInfo &mi ) -{ - if ( !mi.isValid() ) return false; - const bool is_allowed_soil = mi.inorganic->flags.is_set ( inorganic_flags::SOIL ) && !mi.inorganic->flags.is_set ( inorganic_flags::AQUIFER ); - const bool is_allowed_stone = mi.material->flags.is_set ( material_flags::IS_STONE ) && !mi.material->flags.is_set ( material_flags::NO_STONE_STOCKPILE ); - return is_allowed_soil || is_allowed_stone; -} - -void StockpileSerializer::write_stone() -{ - StockpileSettings::StoneSet *stone= mBuffer.mutable_stone(); - - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::stone_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - stone->add_mats ( token ); - }, mPile->settings.stone.mats ); -} - -void StockpileSerializer::read_stone() -{ - if ( mBuffer.has_stone() ) - { - mPile->settings.flags.bits.stone = 1; - const StockpileSettings::StoneSet stone = mBuffer.stone(); - debug() << "stone: " < const std::string& - { - return stone.mats ( idx ); - }, stone.mats_size(), &mPile->settings.stone.mats ); - } - else - { - mPile->settings.flags.bits.stone = 0; - mPile->settings.stone.mats.clear(); - } } -bool StockpileSerializer::ammo_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); +static bool furniture_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && (mi.material->flags.is_set(material_flags::IS_METAL) || mi.material->flags.is_set(material_flags::IS_STONE)); } -void StockpileSerializer::write_ammo() -{ - StockpileSettings::AmmoSet *ammo = mBuffer.mutable_ammo(); - - // ammo type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - ammo->add_type ( token ); - }, mPile->settings.ammo.type, - std::vector ( world->raws.itemdefs.ammo.begin(),world->raws.itemdefs.ammo.end() ), - item_type::AMMO ); - - // metal - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - ammo->add_mats ( token ); - }, mPile->settings.ammo.mats ); +bool StockpileSerializer::write_furniture(StockpileSettings::FurnitureSet* furniture) { + using df::enums::furniture_type::furniture_type; + using type_traits = df::enum_traits; - // other mats - only wood and bone - if ( mPile->settings.ammo.other_mats.size() > 2 ) - { - debug() << "WARNING: ammo other materials > 2! " << mPile->settings.ammo.other_mats.size() << endl; - } + auto & pfurniture = mPile->settings.furniture; + bool all = true; - for ( size_t i = 0; i < std::min ( size_t ( 2 ), mPile->settings.ammo.other_mats.size() ); ++i ) - { - if ( !mPile->settings.ammo.other_mats.at ( i ) ) + for (size_t i = 0; i < pfurniture.type.size(); ++i) { + if (!pfurniture.type.at(i)) { + all = false; continue; - const std::string token = i == 0 ? "WOOD" : "BONE"; - ammo->add_other_mats ( token ); - debug() << " other mats " << i << " is " << token << endl; - } - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - ammo->add_quality_core ( token ); - }, mPile->settings.ammo.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - ammo->add_quality_total ( token ); - }, mPile->settings.ammo.quality_total ); -} - -void StockpileSerializer::read_ammo() -{ - if ( mBuffer.has_ammo() ) - { - mPile->settings.flags.bits.ammo = 1; - const StockpileSettings::AmmoSet ammo = mBuffer.ammo(); - debug() << "ammo: " < const std::string& - { - return ammo.type ( idx ); - }, ammo.type_size(), &mPile->settings.ammo.type, item_type::AMMO ); - - // materials metals - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::ammo_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return ammo.mats ( idx ); - }, ammo.mats_size(), &mPile->settings.ammo.mats ); - - // others - mPile->settings.ammo.other_mats.clear(); - mPile->settings.ammo.other_mats.resize ( 2, '\0' ); - if ( ammo.other_mats_size() > 0 ) - { - // TODO remove hardcoded value - for ( int i = 0; i < ammo.other_mats_size(); ++i ) - { - const std::string token = ammo.other_mats ( i ); - const int32_t idx = token == "WOOD" ? 0 : token == "BONE" ? 1 : -1; - debug() << " other mats " << idx << " is " << token << endl; - if ( idx != -1 ) - mPile->settings.ammo.other_mats.at ( idx ) = 1; - } } + string f_type(type_traits::key_table[i]); + furniture->add_type(f_type); + DEBUG(log).print("furniture_type %zd is %s\n", i, f_type.c_str()); + } + + all = serialize_list_material( + furniture_mat_is_allowed, + [&](const string& token) { furniture->add_mats(token); }, + pfurniture.mats) && all; + all = serialize_list_other_mats( + mOtherMatsFurniture.mats, + [&](const string& token) { furniture->add_other_mats(token); }, + pfurniture.other_mats) && all; + all = serialize_list_quality( + [&](const string& token) { furniture->add_quality_core(token); }, + pfurniture.quality_core) && all; + all = serialize_list_quality( + [&](const string& token) { furniture->add_quality_total(token); }, + pfurniture.quality_total) && all; + + return all; +} + +void StockpileSerializer::read_furniture(DeserializeMode mode, const vector& filters) { + auto & pfurniture = mPile->settings.furniture; + read_category("furniture", mode, + std::bind(&StockpileSettings::has_furniture, mBuffer), + std::bind(&StockpileSettings::furniture, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_furniture, + [&]() { + pfurniture.type.clear(); + pfurniture.other_mats.clear(); + pfurniture.mats.clear(); + quality_clear(pfurniture.quality_core); + quality_clear(pfurniture.quality_total); + }, + [&](bool all, char val) { + auto & bfurniture = mBuffer.furniture(); + + using df::enums::furniture_type::furniture_type; + df::enum_traits type_traits; + size_t num_elems = type_traits.last_item_value + 1; + pfurniture.type.resize(num_elems, '\0'); + + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + string id = ENUM_KEY_STR(furniture_type, (df::furniture_type)idx); + set_filter_elem("type", filters, val, id, idx, pfurniture.type.at(idx)); + } + } else { + for (int i = 0; i < bfurniture.type_size(); ++i) { + const string token = bfurniture.type(i); + df::enum_traits::base_type idx = linear_index(type_traits, token); + if (idx < 0 || size_t(idx) >= pfurniture.type.size()) { + WARN(log).print("furniture type index invalid %s, idx=%d\n", token.c_str(), idx); + continue; + } + set_filter_elem("type", filters, val, token, idx, pfurniture.type.at(idx)); + } + } - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return ammo.quality_core ( idx ); - }, ammo.quality_core_size(), mPile->settings.ammo.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return ammo.quality_total ( idx ); - }, ammo.quality_total_size(), mPile->settings.ammo.quality_total ); - } - else - { - mPile->settings.flags.bits.ammo = 0; - mPile->settings.ammo.type.clear(); - mPile->settings.ammo.mats.clear(); - mPile->settings.ammo.other_mats.clear(); - quality_clear ( mPile->settings.ammo.quality_core ); - quality_clear ( mPile->settings.ammo.quality_total ); - } -} - -bool StockpileSerializer::coins_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid(); -} + unserialize_list_material("mats", all, val, filters, furniture_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bfurniture.mats(idx); }, + bfurniture.mats_size(), pfurniture.mats); -void StockpileSerializer::write_coins() -{ - StockpileSettings::CoinSet *coins = mBuffer.mutable_coin(); - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::coins_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - coins->add_mats ( token ); - }, mPile->settings.coins.mats ); -} + unserialize_list_other_mats("other", all, val, filters, + mOtherMatsFurniture.mats, [&](const size_t& idx) -> const string& { return bfurniture.other_mats(idx); }, + bfurniture.other_mats_size(), pfurniture.other_mats); -void StockpileSerializer::read_coins() -{ - if ( mBuffer.has_coin() ) - { - mPile->settings.flags.bits.coins = 1; - const StockpileSettings::CoinSet coins = mBuffer.coin(); - debug() << "coins: " < const string& { return bfurniture.quality_core(idx); }, + bfurniture.quality_core_size(), pfurniture.quality_core); - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::coins_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return coins.mats ( idx ); - }, coins.mats_size(), &mPile->settings.coins.mats ); - } - else - { - mPile->settings.flags.bits.coins = 0; - mPile->settings.coins.mats.clear(); - } + unserialize_list_quality("total", all, val, filters, + [&](const size_t& idx) -> const string& { return bfurniture.quality_total(idx); }, + bfurniture.quality_total_size(), pfurniture.quality_total); + }); } -void StockpileSerializer::bars_blocks_setup_other_mats() -{ - mOtherMatsBars.insert ( std::make_pair ( 0,"COAL" ) ); - mOtherMatsBars.insert ( std::make_pair ( 1,"POTASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 2,"ASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 3,"PEARLASH" ) ); - mOtherMatsBars.insert ( std::make_pair ( 4,"SOAP" ) ); - - mOtherMatsBlocks.insert ( std::make_pair ( 0,"GREEN_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 1,"CLEAR_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 2,"CRYSTAL_GLASS" ) ); - mOtherMatsBlocks.insert ( std::make_pair ( 3,"WOOD" ) ); +static bool gem_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && mi.material->flags.is_set(material_flags::IS_GEM); } -bool StockpileSerializer::bars_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); +static bool gem_cut_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && (mi.material->flags.is_set(material_flags::IS_GEM) || mi.material->flags.is_set(material_flags::IS_STONE)); } -bool StockpileSerializer::blocks_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_METAL ) - || mi.material->flags.is_set ( material_flags::IS_STONE ) ); +static bool gem_other_mat_is_allowed(MaterialInfo& mi) { + return mi.isValid() && (mi.getToken() == "GLASS_GREEN" || mi.getToken() == "GLASS_CLEAR" || mi.getToken() == "GLASS_CRYSTAL"); } - -void StockpileSerializer::write_bars_blocks() -{ - StockpileSettings::BarsBlocksSet *bars_blocks = mBuffer.mutable_barsblocks(); +bool StockpileSerializer::write_gems(StockpileSettings::GemsSet* gems) { MaterialInfo mi; - FuncMaterialAllowed filter = std::bind ( &StockpileSerializer::bars_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - bars_blocks->add_bars_mats ( token ); - }, mPile->settings.bars_blocks.bars_mats ); - - // blocks mats - filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); - serialize_list_material ( filter, [=] ( const std::string &token ) - { - bars_blocks->add_blocks_mats ( token ); - }, mPile->settings.bars_blocks.blocks_mats ); - // bars other mats - serialize_list_other_mats ( mOtherMatsBars, [=] ( const std::string &token ) - { - bars_blocks->add_bars_other_mats ( token ); - }, mPile->settings.bars_blocks.bars_other_mats ); + auto & pgems = mPile->settings.gems; - // blocks other mats - serialize_list_other_mats ( mOtherMatsBlocks, [=] ( const std::string &token ) - { - bars_blocks->add_blocks_other_mats ( token ); - }, mPile->settings.bars_blocks.blocks_other_mats ); -} - -void StockpileSerializer::read_bars_blocks() -{ - if ( mBuffer.has_barsblocks() ) - { - mPile->settings.flags.bits.bars_blocks = 1; - const StockpileSettings::BarsBlocksSet bars_blocks = mBuffer.barsblocks(); - debug() << "bars_blocks: " < const std::string& - { - return bars_blocks.bars_mats ( idx ); - }, bars_blocks.bars_mats_size(), &mPile->settings.bars_blocks.bars_mats ); - - // blocks - filter = std::bind ( &StockpileSerializer::blocks_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.blocks_mats ( idx ); - }, bars_blocks.blocks_mats_size(), &mPile->settings.bars_blocks.blocks_mats ); - // bars other mats - unserialize_list_other_mats ( mOtherMatsBars, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.bars_other_mats ( idx ); - }, bars_blocks.bars_other_mats_size(), &mPile->settings.bars_blocks.bars_other_mats ); - - - // blocks other mats - unserialize_list_other_mats ( mOtherMatsBlocks, [=] ( const size_t & idx ) -> const std::string& - { - return bars_blocks.blocks_other_mats ( idx ); - }, bars_blocks.blocks_other_mats_size(), &mPile->settings.bars_blocks.blocks_other_mats ); - - } - else - { - mPile->settings.flags.bits.bars_blocks = 0; - mPile->settings.bars_blocks.bars_other_mats.clear(); - mPile->settings.bars_blocks.bars_mats.clear(); - mPile->settings.bars_blocks.blocks_other_mats.clear(); - mPile->settings.bars_blocks.blocks_mats.clear(); - } -} + bool all = serialize_list_material( + gem_mat_is_allowed, + [&](const string& token) { gems->add_rough_mats(token); }, + pgems.rough_mats); -bool StockpileSerializer::gem_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_GEM ); -} -bool StockpileSerializer::gem_cut_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && ( mi.material->flags.is_set ( material_flags::IS_GEM ) || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; -} -bool StockpileSerializer::gem_other_mat_is_allowed ( MaterialInfo &mi ) -{ - return mi.isValid() && ( mi.getToken() == "GLASS_GREEN" || mi.getToken() == "GLASS_CLEAR" || mi.getToken() == "GLASS_CRYSTAL" ); -} + all = serialize_list_material( + gem_cut_mat_is_allowed, + [&](const string& token) { gems->add_cut_mats(token); }, + pgems.cut_mats) && all; -void StockpileSerializer::write_gems() -{ - StockpileSettings::GemsSet *gems = mBuffer.mutable_gems(); - MaterialInfo mi; - // rough mats - FuncMaterialAllowed filter_rough = std::bind ( &StockpileSerializer::gem_mat_is_allowed, this, _1 ); - serialize_list_material ( filter_rough, [=] ( const std::string &token ) - { - gems->add_rough_mats ( token ); - }, mPile->settings.gems.rough_mats ); - // cut mats - FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); - serialize_list_material ( filter_cut, [=] ( const std::string &token ) - { - gems->add_cut_mats ( token ); - }, mPile->settings.gems.cut_mats ); - // rough other - for ( size_t i = 0; i < mPile->settings.gems.rough_other_mats.size(); ++i ) - { - if ( mPile->settings.gems.rough_other_mats.at ( i ) ) - { - mi.decode ( i, -1 ); - if ( !gem_other_mat_is_allowed ( mi ) ) continue; - debug() << " gem rough_other mat" << i << " is " << mi.getToken() << endl; - gems->add_rough_other_mats ( mi.getToken() ); - } - } - // cut other - for ( size_t i = 0; i < mPile->settings.gems.cut_other_mats.size(); ++i ) - { - if ( mPile->settings.gems.cut_other_mats.at ( i ) ) - { - mi.decode ( i, -1 ); - if ( !mi.isValid() ) mi.decode ( 0, i ); - if ( !gem_other_mat_is_allowed ( mi ) ) continue; - debug() << " gem cut_other mat" << i << " is " << mi.getToken() << endl; - gems->add_cut_other_mats ( mi.getToken() ); + for (size_t i = 0; i < pgems.rough_other_mats.size(); ++i) { + if (!pgems.rough_other_mats.at(i)) { + all = false; + continue; } + mi.decode(i, -1); + if (!gem_other_mat_is_allowed(mi)) + continue; + DEBUG(log).print("gem rough_other mat %zd is %s\n", i, mi.getToken().c_str()); + gems->add_rough_other_mats(mi.getToken()); } -} -void StockpileSerializer::read_gems() -{ - if ( mBuffer.has_gems() ) - { - mPile->settings.flags.bits.gems = 1; - const StockpileSettings::GemsSet gems = mBuffer.gems(); - debug() << "gems: " < const std::string& - { - return gems.rough_mats ( idx ); - }, gems.rough_mats_size(), &mPile->settings.gems.rough_mats ); - - // cut - FuncMaterialAllowed filter_cut = std::bind ( &StockpileSerializer::gem_cut_mat_is_allowed, this, _1 ); - unserialize_list_material ( filter_cut, [=] ( const size_t & idx ) -> const std::string& - { - return gems.cut_mats ( idx ); - }, gems.cut_mats_size(), &mPile->settings.gems.cut_mats ); - - const size_t builtin_size = std::extentraws.mat_table.builtin ) >::value; - // rough other - mPile->settings.gems.rough_other_mats.clear(); - mPile->settings.gems.rough_other_mats.resize ( builtin_size, '\0' ); - for ( int i = 0; i < gems.rough_other_mats_size(); ++i ) - { - const std::string token = gems.rough_other_mats ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) - { - debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; - continue; - } - debug() << " rough_other mats " << mi.type << " is " << token << endl; - mPile->settings.gems.rough_other_mats.at ( mi.type ) = 1; + for (size_t i = 0; i < pgems.cut_other_mats.size(); ++i) { + if (!pgems.cut_other_mats.at(i)) { + all = false; + continue; } - - // cut other - mPile->settings.gems.cut_other_mats.clear(); - mPile->settings.gems.cut_other_mats.resize ( builtin_size, '\0' ); - for ( int i = 0; i < gems.cut_other_mats_size(); ++i ) - { - const std::string token = gems.cut_other_mats ( i ); - MaterialInfo mi; - mi.find ( token ); - if ( !mi.isValid() || size_t(mi.type) >= builtin_size ) - { - debug() << "WARNING: invalid gem mat " << token << ". idx=" << mi.type << endl; - continue; + mi.decode(i, -1); + if (!mi.isValid()) + mi.decode(0, i); + if (!gem_other_mat_is_allowed(mi)) + continue; + DEBUG(log).print("gem cut_other mat %zd is %s\n", i, mi.getToken().c_str()); + gems->add_cut_other_mats(mi.getToken()); + } + + return all; +} + +void StockpileSerializer::read_gems(DeserializeMode mode, const vector& filters) { + auto & pgems = mPile->settings.gems; + read_category("gems", mode, + std::bind(&StockpileSettings::has_gems, mBuffer), + std::bind(&StockpileSettings::gems, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_gems, + [&]() { + pgems.cut_other_mats.clear(); + pgems.cut_mats.clear(); + pgems.rough_other_mats.clear(); + pgems.rough_mats.clear(); + }, + [&](bool all, char val) { + auto & bgems = mBuffer.gems(); + + unserialize_list_material("mats/rough", all, val, filters, gem_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bgems.rough_mats(idx); }, + bgems.rough_mats_size(), pgems.rough_mats); + + unserialize_list_material("mats/cut", all, val, filters, gem_cut_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bgems.cut_mats(idx); }, + bgems.cut_mats_size(), pgems.cut_mats); + + const size_t builtin_size = std::extentraws.mat_table.builtin)>::value; + pgems.rough_other_mats.resize(builtin_size, '\0'); + pgems.cut_other_mats.resize(builtin_size, '\0'); + if (all) { + for (size_t idx = 0; idx < builtin_size; ++idx) { + MaterialInfo mi; + mi.decode(idx, -1); + if (gem_other_mat_is_allowed(mi)) + set_filter_elem("other/rough", filters, val, mi.getToken(), idx, pgems.rough_other_mats.at(idx)); + if (!mi.isValid()) + mi.decode(0, idx); + if (gem_other_mat_is_allowed(mi)) + set_filter_elem("other/cut", filters, val, mi.getToken(), idx, pgems.cut_other_mats.at(idx)); + } + return; + } else { + MaterialInfo mi; + for (int i = 0; i < (int)builtin_size; ++i) { + if (i < bgems.rough_other_mats_size()) { + string id = bgems.rough_other_mats(i); + if (mi.find(id) && mi.isValid() && size_t(mi.type) < builtin_size) + set_filter_elem("other/rough", filters, val, id, mi.type, pgems.rough_other_mats.at(mi.type)); + } + if (i < bgems.cut_other_mats_size()) { + string id = bgems.cut_other_mats(i); + if (mi.find(id) && mi.isValid() && size_t(mi.type) < builtin_size) + set_filter_elem("other/cut", filters, val, id, mi.type, pgems.cut_other_mats.at(mi.type)); + } + } } - debug() << " cut_other mats " << mi.type << " is " << token << endl; - mPile->settings.gems.cut_other_mats.at ( mi.type ) = 1; - } - } - else - { - mPile->settings.flags.bits.gems = 0; - mPile->settings.gems.cut_other_mats.clear(); - mPile->settings.gems.cut_mats.clear(); - mPile->settings.gems.rough_other_mats.clear(); - mPile->settings.gems.rough_mats.clear(); - } -} - -bool StockpileSerializer::finished_goods_type_is_allowed ( item_type::item_type type ) -{ - switch ( type ) - { - case item_type::CHAIN: - case item_type::FLASK: - case item_type::GOBLET: - case item_type::INSTRUMENT: - case item_type::TOY: - case item_type::ARMOR: - case item_type::SHOES: - case item_type::HELM: - case item_type::GLOVES: - case item_type::FIGURINE: - case item_type::AMULET: - case item_type::SCEPTER: - case item_type::CROWN: - case item_type::RING: - case item_type::EARRING: - case item_type::BRACELET: - case item_type::GEM: - case item_type::TOTEM: - case item_type::PANTS: - case item_type::BACKPACK: - case item_type::QUIVER: - case item_type::SPLINT: - case item_type::CRUTCH: - case item_type::TOOL: - case item_type::BOOK: - return true; - default: + }); +} + +bool StockpileSerializer::write_leather(StockpileSettings::LeatherSet* leather) { + return serialize_list_organic_mat( + [&](const string& id) { leather->add_mats(id); }, + &mPile->settings.leather.mats, organic_mat_category::Leather); +} + +void StockpileSerializer::read_leather(DeserializeMode mode, const vector& filters) { + auto & pleather = mPile->settings.leather; + read_category("leather", mode, + std::bind(&StockpileSettings::has_leather, mBuffer), + std::bind(&StockpileSettings::leather, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_leather, + [&]() { + pleather.mats.clear(); + }, + [&](bool all, char val) { + auto & bleather = mBuffer.leather(); + + unserialize_list_organic_mat("", all, val, filters, + [&](size_t idx) -> string { return bleather.mats(idx); }, + bleather.mats_size(), pleather.mats, organic_mat_category::Leather); + }); +} + +bool StockpileSerializer::write_corpses(StockpileSettings::CorpsesSet* corpses) { + return serialize_list_creature( + [&](const string& token) { corpses->add_corpses(token); }, + mPile->settings.corpses.corpses); +} + +void StockpileSerializer::read_corpses(DeserializeMode mode, const vector& filters) { + auto & pcorpses = mPile->settings.corpses; + read_category("corpses", mode, + std::bind(&StockpileSettings::has_corpses_v50, mBuffer), + std::bind(&StockpileSettings::corpses_v50, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_corpses, + [&]() { + pcorpses.corpses.clear(); + }, + [&](bool all, char val) { + auto & bcorpses = mBuffer.corpses_v50(); + unserialize_list_creature("", all, val, filters, + [&](const size_t& idx) -> const string& { return bcorpses.corpses(idx); }, + bcorpses.corpses_size(), pcorpses.corpses); + }); +} + +static bool refuse_type_is_allowed(item_type::item_type type) { + if (type == item_type::NONE || type == item_type::BAR || type == item_type::SMALLGEM + || type == item_type::BLOCKS || type == item_type::ROUGH || type == item_type::BOULDER + || type == item_type::CORPSE || type == item_type::CORPSEPIECE || type == item_type::ROCK + || type == item_type::ORTHOPEDIC_CAST) return false; - } - -} - -void StockpileSerializer::finished_goods_setup_other_mats() -{ - mOtherMatsFinishedGoods.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 3,"TOOTH" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 4,"HORN" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 5,"PEARL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 6,"SHELL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 7,"LEATHER" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 8,"SILK" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 9,"AMBER" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 10,"CORAL" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 11,"GREEN_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 12,"CLEAR_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 13,"CRYSTAL_GLASS" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 14,"YARN" ) ); - mOtherMatsFinishedGoods.insert ( std::make_pair ( 15,"WAX" ) ); -} - -bool StockpileSerializer::finished_goods_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() - && mi.material - && ( mi.material->flags.is_set ( material_flags::IS_GEM ) - || mi.material->flags.is_set ( material_flags::IS_METAL ) - || mi.material->flags.is_set ( material_flags::IS_STONE ) ) ; -} - -void StockpileSerializer::write_finished_goods() -{ - StockpileSettings::FinishedGoodsSet *finished_goods = mBuffer.mutable_finished_goods(); - - // type - FuncItemAllowed filter = std::bind ( &StockpileSerializer::finished_goods_type_is_allowed, this, _1 ); - serialize_list_item_type ( filter, [=] ( const std::string &token ) - { - finished_goods->add_type ( token ); - }, mPile->settings.finished_goods.type ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - finished_goods->add_mats ( token ); - }, mPile->settings.finished_goods.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const std::string &token ) - { - finished_goods->add_other_mats ( token ); - }, mPile->settings.finished_goods.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - finished_goods->add_quality_core ( token ); - }, mPile->settings.finished_goods.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - finished_goods->add_quality_total ( token ); - }, mPile->settings.finished_goods.quality_total ); -} - -void StockpileSerializer::read_finished_goods() -{ - if ( mBuffer.has_finished_goods() ) - { - mPile->settings.flags.bits.finished_goods = 1; - const StockpileSettings::FinishedGoodsSet finished_goods = mBuffer.finished_goods(); - debug() << "finished_goods: " < const std::string& - { - return finished_goods.type ( idx ); - }, finished_goods.type_size(), &mPile->settings.finished_goods.type ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::finished_goods_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.mats ( idx ); - }, finished_goods.mats_size(), &mPile->settings.finished_goods.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsFinishedGoods, [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.other_mats ( idx ); - }, finished_goods.other_mats_size(), &mPile->settings.finished_goods.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.quality_core ( idx ); - }, finished_goods.quality_core_size(), mPile->settings.finished_goods.quality_core ); - - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return finished_goods.quality_total ( idx ); - }, finished_goods.quality_total_size(), mPile->settings.finished_goods.quality_total ); - - } - else - { - mPile->settings.flags.bits.finished_goods = 0; - mPile->settings.finished_goods.type.clear(); - mPile->settings.finished_goods.other_mats.clear(); - mPile->settings.finished_goods.mats.clear(); - quality_clear ( mPile->settings.finished_goods.quality_core ); - quality_clear ( mPile->settings.finished_goods.quality_total ); - } -} - -void StockpileSerializer::write_leather() -{ - StockpileSettings::LeatherSet *leather = mBuffer.mutable_leather(); - - FuncWriteExport setter = [=] ( const std::string &id ) - { - leather->add_mats ( id ); - }; - serialize_list_organic_mat ( setter, &mPile->settings.leather.mats, organic_mat_category::Leather ); -} -void StockpileSerializer::read_leather() -{ - if ( mBuffer.has_leather() ) - { - mPile->settings.flags.bits.leather = 1; - const StockpileSettings::LeatherSet leather = mBuffer.leather(); - debug() << "leather: " < std::string - { - return leather.mats ( idx ); - }, leather.mats_size(), &mPile->settings.leather.mats, organic_mat_category::Leather ); - } - else - { - mPile->settings.flags.bits.leather = 0; - mPile->settings.leather.mats.clear(); - } -} - -void StockpileSerializer::write_cloth() -{ - StockpileSettings::ClothSet * cloth = mBuffer.mutable_cloth(); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_silk ( token ); - }, &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_plant ( token ); - }, &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_yarn ( token ); - }, &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_thread_metal ( token ); - }, &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_silk ( token ); - }, &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_plant ( token ); - }, &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_yarn ( token ); - }, &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); - - serialize_list_organic_mat ( [=] ( const std::string &token ) - { - cloth->add_cloth_metal ( token ); - }, &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); - -} -void StockpileSerializer::read_cloth() -{ - if ( mBuffer.has_cloth() ) - { - mPile->settings.flags.bits.cloth = 1; - const StockpileSettings::ClothSet cloth = mBuffer.cloth(); - debug() << "cloth: " < std::string - { - return cloth.thread_silk ( idx ); - }, cloth.thread_silk_size(), &mPile->settings.cloth.thread_silk, organic_mat_category::Silk ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_plant ( idx ); - }, cloth.thread_plant_size(), &mPile->settings.cloth.thread_plant, organic_mat_category::PlantFiber ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_yarn ( idx ); - }, cloth.thread_yarn_size(), &mPile->settings.cloth.thread_yarn, organic_mat_category::Yarn ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.thread_metal ( idx ); - }, cloth.thread_metal_size(), &mPile->settings.cloth.thread_metal, organic_mat_category::MetalThread ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_silk ( idx ); - }, cloth.cloth_silk_size(), &mPile->settings.cloth.cloth_silk, organic_mat_category::Silk ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_plant ( idx ); - }, cloth.cloth_plant_size(), &mPile->settings.cloth.cloth_plant, organic_mat_category::PlantFiber ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_yarn ( idx ); - }, cloth.cloth_yarn_size(), &mPile->settings.cloth.cloth_yarn, organic_mat_category::Yarn ); - - unserialize_list_organic_mat ( [=] ( size_t idx ) -> std::string - { - return cloth.cloth_metal ( idx ); - }, cloth.cloth_metal_size(), &mPile->settings.cloth.cloth_metal, organic_mat_category::MetalThread ); - } - else - { - mPile->settings.cloth.thread_metal.clear(); - mPile->settings.cloth.thread_plant.clear(); - mPile->settings.cloth.thread_silk.clear(); - mPile->settings.cloth.thread_yarn.clear(); - mPile->settings.cloth.cloth_metal.clear(); - mPile->settings.cloth.cloth_plant.clear(); - mPile->settings.cloth.cloth_silk.clear(); - mPile->settings.cloth.cloth_yarn.clear(); - mPile->settings.flags.bits.cloth = 0; - } -} - -bool StockpileSerializer::wood_mat_is_allowed ( const df::plant_raw * plant ) -{ - return plant && plant->flags.is_set ( plant_raw_flags::TREE ); + return true; } -void StockpileSerializer::write_wood() -{ - StockpileSettings::WoodSet * wood = mBuffer.mutable_wood(); - for ( size_t i = 0; i < mPile->settings.wood.mats.size(); ++i ) - { - if ( mPile->settings.wood.mats.at ( i ) ) - { - const df::plant_raw * plant = find_plant ( i ); - if ( !wood_mat_is_allowed ( plant ) ) continue; - wood->add_mats ( plant->id ); - debug() << " plant " << i << " is " << plant->id << endl; +bool StockpileSerializer::write_refuse(StockpileSettings::RefuseSet* refuse) { + auto & prefuse = mPile->settings.refuse; + bool all = prefuse.fresh_raw_hide && prefuse.rotten_raw_hide; + + refuse->set_fresh_raw_hide(prefuse.fresh_raw_hide); + refuse->set_rotten_raw_hide(prefuse.rotten_raw_hide); + + all = serialize_list_item_type(refuse_type_is_allowed, + [&](const string& token) { refuse->add_type(token); }, + prefuse.type) && all; + + all = serialize_list_creature( + [&](const string& token) { refuse->add_corpses(token); }, + prefuse.corpses) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_body_parts(token); }, + prefuse.body_parts) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_skulls(token); }, + prefuse.skulls) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_bones(token); }, + prefuse.bones) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_hair(token); }, + prefuse.hair) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_shells(token); }, + prefuse.shells) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_teeth(token); }, + prefuse.teeth) && all; + all = serialize_list_creature( + [&](const string& token) { refuse->add_horns(token); }, + prefuse.horns) && all; + + return all; +} + +void StockpileSerializer::read_refuse(DeserializeMode mode, const vector& filters) { + auto & prefuse = mPile->settings.refuse; + read_category("refuse", mode, + std::bind(&StockpileSettings::has_refuse, mBuffer), + std::bind(&StockpileSettings::refuse, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_refuse, + [&]() { + prefuse.fresh_raw_hide = false; + prefuse.rotten_raw_hide = false; + prefuse.type.clear(); + prefuse.corpses.clear(); + prefuse.body_parts.clear(); + prefuse.skulls.clear(); + prefuse.bones.clear(); + prefuse.hair.clear(); + prefuse.shells.clear(); + prefuse.teeth.clear(); + prefuse.horns.clear(); + }, + [&](bool all, char val) { + auto & brefuse = mBuffer.refuse(); + + set_flag("rawhide/fresh", filters, all, val, brefuse.fresh_raw_hide(), prefuse.fresh_raw_hide); + set_flag("rawhide/rotten", filters, all, val, brefuse.rotten_raw_hide(), prefuse.rotten_raw_hide); + + unserialize_list_item_type("type", all, val, filters, refuse_type_is_allowed, + [&](const size_t& idx) -> const string& { return brefuse.type(idx); }, + brefuse.type_size(), prefuse.type); + + unserialize_list_creature("corpses", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.corpses(idx); }, + brefuse.corpses_size(), prefuse.corpses); + unserialize_list_creature("bodyparts", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.body_parts(idx); }, + brefuse.body_parts_size(), prefuse.body_parts); + unserialize_list_creature("skulls", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.skulls(idx); }, + brefuse.skulls_size(), prefuse.skulls); + unserialize_list_creature("bones", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.bones(idx); }, + brefuse.bones_size(), prefuse.bones); + unserialize_list_creature("hair", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.hair(idx); }, + brefuse.hair_size(), prefuse.hair); + unserialize_list_creature("shells", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.shells(idx); }, + brefuse.shells_size(), prefuse.shells); + unserialize_list_creature("teeth", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.teeth(idx); }, + brefuse.teeth_size(), prefuse.teeth); + unserialize_list_creature("horns", all, val, filters, + [&](const size_t& idx) -> const string& { return brefuse.horns(idx); }, + brefuse.horns_size(), prefuse.horns); + }); + +} + +bool StockpileSerializer::write_sheet(StockpileSettings::SheetSet* sheet) { + bool all = serialize_list_organic_mat( + [&](const string& token) { sheet->add_paper(token); }, + &mPile->settings.sheet.paper, organic_mat_category::Paper); + + all = serialize_list_organic_mat( + [&](const string& token) { sheet->add_parchment(token); }, + &mPile->settings.sheet.parchment, organic_mat_category::Parchment) && all; + + return all; +} + +void StockpileSerializer::read_sheet(DeserializeMode mode, const vector& filters) { + auto & psheet = mPile->settings.sheet; + read_category("sheet", mode, + std::bind(&StockpileSettings::has_sheet, mBuffer), + std::bind(&StockpileSettings::sheet, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_sheet, + [&]() { + psheet.paper.clear(); + psheet.parchment.clear(); + }, + [&](bool all, char val) { + auto & bsheet = mBuffer.sheet(); + + unserialize_list_organic_mat("paper", all, val, filters, + [&](size_t idx) -> string { return bsheet.paper(idx); }, + bsheet.paper_size(), psheet.paper, organic_mat_category::Paper); + + unserialize_list_organic_mat("parchment", all, val, filters, + [&](size_t idx) -> string { return bsheet.parchment(idx); }, + bsheet.parchment_size(), psheet.parchment, organic_mat_category::Parchment); + }); +} + +static bool stone_is_allowed(const MaterialInfo& mi) { + if (!mi.isValid()) + return false; + const bool is_allowed_soil = mi.inorganic->flags.is_set(inorganic_flags::SOIL) && !mi.inorganic->flags.is_set(inorganic_flags::AQUIFER); + const bool is_allowed_stone = mi.material->flags.is_set(material_flags::IS_STONE) && !mi.material->flags.is_set(material_flags::NO_STONE_STOCKPILE); + return is_allowed_soil || is_allowed_stone; +} + +bool StockpileSerializer::write_stone(StockpileSettings::StoneSet* stone) { + return serialize_list_material( + stone_is_allowed, + [&](const string& token) { stone->add_mats(token); }, + mPile->settings.stone.mats); +} + +void StockpileSerializer::read_stone(DeserializeMode mode, const vector& filters) { + auto & pstone = mPile->settings.stone; + read_category("stone", mode, + std::bind(&StockpileSettings::has_stone, mBuffer), + std::bind(&StockpileSettings::stone, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_stone, + [&]() { + pstone.mats.clear(); + }, + [&](bool all, char val) { + auto & bstone = mBuffer.stone(); + + unserialize_list_material("", all, val, filters, stone_is_allowed, + [&](const size_t& idx) -> const string& { return bstone.mats(idx); }, + bstone.mats_size(), pstone.mats); + }); +} + +static bool weapons_mat_is_allowed(const MaterialInfo& mi) { + return mi.isValid() && mi.material && (mi.material->flags.is_set(material_flags::IS_METAL) || mi.material->flags.is_set(material_flags::IS_STONE)); +} + +bool StockpileSerializer::write_weapons(StockpileSettings::WeaponsSet* weapons) { + auto & pweapons = mPile->settings.weapons; + bool all = pweapons.unusable && pweapons.usable; + + weapons->set_unusable(pweapons.unusable); + weapons->set_usable(pweapons.usable); + + all = serialize_list_itemdef( + [&](const string& token) { weapons->add_weapon_type(token); }, + pweapons.weapon_type, + vector(world->raws.itemdefs.weapons.begin(), world->raws.itemdefs.weapons.end()), + item_type::WEAPON) && all; + + all = serialize_list_itemdef( + [&](const string& token) { weapons->add_trapcomp_type(token); }, + pweapons.trapcomp_type, + vector(world->raws.itemdefs.trapcomps.begin(), world->raws.itemdefs.trapcomps.end()), + item_type::TRAPCOMP) && all; + + all = serialize_list_material( + weapons_mat_is_allowed, + [&](const string& token) { weapons->add_mats(token); }, + pweapons.mats) && all; + + all = serialize_list_other_mats( + mOtherMatsWeaponsArmor.mats, + [&](const string& token) { weapons->add_other_mats(token); }, + pweapons.other_mats) && all; + + all = serialize_list_quality( + [&](const string& token) { weapons->add_quality_core(token); }, + pweapons.quality_core) && all; + + all = serialize_list_quality( + [&](const string& token) { weapons->add_quality_total(token); }, + pweapons.quality_total) && all; + + return all; +} + +void StockpileSerializer::read_weapons(DeserializeMode mode, const vector& filters) { + auto & pweapons = mPile->settings.weapons; + read_category("weapons", mode, + std::bind(&StockpileSettings::has_weapons, mBuffer), + std::bind(&StockpileSettings::weapons, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_weapons, + [&]() { + pweapons.unusable = false; + pweapons.usable = false; + pweapons.weapon_type.clear(); + pweapons.trapcomp_type.clear(); + pweapons.other_mats.clear(); + pweapons.mats.clear(); + quality_clear(pweapons.quality_core); + quality_clear(pweapons.quality_total); + }, + [&](bool all, char val) { + auto & bweapons = mBuffer.weapons(); + + set_flag("nouse", filters, all, val, bweapons.unusable(), pweapons.unusable); + set_flag("canuse", filters, all, val, bweapons.usable(), pweapons.usable); + + unserialize_list_itemdef("type/weapon", all, val, filters, + [&](const size_t& idx) -> const string& { return bweapons.weapon_type(idx); }, + bweapons.weapon_type_size(), pweapons.weapon_type, item_type::WEAPON); + + unserialize_list_itemdef("type/trapcomp", all, val, filters, + [&](const size_t& idx) -> const string& { return bweapons.trapcomp_type(idx); }, + bweapons.trapcomp_type_size(), pweapons.trapcomp_type, item_type::TRAPCOMP); + + unserialize_list_material("mats", all, val, filters, weapons_mat_is_allowed, + [&](const size_t& idx) -> const string& { return bweapons.mats(idx); }, + bweapons.mats_size(), pweapons.mats); + + unserialize_list_other_mats("other", all, val, filters, mOtherMatsWeaponsArmor.mats, + [&](const size_t& idx) -> const string& { return bweapons.other_mats(idx); }, + bweapons.other_mats_size(), pweapons.other_mats); + + unserialize_list_quality("core", all, val, filters, + [&](const size_t& idx) -> const string& { return bweapons.quality_core(idx); }, + bweapons.quality_core_size(), pweapons.quality_core); + + unserialize_list_quality("total", all, val, filters, + [&](const size_t& idx) -> const string& { return bweapons.quality_total(idx); }, + bweapons.quality_total_size(), pweapons.quality_total); + }); +} + +static bool wood_mat_is_allowed(const df::plant_raw* plant) { + return plant && plant->flags.is_set(plant_raw_flags::TREE); +} + +bool StockpileSerializer::write_wood(StockpileSettings::WoodSet* wood) { + bool all = true; + for (size_t i = 0; i < mPile->settings.wood.mats.size(); ++i) { + if (!mPile->settings.wood.mats.at(i)) { + all = false; + continue; } - } -} -void StockpileSerializer::read_wood() -{ - if ( mBuffer.has_wood() ) - { - mPile->settings.flags.bits.wood = 1; - const StockpileSettings::WoodSet wood = mBuffer.wood(); - debug() << "wood: " <settings.wood.mats.clear(); - mPile->settings.wood.mats.resize ( world->raws.plants.all.size(), '\0' ); - for ( int i = 0; i < wood.mats_size(); ++i ) - { - const std::string token = wood.mats ( i ); - const size_t idx = find_plant ( token ); - if ( idx < 0 || idx >= mPile->settings.wood.mats.size() ) - { - debug() << "WARNING wood mat index invalid " << token << ", idx=" << idx << endl; - continue; + const df::plant_raw* plant = find_plant(i); + if (!wood_mat_is_allowed(plant)) + continue; + wood->add_mats(plant->id); + DEBUG(log).print("plant %zd is %s\n", i, plant->id.c_str()); + } + return all; +} + +void StockpileSerializer::read_wood(DeserializeMode mode, const vector& filters) { + auto & pwood = mPile->settings.wood; + read_category("wood", mode, + std::bind(&StockpileSettings::has_wood, mBuffer), + std::bind(&StockpileSettings::wood, mBuffer), + mPile->settings.flags.whole, + mPile->settings.flags.mask_wood, + [&]() { + pwood.mats.clear(); + }, + [&](bool all, char val) { + auto & bwood = mBuffer.wood(); + + size_t num_elems = world->raws.plants.all.size(); + pwood.mats.resize(num_elems, '\0'); + + if (all) { + for (size_t idx = 0; idx < num_elems; ++idx) { + string id = world->raws.plants.all[idx]->id; + set_filter_elem("", filters, val, id, idx, pwood.mats.at(idx)); + } + } else { + for (int i = 0; i < bwood.mats_size(); ++i) { + const string token = bwood.mats(i); + const size_t idx = find_plant(token); + if (idx < 0 || (size_t)idx >= num_elems) { + WARN(log).print("wood mat index invalid %s idx=%zd\n", token.c_str(), idx); + continue; + } + set_filter_elem("", filters, val, token, idx, pwood.mats.at(idx)); + } } - debug() << " plant " << idx << " is " << token << endl; - mPile->settings.wood.mats.at ( idx ) = 1; - } - } - else - { - mPile->settings.flags.bits.wood = 0; - mPile->settings.wood.mats.clear(); - } -} - -bool StockpileSerializer::weapons_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && ( - mi.material->flags.is_set ( material_flags::IS_METAL ) || - mi.material->flags.is_set ( material_flags::IS_STONE ) ); - -} - -void StockpileSerializer::write_weapons() -{ - StockpileSettings::WeaponsSet * weapons = mBuffer.mutable_weapons(); - - weapons->set_unusable ( mPile->settings.weapons.unusable ); - weapons->set_usable ( mPile->settings.weapons.usable ); - - // weapon type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - weapons->add_weapon_type ( token ); - }, mPile->settings.weapons.weapon_type, - std::vector ( world->raws.itemdefs.weapons.begin(),world->raws.itemdefs.weapons.end() ), - item_type::WEAPON ); - - // trapcomp type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - weapons->add_trapcomp_type ( token ); - }, mPile->settings.weapons.trapcomp_type, - std::vector ( world->raws.itemdefs.trapcomps.begin(),world->raws.itemdefs.trapcomps.end() ), - item_type::TRAPCOMP ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - weapons->add_mats ( token ); - }, mPile->settings.weapons.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) - { - weapons->add_other_mats ( token ); - }, mPile->settings.weapons.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - weapons->add_quality_core ( token ); - }, mPile->settings.weapons.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - weapons->add_quality_total ( token ); - }, mPile->settings.weapons.quality_total ); -} - -void StockpileSerializer::read_weapons() -{ - if ( mBuffer.has_weapons() ) - { - mPile->settings.flags.bits.weapons = 1; - const StockpileSettings::WeaponsSet weapons = mBuffer.weapons(); - debug() << "weapons: " <settings.weapons.unusable = unusable; - mPile->settings.weapons.usable = usable; - - // weapon type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.weapon_type ( idx ); - }, weapons.weapon_type_size(), &mPile->settings.weapons.weapon_type, item_type::WEAPON ); - - // trapcomp type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.trapcomp_type ( idx ); - }, weapons.trapcomp_type_size(), &mPile->settings.weapons.trapcomp_type, item_type::TRAPCOMP ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::weapons_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return weapons.mats ( idx ); - }, weapons.mats_size(), &mPile->settings.weapons.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& - { - return weapons.other_mats ( idx ); - }, weapons.other_mats_size(), &mPile->settings.weapons.other_mats ); - - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.quality_core ( idx ); - }, weapons.quality_core_size(), mPile->settings.weapons.quality_core ); - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return weapons.quality_total ( idx ); - }, weapons.quality_total_size(), mPile->settings.weapons.quality_total ); - } - else - { - mPile->settings.flags.bits.weapons = 0; - mPile->settings.weapons.weapon_type.clear(); - mPile->settings.weapons.trapcomp_type.clear(); - mPile->settings.weapons.other_mats.clear(); - mPile->settings.weapons.mats.clear(); - quality_clear ( mPile->settings.weapons.quality_core ); - quality_clear ( mPile->settings.weapons.quality_total ); - } - -} - -void StockpileSerializer::weapons_armor_setup_other_mats() -{ - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 0,"WOOD" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 1,"PLANT_CLOTH" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 2,"BONE" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 3,"SHELL" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 4,"LEATHER" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 5,"SILK" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 6,"GREEN_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 7,"CLEAR_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 8,"CRYSTAL_GLASS" ) ); - mOtherMatsWeaponsArmor.insert ( std::make_pair ( 9,"YARN" ) ); -} - -bool StockpileSerializer::armor_mat_is_allowed ( const MaterialInfo &mi ) -{ - return mi.isValid() && mi.material && mi.material->flags.is_set ( material_flags::IS_METAL ); -} - -void StockpileSerializer::write_armor() -{ - StockpileSettings::ArmorSet * armor = mBuffer.mutable_armor(); - - armor->set_unusable ( mPile->settings.armor.unusable ); - armor->set_usable ( mPile->settings.armor.usable ); - - // armor type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_body ( token ); - }, mPile->settings.armor.body, - std::vector ( world->raws.itemdefs.armor.begin(),world->raws.itemdefs.armor.end() ), - item_type::ARMOR ); - - // helm type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_head ( token ); - }, mPile->settings.armor.head, - std::vector ( world->raws.itemdefs.helms.begin(),world->raws.itemdefs.helms.end() ), - item_type::HELM ); - - // shoes type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_feet ( token ); - }, mPile->settings.armor.feet, - std::vector ( world->raws.itemdefs.shoes.begin(),world->raws.itemdefs.shoes.end() ), - item_type::SHOES ); - - // gloves type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_hands ( token ); - }, mPile->settings.armor.hands, - std::vector ( world->raws.itemdefs.gloves.begin(),world->raws.itemdefs.gloves.end() ), - item_type::GLOVES ); - - // pant type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_legs ( token ); - }, mPile->settings.armor.legs, - std::vector ( world->raws.itemdefs.pants.begin(),world->raws.itemdefs.pants.end() ), - item_type::PANTS ); - - // shield type - serialize_list_itemdef ( [=] ( const std::string &token ) - { - armor->add_shield ( token ); - }, mPile->settings.armor.shield, - std::vector ( world->raws.itemdefs.shields.begin(),world->raws.itemdefs.shields.end() ), - item_type::SHIELD ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); - serialize_list_material ( mat_filter, [=] ( const std::string &token ) - { - armor->add_mats ( token ); - }, mPile->settings.armor.mats ); - - // other mats - serialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const std::string &token ) - { - armor->add_other_mats ( token ); - }, mPile->settings.armor.other_mats ); - - // quality core - serialize_list_quality ( [=] ( const std::string &token ) - { - armor->add_quality_core ( token ); - }, mPile->settings.armor.quality_core ); - - // quality total - serialize_list_quality ( [=] ( const std::string &token ) - { - armor->add_quality_total ( token ); - }, mPile->settings.armor.quality_total ); -} - -void StockpileSerializer::read_armor() -{ - if ( mBuffer.has_armor() ) - { - mPile->settings.flags.bits.armor = 1; - const StockpileSettings::ArmorSet armor = mBuffer.armor(); - debug() << "armor: " <settings.armor.unusable = unusable; - mPile->settings.armor.usable = usable; - - // body type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.body ( idx ); - }, armor.body_size(), &mPile->settings.armor.body, item_type::ARMOR ); - - // head type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.head ( idx ); - }, armor.head_size(), &mPile->settings.armor.head, item_type::HELM ); - - // feet type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.feet ( idx ); - }, armor.feet_size(), &mPile->settings.armor.feet, item_type::SHOES ); - - // hands type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.hands ( idx ); - }, armor.hands_size(), &mPile->settings.armor.hands, item_type::GLOVES ); - - // legs type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.legs ( idx ); - }, armor.legs_size(), &mPile->settings.armor.legs, item_type::PANTS ); - - // shield type - unserialize_list_itemdef ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.shield ( idx ); - }, armor.shield_size(), &mPile->settings.armor.shield, item_type::SHIELD ); - - // materials - FuncMaterialAllowed mat_filter = std::bind ( &StockpileSerializer::armor_mat_is_allowed, this, _1 ); - unserialize_list_material ( mat_filter, [=] ( const size_t & idx ) -> const std::string& - { - return armor.mats ( idx ); - }, armor.mats_size(), &mPile->settings.armor.mats ); - - // other mats - unserialize_list_other_mats ( mOtherMatsWeaponsArmor, [=] ( const size_t & idx ) -> const std::string& - { - return armor.other_mats ( idx ); - }, armor.other_mats_size(), &mPile->settings.armor.other_mats ); - - // core quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.quality_core ( idx ); - }, armor.quality_core_size(), mPile->settings.armor.quality_core ); - // total quality - unserialize_list_quality ( [=] ( const size_t & idx ) -> const std::string& - { - return armor.quality_total ( idx ); - }, armor.quality_total_size(), mPile->settings.armor.quality_total ); - } - else - { - mPile->settings.flags.bits.armor = 0; - mPile->settings.armor.body.clear(); - mPile->settings.armor.head.clear(); - mPile->settings.armor.feet.clear(); - mPile->settings.armor.hands.clear(); - mPile->settings.armor.legs.clear(); - mPile->settings.armor.shield.clear(); - mPile->settings.armor.other_mats.clear(); - mPile->settings.armor.mats.clear(); - quality_clear ( mPile->settings.armor.quality_core ); - quality_clear ( mPile->settings.armor.quality_total ); - } + }); } diff --git a/plugins/stockpiles/StockpileSerializer.h b/plugins/stockpiles/StockpileSerializer.h index ff0a38241..f0e1a62e2 100644 --- a/plugins/stockpiles/StockpileSerializer.h +++ b/plugins/stockpiles/StockpileSerializer.h @@ -1,349 +1,141 @@ #pragma once -// stockpiles plugin -#include "proto/stockpiles.pb.h" - -// dfhack #include "modules/Materials.h" -#include "modules/Items.h" -// df -#include "df/world.h" -#include "df/world_data.h" +#include "df/itemdef.h" #include "df/organic_mat_category.h" -#include "df/furniture_type.h" -#include "df/item_quality.h" -#include "df/item_type.h" -// stl +#include "proto/stockpiles.pb.h" + #include -#include -#include -#include -namespace df { -struct building_stockpilest; +namespace df +{ + struct building_stockpilest; } - -/** - * Null buffer that acts like /dev/null for when debug is disabled - */ -class NullBuffer : public std::streambuf -{ -public: - int overflow ( int c ); +enum IncludedElements { + INCLUDED_ELEMENTS_NONE = 0x00, + INCLUDED_ELEMENTS_CONTAINERS = 0x01, + INCLUDED_ELEMENTS_GENERAL = 0x02, + INCLUDED_ELEMENTS_CATEGORIES = 0x04, + INCLUDED_ELEMENTS_TYPES = 0x08, }; -class NullStream : public std::ostream -{ -public: - NullStream(); -private: - NullBuffer m_sb; +enum DeserializeMode { + DESERIALIZE_MODE_SET = 0, + DESERIALIZE_MODE_ENABLE = 1, + DESERIALIZE_MODE_DISABLE = 2, }; +// read the token from the serialized list during import +typedef std::function FuncReadImport; +// add the token to the serialized list during export +typedef std::function FuncWriteExport; +// are item's of item_type allowed? +typedef std::function FuncItemAllowed; +// is this material allowed? +typedef std::function FuncMaterialAllowed; + +// convenient struct for parsing food stockpile items +struct food_pair { + const char * name; + // exporting + FuncWriteExport set_value; + std::vector* stockpile_values; + // importing + FuncReadImport get_value; + size_t serialized_count; + bool valid; + + food_pair(const char * n, FuncWriteExport s, std::vector* sp_v, FuncReadImport g, size_t count) + : name(n), set_value(s), stockpile_values(sp_v), get_value(g), serialized_count(count), + valid(true) { } + food_pair(): valid(false) { } +}; /** * Class for serializing the stockpile_settings structure into a Google protobuf */ -class StockpileSerializer -{ +class StockpileSerializer { public: - /** - * @param out for debugging - * @param stockpile stockpile to read or write settings to - */ - - StockpileSerializer ( df::building_stockpilest * stockpile ); - - ~StockpileSerializer(); - - void enable_debug ( std::ostream &out ); - - /** - * Since we depend on protobuf-lite, not the full lib, we copy this function from - * protobuf message.cc - */ - bool serialize_to_ostream(std::ostream* output); - - /** - * Will serialize stockpile settings to a file (overwrites existing files) - * @return success/failure - */ - bool serialize_to_file ( const std::string & file ); - - /** - * Again, copied from message.cc - */ - bool parse_from_istream(std::istream* input); - - - /** - * Read stockpile settings from file - */ - bool unserialize_from_file ( const std::string & file ); + /** + * @param stockpile stockpile to read or write settings to + */ + StockpileSerializer(df::building_stockpilest* stockpile); + + ~StockpileSerializer(); + + /** + * Since we depend on protobuf-lite, not the full lib, we copy this function from + * protobuf message.cc + */ + bool serialize_to_ostream(std::ostream* output, uint32_t includedElements); + + /** + * Will serialize stockpile settings to a file (overwrites existing files) + * @return success/failure + */ + bool serialize_to_file(const std::string& file, uint32_t includedElements); + + /** + * Again, copied from message.cc + */ + bool parse_from_istream(std::istream* input, DeserializeMode mode, const std::vector& filters); + + /** + * Read stockpile settings from file + */ + bool unserialize_from_file(const std::string& file, DeserializeMode mode, const std::vector& filters); private: - - bool mDebug; - std::ostream * mOut; - NullStream mNull; - df::building_stockpilest * mPile; - dfstockpiles::StockpileSettings mBuffer; - std::map mOtherMatsFurniture; - std::map mOtherMatsFinishedGoods; - std::map mOtherMatsBars; - std::map mOtherMatsBlocks; - std::map mOtherMatsWeaponsArmor; - - - std::ostream & debug(); - - /** - read memory structures and serialize to protobuf - */ - void write(); - - // parse serialized data into ui indices - void read (); - - /** - * Find an enum's value based off the string label. - * @param traits the enum's trait struct - * @param token the string value in key_table - * @return the enum's value, -1 if not found - */ - template - static typename df::enum_traits::base_type linear_index ( std::ostream & out, df::enum_traits traits, const std::string &token ) - { - auto j = traits.first_item_value; - auto limit = traits.last_item_value; - // sometimes enums start at -1, which is bad news for array indexing - if ( j < 0 ) - { - j += abs ( traits.first_item_value ); - limit += abs ( traits.first_item_value ); - } - for ( ; j <= limit; ++j ) - { - // out << " linear_index("<< token <<") = table["< FuncReadImport; - // add the token to the serialized list during export - typedef std::function FuncWriteExport; - // are item's of item_type allowed? - typedef std::function FuncItemAllowed; - // is this material allowed? - typedef std::function FuncMaterialAllowed; - - // convenient struct for parsing food stockpile items - struct food_pair - { - // exporting - FuncWriteExport set_value; - std::vector * stockpile_values; - // importing - FuncReadImport get_value; - size_t serialized_count; - bool valid; - - food_pair ( FuncWriteExport s, std::vector* sp_v, FuncReadImport g, size_t count ) - : set_value ( s ) - , stockpile_values ( sp_v ) - , get_value ( g ) - , serialized_count ( count ) - , valid ( true ) - {} - food_pair(): valid( false ) {} - }; - - /** - * There are many repeated (un)serialization cases throughout the stockpile_settings structure, - * so the most common cases have been generalized into generic functions using lambdas. - * - * The basic process to serialize a stockpile_settings structure is: - * 1. loop through the list - * 2. for every element that is TRUE: - * 3. map the specific stockpile_settings index into a general material, creature, etc index - * 4. verify that type is allowed in the list (e.g., no stone in gems stockpiles) - * 5. add it to the protobuf using FuncWriteExport - * - * The unserialization process is the same in reverse. - */ - void serialize_list_organic_mat ( FuncWriteExport add_value, const std::vector * list, df::enums::organic_mat_category::organic_mat_category cat ); - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_organic_mat ( FuncReadImport get_value, size_t list_size, std::vector *pile_list, df::enums::organic_mat_category::organic_mat_category cat ); - - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_item_type ( FuncItemAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ); - - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_item_type ( FuncItemAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); - - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_material ( FuncMaterialAllowed is_allowed, FuncWriteExport add_value, const std::vector &list ); - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_material ( FuncMaterialAllowed is_allowed, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_quality ( FuncWriteExport add_value, const bool ( &quality_list ) [7] ); - - /** - * Set all values in a bool[7] to false - */ - void quality_clear ( bool ( &pile_list ) [7] ); - - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_quality ( FuncReadImport read_value, int32_t list_size, bool ( &pile_list ) [7] ); - - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_other_mats ( const std::map other_mats, FuncWriteExport add_value, std::vector list ); - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_other_mats ( const std::map other_mats, FuncReadImport read_value, int32_t list_size, std::vector *pile_list ); - - - /** - * @see serialize_list_organic_mat - */ - void serialize_list_itemdef ( FuncWriteExport add_value, std::vector list, std::vector items, df::enums::item_type::item_type type ); - - - /** - * @see serialize_list_organic_mat - */ - void unserialize_list_itemdef ( FuncReadImport read_value, int32_t list_size, std::vector *pile_list, df::enums::item_type::item_type type ); - - - /** - * Given a list of other_materials and an index, return its corresponding token - * @return empty string if not found - * @see other_mats_token - */ - std::string other_mats_index ( const std::map other_mats, int idx ); - - /** - * Given a list of other_materials and a token, return its corresponding index - * @return -1 if not found - * @see other_mats_index - */ - int other_mats_token ( const std::map other_mats, const std::string & token ); - - void write_general(); - void read_general(); - - - void write_animals(); - void read_animals(); - - - food_pair food_map ( df::enums::organic_mat_category::organic_mat_category cat ); - - - void write_food(); - void read_food(); - - void furniture_setup_other_mats(); - void write_furniture(); - bool furniture_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - void read_furniture(); - - bool refuse_creature_is_allowed ( const df::creature_raw *raw ); - - - void refuse_write_helper ( std::function add_value, const std::vector & list ); - - - bool refuse_type_is_allowed ( df::enums::item_type::item_type type ); - - - void write_refuse(); - void refuse_read_helper ( std::function get_value, size_t list_size, std::vector* pile_list ); - - void read_refuse(); - - bool stone_is_allowed ( const DFHack::MaterialInfo &mi ); - - void write_stone(); - - void read_stone(); - - bool ammo_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - - void write_ammo(); - void read_ammo(); - bool coins_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - - void write_coins(); - - void read_coins(); - - void bars_blocks_setup_other_mats(); - - bool bars_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - - bool blocks_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - - void write_bars_blocks(); - void read_bars_blocks(); - bool gem_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - bool gem_cut_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - bool gem_other_mat_is_allowed(DFHack::MaterialInfo &mi ); - - void write_gems(); - - void read_gems(); - - bool finished_goods_type_is_allowed ( df::enums::item_type::item_type type ); - void finished_goods_setup_other_mats(); - bool finished_goods_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - void write_finished_goods(); - void read_finished_goods(); - void write_leather(); - void read_leather(); - void write_cloth(); - void read_cloth(); - bool wood_mat_is_allowed ( const df::plant_raw * plant ); - void write_wood(); - void read_wood(); - bool weapons_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - void write_weapons(); - void read_weapons(); - void weapons_armor_setup_other_mats(); - bool armor_mat_is_allowed ( const DFHack::MaterialInfo &mi ); - void write_armor(); - void read_armor(); - + df::building_stockpilest* mPile; + dfstockpiles::StockpileSettings mBuffer; + + // read memory structures and serialize to protobuf + void write(uint32_t includedElements); + + // parse serialized data into ui indices + void read(DeserializeMode mode, const std::vector& filters); + + void write_containers(); + void read_containers(DeserializeMode mode); + void write_general(); + void read_general(DeserializeMode mode); + + bool write_ammo(dfstockpiles::StockpileSettings::AmmoSet* ammo); + void read_ammo(DeserializeMode mode, const std::vector& filters); + bool write_animals(dfstockpiles::StockpileSettings::AnimalsSet* animals); + void read_animals(DeserializeMode mode, const std::vector& filters); + bool write_armor(dfstockpiles::StockpileSettings::ArmorSet* armor); + void read_armor(DeserializeMode mode, const std::vector& filters); + bool write_bars_blocks(dfstockpiles::StockpileSettings::BarsBlocksSet* bars_blocks); + void read_bars_blocks(DeserializeMode mode, const std::vector& filters); + bool write_cloth(dfstockpiles::StockpileSettings::ClothSet* cloth); + void read_cloth(DeserializeMode mode, const std::vector& filters); + bool write_coins(dfstockpiles::StockpileSettings::CoinSet* coins); + void read_coins(DeserializeMode mode, const std::vector& filters); + bool write_finished_goods(dfstockpiles::StockpileSettings::FinishedGoodsSet* finished_goods); + void read_finished_goods(DeserializeMode mode, const std::vector& filters); + food_pair food_map(df::enums::organic_mat_category::organic_mat_category cat); + bool write_food(dfstockpiles::StockpileSettings::FoodSet* food); + void read_food(DeserializeMode mode, const std::vector& filters); + bool write_furniture(dfstockpiles::StockpileSettings::FurnitureSet* furniture); + void read_furniture(DeserializeMode mode, const std::vector& filters); + bool write_gems(dfstockpiles::StockpileSettings::GemsSet* gems); + void read_gems(DeserializeMode mode, const std::vector& filters); + bool write_leather(dfstockpiles::StockpileSettings::LeatherSet* leather); + void read_leather(DeserializeMode mode, const std::vector& filters); + bool write_corpses(dfstockpiles::StockpileSettings::CorpsesSet* corpses); + void read_corpses(DeserializeMode mode, const std::vector& filters); + bool write_refuse(dfstockpiles::StockpileSettings::RefuseSet* refuse); + void read_refuse(DeserializeMode mode, const std::vector& filters); + bool write_sheet(dfstockpiles::StockpileSettings::SheetSet* sheet); + void read_sheet(DeserializeMode mode, const std::vector& filters); + bool write_stone(dfstockpiles::StockpileSettings::StoneSet* stone); + void read_stone(DeserializeMode mode, const std::vector& filters); + bool write_weapons(dfstockpiles::StockpileSettings::WeaponsSet* weapons); + void read_weapons(DeserializeMode mode, const std::vector& filters); + bool write_wood(dfstockpiles::StockpileSettings::WoodSet* wood); + void read_wood(DeserializeMode mode, const std::vector& filters); }; diff --git a/plugins/stockpiles/StockpileUtils.h b/plugins/stockpiles/StockpileUtils.h index f5570be92..7dcd8c3a2 100644 --- a/plugins/stockpiles/StockpileUtils.h +++ b/plugins/stockpiles/StockpileUtils.h @@ -3,26 +3,16 @@ #include "MiscUtils.h" #include "df/world.h" -#include "df/world_data.h" #include "df/creature_raw.h" #include "df/plant_raw.h" -#include -#include -#include - -// os -#include - // Utility Functions {{{ // A set of convenience functions for doing common lookups - /** * Retrieve creature raw from index */ -static inline df::creature_raw* find_creature ( int32_t idx ) -{ +static inline df::creature_raw* find_creature(int32_t idx) { return df::global::world->raws.creatures.all[idx]; } @@ -30,16 +20,14 @@ static inline df::creature_raw* find_creature ( int32_t idx ) * Retrieve creature index from id string * @return -1 if not found */ -static inline int16_t find_creature ( const std::string &creature_id ) -{ - return linear_index ( df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id ); +static inline int16_t find_creature(const std::string& creature_id) { + return linear_index(df::global::world->raws.creatures.all, &df::creature_raw::creature_id, creature_id); } /** * Retrieve plant raw from index */ -static inline df::plant_raw* find_plant ( size_t idx ) -{ +static inline df::plant_raw* find_plant(size_t idx) { return df::global::world->raws.plants.all[idx]; } @@ -47,32 +35,26 @@ static inline df::plant_raw* find_plant ( size_t idx ) * Retrieve plant index from id string * @return -1 if not found */ -static inline size_t find_plant ( const std::string &plant_id ) -{ - return linear_index ( df::global::world->raws.plants.all, &df::plant_raw::id, plant_id ); +static inline size_t find_plant(const std::string& plant_id) { + return linear_index(df::global::world->raws.plants.all, &df::plant_raw::id, plant_id); } -struct less_than_no_case -{ - bool operator () (char x, char y) const - { - return toupper( static_cast< unsigned char >(x)) < toupper( static_cast< unsigned char >(y)); - } +struct less_than_no_case { + bool operator () (char x, char y) const { + return toupper(static_cast(x)) < toupper(static_cast(y)); + } }; -static inline bool CompareNoCase(const std::string &a, const std::string &b) -{ - return std::lexicographical_compare( a.begin(),a.end(), b.begin(),b.end(), less_than_no_case() ); +static inline bool CompareNoCase(const std::string& a, const std::string& b) { + return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end(), less_than_no_case()); } - /** * Checks if the parameter has the dfstock extension. * Doesn't check if the file exists or not. */ -static inline bool is_dfstockfile ( const std::string& filename ) -{ - return filename.rfind ( ".dfstock" ) != std::string::npos; +static inline bool is_dfstockfile(const std::string& filename) { + return filename.rfind(".dfstock") != std::string::npos; } // }}} utility Functions diff --git a/plugins/stockpiles/proto/stockpiles.proto b/plugins/stockpiles/proto/stockpiles.proto index 90c95b93f..681e7d927 100644 --- a/plugins/stockpiles/proto/stockpiles.proto +++ b/plugins/stockpiles/proto/stockpiles.proto @@ -7,12 +7,14 @@ option optimize_for = LITE_RUNTIME; message StockpileSettings { message AnimalsSet { + optional bool all = 4; optional bool empty_cages = 1; optional bool empty_traps = 2; repeated string enabled = 3; } message FoodSet { + optional bool all = 21; repeated string meat = 1; repeated string fish = 2; repeated string unprepared_fish = 20; @@ -36,6 +38,7 @@ message StockpileSettings { } message FurnitureSet { + optional bool all = 7; repeated string type = 1; repeated string other_mats = 2; repeated string mats = 3; @@ -44,6 +47,7 @@ message StockpileSettings { // UNUSED: optional bool sand_bags = 6; } message RefuseSet { + optional bool all = 12; repeated string type = 1; repeated string corpses = 2; repeated string body_parts = 3; @@ -57,12 +61,14 @@ message StockpileSettings { optional bool rotten_raw_hide = 11; } message StoneSet { - repeated string mats = 1; + optional bool all = 2; + repeated string mats = 1; } message OreSet { - repeated string mats = 1; + repeated string mats = 1; } message AmmoSet { + optional bool all = 6; repeated string type = 1; repeated string other_mats = 2; repeated string mats = 3; @@ -70,21 +76,25 @@ message StockpileSettings { repeated string quality_total = 5; } message CoinSet { + optional bool all = 2; repeated string mats = 1; } message BarsBlocksSet { + optional bool all = 5; repeated string bars_other_mats = 1; repeated string blocks_other_mats = 2; repeated string bars_mats = 3; repeated string blocks_mats = 4; } message GemsSet { + optional bool all = 5; repeated string rough_other_mats = 1; repeated string cut_other_mats = 2; repeated string rough_mats = 3; repeated string cut_mats = 4; } message FinishedGoodsSet { + optional bool all = 6; repeated string type = 1; repeated string other_mats = 2; repeated string mats = 3; @@ -92,9 +102,11 @@ message StockpileSettings { repeated string quality_total = 5; } message LeatherSet { + optional bool all = 2; repeated string mats = 1; } message ClothSet { + optional bool all = 9; repeated string thread_silk = 1; repeated string thread_plant = 2; repeated string thread_yarn = 3; @@ -105,9 +117,11 @@ message StockpileSettings { repeated string cloth_metal = 8; } message WoodSet { + optional bool all = 2; repeated string mats = 1; } message WeaponsSet { + optional bool all = 9; repeated string weapon_type = 1; repeated string trapcomp_type = 2; repeated string other_mats = 3; @@ -117,8 +131,8 @@ message StockpileSettings { optional bool usable = 7; optional bool unusable = 8; } - message ArmorSet { + optional bool all = 13; repeated string body = 1; repeated string head = 2; repeated string feet = 3; @@ -129,33 +143,48 @@ message StockpileSettings { repeated string mats = 8; repeated string quality_core = 9; repeated string quality_total = 10; - optional bool usable =11; + optional bool usable = 11; optional bool unusable = 12; } + message CorpsesSet { + optional bool all = 1; + repeated string corpses = 2; + } + message SheetSet { + optional bool all = 1; + repeated string paper = 2; + repeated string parchment = 3; + } + + // general settings + optional int32 max_barrels = 20; + optional int32 max_bins = 21; + optional int32 max_wheelbarrows = 22; + optional bool use_links_only = 23; + optional bool allow_organic = 18; + optional bool allow_inorganic = 19; + // categories + optional AmmoSet ammo = 8; optional AnimalsSet animals = 1; + optional ArmorSet armor = 17; + optional BarsBlocksSet barsblocks = 10; + optional ClothSet cloth = 14; + optional CoinSet coin = 9; + optional FinishedGoodsSet finished_goods = 12; optional FoodSet food = 2; optional FurnitureSet furniture = 3; - optional int32 unknown1 = 4 [deprecated=true]; - optional RefuseSet refuse = 5; - optional StoneSet stone = 6; - optional OreSet ore = 7; - optional AmmoSet ammo = 8; - optional CoinSet coin = 9; - optional BarsBlocksSet barsblocks = 10; optional GemsSet gems = 11; - optional FinishedGoodsSet finished_goods = 12; optional LeatherSet leather = 13; - optional ClothSet cloth = 14; - optional WoodSet wood = 15; + optional CorpsesSet corpses_v50 = 25; + optional RefuseSet refuse = 5; + optional SheetSet sheet = 26; + optional StoneSet stone = 6; optional WeaponsSet weapons = 16; - optional ArmorSet armor = 17; - optional bool allow_organic = 18; - optional bool allow_inorganic = 19; - optional bool corpses = 24; - // extras - optional int32 max_barrels = 20; - optional int32 max_bins = 21; - optional int32 max_wheelbarrows = 22; - optional bool use_links_only = 23; + optional WoodSet wood = 15; + + // deprecated + optional bool corpses = 24; // not marked as deprecated since we still read it + optional OreSet ore = 7 [deprecated=true]; + optional int32 unknown1 = 4 [deprecated=true]; } diff --git a/plugins/stockpiles/stockpiles.cpp b/plugins/stockpiles/stockpiles.cpp index 4a2bdc33c..d1ce46e9c 100644 --- a/plugins/stockpiles/stockpiles.cpp +++ b/plugins/stockpiles/stockpiles.cpp @@ -1,153 +1,154 @@ +#include "Debug.h" +#include "LuaTools.h" #include "PluginManager.h" #include "StockpileUtils.h" #include "StockpileSerializer.h" #include "modules/Filesystem.h" -#include "modules/Gui.h" -using std::vector; +#include "df/building.h" +#include "df/building_stockpilest.h" + +#include +#include + using std::string; +using std::vector; using namespace DFHack; DFHACK_PLUGIN("stockpiles"); -static command_result savestock ( color_ostream &out, vector & parameters ); -static command_result loadstock ( color_ostream &out, vector & parameters ); +REQUIRE_GLOBAL(world); + +namespace DFHack { + DBG_DECLARE(stockpiles, log, DebugCategory::LINFO); +} + +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); -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands ) -{ - commands.push_back(PluginCommand( - "savestock", - "Save the active stockpile's settings to a file.", - savestock, - Gui::any_stockpile_hotkey)); commands.push_back(PluginCommand( - "loadstock", - "Load and apply stockpile settings from a file.", - loadstock, - Gui::any_stockpile_hotkey)); + plugin_name, + "Import, export, or modify stockpile settings and features.", + do_command)); return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} +static bool call_stockpiles_lua(color_ostream *out, 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(log).print("calling stockpiles lua function: '%s'\n", fn_name); -// exporting -static command_result savestock ( color_ostream &out, vector & parameters ) -{ - df::building_stockpilest *sp = Gui::getSelectedStockpile(out, true); - if ( !sp ) - { - out.printerr ( "Selected building isn't a stockpile.\n" ); - return CR_WRONG_USAGE; - } + CoreSuspender guard; - if ( parameters.size() > 2 ) - { - out.printerr ( "Invalid parameters\n" ); - return CR_WRONG_USAGE; - } + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); - bool debug = false; - std::string file; - for ( size_t i = 0; i < parameters.size(); ++i ) - { - const std::string o = parameters.at ( i ); - if ( o == "--debug" || o == "-d" ) - debug = true; - else if ( !o.empty() && o[0] != '-' ) - { - file = o; - } - } - if ( file.empty() ) - { - out.printerr ( "You must supply a valid filename.\n" ); - return CR_WRONG_USAGE; - } + if (!out) + out = &Core::getInstance().getConsole(); - StockpileSerializer cereal ( sp ); - if ( debug ) - cereal.enable_debug ( out ); - - if ( !is_dfstockfile ( file ) ) file += ".dfstock"; - try - { - if ( !cereal.serialize_to_file ( file ) ) - { - out.printerr ( "could not save to %s\n", file.c_str() ); - return CR_FAILURE; - } - } - catch ( std::exception &e ) - { - out.printerr ( "serialization failed: protobuf exception: %s\n", e.what() ); + return Lua::CallLuaModuleFunction(*out, L, "plugins.stockpiles", fn_name, + nargs, nres, + std::forward(args_lambda), + std::forward(res_lambda)); +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + bool show_help = false; + if (!call_stockpiles_lua(&out, "parse_commandline", 1, 1, + [&](lua_State *L) { + Lua::PushVector(L, parameters); + }, + [&](lua_State *L) { + show_help = !lua_toboolean(L, -1); + })) { return CR_FAILURE; } - return CR_OK; + return show_help ? CR_WRONG_USAGE : CR_OK; } +///////////////////////////////////////////////////// +// Lua API +// -// importing -static command_result loadstock ( color_ostream &out, vector & parameters ) -{ - df::building_stockpilest *sp = Gui::getSelectedStockpile(out, true); - if ( !sp ) - { - out.printerr ( "Selected building isn't a stockpile.\n" ); - return CR_WRONG_USAGE; - } +static df::building_stockpilest* get_stockpile(int id) { + return virtual_cast(df::building::find(id)); +} - if ( parameters.size() < 1 || parameters.size() > 2 ) - { - out.printerr ( "Invalid parameters\n" ); - return CR_WRONG_USAGE; +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); + return false; } - bool debug = false; - std::string file; - for ( size_t i = 0; i < parameters.size(); ++i ) - { - const std::string o = parameters.at ( i ); - if ( o == "--debug" || o == "-d" ) - debug = true; - else if ( !o.empty() && o[0] != '-' ) - { - file = o; + if (!is_dfstockfile(fname)) + fname += ".dfstock"; + + try { + StockpileSerializer cereal(sp); + if (!cereal.serialize_to_file(fname, includedElements)) { + out.printerr("could not save to '%s'\n", fname.c_str()); + return false; } } - if ( file.empty() ) { - out.printerr ( "ERROR: missing .dfstock file parameter\n"); - return DFHack::CR_WRONG_USAGE; + catch (std::exception& e) { + out.printerr("serialization failed: protobuf exception: %s\n", e.what()); + return false; + } + + return true; +} + +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); + return false; } - if ( !is_dfstockfile ( file ) ) - file += ".dfstock"; - if ( !Filesystem::exists ( file ) ) - { - out.printerr ( "ERROR: the .dfstock file doesn't exist: %s\n", file.c_str()); - return CR_WRONG_USAGE; + + if (!is_dfstockfile(fname)) + fname += ".dfstock"; + + if (!Filesystem::exists(fname)) { + out.printerr("ERROR: file doesn't exist: '%s'\n", fname.c_str()); + return false; } - StockpileSerializer cereal ( sp ); - if ( debug ) - cereal.enable_debug ( out ); - try - { - if ( !cereal.unserialize_from_file ( file ) ) - { - out.printerr ( "unserialization failed: %s\n", file.c_str() ); - return CR_FAILURE; + DeserializeMode mode = DESERIALIZE_MODE_SET; + if (mode_str == "enable") + mode = DESERIALIZE_MODE_ENABLE; + else if (mode_str == "disable") + mode = DESERIALIZE_MODE_DISABLE; + + vector filters; + split_string(&filters, filter, ",", true); + + try { + StockpileSerializer cereal(sp); + if (!cereal.unserialize_from_file(fname, mode, filters)) { + out.printerr("deserialization failed: '%s'\n", fname.c_str()); + return false; } } - catch ( std::exception &e ) - { - out.printerr ( "unserialization failed: protobuf exception: %s\n", e.what() ); - return CR_FAILURE; + catch (std::exception& e) { + out.printerr("deserialization failed: protobuf exception: %s\n", e.what()); + return false; } - return CR_OK; + + return true; } + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(stockpiles_export), + DFHACK_LUA_FUNCTION(stockpiles_import), + DFHACK_LUA_END +}; diff --git a/plugins/stonesense b/plugins/stonesense index 3e494d9d9..b7073b664 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 3e494d9d968add443ebd63cc167933cc813f0eee +Subproject commit b7073b664310b909989ebe68de36a164e452825a diff --git a/plugins/tailor.cpp b/plugins/tailor.cpp index 19237429e..2b44d11ae 100644 --- a/plugins/tailor.cpp +++ b/plugins/tailor.cpp @@ -124,7 +124,8 @@ static const MatType M_ADAMANTINE = MatType("adamantine", df::job_material_category::mask_strand, df::armor_general_flags::SOFT); static const std::list all_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER, M_ADAMANTINE }; -static std::list material_order = { M_SILK, M_CLOTH, M_YARN, M_LEATHER }; // M_ADAMANTINE is not included by default +static const std::list default_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER }; // adamantine not included by default +static std::list material_order = default_materials; static struct BadFlags { uint32_t whole; @@ -143,36 +144,46 @@ static struct BadFlags { } badFlags; class Tailor { -private: - std::map, int> available; // key is item type & size - std::map, int> needed; // same - std::map, int> queued; // same +private: std::map sizes; // this maps body size to races - std::map, int> orders; // key is item type, item subtype, size + + std::map, int> available; + + std::map, int> needed; + + std::map, int> orders; std::map supply; std::map reserves; int default_reserve = 10; + bool inventory_sanity_checking = false; + public: + void set_debug_flag(bool f) + { + inventory_sanity_checking = f; + } + void reset() { available.clear(); needed.clear(); - queued.clear(); sizes.clear(); - orders.clear(); supply.clear(); + orders.clear(); } void scan_clothing() { - for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "clothing" + for (auto i : world->items.other[df::items_other_id::ANY_GENERIC37]) // GENERIC37 is "nontattered clothing" { if (i->flags.whole & badFlags.whole) + { continue; + } if (i->getWear() >= 1) continue; df::item_type t = i->getType(); @@ -180,6 +191,18 @@ public: available[std::make_pair(t, size)] += 1; } + + if (DBG_NAME(cycle).isEnabled(DebugCategory::LDEBUG)) + { + for (auto& i : available) + { + 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); + } + } } void scan_materials() @@ -191,7 +214,7 @@ public: if (i->flags.whole & badFlags.whole) continue; - if (require_dyed && !i->hasImprovements()) + if (require_dyed && (!i->isDyed())) { // only count dyed std::string d; @@ -239,14 +262,13 @@ public: if (!Units::isOwnCiv(u) || !Units::isOwnGroup(u) || !Units::isActive(u) || - Units::isBaby(u)) - continue; // skip units we don't control + Units::isBaby(u) || + !Units::casteFlagSet(u->race, u->caste, df::enums::caste_raw_flags::EQUIPS)) + continue; // skip units we don't control or that can't wear clothes std::set wearing; - wearing.clear(); - + std::set ordered; std::deque worn; - worn.clear(); for (auto inv : u->inventory) { @@ -261,33 +283,35 @@ public: int usize = world->raws.creatures.all[u->race]->adultsize; sizes[usize] = u->race; - for (auto ty : std::set{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES }) - { - if (wearing.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(), - usize, - Translation::TranslateName(&u->name, false).c_str()); - needed[std::make_pair(ty, usize)] += 1; - } - } - for (auto w : worn) { auto ty = w->getType(); - auto oo = itemTypeMap.find(ty); - if (oo == itemTypeMap.end()) - { - continue; - } - const df::job_type o = oo->second; int isize = world->raws.creatures.all[w->getMakerRace()]->adultsize; std::string description; w->getItemDescription(&description, 0); - if (available[std::make_pair(ty, usize)] > 0) + if (wearing.count(ty) == 0) + { + if (available[std::make_pair(ty, usize)] > 0) + { + available[std::make_pair(ty, usize)] -= 1; + DEBUG(cycle).print("tailor: allocating a %s (size %d) to %s\n", + ENUM_KEY_STR(item_type, ty).c_str(), usize, + Translation::TranslateName(&u->name, false).c_str()); + wearing.insert(ty); + } + else if (ordered.count(ty) == 0) + { + DEBUG(cycle).print ("tailor: %s (size %d) worn by %s (size %d) needs replacement, but none available\n", + description.c_str(), isize, + Translation::TranslateName(&u->name, false).c_str(), usize); + needed[std::make_pair(ty, usize)] += 1; + ordered.insert(ty); + } + } + + if (wearing.count(ty) > 0) { if (w->flags.bits.owned) { @@ -301,23 +325,21 @@ public: ); } - if (wearing.count(ty) == 0) - { - DEBUG(cycle).print("tailor: allocating a %s (size %d) to %s\n", - ENUM_KEY_STR(item_type, ty).c_str(), usize, - Translation::TranslateName(&u->name, false).c_str()); - available[std::make_pair(ty, usize)] -= 1; - } - if (w->getWear() > 1) w->flags.bits.dump = true; } - else + + } + + for (auto ty : std::set{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES }) + { + if (wearing.count(ty) == 0 && ordered.count(ty) == 0) { - DEBUG(cycle).print ("tailor: %s (size %d) worn by %s (size %d) needs replacement, but none available\n", - description.c_str(), isize, - Translation::TranslateName(&u->name, false).c_str(), usize); - orders[std::make_tuple(o, w->getSubtype(), usize)] += 1; + TRACE(cycle).print("tailor: one %s of size %d needed to cover %s\n", + ENUM_KEY_STR(item_type, ty).c_str(), + usize, + Translation::TranslateName(&u->name, false).c_str()); + needed[std::make_pair(ty, usize)] += 1; } } } @@ -333,6 +355,9 @@ public: int size = a.first.second; int count = a.second; + if (count <= 0) + continue; + int sub = 0; std::vector v; @@ -380,14 +405,31 @@ public: if (f == jobTypeMap.end()) continue; - auto sub = o->item_subtype; int race = o->hist_figure_id; + + for (auto& m : all_materials) + { + 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", + m.name.c_str(), o->amount_left); + } + } + if (race == -1) continue; // -1 means that the race of the worker will determine the size made; we must ignore these jobs int size = world->raws.creatures.all[race]->adultsize; - orders[std::make_tuple(o->job_type, sub, size)] -= o->amount_left; + + auto tt = jobTypeMap.find(o->job_type); + if (tt == jobTypeMap.end()) + { + continue; + } + + needed[std::make_pair(tt->second, size)] -= o->amount_left; TRACE(cycle).print("tailor: existing order for %d %s of size %d detected\n", o->amount_left, ENUM_KEY_STR(job_type, o->job_type).c_str(), @@ -524,6 +566,18 @@ public: } return ordered; } + + int do_cycle() + { + reset(); + scan_clothing(); + scan_materials(); + scan_replacements(); + scan_existing_orders(); + create_orders(); + return place_orders(); + } + }; static std::unique_ptr tailor_instance; @@ -589,7 +643,7 @@ static void set_material_order() { material_order.push_back(M_ADAMANTINE); } if (!material_order.size()) - std::copy(all_materials.begin(), all_materials.end(), std::back_inserter(material_order)); + std::copy(default_materials.begin(), default_materials.end(), std::back_inserter(material_order)); } DFhackCExport command_result plugin_load_data (color_ostream &out) { @@ -683,13 +737,7 @@ static int do_cycle(color_ostream &out) { DEBUG(cycle,out).print("running %s cycle\n", plugin_name); - tailor_instance->reset(); - tailor_instance->scan_clothing(); - tailor_instance->scan_materials(); - tailor_instance->scan_replacements(); - tailor_instance->create_orders(); - tailor_instance->scan_existing_orders(); - return tailor_instance->place_orders(); + return tailor_instance->do_cycle(); } ///////////////////////////////////////////////////// @@ -730,9 +778,18 @@ static int tailor_getMaterialPreferences(lua_State *L) { return 1; } +static void tailor_setDebugFlag(color_ostream& out, bool enable) +{ + DEBUG(config, out).print("entering tailor_setDebugFlag\n"); + + tailor_instance->set_debug_flag(enable); + +} + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(tailor_doCycle), DFHACK_LUA_FUNCTION(tailor_setMaterialPreferences), + DFHACK_LUA_FUNCTION(tailor_setDebugFlag), DFHACK_LUA_END }; diff --git a/plugins/title-folder.cpp b/plugins/title-folder.cpp index 4f93a6c7f..2659a094f 100644 --- a/plugins/title-folder.cpp +++ b/plugins/title-folder.cpp @@ -20,7 +20,7 @@ static std::string original_title; static DFLibrary *sdl_handle = NULL; static const std::vector sdl_libs { - "SDLreal.dll", + "SDL.dll", "SDL.framework/Versions/A/SDL", "SDL.framework/SDL", "libSDL-1.2.so.0" diff --git a/plugins/title-version.cpp b/plugins/title-version.cpp deleted file mode 100644 index 66bb159ad..000000000 --- a/plugins/title-version.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "modules/Gui.h" -#include "modules/Screen.h" -#include "VTableInterpose.h" -#include "DFHackVersion.h" - -#include "df/graphic.h" -#include "df/viewscreen_optionst.h" -#include "df/viewscreen_titlest.h" -#include "uicommon.h" - -using std::vector; -using std::string; -using namespace DFHack; - -DFHACK_PLUGIN("title-version"); -DFHACK_PLUGIN_IS_ENABLED(is_enabled); -REQUIRE_GLOBAL(gps); - -void draw_version(int start_x, int start_y) { - int x = start_x, - y = start_y; - - OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION); - if (!DFHACK_IS_RELEASE) - { - OutputString(COLOR_WHITE, x, y, " (dev)"); - x = start_x; y++; - OutputString(COLOR_WHITE, x, y, "Git: "); - OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION); - } - if (strlen(DFHACK_BUILD_ID)) - { - x = start_x; y++; - OutputString(COLOR_WHITE, x, y, "Build ID: "); - OutputString(COLOR_WHITE, x, y, DFHACK_BUILD_ID); - } - if (DFHACK_IS_PRERELEASE) - { - x = start_x; y++; - OutputString(COLOR_LIGHTRED, x, y, "Pre-release build"); - } -} - -struct title_version_hook : df::viewscreen_titlest { - typedef df::viewscreen_titlest interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - if (!loading) - draw_version(0, 0); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render); - -struct options_version_hook : df::viewscreen_optionst { - typedef df::viewscreen_optionst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - if (!msg_quit && !in_retire_adv && !msg_peasant && - !in_retire_dwf_abandon_adv && !in_abandon_dwf && !ending_game) - draw_version(2, gps->dimy - 6); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(options_version_hook, render); - -DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) -{ - if (!gps) - return CR_FAILURE; - - if (enable != is_enabled) - { - if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable) || - !INTERPOSE_HOOK(options_version_hook, render).apply(enable)) - return CR_FAILURE; - - is_enabled = enable; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) -{ - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ - INTERPOSE_HOOK(title_version_hook, render).remove(); - INTERPOSE_HOOK(options_version_hook, render).remove(); - return CR_OK; -} diff --git a/plugins/work-now.cpp b/plugins/work-now.cpp new file mode 100644 index 000000000..bce2d9a73 --- /dev/null +++ b/plugins/work-now.cpp @@ -0,0 +1,74 @@ +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/EventManager.h" + +using std::string; +using std::vector; +using namespace DFHack; + +DFHACK_PLUGIN("work-now"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); +REQUIRE_GLOBAL(process_jobs); +REQUIRE_GLOBAL(process_dig); + +namespace DFHack { + DBG_DECLARE(worknow, log, DebugCategory::LINFO); +} + +DFhackCExport command_result work_now(color_ostream& out, vector& parameters); + +static void jobCompletedHandler(color_ostream& out, void* ptr); +EventManager::EventHandler handler(jobCompletedHandler,1); + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { + commands.push_back(PluginCommand( + "work-now", + "Reduce the time that dwarves idle after completing a job.", + work_now)); + + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (enable != is_enabled) + { + if (enable) + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handler, plugin_self); + else + EventManager::unregister(EventManager::EventType::JOB_COMPLETED, handler, plugin_self); + + is_enabled = enable; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + return plugin_enable(out, false); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { + if (e == SC_PAUSED) { + DEBUG(log,out).print("game paused; poking idlers\n"); + *process_jobs = true; + *process_dig = true; + } + + return CR_OK; +} + +DFhackCExport command_result work_now(color_ostream& out, vector& parameters) { + if (parameters.empty() || parameters[0] == "status") { + out.print("work_now is %sactively poking idle dwarves.\n", is_enabled ? "" : "not "); + return CR_OK; + } + + return CR_WRONG_USAGE; +} + +static void jobCompletedHandler(color_ostream& out, void* ptr) { + DEBUG(log,out).print("job completed; poking idlers\n"); + *process_jobs = true; + *process_dig = true; +} diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp deleted file mode 100644 index 2286fee7e..000000000 --- a/plugins/workNow.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "DataDefs.h" - -#include "modules/EventManager.h" -#include "modules/World.h" - -#include "df/global_objects.h" - -#include - -using namespace std; -using namespace DFHack; - -DFHACK_PLUGIN("workNow"); -REQUIRE_GLOBAL(process_jobs); -REQUIRE_GLOBAL(process_dig); - -static int mode = 0; - -DFhackCExport command_result workNow(color_ostream& out, vector& parameters); - -void jobCompletedHandler(color_ostream& out, void* ptr); -EventManager::EventHandler handler(jobCompletedHandler,1); - -DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { - if (!process_jobs || !process_dig) - return CR_FAILURE; - - commands.push_back(PluginCommand( - "workNow", - "Reduce the time that dwarves idle after completing a job.", - workNow)); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - mode = 0; - return CR_OK; -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { - if ( !mode ) - return CR_OK; - if ( e == DFHack::SC_WORLD_UNLOADED ) { - mode = 0; - return CR_OK; - } - if ( e != DFHack::SC_PAUSED ) - return CR_OK; - - *process_jobs = true; - *process_dig = true; - - return CR_OK; -} - -DFhackCExport command_result workNow(color_ostream& out, vector& parameters) { - if ( parameters.size() == 0 ) { - out.print("workNow status = %d\n", mode); - return CR_OK; - } - if ( parameters.size() > 1 ) { - return CR_WRONG_USAGE; - } - int32_t a = atoi(parameters[0].c_str()); - - if (a < 0 || a > 2) - return CR_WRONG_USAGE; - - if ( a == 2 && mode != 2 ) { - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handler, plugin_self); - } else if ( mode == 2 && a != 2 ) { - EventManager::unregister(EventManager::EventType::JOB_COMPLETED, handler, plugin_self); - } - - mode = a; - out.print("workNow status = %d\n", mode); - - return CR_OK; -} - -void jobCompletedHandler(color_ostream& out, void* ptr) { - if ( mode < 2 ) - return; - - *process_jobs = true; - *process_dig = true; -} diff --git a/scripts b/scripts index 288b38c9e..86b083cdf 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 288b38c9e9d8fabf1a0934ffbe23104274c39883 +Subproject commit 86b083cdf5847128e88197834eb953c1716c50a8 diff --git a/travis/authors-rst.py b/travis/authors-rst.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/authors-rst.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/buildmaster-rebuild-pr.py b/travis/buildmaster-rebuild-pr.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/buildmaster-rebuild-pr.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/check-rpc.py b/travis/check-rpc.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/check-rpc.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/download-df.sh b/travis/download-df.sh deleted file mode 100755 index aec2d6d99..000000000 --- a/travis/download-df.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -script_name="$(basename "$0")" -new_script_path="$(dirname "$0")/../ci/${script_name}" - -printf >&2 "\nNote: travis/%s is deprecated. Use ci/%s instead.\n\n" "${script_name}" "${script_name}" - -"${new_script_path}" "$@" -exit $? diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh deleted file mode 100755 index aec2d6d99..000000000 --- a/travis/get-df-version.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/sh - -script_name="$(basename "$0")" -new_script_path="$(dirname "$0")/../ci/${script_name}" - -printf >&2 "\nNote: travis/%s is deprecated. Use ci/%s instead.\n\n" "${script_name}" "${script_name}" - -"${new_script_path}" "$@" -exit $? diff --git a/travis/lint.py b/travis/lint.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/lint.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/run-tests.py b/travis/run-tests.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/run-tests.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/script-docs.py b/travis/script-docs.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/script-docs.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode) diff --git a/travis/script-syntax.py b/travis/script-syntax.py deleted file mode 100755 index cf52385b5..000000000 --- a/travis/script-syntax.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import sys - -script_name = os.path.basename(__file__) -new_script_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'ci', script_name) - -sys.stderr.write('\nNote: travis/{script_name} is deprecated. Use ci/{script_name} instead.\n\n'.format(script_name=script_name)) -sys.stderr.flush() - -p = subprocess.run([sys.executable, new_script_path] + sys.argv[1:]) -sys.exit(p.returncode)