diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4af9f5653..83f3490da 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,8 +17,8 @@ jobs: plugins: - default include: - - os: ubuntu-20.04 - gcc: 11 + - os: ubuntu-22.04 + gcc: 12 plugins: all steps: - name: Set up Python 3 @@ -29,6 +29,7 @@ jobs: run: | sudo apt-get update sudo apt-get install \ + ccache \ libgtk2.0-0 \ libncursesw5 \ libsdl-image1.2-dev \ @@ -55,15 +56,24 @@ jobs: echo "::set-output name=df_version::${DF_VERSION}" echo "DF_VERSION=${DF_VERSION}" >> $GITHUB_ENV echo "DF_FOLDER=${HOME}/DF/${DF_VERSION}/df_linux" >> $GITHUB_ENV + echo "CCACHE_DIR=${HOME}/.ccache" >> $GITHUB_ENV - name: Fetch DF cache uses: actions/cache@v2 with: path: ~/DF - key: ${{ steps.env_setup.outputs.df_version }} + key: dfcache-${{ steps.env_setup.outputs.df_version }}-${{ hashFiles('ci/download-df.sh') }} + - name: Fetch ccache + uses: actions/cache@v2 + with: + path: ~/.ccache + key: ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.ref_name }}-${{ github.sha }} + restore-keys: | + ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }}-${{ github.ref_name }} + ccache-v2-${{ matrix.os }}-gcc-${{ matrix.gcc }} - name: Download DF run: | sh ci/download-df.sh - - name: Build DFHack + - name: Configure DFHack env: CC: gcc-${{ matrix.gcc }} CXX: g++-${{ matrix.gcc }} @@ -76,16 +86,21 @@ jobs: -DBUILD_TESTS:BOOL=ON \ -DBUILD_DEV_PLUGINS:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SIZECHECK:BOOL=${{ matrix.plugins == 'all' }} \ + -DBUILD_SKELETON:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_STONESENSE:BOOL=${{ matrix.plugins == 'all' }} \ -DBUILD_SUPPORTED:BOOL=1 \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache \ -DCMAKE_INSTALL_PREFIX="$DF_FOLDER" + - name: Build DFHack + run: | ninja -C build-ci install + ccache --show-stats - name: Run tests id: run_tests run: | export TERM=dumb status=0 - mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init script -qe -c "python ci/run-tests.py --headless --keep-status \"$DF_FOLDER\"" || status=$((status + 1)) python ci/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" || status=$((status + 2)) mkdir -p artifacts @@ -114,14 +129,14 @@ jobs: python-version: 3 - name: Install dependencies run: | - pip install 'sphinx<4.4.0' + pip install 'sphinx' - name: Clone DFHack uses: actions/checkout@v1 with: submodules: true - name: Build docs run: | - sphinx-build -W --keep-going -j3 . docs/html + sphinx-build -W --keep-going -j auto --color . docs/html - name: Upload docs uses: actions/upload-artifact@v1 with: diff --git a/.gitignore b/.gitignore index 06f2b42e0..9b78fa31f 100644 --- a/.gitignore +++ b/.gitignore @@ -15,9 +15,14 @@ build/VC2010 !build/ # Sphinx generated documentation -docs/_* +docs/changelogs/ docs/html/ docs/pdf/ +docs/pseudoxml/ +docs/tags/ +docs/text/ +docs/tools/ +docs/xml/ # in-place build build/Makefile @@ -74,5 +79,4 @@ tags .idea # external plugins -/plugins/external/ /plugins/CMakeLists.custom.txt diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 47f314e2a..ace64d0d5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,9 +1,10 @@ ci: autofix_prs: false + autoupdate_schedule: monthly repos: # shared across repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.2.0 + rev: v4.3.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -19,11 +20,11 @@ repos: args: ['--fix=lf'] - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.14.3 + rev: 0.18.4 hooks: - id: check-github-workflows - repo: https://github.com/Lucas-C/pre-commit-hooks - rev: v1.1.13 + rev: v1.3.1 hooks: - id: forbid-tabs exclude_types: @@ -32,4 +33,12 @@ repos: exclude_types: - json # specific to dfhack: -exclude: '^(depends/|data/examples/.*\.json$|.*\.diff$)' +- repo: local + hooks: + - id: authors-rst + name: Check Authors.rst + language: python + entry: python3 ci/authors-rst.py + files: docs/Authors\.rst + pass_filenames: false +exclude: '^(depends/|data/.*\.json$|.*\.diff$)' diff --git a/.readthedocs.requirements.txt b/.readthedocs.requirements.txt new file mode 100644 index 000000000..1f028de41 --- /dev/null +++ b/.readthedocs.requirements.txt @@ -0,0 +1 @@ +sphinx==4.4.0 diff --git a/.readthedocs.yml b/.readthedocs.yml new file mode 100644 index 000000000..e66d3da1b --- /dev/null +++ b/.readthedocs.yml @@ -0,0 +1,22 @@ +# .readthedocs.yaml +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-20.04 + tools: + python: "3" + +submodules: + include: all + +sphinx: + configuration: conf.py + +formats: all + +python: + install: + - requirements: .readthedocs.requirements.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 48784ecb1..6808c3785 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ # main project file. use it from a build sub-folder, see COMPILE for details ## some generic CMake magic -cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) +cmake_minimum_required(VERSION 3.6 FATAL_ERROR) cmake_policy(SET CMP0048 NEW) project(dfhack) @@ -192,7 +192,7 @@ endif() # set up versioning. set(DF_VERSION "0.47.05") -set(DFHACK_RELEASE "r4") +set(DFHACK_RELEASE "r7") set(DFHACK_PRERELEASE FALSE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") @@ -206,11 +206,9 @@ set(DFHACK_BUILD_ID "" CACHE STRING "Build ID (should be specified on command li if(UNIX) # put the lib into DF/hack set(DFHACK_LIBRARY_DESTINATION hack) - set(DFHACK_EGGY_DESTINATION libs) else() # windows is crap, therefore we can't do nice things with it. leave the libs on a nasty pile... set(DFHACK_LIBRARY_DESTINATION .) - set(DFHACK_EGGY_DESTINATION .) endif() # external tools will be installed here: @@ -444,43 +442,67 @@ endif() add_subdirectory(data) add_subdirectory(scripts) -find_package(Sphinx QUIET) if(BUILD_DOCS) + find_package(Python3) + find_package(Sphinx) + if(NOT SPHINX_FOUND) message(SEND_ERROR "Sphinx not found but BUILD_DOCS enabled") endif() - file(GLOB SPHINX_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/docs/*.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/guides/*.rst" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/changelog.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/gen_changelog.py" + file(GLOB SPHINX_GLOB_DEPS + LIST_DIRECTORIES false "${CMAKE_CURRENT_SOURCE_DIR}/docs/images/*.png" "${CMAKE_CURRENT_SOURCE_DIR}/docs/styles/*" - "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/about.txt" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*/about.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/data/init/*init" + ) + file(GLOB_RECURSE SPHINX_GLOB_RECURSE_DEPS + "${CMAKE_CURRENT_SOURCE_DIR}/*.rst" + "${CMAKE_CURRENT_SOURCE_DIR}/changelog.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/*py" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/changelogs" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/html" ) - file(GLOB_RECURSE SPHINX_SCRIPT_DEPS - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.lua" - "${CMAKE_CURRENT_SOURCE_DIR}/scripts/*.rb" + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/tags" ) - set(SPHINX_DEPS ${SPHINX_DEPS} ${SPHINX_SCRIPT_DEPS} - "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.rst" + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/text" + ) + list(FILTER SPHINX_GLOB_RECURSE_DEPS + EXCLUDE REGEX "docs/tools" + ) + set(SPHINX_DEPS ${SPHINX_GLOB_DEPS} ${SPHINX_GLOB_RECURSE_DEPS} ${SPHINX_SCRIPT_DEPS} "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.txt" + "${CMAKE_CURRENT_SOURCE_DIR}/conf.py" ) set(SPHINX_OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/docs/html/.buildinfo") - set_source_files_properties(${SPHINX_OUTPUT} PROPERTIES GENERATED TRUE) + set_property( + DIRECTORY PROPERTY ADDITIONAL_CLEAN_FILES TRUE + "${CMAKE_CURRENT_SOURCE_DIR}/docs/changelogs" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/pdf" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/pseudoxml" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/tags" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/text" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/tools" + "${CMAKE_CURRENT_SOURCE_DIR}/docs/xml" + "${CMAKE_BINARY_DIR}/docs/html" + "${CMAKE_BINARY_DIR}/docs/pdf" + "${CMAKE_BINARY_DIR}/docs/pseudoxml" + "${CMAKE_BINARY_DIR}/docs/text" + "${CMAKE_BINARY_DIR}/docs/xml" + ) add_custom_command(OUTPUT ${SPHINX_OUTPUT} - COMMAND ${SPHINX_EXECUTABLE} - -a -E -q -b html - "${CMAKE_CURRENT_SOURCE_DIR}" - "${CMAKE_CURRENT_SOURCE_DIR}/docs/html" - -w "${CMAKE_CURRENT_SOURCE_DIR}/docs/_sphinx-warnings.txt" - -j 2 + COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/docs/build.py" + html text --sphinx="${SPHINX_EXECUTABLE}" -- -q DEPENDS ${SPHINX_DEPS} - COMMENT "Building HTML documentation with Sphinx" + COMMENT "Building documentation with Sphinx" ) add_custom_target(dfhack_docs ALL @@ -492,8 +514,12 @@ if(BUILD_DOCS) COMMAND ${CMAKE_COMMAND} -E touch ${SPHINX_OUTPUT}) install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/html/ + DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs + FILES_MATCHING PATTERN "*" + PATTERN html/_sources EXCLUDE) + install(DIRECTORY ${dfhack_SOURCE_DIR}/docs/text/ DESTINATION ${DFHACK_USERDOC_DESTINATION}/docs) - install(FILES docs/_auto/news.rst docs/_auto/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(FILES docs/changelogs/news.rst docs/changelogs/news-dev.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES "README.html" DESTINATION "${DFHACK_DATA_DESTINATION}") endif() diff --git a/README.md b/README.md index 15f13dbc9..fa6c343e1 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,9 @@ DFHack is a Dwarf Fortress memory access library, distributed with scripts and plugins implementing a wide variety of useful functions and tools. -The full documentation [is available online here](https://dfhack.readthedocs.org), -from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder. +The full documentation [is available online here](https://dfhack.readthedocs.org). +It is also accessible via the README.html page in the DFHack distribution or as raw text in the `./docs` folder. If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. -If that's unclear, or you need more help checkout our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for up-to-date options. +If the docs are unclear or you need more help, please check out our [support page](https://docs.dfhack.org/en/latest/docs/Support.html) for ways to contact the DFHack developers. diff --git a/ci/download-df.sh b/ci/download-df.sh index 49dcedea7..bb1e91759 100755 --- a/ci/download-df.sh +++ b/ci/download-df.sh @@ -2,10 +2,8 @@ set -e -tardest="df.tar.bz2" - -selfmd5=$(openssl md5 < "$0") -echo $selfmd5 +df_tardest="df.tar.bz2" +save_tardest="test_save.tgz" cd "$(dirname "$0")" echo "DF_VERSION: $DF_VERSION" @@ -14,43 +12,41 @@ mkdir -p "$DF_FOLDER" # back out of df_linux cd "$DF_FOLDER/.." -if [ -f receipt ]; then - if [ "$selfmd5" != "$(cat receipt)" ]; then - echo "download-df.sh changed; removing DF" - rm receipt - else - echo "Already downloaded $DF_VERSION" - fi -fi - -if [ ! -f receipt ]; then - rm -f "$tardest" +if ! test -f "$df_tardest"; then minor=$(echo "$DF_VERSION" | cut -d. -f2) patch=$(echo "$DF_VERSION" | cut -d. -f3) - url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" - echo Downloading + echo "Downloading DF $DF_VERSION" while read url; do echo "Attempting download: ${url}" - if wget -v "$url" -O "$tardest"; then + if wget -v "$url" -O "$df_tardest"; then break fi done < receipt -ls +ls -l diff --git a/ci/run-tests.py b/ci/run-tests.py index 11534b3f9..a6a35bc76 100755 --- a/ci/run-tests.py +++ b/ci/run-tests.py @@ -68,12 +68,21 @@ init_contents = change_setting(init_contents, 'FPS', 'YES') if args.headless: init_contents = change_setting(init_contents, 'PRINT_MODE', 'TEXT') -test_init_file = 'dfhackzzz_test.init' # Core sorts these alphabetically +init_path = 'dfhack-config/init' +if not os.path.isdir('hack/init'): + # we're on an old branch that still reads init files from the root dir + init_path = '.' +try: + os.mkdir(init_path) +except OSError as error: + # ignore already exists errors + pass +test_init_file = os.path.join(init_path, 'dfhackzzz_test.init') # Core sorts these alphabetically with open(test_init_file, 'w') as f: f.write(''' devel/dump-rpc dfhack-rpc.txt :lua dfhack.internal.addScriptPath(dfhack.getHackPath()) - test --resume --modes=none,title "lua scr.breakdown_level=df.interface_breakdown_types.%s" + test --resume -- lua scr.breakdown_level=df.interface_breakdown_types.%s ''' % ('NONE' if args.no_quit else 'QUIT')) test_config_file = 'test_config.json' diff --git a/ci/script-docs.py b/ci/script-docs.py index 71d7f37b2..0106a8f70 100755 --- a/ci/script-docs.py +++ b/ci/script-docs.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import os -from os.path import basename, dirname, join, splitext +from os.path import basename, dirname, exists, join, splitext import sys SCRIPT_PATH = sys.argv[1] if len(sys.argv) > 1 else 'scripts' +DOCS_PATH = join(SCRIPT_PATH, 'docs') IS_GITHUB_ACTIONS = bool(os.environ.get('GITHUB_ACTIONS')) -def expected_cmd(path): +def get_cmd(path): """Get the command from the name of a script.""" dname, fname = basename(dirname(path)), splitext(basename(path))[0] if dname in ('devel', 'fix', 'gui', 'modtools'): @@ -14,16 +15,6 @@ def expected_cmd(path): return fname -def check_ls(fname, line): - """Check length & existence of leading comment for "ls" builtin command.""" - line = line.strip() - comment = '--' if fname.endswith('.lua') else '#' - if '[====[' in line or not line.startswith(comment): - print_error('missing leading comment (requred for `ls`)', fname) - return 1 - return 0 - - def print_error(message, filename, line=None): if not isinstance(line, int): line = 1 @@ -32,48 +23,42 @@ def print_error(message, filename, line=None): print('::error file=%s,line=%i::%s' % (filename, line, message)) +def check_ls(docfile, lines): + """Check length & existence of first sentence for "ls" builtin command.""" + # TODO + return 0 + + def check_file(fname): - errors, doclines = 0, [] - tok1, tok2 = ('=begin', '=end') if fname.endswith('.rb') else \ - ('[====[', ']====]') - doc_start_line = None - with open(fname, errors='ignore') as f: + errors, doc_start_line = 0, None + docfile = join(DOCS_PATH, get_cmd(fname)+'.rst') + if not exists(docfile): + print_error('missing documentation file: {!r}'.format(docfile), fname) + return 1 + with open(docfile, errors='ignore') as f: lines = f.readlines() if not lines: - print_error('empty file', fname) + print_error('empty documentation file', docfile) return 1 - errors += check_ls(fname, lines[0]) for i, l in enumerate(lines): - if doclines or l.strip().endswith(tok1): - if not doclines: - doc_start_line = i + 1 - doclines.append(l.rstrip()) - if l.startswith(tok2): - break - else: - if doclines: - print_error('docs start but do not end', fname, doc_start_line) - else: - print_error('no documentation found', fname) - return 1 - - if not doclines: - print_error('missing or malformed documentation', fname) - return 1 + l = l.strip() + if l and not doc_start_line and doc_start_line != 0: + doc_start_line = i + doc_end_line = i + lines[i] = l - title, underline = [d for d in doclines - if d and '=begin' not in d and '[====[' not in d][:2] - title_line = doc_start_line + doclines.index(title) + errors += check_ls(docfile, lines) + title, underline = lines[doc_start_line:doc_start_line+2] expected_underline = '=' * len(title) if underline != expected_underline: print_error('title/underline mismatch: expected {!r}, got {!r}'.format( expected_underline, underline), - fname, title_line + 1) + docfile, doc_start_line+1) errors += 1 - if title != expected_cmd(fname): + if title != get_cmd(fname): print_error('expected script title {!r}, got {!r}'.format( - expected_cmd(fname), title), - fname, title_line) + get_cmd(fname), title), + docfile, doc_start_line) errors += 1 return errors @@ -81,11 +66,11 @@ def check_file(fname): def main(): """Check that all DFHack scripts include documentation""" err = 0 - exclude = set(['internal', 'test']) + exclude = {'.git', 'internal', 'test'} for root, dirs, files in os.walk(SCRIPT_PATH, topdown=True): dirs[:] = [d for d in dirs if d not in exclude] for f in files: - if f[-3:] in {'.rb', 'lua'}: + if f.split('.')[-1] in {'rb', 'lua'}: err += check_file(join(root, f)) return err diff --git a/ci/test.lua b/ci/test.lua index 9a4a33ef2..aca78e851 100644 --- a/ci/test.lua +++ b/ci/test.lua @@ -240,7 +240,7 @@ end -- output doesn't trigger its own dfhack.printerr usage detection (see -- detect_printerr below) local orig_printerr = dfhack.printerr -local function wrap_expect(func, private) +local function wrap_expect(func, private, path) return function(...) private.checks = private.checks + 1 local ret = {func(...)} @@ -269,7 +269,7 @@ local function wrap_expect(func, private) end -- Skip any frames corresponding to C calls, or Lua functions defined in another file -- these could include pcall(), with_finalize(), etc. - if info.what == 'Lua' and info.short_src == caller_src then + if info.what == 'Lua' and (info.short_src == caller_src or info.short_src == path) then orig_printerr((' at %s:%d'):format(info.short_src, info.currentline)) end frame = frame + 1 @@ -278,7 +278,7 @@ local function wrap_expect(func, private) end end -local function build_test_env() +local function build_test_env(path) local env = { test = utils.OrderedTable(), -- config values can be overridden in the test file to define @@ -309,7 +309,7 @@ local function build_test_env() checks_ok = 0, } for name, func in pairs(expect) do - env.expect[name] = wrap_expect(func, private) + env.expect[name] = wrap_expect(func, private, path) end setmetatable(env, {__index = _G}) return env, private @@ -345,9 +345,9 @@ local function finish_tests(done_command) end local function load_tests(file, tests) - local short_filename = file:sub((file:find('test') or -4)+5, -1) + local short_filename = file:sub((file:find('test') or -4) + 5, -1) print('Loading file: ' .. short_filename) - local env, env_private = build_test_env() + local env, env_private = build_test_env(file) local code, err = loadfile(file, 't', env) if not code then dfhack.printerr('Failed to load file: ' .. tostring(err)) diff --git a/ci/update-submodules.manifest b/ci/update-submodules.manifest index 4bd383018..e97cae6f3 100644 --- a/ci/update-submodules.manifest +++ b/ci/update-submodules.manifest @@ -1,7 +1,9 @@ library/xml master scripts master plugins/stonesense master +plugins/isoworld dfhack depends/libzip dfhack depends/libexpat dfhack depends/xlsxio dfhack depends/luacov dfhack +depends/jsoncpp-sub dfhack diff --git a/conf.py b/conf.py index c762a3cfa..661b0daea 100644 --- a/conf.py +++ b/conf.py @@ -15,178 +15,81 @@ serve to show the default. # pylint:disable=redefined-builtin import datetime -from io import open import os import re import shlex # pylint:disable=unused-import +import sphinx import sys +sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +from dfhack.util import write_file_if_changed -# -- Support :dfhack-keybind:`command` ------------------------------------ -# this is a custom directive that pulls info from dfhack.init-example +if os.environ.get('DFHACK_DOCS_BUILD_OFFLINE'): + # block attempted image downloads, particularly for the PDF builder + def request_disabled(*args, **kwargs): + raise RuntimeError('Offline build - network request blocked') -from docutils import nodes -from docutils.parsers.rst import roles + import urllib3.util + urllib3.util.create_connection = request_disabled + import urllib3.connection + urllib3.connection.HTTPConnection.connect = request_disabled -def get_keybinds(): - """Get the implemented keybinds, and return a dict of - {tool: [(full_command, keybinding, context), ...]}. - """ - with open('dfhack.init-example') as f: - lines = [l.replace('keybinding add', '').strip() for l in f.readlines() - if l.startswith('keybinding add')] - keybindings = dict() - for k in lines: - first, command = k.split(' ', 1) - bind, context = (first.split('@') + [''])[:2] - if ' ' not in command: - command = command.replace('"', '') - tool = command.split(' ')[0].replace('"', '') - keybindings[tool] = keybindings.get(tool, []) + [ - (command, bind.split('-'), context)] - return keybindings - -KEYBINDS = get_keybinds() - - -# pylint:disable=unused-argument,dangerous-default-value,too-many-arguments -def dfhack_keybind_role_func(role, rawtext, text, lineno, inliner, - options={}, content=[]): - """Custom role parser for DFHack default keybinds.""" - roles.set_classes(options) - if text not in KEYBINDS: - msg = inliner.reporter.error( - 'no keybinding for {} in dfhack.init-example'.format(text), - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - return [prb], [msg] - newnode = nodes.paragraph() - for cmd, key, ctx in KEYBINDS[text]: - n = nodes.paragraph() - newnode += n - n += nodes.strong('Keybinding: ', 'Keybinding: ') - for k in key: - n += nodes.inline(k, k, classes=['kbd']) - if cmd != text: - n += nodes.inline(' -> ', ' -> ') - n += nodes.literal(cmd, cmd, classes=['guilabel']) - if ctx: - n += nodes.inline(' in ', ' in ') - n += nodes.literal(ctx, ctx) - return [newnode], [] - - -roles.register_canonical_role('dfhack-keybind', dfhack_keybind_role_func) - -# -- Autodoc for DFhack scripts ------------------------------------------- - -def doc_dir(dirname, files): - """Yield (command, includepath) for each script in the directory.""" + import requests + requests.request = request_disabled + requests.get = request_disabled + + +# -- Autodoc for DFhack plugins and scripts ------------------------------- + +def doc_dir(dirname, files, prefix): + """Yield (name, includepath) for each file in the directory.""" sdir = os.path.relpath(dirname, '.').replace('\\', '/').replace('../', '') + if prefix == '.': + prefix = '' + else: + prefix += '/' for f in files: - if f[-3:] not in ('lua', '.rb'): + if f[-4:] != '.rst': continue - with open(os.path.join(dirname, f), 'r', encoding='utf8') as fstream: - text = [l.rstrip() for l in fstream.readlines() if l.strip()] - # Some legacy lua files use the ruby tokens (in 3rdparty scripts) - tokens = ('=begin', '=end') - if f[-4:] == '.lua' and any('[====[' in line for line in text): - tokens = ('[====[', ']====]') - command = None - for line in text: - if command and line == len(line) * '=': - yield command, sdir + '/' + f, tokens[0], tokens[1] - break - command = line + yield prefix + f[:-4], sdir + '/' + f def doc_all_dirs(): """Collect the commands and paths to include in our docs.""" - scripts = [] - for root, _, files in os.walk('scripts'): - scripts.extend(doc_dir(root, files)) - return tuple(scripts) - -DOC_ALL_DIRS = doc_all_dirs() + tools = [] + for root, _, files in os.walk('docs/builtins'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/builtins'))) + for root, _, files in os.walk('docs/plugins'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'docs/plugins'))) + for root, _, files in os.walk('scripts/docs'): + tools.extend(doc_dir(root, files, os.path.relpath(root, 'scripts/docs'))) + return tuple(tools) -def document_scripts(): - """Autodoc for files with the magic script documentation marker strings. - - Returns a dict of script-kinds to lists of .rst include directives. - """ - # Next we split by type and create include directives sorted by command - kinds = {'base': [], 'devel': [], 'fix': [], 'gui': [], 'modtools': []} - for s in DOC_ALL_DIRS: - k_fname = s[0].split('/', 1) - if len(k_fname) == 1: - kinds['base'].append(s) - else: - kinds[k_fname[0]].append(s) - - def template(arg): - tmp = '.. _{}:\n\n.. include:: /{}\n' +\ - ' :start-after: {}\n :end-before: {}\n' - if arg[0] in KEYBINDS: - tmp += '\n:dfhack-keybind:`{}`\n'.format(arg[0]) - return tmp.format(*arg) - - return {key: '\n\n'.join(map(template, sorted(value))) - for key, value in kinds.items()} - - -def write_script_docs(): +def write_tool_docs(): """ - Creates a file for eack kind of script (base/devel/fix/gui/modtools) - with all the ".. include::" directives to pull out docs between the - magic strings. + Creates a file for each tool with the ".. include::" directives to pull in + the original documentation. """ - kinds = document_scripts() - head = { - 'base': 'Basic Scripts', - 'devel': 'Development Scripts', - 'fix': 'Bugfixing Scripts', - 'gui': 'GUI Scripts', - 'modtools': 'Scripts for Modders'} - for k in head: - title = ('.. _scripts-{k}:\n\n{l}\n{t}\n{l}\n\n' - '.. include:: /scripts/{a}about.txt\n\n' - '.. contents:: Contents\n' - ' :local:\n\n').format( - k=k, t=head[k], - l=len(head[k])*'#', - a=('' if k == 'base' else k + '/') - ) - mode = 'w' if sys.version_info.major > 2 else 'wb' - with open('docs/_auto/{}.rst'.format(k), mode) as outfile: - outfile.write(title) - outfile.write(kinds[k]) - - -def all_keybinds_documented(): - """Check that all keybindings are documented with the :dfhack-keybind: - directive somewhere.""" - configured_binds = set(KEYBINDS) - script_commands = set(i[0] for i in DOC_ALL_DIRS) - with open('./docs/Plugins.rst') as f: - plugin_binds = set(re.findall(':dfhack-keybind:`(.*?)`', f.read())) - undocumented_binds = configured_binds - script_commands - plugin_binds - if undocumented_binds: - raise ValueError('The following DFHack commands have undocumented ' - 'keybindings: {}'.format(sorted(undocumented_binds))) - - -# Actually call the docs generator and run test -write_script_docs() -all_keybinds_documented() + for k in doc_all_dirs(): + label = ('.. _{name}:\n\n').format(name=k[0]) + include = ('.. include:: /{path}\n\n').format(path=k[1]) + os.makedirs(os.path.join('docs/tools', os.path.dirname(k[0])), + mode=0o755, exist_ok=True) + with write_file_if_changed('docs/tools/{}.rst'.format(k[0])) as outfile: + if k[0] != 'search': + outfile.write(label) + outfile.write(include) -# -- General configuration ------------------------------------------------ -sys.path.append(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'docs', 'sphinx_extensions')) +write_tool_docs() + + +# -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.8' +needs_sphinx = '3.4.3' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom @@ -195,22 +98,33 @@ extensions = [ 'sphinx.ext.extlinks', 'dfhack.changelog', 'dfhack.lexer', + 'dfhack.tool_docs', ] +sphinx_major_version = sphinx.version_info[0] + +def get_caption_str(prefix=''): + return prefix + (sphinx_major_version >= 5 and '%s' or '') + # This config value must be a dictionary of external sites, mapping unique # short alias names to a base URL and a prefix. # See http://sphinx-doc.org/ext/extlinks.html extlinks = { - 'wiki': ('https://dwarffortresswiki.org/%s', ''), + 'wiki': ('https://dwarffortresswiki.org/%s', get_caption_str()), 'forums': ('http://www.bay12forums.com/smf/index.php?topic=%s', - 'Bay12 forums thread '), - 'dffd': ('https://dffd.bay12games.com/file.php?id=%s', 'DFFD file '), + get_caption_str('Bay12 forums thread ')), + 'dffd': ('https://dffd.bay12games.com/file.php?id=%s', + get_caption_str('DFFD file ')), 'bug': ('https://www.bay12games.com/dwarves/mantisbt/view.php?id=%s', - 'Bug '), - 'source': ('https://github.com/DFHack/dfhack/tree/develop/%s', ''), - 'source-scripts': ('https://github.com/DFHack/scripts/tree/master/%s', ''), - 'issue': ('https://github.com/DFHack/dfhack/issues/%s', 'Issue '), - 'commit': ('https://github.com/DFHack/dfhack/commit/%s', 'Commit '), + get_caption_str('Bug ')), + 'source': ('https://github.com/DFHack/dfhack/tree/develop/%s', + get_caption_str()), + 'source-scripts': ('https://github.com/DFHack/scripts/tree/master/%s', + get_caption_str()), + 'issue': ('https://github.com/DFHack/dfhack/issues/%s', + get_caption_str('Issue ')), + 'commit': ('https://github.com/DFHack/dfhack/commit/%s', + get_caption_str('Commit ')), } # Add any paths that contain templates here, relative to this directory. @@ -259,7 +173,7 @@ version = release = get_version() # for a list of supported languages. # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = None +language = 'en' # strftime format for |today| and 'Last updated on:' timestamp at page bottom today_fmt = html_last_updated_fmt = '%Y-%m-%d' @@ -268,11 +182,18 @@ today_fmt = html_last_updated_fmt = '%Y-%m-%d' # directories to ignore when looking for source files. exclude_patterns = [ 'README.md', - 'docs/html*', - 'depends/*', 'build*', - 'docs/_auto/news*', - 'docs/_changelogs/', + 'depends/*', + 'docs/html/*', + 'docs/tags/*', + 'docs/text/*', + 'docs/builtins/*', + 'docs/pdf/*', + 'docs/plugins/*', + 'docs/pseudoxml/*', + 'docs/xml/*', + 'scripts/docs/*', + 'plugins/*', ] # The reST default role (used for this markup: `text`) to use for all @@ -345,16 +266,20 @@ html_sidebars = { ] } -# If false, no module index is generated. -html_domain_indices = False - -# If false, no index is generated. +# generate domain indices but not the (unused) genindex html_use_index = False +html_domain_indices = True + +# don't link to rst sources in the generated pages +html_show_sourcelink = False html_css_files = [ 'dfhack.css', ] +if sphinx_major_version >= 5: + html_css_files.append('sphinx5.css') + # -- Options for LaTeX output --------------------------------------------- # Grouping the document tree into LaTeX files. List of tuples @@ -366,3 +291,15 @@ latex_documents = [ ] latex_toplevel_sectioning = 'part' + +# -- Options for text output --------------------------------------------- + +from sphinx.writers import text + +# this value is arbitrary. it just needs to be bigger than the number of +# characters in the longest paragraph in the DFHack docs +text.MAXWIDTH = 1000000000 + +# this is the order that section headers will use the characters for underlines +# they are in the order of (subjective) text-mode readability +text_sectionchars = '=-~`+"*' diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 53e88fed9..412ffa347 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -1,9 +1,21 @@ +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/init/ + DESTINATION "${DFHACK_DATA_DESTINATION}/init") + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/base_command_counts.json + DESTINATION "${DFHACK_DATA_DESTINATION}/data/base_command_counts.json") + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/quickfort/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/quickfort") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/orders/ + DESTINATION dfhack-config/orders/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/examples/ DESTINATION "${DFHACK_DATA_DESTINATION}/examples") +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/professions/ + DESTINATION dfhack-config/professions/library) + install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/ DESTINATION blueprints FILES_MATCHING PATTERN "*" diff --git a/data/base_command_counts.json b/data/base_command_counts.json new file mode 100644 index 000000000..6acfda21b --- /dev/null +++ b/data/base_command_counts.json @@ -0,0 +1,145 @@ +{ + "manipulator": 75, + "autolabor": 59, + "reveal": 51, + "help": 50, + "ls": 50, + "die": 50, + "tags": 50, + "embark-assistant": 42, + "prospect": 37, + "autodump": 36, + "clean": 35, + "gui/workflow": 28, + "workflow": 28, + "exportlegends": 26, + "gui/autobutcher": 25, + "autobutcher": 25, + "digv": 24, + "fastdwarf": 22, + "autonestbox": 20, + "showmood": 19, + "gui/liquids": 18, + "liquids": 18, + "search": 18, + "gui/quickfort": 15, + "quickfort": 15, + "createitem": 14, + "stocks": 14, + "autofarm": 12, + "autochop": 12, + "tiletypes": 12, + "exterminate": 12, + "buildingplan": 12, + "quicksave": 11, + "gui/gm-editor": 11, + "cleanowned": 10, + "gui/autogems": 9, + "autogems": 9, + "stonesense": 9, + "gui/stockpiles": 8, + "stockpiles": 8, + "changevein": 8, + "gui/teleport": 7, + "teleport": 7, + "seedwatch": 6, + "automelt": 6, + "embark-tools": 6, + "cursecheck": 5, + "open-legends": 5, + "ban-cooking": 5, + "burial": 5, + "automaterial": 5, + "remove-stress": 5, + "gui/blueprint": 5, + "blueprint": 5, + "tailor": 4, + "startdwarf": 4, + "3dveins": 4, + "digcircle": 4, + "nestboxes": 3, + "deathcause": 3, + "list-agreements": 3, + "gui/room-list": 3, + "points": 3, + "region-pops": 3, + "gui/advfort": 3, + "unsuspend": 3, + "locate-ore": 3, + "changelayer": 3, + "source": 3, + "gui/gm-unit": 3, + "combine-drinks": 3, + "combine-plants": 3, + "deteriorate": 3, + "warn-starving": 3, + "gaydar": 2, + "gui/dfstatus": 2, + "gui/rename": 2, + "rename": 2, + "fix-ster": 2, + "job-material": 2, + "stockflow": 2, + "drain-aquifer": 2, + "full-heal": 2, + "spawnunit": 2, + "flashstep": 2, + "gui/family-affairs": 2, + "caravan": 2, + "mousequery": 2, + "tweak": 2, + "confirm": 2, + "autoclothing": 1, + "autounsuspend": 1, + "prioritize": 1, + "dwarfmonitor": 1, + "show-unit-syndromes": 1, + "troubleshoot-item": 1, + "gui/mechanisms": 1, + "gui/pathable": 1, + "hotkeys": 1, + "infiniteSky": 1, + "force": 1, + "hermit": 1, + "strangemood": 1, + "weather": 1, + "add-recipe": 1, + "autotrade": 1, + "zone": 1, + "autonick": 1, + "stripcaged": 1, + "unforbid": 1, + "workorder": 1, + "gui/mod-manager": 1, + "spotclean": 1, + "plant": 1, + "regrass": 1, + "dig-now": 1, + "build-now": 1, + "clear-webs": 1, + "gui/siege-engine": 1, + "assign-skills": 1, + "brainwash": 1, + "elevate-mental": 1, + "elevate-physical": 1, + "launch": 1, + "linger": 1, + "make-legendary": 1, + "rejuvenate": 1, + "resurrect-adv": 1, + "questport": 1, + "getplants": 1, + "gui/stamper": 1, + "tweak": 1, + "fixveins": 1, + "deramp": 1, + "fix/dead-units": 1, + "fix/fat-dwarves": 1, + "fix/loyaltycascade": 1, + "fix/retrieve-units": 1, + "tweak": 1, + "sort-items": 1, + "gui/color-schemes": 1, + "color-schemes": 1, + "season-palette": 1 +} diff --git a/data/blueprints/library/aquifer_tap.csv b/data/blueprints/library/aquifer_tap.csv index 22025d8e7..3cc560ce8 100644 --- a/data/blueprints/library/aquifer_tap.csv +++ b/data/blueprints/library/aquifer_tap.csv @@ -5,21 +5,21 @@ Here's the procedure: "" 1) Dig a tunnel from where you want the water to end up (e.g. your well cistern) off to an unused portion of the map. Be sure to dig a one-tile-wide diagonal segment in this tunnel so water that will eventually flow through the tunnel is depressurized. "" -"2) From the end of that tunnel, dig a staircase straight up to the z-level just below the lowest aquifer level. Also dig the staircase down one z-level." +"2) From the end of that tunnel, dig a staircase straight up to the z-level just below the lowest aquifer level. Also dig the staircase down one z-level below the tunnel level." "" -"3) From the bottom of the staircase (the z-level below where the water will flow to your cisterns), dig a straight, one-tile wide tunnel to the edge of the map. Smooth the map edge tile and carve a fortification. The water can flow through the fortification and off the map, allowing the dwarves to dig out the aquifer tap without drowning." +"3) From the bottom of the staircase (the z-level below where the water will flow to your cisterns), dig a straight, one-tile wide tunnel to the edge of the map. This is your drainage tunnel. Smooth the map edge tile and carve a fortification. The water can flow through the fortification and off the map, allowing the dwarves to dig out the aquifer tap without drowning." "" -4) Place a lever-controlled floodgate in the drainage tunnel and open the floodgate. +4) Place a lever-controlled floodgate in the drainage tunnel and open the floodgate. Place the lever somewhere else in your fort so that it will remain dry and accessible. "" -"5) If you care about how nice things look, haul away any boulders and smooth the tiles. You won't be able to access any of this area once it fills up with water!" +"5) If you care about how nice things look, haul away any boulders in the tunnels and smooth the tiles. You won't be able to access any of this area once it fills up with water!" "" -"6) Apply this blueprint (quickfort run library/aquifer_tap.csv -n /dig) to the z-level above the top of the staircase, inside the lowest aquifer level. Do not unpause until after the next step." +"6) Apply this blueprint (gui/quickfort library/aquifer_tap.csv -n /dig) to the z-level above the top of the staircase, inside the lowest aquifer level. Do not unpause until after the next step." "" "7) Damp tiles cancel dig designations if the tile is currently hidden, so to avoid having to re-apply this blueprint after every tile your dwarves dig, position the cursor straight up (north) of the left-most tile designated for digging and straight left (west) of the topmost designated tile and run the following commands in the DFHack console:" tiletypes-command f any ; f designated 1 ; p any ; p hidden 0 ; r 23 23 1 tiletypes-here "" -"8) Unpause and dig out the tap. If you care about appearances, haul away any boulders and feel free to remove the ramps (d-z). The water will safely flow down the staircase, through the open floodgate, and off the map until you choose to close the floodgate." +"8) Unpause and dig out the tap. If you care about appearances, haul away any boulders and feel free to remove the ramps (d-z). The water will safely flow down the staircase, through the open floodgate, down the drainage tunnel, and off the map until you choose to close the floodgate." "9) Once everything is dug out and all dwarves are out of the waterways, close the floodgate. Your cisterns will fill with water. Since the waterway to your cisterns is depressurized, the cisterns will stay forever full, but will not flood." "" diff --git a/data/blueprints/library/dreamfort.csv b/data/blueprints/library/dreamfort.csv index c58ce9111..e26650943 100644 --- a/data/blueprints/library/dreamfort.csv +++ b/data/blueprints/library/dreamfort.csv @@ -28,12 +28,12 @@ "" "Dreamfort works best at an embark site that is flat and has at least one soil layer. New players should avoid embarks with aquifers if they are not prepared to deal with them. Bring picks for mining, an axe for woodcutting, and an anvil for a forge. Bring a few blocks to speed up initial workshop construction as well. That's all you really need, but see the example embark profile in the online spreadsheets for a more complete setup." "" -"Other DFHack scripts and plugins also work very well with Dreamfort, such as autofarm, automelt, autonestbox, burial, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that configures all these plugins is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." -Put that file in your Dwarf Fortress directory -- the same directory that has dfhack.init. +"Other DFHack commands also work very well with Dreamfort, such as autofarm, autonestbox, prioritize, seedwatch, tailor, and, of course, buildingplan. An init file that gets everything configured for you is distributed with DFHack as hack/examples/init/onMapLoad_dreamfort.init." +Put that file in your dfhack-config/init/ directory -- the same directory that has dfhack.init. "" -"Also copy the files in hack/examples/orders/ to dfhack-config/orders/ and the files in hack/examples/professions/ to professions/. We'll be using these files later. See https://docs.dfhack.org/en/stable/docs/guides/examples-guide.html for more information, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." +"Also check out https://docs.dfhack.org/en/stable/docs/Plugins.html#professions for more information on the default labor professions that are distributed with DFHack, including suggestions on how many dwarves of each profession you are likely to need at each stage of fort maturity." "" -"Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your onMapLoad.init file like this:" +"Once you have your starting surface workshops up and running, you might want to configure buildingplan (in its global settings, accessible from any building placement screen, e.g.: b-a-G) to only use blocks for constructions so it won't use your precious wood, boulders, and bars to build floors and walls. If you bring at least 7 blocks with you on embark, you can even set this in your dfhack-config/init/onMapLoad.init file like this:" on-new-fortress buildingplan set boulders false; buildingplan set logs false "" "Directly after embark, run ""quickfort run library/dreamfort.csv -n /setup"" with your cursor on your wagon to set settings, and get started building your fort with ""quickfort run library/dreamfort.csv -n /surface1"" on the surface (see /surface_help for how to select a good spot). Read the walkthroughs for each level to understand what's going on and follow the checklist to keep track of where you are in the building process. Good luck, and have fun building an awesome Dreamfort-based fort!" @@ -46,12 +46,10 @@ interactively." "# The dreamfort.csv distributed with DFHack is generated with the following command: for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' ""$fname""; done | sed 's/,*$//'" #notes label(checklist) command checklist -"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the level walkthroughs for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." +"Here is the recommended order for Dreamfort commands. You can copy/paste the command lines directly into the DFHack terminal, or, if you prefer, you can run the blueprints in the UI with gui/quickfort. See the walkthroughs (the ""help"" blueprints) for context and details. Also remember to read the messages the blueprints print out after you run them so you don't miss any important manual steps." "" -- Preparation (before you embark!) -- -Copy hack/examples/init/onMapLoad_dreamfort.init to your DF directory -Copy the fort automation orders from hack/examples/orders/*.json to the dfhack-config/orders/ directory -Optionally copy the premade profession definitions from hack/examples/professions/ to the professions/ directory +Copy hack/examples/init/onMapLoad_dreamfort.init to your dfhack-config/init directory inside your DF installation Optionally copy the premade Dreamfort embark profile from the online spreadsheets to the data/init/embark_profiles.txt file "" -- Set settings and preload initial orders -- @@ -59,15 +57,14 @@ quickfort run library/dreamfort.csv -n /setup,# Run before making any manual adj "quickfort orders library/dreamfort.csv -n ""/surface2, /farming2, /surface3, /farming3, /industry2, /surface4""","# Queue up orders required to get the fort minimally functional and secure. You can remove the order for the anvil (you brought one with you, right?)." "" -- Find a good starting spot on the surface -- -quickfort run library/dreamfort.csv -n /perimeter,# Run at embark. -quickfort undo library/dreamfort.csv -n /perimeter,# Clean up after you find your center tile. +gui/quickfort library/dreamfort.csv -n /perimeter,# Run at embark. Don't actually apply the blueprint. Just use the preview shadow to find a good spot on the surface. "" -- Dig -- quickfort run library/dreamfort.csv -n /surface1,# Run when you find your center tile. quickfort run library/dreamfort.csv -n /dig_all,"# Run when you find a suitable rock layer for the industry level. It designates digging for industry, services, guildhall, suites, and apartments all in one go. This list does not include the farming level, which we'll dig in the uppermost soil layer a bit later. Note that it is more efficient for your miners if you designate your digging before they dig the central stairs past that level since the stairs are dug at a low priority. This keeps your miners focused on one level at a time. If you need to designate your levels individually due to caverns interrupting the sequence or just because it is your preference, run the level-specific dig blueprints (i.e. /industry1, /services1, /guildhall1, /suites1, and 5 levels of /apartments1) instead of running /dig_all." "" -- Core fort (should finish at about the third migration wave) -- -quickfort run library/dreamfort.csv -n /surface2,# Run after initial trees are cleared. +quickfort run library/dreamfort.csv -n /surface2,"# Run after initial trees are cleared. If you are deconstructing the wagon now, wait until after the wagon is deconstructed so the jobs that depend on wagon contents (e.g. blocks) don't get canceled later." quickfort run library/dreamfort.csv -n /farming1,# Run when channels are dug and the additional designated trees are cleared. quickfort run library/dreamfort.csv -n /farming2,# Run when the farming level has been dug out. quickfort run library/dreamfort.csv -n /surface3,# Run right after /farming2. @@ -75,36 +72,37 @@ quickfort run library/dreamfort.csv -n /farming3,# Run when furniture has been p quickfort run library/dreamfort.csv -n /industry2,# Run when the industry level has been dug out. prioritize ConstructBuilding,# To get those workshops up and running ASAP. You may have to run this several times as the materials for the building construction jobs become ready. quickfort run library/dreamfort.csv -n /surface4,"# Run after the walls and floors are built on the surface. Even if /surface3 is finished before you run /industry2, though, wait until after /industry2 to run this blueprint so that surface walls, floors, and roofing don't prevent your workshops from being built (due to lack of blocks)." -"quickfort run,orders library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. -orders import basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." -"quickfort run,orders library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." -prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the masons come and actually build them. -"quickfort run,orders library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. -"quickfort run,orders library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. +"quickfort orders,run library/dreamfort.csv -n /services2",# Run when the services levels have been dug out. Feel free to remove the orders for the ropes if you already brought them with you. +orders import library/basic,"# Run after the first migration wave, so you have dorfs to do all the basic tasks. Note that this is the ""orders"" plugin, not the ""quickfort orders"" command." +"quickfort orders,run library/dreamfort.csv -n /surface5","# Run when all marked trees on the surface are chopped down and walls and floors have been constructed, including the roof section over the future barracks." +prioritize ConstructBuilding,# Run when you see the bridges ready to be built so the busy masons come and build them. +"quickfort orders,run library/dreamfort.csv -n /surface6",# Run when at least the beehives and weapon rack are constructed and you have linked all levers to their respective bridges. +"quickfort orders,run library/dreamfort.csv -n /surface7",# Run after the surface walls are completed and any marked trees are chopped down. "" -- Plumbing -- "This is a good time to fill your well cisterns, either with a bucket brigade or by routing water from a freshwater stream or an aquifer (see the library/aquifer_tap.csv blueprint for help with this)." "Also consider bringing magma up to your services level so you can replace the forge and furnaces on your industry level with more powerful magma versions. This is especially important if your embark has insufficient trees to convert into charcoal. Keep in mind that moving magma is a tricky process and can take a long time. Don't forget to continue making progress through the checklist! If you choose to use magma, I suggest getting it in place before importing the military and smelting automation orders since they make heavy use of furnaces and forges." "" -- Mature fort (third migration wave onward) -- -orders import furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). -"quickfort run,orders library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. -"quickfort run,orders library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." -"quickfort run,orders library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. -orders import military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. -orders import smelting,# Automated production of all types of metal bars. -"quickfort run,orders library/dreamfort.csv -n /services4","# Run when you need a jail and/or fancy statues in the dining room, anytime after the restraints are placed from /services3." -orders import rockstock,# Maintains a small stock of all types of rock furniture. -orders import glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). +orders import library/furnace,# Automated production of basic furnace-related items. Don't forget to create a sand collection zone (or remove the sand- and glass-related orders if you have no sand). +"quickfort orders,run library/dreamfort.csv -n /suites2",# Run when the suites level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /surface8","# Run if/when you need longer trap corridors on the surface for larger sieges, anytime after you run /surface7." +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the first apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /services3","# Run after the dining table and chair, weapon rack, and archery targets have been constructed. Also wait until after you complete /surface7, though, because surface defenses are more important than a grand dining hall." +"quickfort orders,run library/dreamfort.csv -n /guildhall2",# Run when the guildhall level has been dug out. +"quickfort orders,run library/dreamfort.csv -n ""/guildhall3, /guildhall4""",# Optionally run after /guildhall2 to build default furnishings and declare a library and temple. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed on the first apartments level. +"quickfort orders,run library/dreamfort.csv -n /farming4",# Run once you have a cache of potash. +orders import library/military,# Automated production of military equipment. Turn on automelt in the meltables piles on the industry level to automatically upgrade all metal military equipment to masterwork quality. These orders are optional if you are not using a military. +orders import library/smelting,# Automated production of all types of metal bars. +"quickfort orders,run library/dreamfort.csv -n /services4","# Run when you need a jail, anytime after the restraints are placed from /services3." +orders import library/rockstock,# Maintains a small stock of all types of rock furniture. +orders import library/glassstock,# Maintains a small stock of all types of glass furniture and parts (only import if you have sand). "" -- Repeat for each remaining apartments level as needed -- -"quickfort run,orders library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. -"quickfort run,orders library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. -burial -pets,# Run once the coffins are placed to set them to allow for burial. This is handled for you if you are using the provided onMapLoad_dreamfort.init file. +"quickfort orders,run library/dreamfort.csv -n /apartments2",# Run when the apartment level has been dug out. +"quickfort orders,run library/dreamfort.csv -n /apartments3",# Run when all beds have been constructed. +burial -pets,# Run once the coffins are placed to set them to allow for burial. See this checklist online at https://docs.google.com/spreadsheets/d/13PVZ2h3Mm3x_G1OXQvwKd7oIR2lK4A1Ahf6Om1kFigw/edit#gid=1459509569 #notes label(setup_help) @@ -113,14 +111,14 @@ Makes common initial adjustments to in-game settings. "The /setup blueprint is intended to be run once at the start of the game, before anything else is changed. Players are welcome to make any further customizations after this blueprint is run. Please be sure to run the /setup blueprint before making any other changes, though, so your settings are not overwritten!" "" The following settings are changed: -"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is also set to the highest precision." +"- The manager, chief medical dwarf, broker, and bookkeeper noble roles are assigned to the first suggested dwarf. This is likely to be the expedition leader, but the game could suggest others if they have relevant skills. Bookkeeping is set to the highest precision." "" - Standing orders are set to: - only farmers harvest - gather refuse from outside (incl. vermin) - - no autoloom (we'll be managing cloth production with automated orders) + - no autoloom (so the hospital always has thread -- we'll be managing cloth production with automated orders) "" -"- A burrow named ""Inside"" is created (it's up to the player to define the area). It is intended for use in getting your civilians to safety during sieges. An alert named ""Siege"" is also created and associated with the ""Inside"" burrow." +"- A burrow named ""Inside"" is created, but it's up to the player to define the area as the fort expands. It is intended for use in getting your civilians to safety during sieges. A military alert named ""Siege"" is also created and associated with the ""Inside"" burrow." "" - Military uniforms get the following modifications: - all default uniforms set to replace clothing @@ -144,7 +142,7 @@ starthotkeys: H{sethotkey fkey={F2} name=Farming}{sethotkey fkey={F3} name=Indus "" "#config label(setup) message(Please set the zoom targets of the hotkeys (the 'H' menu) according to where you actually end up digging the levels. As you build your fort, expand the ""Inside"" burrow to include new civilian-safe areas. -Optionally, add a leather cloak to your military uniforms to enhance the protection of the uniforms. +Optionally, add a leather cloak to your military uniforms to enhance their protection rating. Nothing in Dreamfort depends on these settings staying as they are. Feel free to change any setting to your personal preference.) assign nobles, set standing orders, create burrows, make adjustments to military uniforms, and set hotkey names" {startnobles}{startorders}{startburrows}{startmilitary}{starthotkeys} "#meta label(dig_all) start(central stairs on industry level) dig industry, services, guildhall, suites, and apartments levels. does not include farming." @@ -232,6 +230,7 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari [PET:2:BIRD_GOOSE:FEMALE:STANDARD] [PET:2:BIRD_GOOSE:MALE:STANDARD] #meta label(all_orders) hidden() references all blueprints that generate orders; for testing only +/surface1 /surface2 /surface3 /surface4 @@ -239,27 +238,25 @@ https://drive.google.com/file/d/1Et42JTzeYK23iI5wrPMsFJ7lUXwVBQob/view?usp=shari /surface6 /surface7 /surface8 +/farming1 /farming2 /farming3 /farming4 +/industry1 /industry2 +/services1 /services2 /services3 /services4 +/guildhall1 /guildhall2 +/guildhall3 +/guildhall4 +/suites1 /suites2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments2 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 -/apartments3 +/apartments1 repeat(>5) +/apartments2 repeat(>5) +/apartments3 repeat(>5) #notes label(surface_help) Sets up a protected entrance to your fort in a flat area on the surface. Screenshot: https://drive.google.com/file/d/1YL_vQJLB2YnUEFrAg9y3HEdFq3Wpw9WP @@ -325,7 +322,7 @@ Here are some tips and procedures for handling seiges -- including how to clean "" "- Hauler dwarves will come and release prisoners one by one. Your military dwarves will immediately pounce on the released prisoner and chop them to bits, saving the hauler dwarves from being attacked. Repeat until all prisoners have been ""processed""." "" -"Only types of creatures that you commonly see in sieges are accepted by the prisoner hauling route by default. You can add additional creature types by configuring the hauling route stop in the 'h' menu. Note that generated creature types, like necromancer experiments, can't be explicitly added. You have to (at least temporarily) accept all animals to include them." +"Only common hostile creatures are accepted by the prisoner hauling route by default. You can add additional creature types by configuring the hauling route stop in the 'h' menu. Note that generated creature types, like necromancer experiments, can't be explicitly added. You have to (at least temporarily) accept all animals to include them." #meta label(perimeter) start(central stairs) message(Run quickfort undo on this blueprint to clean up.) show the eventual perimeter of the surface fort; useful for location scouting walls/surface_walls corridor/surface_corridor @@ -334,10 +331,11 @@ corridor/surface_corridor message(Once the central stairs are mined out deeply enough, you should start digging the industry level in a non-aquifer rock layer. You'll need the boulders from the digging to make blocks. If your wagon is within the fort perimeter, deconstruct it to get it out of the way. Once the marked trees are all chopped down (if any), continue with /surface2.) clear trees and set up pastures" -central_stairs/central_stairs repeat(down 10) clear_small/surface_clear_small zones/surface_zones name_zones/surface_name_zones +#> +central_stairs/central_stairs repeat(down 10) "" "#meta label(surface2) start(central stairs) message(Remember to enqueue manager orders for this blueprint. Once the channels are dug out and the marked trees are cleared, continue with /surface3.) set up starting workshops/stockpiles, channel miasma vents, and clear more trees" @@ -363,6 +361,7 @@ Once the marked trees are cleared and at least the beehives and weapon rack have place/surface_place build/surface_build query/surface_query +traffic/surface_traffic clear_large/surface_clear_large "" "#meta label(surface6) start(central stairs) message(Remember to enqueue manager orders for this blueprint. @@ -382,13 +381,14 @@ roof3/surface_roof3 roof4/surface_roof4 "" #meta label(surface8) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) build extended trap corridors +corridor_gates/surface_corridor_gates corridor/surface_corridor corridor_traps/surface_corridor_traps query_corridor/surface_query_corridor #dig label(central_stairs_odd) start(2;2) hidden() spiral stairs odd levels -`,u,` -j6,`,j6 -`,u,` +`,j6,` +u,`,u +`,j6,` #meta label(central_stairs_even) hidden() spiral stairs even levels /central_stairs_odd transform(cw) #meta label(central_stairs) two levels of spiral stairs (use --repeat down) @@ -412,11 +412,11 @@ j6,`,j6 ,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,`,`,`,`,t1,`,`,`,`,`,`,,`,,`,`,`,`,`,t1,`,`,`,t1,`,`,,` -,,,`,,`,,`,t1,t1,,,,,,`,,,,,,`,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` -,,,`,,`,,,,,,,,,,`,,`,~,`,,`,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` -,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,,`,t1,t1,,,,,,`,t1,t1,t1,t1,t1,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,t1,t1,t1,t1,t1,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,`,t1,j,t1,j,t1,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,t1,t1,t1,t1,t1,,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,`,t1,t1,t1,t1,t1,`,,,,,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,` ,,,`,,`,,,,,,,,,`,t1,,,,,,t1,`,,,,,,,,,`,,` ,,,`,,`,,,,,,,,,t1,t1,,,,,,t1,t1,,,,,,,,,`,,` @@ -532,7 +532,7 @@ Feel free to assign an unimportant animal to the pasture in the main entranceway -"#place label(surface_place_start) start(19; 19) hidden() message(if you haven't already, now is a good time to deconstruct the wagon) starting stockpiles" +#place label(surface_place_start) start(19; 19) hidden() starting stockpiles @@ -865,8 +865,8 @@ Feel free to assign an unimportant animal to the pasture in the main entranceway ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,`,~,,,,,,~,`,,,,,,,Cf,Cf,`,,` ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,~,Cf,Cf,Cf,Cf,Cf,~,~,,,,,,,,Cf,`,,` ,,,`,,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,~,,~,~,~,,~,~,,,,,Cf,Cf,,Cf,`,,` -,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,,Cf,,~,~,~,,Cf,,,,,,,,,Cf,`,,` -,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,`,,,~,~,~,,,`,,,,,,,Cf,Cf,`,,` +,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,,Cf,,~,~,~,,Cf,,,Cf,,,,,,Cf,`,,` +,,,`,,`,,Cf,,Cf,Cf,Cf,Cf,Cf,`,,,~,~,~,,,`,,Cf,,,,,Cf,Cf,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,Cf,,,,,,,,,Cf,,,,,~,,,,,Cf,,,,,,,,,Cf,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -943,10 +943,9 @@ Remember to connect the levers to the gates once they are built.) gates, barrack ,,,,,,,,,,,,,,,gw,gw,gw,gw,gw,gw,gw #aliases -permit_prisoner: {animalsprefix}{Right}s{search}&&^ -prisoner_route_enable: {enableanimals}{cages}{permittraps}{permit_prisoner search=dwarves}{permit_prisoner search=elves}{permit_prisoner search=humans}{permit_prisoner search=giants}{permit_prisoner search=goblins}{permit_prisoner search=ettins}{permit_prisoner search=cyclopes} +"prisoner_route_enable: {enableanimals}{cages}{permittraps}{animalsprefix}{Right}{permitsearch search="" men""}{permitsearch search=dwarves}{permitsearch search=elves}{permitsearch search=humans}{permitsearch search=kobolds}{permitsearch search=gremlins}{permitsearch search=giants}{permitsearch search=goblins}{permitsearch search=ettins}{permitsearch search=cyclopes}{permitsearch search=ogres}{permitsearch search=eyes}{permitsearch search=reachers}{permitsearch search=gorlaks}{permitsearch search=trolls}{permitsearch search=minotaurs}^" -"#query label(surface_query) start(19; 19) hidden() message(Remember to assign minecarts to the trade goods and prisoner processing quantum stockpiles. +"#query label(surface_query) start(19; 19) hidden() message(Remember to assign minecarts to the trade goods and prisoner processing quantum stockpiles (run ""assign-minecarts all""). Feel free to adjust the configuration of the ""trade goods"" feeder stockpile so it accepts the item types you want to trade away. If those items types are also accepted by other stockpiles, configure those stockpiles to give to the ""trade goods"" stockpile. You might also want to set the ""trade goods quantum"" stockpile to Auto Trade if you have the autotrade DFHack plugin enabled.)" @@ -974,14 +973,48 @@ You might also want to set the ""trade goods quantum"" stockpile to Auto Trade i ,,,`,,`,nocontainers,crafts,,,,,,,`,,,,"{givename name=""inner main gate""}",,,,`,,,,,,,,,`,,` ,,,`,,`,"{givename name=""trade goods""}",,,,,,,,,,,,,,,,,,,,,,,,,`,,` ,,,`,,`,{forbidmasterworkfinishedgoods}{forbidartifactfinishedgoods},,,,,,,,"{givename name=""trade depo gate""}",,,,,,,,"{givename name=""barracks gate""}",,,,,,,,,`,,` -,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods Dumper""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner dumper""}",,,,,,,`,,` -,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` +,,,`,,`,,"{quantumstopfromnorth name=""Trade Goods quantum""}",,,,,,,,,,,,,,,,,"{quantumstop name=""Prisoner/Cage quantum"" move={Up 5} move_back={Down 5} route_enable={prisoner_route_enable}}{givename name=""prisoner/cage dumper""}",,,,,,,`,,` +,,,`,,`,,"{quantum name=""trade goods quantum""}",,,,,,,`,,,,,,,,`,,"{quantum name=""prisoner/cage quantum"" quantum_enable={enableanimals}}",,,,,,,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` ,,,`,,"{givename name=""left outer gate""}",,,,,,,,,"{givename name=""left inner gate""}",,,,,,,,"{givename name=""right inner gate""}",,,,,,,,,"{givename name=""right outer gate""}",,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,"{givename name=""outer main gate""}",,,,`,`,`,`,`,`,`,`,`,`,`,` +#dig label(surface_traffic) start(19; 19) hidden() set traffic designations + + + +,,,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,`,,`,`,`,,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,or,`,`,`,`,`,`,`,,` +,,,`,,`,,,,,,,,,`,,ol,ol,ol,ol,ol,,`,or,,,,,,,,`,,` +,,,`,,`,,,,,,,,,ol,ol,ol,ol,ol,ol,ol,ol,ol,or,,,,,,,,`,,` +,,,`,,`,,,,,,,,,ol,ol,,,,,,ol,ol,or,,,,,,,,`,,` +,,,`,,`,,,,,,,,,ol,ol,,,,,,ol,ol,or,,,,,,,,`,,` +,,,`,,`,,,,,,,,,`,,,,,,,,`,or,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,` +,,,`,`,`,`,`,`,`,`,`,`,`,`,ol,ol,ol,ol,ol,ol,ol,`,`,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,ol,ol,ol,ol,ol,ol,ol +,,,,,,,,,,,,,,,ol,ol,ol,ol,ol,ol,ol + #dig label(surface_clear_large) start(19; 19) hidden() clear wider area of trees t1(37x33) @@ -1110,8 +1143,8 @@ t1(37x33) ,,,`,,`,~,~,~,~,~,~,~,~,`,~,~,~,~,~,~,~,`,Cf,Cf,Cf,Cf,Cf,Cf,~,~,`,,` ,,,`,,`,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,~,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,`,,` ,,,`,,`,~,~,~,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,Cf,Cf,Cf,~,~,Cf,~,`,,` -,,,`,,`,Cf,~,Cf,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,Cf,Cf,Cf,Cf,Cf,Cf,~,`,,` -,,,`,,`,Cf,~,Cf,~,~,~,~,~,`,Cf,Cf,~,~,~,Cf,Cf,`,Cf,Cf,Cf,Cf,Cf,Cf,~,~,`,,` +,,,`,,`,Cf,~,Cf,~,~,~,~,~,~,~,Cf,~,~,~,Cf,~,~,Cf,~,Cf,Cf,Cf,Cf,Cf,~,`,,` +,,,`,,`,Cf,~,Cf,~,~,~,~,~,`,Cf,Cf,~,~,~,Cf,Cf,`,Cf,~,Cf,Cf,Cf,Cf,~,~,`,,` ,,,`,,`,`,`,`,`,`,`,`,`,`,Cf,Cf,Cf,Cf,Cf,Cf,Cf,`,`,`,`,`,`,`,`,`,`,,` ,,,`,~,,,,,,,,,,,Cf,Cf,Cf,~,Cf,Cf,Cf,,,,,,,,,,,~,` ,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` @@ -1288,11 +1321,45 @@ t1(37x33) -#build label(surface_corridor) start(19; 19) hidden() trap hallway walls +#build label(surface_corridor_gates) start(19; 19) hidden() gates for the longer trap hallways +,,,,gx,,,,,,,,,,,,,,,,,,,,,,,,,,,,gx +,,,`,gx,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,gx,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,`,Tl,`,`,`,Tl,`,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,` +,,,`,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,` +,,,`,,`,,,,,,,,,`,,,,,,,,`,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,` +,,,`,,`,,,,,,,,,`,,,,,,,,`,,,,,,,,,`,,` +,,,`,,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,,` +,,,`,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,` +,,,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,` -,,,Cw,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,Cw + + +#build label(surface_corridor) start(19; 19) hidden() longer trap hallway walls + + +,,,,~,,,,,,,,,,,,,,,,,,,,,,,,,,,,~ +,,,Cw,~,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,,Cw @@ -1307,7 +1374,7 @@ t1(37x33) ,,,Cw,,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,,Cw ,,,Cw,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,Cw -,,,Cw,,`,,,,,,,,,,`,,`,`,`,,`,,,,,,,,,,`,,Cw +,,,Cw,,`,,,,,,,,,,`,~,`,`,`,~,`,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,Cw ,,,Cw,,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,,Cw ,,,Cw,,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,,Cw @@ -1356,11 +1423,11 @@ t1(37x33) -"#build label(surface_corridor_traps) start(19; 19) hidden() barracks, longer trap hallways, and outer levers/gates" +#build label(surface_corridor_traps) start(19; 19) hidden() traps for the longer trap hallways -,,,,gx,,,,,,,,,,,,,,,,,,,,,,,,,,,,gx -,,,`,gx,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,gx,` +,,,,~,,,,,,,,,,,,,,,,,,,,,,,,,,,,~ +,,,`,~,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,~,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,,,,,,,,,,,,,,,,,,,,,,,,,`,Tc,` @@ -1375,7 +1442,7 @@ t1(37x33) ,,,`,Tc,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,Tc,` -,,,`,Tc,`,,,,,,,,,,`,Tl,`,`,`,Tl,`,,,,,,,,,,`,Tc,` +,,,`,Tc,`,,,,,,,,,,`,~,`,`,`,~,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,,,`,`,`,,,,,,,,,,,,`,Tc,` ,,,`,Tc,`,,`,,,,,,,,`,,,,,,`,,,,,,,,,,`,Tc,` ,,,`,Tc,`,`,`,`,`,`,`,,`,`,`,`,,`,,`,`,`,`,,`,`,`,`,`,`,`,Tc,` @@ -1448,7 +1515,7 @@ Workshops: "" Manual steps you have to take: - Check to make sure the lower office is assigned to your manager and assign the upper office to your bookkeeper (if different from your manager) -- Assign a minecart to your refuse quantum stockpile hauling route +"- Assign a minecart to your refuse quantum stockpile hauling route (you can run ""assign-minecarts all"" at the DFHack prompt to do this)" "- If the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile (the one on the left) on the industry level" "" Farming Walkthrough: @@ -1498,6 +1565,7 @@ Farming Walkthrough: Once furniture has been placed, continue with /farming3.) workshops, stockpiles, and important furniture" build/farming_build place/farming_place +traffic/farming_traffic query_stockpiles/farming_query_stockpiles link_stockpiles/farming_link "" @@ -1570,8 +1638,39 @@ build3/farming_build3 ,,,,,,,,,,,,,,,ry +#dig label(farming_traffic) start(16; 18) hidden() keep hungry dwarves away from the crops and food stores so they prefer the prepared meals + + +,,,,,,,,,ol,ol,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,,,ol,ol,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,,,ol,ol,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,,,,,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,ol,ol,ol,,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,ol,ol,ol,ol,ol,,or,or,or,or,or,,ol,ol,ol,ol +,,,,,,,ol,ol,ol,,ol,,or,or,or,or,or,,ol +,,,,,,,,,,,ol,,or,or,or,or,or,,ol,,ol,ol,ol +,,,,,,ol,ol,ol,ol,,ol,,or,or,or,or,or,,ol,ol,ol,ol,ol +,,,,,,ol,ol,ol,ol,,ol,,or,or,or,or,or,,ol,,ol,ol,ol +,,,ol,ol,,ol,ol,ol,ol,,ol,,or,or,or,or,or,,ol +,,ol,ol,ol,,ol,ol,ol,ol,,ol,,or,or,or,or,or,,ol,,ol,ol,ol,,ol,ol,ol +,,ol,ol,ol,ol,ol,ol,ol,ol,ol,ol,,,or,,or,,,ol,ol,ol,ol,ol,ol,ol,ol,ol +,,,ol,ol,,ol,ol,ol,ol,,ol,ol,ol,ol,ol,ol,ol,ol,ol,,ol,ol,ol,,ol,ol,ol +,,,,or,,,or,,,,,,ol,`,`,`,ol,,,,,or,,,,or +,,or,or,or,or,or,or,or,or,or,or,,ol,`,~,`,ol,,or,or,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,or,or,or,or,ol,`,`,`,ol,or,or,or,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,or,or,or,,ol,ol,ol,ol,ol,,or,or,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,or,or,,,,ol,,ol,,,,or,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,or,,,ol,ol,ol,ol,ol,ol,ol,,,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,or,,ol,ol,,,ol,,,ol,ol,,or,or,or,or,or,or,or,or +,,or,or,or,or,or,or,or,,,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,,or,or,or,or,or,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 +,,or,or,or,or,or,or,or,,ol,ol,ol,,,ol,,,ol,ol,ol,,or,or,or,or,or,or,or +,,,,,,,,,,,,,,,ol + + "#query label(farming_query_stockpiles) start(16; 18) hidden() message(remember to: -- assign a minecart to the refuse quantum stockpile +- assign a minecart to the refuse quantum stockpile (run ""assign-minecarts all"") - if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level) config stockpiles" @@ -1587,7 +1686,7 @@ build3/farming_build3 ,,,,,,`,`,`,"{givename name=""pots""}",,`,,seeds,nocontainers,"{givename name=""seeds feeder""}",give2up,`,,`,,`,`,` ,,,bags,,,`,`,`,`,,`,,`,`,`,`,`,,` ,,`,nocontainers,"{givename name=""bags""}",,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,,`,`,` -,,`,jugs,,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,` +,,`,woodentools,,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,` ,,,nocontainers,"{givename name=""jugs""}",,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,,`,`,` ,,,,`,,,`,,,,,,`,`,`,`,`,,,,,`,,,,` ,,plants,,,`,`,`,`,`,`,`,,`,`,~,`,`,,`,`,`,,,,,,,"{givename name=""cookable food""}" @@ -1638,7 +1737,7 @@ build3/farming_build3 ,,,,,,,,,`,`,`,,`,`,`,`,`,,`,`,`,` -,,,,,,,,,r&,`,`,,`,`,`,`,`,,`,`,`,` +,,,,,,,,,r&a+&,,,,`,`,`,`,`,,`,`,`,` ,,,,,,,,,`,`,`,,`,`,`,`,`,,`,`,r+&h ,,,,,,,,,,,`,,`,`,`,`,`,,`,`,`,` ,,,,,,,`,`,`,,`,,`,`,`,`,`,,`,`,`,` @@ -1796,11 +1895,10 @@ Workshops: Manual steps you have to take: - Assign minecarts to your quantum stockpile hauling routes "- Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level" -- Copy the fort automation manager orders (the .json files) from hack/examples/orders/ and put them in your dfhack-config/orders/ directory. "" Optional manual steps you can take: - Restrict the Mechanic's workshop to only allow skilled workers so unskilled trap-resetters won't be tasked to build mechanisms. -"- Restrict the Craftsdwarf's workshops to only allow labors that take from the adjacent stockpiles. That is, only allow Woodcrafting for the Craftsdwarf's workshop on the left near the wood stockpile, Stonecrafting for the Craftsdwarf's workshop near the Mason's workshops, and Bonecrafting for one of the Craftsdwarf's workshop near the Clothier's workshop. The last Craftdwarf's workshop can hold all the remaining labors, or it can be a secondary workshop for a labor that you want more dwarves working on." +"- Restrict the Craftsdwarf's workshops to only allow labors that take from the adjacent stockpiles. That is, only allow Woodcrafting for the Craftsdwarf's workshop on the left near the wood stockpile, Stonecrafting and Strand Extraction for the Craftsdwarf's workshop near the Mason's workshops, and Bonecrafting for one of the Craftsdwarf's workshop near the Clothier's workshop. The last Craftdwarf's workshop can hold all the remaining labors, or it can be a secondary workshop for a labor that you want more dwarves working on." "- To encourage your masons to concentrate on building blocks and other high-volume orders, it helps to set one of your Mason's workshops to service a maximum of 2 manager orders at a time." "- Once you have enough haulers, you can increase the rate of stone and ore hauling jobs by building more wheelbarrows and adding them to the stone and ore feeder stockpiles." "- If desired, set one or both stockpiles in the bottom left to auto-melt. This results in melting all weapons and armor that are inferior to masterwork. This is great for upgrading your military, but it takes a *lot* of fuel unless you have first replaced the forge and smelters with magma versions. If you enable automelt and you don't have magma forges and magma smelters, be sure to be in a heavily forested area, enable auto-chop, and keep your coal stocks high." @@ -1812,11 +1910,11 @@ Industry Walkthrough: "" "3) Once the area is dug out, run /industry2. Remember to assign minecarts to to your quantum stockpile hauling routes, and if the farming level is already built, give from the ""Goods"" quantum stockpile (the one on the left) to the jugs, pots, and bags stockpiles on the farming level." "" -"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." +"4) Once you have enough dwarves to do maintenance tasks (that is, after the first or second migration wave), run ""orders import library/basic"" to use the provided basic.json to take care of your fort's basic needs, such as food, booze, and raw material processing." "" "5) If you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt. If you don't have a high density of trees to make into charcoal, though, be sure to route magma to the level beneath this one and replace the forge and furnaces with magma equivalents." "" -"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import furnace"", ""orders import military"", and ""orders import smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." +"6) Once you have magma furnaces (or abundant fuel) and more dwarves, run ""orders import library/furnace"", ""orders import library/military"", and ""orders import library/smelting"" to import the remaining fort automation orders. The military orders are optional if you are not planning to have a military, of course." "" "7) At any time, feel free to build extra workshops or designate custom stockpiles in the unused space in the top and bottom right. The space is there for you to use!" "#dig label(industry1) start(18; 18; central stairs) message(Once the area is dug out, continue with /industry2.)" @@ -1911,13 +2009,13 @@ query/industry_query ,,w,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,`,`,`,`,`,`,e(5x1),,,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,w,`,`,`,`,`,`,w(2x5),,fg(3x3),,`,,`,`,`,`,`,,rhlS(5x5),,,~,~,`,`,`,`,`,`,` +,,w,`,`,`,`,`,`,w(2x5),,fg(3x3),,`,,`,`,`,`,`,,frhlS(5x5),,,~,~,`,`,`,`,`,`,` ,,w,`,`,`,`,`,`,~,~,~,~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` ,,`,`,`,`,`,c,`,~,~,~,~,~,,`,,`,,`,,~,~,~,~,~,`,r,`,`,`,`,` ,,f3,`,`,`,`,`,`,~,~,u2(3x2),~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,~,~,~,~,~,,`,`,`,`,`,,~,~,~,~,~,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,f3,`,`,`,`,`,`,`,`,`,`,`,`,bnpdz(5x3),,,,~,`,`,`,`,`,`,`,`,`,`,`,`,` +,,f3,`,`,`,`,`,`,`,`,`,`,`,`,bnpdhz(5x3),,,,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,f3,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,pd(7x3),,,~,~,~,~,`,`,`,`,s2(5x2),,,~,~,`,`,`,`,`,`,`,`,`,`,` @@ -1932,10 +2030,10 @@ query/industry_query "#query label(industry_query) start(18; 18) hidden() message(remember to: -- assign minecarts to to your quantum stockpile hauling routes +- assign minecarts to to your quantum stockpile hauling routes (use ""assign-minecarts all"") - if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level - if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt -- once you have enough dwarves, run ""orders import basic"" to automate your fort's basic needs (see /industry_help for more info on this file) +- once you have enough dwarves, run ""orders import library/basic"" to automate your fort's basic needs (see /industry_help for more info on this file) - optionally, restrict the labors for your Craftsdwarf's and Mechanic's workshops as per the guidance in /industry_help)" @@ -1944,7 +2042,7 @@ query/industry_query ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,~,`,`,`,"{quantum name=""stoneworker quantum""}g{Up 3}&",`,`,`,~,`,`,`,`,`,`,`,`,` +,,,,`,`,`,`,`,`,`,`,`,"{givename name=""stone craftsdwarf""}",`,`,`,"{quantum name=""stoneworker quantum""}g{Up 3}&",`,`,`,~,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,"{quantumstop name=""Stoneworker quantum"" sp_links=""{sp_link move={Down} move_back={Up}}{sp_link move=""""{Down 5}"""" move_back=""""{Up 5}""""}""}{givename name=""stoneworker dumper""}",`,`,`,`,`,`,`,`,~,`,`,`,` ,,,,`,`,~,`,`,~,`,`,`,`,`,otherstone,,,,~,`,`,`,`,`,`,`,`,`,`,` ,,,,`,`,`,`,`,`,`,`,`,~,`,"{givename name=""stone feeder""}",~,~,~,~,`,~,`,`,`,`,`,`,`,`,` @@ -1953,13 +2051,13 @@ query/industry_query ,,~,`,~,`,`,~,`,`,~,`,`,`,`,nocontainers,"{givename name=""gem feeder""}",~,~,~,`,`,`,`,~,`,`,~,`,`,~,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,"{givename name=""wood feeder""}",~,"{givename name=""goods feeder""}",nocontainers,~,,`,`,`,`,`,,craftrefuse,,,,~,`,`,`,`,`,`,` -,,t{Right 5}{Down}&,`,`,`,`,`,`,~,~,{tallow}{permitdye}{permitwax},~,~,`,`,,,,`,`,"{givename name=""cloth/bones feeder""}",~,~,~,~,`,`,`,`,`,`,` -,,`,`,~,`,`,"{quantum name=""goods/wood quantum""}g{Up 13}{Right 10}&","{quantumstop name=""Goods/Wood quantum"" sp_links=""{sp_link move={Right} move_back={Left}}{sp_link move=""""{Right 5}"""" move_back=""""{Left 5}""""}{sp_link move=""""{Down}{Right 5}"""" move_back=""""{Left 5}{Up}""""}""}{givename name=""goods/wood dumper""}",~,~,{forbidcrafts}{forbidgoblets},~,~,,`,,`,,`,,nocontainers,~,~,~,~,"{quantumstopfromwest name=""Clothier/Bones quantum""}{givename name=""cloth/bones dumper""}","{quantum name=""cloth/bones quantum""}",`,`,~,`,` -,,miscliquid,`,`,`,`,`,`,~,~,"{givename name=""furniture feeder""}",~,~,`,`,,,,`,`,~,~,~,~,~,`,`,`,`,`,`,` -,,"{givename name=""miscliquid""}",`,`,`,`,`,`,~,~,forbidsand,~,~,,`,`,`,`,`,,~,~,~,~,~,`,`,`,`,`,`,` +,,t{Right 5}{Down}&,`,`,`,`,`,`,~,~,{tallow}{permitwax},~,~,`,`,,,,`,`,"{givename name=""cloth/bones feeder""}",g{Up 3}{Right 5}&,~,~,~,`,`,`,`,`,`,` +,,`,`,~,`,`,"{quantum name=""goods/wood quantum""}g{Up 13}{Right 10}&","{quantumstop name=""Goods/Wood quantum"" sp_links=""{sp_link move={Right} move_back={Left}}{sp_link move=""""{Right 5}"""" move_back=""""{Left 5}""""}{sp_link move=""""{Down}{Right 5}"""" move_back=""""{Left 5}{Up}""""}""}{givename name=""goods/wood dumper""}",~,~,{forbidcrafts}{forbidgoblets},~,~,,`,,`,,`,,nocontainers,~,~,~,~,"{quantumstopfromwest name=""Clothier/Bones quantum""}{givename name=""cloth/bones dumper""}","{quantum name=""cloth/bones quantum""}g{Up 4}&",`,`,~,`,` +,,miscliquid,`,`,`,`,`,`,~,~,"{givename name=""furniture feeder""}",~,~,`,`,,,,`,`,forbidadamantinethread,~,~,~,~,`,`,`,`,`,`,` +,,"{givename name=""miscliquid""}",`,`,`,`,`,`,~,~,forbidsand,~,~,,`,`,`,`,`,,dye,~,~,~,~,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,,,`,,`,,,`,`,`,`,`,`,`,`,`,`,`,` -,,~,`,~,`,`,~,`,`,~,`,`,`,`,forbidpotash,nocontainers,"{givename name=""bar/military feeder""}",~,~,`,`,`,`,~,`,`,~,`,`,`,`,` -,,~,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` +,,~,`,"{givename name=""wood craftsdwarf""}",`,`,~,`,`,~,`,`,`,`,forbidpotash,nocontainers,"{givename name=""bar/military feeder""}",~,~,`,`,`,`,"{givename name=""misc craftsdwarf""}",`,`,"{givename name=""bone craftsdwarf""}",`,`,`,`,` +,,~,`,`,`,`,`,`,`,`,`,`,`,`,adamantinethread,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,~,`,`,`,`,`,`,`,`,`,`,`,`,~,~,~,~,~,`,`,`,`,`,`,`,`,`,`,`,`,` ,,,,nocontainers,t{Right 12}{Up 3}&,t{Right 11}{Down 3}&,"{givename name=""meltable steel/brnze""}",,,,`,`,~,`,forbidotherstone,,,,,`,~,`,`,`,`,`,`,`,`,` ,,,,{bronzeweapons}{permitsteelweapons}{forbidmasterworkweapons}{forbidartifactweapons},,,,,,,`,`,`,`,"{givename name=""ore/clay feeder""}",~,~,~,~,`,`,`,`,`,`,`,`,`,`,` @@ -1977,7 +2075,7 @@ query/industry_query Screenshot: https://drive.google.com/file/d/13vDIkTVOZGkM84tYf4O5nmRs4VZdE1gh "" Features: -- Spacious dining room (also usable as a tavern) +- Spacious dining room/tavern (tavern is restricted to residents-only by default) - Prepared food and drink stockpiles - Well cistern system (bring your own water) - Hospital with a well for washing @@ -1987,9 +2085,10 @@ Features: Note the hospital also has animal training enabled so it can be used with the dwarfvet plugin if it's enabled. "" Manual steps you have to take: -"- If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms." +"- If you want to tavern to attract visitors, change the restriction in the (l)ocation menu." "- Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water to prevent muddiness. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experienced with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)." -"- If you are filling the wells with a bucket brigade and at least one well is already constructed, you'll run into issues with the dwarves stealing water from the wells to fill the wells. Temporarily deconstruct the wells or temporarily build grates beneath the wells to block the buckets to avoid this issue. Remember to rebuild the wells or remove the grates afterwards, though!" +"- If you are filling the wells with a bucket brigade and at least one well is already constructed, you'll run into issues with the dwarves stealing water from one well to fill another. Temporarily deconstruct the wells or temporarily build grates beneath the wells to block the buckets to avoid this issue. Remember to rebuild the wells or remove the grates afterwards, though!" +- Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as an interrogation room. "" Services Walkthrough: 1) Start this level when your fort grows to about 50 dwarves so everyone has a place to eat. @@ -1998,82 +2097,78 @@ Services Walkthrough: "" "3) Once the area is dug out, set up important furniture, stockpiles, hospital zone and garbage dump zone with /services2. Run ""quickfort orders"" for /services2." "" -"4) When the table and chair have been placed in the dining room and the weapon rack and archery targets have been constructed in the barracks, run /services3 to build the rest of the furniture and configure your dining room and barracks. Run ""quickfort orders"" for /services3." +"4) When the table and chair have been placed in the dining room, the beds are placed in the rented rooms, and the weapon rack and archery targets are constructed in the barracks, run /services3 to build the rest of the furniture and configure your dining room/tavern and barracks. Run ""quickfort orders"" for /services3." "" 5) Fill the wells with either bucket brigades or by carefully routing flowing water. "" -"6) When your fort is mature enough to need jail cells, run /services4 to set those up -- anytime after the restraints are built in the jail cell block. You also get some decorative statues to increase the value of your dining hall. Run ""quickfort orders"" for /services4." -"#dig label(services1) start(23; 22; central stairs) message(Once the area is dug out, continue with /services2.)" - - -,,,,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,h,d,,j,,d,h,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,d,d,d,d,,d,h,d,,d,,d,h,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,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 -,,,,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,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,d,h,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,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,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 -#> - - - - - - -,,,,,,,,,,,,,,,,,,j5,h5,j,,u,,j,h5,j5 -,,,,,,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,j5,h5,j,,d,,j,h5,j5 -,,,,,,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,,,d,,,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,5,5,5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,j,h5,j5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d - +"6) When your fort is mature enough to need jail cells, run /services4 to set those up, anytime after the restraints are built in the jail cell block. You also get some decorative statues to increase the value of your dining hall. Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as an interrogation room. Run ""quickfort orders"" for /services4." +"#dig label(services1) start(18; 18; central stairs) message(Once the area is dug out, continue with /services2.)" + +,d,d,d,,d,d,d,,d,d,d,,d,h,d,,j,,d,h,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,h,d,,d,,d,h,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,,,,,,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,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,,,,,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,,d,,d,`,`,`,d,,d,,d,d,d,h,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,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,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,d,d,d,d,,d,d,d,d,d #> +,,,,,,,,,,,,,j5,h5,j,,u,,j,h5,j5 +,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,j5,h5,j,,d,,j,h5,j5 +,,,,,,,,,,,,,5,5,5,5,d,5,5,5,5 +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d +,,,,,,,,,,,,,,,,,d,d,d,d,d,d,d,d,d,d +,,,,,,,,,,,,,,,,,6,,,,,,,,,d +,,,,,,,,,,,,,,,,,d,,,,,,,,,d +,,,,,,,,,,,,,,,d,d,d,d,d,,,,,,,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,5,5,5 +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,j,h5,j5 +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d +#> +,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 - +,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,u5,h5,i,,,,i,h5,u5 @@ -2081,24 +2176,21 @@ Services Walkthrough: +,,,,,,,,,,,,,,,d,d,d,d,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,i,h5,u5 +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,i,h5,u5 -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d #> +,,,,,,,,,,,,,,d,u,,,,u,d +,,,,,,,,,,,,,,d,u,,,,u,d -,,,,,,,,,,,,,,,,,,,d,u,,,,u,d - - - -,,,,,,,,,,,,,,,,,,,d,u,,,,u,d @@ -2106,462 +2198,540 @@ Services Walkthrough: - -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,,u,d -,,,,,,,,,,,,,,,,,,,,d,`,`,`,d -,,,,,,,,,,,,,,,,,,,,d,d,d,d,d - +,,,,,,,,,,,,,,,d,d,d,d,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,`,`,`,d,,,,,,u,d +,,,,,,,,,,,,,,,d,`,`,`,d +,,,,,,,,,,,,,,,d,d,d,d,d "#meta label(services2) start(central stairs) message(Remember to enqueue manager orders for this blueprint. Once furniture has been placed, continue with /services3.) dining hall anchors, stockpiles, hospital, garbage dump" +zones/services_zones build/services_build place/services_place -zones/services_zones name_zones/services_name_zones query_stockpiles/services_query_stockpiles "" -"#meta label(services3) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) configure dining room, build dining hall and hospital furniture" +"#meta label(services3) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) configure dining room/tavern, build dining hall and hospital furniture" query_dining/services_query_dining +query_rented_rooms/services_query_rented_rooms build2/services_build2 "" -#meta label(services4) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) complete jail and build decorative furniture +#meta label(services4) start(central stairs) message(Remember to enqueue manager orders for this blueprint.) declare and furnish jail and build decorative furniture build3/services_build3 place_jail/services_place_jail query_jail/services_query_jail -#build label(services_build) start(23; 22) hidden() build basic hospital and dining room anchor - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,d -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,A,A,A,`,A,A,A,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,b,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,h,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,d,,d,,,,,`,`,`,`,`,`,t,`,` -,,,,`,`,`,`,t,c,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,l,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,R -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,h,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#place label(services_place) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,z(7x2),`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,f(6x5),`,`,`,`,`,,f(6x5),`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -"#zone label(services_zones) start(23; 22) 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.) hospital, garbage dump, and pond zones" - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,ht(9x11),`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -#> - - - - +"#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" + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,ht,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,ht,ht,ht,ht,ht,ht,ht,ht,ht +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` - -,,,,,,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,apPf,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - -#query label(services_name_zones) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,"{namezone name=""hospital""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{namezone name=""garbage dump""}",,,,,,,,,,,,,,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` #> - - - - - -,,,,,,,,,,,,,,,,,,`,"{namezone name=""jail3 well""}",`,,`,,`,"{namezone name=""jail4 well""}",` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,"{namezone name=""jail1 well""}",`,,`,,`,"{namezone name=""jail2 well""}",` -,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,"{namezone name=""hospital well""}",` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - -#query label(services_query_stockpiles) start(23; 22) hidden() message(Configure the training ammo stockpile to take from the metalworker quantum on the industry level.) configure stockpiles - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,nocontainers,{bolts}{forbidmetalbolts}{forbidartifactammo},"{givename name=""training bolts""}",`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,preparedfood,"{givename name=""prepared food""}",`,`,`,`,,booze,"{givename name=""booze""}",`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#query label(services_query_dining) start(23; 22) message(the bedrooms above the tavern are left unconfigured so you can add them as rented rooms) set up dining room and barracks - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,r&w,r&w,r&w,`,r&w,r&w,r&w,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r+++&,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,"r{+ 11}&h{givename name=""grand hall""}",,,,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -"#build label(services_build2) start(23; 22) hidden() build rest of hospital and dining room, doors, prep for jail" - - -,,,,b,b,b,,,b,b,b,,,b,b,b -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,f,`,h,,,f,`,h,,,f,`,h -,,,,,d,,,,,d,,,,,d -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,~,~,~,`,~,~,~,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,~,`,`,`,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,a,`,`,`,b,,f,`,`,`,`,b,~,b,b -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,h,`,`,`,`,`,`,`,h,,~,`,`,`,`,`,`,`,b -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,,,,~,,~,,,,,f,`,`,`,`,`,~,`,b -,,,,`,`,c,t,~,~,`,c,t,t,c,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,`,`,~ -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,~ -,,,,`,`,c,t,t,c,`,c,t,t,c,`,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,h,`,`,~,`,`,`,`,`,h,`,`,h,,,,,,`,,,,,,f,`,`,`,`,`,t,`,b -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,h,`,`,`,`,`,`,`,b -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,f,`,`,`,`,b,b,b,b -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - +,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,apPf,`,,`,,`,apPf,` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,apPf,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#build label(services_build) start(18; 18) hidden() build basic hospital and dining room anchor + +,b,b,b,,b,b,b,,b,b,b,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,d,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,A,A,A,`,A,A,A,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,d,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,b +,`,`,`,`,`,`,`,`,`,`,`,,,,,d,,d,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,t,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,h +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,l,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,d,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,t,c,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,R +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,h,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#place label(services_place) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,z(7x3),,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,c,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,f(5x5),,,`,`,,f(5x5),,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#query label(services_name_zones) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,"{namezone name=""hospital""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{namezone name=""garbage dump""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` #> +,,,,,,,,,,,,,`,"{namezone name=""jail3 well""}",`,,`,,`,"{namezone name=""jail4 well""}",` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,"{namezone name=""jail1 well""}",`,,`,,`,"{namezone name=""jail2 well""}",` +,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,"{namezone name=""hospital well""}",` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#query label(services_query_stockpiles) start(18; 18) hidden() message(Configure the training ammo stockpile to take from the metalworker quantum on the industry level.) configure stockpiles + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,nocontainers,{bolts}{forbidmetalbolts}{forbidartifactammo},"{givename name=""training bolts""}",`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,"{givename name=""garbage dump""}" +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,preparedfood,"{givename name=""prepared food""}",`,`,`,,booze,"{givename name=""booze""}",`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +"#query label(services_query_dining) start(18; 18) hidden() message(The tavern is restricted to residents only by default. If you'd like your tavern to attract vistors, please go to the (l)ocation menu and change the restriction.) set up dining room/tavern and barracks" + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,r+&w,r+&w,r+&w,`,r+&w,r+&w,r+&w,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,r{+ 2}&,,,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,"r{+ 12}&h{givename name=""grand hall""}lai^l{Up}r^q",,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#query label(services_query_rented_rooms) start(18; 18) hidden() attach rented rooms to tavern + +,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,r&l-&,r&l-&,r&l-&,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +"#build label(services_build2) start(18; 18) hidden() build rest of hospital and dining room, doors, prep for jail" + +,~,~,~,,~,~,~,,~,~,~,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,v,`,d,`,d,`,v,` +,f,`,h,,f,`,h,,f,`,h,,`,`,`,,`,,`,`,` +,,d,,,,d,,,,d,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,v,`,d,`,d,`,v,`,,`,`,c,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,~,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,~,~,~,`,~,~,~,h,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,~,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,`,~,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,b,`,`,`,`,`,`,`,a,,b,,b,,b,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,,~,,~,,,,,d,,d,,d,,d +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,~,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,~ +,`,c,t,t,c,`,c,t,t,c,`,,`,,`,`,`,`,`,,`,,`,`,`,~,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,~,`,d,`,`,`,`,`,d,`,d,`,`,`,`,`,`,f +,`,c,t,~,~,`,c,t,t,c,`,,,,`,`,`,`,`,,,,`,`,`,t,`,`,`,d,~ +,`,c,t,t,c,`,c,t,t,c,`,,,,,,`,,,,,,d,,d,,d,,d +,`,c,t,t,c,`,c,t,t,c,`,,,,,,,,,,,,b,,b,,b,,b +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,c,t,t,c,`,c,t,t,c,` +,`,`,`,`,`,`,`,`,`,`,` +,h,`,`,`,`,~,`,`,`,`,h +,,,,d,d,,d,d +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +#> - - - -,,,,,,,,,,,,,,,,,,`,`,`,,`,,`,`,` -,,,,,,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,`,`,`,,`,,`,`,` -,,,,,,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,,,d,,,,,,,,,,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,,d -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` -,,,,,,,,,,,,,,,,,,,,`,`,`,`,` - - - - -"#build label(services_build3) start(23; 22) hidden() jail, statues" - - -,,,,~,~,~,,,~,~,~,,,~,~,~ -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,~,`,~,,,~,`,~,,,~,`,~ -,,,,,~,,,,,~,,,,,~ -,,,,s,`,`,s,s,`,`,`,s,s,`,`,s,,t,l,b,,`,,t,l,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,t,l,b,,`,,t,l,b -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,` -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,~ -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,~,`,`,`,~ -,,,,s,`,`,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,~,`,`,`,~,,~,`,s,s,`,~,~,~,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,~,`,`,`,`,`,`,`,~,,~,`,`,`,`,`,`,`,~ -,,,,s,`,~,~,~,~,`,~,~,~,~,`,s,,,,,~,,~,,,,,~,`,`,`,`,`,~,`,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,s,`,~,~,~,~,`,~,~,~,~,`,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,`,`,~ -,,,,`,`,~,~,~,~,`,~,~,~,~,`,`,,s,,`,`,`,`,`,,s,,`,`,`,`,~,`,`,`,~ -,,,,s,`,~,~,~,~,`,~,~,~,~,`,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,~,s,s,~,`,`,`,`,`,~,s,s,~,,,,,,`,,,,,,~,`,`,`,`,`,~,`,~ -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,~,`,`,`,`,`,`,`,~ -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,~,`,s,s,`,~,~,~,~ -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#place label(services_place_jail) start(23; 22) hidden() - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - - -#query label(services_query_jail) start(23; 22) hidden() set up barracks and jail - - -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,`,`,`,,,`,`,`,,,`,`,` -,,,,,`,,,,,`,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail3""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail4""}","{booze}{givename name=""booze""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}& -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail1""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail2""}","{booze}{givename name=""booze""}" -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 14}{Left 10}&,t{Down 14}{Left 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Left 6}&,t{Left 6}& -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,`,`,`,`,` -,,,,,,,,`,`,,`,`,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,`,,,,,,,,,,,,`,`,`,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` -,,,,`,`,`,`,`,`,,`,`,`,`,`,` - +,,,,,,,,,,,,,`,`,`,,`,,`,`,` +,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,`,`,`,,`,,`,`,` +,,,,,,,,,,,,,`,`,`,d,`,d,`,`,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,` +,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,` +,,,,,,,,,,,,,,,,,`,,,,,,,,,` +,,,,,,,,,,,,,,,,,d,,,,,,,,,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,,d +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,`,,,,,,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` +,,,,,,,,,,,,,,,`,`,`,`,` + +#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 +,`,`,`,,`,`,`,,`,`,`,,c,~,`,~,`,~,c,~,` +,~,`,~,,~,`,~,,~,`,~,,`,`,`,,`,,`,`,` +,,~,,,,~,,,,~,,,,,,,` +,`,`,`,s,`,`,`,s,`,`,`,,t,l,b,,`,,t,l,b,,j,`,`,`,j +,`,`,`,`,`,`,`,`,`,`,`,,c,~,`,~,`,~,c,~,`,,`,`,~,`,` +,s,`,`,`,`,`,`,`,`,`,s,,`,`,`,,`,,`,`,`,,v,`,t,`,v +,`,`,`,`,`,`,`,`,`,`,`,,,,,,~,,,,,,`,`,c,`,` +,s,`,`,`,`,`,`,`,`,`,s,,~,~,~,~,`,~,~,~,~,,j,`,`,`,j +,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,~,`,`,`,`,,,,d +,s,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,`,~,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,~,`,`,`,`,`,`,`,` +,s,`,`,`,`,`,`,`,`,`,s,,~,`,`,`,`,`,`,`,~,,~,,~,,~,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,,~,,~,,,,,~,,~,,~,,~ +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,s,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,~ +,`,~,~,~,~,`,~,~,~,~,`,,s,,`,`,`,`,`,,s,,`,`,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,~,`,~,`,`,`,`,`,~,`,~,`,`,`,`,`,`,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,`,`,`,`,`,,,,`,s,`,~,`,`,`,~,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,,,`,,,,,,~,,~,,~,,~ +,`,~,~,~,~,`,~,~,~,~,`,,,,,,,,,,,,~,,~,,~,,~ +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,~,~,~,~,`,~,~,~,~,` +,`,`,`,`,`,`,`,`,`,`,` +,~,`,s,`,`,~,`,`,s,`,~ +,,,,~,~,,~,~ +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#place label(services_place_jail) start(18; 18) hidden() + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2) +,`,`,`,,`,`,`,,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,` +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,f(1x2),`,`,`,`,`,f(1x2),,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,f(2x1),`,`,,`,,f(2x1),`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` + +#query label(services_query_jail) start(18; 18) hidden() message(Assign the office to the right of the barracks to your Sheriff/Captain of the Guard to use it as your interrogation room) set up barracks and jail + +,`,`,`,,`,`,`,,`,`,`,,`,`,`,,`,,`,`,` +,`,`,`,,`,`,`,,`,`,`,,`,"r--&j{givename name=""jail3""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail4""}","{booze}{givename name=""booze""}" +,`,`,`,,`,`,`,,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 4}&,t{Down 4}& +,,`,,,,`,,,,`,,,,,,,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,,`,,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,"r--&j{givename name=""jail1""}","{booze}{givename name=""booze""}",`,`,`,`,"r--&j{givename name=""jail2""}","{booze}{givename name=""booze""}",,`,`,"r+&{givename name=""sheriff's office""}",`,` +,`,`,`,`,`,`,`,`,`,`,`,,"{preparedfood}{givename name=""prepared food""}",t{Down 22}{Left 10}&,t{Down 22}{Left 4}&,,`,,"{preparedfood}{givename name=""prepared food""}",t{Left 6}&,t{Left 6}&,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,,,"{givename name=""interrogation""}" +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,`,`,`,`,`,`,`,`,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,`,,`,`,`,`,`,,`,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,`,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,`,,,,,,,,,,,,`,,`,,`,,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,`,`,`,`,`,`,`,`,`,`,` +,,,,`,`,,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` +,`,`,`,`,`,,`,`,`,`,` #notes label(guildhall_help) "Eight 7x7 rooms for guildhalls, temples, libraries, etc." Screenshot: https://drive.google.com/file/d/17jHiCKeZm6FSS-CI4V0r0GJZh09nzcO_ "" Features: -"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Fill with furniture and assign as needed." +"- Big rooms, optionally pre-furnished. Double-thick walls to ensure engravings add value to the ""correct"" side. Declare locations from the pre-created meeting zones as needed." "" Guildhall Walkthrough: 1) Dig out the rooms with /guildhall1. "" -"2) Once the area is dug out, add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." +"2) Once the area is dug out, pre-create the zones and add doors and a few statues with /guildhall2. Run ""quickfort orders"" for /guildhall2." "" -"3) Furnish individual rooms manually, or alternately get default furnishings for a variety of room types by running /guildhall3. Declare appropriate locations as you need guildhalls, libraries, and temples. If you need more rooms, you can dig another /guildhall1 in an unused z-level." +"3) Furnish individual rooms manually, or get default furnishings for a variety of room types by running /guildhall3. If you use the default furnishings, also run ""quickfort orders"" for /guildhall3. Declare appropriate locations from the pre-created zones as you need guildhalls, libraries, and temples. If you'd like a ""no specific diety"" temple declared for the top room and a library declared for the bottom room, run /guildhall4. Both locations will be ""Residents only"" by default, but you can change this in the (l)ocation menu if you want them to attract visitors. If you need more rooms, you can dig another /guildhall1 in an unused z-level." "#dig label(guildhall1) start(15; 15; central stairs) message(Once the area is dug out, continue with /guildhall2.)" @@ -2592,7 +2762,10 @@ Guildhall Walkthrough: ,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d,,,d,d,d,d,d,d,d -"#build label(guildhall2) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint. +#meta label(guildhall2) +doors/guildhall_doors +zones/guildhall_zones +"#build label(guildhall_doors) start(15; 15; central stairs) hidden() message(Remember to enqueue manager orders for this blueprint. Smooth/engrave tiles, furnish rooms, and declare locations as required.) build doors" @@ -2623,6 +2796,36 @@ Smooth/engrave tiles, furnish rooms, and declare locations as required.) build d ,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +#zone label(guildhall_zones) start(15; 15; central stairs) hidden() designate zones + +,m(9x9),,,,,,,,,m(9x9),,,,,,,,,m(9x9) +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,~,,~,,,,,,~ +,m(9x9),,,,,,~,,,,,,`,~,`,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,~,,`,,`,,`,,~,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,`,~,`,,,,`,~,`,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,,~,,~,,,,,`,`,`,`,`,`,` +,,,,,,,~,,,,,,`,~,`,,,,,,~ +,m(9x9),,,,,,~,,,m(9x9),,,~,,~,,,,m(9x9),,~ +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,`,~,~,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` + + "#build label(guildhall3) start(15; 15; central stairs) message(Remember to enqueue manager orders for this blueprint.) furnish 4 guildhalls, 3 temples, and a library" @@ -2653,6 +2856,36 @@ Smooth/engrave tiles, furnish rooms, and declare locations as required.) build d ,,`,`,`,`,s,`,`,,,h,~c,~c,s,~c,~c,h,,,`,`,s,`,`,`,` +"#query label(guildhall4) start(15; 15; central stairs) message(The library and temple are restricted to residents only by default. If you'd like them to attract vistors, please go to the (l)ocation menu and change the restriction.) declare a library and temple" + + +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,^ilat&^l{Up}r^q,,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,,,,,,`,,,,,,`,,`,,,,,,` +,,,,,,,`,,,,,,`,`,`,,,,,,` +,,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,`,,`,,`,,`,,`,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,`,`,`,`,`,,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,,,`,,`,,,,,`,`,`,`,`,`,` +,,,,,,,`,,,,,,`,`,`,,,,,,` +,,,,,,,`,,,,,,`,,`,,,,,,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,^ilal&^l{Up}r^q,,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` +,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,,,`,`,`,`,`,`,` + + #notes label(beds_help) Suites for nobles and apartments for the teeming masses Suites screenshot: https://drive.google.com/file/d/1IBqCf6fF3lw7sHiBE_15Euubysl5AAiS @@ -2676,7 +2909,7 @@ Apartments Walkthrough: "" "3) Once the beds are built, configure the rooms and build the remaining furniture with /apartments3. Run ""quickfort orders"" for /apartments3." "" -"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials. This is handled for you if you're using the onMapLoad_dreamfort.init file included with DFHack." +"4) Once the coffins are all in place, run ""burial -pets"" to set them all to accept burials." "#dig label(suites1) start(18; 18; central ramp) message(Once the area is dug out, run /suites2) noble suites" ,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 diff --git a/data/blueprints/library/pump_stack.csv b/data/blueprints/library/pump_stack.csv index c49a2c310..6bd129308 100644 --- a/data/blueprints/library/pump_stack.csv +++ b/data/blueprints/library/pump_stack.csv @@ -1,23 +1,23 @@ #notes label(help) A pump stack is useful for moving water or magma up through the z-levels. "" -To use these blueprints: +"These blueprints can be used from the quickfort commandline, but are much easier to use with the visual interface. That way you can check the vertical path interactively before you apply. Run gui/quickfort pump_stack" "" -1) Measure how many z-levels the pump stack should span. +"1) Select the ""dig"" blueprint and position the blueprint preview on the bottom level of the future pump stack. It should be on the z-level just above the liquid you want to pump." "" -"2) Position the cursor on the bottom level of the future pump stack. It should be on the z-level just above the liquid you want to pump. Run ""quickfort run library/pump_stack.csv -n /dig2SN"" to see where the suction hole will end up. Replace ""run"" with ""undo"" in the previous command to clean up." +"2) Enable repetitions with the ""R"" hotkey and lock the blueprint in place with the ""L"" hotkey. Move up the z-levels to check that the pump stack has a clear path and doesn't intersect with any open areas (e.g. caverns). Increase the number of repetitions with the ""+"" or ""*"" hotkeys if you need the pump stack to extend further up. Unlock the blueprint and shift it around if you need to, then lock it again to recheck the vertical path." "" -"3) If you need an East-West pump stack, or if you need the staircase in another spot, use the ""--transform"" commandline option to alter the blueprint to your needs. For example: ""quickfort run library/pump_stack.csv -n /dig2SN --transform rotcw,fliph"". If you use a transformation, be sure to use the same option for the remaining commandlines." +"3) If you need to flip the pump stack around to make it fit through the rock layers, enable transformations with the ""t"" hotkey and rotate/flip the blueprint with Ctrl+arrow keys." "" -"4) Once you have everything lined up, run ""quickfort run library/pump_stack.csv -n /dig2SN --repeat up,20"" to designate the entire pump stack for digging. Replace that last ""20"" with the height of your pump stack divided by 2 (since each repetition of /dig2SN is two z-levels high). If the height ends up being one too many at the top, manually undesignate the top level." +"4) Once you have everything lined up, hit Enter to apply. If the height ends up being one too many at the top, manually undesignate the top level." "" -"5) Since you do not need to transmit power down below the lowest level, replace the channel designation on the middle tile of the bottom-most pump stack level with a regular dig designation. Likewise, replace the Up/Down staircase designation on the lowest level with an Up staircase designation." +"5) Since you do not need to transmit power down below the lowest level, replace the channel designation on the middle tile of the bottom-most pump stack level with a regular dig designation. Likewise, replace the Up/Down staircase designation on the lowest level with an Up staircase designation. Otherwise you might get magma critters climbing up through your access stairway!" "" -"6) After the stack is dug out, prepare for building by setting the buildingplan plugin material filters for screw pumps (b-M-s-M). If you are planning to move magma, be sure to select magma-safe materials." +"6) After the stack is dug out, prepare for building by setting the buildingplan plugin material filters for screw pumps (b-M-s-M). If you are planning to move magma, be sure to select magma-safe materials (like green glass) for all three components of the screw pump." "" -"7) Finally, position the cursor back on the access stairs on the lowest level and run ""quickfort run library/pump_stack.csv -n /build2SN --repeat up,20"" (with 20 replaced with your desired repetition count and with your --transform command, if any)." +"7) Finally, position the cursor back on the access stairs on the lowest level and run the ""build"" blueprint with the same repetition and transformation settings that you used for the ""dig"" blueprint. As you manufacture the materials you need to construct the screw pumps, your dwarves will build the pump stack from the bottom up." "" -"Sometimes, a screw pump will spontaneously deconstruct while you are building the stack. This will reduce the efficiency of the stack a little, but it's nothing to worry about. Just re-run the /build2SN blueprint over the entire stack to ""fix up"" any broken pieces. The blueprint will harmlessly skip over any correctly-built screw pumps." +"Sometimes, a screw pump will spontaneously deconstruct while you are building the stack. This will reduce the efficiency of the stack a little, but it's nothing to worry about. Just re-run the ""build"" blueprint over the entire stack to ""fix up"" any broken pieces. The blueprint will harmlessly skip over any correctly-built screw pumps." "" See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.php/Screw_pump#Pump_stack #dig label(digSN) start(2;4;on access stairs) hidden() for a pump from south level @@ -34,7 +34,7 @@ See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.p ,i,,h ,,,d -#meta label(dig2SN) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south +#meta label(dig) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south /digSN #< /digNS @@ -52,7 +52,7 @@ See the wiki for more info on pump stacks: https://dwarffortresswiki.org/index.p ,`,,Msu ,,,` -#meta label(build2SN) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south +#meta label(build) start(at the bottom level on the access stairs) 2 levels of pump stack - bottom level pumps from the south /buildSN #< /buildNS diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-2-build.csv b/data/blueprints/library/test/ecosystem/golden/gui_quantum-2-build.csv new file mode 100644 index 000000000..102c98f4a --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/gui_quantum-2-build.csv @@ -0,0 +1,5 @@ +#build label(build) + + + +,,CSdd diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-3-place.csv b/data/blueprints/library/test/ecosystem/golden/gui_quantum-3-place.csv new file mode 100644 index 000000000..0cb7a5b6b --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/gui_quantum-3-place.csv @@ -0,0 +1,6 @@ +#place label(place) +s(5x3) + + + +,,afunswebhlzSgpd diff --git a/data/blueprints/library/test/ecosystem/golden/gui_quantum-4-query.csv b/data/blueprints/library/test/ecosystem/golden/gui_quantum-4-query.csv new file mode 100644 index 000000000..811f7b3b0 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/gui_quantum-4-query.csv @@ -0,0 +1,6 @@ +#query label(query) + + + +,,"{givename name=""foo dumper""}" +,,"{givename name=""foo""}" diff --git a/data/blueprints/library/test/ecosystem/golden/meta-dig.csv b/data/blueprints/library/test/ecosystem/golden/meta-1-dig.csv similarity index 94% rename from data/blueprints/library/test/ecosystem/golden/meta-dig.csv rename to data/blueprints/library/test/ecosystem/golden/meta-1-dig.csv index 827ccaf73..456d2ba92 100644 --- a/data/blueprints/library/test/ecosystem/golden/meta-dig.csv +++ b/data/blueprints/library/test/ecosystem/golden/meta-1-dig.csv @@ -1,4 +1,4 @@ -#dig label(dig) +#dig label(dig) start(3;3) d,d,,,d d,,j,,d d,u,d,u,d diff --git a/data/blueprints/library/test/ecosystem/golden/tracks-carve.csv b/data/blueprints/library/test/ecosystem/golden/tracks-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/golden/tracks-carve.csv rename to data/blueprints/library/test/ecosystem/golden/tracks-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/golden/transform-dig.csv b/data/blueprints/library/test/ecosystem/golden/transform-1-dig.csv similarity index 97% rename from data/blueprints/library/test/ecosystem/golden/transform-dig.csv rename to data/blueprints/library/test/ecosystem/golden/transform-1-dig.csv index 3772f2d5f..b26339d8a 100644 --- a/data/blueprints/library/test/ecosystem/golden/transform-dig.csv +++ b/data/blueprints/library/test/ecosystem/golden/transform-1-dig.csv @@ -1,4 +1,4 @@ -#dig label(dig) +#dig label(dig) start(14;14) ,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 diff --git a/data/blueprints/library/test/ecosystem/golden/transform-2-construct.csv b/data/blueprints/library/test/ecosystem/golden/transform-2-construct.csv new file mode 100644 index 000000000..9620c96c1 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/transform-2-construct.csv @@ -0,0 +1,28 @@ +#build label(construct) start(14;14) +,trackNS,trackE,,trackW,trackS,trackN,,,,,,,,,,,,,,trackN,trackS,trackE,,trackW,trackNS +trackEW,,trackSE,,trackSW,trackNE,trackNW,,,,,,,,,,,,,,trackNE,trackNW,trackSE,,trackSW,,trackEW +trackS,trackSE,,,trackNSE,trackNSW,trackEW,,,,,,,,,,,,,,trackEW,trackNSE,trackNSW,,,trackSW,trackS + +trackN,trackNE,trackSEW,,,trackSEW,trackNEW,,,,,,,,,,,,,,trackNEW,trackSEW,,,trackSEW,trackNW,trackN +trackE,trackSW,trackNEW,,trackNSE,,trackNSEW,,,,,,,,,,,,,,trackNSEW,,trackNSW,,trackNEW,trackSE,trackW +trackW,trackNW,trackNS,,trackNSW,trackNSEW,,,,,,,,,,,,,,,,trackNSEW,trackNSE,,trackNS,trackNE,trackE + + +,,,,,,,,,,trackrampNW,trackrampNS,trackrampN,,trackrampN,trackrampNS,trackrampNE +,,,,,,,,,trackrampNW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampNE +,,,,,,,,,trackrampEW,trackrampSEW,,trackrampNSEW,,trackrampNSEW,,trackrampSEW,trackrampEW +,,,,,,,,,trackrampW,trackrampNEW,trackrampNSEW,,,,trackrampNSEW,trackrampNEW,trackrampE + +,,,,,,,,,trackrampW,trackrampSEW,trackrampNSEW,,,,trackrampNSEW,trackrampSEW,trackrampE +,,,,,,,,,trackrampEW,trackrampNEW,,trackrampNSEW,,trackrampNSEW,,trackrampNEW,trackrampEW +,,,,,,,,,trackrampSW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampSE +,,,,,,,,,,trackrampSW,trackrampNS,trackrampS,,trackrampS,trackrampNS,trackrampSE + + +trackW,trackSW,trackNS,,trackNSW,trackNSEW,,,,,,,,,,,,,,,,trackNSEW,trackNSE,,trackNS,trackSE,trackE +trackE,trackNW,trackSEW,,trackNSE,,trackNSEW,,,,,,,,,,,,,,trackNSEW,,trackNSW,,trackSEW,trackNE,trackW +trackS,trackSE,trackNEW,,,trackNEW,trackSEW,,,,,,,,,,,,,,trackSEW,trackNEW,,,trackNEW,trackSW,trackS + +trackN,trackNE,,,trackNSE,trackNSW,trackEW,,,,,,,,,,,,,,trackEW,trackNSE,trackNSW,,,trackNW,trackN +trackEW,,trackNE,,trackNW,trackSE,trackSW,,,,,,,,,,,,,,trackSE,trackSW,trackNE,,trackNW,,trackEW +,trackNS,trackE,,trackW,trackN,trackS,,,,,,,,,,,,,,trackS,trackN,trackE,,trackW,trackNS diff --git a/data/blueprints/library/test/ecosystem/golden/transform-3-build.csv b/data/blueprints/library/test/ecosystem/golden/transform-3-build.csv new file mode 100644 index 000000000..74434544a --- /dev/null +++ b/data/blueprints/library/test/ecosystem/golden/transform-3-build.csv @@ -0,0 +1,28 @@ +#build label(build) start(14;14) +,~,~,,~,~,~,,gs(1x2),ga(2x1),,gx(1x2),gw(1x2),,gw(1x2),gx(1x2),gd(2x1),,gs(1x2),,~,~,~,,~,~ +~,,~,,~,~,~,,,gd(2x1),,,,,,,ga(2x1),,,,~,~,~,,~,,~ +~,~,,,~,~,~,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,~,~,~,,,~,~ +,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa +~,~,~,,,~,~,,CSa,,Mrss(1x2),Mw,,,,Mw,Mrss(1x2),,CSa,,~,~,,,~,~,~ +~,~,~,,~,,~,,,Msm,,,Mhs(1x2),,Mhs(1x2),,,Msm,,,~,,~,,~,~,~ +~,~,~,,~,~,,,Msu,,Mws,,,,,,Mws,,Msu,,,~,~,,~,~,~ +,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws +gs(2x1),,Mrqq(1x2),CSddaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSddaaaa,Mrqq(1x2),gs(2x1) +gw(1x2),gx(1x2),,,,Msk,,Mw,,,~,~,~,,~,~,~,,,Mw,,,Msh,,,gx(1x2),gw(1x2) +,,,Msm,Mrs(2x1),,Mw,,,~,,~,~,,~,~,,~,,,Mw,Mrsss(2x1),,Msm +gd(2x1),,Msu,,Mws,,,Mhs(1x2),,~,~,,~,,~,,~,~,,Mhs(1x2),,,Mws,,Msu,ga(2x1) +ga(2x1),,,Mws,,Mh(2x1),,,,~,~,~,,,,~,~,~,,,Mh(2x1),,,Mws,,gd(2x1) + +ga(2x1),,,Mws,,Mh(2x1),,Mhs(1x2),,~,~,~,,,,~,~,~,,Mhs(1x2),Mh(2x1),,,Mws,,gd(2x1) +gd(2x1),,,,Mws,,,,,~,~,,~,,~,,~,~,,,,,Mws,,,ga(2x1) +gx(1x2),gw(1x2),Msm,,Mrs(2x1),,Mw,,,~,,~,~,,~,~,,~,,,Mw,Mrsss(2x1),,,Msm,gw(1x2),gx(1x2) +,,Mrssqq(1x2),Msu,,Msk,,Mw,,,~,~,~,,~,~,~,,,Mw,,,Msh,Msu,Mrssqq(1x2) +gs(2x1),,,CSdaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSdaaaa,,gs(2x1) +,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws +~,~,~,,~,~,,,,,Mws,,Mhs(1x2),,Mhs(1x2),,Mws,,,,,~,~,,~,~,~ +~,~,~,,~,,~,,Msm,,Mr(1x2),,,,,,Mr(1x2),,Msm,,~,,~,,~,~,~ +~,~,~,,,~,~,,CSa,Msu,,Mw,,,,Mw,,Msu,CSa,,~,~,,,~,~,~ +,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa +~,~,,,~,~,~,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,~,~,~,,,~,~ +~,,~,,~,~,~,,gs(1x2),gd(2x1),,gw(1x2),gx(1x2),,gx(1x2),gw(1x2),ga(2x1),,gs(1x2),,~,~,~,,~,,~ +,~,~,,~,~,~,,,ga(2x1),,,,,,,gd(2x1),,,,~,~,~,,~,~ diff --git a/data/blueprints/library/test/ecosystem/golden/transform-build.csv b/data/blueprints/library/test/ecosystem/golden/transform-build.csv deleted file mode 100644 index feb7a3cb5..000000000 --- a/data/blueprints/library/test/ecosystem/golden/transform-build.csv +++ /dev/null @@ -1,28 +0,0 @@ -#build label(build) -,trackNS,trackE,,trackW,trackS,trackN,,gs(1x2),ga(2x1),,gx(1x2),gw(1x2),,gw(1x2),gx(1x2),gd(2x1),,gs(1x2),,trackN,trackS,trackE,,trackW,trackNS -trackEW,,trackSE,,trackSW,trackNE,trackNW,,,gd(2x1),,,,,,,ga(2x1),,,,trackNE,trackNW,trackSE,,trackSW,,trackEW -trackS,trackSE,,,trackNSE,trackNSW,trackEW,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,trackEW,trackNSE,trackNSW,,,trackSW,trackS -,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa -trackN,trackNE,trackSEW,,,trackSEW,trackNEW,,CSa,,Mrss(1x2),Mw,,,,Mw,Mrss(1x2),,CSa,,trackNEW,trackSEW,,,trackSEW,trackNW,trackN -trackE,trackSW,trackNEW,,trackNSE,,trackNSEW,,,Msm,,,Mhs(1x2),,Mhs(1x2),,,Msm,,,trackNSEW,,trackNSW,,trackNEW,trackSE,trackW -trackW,trackNW,trackNS,,trackNSW,trackNSEW,,,Msu,,Mws,,,,,,Mws,,Msu,,,trackNSEW,trackNSE,,trackNS,trackNE,trackE -,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws -gs(2x1),,Mrqq(1x2),CSddaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSddaaaa,Mrqq(1x2),gs(2x1) -gw(1x2),gx(1x2),,,,Msk,,Mw,,,trackrampNW,trackrampNS,trackrampN,,trackrampN,trackrampNS,trackrampNE,,,Mw,,,Msh,,,gx(1x2),gw(1x2) -,,,Msm,Mrs(2x1),,Mw,,,trackrampNW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampNE,,,Mw,Mrsss(2x1),,Msm -gd(2x1),,Msu,,Mws,,,Mhs(1x2),,trackrampEW,trackrampSEW,,trackrampNSEW,,trackrampNSEW,,trackrampSEW,trackrampEW,,Mhs(1x2),,,Mws,,Msu,ga(2x1) -ga(2x1),,,Mws,,Mh(2x1),,,,trackrampW,trackrampNEW,trackrampNSEW,,,,trackrampNSEW,trackrampNEW,trackrampE,,,Mh(2x1),,,Mws,,gd(2x1) - -ga(2x1),,,Mws,,Mh(2x1),,Mhs(1x2),,trackrampW,trackrampSEW,trackrampNSEW,,,,trackrampNSEW,trackrampSEW,trackrampE,,Mhs(1x2),Mh(2x1),,,Mws,,gd(2x1) -gd(2x1),,,,Mws,,,,,trackrampEW,trackrampNEW,,trackrampNSEW,,trackrampNSEW,,trackrampNEW,trackrampEW,,,,,Mws,,,ga(2x1) -gx(1x2),gw(1x2),Msm,,Mrs(2x1),,Mw,,,trackrampSW,,trackrampNSE,trackrampNSW,,trackrampNSE,trackrampNSW,,trackrampSE,,,Mw,Mrsss(2x1),,,Msm,gw(1x2),gx(1x2) -,,Mrssqq(1x2),Msu,,Msk,,Mw,,,trackrampSW,trackrampNS,trackrampS,,trackrampS,trackrampNS,trackrampSE,,,Mw,,,Msh,Msu,Mrssqq(1x2) -gs(2x1),,,CSdaaaa,CSa,,Msh,,,,,,,,,,,,,,,Msk,CSa,CSdaaaa,,gs(2x1) -,,,,,,,,,Mws,,Mh(2x1),,,Mh(2x1),,,Mws -trackW,trackSW,trackNS,,trackNSW,trackNSEW,,,,,Mws,,Mhs(1x2),,Mhs(1x2),,Mws,,,,,trackNSEW,trackNSE,,trackNS,trackSE,trackE -trackE,trackNW,trackSEW,,trackNSE,,trackNSEW,,Msm,,Mr(1x2),,,,,,Mr(1x2),,Msm,,trackNSEW,,trackNSW,,trackSEW,trackNE,trackW -trackS,trackSE,trackNEW,,,trackNEW,trackSEW,,CSa,Msu,,Mw,,,,Mw,,Msu,CSa,,trackSEW,trackNEW,,,trackNEW,trackSW,trackS -,,,,,,,,CSdddaaaa,,Msk,,Mw,,Mw,,,Msh,CSddddaaaa -trackN,trackNE,,,trackNSE,trackNSW,trackEW,,Mrsssqq(2x1),,,Msh,,,,,Msk,Mrsqq(2x1),,,trackEW,trackNSE,trackNSW,,,trackNW,trackN -trackEW,,trackNE,,trackNW,trackSE,trackSW,,gs(1x2),gd(2x1),,gw(1x2),gx(1x2),,gx(1x2),gw(1x2),ga(2x1),,gs(1x2),,trackSE,trackSW,trackNE,,trackNW,,trackEW -,trackNS,trackE,,trackW,trackN,trackS,,,ga(2x1),,,,,,,gd(2x1),,,,trackS,trackN,trackE,,trackW,trackNS diff --git a/data/blueprints/library/test/ecosystem/in/basic-dig.csv b/data/blueprints/library/test/ecosystem/in/basic-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-dig.csv rename to data/blueprints/library/test/ecosystem/in/basic-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-carve.csv b/data/blueprints/library/test/ecosystem/in/basic-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-carve.csv rename to data/blueprints/library/test/ecosystem/in/basic-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-build.csv b/data/blueprints/library/test/ecosystem/in/basic-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-build.csv rename to data/blueprints/library/test/ecosystem/in/basic-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-place.csv b/data/blueprints/library/test/ecosystem/in/basic-4-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-place.csv rename to data/blueprints/library/test/ecosystem/in/basic-4-place.csv diff --git a/data/blueprints/library/test/ecosystem/in/basic-zone.csv b/data/blueprints/library/test/ecosystem/in/basic-5-zone.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/basic-zone.csv rename to data/blueprints/library/test/ecosystem/in/basic-5-zone.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-dig.csv b/data/blueprints/library/test/ecosystem/in/buildings-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/buildings-dig.csv rename to data/blueprints/library/test/ecosystem/in/buildings-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/buildings-2-construct.csv b/data/blueprints/library/test/ecosystem/in/buildings-2-construct.csv new file mode 100644 index 000000000..e7e1d12bb --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/buildings-2-construct.csv @@ -0,0 +1,21 @@ +#build label(construct) + + + + + + + + + + + + + +Cw +Cf +Cr +Cu +Cd +Cx +CF diff --git a/data/blueprints/library/test/ecosystem/in/buildings-3-build.csv b/data/blueprints/library/test/ecosystem/in/buildings-3-build.csv new file mode 100644 index 000000000..f48457c0a --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/buildings-3-build.csv @@ -0,0 +1,33 @@ +#build label(build) +a,Mg,,CS +b,Mh(1x1),S,CSa,,,,,,Mw,,,wm,,,wp +c,Mhs(1x1),m,CSaa,,,,,,,,,,,,,,,,,D +n,Mv,v,CSaaa,,Msu +,Mr(1x1),j,CSaaaa,,,,,,Mws,,,wu,,,ew +d,Mrq(1x1),A,CSd,,,Msk +,Mrqq(1x1),R,CSda +l,Mrqqq(1x1),N,CSdaa,,Msm,,,,we,,,wn,,,es,,,,,k +x,Mrqqqq(1x1),~h,CSdaaa +H,Mrs(1x1),~a,CSdaaaa,,,Msh +W,Mrsq(1x1),~c,CSdd,,,,,,wq,,,wr,,,el +G,Mrsqq(1x1),F,CSdda +B,Mrsqqq(1x1),o(1x1),CSddaa,,,,,,,,,,,,,,,,,ws +~,~b,Mrsqqqq(1x1),CSddaaa,,,,,,wM,,,wt,,,eg +~,f,Mrss(1x1),CSddaaaa +~,h,Mrssq(1x1),CSddd +~,r,Mrssqq(1x1),CSddda,,,,,,wo,,,wl,,,ea,,,,gx(1x2),gx(1x2) +~,s,Mrssqqq(1x1),CSdddaa +~,~s,Mrssqqqq(1x1),CSdddaaa,,,,,,,,,,,,,,gd(2x1),,gs(2x1),,ga(2x1) +~,t,Mrsss(1x1),CSdddaaaa,,,,,,wk,,,ww,,,ek,,gd(2x1),,gs(2x1),,ga(2x1) +gs(1x1),Mrsssq(1x1),,CSdddd,,,,,,,,,,,,,,,,gw(1x2),gw(1x2) +ga(1x1),Mrsssqq(1x1),,CSdddda +gd(1x1),Mrsssqqq(1x1),,CSddddaa,,,,,,wb,,,wz,,,en +gw(1x1),Mrsssqqqq(1x1),,CSddddaaa,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1),,Mhs(1x2),Mhs(1x2) +gx(1x1),,,CSddddaaaa,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1) +,,,Ts,,,,,,wc,,,wh,,,ib,,Mr(1x2),Mr(1x2),Mrs(2x1),,Mhs(1x2),Mhs(1x2) +y,,,Tw,,,,,,,,,,,,,,,,Mrs(2x1) +Y,,,Tl,,,,,,,,,,,,,,Mr(1x2),Mr(1x2),Mrsq(2x1),,Mrsq(2x1) +,,,Tp,,,,,,wf,,,wy,,,ic,,,,Mrsssqqqq(2x1),,Mrsssqqqq(2x1) +,,,Tc +,,,TS +,,,,,,,,,,wv,,,wd,,,wj,,,wS diff --git a/data/blueprints/library/test/ecosystem/in/buildings-build.csv b/data/blueprints/library/test/ecosystem/in/buildings-build.csv deleted file mode 100644 index 89e066e80..000000000 --- a/data/blueprints/library/test/ecosystem/in/buildings-build.csv +++ /dev/null @@ -1,33 +0,0 @@ -#build label(build) -a,Mg,,CS,trackN -b,Mh(1x1),S,CSa,trackS,,,,,,Mw,,,wm,,,wp -c,Mhs(1x1),m,CSaa,trackE,,,,,,,,,,,,,,,,,D -n,Mv,v,CSaaa,trackW,,Msu -,Mr(1x1),j,CSaaaa,trackNS,,,,,,Mws,,,wu,,,ew -d,Mrq(1x1),A,CSd,trackNE,,,Msk -,Mrqq(1x1),R,CSda,trackNW -l,Mrqqq(1x1),N,CSdaa,trackSE,,Msm,,,,we,,,wn,,,es,,,,,k -x,Mrqqqq(1x1),~h,CSdaaa,trackSW -H,Mrs(1x1),~a,CSdaaaa,trackEW,,,Msh -W,Mrsq(1x1),~c,CSdd,trackNSE,,,,,,wq,,,wr,,,el -G,Mrsqq(1x1),F,CSdda,trackNSW -B,Mrsqqq(1x1),o(1x1),CSddaa,trackNEW,,,,,,,,,,,,,,,,,ws -~b,Mrsqqqq(1x1),Cw,CSddaaa,trackSEW,,,,,,wM,,,wt,,,eg -f,Mrss(1x1),Cf,CSddaaaa,trackNSEW -h,Mrssq(1x1),Cr,CSddd,trackrampN -r,Mrssqq(1x1),Cu,CSddda,trackrampS,,,,,,wo,,,wl,,,ea,,,,gx(1x2),gx(1x2) -s,Mrssqqq(1x1),Cd,CSdddaa,trackrampE -~s,Mrssqqqq(1x1),Cx,CSdddaaa,trackrampW,,,,,,,,,,,,,,gd(2x1),,gs(2x1),,ga(2x1) -t,Mrsss(1x1),CF,CSdddaaaa,trackrampNS,,,,,,wk,,,ww,,,ek,,gd(2x1),,gs(2x1),,ga(2x1) -gs(1x1),Mrsssq(1x1),,CSdddd,trackrampNE,,,,,,,,,,,,,,,,gw(1x2),gw(1x2) -ga(1x1),Mrsssqq(1x1),,CSdddda,trackrampNW -gd(1x1),Mrsssqqq(1x1),,CSddddaa,trackrampSE,,,,,,wb,,,wz,,,en -gw(1x1),Mrsssqqqq(1x1),,CSddddaaa,trackrampSW,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1),,Mhs(1x2),Mhs(1x2) -gx(1x1),,,CSddddaaaa,trackrampEW,,,,,,,,,,,,,,Mh(2x1),,Mh(2x1) -,,,Ts,trackrampNSE,,,,,,wc,,,wh,,,ib,,Mr(1x2),Mr(1x2),Mrs(2x1),,Mhs(1x2),Mhs(1x2) -y,,,Tw,trackrampNSW,,,,,,,,,,,,,,,,Mrs(2x1) -Y,,,Tl,trackrampNEW,,,,,,,,,,,,,,Mr(1x2),Mr(1x2),Mrsq(2x1),,Mrsq(2x1) -,,,Tp,trackrampSEW,,,,,,wf,,,wy,,,ic,,,,Mrsssqqqq(2x1),,Mrsssqqqq(2x1) -,,,Tc,trackrampNSEW -,,,TS -,,,,,,,,,,wv,,,wd,,,wj,,,wS diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-dig.csv b/data/blueprints/library/test/ecosystem/in/fortifications-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-dig.csv rename to data/blueprints/library/test/ecosystem/in/fortifications-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-smooth.csv b/data/blueprints/library/test/ecosystem/in/fortifications-2-smooth.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-smooth.csv rename to data/blueprints/library/test/ecosystem/in/fortifications-2-smooth.csv diff --git a/data/blueprints/library/test/ecosystem/in/fortifications-carve.csv b/data/blueprints/library/test/ecosystem/in/fortifications-3-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/fortifications-carve.csv rename to data/blueprints/library/test/ecosystem/in/fortifications-3-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-2-build.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-2-build.csv new file mode 100644 index 000000000..ebbc0207d --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-2-build.csv @@ -0,0 +1 @@ +#build diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-3-place.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-3-place.csv new file mode 100644 index 000000000..ec9f6e42d --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-3-place.csv @@ -0,0 +1,2 @@ +#place label(place) +s(5x3) diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-4-query.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-4-query.csv new file mode 100644 index 000000000..eb287fc8a --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-4-query.csv @@ -0,0 +1 @@ +#query diff --git a/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv new file mode 100644 index 000000000..7ba7b2ecb --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/gui_quantum-spec.csv @@ -0,0 +1,5 @@ +#notes +description=integration test for the gui/quantum script +width=5 +height=5 +extra_fn=gui_quantum diff --git a/data/blueprints/library/test/ecosystem/in/meta-dig.csv b/data/blueprints/library/test/ecosystem/in/meta-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/meta-dig.csv rename to data/blueprints/library/test/ecosystem/in/meta-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/stockpiles-place.csv b/data/blueprints/library/test/ecosystem/in/stockpiles-2-place.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/stockpiles-place.csv rename to data/blueprints/library/test/ecosystem/in/stockpiles-2-place.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-dig.csv b/data/blueprints/library/test/ecosystem/in/tracks-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-dig.csv rename to data/blueprints/library/test/ecosystem/in/tracks-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-carve.csv b/data/blueprints/library/test/ecosystem/in/tracks-2-carve.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-carve.csv rename to data/blueprints/library/test/ecosystem/in/tracks-2-carve.csv diff --git a/data/blueprints/library/test/ecosystem/in/tracks-build.csv b/data/blueprints/library/test/ecosystem/in/tracks-3-build.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/tracks-build.csv rename to data/blueprints/library/test/ecosystem/in/tracks-3-build.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-dig.csv b/data/blueprints/library/test/ecosystem/in/transform-1-dig.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/transform-dig.csv rename to data/blueprints/library/test/ecosystem/in/transform-1-dig.csv diff --git a/data/blueprints/library/test/ecosystem/in/transform-build.csv b/data/blueprints/library/test/ecosystem/in/transform-2-construct.csv similarity index 71% rename from data/blueprints/library/test/ecosystem/in/transform-build.csv rename to data/blueprints/library/test/ecosystem/in/transform-2-construct.csv index fdc2e18ce..95717afa8 100644 --- a/data/blueprints/library/test/ecosystem/in/transform-build.csv +++ b/data/blueprints/library/test/ecosystem/in/transform-2-construct.csv @@ -1,12 +1,3 @@ -#build label(big) hidden() -gw(1x2),gx(1x2),gd(2x1),,gs(1x2) -,,ga(2x1) -,,Msk,Mrsqq(2x1) -Mw,,,Msh,CSddddaaaa -,Mw,Mrss(1x2),,CSa -Mhs(1x2),,,Msm -,,Mws,,Msu -Mh(2x1),,,Mws #build label(outer) hidden() trackN,trackS,trackE,,trackW,trackNS trackNE,trackNW,trackSE,,trackSW @@ -19,10 +10,9 @@ trackrampN,trackrampNS,trackrampNE trackrampNSE,trackrampNSW trackrampNSEW #meta label(chunk) hidden() -/big shift(1 -13) /outer shift(7 -13) /inner shift(1 -4) -#meta label(build) +#meta label(construct) /chunk /chunk transform(cw) /chunk transform(cw cw) diff --git a/data/blueprints/library/test/ecosystem/in/transform-3-build.csv b/data/blueprints/library/test/ecosystem/in/transform-3-build.csv new file mode 100644 index 000000000..7907220e7 --- /dev/null +++ b/data/blueprints/library/test/ecosystem/in/transform-3-build.csv @@ -0,0 +1,20 @@ +#build label(big) hidden() +gw(1x2),gx(1x2),gd(2x1),,gs(1x2) +,,ga(2x1) +,,Msk,Mrsqq(2x1) +Mw,,,Msh,CSddddaaaa +,Mw,Mrss(1x2),,CSa +Mhs(1x2),,,Msm +,,Mws,,Msu +Mh(2x1),,,Mws +#meta label(chunk) hidden() +/big shift(1 -13) +#meta label(build) +/chunk +/chunk transform(cw) +/chunk transform(cw cw) +/chunk transform(ccw) +/chunk transform(fliph) +/chunk transform(flipv) +/chunk transform(cw flipv) +/chunk transform(ccw flipv) diff --git a/data/blueprints/library/test/ecosystem/in/zones-zone.csv b/data/blueprints/library/test/ecosystem/in/zones-2-zone.csv similarity index 100% rename from data/blueprints/library/test/ecosystem/in/zones-zone.csv rename to data/blueprints/library/test/ecosystem/in/zones-2-zone.csv diff --git a/data/examples/init/onMapLoad_dreamfort.init b/data/examples/init/onMapLoad_dreamfort.init index 8e9c0134d..fe2b1d54e 100644 --- a/data/examples/init/onMapLoad_dreamfort.init +++ b/data/examples/init/onMapLoad_dreamfort.init @@ -1,31 +1,34 @@ # This dfhack config file automates common tasks for your forts. # It was written for the Dreamfort set of quickfort blueprints, but the -# configuration here is useful for any fort! Feed free to edit or override +# configuration here is useful for any fort! Copy this file to your +# dfhack-config/init directory to use. Feed free to edit or override # to your liking. -# Disallow cooking of otherwise useful item types -on-new-fortress ban-cooking tallow; ban-cooking honey; ban-cooking oil; ban-cooking seeds; ban-cooking brew; ban-cooking fruit; ban-cooking mill; ban-cooking thread; ban-cooking milk; ban-cooking booze - # Uncomment this next line if you want buildingplan (and quickfort) to use only -# blocks for construction. If you do uncomment, be sure to bring some blocks -# with you for starting workshops! +# blocks (not bars or logs) for constructions and workshops. If you do +# uncomment, be sure to bring some blocks with you for starting workshops! #on-new-fortress buildingplan set boulders false; buildingplan set logs false +# Disable cooking of useful item types when you start a new fortress. +on-new-fortress ban-cooking tallow; ban-cooking honey; ban-cooking oil; ban-cooking seeds; ban-cooking brew; ban-cooking fruit; ban-cooking mill; ban-cooking thread; ban-cooking milk; ban-cooking booze + +# Show a warning dialog when units are starving repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] -repeat -name burial -time 7 -timeUnits days -command [ burial -pets ] + +# Force dwarves to drop tattered clothing instead of clinging to the scraps repeat -name cleanowned -time 1 -timeUnits months -command [ cleanowned X ] -repeat -name clean -time 1 -timeUnits months -command [ clean all ] -repeat -name feeding-timers -time 1 -timeUnits months -command [ fix/feeding-timers ] -repeat -name stuckdoors -time 1 -timeUnits months -command [ fix/stuckdoors ] + +# Automatically enqueue orders to shear and milk animals repeat -name autoShearCreature -time 14 -timeUnits days -command [ workorder ShearCreature ] repeat -name autoMilkCreature -time 14 -timeUnits days -command [ workorder "{\"job\":\"MilkCreature\",\"item_conditions\":[{\"condition\":\"AtLeast\",\"value\":2,\"flags\":[\"empty\"],\"item_type\":\"BUCKET\"}]}" ] + +# Fulfill high-volume orders before slower once-daily orders repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] -tweak fast-heat 100 -tweak do-job-now -fix/blood-del enable +# Don't let caravans bring barrels of blood and other useless liquids +fix/blood-del -# manages crop assignment for farm plots +# Manages crop assignment for farm plots enable autofarm autofarm default 30 autofarm threshold 150 GRASS_TAIL_PIG @@ -35,26 +38,18 @@ enable automelt # creates manager orders to produce replacements for worn clothing enable tailor -tailor enable -# auto-assigns nesting birds to nestbox zones -enable zone nestboxes -autonestbox start +# auto-assigns nesting birds to nestbox zones and protects fertile eggs from +# being cooked/eaten +enable zone autonestbox nestboxes # manages seed stocks enable seedwatch seedwatch all 30 -seedwatch start # ensures important tasks get assigned to workers. # otherwise these job types can get ignored in busy forts. -prioritize -a StoreItemInVehicle StoreItemInBag StoreItemInBarrel PullLever -prioritize -a StoreItemInLocation StoreItemInHospital -prioritize -a DestroyBuilding RemoveConstruction RecoverWounded DumpItem -prioritize -a CleanSelf SlaughterAnimal PrepareRawFish ExtractFromRawFish -prioritize -a TradeAtDepot BringItemToDepot CleanTrap ManageWorkOrders -prioritize -a --haul-labor=Food,Body StoreItemInStockpile -prioritize -a --reaction-name=TAN_A_HIDE CustomReaction +prioritize -aq defaults # autobutcher settings are saved in the savegame, so we only need to set them once. # this way, any custom settings you set during gameplay are not overwritten @@ -62,6 +57,7 @@ prioritize -a --reaction-name=TAN_A_HIDE CustomReaction # feel free to change this to "target 0 0 0 0" if you don't expect to want to raise # any animals not listed here -- you can always change it anytime during the game # later if you change your mind. +on-new-fortress enable autobutcher on-new-fortress autobutcher target 2 2 2 2 new # dogs and cats. You should raise the limits for dogs if you will be training them # for hunting or war. @@ -77,7 +73,7 @@ on-new-fortress autobutcher target 50 50 14 2 BIRD_GOOSE on-new-fortress autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA # pigs give milk and meat and are zero-maintenance. on-new-fortress autobutcher target 5 5 6 2 PIG -# generally unprofitable animals +# immediately butcher all unprofitable animals on-new-fortress autobutcher target 0 0 0 0 HORSE YAK DONKEY WATER_BUFFALO GOAT CAVY BIRD_DUCK BIRD_GUINEAFOWL -# start it up! -on-new-fortress autobutcher start; autobutcher watch all; autobutcher autowatch +# watch for new animals +on-new-fortress autobutcher autowatch diff --git a/data/init/dfhack.default.init b/data/init/dfhack.default.init new file mode 100644 index 000000000..78dc70450 --- /dev/null +++ b/data/init/dfhack.default.init @@ -0,0 +1,8 @@ +# Default DFHack commands to run on program init + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +script hack/init/dfhack.keybindings.init +script hack/init/dfhack.tools.init diff --git a/dfhack.init-example b/data/init/dfhack.keybindings.init similarity index 59% rename from dfhack.init-example rename to data/init/dfhack.keybindings.init index 702ce5276..fd02635e2 100644 --- a/dfhack.init-example +++ b/data/init/dfhack.keybindings.init @@ -1,11 +1,28 @@ -############################## -# Generic dwarfmode bindings # -############################## +# Default DFHack keybindings + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +################### +# Global bindings # +################### + +# the GUI command launcher (two bindings since some keyboards don't have `) +keybinding add ` gui/launcher +keybinding add Ctrl-Shift-D gui/launcher # show all current key bindings keybinding add Ctrl-F1 hotkeys keybinding add Alt-F1 hotkeys +# on-screen keyboard +keybinding add Ctrl-Shift-K gui/cp437-table + +############################## +# Generic dwarfmode bindings # +############################## + # toggle the display of water level as 1-7 tiles keybinding add Ctrl-W twaterlvl @@ -34,6 +51,10 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort) +keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort +keybinding add Alt-F@dwarfmode gui/quickfort + # gui/rename script - rename units and buildings keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-T "gui/rename unit-profession" @@ -58,12 +79,21 @@ keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity # re-check manager orders keybinding add Alt-R@jobmanagement/Main workorder-recheck +# workorder detail configuration +keybinding add D@workquota_details gui/workorder-details + # view combat reports for the selected unit/corpse/spatter keybinding add Ctrl-Shift-R view-unit-reports # view extra unit information keybinding add Alt-I@dwarfmode/ViewUnits|unitlist gui/unit-info-viewer +# set workorder item details (on workorder details screen press D again) +keybinding add D@workquota_details gui/workorder-details + +# boost priority of jobs related to the selected entity +keybinding add Alt-N do-job-now + ############################## # Generic adv mode bindings # ############################## @@ -155,139 +185,5 @@ keybinding add Alt-W@dfhack/lua/status_overlay "gui/workflow status" # autobutcher front-end keybinding add Shift-B@pet/List/Unit "gui/autobutcher" -# assign weapon racks to squads so that they can be used -keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack - # view pathable tiles from active cursor keybinding add Alt-Shift-P@dwarfmode/LookAround gui/pathable - -############################ -# UI and game logic tweaks # -############################ - -# stabilize the cursor of dwarfmode when switching menus -tweak stable-cursor - -# stop stacked liquid/bar/thread/cloth items from lasting forever -# if used in reactions that use only a fraction of the dimension. -# might be fixed by DF -# tweak fix-dimensions - -# make reactions requiring containers usable in advmode - the issue is -# that the screen asks for those reagents to be selected directly -tweak advmode-contained - -# support Shift-Enter in Trade and Move Goods to Depot screens for faster -# selection; it selects the current item or stack and scrolls down one line -tweak fast-trade - -# stop the right list in military->positions from resetting to top all the time -tweak military-stable-assign -# in same list, color units already assigned to squads in brown & green -tweak military-color-assigned - -# remove inverse dependency of squad training speed on unit list size and use more sparring -# tweak military-training - -# make crafted cloth items wear out with time like in old versions (bug 6003) -tweak craft-age-wear - -# stop adamantine clothing from wearing out (bug 6481) -#tweak adamantine-cloth-wear - -# Add "Select all" and "Deselect all" options to farm plot menus -tweak farm-plot-select - -# Add Shift-Left/Right controls to import agreement screen -tweak import-priority-category - -# Fixes a crash in the work order contition material list (bug 9905). -tweak condition-material - -# Adds an option to clear currently-bound hotkeys -tweak hotkey-clear - -# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving -tweak embark-profile-name - -# Misc. UI tweaks -tweak block-labors # Prevents labors that can't be used from being toggled -tweak burrow-name-cancel -tweak cage-butcher -tweak civ-view-agreement -tweak eggs-fertile -tweak fps-min -tweak hide-priority -tweak kitchen-prefs-all -tweak kitchen-prefs-empty -tweak max-wheelbarrow -tweak shift-8-scroll -tweak stone-status-all -tweak title-start-rename -tweak tradereq-pet-gender - -########################### -# Globally acting plugins # -########################### - -# Display DFHack version on title screen -enable title-version - -# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) -enable manipulator - -# Search tool in various screens (by falconne) -enable search - -# Improved build material selection interface (by falconne) -enable automaterial - -# Other interface improvement tools -enable \ - confirm \ - dwarfmonitor \ - mousequery \ - autogems \ - autodump \ - automelt \ - autotrade \ - buildingplan \ - resume \ - trackstop \ - zone \ - stocks \ - autochop \ - stockpiles -#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. -# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. -# You cannot extend a commented line. -# You can comment out the extension of a line. - -# enable mouse controls and sand indicator in embark screen -embark-tools enable sticky sand mouse - -# enable option to enter embark assistant -enable embark-assistant - -########### -# Scripts # -########### - -# write extra information to the gamelog -modtools/extra-gamelog enable - -# extended status screen (bedrooms page) -enable gui/extended-status - -# add information to item viewscreens -view-item-info enable - -# a replacement for the "load game" screen -gui/load-screen enable - -############################## -# Extra DFHack command files # -############################## - -# Run commands in this file when a world loads -sc-script add SC_WORLD_LOADED onLoad.init-example diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init new file mode 100644 index 000000000..aab17ebbe --- /dev/null +++ b/data/init/dfhack.tools.init @@ -0,0 +1,131 @@ +# Default DFHack tool configuration + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/dfhack.init + +############################ +# UI and game logic tweaks # +############################ + +# stabilize the cursor of dwarfmode when switching menus +tweak stable-cursor + +# stop stacked liquid/bar/thread/cloth items from lasting forever +# if used in reactions that use only a fraction of the dimension. +# might be fixed by DF +# tweak fix-dimensions + +# make reactions requiring containers usable in advmode - the issue is +# that the screen asks for those reagents to be selected directly +tweak advmode-contained + +# support Shift-Enter in Trade and Move Goods to Depot screens for faster +# selection; it selects the current item or stack and scrolls down one line +tweak fast-trade + +# stop the right list in military->positions from resetting to top all the time +tweak military-stable-assign +# in same list, color units already assigned to squads in brown & green +tweak military-color-assigned + +# make crafted cloth items wear out with time like in old versions (bug 6003) +tweak craft-age-wear + +# stop adamantine clothing from wearing out (bug 6481) +#tweak adamantine-cloth-wear + +# Add "Select all" and "Deselect all" options to farm plot menus +tweak farm-plot-select + +# Add Shift-Left/Right controls to import agreement screen +tweak import-priority-category + +# Fixes a crash in the work order contition material list (bug 9905). +tweak condition-material + +# Adds an option to clear currently-bound hotkeys +tweak hotkey-clear + +# Allows lowercase letters in embark profile names, and allows exiting the name prompt without saving +tweak embark-profile-name + +# Reduce performance impact of temperature changes +tweak fast-heat 100 + +# Misc. UI tweaks +tweak block-labors # Prevents labors that can't be used from being toggled +tweak burrow-name-cancel +tweak cage-butcher +tweak civ-view-agreement +tweak do-job-now +tweak eggs-fertile +tweak fps-min +tweak hide-priority +tweak kitchen-prefs-all +tweak kitchen-prefs-empty +tweak max-wheelbarrow +tweak partial-items +tweak shift-8-scroll +tweak stone-status-all +tweak title-start-rename +tweak tradereq-pet-gender + +########################### +# Globally acting plugins # +########################### + +# Display DFHack version on title screen +enable title-version + +# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) +enable manipulator + +# Search tool in various screens (by falconne) +enable search + +# Improved build material selection interface (by falconne) +enable automaterial + +# Other interface improvement tools +enable \ + confirm \ + dwarfmonitor \ + mousequery \ + autogems \ + autodump \ + automelt \ + autotrade \ + buildingplan \ + resume \ + trackstop \ + zone \ + stocks \ + autochop \ + stockpiles +#end a line with a backslash to make it continue to the next line. The \ is deleted for the final command. +# Multiline commands are ONLY supported for scripts like dfhack.init. You cannot do multiline command manually on the DFHack console. +# You cannot extend a commented line. +# You can comment out the extension of a line. + +# enable mouse controls and sand indicator in embark screen +embark-tools enable sticky sand mouse + +# enable option to enter embark assistant +enable embark-assistant + +########### +# Scripts # +########### + +# write extra information to the gamelog +modtools/extra-gamelog enable + +# extended status screen (bedrooms page) +enable gui/extended-status + +# add information to item viewscreens +view-item-info enable + +# a replacement for the "load game" screen +gui/load-screen enable diff --git a/data/init/onLoad.default.init b/data/init/onLoad.default.init new file mode 100644 index 000000000..c13190357 --- /dev/null +++ b/data/init/onLoad.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is loaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onLoad.init diff --git a/data/init/onMapLoad.default.init b/data/init/onMapLoad.default.init new file mode 100644 index 000000000..44986a044 --- /dev/null +++ b/data/init/onMapLoad.default.init @@ -0,0 +1,6 @@ +# Default DFHack commands to run when a map is loaded, either in +# adventure or fort mode. + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapLoad.init diff --git a/data/init/onMapUnload.default.init b/data/init/onMapUnload.default.init new file mode 100644 index 000000000..6441d72ff --- /dev/null +++ b/data/init/onMapUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a map is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onMapUnload.init diff --git a/data/init/onUnload.default.init b/data/init/onUnload.default.init new file mode 100644 index 000000000..9254a257b --- /dev/null +++ b/data/init/onUnload.default.init @@ -0,0 +1,5 @@ +# Default DFHack commands to run when a world is unloaded + +# Please do not edit this file directly. It will be overwritten with new +# defaults when you update DFHack. Instead, add your configuration to +# dfhack-config/init/onUnload.init diff --git a/data/examples/orders/basic.json b/data/orders/basic.json similarity index 97% rename from data/examples/orders/basic.json rename to data/orders/basic.json index fb4668e81..4c785e7ab 100644 --- a/data/examples/orders/basic.json +++ b/data/orders/basic.json @@ -1,12 +1,34 @@ [ { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", + "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, + "is_active" : false, + "is_validated" : false, + "item_conditions" : [ { "condition" : "AtLeast", @@ -25,7 +47,7 @@ "unrotten", "cookable" ], - "value" : 15 + "value" : 500 }, { "condition" : "AtMost", @@ -35,6 +57,15 @@ ], "item_type" : "FOOD", "value" : 3500 + }, + { + "condition" : "AtLeast", + "flags" : + [ + "unrotten" + ], + "item_type" : "FOOD", + "value" : 400 } ], "job" : "PrepareMeal", @@ -44,7 +75,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 1, + "id" : 2, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -81,7 +112,7 @@ "amount_left" : 2, "amount_total" : 2, "frequency" : "Daily", - "id" : 2, + "id" : 3, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -118,7 +149,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 3, + "id" : 4, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -139,7 +170,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 4, + "id" : 5, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -174,7 +205,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 5, + "id" : 6, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -206,7 +237,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 6, + "id" : 7, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -238,7 +269,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 7, + "id" : 8, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -260,7 +291,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 8, + "id" : 9, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -293,7 +324,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 9, + "id" : 10, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -324,7 +355,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 10, + "id" : 11, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -357,7 +388,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 11, + "id" : 12, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -397,7 +428,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 12, + "id" : 13, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -423,7 +454,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 13, + "id" : 14, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -458,7 +489,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 14, + "id" : 15, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -493,7 +524,7 @@ "amount_left" : 1, "amount_total" : 1, "frequency" : "Daily", - "id" : 15, + "id" : 16, "is_active" : false, "is_validated" : false, "item_conditions" : @@ -524,37 +555,6 @@ "yarn" ] }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 16, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "flags" : - [ - "collected", - "dyeable" - ], - "item_type" : "THREAD", - "value" : 5 - }, - { - "condition" : "AtLeast", - "flags" : - [ - "unrotten", - "dye" - ], - "value" : 15 - } - ], - "job" : "DyeThread" - }, { "amount_left" : 1, "amount_total" : 1, @@ -580,7 +580,7 @@ "unrotten", "dye" ], - "value" : 15 + "value" : 3 } ], "job" : "DyeCloth" @@ -613,7 +613,6 @@ ], "item_subtype" : "ITEM_TOOL_LARGE_POT", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 25 } ], @@ -632,13 +631,7 @@ [ { "condition" : "AtLeast", - "flags" : - [ - "non_economic", - "hard" - ], - "item_type" : "BOULDER", - "material" : "INORGANIC", + "item_type" : "WOOD", "value" : 20 }, { @@ -649,13 +642,15 @@ ], "item_subtype" : "ITEM_TOOL_JUG", "item_type" : "TOOL", - "material" : "INORGANIC", "value" : 10 } ], "item_subtype" : "ITEM_TOOL_JUG", "job" : "MakeTool", - "material" : "INORGANIC" + "material_category" : + [ + "wood" + ] }, { "amount_left" : 1, @@ -995,7 +990,7 @@ "frequency" : "Daily", "id" : 31, "is_active" : false, - "is_validated" : true, + "is_validated" : false, "item_conditions" : [ { @@ -1366,7 +1361,7 @@ "frequency" : "Daily", "id" : 44, "is_active" : false, - "is_validated" : true, + "is_validated" : false, "item_conditions" : [ { diff --git a/data/examples/orders/furnace.json b/data/orders/furnace.json similarity index 100% rename from data/examples/orders/furnace.json rename to data/orders/furnace.json diff --git a/data/examples/orders/glassstock.json b/data/orders/glassstock.json similarity index 100% rename from data/examples/orders/glassstock.json rename to data/orders/glassstock.json diff --git a/data/examples/orders/military.json b/data/orders/military.json similarity index 100% rename from data/examples/orders/military.json rename to data/orders/military.json diff --git a/data/examples/orders/rockstock.json b/data/orders/rockstock.json similarity index 100% rename from data/examples/orders/rockstock.json rename to data/orders/rockstock.json diff --git a/data/examples/orders/smelting.json b/data/orders/smelting.json similarity index 100% rename from data/examples/orders/smelting.json rename to data/orders/smelting.json diff --git a/data/examples/professions/Chef b/data/professions/Chef similarity index 92% rename from data/examples/professions/Chef rename to data/professions/Chef index 1f777c81a..218e08301 100644 --- a/data/examples/professions/Chef +++ b/data/professions/Chef @@ -1,4 +1,4 @@ -NAME Chef +NAME library/Chef BUTCHER TANNER COOK diff --git a/data/examples/professions/Craftsdwarf b/data/professions/Craftsdwarf similarity index 92% rename from data/examples/professions/Craftsdwarf rename to data/professions/Craftsdwarf index 29ed1ad0d..8c9707f17 100644 --- a/data/examples/professions/Craftsdwarf +++ b/data/professions/Craftsdwarf @@ -1,4 +1,4 @@ -NAME Craftsdwarf +NAME library/Craftsdwarf WOOD_CRAFT STONE_CRAFT BONE_CARVE diff --git a/data/examples/professions/Doctor b/data/professions/Doctor similarity index 93% rename from data/examples/professions/Doctor rename to data/professions/Doctor index 893708947..14959f599 100644 --- a/data/examples/professions/Doctor +++ b/data/professions/Doctor @@ -1,4 +1,4 @@ -NAME Doctor +NAME library/Doctor ANIMALCARE DIAGNOSE SURGERY diff --git a/data/examples/professions/Farmer b/data/professions/Farmer similarity index 83% rename from data/examples/professions/Farmer rename to data/professions/Farmer index 149b3c368..0b2801f4c 100644 --- a/data/examples/professions/Farmer +++ b/data/professions/Farmer @@ -1,4 +1,4 @@ -NAME Farmer +NAME library/Farmer PLANT MILLER BREWER diff --git a/data/examples/professions/Fisherdwarf b/data/professions/Fisherdwarf similarity index 90% rename from data/examples/professions/Fisherdwarf rename to data/professions/Fisherdwarf index 3c369e61d..1b5d7a1a8 100644 --- a/data/examples/professions/Fisherdwarf +++ b/data/professions/Fisherdwarf @@ -1,4 +1,4 @@ -NAME Fisherdwarf +NAME library/Fisherdwarf FISH CLEAN_FISH DISSECT_FISH diff --git a/data/examples/professions/Hauler b/data/professions/Hauler similarity index 92% rename from data/examples/professions/Hauler rename to data/professions/Hauler index a108b1bfd..4fd8b89f8 100644 --- a/data/examples/professions/Hauler +++ b/data/professions/Hauler @@ -1,4 +1,4 @@ -NAME Hauler +NAME library/Hauler FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Laborer b/data/professions/Laborer similarity index 92% rename from data/examples/professions/Laborer rename to data/professions/Laborer index bca22a302..ccd688428 100644 --- a/data/examples/professions/Laborer +++ b/data/professions/Laborer @@ -1,4 +1,4 @@ -NAME Laborer +NAME library/Laborer SOAP_MAKER BURN_WOOD POTASH_MAKING diff --git a/data/examples/professions/Marksdwarf b/data/professions/Marksdwarf similarity index 89% rename from data/examples/professions/Marksdwarf rename to data/professions/Marksdwarf index 583afd08e..f34d67cd2 100644 --- a/data/examples/professions/Marksdwarf +++ b/data/professions/Marksdwarf @@ -1,4 +1,4 @@ -NAME Marksdwarf +NAME library/Marksdwarf MECHANIC HAUL_STONE HAUL_WOOD diff --git a/data/examples/professions/Mason b/data/professions/Mason similarity index 92% rename from data/examples/professions/Mason rename to data/professions/Mason index 5f996f448..1977d2df5 100644 --- a/data/examples/professions/Mason +++ b/data/professions/Mason @@ -1,4 +1,4 @@ -NAME Mason +NAME library/Mason MASON CUT_GEM ENCRUST_GEM diff --git a/data/examples/professions/Meleedwarf b/data/professions/Meleedwarf similarity index 90% rename from data/examples/professions/Meleedwarf rename to data/professions/Meleedwarf index 8eac5ffd6..6a8338fea 100644 --- a/data/examples/professions/Meleedwarf +++ b/data/professions/Meleedwarf @@ -1,4 +1,4 @@ -NAME Meleedwarf +NAME library/Meleedwarf RECOVER_WOUNDED MECHANIC HAUL_STONE diff --git a/data/examples/professions/Migrant b/data/professions/Migrant similarity index 92% rename from data/examples/professions/Migrant rename to data/professions/Migrant index 59fd70405..23a3eeddb 100644 --- a/data/examples/professions/Migrant +++ b/data/professions/Migrant @@ -1,4 +1,4 @@ -NAME Migrant +NAME library/Migrant FEED_WATER_CIVILIANS SIEGEOPERATE MECHANIC diff --git a/data/examples/professions/Miner b/data/professions/Miner similarity index 66% rename from data/examples/professions/Miner rename to data/professions/Miner index 7be84512d..3170969d9 100644 --- a/data/examples/professions/Miner +++ b/data/professions/Miner @@ -1,4 +1,4 @@ -NAME Miner +NAME library/Miner MINE DETAIL RECOVER_WOUNDED diff --git a/data/examples/professions/Outdoorsdwarf b/data/professions/Outdoorsdwarf similarity index 92% rename from data/examples/professions/Outdoorsdwarf rename to data/professions/Outdoorsdwarf index a3f696419..31dbd2ad8 100644 --- a/data/examples/professions/Outdoorsdwarf +++ b/data/professions/Outdoorsdwarf @@ -1,4 +1,4 @@ -NAME Outdoorsdwarf +NAME library/Outdoorsdwarf CARPENTER BOWYER CUTWOOD diff --git a/data/examples/professions/Smith b/data/professions/Smith similarity index 92% rename from data/examples/professions/Smith rename to data/professions/Smith index f5fe0f982..7809d80e1 100644 --- a/data/examples/professions/Smith +++ b/data/professions/Smith @@ -1,4 +1,4 @@ -NAME Smith +NAME library/Smith FORGE_WEAPON FORGE_ARMOR FORGE_FURNITURE diff --git a/data/examples/professions/StartManager b/data/professions/StartManager similarity index 96% rename from data/examples/professions/StartManager rename to data/professions/StartManager index 751d75cc9..a70c705bf 100644 --- a/data/examples/professions/StartManager +++ b/data/professions/StartManager @@ -1,4 +1,4 @@ -NAME StartManager +NAME library/StartManager CUTWOOD ANIMALCARE DIAGNOSE diff --git a/data/examples/professions/Tailor b/data/professions/Tailor similarity index 91% rename from data/examples/professions/Tailor rename to data/professions/Tailor index 74ac03a93..f7986553a 100644 --- a/data/examples/professions/Tailor +++ b/data/professions/Tailor @@ -1,4 +1,4 @@ -NAME Tailor +NAME library/Tailor DYER LEATHER WEAVER diff --git a/data/quickfort/aliases-common.txt b/data/quickfort/aliases-common.txt index e918b28a9..f000b5bd1 100644 --- a/data/quickfort/aliases-common.txt +++ b/data/quickfort/aliases-common.txt @@ -77,6 +77,11 @@ 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}&^ @@ -116,7 +121,7 @@ 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}stallow&p^ +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}&^ @@ -126,7 +131,7 @@ 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}stallow&f^ +forbidtallow: {foodprefix}{Right}{Down 13}{Right}{forbidsearch search=tallow}^ forbidmiscliquid: {foodprefix}{Right}{Down 18}f^ forbidwax: {foodprefix}{Right}{Down 15}{Right}{Down 6}&^ @@ -136,7 +141,7 @@ 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}stallow&p^ +permittallow: {foodprefix}{Right}{Down 13}{Right}{permitsearch search=tallow}^ permitmiscliquid: {foodprefix}{Right}{Down 18}p^ permitwax: {forbidwax} @@ -200,7 +205,8 @@ 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^ -craftrefuse: {skulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permithair} +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}&^ @@ -211,7 +217,8 @@ forbidshells: {refuseprefix}{Right}{Down 5}f^ forbidteeth: {refuseprefix}{Right}{Down 6}f^ forbidhorns: {refuseprefix}{Right}{Down 7}f^ forbidhair: {refuseprefix}{Right}{Down 8}f^ -forbidcraftrefuse: {forbidskulls}{forbidbones}{forbidshells}{forbidteeth}{forbidhorns}{forbidhair} +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} @@ -222,7 +229,8 @@ permitshells: {refuseprefix}{Right}{Down 5}p^ permitteeth: {refuseprefix}{Right}{Down 6}p^ permithorns: {refuseprefix}{Right}{Down 7}p^ permithair: {refuseprefix}{Right}{Down 8}p^ -permitcraftrefuse: {permitskulls}{permitbones}{permitshells}{permitteeth}{permithorns}{permithair} +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} ################################## @@ -352,9 +360,11 @@ 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^ +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}&^ @@ -385,6 +395,15 @@ 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 diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index 0fcb4ca3c..405a9555e 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -12,10 +12,11 @@ endif() add_subdirectory(tthread) option(JSONCPP_WITH_TESTS "Compile and (for jsoncpp_check) run JsonCpp test executables" OFF) option(JSONCPP_WITH_POST_BUILD_UNITTEST "Automatically run unit-tests as a post build step" OFF) +option(JSONCPP_BUILD_SHARED_LIBS "Build jsoncpp_lib as a shared library." OFF) +option(JSONCPP_BUILD_OBJECT_LIBS "Build jsoncpp_lib as a object library." OFF) +option(JSONCPP_WITH_CMAKE_PACKAGE "Generate and install cmake package files" OFF) + add_subdirectory(jsoncpp-sub EXCLUDE_FROM_ALL) -if(UNIX) - set_target_properties(jsoncpp_lib_static PROPERTIES COMPILE_FLAGS "-Wno-deprecated-declarations") -endif() # build clsocket static and only as a dependency. Setting those options here overrides its own default settings. option(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) option(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) @@ -37,6 +38,7 @@ if(UNIX) set_target_properties(expat PROPERTIES COMPILE_FLAGS "-Wno-maybe-uninitialized") endif() +set(CMAKE_REQUIRED_QUIET ON) set(LIBZIP_BUILD_DOC OFF CACHE BOOL "") set(LIBZIP_BUILD_EXAMPLES OFF CACHE BOOL "") set(LIBZIP_BUILD_REGRESS OFF CACHE BOOL "") diff --git a/depends/clsocket b/depends/clsocket index ae19aebd7..6ed8aa464 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit ae19aebd795d6d91803e60f46de037b604593cb4 +Subproject commit 6ed8aa46462ea01a1122fc49422840a2facc9757 diff --git a/depends/jsoncpp-sub b/depends/jsoncpp-sub index ddabf50f7..ba5eac541 160000 --- a/depends/jsoncpp-sub +++ b/depends/jsoncpp-sub @@ -1 +1 @@ -Subproject commit ddabf50f72cf369bf652a95c4d9fe31a1865a781 +Subproject commit ba5eac54136064af94ab4a923ac110d7534d4f83 diff --git a/depends/libexpat b/depends/libexpat index 3c0f2e86c..6ac8628a3 160000 --- a/depends/libexpat +++ b/depends/libexpat @@ -1 +1 @@ -Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f +Subproject commit 6ac8628a3c7a1677b27fb007db96f665b684a183 diff --git a/depends/libzip b/depends/libzip index da0d18ae5..081249cce 160000 --- a/depends/libzip +++ b/depends/libzip @@ -1 +1 @@ -Subproject commit da0d18ae59ef2699013316b703cdc93809414c93 +Subproject commit 081249cceb59adc857a72d67e60c32047680f787 diff --git a/depends/lua/src/lapi.c b/depends/lua/src/lapi.c index 711895b39..aa01148ab 100644 --- a/depends/lua/src/lapi.c +++ b/depends/lua/src/lapi.c @@ -395,7 +395,7 @@ LUA_API size_t lua_rawlen (lua_State *L, int idx) { case LUA_TSHRSTR: return tsvalue(o)->shrlen; case LUA_TLNGSTR: return tsvalue(o)->u.lnglen; case LUA_TUSERDATA: return uvalue(o)->len; - case LUA_TTABLE: return luaH_getn(hvalue(o)); + case LUA_TTABLE: return size_t(luaH_getn(hvalue(o))); default: return 0; } } diff --git a/depends/luacov b/depends/luacov index 87d6ae018..99d068278 160000 --- a/depends/luacov +++ b/depends/luacov @@ -1 +1 @@ -Subproject commit 87d6ae018cb8d288d854f632e9d8d959d75d7db4 +Subproject commit 99d06827848583232dd77afb34cd7ab589567086 diff --git a/depends/xlsxio b/depends/xlsxio index 4056226fe..439fdbc25 160000 --- a/depends/xlsxio +++ b/depends/xlsxio @@ -1 +1 @@ -Subproject commit 4056226fe0df6bff4593ee2353cca07c2b7f327e +Subproject commit 439fdbc259c13f23a3122e68ba35ad5a13bcd97c diff --git a/dfhack-config/dwarfmonitor.json b/dfhack-config/dwarfmonitor.json index 3fd365e74..007dad020 100644 --- a/dfhack-config/dwarfmonitor.json +++ b/dfhack-config/dwarfmonitor.json @@ -2,7 +2,7 @@ "widgets": [ { "type": "weather", - "x": 1, + "x": 22, "y": -1 }, { diff --git a/dfhack-config/init/default.dfhack.init b/dfhack-config/init/default.dfhack.init new file mode 100644 index 000000000..aad18dd1c --- /dev/null +++ b/dfhack-config/init/default.dfhack.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/dfhack.default.init diff --git a/dfhack-config/init/default.onLoad.init b/dfhack-config/init/default.onLoad.init new file mode 100644 index 000000000..fe87d4209 --- /dev/null +++ b/dfhack-config/init/default.onLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onLoad.default.init diff --git a/dfhack-config/init/default.onMapLoad.init b/dfhack-config/init/default.onMapLoad.init new file mode 100644 index 000000000..9e781b924 --- /dev/null +++ b/dfhack-config/init/default.onMapLoad.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapLoad.default.init diff --git a/dfhack-config/init/default.onMapUnload.init b/dfhack-config/init/default.onMapUnload.init new file mode 100644 index 000000000..716680fd0 --- /dev/null +++ b/dfhack-config/init/default.onMapUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onMapUnload.default.init diff --git a/dfhack-config/init/default.onUnload.init b/dfhack-config/init/default.onUnload.init new file mode 100644 index 000000000..712c35098 --- /dev/null +++ b/dfhack-config/init/default.onUnload.init @@ -0,0 +1,7 @@ +# Load DFHack defaults. +# +# If you delete this file, it will reappear when you restart DFHack. +# Instead, please comment out the following line if you do not want DFHack to +# load its default configuration. + +script hack/init/onUnload.default.init diff --git a/dfhack-config/init/dfhack.init b/dfhack-config/init/dfhack.init new file mode 100644 index 000000000..b05598f98 --- /dev/null +++ b/dfhack-config/init/dfhack.init @@ -0,0 +1,5 @@ +# This file runs when DFHack is initialized, when Dwarf Fortress is first +# started, before any world or save data is loaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onLoad.init b/dfhack-config/init/onLoad.init new file mode 100644 index 000000000..ef4fd97af --- /dev/null +++ b/dfhack-config/init/onLoad.init @@ -0,0 +1,6 @@ +# This file runs when a world is loaded. This happens when you open a save file +# in fort, adventure, or legends mode. If a fort is being loaded, this file runs +# before any onMapLoad.init files. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapLoad.init b/dfhack-config/init/onMapLoad.init new file mode 100644 index 000000000..90c6b9e14 --- /dev/null +++ b/dfhack-config/init/onMapLoad.init @@ -0,0 +1,5 @@ +# This file runs when a map is loaded in adventure or fort mode, after any +# onLoad.init files (which run earlier, when the world is loaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onMapUnload.init b/dfhack-config/init/onMapUnload.init new file mode 100644 index 000000000..c513d9cae --- /dev/null +++ b/dfhack-config/init/onMapUnload.init @@ -0,0 +1,5 @@ +# This file runs when a fortress map is unloaded, before any onUnload.init files +# (which run later, when the world is unloaded). +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/dfhack-config/init/onUnload.init b/dfhack-config/init/onUnload.init new file mode 100644 index 000000000..c8ed3ab5b --- /dev/null +++ b/dfhack-config/init/onUnload.init @@ -0,0 +1,4 @@ +# This file runs when a world is unloaded. +# +# You can extend or override DFHack's default configuration by adding commands +# to this file. diff --git a/docs/Authors.rst b/docs/Authors.rst index a6fa5a3d8..280594674 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -1,4 +1,4 @@ -List of Authors +List of authors =============== The following is a list of people who have contributed to DFHack, in alphabetical order. @@ -32,8 +32,10 @@ billw2012 billw2012 BrickViking brickviking brndd brndd burneddi Caldfir caldfir +Cameron Ewell Ozzatron Carter Bray Qartar Chris Dombroski cdombroski +Chris Parsons chrismdp Clayton Hughes Clément Vuchener cvuchener daedsidog daedsidog @@ -47,6 +49,7 @@ Deon DoctorVanGogh DoctorVanGogh Donald Ruegsegger hashaash doomchild doomchild +DwarvenM DwarvenM ElMendukol ElMendukol enjia2000 Eric Wald eswald @@ -59,6 +62,7 @@ Gabe Rau gaberau gchristopher gchristopher George Murray GitOnUp grubsteak grubsteak +Guilherme Abraham GuilhermeAbraham Harlan Playford playfordh Hayati Ayguen hayguen Herwig Hochleitner bendlas @@ -112,6 +116,7 @@ napagokc napagokc Neil Little nmlittle Nick Rart nickrart comestible Nicolas Ayala nicolasayala +Nik Nyby nikolas Nikolay Amiantov abbradar nocico nocico Omniclasm @@ -123,11 +128,13 @@ Paul Fenwick pjf PeridexisErrant PeridexisErrant Petr Mrázek peterix Pfhreak Pfhreak +Pierre Lulé plule Pierre-David Bélanger pierredavidbelanger potato Priit Laes plaes Putnam Putnam3145 Quietust quietust _Q +Rafał Karczmarczyk CarabusX Raidau Raidau Ralph Bisschops ralpha Ramblurr Ramblurr @@ -173,6 +180,7 @@ Theo Kalfas teolandon therahedwig therahedwig ThiagoLira ThiagoLira thurin thurin +Tim Siegel softmoth Tim Walberg twalberg Timothy Collett danaris Timur Kelman TymurGubayev diff --git a/docs/Compile.rst b/docs/Compile.rst index 85ec50344..ae23a1b73 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -40,8 +40,8 @@ This will check out the code on the default branch of the GitHub repo, currently ``develop``, which may be unstable. If you want code for the latest stable release, you can check out the ``master`` branch instead:: - git checkout master - git submodule update + git checkout master + git submodule update In general, a single DFHack clone is suitable for development - most Git operations such as switching branches can be done on an existing clone. If you @@ -289,7 +289,7 @@ DF, which causes DF to use your system libstdc++ instead:: rm libs/libstdc++.so.6 Note that distributing binaries compiled with newer GCC versions may result in -the opposite compatibily issue: users with *older* GCC versions may encounter +the opposite compatibility issue: users with *older* GCC versions may encounter similar errors. This is why DFHack distributes both GCC 4.8 and GCC 7 builds. If you are planning on distributing binaries to other users, we recommend using an older GCC (but still at least 4.8) version if possible. @@ -314,7 +314,7 @@ Notes for GCC 8+ or OS X 10.10+ users If none of these situations apply to you, skip to `osx-setup`. -If you have issues building on OS X 10.10 (Yosemite) or above, try definining +If you have issues building on OS X 10.10 (Yosemite) or above, try defining the following environment variable:: export MACOSX_DEPLOYMENT_TARGET=10.9 @@ -503,7 +503,7 @@ in their name. If this redirect doesn't occur, just copy, paste, and enter the download link again and you should see the options. You need to get: Visual C++ Build Tools for Visual Studio 2015 with Update 3. Click the download button next to it and a dropdown of download formats will appear. -Select the DVD format to download an ISO file. When the donwload is complete, +Select the DVD format to download an ISO file. When the download is complete, click on the ISO file and a folder will popup with the following contents: * packages (folder) @@ -561,7 +561,7 @@ Additional dependencies: installing with the Chocolatey Package Manager ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The remainder of dependencies - Git, CMake, StrawberryPerl, and Python - can be -most easily installed using the Chocolatey Package Manger. Chocolatey is a +most easily installed using the Chocolatey Package Manager. Chocolatey is a \*nix-style package manager for Windows. It's fast, small (8-20MB on disk) and very capable. Think "``apt-get`` for Windows." diff --git a/docs/Core.rst b/docs/Core.rst index ef178cc6e..012236d59 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -9,10 +9,9 @@ DFHack Core :depth: 2 -Command Implementation +Command implementation ====================== -DFHack commands can be implemented in three ways, all of which -are used in the same way: +DFHack commands can be implemented in any of three ways: :builtin: commands are implemented by the core of DFHack. They manage other DFHack tools, interpret commands, and control basic @@ -27,8 +26,9 @@ are used in the same way: more flexible about versions, and easier to distribute. Most third-party DFHack addons are scripts. +All tools distributed with DFHack are documented `here `. -Using DFHack Commands +Using DFHack commands ===================== DFHack commands can be executed in a number of ways: @@ -38,7 +38,7 @@ DFHack commands can be executed in a number of ways: #. From one of several `init-files`, automatically #. Using `script` to run a batch of commands from a file -The DFHack Console +The DFHack console ------------------ The command line has some nice line editing capabilities, including history that's preserved between different runs of DF - use :kbd:`↑` and :kbd:`↓` @@ -113,269 +113,27 @@ second (Windows) example uses `kill-lua` to stop a Lua script. you have multiple copies of DF running simultaneously. To assign a different port, see `remote-server-config`. +.. _dfhack-config: -Built-in Commands -================= -The following commands are provided by the 'core' components -of DFHack, rather than plugins or scripts. - -.. contents:: - :local: - - -.. _alias: - -alias ------ -The ``alias`` command allows configuring aliases to other DFHack commands. -Aliases are resolved immediately after built-in commands, which means that an -alias cannot override a built-in command, but can override a command implemented -by a plugin or script. - -Usage: - -:``alias list``: lists all configured aliases -:``alias add [arguments...]``: adds an alias -:``alias replace [arguments...]``: replaces an existing - alias with a new command, or adds the alias if it does not already exist -:``alias delete ``: removes the specified alias - -Aliases can be given additional arguments when created and invoked, which will -be passed to the underlying command in order. An example with `devel/print-args`:: - - [DFHack]# alias add pargs devel/print-args example - [DFHack]# pargs text - example - text - - -.. _cls: - -cls ---- -Clear the terminal. Does not delete command history. - - -.. _die: - -die ---- -Instantly kills DF without saving. - - -.. _disable: - -.. _enable: - -enable ------- -Many plugins can be in a distinct enabled or disabled state. Some of -them activate and deactivate automatically depending on the contents -of the world raws. Others store their state in world data. However a -number of them have to be enabled globally, and the init file is the -right place to do it. - -Most such plugins or scripts support the built-in ``enable`` and ``disable`` -commands. Calling them at any time without arguments prints a list -of enabled and disabled plugins, and shows whether that can be changed -through the same commands. Passing plugin names to these commands will enable -or disable the specified plugins. For example, to enable the `manipulator` -plugin:: - - enable manipulator - -It is also possible to enable or disable multiple plugins at once:: - - enable manipulator search - - -.. _fpause: - -fpause ------- -Forces DF to pause. This is useful when your FPS drops below 1 and you lose -control of the game. - - -.. _help: - -help ----- -Most commands support using the ``help `` built-in command -to retrieve further help without having to look at this document. -``? `` and ``man `` are aliases. - -Some commands (including many scripts) instead take ``help`` or ``?`` -as an option on their command line - ie `` help``. - - -.. _hide: - -hide ----- -Hides the DFHack terminal window. Only available on Windows. - - -.. _keybinding: - -keybinding ----------- -To set keybindings, use the built-in ``keybinding`` command. Like any other -command it can be used at any time from the console, but bindings are not -remembered between runs of the game unless re-created in `dfhack.init`. - -Currently, any combinations of Ctrl/Alt/Shift with A-Z, 0-9, or F1-F12 are supported. - -Possible ways to call the command: - -``keybinding list `` - List bindings active for the key combination. -``keybinding clear ...`` - Remove bindings for the specified keys. -``keybinding add "cmdline" "cmdline"...`` - Add bindings for the specified key. -``keybinding set "cmdline" "cmdline"...`` - Clear, and then add bindings for the specified key. - -The ```` parameter above has the following *case-sensitive* syntax:: - - [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] - -where the *KEY* part can be any recognized key and [] denote optional parts. - -When multiple commands are bound to the same key combination, DFHack selects -the first applicable one. Later ``add`` commands, and earlier entries within one -``add`` command have priority. Commands that are not specifically intended for use -as a hotkey are always considered applicable. - -The ``context`` part in the key specifier above can be used to explicitly restrict -the UI state where the binding would be applicable. If called without parameters, -the ``keybinding`` command among other things prints the current context string. - -Only bindings with a ``context`` tag that either matches the current context fully, -or is a prefix ending at a ``/`` boundary would be considered for execution, i.e. -when in context ``foo/bar/baz``, keybindings restricted to any of ``@foo/bar/baz``, -``@foo/bar``, ``@foo`` or none will be active. - -Multiple contexts can be specified by separating them with a -pipe (``|``) - for example, ``@foo|bar|baz/foo`` would match -anything under ``@foo``, ``@bar``, or ``@baz/foo``. - -Interactive commands like `liquids` cannot be used as hotkeys. - - -.. _kill-lua: - -kill-lua --------- -Stops any currently-running Lua scripts. By default, scripts can -only be interrupted every 256 instructions. Use ``kill-lua force`` -to interrupt the next instruction. - - -.. _load: -.. _unload: -.. _reload: - -load ----- -``load``, ``unload``, and ``reload`` control whether a plugin is loaded -into memory - note that plugins are loaded but disabled unless you do -something. Usage:: - - load|unload|reload PLUGIN|(-a|--all) - -Allows dealing with plugins individually by name, or all at once. - -Note that plugins do not maintain their enabled state if they are reloaded, so -you may need to use `enable` to re-enable a plugin after reloading it. - - -.. _ls: - -ls --- -``ls`` does not list files like the Unix command, but rather -available commands - first built in commands, then plugins, -and scripts at the end. Usage: - -:ls -a: Also list scripts in subdirectories of ``hack/scripts/``, - which are generally not intended for direct use. -:ls : List subcommands for the given plugin. - - -.. _plug: - -plug ----- -Lists available plugins, including their state and detailed description. - -``plug`` - Lists available plugins (*not* commands implemented by plugins) -``plug [PLUGIN] [PLUGIN] ...`` - List state and detailed description of the given plugins, - including commands implemented by the plugin. - - -.. _sc-script: - -sc-script ---------- -Allows additional scripts to be run when certain events occur -(similar to onLoad*.init scripts) - - -.. _script: - -script ------- -Reads a text file, and runs each line as a DFHack command -as if it had been typed in by the user - treating the -input like `an init file `. - -Some other tools, such as `autobutcher` and `workflow`, export -their settings as the commands to create them - which are later -loaded with ``script`` - - -.. _show: - -show ----- -Shows the terminal window after it has been `hidden `. -Only available on Windows. You'll need to use it from a -`keybinding` set beforehand, or the in-game `command-prompt`. - -.. _type: - -type ----- -``type command`` shows where ``command`` is implemented. - -Other Commands --------------- -The following commands are *not* built-in, but offer similarly useful functions. - -* `command-prompt` -* `hotkeys` -* `lua` -* `multicmd` -* `nopause` -* `quicksave` -* `rb` -* `repeat` +Configuration files +=================== +Most DFHack settings can be changed by modifying files in the ``dfhack-config`` +folder (which is in the DF folder). The default versions of these files, if they +exist, are in ``dfhack-config/default`` and are installed when DFHack starts if +necessary. .. _init-files: -Init Files -========== +Init files +---------- .. contents:: :local: DFHack allows users to automatically run commonly-used DFHack commands -when DF is first loaded, when a game is loaded, and when a game is unloaded. +when DF is first loaded, when a world is loaded, when a map is loaded, when a +map is unloaded, and when a world is unloaded. Init scripts function the same way they would if the user manually typed in their contents, but are much more convenient. In order to facilitate @@ -385,32 +143,33 @@ save-specific init files in the save folders. DFHack looks for init files in three places each time they could be run: -#. The main DF directory +#. The :file:`dfhack-config/init` subdirectory in the main DF directory #. :file:`data/save/{world}/raw`, where ``world`` is the current save, and #. :file:`data/save/{world}/raw/objects` -When reading commands from dfhack.init or with the `script` command, if the final -character on a line is a backslash then the next uncommented line is considered a -continuation of that line, with the backslash deleted. Commented lines are skipped, -so it is possible to comment out parts of a command with the ``#`` character. +For each of those directories, all matching init files will be executed in +alphabetical order. +Before running matched init scripts in any of those locations, the +:file:`dfhack-config/init/default.*` file that matches the event will be run to +load DFHack defaults. Only the :file:`dfhack-config/init` directory is checked +for this file, not any :file:`raw` directories. If you want DFHack to load +without running any of its default configuration commands, edit the +:file:`dfhack-config/init/default.*` files and comment out the commands you see +there. -.. _dfhack.init: - -dfhack*.init ------------- -If your DF folder contains at least one file named ``dfhack*.init`` -(where ``*`` is a placeholder for any string), then all such files -are executed in alphabetical order when DF is first started. +When reading commands from the init files or with the `script` command, if the +final character on a line is a backslash then the next uncommented line is +considered a continuation of that line, with the backslash deleted. Commented +lines are skipped, so it is possible to comment out parts of a command with the +``#`` character. -DFHack is distributed with :download:`/dfhack.init-example` as an example -with an up-to-date collection of basic commands; mostly setting standard -keybindings and `enabling ` plugins. You are encouraged to look -through this file to learn which features it makes available under which -key combinations. You may also customise it and rename it to ``dfhack.init``. +.. _dfhack.init: -If your DF folder does not contain any ``dfhack*.init`` files, the example -will be run as a fallback. +dfhack\*.init +............. +On startup, DFHack looks for files of the form ``dfhack*.init`` (where ``*`` is +a placeholder for any string, including the empty string). These files are best used for keybindings and enabling persistent plugins which do not require a world to be loaded. @@ -418,51 +177,49 @@ which do not require a world to be loaded. .. _onLoad.init: -onLoad*.init ------------- +onLoad\*.init +............. When a world is loaded, DFHack looks for files of the form ``onLoad*.init``, where ``*`` can be any string, including the empty string. -All matching init files will be executed in alphabetical order. A world being loaded can mean a fortress, an adventurer, or legends mode. These files are best used for non-persistent commands, such as setting -a `fix ` script to run on `repeat`. +a `bugfix-tag-index` script to run on `repeat`. -.. _onUnload.init: +.. _onMapLoad.init: -onUnload*.init --------------- -When a world is unloaded, DFHack looks for files of the form ``onUnload*.init``. -Again, these files may be in any of the above three places. -All matching init files will be executed in alphebetical order. +onMapLoad\*.init +................ +When a map is loaded, either in adventure or fort mode, DFHack looks for files +of the form ``onMapLoad*.init``, where ``*`` can be any string, including the +empty string. -Modders often use such scripts to disable tools which should not affect -an unmodded save. +These files are best used for commands that are only relevant once there is a +game map loaded. -.. _other_init_files: -Other init files ----------------- +.. _onMapUnload.init: +.. _onUnload.init: -* ``onMapLoad*.init`` and ``onMapUnload*.init`` are run when a map, - distinct from a world, is loaded. This is good for map-affecting - commands (e.g. `clean`), or avoiding issues in Legends mode. +onMapUnload\*.init and onUnload\*.init +...................................... +When a map or world is unloaded, DFHack looks for files of the form +``onMapUnload*.init`` or ``onUnload*.init``, respectively. -* Any lua script named ``raw/init.d/*.lua``, in the save or main DF - directory, will be run when any world or that save is loaded. +Modders often use unload init scripts to disable tools which should not run +after a modded save is unloaded. -.. _dfhack-config: +.. _other_init_files: -Configuration Files -=================== +raw/init.d/\*.lua +................. + +Any lua script named ``raw/init.d/*.lua``, in the save or main DF directory, +will be run when any world or that save is loaded. -Some DFHack settings can be changed by modifying files in the ``dfhack-config`` -folder (which is in the DF folder). The default versions of these files, if they -exist, are in ``dfhack-config/default`` and are installed when DFHack starts if -necessary. .. _script-paths: @@ -503,7 +260,7 @@ modified programmatically at any time through the `Lua API `. .. _env-vars: -Environment Variables +Environment variables ===================== DFHack's behavior can be adjusted with some environment variables. For example, @@ -549,7 +306,7 @@ Other (non-DFHack-specific) variables that affect DFHack: sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this should be the case in most UTF-8-capable \*nix terminal emulators already. -Miscellaneous Notes +Miscellaneous notes =================== This section is for odd but important notes that don't fit anywhere else. diff --git a/docs/Dev-intro.rst b/docs/Dev-intro.rst index 758bf225f..a49cb96f6 100644 --- a/docs/Dev-intro.rst +++ b/docs/Dev-intro.rst @@ -22,7 +22,7 @@ Plugins DFHack plugins are written in C++ and located in the ``plugins`` folder. Currently, documentation on how to write plugins is somewhat sparse. There are -templates that you can use to get started in the ``plugins/skeleton`` +templates that you can use to get started in the ``plugins/examples`` folder, and the source code of existing plugins can also be helpful. If you want to compile a plugin that you have just added, you will need to add a @@ -35,13 +35,13 @@ other commands). Plugins can also register handlers to run on every tick, and can interface with the built-in `enable` and `disable` commands. For the full plugin API, see the -skeleton plugins or ``PluginManager.cpp``. +example ``skeleton`` plugin or ``PluginManager.cpp``. Installed plugins live in the ``hack/plugins`` folder of a DFHack installation, and the `load` family of commands can be used to load a recompiled plugin without restarting DF. -See `plugins-index` for a list of all plugins included in DFHack. +Run `plug` at the DFHack prompt for a list of all plugins included in DFHack. Scripts ------- @@ -51,10 +51,10 @@ is more complete and currently better-documented, however. Referring to existing scripts as well as the API documentation can be helpful when developing new scripts. -`Scripts included in DFHack ` live in a separate `scripts repository `_. -This can be found in the ``scripts`` submodule if you have -`cloned DFHack `, or the ``hack/scripts`` folder -of an installed copy of DFHack. +Scripts included in DFHack live in a separate +:source-scripts:`scripts repository <>`. This can be found in the ``scripts`` +submodule if you have `cloned DFHack `, or the +``hack/scripts`` folder of an installed copy of DFHack. Core ---- diff --git a/docs/Documentation.rst b/docs/Documentation.rst index be4511ba8..6ac5ef382 100644 --- a/docs/Documentation.rst +++ b/docs/Documentation.rst @@ -1,143 +1,352 @@ .. _documentation: ########################### -DFHack Documentation System +DFHack documentation system ########################### -DFHack documentation, like the file you are reading now, is created as ``.rst`` files, -which are in `reStructuredText (reST) `_ format. -This is a documentation format common in the Python community. It is very -similar in concept - and in syntax - to Markdown, as found on GitHub and many other +DFHack documentation, like the file you are reading now, is created as a set of +``.rst`` files in `reStructuredText (reST) `_ +format. This is a documentation format common in the Python community. It is very +similar in concept -- and in syntax -- to Markdown, as found on GitHub and many other places. However it is more advanced than Markdown, with more features available when compiled to HTML, such as automatic tables of contents, cross-linking, special external links (forum, wiki, etc) and more. The documentation is compiled by a -Python tool, `Sphinx `_. +Python tool named `Sphinx `_. -The DFHack build process will compile the documentation, but this is disabled -by default due to the additional Python and Sphinx requirements. You typically -only need to build the docs if you're changing them, or perhaps -if you want a local HTML copy; otherwise, you can read an -`online version hosted by ReadTheDocs `_. +The DFHack build process will compile and install the documentation so it can be +displayed in-game by the `help` and `ls` commands (and any other command or GUI that +displays help text), but documentation compilation is disabled by default due to the +additional Python and Sphinx requirements. If you already have a version of the docs +installed (say from a downloaded release binary), then you only need to build the docs +if you're changing them and want to see the changes reflected in your game. -(Note that even if you do want a local copy, it is certainly not necessary to +You can also build the docs if you just want a local HTML- or text-rendered copy, though +you can always read the `online version `_ too. +The active development version of the documentation is tagged with ``latest`` and +is available `here `_ + +Note that even if you do want a local copy, it is certainly not necessary to compile the documentation in order to read it. Like Markdown, reST documents are designed to be just as readable in a plain-text editor as they are in HTML format. -The main thing you lose in plain text format is hyperlinking.) +The main thing you lose in plain text format is hyperlinking. .. contents:: Contents :local: +Concepts and general guidance +============================= + +The source ``.rst`` files are compiled to HTML for viewing in a browser and to text +format for viewing in-game. For in-game help, the help text is read from its installed +location in ``hack/docs`` under the DF directory. + +When writing documentation, remember that everything should be documented! If it's not +clear *where* a particular thing should be documented, ask on Discord or in the DFHack +thread on Bay12 -- you'll not only be getting help, you'll also be providing valuable +feedback that makes it easier for future contributors to find documentation on how to +write the documentation! + +Try to keep lines within 80-100 characters so it's readable in plain text in the +terminal - Sphinx (our documentation system) will make sure paragraphs flow. + +Short descriptions +------------------ + +Each command that a user can run -- as well as every plugin -- needs to have a +short (~54 character) descriptive string associated with it. This description text is: + +- used in-game by the `ls` command and DFHack UI screens that list commands +- used in the generated index entries in the HTML docs + +Tags +---- + +To make it easier for players to find related commands, all plugins and commands are marked +with relevant tags. These are used to compile indices and generate cross-links between the +commands, both in the HTML documents and in-game. See the list of available `tag-list` and +think about which categories your new tool belongs in. + +Links +----- + +If it would be helpful to mention another DFHack command, don't just type the +name - add a hyperlink! Specify the link target in backticks, and it will be +replaced with the corresponding title and linked: e.g. ```autolabor``` +=> `autolabor`. Scripts and plugins have link targets that match their names +created for you automatically. + +If you want to link to a heading in your own page, you can specify it like this:: + + `Heading text exactly as written`_ + +Note that the DFHack documentation is configured so that single backticks (with +no prefix or suffix) produce links to internal link targets, such as the +``autolabor`` target shown above. This is different from the reStructuredText +default behavior of rendering such text in italics (as a reference to a title). +For alternative link behaviors, see: + +- `The reStructuredText documentation on roles `__ +- `The reStructuredText documentation on external links `__ +- `The Sphinx documentation on roles `__ + - ``:doc:`` is useful for linking to another document outside of DFHack. + .. _docs-standards: Documentation standards ======================= +.. highlight:: rst + Whether you're adding new code or just fixing old documentation (and there's plenty), there are a few important standards for completeness and consistent style. Treat this section as a guide rather than iron law, match the surrounding text, and you'll be fine. -Command documentation ---------------------- +Where do I add the help text? +----------------------------- -Each command should have a short (~54 character) help string, which is shown -by the `ls` command. For scripts, this is a comment on the first line -(the comment marker and whitespace is stripped). For plugins it's the second -argument to ``PluginCommand``. Please make this brief but descriptive! +For scripts and plugins that are distributed as part of DFHack, documentation files +should be added to the :source-scripts:`scripts/docs ` and :source:`docs/plugins` directories, +respectively, in a file named after the script or plugin. For example, a script named +``gui/foobar.lua`` (which provides the ``gui/foobar`` command) should be documented +in a file named ``docs/gui/foobar.rst`` in the scripts repo. Similarly, a plugin named +``foobaz`` should be documented in a file named ``docs/plugins/foobaz.rst`` in the dfhack repo. +For plugins, all commands provided by that plugin should be documented in that same file. -Everything should be documented! If it's not clear *where* a particular -thing should be documented, ask on IRC or in the DFHack thread on Bay12 - -as well as getting help, you'll be providing valuable feedback that -makes it easier for future readers! +Short descriptions (the ~54 character short help) for scripts and plugins are taken from +the ``summary`` attribute of the ``dfhack-tool`` directive that each tool help document must +have (see the `Header format`_ section below). Please make this brief but descriptive! -Scripts can use a custom autodoc function, based on the Sphinx ``include`` -directive - anything between the tokens is copied into the appropriate scripts -documentation page. For Ruby, we follow the built-in docstring convention -(``=begin`` and ``=end``). For Lua, the tokens are ``[====[`` and ``]====]`` -- ordinary multi-line strings. It is highly encouraged to reuse this string -as the in-console documentation by (e.g.) printing it when a ``-help`` argument -is given. +Short descriptions for commands provided by plugins are taken from the ``description`` +parameter passed to the ``PluginCommand`` constructor used when the command is registered +in the plugin source file. -The docs **must** have a heading which exactly matches the command, underlined -with ``=====`` to the same length. For example, a lua file would have: +Header format +------------- -.. code-block:: lua +The docs **must** begin with a heading which exactly matches the script or plugin name, underlined +with ``=====`` to the same length. This must be followed by a ``.. dfhack-tool:`` directive with +at least the following parameters: - local helpstr = [====[ +* ``:summary:`` - a short, single-sentence description of the tool +* ``:tags:`` - a space-separated list of `tags ` that apply to the tool - add-thought - =========== - Adds a thought or emotion to the selected unit. Can be used by other scripts, - or the gui invoked by running ``add-thought gui`` with a unit selected. +By default, ``dfhack-tool`` generates both a description of a tool and a command +with the same name. For tools (specifically plugins) that do not provide exactly +1 command with the same name as the tool, pass the ``:no-command:`` parameter (with +no content after it) to prevent the command block from being generated. - ]====] +For tools that provide multiple commands, or a command by the same name but with +significantly different functionality (e.g. a plugin that can be both enabled +and invoked as a command for different results), use the ``.. dfhack-command:`` +directive for each command. This takes only a ``:summary:`` argument, with the +same meaning as above. +For example, documentation for the ``build-now`` script might look like:: -.. highlight:: rst + build-now + ========= -Where the heading for a section is also the name of a command, the spelling -and case should exactly match the command to enter in the DFHack command line. + .. dfhack-tool:: + :summary: Instantly completes unsuspended building construction jobs. + :tags: fort armok buildings -Try to keep lines within 80-100 characters, so it's readable in plain text -in the terminal - Sphinx (our documentation system) will make sure -paragraphs flow. + By default, all buildings on the map are completed, but the area of effect is configurable. -Command usage -------------- +And documentation for the ``autodump`` plugin might look like:: -If there aren't many options or examples to show, they can go in a paragraph of -text. Use double-backticks to put commands in monospaced font, like this:: + autodump + ======== - You can use ``cleanowned scattered x`` to dump tattered or abandoned items. + .. dfhack-tool:: + :summary: Automatically set items in a stockpile to be dumped. + :tags: fort armok fps productivity items stockpiles + :no-command: -If the command takes more than three arguments, format the list as a table -called Usage. The table *only* lists arguments, not full commands. -Input values are specified in angle brackets. Example:: + .. dfhack-command:: autodump + :summary: Teleports items marked for dumping to the cursor position. - Usage: + .. dfhack-command:: autodump-destroy-here + :summary: Destroy items marked for dumping under the cursor. - :arg1: A simple argument. - :arg2 : Does something based on the input value. - :Very long argument: - Is very specific. + .. dfhack-command:: autodump-destroy-item + :summary: Destroys the selected item. -To demonstrate usage - useful mainly when the syntax is complicated, list the -full command with arguments in monospaced font, then indent the next line and -describe the effect:: + When `enabled `, this plugin adds an option to the :kbd:`q` menu for + stockpiles. - ``resume all`` - Resumes all suspended constructions. + When invoked as a command, it can instantly move all unforbidden items marked + for dumping to the tile under the cursor. -Links ------ +Usage help +---------- -If it would be helpful to mention another DFHack command, don't just type the -name - add a hyperlink! Specify the link target in backticks, and it will be -replaced with the corresponding title and linked: e.g. ```autolabor``` -=> `autolabor`. Link targets should be equivalent to the command -described (without file extension), and placed above the heading of that -section like this:: +The first section after the header and introductory text should be the usage section. You can +choose between two formats, based on whatever is cleaner or clearer for your syntax. The first +option is to show usage formats together, with an explanation following the block:: - .. _autolabor: + Usage + ----- - autolabor - ========= + :: -Add link targets if you need them, but otherwise plain headings are preferred. -Scripts have link targets created automatically. + build-now [] + build-now here [] + build-now [ []] [] -Note that the DFHack documentation is configured so that single backticks (with -no prefix or suffix) produce links to internal link targets, such as the -``autolabor`` target shown above. This is different from the reStructuredText -default behavior of rendering such text in italics (as a reference to a title). -For alternative link behaviors, see: + Where the optional ```` pair can be used to specify the + coordinate bounds within which ``build-now`` will operate. If + they are not specified, ``build-now`` will scan the entire map. + If only one ```` is specified, only the building at that + coordinate is built. -- `The reStructuredText documentation on roles `__ -- `The reStructuredText documentation on external links `__ -- `The Sphinx documentation on roles `__ + The ```` parameters can either be an ``,,`` triple + (e.g. ``35,12,150``) or the string ``here``, which means the + position of the active game cursor. + +The second option is to arrange the usage options in a list, with the full command +and arguments in monospaced font. Then indent the next line and describe the effect:: + + Usage + ----- + + ``build-now []`` + Scan the entire map and build all unsuspended constructions + and buildings. + ``build-now here []`` + Build the unsuspended construction or building under the + cursor. + ``build-now [ []] []`` + Build all unsuspended constructions within the specified + coordinate box. - - ``:doc:`` is useful for linking to another document + The ```` parameters are specified as... + +Note that in both options, the entire commandline syntax is written, including the command itself. +Literal text is written as-is (e.g. the word ``here`` in the above example), and text that +describes the kind of parameter that is being passed (e.g. ``pos`` or ``options``) is enclosed in +angle brackets (``<`` and ``>``). Optional elements are enclosed in square brackets (``[`` and ``]``). +If the command takes an arbitrary number of elements, use ``...``, for example:: + + prioritize [] [ ...] + quickfort [,...] [,...] [] + +Examples +-------- + +If the only way to run the command is to type the command itself, then this section is not necessary. +Otherwise, please consider adding a section that shows some real, practical usage examples. For +many users, this will be the **only** section they will read. It is so important that it is a good +idea to include the ``Examples`` section **before** you describe any extended options your command +might take. Write examples for what you expect the popular use cases will be. Also be sure to write +examples showing specific, practical values being used for any parameter that takes a value or has +tricky formatting. + +Examples should go in their own subheading. The examples themselves should be organized as in +option 2 for Usage above. Here is an example ``Examples`` section:: + + Examples + -------- + + ``build-now`` + Completes all unsuspended construction jobs on the map. + ``build-now 37,20,154 here`` + Builds the unsuspended, unconstructed buildings in the box + bounded by the coordinate x=37,y=20,z=154 and the cursor. + +Options +------- + +The options header should follow the examples, with each option in the same format as the +examples:: + + Options + ------- + + ``-h``, ``--help`` + Show help text. + ``-l``, ``--quality `` + Set the quality of the architecture for built architected + builtings. + ``-q``, ``--quiet`` + Suppress informational output (error messages are still + printed). + +Note that for parameters that have both short and long forms, any values that those options +take only need to be specified once (e.g. ````). + +External scripts and plugins +============================ + +Scripts and plugins distributed separately from DFHack's release packages don't have the +opportunity to add their documentation to the rendered HTML or text output. However, these +scripts and plugins can use a different mechanism to at least make their help text available +in-game. + +Note that since help text for external scripts and plugins is not rendered by Sphinx, +it should be written in plain text. Any reStructuredText markup will not be processed and, +if present, will be shown verbatim to the player (which is probably not what you want). + +For external scripts, the short description comes from a comment on the first line +(the comment marker and extra whitespace is stripped). For Lua, this would look like: + +.. code-block:: lua + + -- A short description of my cool script. + +and for Ruby scripts it would look like: + +.. code-block:: ruby + + # A short description of my cool script. + +The main help text for an external script needs to appear between two markers. For +Lua, these markers are ``[====[`` and ``]====]``, and for Ruby they are ``=begin`` and +``=end``. The documentation standards above still apply to external tools, but there is +no need to include backticks for links or monospaced fonts. Here is a Lua example for an +entire script header:: + + -- Inventory management for adventurers. + -- [====[ + gui/adv-inventory + ================= + + Tags: adventure | items + + Allows you to quickly move items between containers. This + includes yourself and any followers you have. + + Usage + ----- + + gui/adv-inventory [] + + Examples + -------- + + gui/adv-inventory + Opens the GUI with nothing preselected + + gui/adv-inventory take-all + Opens the GUI with all container items already selected and + ready to move into the adventurer's inventory. + + Options + ------- + + take-all + Starts the GUI with container items pre-selected + + give-all + Starts the GUI with your own items pre-selected + ]====] + +For external plugins, help text for provided commands can be passed as the ``usage`` +parameter when registering the commands with the ``PluginCommand`` constructor. There +is currently no way for associating help text with the plugin itself, so any +information about what the plugin does when enabled should be combined into the command +help. Required dependencies ===================== @@ -145,10 +354,10 @@ Required dependencies .. highlight:: shell In order to build the documentation, you must have Python with Sphinx -version |sphinx_min_version| or later. Python 3 is recommended. +version |sphinx_min_version| or later and Python 3. When installing Sphinx from OS package managers, be aware that there is -another program called Sphinx, completely unrelated to documentation management. +another program called "Sphinx", completely unrelated to documentation management. Be sure you are installing the right Sphinx; it may be called ``python-sphinx``, for example. To avoid doubt, ``pip`` can be used instead as detailed below. @@ -162,13 +371,12 @@ For more detailed platform-specific instructions, see the sections below: :local: :backlinks: none - Linux ----- Most Linux distributions will include Python by default. If not, start by -installing Python (preferably Python 3). On Debian-based distros:: +installing Python 3. On Debian-based distros:: - sudo apt install python3 + sudo apt install python3 Check your package manager to see if Sphinx |sphinx_min_version| or later is available. On Debian-based distros, this package is named ``python3-sphinx``. @@ -177,11 +385,11 @@ want to use a newer Sphinx version (which may result in faster builds), you can install Sphinx through the ``pip`` package manager instead. On Debian-based distros, you can install pip with:: - sudo apt install python3-pip + sudo apt install python3-pip Once pip is available, you can then install Sphinx with:: - pip3 install sphinx + pip3 install sphinx If you run this as an unprivileged user, it may install a local copy of Sphinx for your user only. The ``sphinx-build`` executable will typically end up in @@ -196,34 +404,23 @@ macOS has Python 2.7 installed by default, but it does not have the pip package You can install Homebrew's Python 3, which includes pip, and then install the latest Sphinx using pip:: - brew install python3 - pip3 install sphinx - -Alternatively, you can simply install Sphinx directly from Homebrew:: - - brew install sphinx-doc - -This will install Sphinx for macOS's system Python 2.7, without needing pip. - -Either method works; if you plan to use Python for other purposes, it might best -to install Homebrew's Python 3 so that you have the latest Python as well as pip. -If not, just installing sphinx-doc for macOS's system Python 2.7 is fine. - + brew install python3 + pip3 install sphinx Windows ------- Python for Windows can be downloaded `from python.org `_. -The latest version of Python 3 is recommended, as it includes pip already. +The latest version of Python 3 includes pip already. You can also install Python and pip through the Chocolatey package manager. After installing Chocolatey as outlined in the `Windows compilation instructions `, run the following command from an elevated (admin) command prompt (e.g. ``cmd.exe``):: - choco install python pip -y + choco install python pip -y Once you have pip available, you can install Sphinx with the following command:: - pip install sphinx + pip install sphinx Note that this may require opening a new (admin) command prompt if you just installed pip from the same command prompt. @@ -258,33 +455,51 @@ ways to do this: * On Windows, if you prefer to use the batch scripts, you can run ``generate-msvc-gui.bat`` and set ``BUILD_DOCS`` through the GUI. If you are running another file, such as ``generate-msvc-all.bat``, you will need to edit - it to add the flag. You can also run ``cmake`` on the command line, similar to - other platforms. + the batch script to add the flag. You can also run ``cmake`` on the command line, + similar to other platforms. -The generated documentation will be stored in ``docs/html`` in the root DFHack -folder, and will be installed to ``hack/docs`` when you next install DFHack in a -DF folder. +By default, both HTML and text docs are built by CMake. The generated +documentation is stored in ``docs/html`` and ``docs/text`` (respectively) in the +root DFHack folder, and they will both be installed to ``hack/docs`` when you +install DFHack. The html and txt files will intermingle, but will not interfere +with one another. Running Sphinx manually ----------------------- You can also build the documentation without running CMake - this is faster if -you only want to rebuild the documentation regardless of any code changes. There -is a ``docs/build.sh`` script provided for Linux and macOS that will run -essentially the same command that CMake runs when building the docs - see the -script for additional options. +you only want to rebuild the documentation regardless of any code changes. The +``docs/build.py`` script will build the documentation in any specified formats +(HTML only by default) using the same command that CMake runs when building the +docs. Run the script with ``--help`` to see additional options. + +Examples: + +* ``docs/build.py`` + Build just the HTML docs + +* ``docs/build.py html text`` + Build both the HTML and text docs + +* ``docs/build.py --clean`` + Build HTML and force a clean build (all source files are re-read) -To build the documentation with default options, run the following command from -the root DFHack folder:: +The resulting documentation will be stored in ``docs/html`` and/or ``docs/text``. + +Alternatively, you can run Sphinx manually with:: sphinx-build . docs/html -The resulting documentation will be stored in ``docs/html`` (you can specify -a different path when running ``sphinx-build`` manually, but be warned that -Sphinx may overwrite existing files in this folder). +or, to build plain-text output:: + + sphinx-build -b text . docs/text Sphinx has many options to enable clean builds, parallel builds, logging, and -more - run ``sphinx-build --help`` for details. +more - run ``sphinx-build --help`` for details. If you specify a different +output path, be warned that Sphinx may overwrite existing files in the output +folder. Also be aware that when running ``sphinx-build`` directly, the +``docs/html`` folder may be polluted with intermediate build files that normally +get written in the cmake ``build`` directory. Building a PDF version ---------------------- @@ -295,10 +510,11 @@ want to build a PDF version locally, you will need ``pdflatex``, which is part of a TeX distribution. The following command will then build a PDF, located in ``docs/pdf/latex/DFHack.pdf``, with default options:: - sphinx-build -M latexpdf . docs/pdf + docs/build.py pdf -There is a ``docs/build-pdf.sh`` script provided for Linux and macOS that runs -this command for convenience - see the script for additional options. +Alternatively, you can run Sphinx manually with:: + + sphinx-build -M latexpdf . docs/pdf .. _build-changelog: @@ -318,17 +534,16 @@ changelogs are combined as part of the changelog build process: * ``scripts/changelog.txt`` for changes made to scripts in the ``scripts`` repo * ``library/xml/changelog.txt`` for changes made in the ``df-structures`` repo -Building the changelogs generates two files: ``docs/_auto/news.rst`` and -``docs/_auto/news-dev.rst``. These correspond to `changelog` and `dev-changelog` -and contain changes organized by stable and development DFHack releases, -respectively. For example, an entry listed under "0.44.05-alpha1" in +Building the changelogs generates two files: ``docs/changelogs/news.rst`` and +``docs/changelogs/news-dev.rst``. These correspond to `changelog` and +`dev-changelog` and contain changes organized by stable and development DFHack +releases, respectively. For example, an entry listed under "0.44.05-alpha1" in changelog.txt will be listed under that version in the development changelog as well, but under "0.44.05-r1" in the stable changelog (assuming that is the closest stable release after 0.44.05-alpha1). An entry listed under a stable release like "0.44.05-r1" in changelog.txt will be listed under that release in both the stable changelog and the development changelog. - Changelog syntax ---------------- diff --git a/docs/History.rst b/docs/History.rst index dc02d02b0..67b65f3bc 100644 --- a/docs/History.rst +++ b/docs/History.rst @@ -1350,7 +1350,7 @@ Misc improvements - `createitem`: in adventure mode it now defaults to the controlled unit as maker. - `autotrade`: adds "(Un)mark All" options to both panes of trade screen. - `mousequery`: several usability improvements; show live overlay (in menu area) of what's on the tile under the mouse cursor. -- `search`: workshop profile search added. +- `search-plugin`: workshop profile search added. - `dwarfmonitor`: add screen to summarise preferences of fortress dwarfs. - `getplants`: add autochop function to automate woodcutting. - `stocks`: added more filtering and display options. @@ -1510,7 +1510,7 @@ New binary patches New Plugins ----------- - `fix-armory`: Together with a couple of binary patches and the `gui/assign-rack` script, this plugin makes weapon racks, armor stands, chests and cabinets in properly designated barracks be used again for storage of squad equipment. -- `search`: Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. +- `search-plugin`: Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. - `automaterial`: Makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one. - Dfusion: Reworked to make use of lua modules, now all the scripts can be used from other scripts. - Eventful: A collection of lua events, that will allow new ways to interact with df world. diff --git a/docs/Installing.rst b/docs/Installing.rst index 5b564f45f..05db198b8 100644 --- a/docs/Installing.rst +++ b/docs/Installing.rst @@ -96,13 +96,14 @@ When you `download DFHack `, you will end up with a release archive operating system should have built-in utilities capable of extracting files from these archives. -The release archives contain several files and folders, including a ``hack`` -folder, a ``dfhack-config`` folder, and a ``dfhack.init-example`` file. 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. +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:: diff --git a/docs/Introduction.rst b/docs/Introduction.rst index 27c887a8b..798a9c3c8 100644 --- a/docs/Introduction.rst +++ b/docs/Introduction.rst @@ -1,7 +1,7 @@ .. _introduction: ######################### -Introduction and Overview +Introduction and overview ######################### DFHack is a Dwarf Fortress memory access library, distributed with @@ -21,14 +21,14 @@ enhancements by default, and more can be enabled. There are also many tools You can even add third-party scripts and plugins to do almost anything! For modders, DFHack makes many things possible. Custom reactions, new -interactions, magic creature abilities, and more can be set through `scripts-modtools` +interactions, magic creature abilities, and more can be set through `DFHack tools ` and custom raws. Non-standard DFHack scripts and inits can be stored in the raw directory, making raws or saves fully self-contained for distribution - or for coexistence in a single DF install, even with incompatible components. For developers, DFHack unites the various ways tools access DF memory and allows easier development of new tools. As an open-source project under -`various open-source licences `, contributions are welcome. +`various open-source licenses `, contributions are welcome. .. contents:: Contents @@ -54,12 +54,48 @@ used by the DFHack console. to be used this way. * Commands can also run at startup via `init files `, - on in batches at other times with the `script` command. + or in batches at other times with the `script` command. * Finally, some commands are persistent once enabled, and will sit in the background managing or changing some aspect of the game if you `enable` them. +.. note:: + In order to avoid user confusion, as a matter of policy all GUI tools + display the word :guilabel:`DFHack` on the screen somewhere while active. + + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. + +.. _support: Getting help ============ -There are several support channels available - see `support` for details. + +DFHack has several ways to get help online, including: + +- The `DFHack Discord server `__ +- The ``#dfhack`` IRC channel on `Libera `__ +- GitHub: + - for bugs, use the :issue:`issue tracker <>` + - for more open-ended questions, use the `discussion board + `__. Note that this is a + relatively-new feature as of 2021, but maintainers should still be + notified of any discussions here. +- The `DFHack thread on the Bay 12 Forum `__ + +Some additional, but less DFHack-specific, places where questions may be answered include: + +- The `/r/dwarffortress `_ questions thread on Reddit +- If you are using a starter pack, the relevant thread on the `Bay 12 Forum `__ - + see the :wiki:`DF Wiki ` for a list of these threads + +When reaching out to any support channels regarding problems with DFHack, please +remember to provide enough details for others to identify the issue. For +instance, specific error messages (copied text or screenshots) are helpful, as +well as any steps you can follow to reproduce the problem. Sometimes, log output +from ``stderr.log`` in the DF folder can point to the cause of issues as well. + +Some common questions may also be answered in documentation, including: + +- This documentation (`online here `__; search functionality available `here `) +- :wiki:`The DF wiki <>` diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 64b6dbf0d..5b6a30d93 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -30,6 +30,7 @@ implemented by Lua files located in :file:`hack/lua/*` :local: :depth: 2 +.. _lua-df: ========================= DF data structure wrapper @@ -524,6 +525,20 @@ Input & Output lock. Using an explicit ``dfhack.with_suspend`` will prevent this, forcing the function to block on input with lock held. +* ``dfhack.getCommandHistory(history_id, history_filename)`` + + Returns the list of strings in the specified history. Intended to be used by + GUI scripts that don't have access to a console and so can't use + ``dfhack.lineedit``. The ``history_id`` parameter is some unique string that + the script uses to identify its command history, such as the script's name. If + this is the first time the history with the given ``history_id`` is being + accessed, it is initialized from the given file. + +* ``dfhack.addCommandToHistory(history_id, history_filename, command)`` + + Adds a command to the specified history and saves the updated history to the + specified file. + * ``dfhack.interpreter([prompt[,history_filename[,env]]])`` Starts an interactive lua interpreter, using the specified prompt @@ -725,7 +740,7 @@ Functions: * ``dfhack.matinfo.decode(type,index)`` - Looks up material info for the given number pair; if not found, returs *nil*. + Looks up material info for the given number pair; if not found, returns *nil*. * ``....decode(matinfo)``, ``....decode(item)``, ``....decode(obj)`` @@ -837,6 +852,7 @@ can be omitted. * ``dfhack.getGitXmlExpectedCommit()`` * ``dfhack.gitXmlMatch()`` * ``dfhack.isRelease()`` +* ``dfhack.isPrerelease()`` Return information about the DFHack build in use. @@ -890,7 +906,6 @@ can be omitted. from ``dfhack.TranslateName()``), use ``print(dfhack.df2console(text))`` to ensure proper display on all platforms. - * ``dfhack.utf2df(string)`` Convert a string from UTF-8 to DF's CP437 encoding. @@ -914,9 +929,9 @@ can be omitted. The following examples are equivalent:: - dfhack.run_command({'ls', '-a'}) - dfhack.run_command('ls', '-a') - dfhack.run_command('ls -a') -- not recommended + dfhack.run_command({'ls', 'quick'}) + dfhack.run_command('ls', 'quick') + dfhack.run_command('ls quick') -- not recommended * ``dfhack.run_command_silent(command[, ...])`` @@ -1073,13 +1088,18 @@ Announcements Uses the type to look up options from announcements.txt, and calls the above operations accordingly. The units are used to call ``addCombatReportAuto``. +* ``dfhack.gui.getMousePos()`` + + Returns the map coordinates of the map tile the mouse is over as a table of + ``{x, y, z}``. If the cursor is not over the map, returns ``nil``. + Other ~~~~~ * ``dfhack.gui.getDepthAt(x, y)`` Returns the distance from the z-level of the tile at map coordinates (x, y) to - the closest ground z-level below. Defaults to 0, unless overriden by plugins. + the closest ground z-level below. Defaults to 0, unless overridden by plugins. Job module ---------- @@ -1849,7 +1869,7 @@ Among them are: - ``full_rectangle = true`` For buildings like stockpiles or farm plots that can normally - accomodate individual tile exclusion, forces an error if any + accommodate individual tile exclusion, forces an error if any tiles within the specified width*height are obstructed. - ``items = { item, item ... }``, or ``filters = { {...}, {...}... }`` @@ -1886,6 +1906,14 @@ Constructions module coordinates, designates it for removal, or instantly cancels the planned one. Returns *true, was_only_planned* if removed; or *false* if none found. +* ``dfhack.constructions.findAtTile(pos)``, or ``findAtTile(x,y,z)`` + + Returns the construction at the given position, or ``nil`` if there isn't one. + +* ``dfhack.constructions.insert(construction)`` + + Properly inserts the given construction into the game. Returns false and fails to + insert if there was already a construction at the position. Kitchen module -------------- @@ -1978,6 +2006,12 @@ Functions: Returns: *tile, tile_grayscale*, or *nil* if not found. The values can then be used for the *tile* field of *pen* structures. +* ``dfhack.screen.hideGuard(screen,callback[,args...])`` + + Removes screen from the viewscreen stack, calls the callback (with optional + supplied arguments), and then restores the screen on the top of the viewscreen + stack. + * ``dfhack.screen.clear()`` Fills the screen with blank background. @@ -2143,7 +2177,7 @@ Supported callbacks and fields are: Called when keyboard or mouse events are available. If any keys are pressed, the keys argument is a table mapping them to *true*. - Note that this refers to logical keybingings computed from real keys via + Note that this refers to logical keybindings computed from real keys via options; if multiple interpretations exist, the table will contain multiple keys. The table also may contain special keys: @@ -2468,7 +2502,7 @@ Core context specific functions: unit of time used, and may be one of ``'frames'`` (raw FPS), ``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``, ``'years'`` (in-game time). All timers other than - ``'frames'`` are cancelled when the world is unloaded, + ``'frames'`` are canceled when the world is unloaded, and cannot be queued until it is loaded again. Returns the timer id, or *nil* if unsuccessful due to world being unloaded. @@ -2643,9 +2677,15 @@ environment by the mandatory init file dfhack.lua: Walks a sequence of dereferences, which may be represented by numbers or strings. Returns *nil* if any of obj or indices is *nil*, or a numeric index is out of array bounds. +* ``ensure_key(t, key[, default_value])`` + + If the Lua table ``t`` doesn't include the specified ``key``, ``t[key]`` is + set to the value of ``default_value``, which defaults to ``{}`` if not set. + The new or existing value of ``t[key]`` is then returned. + .. _lua-string: -String class extentions +String class extensions ----------------------- DFHack extends Lua's basic string class to include a number of convenience @@ -2666,7 +2706,7 @@ functions. These are invoked just like standard string functions, e.g.:: * ``string:split([delimiter[, plain]])`` Split a string by the given delimiter. If no delimiter is specified, space - (``' '``) is used. The delimter is treated as a pattern unless a ``plain`` is + (``' '``) is used. The delimiter is treated as a pattern unless a ``plain`` is specified and set to ``true``. To treat multiple successive delimiter characters as a single delimiter, e.g. to avoid getting empty string elements, pass a pattern like ``' +'``. Be aware that passing patterns that match empty @@ -2943,10 +2983,10 @@ parameters. (e.g. combining the previous two examples into ``-abcdparam``) Long options focus on clarity. They are usually entire words, or several words - combined with hypens (``-``) or underscores (``_``). If they take an argument, - the argument can be separated from the option name by a space or an equals - sign (``=``). For example, the following two commandlines are equivalent: - ``yourscript --style pretty`` and ``yourscript --style=pretty``. + combined with hyphens (``-``) or underscores (``_``). If they take an + argument, the argument can be separated from the option name by a space or an + equals sign (``=``). For example, the following two commandlines are + equivalent: ``yourscript --style pretty`` and ``yourscript --style=pretty``. Another reason to use long options is if they represent an esoteric parameter that you don't expect to be commonly used and that you don't want to "waste" a @@ -3023,6 +3063,18 @@ parameters. function also verifies that the coordinates are valid for the current map and throws if they are not (unless ``skip_validation`` is set to true). +* ``argparse.positiveInt(arg, arg_name)`` + + Throws if ``tonumber(arg)`` is not a positive integer; otherwise returns + ``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error + messages more useful. + +* ``argparse.nonnegativeInt(arg, arg_name)`` + + Throws if ``tonumber(arg)`` is not a non-negative integer; otherwise returns + ``tonumber(arg)``. If ``arg_name`` is specified, it is used to make error + messages more useful. + dumper ====== @@ -3036,6 +3088,96 @@ function: argument specifies the indentation step size in spaces. For the other arguments see the original documentation link above. +helpdb +====== + +Unified interface for DFHack tool help text. Help text is read from the rendered +text in ``hack/docs/docs/``. If no rendered text exists, help is read from the +script sources (for scripts) or the string passed to the ``PluginCommand`` +initializer (for plugins). See `documentation` for details on how DFHack's help +system works. + +The database is lazy-loaded when an API method is called. It rechecks its help +sources for updates if an API method has not been called in the last 60 seconds. + +Each entry has several properties associated with it: + +- The entry name, which is the name of a plugin, script, or command provided by + a plugin. +- The entry types, which can be ``builtin``, ``plugin``, and/or ``command``. + Entries for built-in commands (like ``ls`` or ``quicksave``) are both type + ``builtin`` and ``command``. Entries named after plugins are type ``plugin``, + and if that plugin also provides a command with the same name as the plugin, + then the entry is also type ``command``. Entry types are returned as a map + of one or more of the type strings to ``true``. +- Short help, a the ~54 character description string. +- Long help, the entire contents of the associated help file. +- A list of tags that define the groups that the entry belongs to. + +* ``helpdb.is_entry(str)``, ``helpdb.is_entry(list)`` + + Returns whether the given string (or list of strings) is an entry (are all + entries) in the db. + +* ``helpdb.get_entry_types(entry)`` + + Returns the set (that is, a map of string to ``true``) of entry types for the + given entry. + +* ``helpdb.get_entry_short_help(entry)`` + + Returns the short (~54 character) description for the given entry. + +* ``helpdb.get_entry_long_help(entry[, width])`` + + Returns the full help text for the given entry. If ``width`` is specified, the + text will be wrapped at that width, preserving block indents. The wrap width + defaults to 80. + +* ``helpdb.get_entry_tags(entry)`` + + Returns the set of tag names for the given entry. + +* ``helpdb.is_tag(str)``, ``helpdb.is_tag(list)`` + + Returns whether the given string (or list of strings) is a (are all) valid tag + name(s). + +* ``helpdb.get_tags()`` + + Returns the full alphabetized list of valid tag names. + +* ``helpdb.get_tag_data(tag)`` + + Returns a list of entries that have the given tag. The returned table also + has a ``description`` key that contains the string description of the tag. + +* ``helpdb.search_entries([include[, exclude]])`` + + Returns a list of names for entries that match the given filters. The list is + alphabetized by their last path component, with populated path components + coming before null path components (e.g. ``autobutcher`` will immediately + follow ``gui/autobutcher``). + The optional ``include`` and ``exclude`` filter params are maps (or lists of + maps) with the following elements: + + :str: if a string, filters by the given substring. if a table of strings, + includes entry names that match any of the given substrings. + :tag: if a string, filters by the given tag name. if a table of strings, + includes entries that match any of the given tags. + :entry_type: if a string, matches entries of the given type. if a table of + strings, includes entries that match any of the given types. + + Elements in a map are ANDed together (e.g. if both ``str`` and ``tag`` are + specified, the match is on any of the ``str`` elements AND any of the ``tag`` + elements). + + If lists of filters are passed instead of a single map, the maps are ORed + (that is, the match succeeds if any of the filters match). + + If ``include`` is ``nil`` or empty, then all entries are included. If + ``exclude`` is ``nil`` or empty, then no entries are filtered out. + profiler ======== @@ -3045,7 +3187,7 @@ create profiler objects which can be used to profile and generate report. * ``profiler.newProfiler([variant[, sampling_frequency]])`` - Returns an profile object with ``variant`` either ``'time'`` or ``'call'``. + Returns a profile object with ``variant`` either ``'time'`` or ``'call'``. ``'time'`` variant takes optional ``sampling_frequency`` parameter to select lua instruction counts between samples. Default is ``'time'`` variant with ``10*1000`` frequency. @@ -3130,7 +3272,7 @@ Implements a trivial single-inheritance class system. The main difference is that attributes are processed as a separate initialization step, before any ``init`` methods are called. They - also make the directy relation between instance fields and constructor + also make the direct relation between instance fields and constructor arguments more explicit. * ``new_obj = Class{ foo = arg, bar = arg, ... }`` @@ -3140,8 +3282,8 @@ Implements a trivial single-inheritance class system. 1. An empty instance table is created, and its metatable set. 2. The ``preinit`` methods are called via ``invoke_before`` (see below) - with the table used as argument to the class. These methods are intended - for validating and tweaking that argument table. + with the table used as the argument to the class. These methods are + intended for validating and tweaking that argument table. 3. Declared ATTRS are initialized from the argument table or their default values. 4. The ``init`` methods are called via ``invoke_after`` with the argument table. This is the main constructor method. @@ -3192,6 +3334,83 @@ Predefined instance methods: To avoid confusion, these methods cannot be redefined. +.. _custom-raw-tokens: + +custom-raw-tokens +================= + +A module for reading custom tokens added to the raws by mods. + +* ``customRawTokens.getToken(typeDefinition, token)`` + + Where ``typeDefinition`` is a type definition struct as seen in ``df.global.world.raws`` + (e.g.: ``dfhack.gui.getSelectedItem().subtype``) and ``token`` is the name of the custom token + you want read. The arguments from the token will then be returned as strings using single or + multiple return values. If the token is not present, the result is false; if it is present + but has no arguments, the result is true. For ``creature_raw``, it checks against no caste. + For ``plant_raw``, it checks against no growth. + +* ``customRawTokens.getToken(typeInstance, token)`` + + Where ``typeInstance`` is a unit, entity, item, job, projectile, building, plant, or interaction + instance. Gets ``typeDefinition`` and then returns the same as ``getToken(typeDefinition, token)``. + For units, it gets the token from the race or caste instead if applicable. For plant growth items, + it gets the token from the plant or plant growth instead if applicable. For plants it does the same + but with growth number -1. + +* ``customRawTokens.getToken(raceDefinition, casteNumber, token)`` + + The same as ``getToken(unit, token)`` but with a specified race and caste. Caste number -1 is no caste. + +* ``customRawTokens.getToken(raceDefinition, casteName, token)`` + + The same as ``getToken(unit, token)`` but with a specified race and caste, using caste name (e.g. "FEMALE") + instead of number. + +* ``customRawTokens.getToken(plantDefinition, growthNumber, token)`` + + The same as ``getToken(plantGrowthItem, token)`` but with a specified plant and growth. Growth number -1 + is no growth. + +* ``customRawTokens.getToken(plantDefinition, growthName, token)`` + + The same as ``getToken(plantGrowthItem, token)`` but with a specified plant and growth, using growth name + (e.g. "LEAVES") instead of number. + +It is recommended to prefix custom raw tokens with the name of your mod to avoid duplicate behaviour where +two mods make callbacks that work on the same tag. + +Examples: + +* Using an eventful onReactionComplete hook, something for disturbing dwarven science:: + + if customRawTokens.getToken(reaction, "EXAMPLE_MOD_CAUSES_INSANITY") then + -- make unit who performed reaction go insane + +* Using an eventful onProjItemCheckMovement hook, a fast or slow-firing crossbow:: + + -- check projectile distance flown is zero, get firer, etc... + local multiplier = tonumber(customRawTokens.getToken(bow, "EXAMPLE_MOD_FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = firer.counters.think_counter * multiplier + +* Something for a script that prints help text about different types of units:: + + local unit = dfhack.gui.getSelectedUnit() + if not unit then return end + local helpText = customRawTokens.getToken(unit, "EXAMPLE_MOD_HELP_TEXT") + if helpText then print(helpText) end + +* Healing armour:: + + -- (per unit every tick) + local healAmount = 0 + for _, entry in ipairs(unit.inventory) do + if entry.mode == 2 then -- Worn + healAmount = healAmount + tonumber((customRawTokens.getToken(entry.item, "EXAMPLE_MOD_HEAL_AMOUNT")) or 0) + end + end + unit.body.blood_count = math.min(unit.body.blood_max, unit.body.blood_count + healAmount) + ================== In-game UI Library ================== @@ -3436,18 +3655,28 @@ The class defines the following attributes: :visible: Specifies that the view should be painted. :active: Specifies that the view should receive events, if also visible. :view_id: Specifies an identifier to easily identify the view among subviews. - This is reserved for implementation of top-level views, and should - not be used by widgets for their internal subviews. + This is reserved for use by script writers and should not be set by + library widgets for their internal subviews. +:on_focus: Called when the view gains keyboard focus; see ``setFocus()`` below. +:on_unfocus: Called when the view loses keyboard focus. It also always has the following fields: :subviews: Contains a table of all subviews. The sequence part of the table is used for iteration. In addition, subviews are also - indexed under their *view_id*, if any; see ``addviews()`` below. + indexed under their ``view_id``, if any; see ``addviews()`` below. +:parent_view: A reference to the parent view. This field is ``nil`` until the + view is added as a subview to another view with ``addviews()``. +:focus_group: The list of widgets in a hierarchy. This table is unique and empty + when a view is initialized, but is replaced by a shared table when + the view is added to a parent via ``addviews()``. If a view in the + focus group has keyboard focus, that widget can be accessed via + ``focus_group.cur``. +:focus: A boolean indicating whether the view currently has keyboard focus. These fields are computed by the layout process: -:frame_parent_rect: The ViewRect represeting the client area of the parent view. +:frame_parent_rect: The ViewRect representing the client area of the parent view. :frame_rect: The ``mkdims`` rect of the outer frame in parent-local coordinates. :frame_body: The ViewRect representing the body part of the View's own frame. @@ -3478,10 +3707,11 @@ The class has the following methods: Returns the dimensions of the ``frame_body`` rectangle. -* ``view:getMousePos()`` +* ``view:getMousePos([view_rect])`` - Returns the mouse *x,y* in coordinates local to the ``frame_body`` - rectangle if it is within its clip area, or nothing otherwise. + Returns the mouse *x,y* in coordinates local to the given ViewRect (or + ``frame_body`` if no ViewRect is passed) if it is within its clip area, + or nothing otherwise. * ``view:updateLayout([parent_rect])`` @@ -3537,8 +3767,27 @@ The class has the following methods: Calls ``onInput`` on all visible active subviews, iterating the ``subviews`` sequence in *reverse order*, so that topmost subviews get events first. - Returns *true* if any of the subviews handled the event. + Returns ``true`` if any of the subviews handled the event. If a subview within + the view's ``focus_group`` has focus and it and all of its ancestors are + active and visible, that subview is offered the chance to handle the input + before any other subviews. +* ``view:getPreferredFocusState()`` + + Returns ``false`` by default, but should be overridden by subclasses that may + want to take keyboard focus (if it is unclaimed) when they are added to a + parent view with ``addviews()``. + +* ``view:setFocus(focus)`` + + Sets the keyboard focus to the view if ``focus`` is ``true``, or relinquishes + keyboard focus if ``focus`` is ``false``. Views that newly acquire keyboard + focus will trigger the ``on_focus`` callback, and views that lose keyboard + focus will trigger the ``on_unfocus`` callback. While a view has focus, all + keyboard input is sent to that view before any of its siblings or parents. + Keyboard input is propagated as normal (see ``inputToSubviews()`` above) if + there is no view with focus or if the view with focus returns ``false`` from + its ``onInput()`` function. .. _lua-gui-screen: @@ -3581,7 +3830,9 @@ It adds the following methods: ``dfhack.screen.show``, calls ``self:onAboutToShow(parent)``. Note that ``onAboutToShow()`` can dismiss active screens, and therefore change the potential parent. If parent is not specified, this function will re-detect the - current topmost window after ``self:onAboutToShow(parent)`` returns. + current topmost window after ``self:onAboutToShow(parent)`` returns. This + function returns ``self`` as a convenience so you can write such code as + ``local view = MyScreen{params=val}:show()``. * ``screen:onAboutToShow(parent)`` *(for overriding)* @@ -3664,13 +3915,13 @@ Base of all the widgets. Inherits from View and has the following attributes: :r: gap between the right edges of the frame and the parent. :b: gap between the bottom edges of the frame and the parent. :w: maximum width of the frame. - :h: maximum heigth of the frame. + :h: maximum height of the frame. :xalign: X alignment of the frame. :yalign: Y alignment of the frame. First the ``l,t,r,b`` fields restrict the available area for placing the frame. If ``w`` and ``h`` are not specified or - larger then the computed area, it becomes the frame. Otherwise + larger than the computed area, it becomes the frame. Otherwise the smaller frame is placed within the are based on the ``xalign/yalign`` fields. If the align hints are omitted, they are assumed to be 0, 1, or 0.5 based on which of the ``l/r/t/b`` @@ -3698,7 +3949,7 @@ Base of all the widgets. Inherits from View and has the following attributes: Panel class ----------- -Inherits from Widget, and intended for grouping a number of subviews. +Inherits from Widget, and intended for framing and/or grouping subviews. Has attributes: @@ -3720,6 +3971,13 @@ Has attributes: height or become visible/hidden and you don't have to worry about recalculating subview positions. +* ``frame_style``, ``frame_title`` (default: nil) + If defined, a frame will be drawn around the panel and subviews will be inset + by 1. The attributes are identical to what is defined in the + `FramedScreen class`_. When using the predefined frame styles in the ``gui`` + module, remember to ``require`` the gui module and prefix the identifier with + ``gui.``, e.g. ``gui.GREY_LINE_FRAME``. + ResizingPanel class ------------------- @@ -3752,13 +4010,97 @@ Subclass of Widget; implements a simple edit field. Attributes: +:label_text: The optional text label displayed before the editable text. :text: The current contents of the field. :text_pen: The pen to draw the text with. :on_char: Input validation callback; used as ``on_char(new_char,text)``. If it returns false, the character is ignored. :on_change: Change notification callback; used as ``on_change(new_text,old_text)``. :on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. +:on_submit2: Shift-Enter key callback; if set the field will handle the key and call ``on_submit2(text)``. :key: If specified, the field is disabled until this key is pressed. Must be given as a string. +:key_sep: If specified, will be used to customize how the activation key is + displayed. See ``token.key_sep`` in the ``Label`` documentation below. +:modal: Whether the ``EditField`` should prevent input from propagating to other + widgets while it has focus. You can set this to ``true``, for example, + if you don't want a ``List`` widget to react to arrow keys while the + user is editing. +:ignore_keys: If specified, must be a list of key names that the edit field + should ignore. This is useful if you have plain string characters + that you want to use as hotkeys (like ``+``). + +An ``EditField`` will only read and process text input if it has keyboard focus. +It will automatically acquire keyboard focus when it is added as a subview to +a parent that has not already granted keyboard focus to another widget. If you +have more than one ``EditField`` on a screen, you can select which has focus by +calling ``setFocus(true)`` on the field object. + +If an activation ``key`` is specified, the ``EditField`` will manage its own +focus. It will start in the unfocused state, and pressing the activation key +will acquire keyboard focus. Pressing the Enter key will release keyboard focus +and then call the ``on_submit`` callback. Pressing the Escape key will also +release keyboard focus, but first it will restore the text that was displayed +before the ``EditField`` gained focus and then call the ``on_change`` callback. + +The ``EditField`` cursor can be moved to where you want to insert/remove text. +You can click where you want the cursor to move or you can use any of the +following keyboard hotkeys: + +- Left/Right arrow: move the cursor one character to the left or right. +- Ctrl-Left/Right arrow: move the cursor one word to the left or right. +- Alt-Left/Right arrow: move the cursor to the beginning/end of the text. + +Scrollbar class +--------------- + +This Widget subclass implements mouse-interactive scrollbars whose bar sizes +represent the amount of content currently visible in an associated display +widget (like a `Label class`_ or a `List class`_). By default they are styled +like scrollbars used in the vanilla DF help screens, but they are configurable. + +Scrollbars have the following attributes: + +:fg: Specifies the pen for the scroll icons and the active part of the bar. Default is ``COLOR_LIGHTGREEN``. +:bg: Specifies the pen for the background part of the scrollbar. Default is ``COLOR_CYAN``. +:on_scroll: A callback called when the scrollbar is scrolled. If the scrollbar is clicked, + the callback will be called with one of the following string parameters: "up_large", + "down_large", "up_small", or "down_small". If the scrollbar is dragged, the callback will + be called with the value that ``top_elem`` should be set to on the next call to + ``update()`` (see below). + +The Scrollbar widget implements the following methods: + +* ``scrollbar:update(top_elem, elems_per_page, num_elems)`` + + Updates the info about the widget that the scrollbar is paired with. + The ``top_elem`` param is the (one-based) index of the first visible element. + The ``elems_per_page`` param is the maximum number of elements that can be + shown at one time. The ``num_elems`` param is the total number of elements + that the paried widget can scroll through. If ``elems_per_page`` or + ``num_elems`` is not specified, the most recently specified value for these + parameters is used. The scrollbar will adjust its scrollbar size and position + according to the values passed to this function. + +Clicking on the arrows at the top or the bottom of a scrollbar will scroll an +associated widget by a small amount. Clicking on the unfilled portion of the +scrollbar above or below the filled area will scroll by a larger amount in that +direction. The amount of scrolling done in each case in determined by the +associated widget, and after scrolling is complete, the associated widget must +call ``scrollbar:update()`` with updated new display info. + +You can click and drag the scrollbar to scroll to a specific spot, or you can +click and hold on the end arrows or in the unfilled portion of the scrollbar to +scroll multiple times, just like in a normal browser scrollbar. The speed of +scroll events when the mouse button is held down is controlled by two global +variables: + +:``SCROLL_INITIAL_DELAY_MS``: The delay before the second scroll event. +:``SCROLL_DELAY_MS``: The delay between further scroll events. + +The defaults are 300 and 20, respectively, but they can be overridden by the +user in their :file:`dfhack-config/init/dfhack.init` file, for example:: + + :lua require('gui.widgets').SCROLL_DELAY_MS = 100 Label class ----------- @@ -3776,6 +4118,10 @@ It has the following attributes: :auto_width: Sets self.frame.w from the text width. :on_click: A callback called when the label is clicked (optional) :on_rclick: A callback called when the label is right-clicked (optional) +:scroll_keys: Specifies which keys the label should react to as a table. The table should map + keys to the number of lines to scroll as positive or negative integers or one of the keywords + supported by the ``scroll`` method. The default is up/down arrows scrolling by one line and page + up/down scrolling by one page. The text itself is represented as a complex structure, and passed to the object via the ``text`` argument of the constructor, or via @@ -3819,8 +4165,8 @@ containing newlines, or a table with the following possible fields: * ``token.key_sep = '...'`` Specifies the separator to place between the keybinding label produced - by ``token.key``, and the main text of the token. If the separator is - '()', the token is formatted as ``text..' ('..binding..')'``. Otherwise + by ``token.key``, and the main text of the token. If the separator starts with + '()', the token is formatted as ``text..' ('..binding..sep:sub(2)``. Otherwise it is simply ``binding..sep..text``. * ``token.enabled``, ``token.disabled`` @@ -3864,48 +4210,72 @@ The Label widget implements the following methods: Computes the width of the text. -TooltipLabel class +* ``label:scroll(nlines)`` + + This method takes the number of lines to scroll as positive or negative + integers or one of the following keywords: ``+page``, ``-page``, + ``+halfpage``, or ``-halfpage``. It returns the number of lines that were + actually scrolled (negative for scrolling up). + +WrappedLabel class ------------------ This Label subclass represents text that you want to be able to dynamically -hide, like help text in a tooltip. +wrap. This frees you from having to pre-split long strings into multiple lines +in the Label ``text`` list. It has the following attributes: -:tooltip: The string (or a table of strings or a function that returns a string - or a table of strings) to display. The text will be autowrapped to the - width of the widget, though any existing newlines will be kept. -:show_tooltip: Boolean or a callback; if true, the widget is visible. Defaults - to ``true``. -:indent: The number of spaces to indent the tooltip from the left margin. The - default is ``2``. +:text_to_wrap: The string (or a table of strings or a function that returns a + string or a table of strings) to display. The text will be autowrapped to + the width of the widget, though any existing newlines will be kept. +:indent: The number of spaces to indent the text from the left margin. The + default is ``0``. + +The displayed text is refreshed and rewrapped whenever the widget bounds change. +To force a refresh (to pick up changes in the string that ``text_to_wrap`` +returns, for example), all ``updateLayout()`` on this widget or on a widget that +contains this widget. + +TooltipLabel class +------------------ + +This WrappedLabel subclass represents text that you want to be able to +dynamically hide, like help text in a tooltip. + +It has the following attributes: + +:show_tooltip: Boolean or a callback; if true, the widget is visible. The ``text_pen`` attribute of the ``Label`` class is overridden with a default -of COLOR_GREY. +of ``COLOR_GREY`` and the ``indent`` attribute of the ``WrappedLabel`` class is +overridden with a default of ``2``. -Note that the text of the tooltip is only refreshed when the widget layout is -updated (i.e. ``updateLayout()`` is called on this widget or a widget that -contains this widget) and the tooltip needs to be rewrapped. +The text of the tooltip can be passed in the inherited ``text_to_wrap`` +attribute so it can be autowrapped, or in the basic ``text`` attribute if no +wrapping is required. HotkeyLabel class ----------------- This Label subclass is a convenience class for formatting text that responds to -a hotkey. +a hotkey or mouse click. It has the following attributes: :key: The hotkey keycode to display, e.g. ``'CUSTOM_A'``. +: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. :on_activate: If specified, it is the callback that will be called whenever - the hotkey is pressed. + the hotkey is pressed or the label is clicked. CycleHotkeyLabel class ---------------------- This Label subclass represents a group of related options that the user can -cycle through by pressing a specified hotkey. +cycle through by pressing a specified hotkey or clicking on the text. It has the following attributes: @@ -3948,7 +4318,8 @@ This is a specialized subclass of CycleHotkeyLabel that has two options: List class ---------- -The List widget implements a simple list with paging. +The List widget implements a simple list with paging. You can click on a list +item to call the ``on_submit`` callback for that item. It has the following attributes: @@ -3959,10 +4330,10 @@ It has the following attributes: :on_select: Selection change callback; called as ``on_select(index,choice)``. This is also called with *nil* arguments if ``setChoices`` is called with an empty list. -:on_submit: Enter key callback; if specified, the list reacts to the key - and calls it as ``on_submit(index,choice)``. -:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key - and calls it as ``on_submit2(index,choice)``. +:on_submit: Enter key or mouse click callback; if specified, the list reacts to the + key/click and calls the callback as ``on_submit(index,choice)``. +:on_submit2: Shift-Enter key or shift-mouse click callback; if specified, the list + reacts to the key/click and calls it as ``on_submit2(index,choice)``. :row_height: Height of every row in text lines. :icon_width: If not *nil*, the specified number of character columns are reserved to the left of the list item for the icons. @@ -3972,7 +4343,6 @@ Every list item may be specified either as a string, or as a lua table with the following fields: :text: Specifies the label text in the same format as the Label text. -:caption, [1]: Deprecated legacy aliases for **text**. :text_*: Reserved for internal use. :key: Specifies a keybinding that acts as a shortcut for the specified item. :icon: Specifies an icon string, or a pen to paint a single character. May be a callback. @@ -4000,6 +4370,11 @@ The list supports the following methods: Returns the selected *index, choice*, or nothing if the list is empty. +* ``list:getIdxUnderMouse()`` + + Returns the index of the list item under the mouse cursor, or nothing if the + list is empty or the mouse is not over a list item. + * ``list:getContentWidth()`` Returns the minimal width to draw all choices without clipping. @@ -4028,6 +4403,8 @@ supports: :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_below: If true, the edit field is placed below the list instead of above. :edit_key: If specified, the edit field is disabled until this key is pressed. +:edit_ignore_keys: If specified, will be passed to the filter edit field as its ``ignore_keys`` attribute. +:edit_on_char: If specified, will be passed to the filter edit field as its ``on_char`` attribute. :not_found_label: Specifies the text of the label shown when no items match the filter. The list choices may include the following attributes: @@ -4110,7 +4487,7 @@ blueprint files: The names of the functions are also available as the keys of the ``valid_phases`` table. -.. _building-hacks: +.. _building-hacks-api: building-hacks ============== @@ -4132,7 +4509,7 @@ Functions .. note:: this is the only mandatory field. :fix_impassible: - if true make impassible tiles impassible to liquids too + if true make impassable tiles impassable to liquids too :consume: how much machine power is needed to work. Disables reactions if not supplied enough and ``needs_power==1`` @@ -4156,7 +4533,7 @@ Functions :canBeRoomSubset: a flag if this building can be counted in room. 1 means it can, 0 means it can't and -1 default building behaviour :auto_gears: - a flag that automatically fills up gears and animate. It looks over building definition for gear icons and maps them. + a flag that automatically fills up gears and animations. It looks over the building definition for gear icons and maps them. Animate table also might contain: @@ -4167,7 +4544,7 @@ Functions ``getPower(building)`` returns two number - produced and consumed power if building can be modified and returns nothing otherwise -``setPower(building,produced,consumed)`` sets current productiona and consumption for a building. +``setPower(building,produced,consumed)`` sets current power production and consumption for a building. Examples -------- @@ -4201,7 +4578,7 @@ Native functions provided by the `buildingplan` plugin: * ``bool isPlanModeEnabled(df::building_type type, int16_t subtype, int32_t custom)`` returns whether the buildingplan UI is enabled for the specified building type. * ``bool isPlannedBuilding(df::building *bld)`` returns whether the given building is managed by buildingplan. * ``void addPlannedBuilding(df::building *bld)`` suspends the building jobs and adds the building to the monitor list. -* ``void doCycle()`` runs a check for whether buildlings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. +* ``void doCycle()`` runs a check for whether buildings in the monitor list can be assigned items and unsuspended. This method runs automatically twice a game day, so you only need to call it directly if you want buildingplan to do a check right now. * ``void scheduleCycle()`` schedules a cycle to be run during the next non-paused game frame. Can be called multiple times while the game is paused and only one cycle will be scheduled. burrows @@ -4249,7 +4626,7 @@ Native functions: The lua module file also re-exports functions from ``dfhack.burrows``. -.. _cxxrandom: +.. _cxxrandom-api: cxxrandom ========= @@ -4298,7 +4675,7 @@ Native functions (exported to Lua) adds a number to the sequence -- ``ShuffleSequence(rngID, seqID)`` +- ``ShuffleSequence(seqID, rngID)`` shuffles the number sequence @@ -4361,7 +4738,7 @@ Lua plugin classes ``bool_distribution`` ~~~~~~~~~~~~~~~~~~~~~ -- ``init(min, max)``: constructor +- ``init(chance)``: constructor - ``next(id)``: returns next boolean in the distribution - ``id``: engine ID to pass to native function @@ -4374,6 +4751,41 @@ Lua plugin classes - ``shuffle()``: shuffles the sequence of numbers - ``next()``: returns next number in the sequence +Usage +----- + +The basic idea is you create a number distribution which you generate random numbers along. The C++ relies +on engines keeping state information to determine the next number along the distribution. +You're welcome to try and (ab)use this knowledge for your RNG purposes. + +Example:: + + local rng = require('plugins.cxxrandom') + local norm_dist = rng.normal_distribution(6820,116) // avg, stddev + local engID = rng.MakeNewEngine(0) + -- somewhat reminiscent of the C++ syntax + print(norm_dist:next(engID)) + + -- a bit more streamlined + local cleanup = true --delete engine on cleanup + local number_generator = rng.crng:new(engID, cleanup, norm_dist) + print(number_generator:next()) + + -- simplified + print(rng.rollNormal(engID,6820,116)) + +The number sequences are much simpler. They're intended for where you need to randomly generate an index, perhaps in a loop for an array. You technically don't need an engine to use it, if you don't mind never shuffling. + +Example:: + + local rng = require('plugins.cxxrandom') + local g = rng.crng:new(rng.MakeNewEngine(0), true, rng.num_sequence:new(0,table_size)) + g:shuffle() + for _ = 1, table_size do + func(array[g:next()]) + end + + dig-now ======= @@ -4384,7 +4796,7 @@ The dig-now plugin exposes the following functions to Lua: command ``dig-now ``. See the `dig-now` documentation for details on default settings. -.. _eventful: +.. _eventful-api: eventful ======== @@ -4398,35 +4810,40 @@ on DF world events. List of events -------------- -1. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` +1. ``onReactionCompleting(reaction,reaction_product,unit,input_items,input_reagents,output_items,call_native)`` - Auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. + Is called once per reaction product, before the reaction has a chance to call native code for item creation. + Setting ``call_native.value=false`` cancels further processing: no items are created and ``onReactionComplete`` is not called. -2. ``onItemContaminateWound(item,unit,wound,number1,number2)`` +2. ``onReactionComplete(reaction,reaction_product,unit,input_items,input_reagents,output_items)`` + + Is called once per reaction product, when reaction finishes and has at least one product. + +3. ``onItemContaminateWound(item,unit,wound,number1,number2)`` Is called when item tries to contaminate wound (e.g. stuck in). -3. ``onProjItemCheckMovement(projectile)`` +4. ``onProjItemCheckMovement(projectile)`` Is called when projectile moves. -4. ``onProjItemCheckImpact(projectile,somebool)`` +5. ``onProjItemCheckImpact(projectile,somebool)`` Is called when projectile hits something. -5. ``onProjUnitCheckMovement(projectile)`` +6. ``onProjUnitCheckMovement(projectile)`` Is called when projectile moves. -6. ``onProjUnitCheckImpact(projectile,somebool)`` +7. ``onProjUnitCheckImpact(projectile,somebool)`` Is called when projectile hits something. -7. ``onWorkshopFillSidebarMenu(workshop,callnative)`` +8. ``onWorkshopFillSidebarMenu(workshop,callnative)`` Is called when viewing a workshop in 'q' mode, to populate reactions, useful for custom viewscreens for shops. -8. ``postWorkshopFillSidebarMenu(workshop)`` +9. ``postWorkshopFillSidebarMenu(workshop)`` Is called after calling (or not) native fillSidebarMenu(). Useful for job button tweaking (e.g. adding custom reactions) @@ -4451,7 +4868,7 @@ These events are straight from EventManager module. Each of them first needs to 4. ``onJobCompleted(job)`` - Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were cancelled by the user and workshop jobs that completed successfully. + Gets called when job is finished. The job that is passed to this function is a copy. Requires a frequency of 0 in order to distinguish between workshop jobs that were canceled by the user and workshop jobs that completed successfully. 5. ``onUnitDeath(unit_id)`` @@ -4494,7 +4911,7 @@ Functions 1. ``registerReaction(reaction_name,callback)`` - Simplified way of using onReactionComplete; the callback is function (same params as event). + Simplified way of using onReactionCompleting; the callback is function (same params as event). 2. ``removeNative(shop_name)`` @@ -4510,7 +4927,7 @@ Functions 5. ``registerSidebar(shop_name,callback)`` - Enable callback when sidebar for ``shop_name`` is drawn. Usefull for custom workshop views e.g. using gui.dwarfmode lib. Also accepts a ``class`` instead of function + Enable callback when sidebar for ``shop_name`` is drawn. Useful for custom workshop views e.g. using gui.dwarfmode lib. Also accepts a ``class`` instead of function as callback. Best used with ``gui.dwarfmode`` class ``WorkshopOverlay``. Examples @@ -4526,7 +4943,7 @@ Reaction complete example:: b=require "plugins.eventful" - b.registerReaction("LUA_HOOK_LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native) + b.registerReaction("LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native) local pos=copyall(unit.pos) -- spawn dragonbreath after 100 ticks dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) @@ -4547,13 +4964,13 @@ Integrated tannery:: b=require "plugins.eventful" b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") -.. _luasocket: +.. _luasocket-api: luasocket ========= A way to access csocket from lua. The usage is made similar to luasocket in vanilla lua distributions. Currently -only subset of functions exist and only tcp mode is implemented. +only a subset of the functions exist and only tcp mode is implemented. .. contents:: :local: @@ -4618,7 +5035,7 @@ A class with all the tcp functionality. Tries connecting to that address and port. Returns ``client`` object. -.. _map-render: +.. _map-render-api: map-render ========== @@ -4632,9 +5049,9 @@ Functions - ``render_map_rect(x,y,z,w,h)`` - returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background). + returns a table with w*h*4 entries of rendered tiles. The format is the same as ``df.global.gps.screen`` (tile,foreground,bright,background). -.. _pathable: +.. _pathable-api: pathable ======== @@ -4668,7 +5085,7 @@ sort The `sort ` plugin does not export any native functions as of now. Instead, it calls Lua code to perform the actual ordering of list items. -.. _xlsxreader: +.. _xlsxreader-api: xlsxreader ========== @@ -4749,7 +5166,7 @@ Scripts :local: Any files with the ``.lua`` extension placed into the :file:`hack/scripts` folder -are automatically made avaiable as DFHack commands. The command corresponding to +are automatically made available as DFHack commands. The command corresponding to a script is simply the script's filename, relative to the scripts folder, with the extension omitted. For example: @@ -4757,9 +5174,8 @@ the extension omitted. For example: * :file:`hack/scripts/gui/teleport.lua` is invoked as ``gui/teleport`` .. note:: - Scripts placed in subdirectories can be run as described above, but are not - listed by the `ls` command unless ``-a`` is specified. In general, scripts - should be placed in subfolders in the following situations: + In general, scripts should be placed in subfolders in the following + situations: * ``devel``: scripts that are intended exclusively for DFHack development, including examples, or scripts that are experimental and unstable @@ -4777,16 +5193,12 @@ folders can be added (for example, a copy of the :source-scripts:`scripts repository <>` for local development). See `script-paths` for more information on how to configure this behavior. -If the first line of the script is a one-line comment (starting with ``--``), -the content of the comment is used by the built-in ``ls`` and ``help`` commands. -Such a comment is required for every script in the official DFHack repository. - Scripts are read from disk when run for the first time, or if they have changed since the last time they were run. Each script has an isolated environment where global variables set by the script are stored. Values of globals persist across script runs in the same DF session. -See `devel/lua-example` for an example of this behavior. Note that local +See `devel/lua-example` for an example of this behavior. Note that ``local`` variables do *not* persist. Arguments are passed in to the scripts via the ``...`` built-in quasi-variable; @@ -4808,9 +5220,9 @@ General script API * ``dfhack.run_script(name[,args...])`` - Run a Lua script in hack/scripts/, as if it were started from the DFHack - command-line. The ``name`` argument should be the name of the script without - its extension, as it would be used on the command line. + Run a Lua script in :file:`hack/scripts/`, as if it were started from the + DFHack command-line. The ``name`` argument should be the name of the script + without its extension, as it would be used on the command line. Example: @@ -4830,10 +5242,10 @@ General script API * ``dfhack.script_help([name, [extension]])`` - Returns the contents of the embedded documentation of the specified script. - ``extension`` defaults to "lua", and ``name`` defaults to the name of the - script where this function was called. For example, the following can be used - to print the current script's help text:: + Returns the contents of the rendered (or embedded) `documentation` for the + specified script. ``extension`` defaults to "lua", and ``name`` defaults to + the name of the script where this function was called. For example, the + following can be used to print the current script's help text:: local args = {...} if args[1] == 'help' then @@ -4841,6 +5253,7 @@ General script API return end +.. _reqscript: Importing scripts ================= @@ -4895,12 +5308,12 @@ Importing scripts .. warning:: Avoid caching the table returned by ``reqscript()`` beyond storing it in - a local or global variable as in the example above. ``reqscript()`` is fast - for scripts that have previously been loaded and haven't changed. If you - retain a reference to a table returned by an old ``reqscript()`` call, this - may lead to unintended behavior if the location of the script changes - (e.g. if a save is loaded or unloaded, or if a `script path ` - is added in some other way). + a local variable as in the example above. ``reqscript()`` is fast for + scripts that have previously been loaded and haven't changed. If you retain + a reference to a table returned by an old ``reqscript()`` call, this may + lead to unintended behavior if the location of the script changes (e.g. if a + save is loaded or unloaded, or if a `script path ` is added in + some other way). .. admonition:: Tip diff --git a/docs/Memory-research.rst b/docs/Memory-research.rst index 9729072f5..c4c7aeb63 100644 --- a/docs/Memory-research.rst +++ b/docs/Memory-research.rst @@ -63,7 +63,7 @@ are not built by default, but can be built by setting the ``BUILD_DEVEL`` Scripts ~~~~~~~ -Several `development scripts ` can be useful for memory research. +Several `development tools ` can be useful for memory research. These include (but are not limited to): - `devel/dump-offsets` diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index f0c47fe96..f77b1b4f5 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -5,7 +5,7 @@ .. _dev-changelog: ##################### -Development Changelog +Development changelog ##################### This file contains changes grouped by the release (stable or development) in @@ -17,4 +17,4 @@ See `changelog` for a list of changes grouped by stable releases. :local: :depth: 1 -.. include:: /docs/_auto/news-dev.rst +.. include:: /docs/changelogs/news-dev.rst diff --git a/docs/NEWS.rst b/docs/NEWS.rst index 1283b079a..13d5e38de 100644 --- a/docs/NEWS.rst +++ b/docs/NEWS.rst @@ -17,11 +17,11 @@ See `dev-changelog` for a list of changes grouped by development releases. :local: :depth: 1 -.. include:: /docs/_auto/news.rst +.. include:: /docs/changelogs/news.rst Older Changelogs ================ -Are kept in a seperate file: `History` +Are kept in a separate file: `History` .. that's ``docs/History.rst``, if you're reading the raw text. diff --git a/docs/Plugins.rst b/docs/Plugins.rst deleted file mode 100644 index 48ecfe453..000000000 --- a/docs/Plugins.rst +++ /dev/null @@ -1,3197 +0,0 @@ -.. _plugins-index: - -############## -DFHack Plugins -############## - -DFHack plugins are the commands, that are compiled with a specific version. -They can provide anything from a small keybinding, to a complete overhaul of -game subsystems or the entire renderer. - -Most commands offered by plugins are listed here, -hopefully organised in a way you will find useful. - -.. contents:: Contents - :local: - :depth: 2 - -======== -Bugfixes -======== - -.. contents:: - :local: - -.. _fix-armory: - -fix-armory -========== -`This plugin requires a binpatch `, which has not -been available since DF 0.34.11 - -.. _fix-unit-occupancy: - -fix-unit-occupancy -================== -This plugin fixes issues with unit occupancy, notably phantom -"unit blocking tile" messages (:bug:`3499`). It can be run manually, or -periodically when enabled with the built-in enable/disable commands: - -:(no argument): Run the plugin once immediately, for the whole map. -:-h, here, cursor: Run immediately, only operate on the tile at the cursor -:-n, dry, dry-run: Run immediately, do not write changes to map -:interval : Run the plugin every ``X`` ticks (when enabled). - The default is 1200 ticks, or 1 day. - Ticks are only counted when the game is unpaused. - -.. _fixveins: - -fixveins -======== -Removes invalid references to mineral inclusions and restores missing ones. -Use this if you broke your embark with tools like `tiletypes`, or if you -accidentally placed a construction on top of a valuable mineral floor. - -.. _petcapRemover: - -petcapRemover -============= -Allows you to remove or raise the pet population cap. 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 allows -removing the second restriction and removing or raising the first. Pets still -require PET or PET_EXOTIC tags in order to reproduce. Type ``help petcapRemover`` -for exact usage. In order to make population more stable and avoid sudden -population booms as you go below the raised population cap, this plugin counts -pregnancies toward the new population cap. It can still go over, but only in the -case of multiple births. - -Usage: - -:petcapRemover: cause pregnancies now and schedule the next check -:petcapRemover every n: set how often in ticks the plugin checks for possible pregnancies -:petcapRemover cap n: set the new cap to n. if n = 0, no cap -:petcapRemover pregtime n: sets the pregnancy duration to n ticks. natural pregnancies are - 300000 ticks for the current race and 200000 for everyone else - -.. _tweak: - -tweak -===== -Contains various tweaks for minor bugs. - -One-shot subcommands: - -:clear-missing: Remove the missing status from the selected unit. - This allows engraving slabs for ghostly, but not yet - found, creatures. -:clear-ghostly: Remove the ghostly status from the selected unit and mark - it as dead. This allows getting rid of bugged ghosts - which do not show up in the engraving slab menu at all, - even after using clear-missing. It works, but is - potentially very dangerous - so use with care. Probably - (almost certainly) it does not have the same effects like - a proper burial. You've been warned. -:fixmigrant: Remove the resident/merchant flag from the selected unit. - Intended to fix bugged migrants/traders who stay at the - map edge and don't enter your fort. Only works for - dwarves (or generally the player's race in modded games). - Do NOT abuse this for 'real' caravan merchants (if you - really want to kidnap them, use 'tweak makeown' instead, - otherwise they will have their clothes set to forbidden etc). -:makeown: Force selected unit to become a member of your fort. - Can be abused to grab caravan merchants and escorts, even if - they don't belong to the player's race. Foreign sentients - (humans, elves) can be put to work, but you can't assign rooms - to them and they don't show up in DwarfTherapist because the - game treats them like pets. Grabbing draft animals from - a caravan can result in weirdness (animals go insane or berserk - and are not flagged as tame), but you are allowed to mark them - for slaughter. Grabbing wagons results in some funny spam, then - they are scuttled. - -Subcommands that persist until disabled or DF quits: - -.. comment: sort these alphabetically - -:adamantine-cloth-wear: Prevents adamantine clothing from wearing out while being worn (:bug:`6481`). -:advmode-contained: Works around :bug:`6202`, custom reactions with container inputs - in advmode. The issue is that the screen tries to force you to select - the contents separately from the container. This forcefully skips child - reagents. -:block-labors: Prevents labors that can't be used from being toggled -:burrow-name-cancel: Implements the "back" option when renaming a burrow, - which currently does nothing (:bug:`1518`) -:cage-butcher: Adds an option to butcher units when viewing cages with :kbd:`q` -:civ-view-agreement: Fixes overlapping text on the "view agreement" screen -:condition-material: Fixes a crash in the work order contition material list (:bug:`9905`). -:craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`). - With this tweak, items made from cloth and leather will gain a level of - wear every 20 years. -:do-job-now: Adds a job priority toggle to the jobs list -:embark-profile-name: Allows the use of lowercase letters when saving embark profiles -:eggs-fertile: Displays a fertility indicator on nestboxes -:farm-plot-select: Adds "Select all" and "Deselect all" options to farm plot menus -:fast-heat: Further improves temperature update performance by ensuring that 1 degree - of item temperature is crossed in no more than specified number of frames - when updating from the environment temperature. This reduces the time it - takes for stable-temp to stop updates again when equilibrium is disturbed. -:fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select - the current item (fully, in case of a stack), and scroll down one line. -:fps-min: Fixes the in-game minimum FPS setting -:hide-priority: Adds an option to hide designation priority indicators -:hotkey-clear: Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu) -:import-priority-category: - Allows changing the priority of all goods in a - category when discussing an import agreement with the liaison -:kitchen-prefs-all: Adds an option to toggle cook/brew for all visible items in kitchen preferences -:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences -:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`) -:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile -:military-color-assigned: - Color squad candidates already assigned to other squads in yellow/green - to make them stand out more in the list. - - .. image:: images/tweak-mil-color.png - -:military-stable-assign: - Preserve list order and cursor position when assigning to squad, - i.e. stop the rightmost list of the Positions page of the military - screen from constantly resetting to the top. -:nestbox-color: Fixes the color of built nestboxes -:reaction-gloves: Fixes reactions to produce gloves in sets with correct handedness (:bug:`6273`) -:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map -:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode, if the - map view is near enough to its previous position. -:stone-status-all: Adds an option to toggle the economic status of all stones -:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu -:tradereq-pet-gender: Displays pet genders on the trade request screen - -.. comment: sort these alphabetically - - -=============================== -Data inspection and visualizers -=============================== - -.. contents:: - :local: - -.. _blueprint: - -blueprint -========= -The ``blueprint`` command exports the structure of a portion of your fortress in -a blueprint file that you (or anyone else) can later play back with `quickfort`. - -Blueprints are ``.csv`` or ``.xlsx`` files created in the ``blueprints`` -subdirectory of your DF folder. The map area to turn into a blueprint is either -selected interactively with the ``blueprint gui`` command or, if the GUI is not -used, starts at the active cursor location and extends right and down for the -requested width and height. - -**Usage:** - - ``blueprint [] [ []] []`` - - ``blueprint gui [ []] []`` - -**Examples:** - -``blueprint gui`` - Runs `gui/blueprint`, the interactive frontend, where all configuration for - a ``blueprint`` command can be set visually and interactively. - -``blueprint 30 40 bedrooms`` - Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting - from the active cursor on the current z-level. Blueprints are written - sequentially to ``bedrooms.csv`` in the ``blueprints`` directory. - -``blueprint 30 40 bedrooms dig --cursor 108,100,150`` - Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and - the start of the blueprint area is set to a specific value instead of using - the in-game cursor position. - -**Positional Parameters:** - -:``width``: Width of the area (in tiles) to translate. -:``height``: Height of the area (in tiles) to translate. -:``depth``: Number of z-levels to translate. Positive numbers go *up* from the - cursor and negative numbers go *down*. Defaults to 1 if not specified, - indicating that the blueprint should only include the current z-level. -:``name``: Base name for blueprint files created in the ``blueprints`` - directory. If no name is specified, "blueprint" is used by default. The - string must contain some characters other than numbers so the name won't be - confused with the optional ``depth`` parameter. - -**Phases:** - -If you want to generate blueprints only for specific phases, add their names to -the commandline, anywhere after the blueprint base name. You can list multiple -phases; just separate them with a space. - -:``dig``: Generate quickfort ``#dig`` blueprints for digging natural stone. -:``carve``: Generate quickfort ``#dig`` blueprints for smoothing and carving. -:``build``: Generate quickfort ``#build`` blueprints for constructions and - buildings. -:``place``: Generate quickfort ``#place`` blueprints for placing stockpiles. -:``zone``: Generate quickfort ``#zone`` blueprints for designating zones. -:``query``: Generate quickfort ``#query`` blueprints for configuring rooms. - -If no phases are specified, phases are autodetected. For example, a ``#place`` -blueprint will be created only if there are stockpiles in the blueprint area. - -**Options:** - -``-c``, ``--cursor ,,``: - Use the specified map coordinates instead of the current cursor position for - the upper left corner of the blueprint range. If this option is specified, - then an active game map cursor is not necessary. -``-e``, ``--engrave``: - Record engravings in the ``carve`` phase. If this option is not specified, - engravings are ignored. -``-f``, ``--format ``: - Select the output format of the generated files. See the ``Output formats`` - section below for options. If not specified, the output format defaults to - "minimal", which will produce a small, fast ``.csv`` file. -``-h``, ``--help``: - Show command help text. -``-s``, ``--playback-start ,,``: - Specify the column and row offsets (relative to the upper-left corner of the - blueprint, which is ``1,1``) where the player should put the cursor when the - blueprint is played back with `quickfort`, in - `quickfort start marker ` format, for example: - ``10,10,central stairs``. If there is a space in the comment, you will need - to surround the parameter string in double quotes: ``"-s10,10,central stairs"`` or - ``--playback-start "10,10,central stairs"`` or - ``"--playback-start=10,10,central stairs"``. -``-t``, ``--splitby ``: - Split blueprints into multiple files. See the ``Splitting output into - multiple files`` section below for details. If not specified, defaults to - "none", which will create a standard quickfort - `multi-blueprint ` file. - -**Output formats:** - -Here are the values that can be passed to the ``--format`` flag: - -:``minimal``: - Creates ``.csv`` files with minimal file size that are fast to read and - write. This is the default. -:``pretty``: - Makes the blueprints in the ``.csv`` files easier to read and edit with a text - editor by adding extra spacing and alignment markers. - -**Splitting output into multiple files:** - -The ``--splitby`` flag can take any of the following values: - -:``none``: - Writes all blueprints into a single file. This is the standard format for - quickfort fortress blueprint bundles and is the default. -:``phase``: - Creates a separate file for each phase. - -.. _cursecheck: - -cursecheck -========== -Checks a single map tile or the whole map/world for cursed creatures (ghosts, -vampires, necromancers, werebeasts, zombies). - -With an active in-game cursor only the selected tile will be observed. -Without a cursor the whole map will be checked. - -By default cursed creatures will be only counted in case you just want to find -out if you have any of them running around in your fort. Dead and passive -creatures (ghosts who were put to rest, killed vampires, ...) are ignored. -Undead skeletons, corpses, bodyparts and the like are all thrown into the curse -category "zombie". Anonymous zombies and resurrected body parts will show -as "unnamed creature". - -Options: - -:detail: Print full name, date of birth, date of curse and some status - info (some vampires might use fake identities in-game, though). -:nick: Set the type of curse as nickname (does not always show up - in-game, some vamps don't like nicknames). -:all: Include dead and passive cursed creatures (can result in a quite - long list after having FUN with necromancers). -:verbose: Print all curse tags (if you really want to know it all). - -Examples: - -``cursecheck detail all`` - Give detailed info about all cursed creatures including deceased ones (no - in-game cursor). -``cursecheck nick`` - Give a nickname all living/active cursed creatures on the map(no in-game - cursor). - -.. note:: - - If you do a full search (with the option "all") former ghosts will show up - with the cursetype "unknown" because their ghostly flag is not set. - - Please report any living/active creatures with cursetype "unknown" - - this is most likely with mods which introduce new types of curses. - -.. _flows: - -flows -===== -A tool for checking how many tiles contain flowing liquids. If you suspect that -your magma sea leaks into HFS, you can use this tool to be sure without -revealing the map. - -.. _isoworldremote: - -isoworldremote -============== -A plugin that implements a `remote API ` used by Isoworld. - -.. _probe: - -probe -===== - -This plugin provides multiple commands that print low-level properties of the -selected objects. - -* ``probe``: prints some properties of the tile selected with :kbd:`k`. Some of - these properties can be passed into `tiletypes`. -* ``cprobe``: prints some properties of the unit selected with :kbd:`v`, as well - as the IDs of any worn items. `gui/gm-unit` and `gui/gm-editor` are more - complete in-game alternatives. -* ``bprobe``: prints some properties of the building selected with :kbd:`q` or - :kbd:`t`. `gui/gm-editor` is a more complete in-game alternative. - -.. _prospect: -.. _prospector: - -prospect -======== -Prints a big list of all the present minerals and plants. By default, only -the visible part of the map is scanned. - -Options: - -:all: Scan the whole map, as if it were revealed. -:value: Show material value in the output. Most useful for gems. -:hell: Show the Z range of HFS tubes. Implies 'all'. - -If prospect is called during the embark selection screen, it displays an estimate of -layer stone availability. - -.. note:: - - The results of pre-embark prospect are an *estimate*, and can at best be expected - to be somewhere within +/- 30% of the true amount; sometimes it does a lot worse. - Especially, it is not clear how to precisely compute how many soil layers there - will be in a given embark tile, so it can report a whole extra layer, or omit one - that is actually present. - -Options: - -:all: Also estimate vein mineral amounts. - -.. _remotefortressreader: - -remotefortressreader -==================== -An in-development plugin for realtime fortress visualisation. -See :forums:`Armok Vision <146473>`. - -.. _reveal: -.. _unreveal: -.. _revtoggle: -.. _revflood: -.. _revforget: - -reveal -====== -This reveals the map. By default, HFS will remain hidden so that the demons -don't spawn. You can use ``reveal hell`` to reveal everything. With hell revealed, -you won't be able to unpause until you hide the map again. If you really want -to unpause with hell revealed, use ``reveal demons``. - -Reveal also works in adventure mode, but any of its effects are negated once -you move. When you use it this way, you don't need to run ``unreveal``. - -Usage and related commands: - -:reveal: Reveal the whole map, except for HFS to avoid demons spawning -:reveal hell: Also show hell, but requires ``unreveal`` before unpausing -:reveal demon: Reveals everything and allows unpausing - good luck! -:unreveal: Reverts the effects of ``reveal`` -:revtoggle: Switches between ``reveal`` and ``unreveal`` -:revflood: Hide everything, then reveal tiles with a path to the cursor. - Note that tiles behind constructed walls are also revealed as a - workaround for :bug:`1871`. -:revforget: Discard info about what was visible before revealing the map. - Only useful where (e.g.) you abandoned with the fort revealed - and no longer want the data. - -.. _showmood: - -showmood -======== -Shows all items needed for the currently active strange mood. - -.. _spectate: - -spectate -======== -Simple plugin to automate following random dwarves. Most of the time things will -be weighted towards z-levels with the highest job activity. Simply enter the -``spectate`` command to toggle the plugin's state. - -.. _plugin-stonesense: - -stonesense -========== -An isometric visualizer that runs in a second window. Usage: - -:stonesense: Open the visualiser in a new window. Alias ``ssense``. -:ssense overlay: Overlay DF window, replacing the map area. - -For more information, see `the full Stonesense README `. - -=========================== -Job and Fortress management -=========================== - -.. contents:: - :local: - - -.. _autobutcher: - -autobutcher -=========== -Assigns lifestock for slaughter once it reaches a specific count. Requires that -you add the target race(s) to a watch list. Only tame units will be processed. - -Units will be ignored if they are: - -* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool - individually, or `zone` ``nick`` for groups) -* Caged, if and only if the cage is defined as a room (to protect zoos) -* Trained for war or hunting - -Creatures who will not reproduce (because they're not interested in the -opposite sex or have been gelded) will be butchered before those who will. -Older adults and younger children will be butchered first if the population -is above the target (default 1 male, 5 female kids and adults). Note that -you may need to set a target above 1 to have a reliable breeding population -due to asexuality etc. See `fix-ster` if this is a problem. - -Options: - -:example: Print some usage examples. -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep : Changes the timer to sleep X frames between runs. -:watch R: Start watching a race. R can be a valid race RAW id (ALPACA, - BIRD_TURKEY, etc) or a list of ids seperated by spaces or - the keyword 'all' which affects all races on your current - watchlist. -:unwatch R: Stop watching race(s). The current target settings will be - remembered. R can be a list of ids or the keyword 'all'. -:forget R: Stop watching race(s) and forget it's/their target settings. - R can be a list of ids or the keyword 'all'. -:autowatch: Automatically adds all new races (animals you buy from merchants, - tame yourself or get from migrants) to the watch list using - default target count. -:noautowatch: Stop auto-adding new races to the watchlist. -:list: Print the current status and watchlist. -:list_export: Print the commands needed to set up status and watchlist, - which can be used to import them to another save (see notes). -:target : - Set target count for specified race(s). The first four arguments - are the number of female and male kids, and female and male adults. - R can be a list of spceies ids, or the keyword ``all`` or ``new``. - ``R = 'all'``: change target count for all races on watchlist - and set the new default for the future. ``R = 'new'``: don't touch - current settings on the watchlist, only set the new default - for future entries. -:list_export: Print the commands required to rebuild your current settings. - -.. note:: - - Settings and watchlist are stored in the savegame, so that you can have - different settings for each save. If you want to copy your watchlist to - another savegame you must export the commands required to recreate your settings. - - To export, open an external terminal in the DF directory, and run - ``dfhack-run autobutcher list_export > filename.txt``. To import, load your - new save and run ``script filename.txt`` in the DFHack terminal. - - -Examples: - -You want to keep max 7 kids (4 female, 3 male) and max 3 adults (2 female, -1 male) of the race alpaca. Once the kids grow up the oldest adults will get -slaughtered. Excess kids will get slaughtered starting with the youngest -to allow that the older ones grow into adults. Any unnamed cats will -be slaughtered as soon as possible. :: - - autobutcher target 4 3 2 1 ALPACA BIRD_TURKEY - autobutcher target 0 0 0 0 CAT - autobutcher watch ALPACA BIRD_TURKEY CAT - autobutcher start - -Automatically put all new races onto the watchlist and mark unnamed tame units -for slaughter as soon as they arrive in your fort. Settings already made -for specific races will be left untouched. :: - - autobutcher target 0 0 0 0 new - autobutcher autowatch - autobutcher start - -Stop watching the races alpaca and cat, but remember the target count -settings so that you can use 'unwatch' without the need to enter the -values again. Note: 'autobutcher unwatch all' works, but only makes sense -if you want to keep the plugin running with the 'autowatch' feature or manually -add some new races with 'watch'. If you simply want to stop it completely use -'autobutcher stop' instead. :: - - autobutcher unwatch ALPACA CAT - -.. _autochop: - -autochop -======== -Automatically manage tree cutting designation to keep available logs withing given -quotas. - -Open the dashboard by running:: - - enable autochop - -The plugin must be activated (with :kbd:`d`-:kbd:`t`-:kbd:`c`-:kbd:`a`) before -it can be used. You can then set logging quotas and restrict designations to -specific burrows (with 'Enter') if desired. The plugin's activity cycle runs -once every in game day. - -If you add ``enable autochop`` to your dfhack.init there will be a hotkey to -open the dashboard from the chop designation menu. - -.. _autoclothing: - -autoclothing -============ - -Automatically manage clothing work orders, allowing the user to set how many of -each clothing type every citizen should have. Usage:: - - autoclothing [number] - -Examples: - -* ``autoclothing cloth "short skirt" 10``: - Sets the desired number of cloth short skirts available per citizen to 10. -* ``autoclothing cloth dress``: - Displays the currently set number of cloth dresses chosen per citizen. - -.. _autodump: - -autodump -======== -This plugin adds an option to the :kbd:`q` menu for stckpiles when `enabled `. -When autodump is enabled for a stockpile, any items placed in the stockpile will -automatically be designated to be dumped. - -Alternatively, you can use it to quickly move all items designated to be dumped. -Items are instantly moved to the cursor position, the dump flag is unset, -and the forbid flag is set, as if it had been dumped normally. -Be aware that any active dump item tasks still point at the item. - -Cursor must be placed on a floor tile so the items can be dumped there. - -Options: - -:destroy: Destroy instead of dumping. Doesn't require a cursor. - If called again before the game is resumed, cancels destroy. -:destroy-here: As ``destroy``, but only the selected item in the :kbd:`k` list, - or inside a container. - Alias ``autodump-destroy-here``, for keybindings. - :dfhack-keybind:`autodump-destroy-here` -:visible: Only process items that are not hidden. -:hidden: Only process hidden items. -:forbidden: Only process forbidden items (default: only unforbidden). - -``autodump-destroy-item`` destroys the selected item, which may be selected -in the :kbd:`k` list, or inside a container. If called again before the game -is resumed, cancels destruction of the item. -:dfhack-keybind:`autodump-destroy-item` - -.. _autofarm: - -autofarm -======== - -Automatically handles crop selection in farm plots based on current plant -stocks, and selects crops for planting if current stock is below a threshold. -Selected crops are dispatched on all farmplots. (Note that this plugin replaces -an older Ruby script of the same name.) - -Use the `enable` or `disable ` commands to change whether this plugin is -enabled. - -Usage: - -* ``autofarm runonce``: - Updates all farm plots once, without enabling the plugin -* ``autofarm status``: - Prints status information, including any applied limits -* ``autofarm default 30``: - Sets the default threshold -* ``autofarm threshold 150 helmet_plump tail_pig``: - Sets thresholds of individual plants - -.. _autogems: - -autogems -======== -Creates a new Workshop Order setting, automatically cutting rough gems -when `enabled `. - -See `gui/autogems` for a configuration UI. If necessary, the ``autogems-reload`` -command reloads the configuration file produced by that script. - -.. _autohauler: - -autohauler -========== -Autohauler is an autolabor fork. - -Rather than the all-of-the-above means of autolabor, autohauler will instead -only manage hauling labors and leave skilled labors entirely to the user, who -will probably use Dwarf Therapist to do so. - -Idle dwarves will be assigned the hauling labors; everyone else (including -those currently hauling) will have the hauling labors removed. This is to -encourage every dwarf to do their assigned skilled labors whenever possible, -but resort to hauling when those jobs are not available. This also implies -that the user will have a very tight skill assignment, with most skilled -labors only being assigned to just one dwarf, no dwarf having more than two -active skilled labors, and almost every non-military dwarf having at least -one skilled labor assigned. - -Autohauler allows skills to be flagged as to prevent hauling labors from -being assigned when the skill is present. By default this is the unused -ALCHEMIST labor but can be changed by the user. - -.. _autolabor: - -autolabor -========= -Automatically manage dwarf labors to efficiently complete jobs. -Autolabor tries to keep as many dwarves as possible busy but -also tries to have dwarves specialize in specific skills. - -The key is that, for almost all labors, once a dwarf begins a job it will finish that -job even if the associated labor is removed. Autolabor therefore frequently checks -which dwarf or dwarves should take new jobs for that labor, and sets labors accordingly. -Labors with equiptment (mining, hunting, and woodcutting), which are abandoned -if labors change mid-job, are handled slightly differently to minimise churn. - -.. warning:: - - *autolabor will override any manual changes you make to labors while - it is enabled, including through other tools such as Dwarf Therapist* - -Simple usage: - -:enable autolabor: Enables the plugin with default settings. (Persistent per fortress) -:disable autolabor: Disables the plugin. - -Anything beyond this is optional - autolabor works well on the default settings. - -By default, each labor is assigned to between 1 and 200 dwarves (2-200 for mining). -By default 33% of the workforce become haulers, who handle all hauling jobs as well -as cleaning, pulling levers, recovering wounded, removing constructions, and filling ponds. -Other jobs are automatically assigned as described above. Each of these settings can be adjusted. - -Jobs are rarely assigned to nobles with responsibilities for meeting diplomats or merchants, -never to the chief medical dwarf, and less often to the bookeeper and manager. - -Hunting is never assigned without a butchery, and fishing is never assigned without a fishery. - -For each labor a preference order is calculated based on skill, biased against masters of other -trades and excluding those who can't do the job. The labor is then added to the best -dwarves for that labor. We assign at least the minimum number of dwarfs, in order of preference, -and then assign additional dwarfs that meet any of these conditions: - -* The dwarf is idle and there are no idle dwarves assigned to this labor -* The dwarf has non-zero skill associated with the labor -* The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. - -We stop assigning dwarfs when we reach the maximum allowed. - -Advanced usage: - -:autolabor []: - Set number of dwarves assigned to a labor. -:autolabor haulers: Set a labor to be handled by hauler dwarves. -:autolabor disable: Turn off autolabor for a specific labor. -:autolabor reset: Return a labor to the default handling. -:autolabor reset-all: Return all labors to the default handling. -:autolabor list: List current status of all labors. -:autolabor status: Show basic status information. - -See `autolabor-artisans` for a differently-tuned setup. - -Examples: - -``autolabor MINE`` - Keep at least 5 dwarves with mining enabled. -``autolabor CUT_GEM 1 1`` - Keep exactly 1 dwarf with gemcutting enabled. -``autolabor COOK 1 1 3`` - Keep 1 dwarf with cooking enabled, selected only from the top 3. -``autolabor FEED_WATER_CIVILIANS haulers`` - Have haulers feed and water wounded dwarves. -``autolabor CUTWOOD disable`` - Turn off autolabor for wood cutting. - -.. _autonestbox: - -autonestbox -=========== -Assigns unpastured female egg-layers to nestbox zones. Requires that you create -pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox -must be in the top left corner. Only 1 unit will be assigned per pen, regardless -of the size. The age of the units is currently not checked, most birds grow up -quite fast. Egglayers who are also grazers will be ignored, since confining them -to a 1x1 pasture is not a good idea. Only tame and domesticated own units are -processed since pasturing half-trained wild egglayers could destroy your neat -nestbox zones when they revert to wild. When called without options autonestbox -will instantly run once. - -Options: - -:start: Start running every X frames (df simulation ticks). - Default: X=6000, which would be every 60 seconds at 100fps. -:stop: Stop running automatically. -:sleep: Must be followed by number X. Changes the timer to sleep X - frames between runs. - -.. _clean: - -clean -===== -Cleans all the splatter that get scattered all over the map, items and -creatures. In an old fortress, this can significantly reduce FPS lag. It can -also spoil your !!FUN!!, so think before you use it. - -Options: - -:map: Clean the map tiles. By default, it leaves mud and snow alone. -:units: Clean the creatures. Will also clean hostiles. -:items: Clean all the items. Even a poisoned blade. - -Extra options for ``map``: - -:mud: Remove mud in addition to the normal stuff. -:snow: Also remove snow coverings. - -.. _cleanowned: - -cleanowned -========== -Confiscates items owned by dwarfs. By default, owned food on the floor -and rotten items are confistacted and dumped. - -Options: - -:all: confiscate all owned items -:scattered: confiscated and dump all items scattered on the floor -:x: confiscate/dump items with wear level 'x' and more -:X: confiscate/dump items with wear level 'X' and more -:dryrun: a dry run. combine with other options to see what will happen - without it actually happening. - -Example: - -``cleanowned scattered X`` - This will confiscate rotten and dropped food, garbage on the floors and any - worn items with 'X' damage and above. - -.. _dwarfmonitor: - -dwarfmonitor -============ -Records dwarf activity to measure fort efficiency. - -Options: - -:enable : Start monitoring ``mode``. ``mode`` can be "work", "misery", - "weather", or "all". This will enable all corresponding widgets, - if applicable. -:disable : Stop monitoring ``mode``, and disable corresponding widgets, if applicable. -:stats: Show statistics summary -:prefs: Show dwarf preferences summary -:reload: Reload configuration file (``dfhack-config/dwarfmonitor.json``) - -:dfhack-keybind:`dwarfmonitor` - -Widget configuration: - -The following types of widgets (defined in :file:`hack/lua/plugins/dwarfmonitor.lua`) -can be displayed on the main fortress mode screen: - -:date: Show the in-game date -:misery: Show overall happiness levels of all dwarves -:weather: Show current weather (rain/snow) -:cursor: Show the current mouse cursor position - -The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the -positions and settings of all widgets displayed. This file should contain a -JSON object with the key ``widgets`` containing an array of objects - see the -included file in the ``dfhack-config`` folder for an example: - -.. code-block:: lua - - { - "widgets": [ - { - "type": "widget type (weather, misery, etc.)", - "x": X coordinate, - "y": Y coordinate - <...additional options...> - } - ] - } - -X and Y coordinates begin at zero (in the upper left corner of the screen). -Negative coordinates will be treated as distances from the lower right corner, -beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x -coordinate of 1 is the rightmost column. - -By default, the x and y coordinates given correspond to the leftmost tile of -the widget. Including an ``anchor`` option set to ``right`` will cause the -rightmost tile of the widget to be located at this position instead. - -Some widgets support additional options: - -* ``date`` widget: - - * ``format``: specifies the format of the date. The following characters - are replaced (all others, such as punctuation, are not modified) - - * ``Y`` or ``y``: The current year - * ``M``: The current month, zero-padded if necessary - * ``m``: The current month, *not* zero-padded - * ``D``: The current day, zero-padded if necessary - * ``d``: The current day, *not* zero-padded - - The default date format is ``Y-M-D``, per the ISO8601_ standard. - - .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 - -* ``cursor`` widget: - - * ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are - replaced with the corresponding cursor cordinates, while all other - characters are unmodified. - * ``show_invalid``: If set to ``true``, the mouse coordinates will both be - displayed as ``-1`` when the cursor is outside of the DF window; otherwise, - nothing will be displayed. - -.. _dwarfvet: - -dwarfvet -======== -Enables Animal Caretaker functionality - -Always annoyed your dragons become useless after a minor injury? Well, with -dwarfvet, your animals become first rate members of your fort. It can also -be used to train medical skills. - -Animals need to be treated in an animal hospital, which is simply a hospital -that is also an animal training zone. The console will print out a list on game -load, and whenever one is added or removed. Dwarfs must have the Animal Caretaker -labor to treat animals. Normal medical skills are used (and no experience is given -to the Animal Caretaker skill). - -Options: - -:enable: Enables Animal Caretakers to treat and manage animals -:disable: Turns off the plguin -:report: Reports all zones that the game considers animal hospitals - -.. _fix-job-postings: - -fix-job-postings ----------------- -This command fixes crashes caused by previous versions of workflow, mostly in -DFHack 0.40.24-r4, and should be run automatically when loading a world (but can -also be run manually if desired). - -.. _job: - -job -=== -Command for general job query and manipulation. - -Options: - -*no extra options* - Print details of the current job. The job can be selected - in a workshop, or the unit/jobs screen. -**list** - Print details of all jobs in the selected workshop. -**item-material ** - Replace the exact material id in the job item. -**item-type ** - Replace the exact item type id in the job item. - -.. _job-duplicate: - -job-duplicate -============= -In :kbd:`q` mode, when a job is highlighted within a workshop or furnace -building, calling ``job-duplicate`` instantly duplicates the job. - -:dfhack-keybind:`job-duplicate` - -.. _job-material: - -job-material -============ -Alter the material of the selected job. Similar to ``job item-material ...`` - -Invoked as:: - - job-material - -:dfhack-keybind:`job-material` - -* In :kbd:`q` mode, when a job is highlighted within a workshop or furnace, - changes the material of the job. Only inorganic materials can be used - in this mode. -* In :kbd:`b` mode, during selection of building components positions the cursor - over the first available choice with the matching material. - -.. _labormanager: - -labormanager -============ -Automatically manage dwarf labors to efficiently complete jobs. -Labormanager is derived from autolabor (above) but uses a completely -different approach to assigning jobs to dwarves. While autolabor tries -to keep as many dwarves busy as possible, labormanager instead strives -to get jobs done as quickly as possible. - -Labormanager frequently scans the current job list, current list of -dwarfs, and the map to determine how many dwarves need to be assigned to -what labors in order to meet all current labor needs without starving -any particular type of job. - -.. warning:: - - *As with autolabor, labormanager will override any manual changes you - make to labors while it is enabled, including through other tools such - as Dwarf Therapist* - -Simple usage: - -:enable labormanager: Enables the plugin with default settings. - (Persistent per fortress) - -:disable labormanager: Disables the plugin. - -Anything beyond this is optional - labormanager works fairly well on the -default settings. - -The default priorities for each labor vary (some labors are higher -priority by default than others). The way the plugin works is that, once -it determines how many of each labor is needed, it then sorts them by -adjusted priority. (Labors other than hauling have a bias added to them -based on how long it's been since they were last used, to prevent job -starvation.) The labor with the highest priority is selected, the "best -fit" dwarf for that labor is assigned to that labor, and then its -priority is *halved*. This process is repeated until either dwarfs or -labors run out. - -Because there is no easy way to detect how many haulers are actually -needed at any moment, the plugin always ensures that at least one dwarf -is assigned to each of the hauling labors, even if no hauling jobs are -detected. At least one dwarf is always assigned to construction removing -and cleaning because these jobs also cannot be easily detected. Lever -pulling is always assigned to everyone. Any dwarfs for which there are -no jobs will be assigned hauling, lever pulling, and cleaning labors. If -you use animal trainers, note that labormanager will misbehave if you -assign specific trainers to specific animals; results are only guaranteed -if you use "any trainer", and animal trainers will probably be -overallocated in any case. - -Labormanager also sometimes assigns extra labors to currently busy -dwarfs so that when they finish their current job, they will go off and -do something useful instead of standing around waiting for a job. - -There is special handling to ensure that at least one dwarf is assigned -to haul food whenever food is detected left in a place where it will rot -if not stored. This will cause a dwarf to go idle if you have no -storepiles to haul food to. - -Dwarfs who are unable to work (child, in the military, wounded, -handless, asleep, in a meeting) are entirely excluded from labor -assignment. Any dwarf explicitly assigned to a burrow will also be -completely ignored by labormanager. - -The fitness algorithm for assigning jobs to dwarfs generally attempts to -favor dwarfs who are more skilled over those who are less skilled. It -also tries to avoid assigning female dwarfs with children to jobs that -are "outside", favors assigning "outside" jobs to dwarfs who are -carrying a tool that could be used as a weapon, and tries to minimize -how often dwarfs have to reequip. - -Labormanager automatically determines medical needs and reserves health -care providers as needed. Note that this may cause idling if you have -injured dwarfs but no or inadequate hospital facilities. - -Hunting is never assigned without a butchery, and fishing is never -assigned without a fishery, and neither of these labors is assigned -unless specifically enabled. - -The method by which labormanager determines what labor is needed for a -particular job is complicated and, in places, incomplete. In some -situations, labormanager will detect that it cannot determine what labor -is required. It will, by default, pause and print an error message on -the dfhack console, followed by the message "LABORMANAGER: Game paused -so you can investigate the above message.". If this happens, please open -an issue on github, reporting the lines that immediately preceded this -message. You can tell labormanager to ignore this error and carry on by -typing ``labormanager pause-on-error no``, but be warned that some job may go -undone in this situation. - -Advanced usage: - -:labormanager enable: Turn plugin on. -:labormanager disable: Turn plugin off. -:labormanager priority : Set the priority value (see above) for labor to . -:labormanager reset : Reset the priority value of labor to its default. -:labormanager reset-all: Reset all priority values to their defaults. -:labormanager allow-fishing: Allow dwarfs to fish. *Warning* This tends to result in most of the fort going fishing. -:labormanager forbid-fishing: Forbid dwarfs from fishing. Default behavior. -:labormanager allow-hunting: Allow dwarfs to hunt. *Warning* This tends to result in as many dwarfs going hunting as you have crossbows. -:labormanager forbid-hunting: Forbid dwarfs from hunting. Default behavior. -:labormanager list: Show current priorities and current allocation stats. -:labormanager pause-on-error yes: Make labormanager pause if the labor inference engine fails. See above. -:labormanager pause-on-error no: Allow labormanager to continue past a labor inference engine failure. - -.. _nestboxes: - -nestboxes -========= - -Automatically scan for and forbid fertile eggs incubating in a nestbox. -Toggle status with `enable` or `disable `. - -.. _orders: - -orders -====== - -A plugin for manipulating manager orders. - -Subcommands: - -:export NAME: Exports the current list of manager orders to a file named ``dfhack-config/orders/NAME.json``. -:import NAME: Imports manager orders from a file named ``dfhack-config/orders/NAME.json``. -:clear: Deletes all manager orders in the current embark. -:sort: Sorts current manager orders by repeat frequency so daily orders don't - prevent other orders from ever being completed: one-time orders first, then - yearly, seasonally, monthly, then finally daily. - -You can keep your orders automatically sorted by adding the following command to -your ``onMapLoad.init`` file:: - - repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] - -.. _seedwatch: - -seedwatch -========= -Watches the numbers of seeds available and enables/disables seed and plant cooking. - -Each plant type can be assigned a limit. If their number falls below that limit, -the plants and seeds of that type will be excluded from cookery. -If the number rises above the limit + 20, then cooking will be allowed. - -The plugin needs a fortress to be loaded and will deactivate automatically otherwise. -You have to reactivate with 'seedwatch start' after you load the game. - -Options: - -:all: Adds all plants from the abbreviation list to the watch list. -:start: Start watching. -:stop: Stop watching. -:info: Display whether seedwatch is watching, and the watch list. -:clear: Clears the watch list. - -Examples: - -``seedwatch MUSHROOM_HELMET_PLUMP 30`` - add ``MUSHROOM_HELMET_PLUMP`` to the watch list, limit = 30 -``seedwatch MUSHROOM_HELMET_PLUMP`` - removes ``MUSHROOM_HELMET_PLUMP`` from the watch list. -``seedwatch all 30`` - adds all plants from the abbreviation list to the watch list, the limit being 30. - -.. _spotclean: - -spotclean -========= -Works like ``clean map snow mud``, but only for the tile under the cursor. Ideal -if you want to keep that bloody entrance ``clean map`` would clean up. - -:dfhack-keybind:`spotclean` - -.. _stockflow: - -stockflow -========= -Allows the fortress bookkeeper to queue jobs through the manager, -based on space or items available in stockpiles. - -Inspired by `workflow`. - -Usage: - -``stockflow enable`` - Enable the plugin. -``stockflow disable`` - Disable the plugin. -``stockflow fast`` - Enable the plugin in fast mode. -``stockflow list`` - List any work order settings for your stockpiles. -``stockflow status`` - Display whether the plugin is enabled. - -While enabled, the :kbd:`q` menu of each stockpile will have two new options: - -* :kbd:`j`: Select a job to order, from an interface like the manager's screen. -* :kbd:`J`: Cycle between several options for how many such jobs to order. - -Whenever the bookkeeper updates stockpile records, new work orders will -be placed on the manager's queue for each such selection, reduced by the -number of identical orders already in the queue. - -In fast mode, new work orders will be enqueued once per day, instead of -waiting for the bookkeeper. - -.. _tailor: - -tailor -====== - -Whenever the bookkeeper updates stockpile records, this plugin will scan every unit in the fort, -count up the number that are worn, and then order enough more made to replace all worn items. -If there are enough replacement items in inventory to replace all worn items, the units wearing them -will have the worn items confiscated (in the same manner as the `cleanowned` plugin) so that they'll -reeequip with replacement items. - -Use the `enable` and `disable ` commands to toggle this plugin's status, or run -``tailor status`` to check its current status. - -.. _workflow: - -workflow -======== -Manage control of repeat jobs. `gui/workflow` provides a simple -front-end integrated in the game UI. - -Usage: - -``workflow enable [option...], workflow disable [option...]`` - If no options are specified, enables or disables the plugin. - Otherwise, enables or disables any of the following options: - - - drybuckets: Automatically empty abandoned water buckets. - - auto-melt: Resume melt jobs when there are objects to melt. -``workflow jobs`` - List workflow-controlled jobs (if in a workshop, filtered by it). -``workflow list`` - List active constraints, and their job counts. -``workflow list-commands`` - List active constraints as workflow commands that re-create them; - this list can be copied to a file, and then reloaded using the - ``script`` built-in command. -``workflow count [cnt-gap]`` - Set a constraint, counting every stack as 1 item. -``workflow amount [cnt-gap]`` - Set a constraint, counting all items within stacks. -``workflow unlimit `` - Delete a constraint. -``workflow unlimit-all`` - Delete all constraints. - -Function --------- -When the plugin is enabled, it protects all repeat jobs from removal. -If they do disappear due to any cause, they are immediately re-added to their -workshop and suspended. - -In addition, when any constraints on item amounts are set, repeat jobs that -produce that kind of item are automatically suspended and resumed as the item -amount goes above or below the limit. The gap specifies how much below the limit -the amount has to drop before jobs are resumed; this is intended to reduce -the frequency of jobs being toggled. - -Constraint format ------------------ -The constraint spec consists of 4 parts, separated with ``/`` characters:: - - ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] - -The first part is mandatory and specifies the item type and subtype, -using the raw tokens for items (the same syntax used custom reaction inputs). -For more information, see :wiki:`this wiki page `. - -The subsequent parts are optional: - -- A generic material spec constrains the item material to one of - the hard-coded generic classes, which currently include:: - - PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN - METAL STONE SAND GLASS CLAY MILK - -- A specific material spec chooses the material exactly, using the - raw syntax for reaction input materials, e.g. ``INORGANIC:IRON``, - although for convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. - See the link above for more details on the unabbreviated raw syntax. - -- A comma-separated list of miscellaneous flags, which currently can - be used to ignore imported items or items below a certain quality. - -Constraint examples -------------------- -Keep metal bolts within 900-1000, and wood/bone within 150-200:: - - workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 - workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50 - -Keep the number of prepared food & drink stacks between 90 and 120:: - - workflow count FOOD 120 30 - workflow count DRINK 120 30 - -Make sure there are always 25-30 empty bins/barrels/bags:: - - workflow count BIN 30 - workflow count BARREL 30 - workflow count BOX/CLOTH,SILK,YARN 30 - -Make sure there are always 15-20 coal and 25-30 copper bars:: - - workflow count BAR//COAL 20 - workflow count BAR//COPPER 30 - -Produce 15-20 gold crafts:: - - workflow count CRAFTS//GOLD 20 - -Collect 15-20 sand bags and clay boulders:: - - workflow count POWDER_MISC/SAND 20 - workflow count BOULDER/CLAY 20 - -Make sure there are always 80-100 units of dimple dye:: - - workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 - -.. note:: - - In order for this to work, you have to set the material of the PLANT input - on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the `job item-material ` - command. Otherwise the plugin won't be able to deduce the output material. - -Maintain 10-100 locally-made crafts of exceptional quality:: - - workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 - -.. _workNow: - -workNow -======= -Don't allow dwarves to idle if any jobs are available. - -When workNow is active, every time the game pauses, DF will make dwarves -perform any appropriate available jobs. This includes when you one step -through the game using the pause menu. Usage: - -:workNow: print workNow status -:workNow 0: deactivate workNow -:workNow 1: activate workNow (look for jobs on pause, and only then) -:workNow 2: make dwarves look for jobs whenever a job completes - -.. _zone: - -zone -==== -Helps a bit with managing activity zones (pens, pastures and pits) and cages. - -:dfhack-keybind:`zone` - -Options: - -:set: Set zone or cage under cursor as default for future assigns. -:assign: Assign unit(s) to the pen or pit marked with the 'set' command. - If no filters are set a unit must be selected in the in-game ui. - Can also be followed by a valid zone id which will be set - instead. -:unassign: Unassign selected creature from it's zone. -:nick: Mass-assign nicknames, must be followed by the name you want - to set. -:remnick: Mass-remove nicknames. -:enumnick: Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). Must be - followed by the prefix to use in nicknames. -:tocages: Assign unit(s) to cages inside a pasture. -:uinfo: Print info about unit(s). If no filters are set a unit must - be selected in the in-game ui. -:zinfo: Print info about zone(s). If no filters are set zones under - the cursor are listed. -:verbose: Print some more info. -:filters: Print list of valid filter options. -:examples: Print some usage examples. -:not: Negates the next filter keyword. - -Filters: - -:all: Process all units (to be used with additional filters). -:count: Must be followed by a number. Process only n units (to be used - with additional filters). -:unassigned: Not assigned to zone, chain or built cage. -:minage: Minimum age. Must be followed by number. -:maxage: Maximum age. Must be followed by number. -:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, - etc). Negatable. -:caged: In a built cage. Negatable. -:own: From own civilization. Negatable. -:merchant: Is a merchant / belongs to a merchant. Should only be used for - pitting, not for stealing animals (slaughter should work). -:war: Trained war creature. Negatable. -:hunting: Trained hunting creature. Negatable. -:tamed: Creature is tame. Negatable. -:trained: Creature is trained. Finds war/hunting creatures as well as - creatures who have a training level greater than 'domesticated'. - If you want to specifically search for war/hunting creatures use - 'war' or 'hunting' Negatable. -:trainablewar: Creature can be trained for war (and is not already trained for - war/hunt). Negatable. -:trainablehunt: Creature can be trained for hunting (and is not already trained - for war/hunt). Negatable. -:male: Creature is male. Negatable. -:female: Creature is female. Negatable. -:egglayer: Race lays eggs. Negatable. -:grazer: Race is a grazer. Negatable. -:milkable: Race is milkable. Negatable. - -Usage with single units ------------------------ -One convenient way to use the zone tool is to bind the command 'zone assign' to -a hotkey, maybe also the command 'zone set'. Place the in-game cursor over -a pen/pasture or pit, use 'zone set' to mark it. Then you can select units -on the map (in 'v' or 'k' mode), in the unit list or from inside cages -and use 'zone assign' to assign them to their new home. Allows pitting your -own dwarves, by the way. - -Usage with filters ------------------- -All filters can be used together with the 'assign' command. - -Restrictions: It's not possible to assign units who are inside built cages -or chained because in most cases that won't be desirable anyways. -It's not possible to cage owned pets because in that case the owner -uncages them after a while which results in infinite hauling back and forth. - -Usually you should always use the filter 'own' (which implies tame) unless you -want to use the zone tool for pitting hostiles. 'own' ignores own dwarves unless -you specify 'race DWARF' (so it's safe to use 'assign all own' to one big -pasture if you want to have all your animals at the same place). 'egglayer' and -'milkable' should be used together with 'female' unless you have a mod with -egg-laying male elves who give milk or whatever. Merchants and their animals are -ignored unless you specify 'merchant' (pitting them should be no problem, -but stealing and pasturing their animals is not a good idea since currently they -are not properly added to your own stocks; slaughtering them should work). - -Most filters can be negated (e.g. 'not grazer' -> race is not a grazer). - -Mass-renaming -------------- -Using the 'nick' command you can set the same nickname for multiple units. -If used without 'assign', 'all' or 'count' it will rename all units in the -current default target zone. Combined with 'assign', 'all' or 'count' (and -further optional filters) it will rename units matching the filter conditions. - -Cage zones ----------- -Using the 'tocages' command you can assign units to a set of cages, for example -a room next to your butcher shop(s). They will be spread evenly among available -cages to optimize hauling to and butchering from them. For this to work you need -to build cages and then place one pen/pasture activity zone above them, covering -all cages you want to use. Then use 'zone set' (like with 'assign') and use -'zone tocages filter1 filter2 ...'. 'tocages' overwrites 'assign' because it -would make no sense, but can be used together with 'nick' or 'remnick' and all -the usual filters. - -Examples --------- -``zone assign all own ALPACA minage 3 maxage 10`` - Assign all own alpacas who are between 3 and 10 years old to the selected - pasture. -``zone assign all own caged grazer nick ineedgrass`` - Assign all own grazers who are sitting in cages on stockpiles (e.g. after - buying them from merchants) to the selected pasture and give them - the nickname 'ineedgrass'. -``zone assign all own not grazer not race CAT`` - Assign all own animals who are not grazers, excluding cats. -``zone assign count 5 own female milkable`` - Assign up to 5 own female milkable creatures to the selected pasture. -``zone assign all own race DWARF maxage 2`` - Throw all useless kids into a pit :) -``zone nick donttouchme`` - Nicknames all units in the current default zone or cage to 'donttouchme'. - Mostly intended to be used for special pastures or cages which are not marked - as rooms you want to protect from autobutcher. -``zone tocages count 50 own tame male not grazer`` - Stuff up to 50 owned tame male animals who are not grazers into cages built - on the current default zone. - -================ -Map modification -================ - -.. contents:: - :local: - -.. _3dveins: - -3dveins -======= -Removes all existing veins from the map and generates new ones using -3D Perlin noise, in order to produce a layout that smoothly flows between -Z levels. The vein distribution is based on the world seed, so running -the command for the second time should produce no change. It is best to -run it just once immediately after embark. - -This command is intended as only a cosmetic change, so it takes -care to exactly preserve the mineral counts reported by `prospect` ``all``. -The amounts of different layer stones may slightly change in some cases -if vein mass shifts between Z layers. - -The only undo option is to restore your save from backup. - -.. _alltraffic: - -alltraffic -========== -Set traffic designations for every single tile of the map - useful for resetting -traffic designations. See also `filltraffic`, `restrictice`, and `restrictliquids`. - -Options: - -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic - -.. _burrows: - -burrows -======= -Miscellaneous burrow control. Allows manipulating burrows and automated burrow -expansion while digging. - -Options: - -:enable feature ...: - Enable features of the plugin. -:disable feature ...: - Disable features of the plugin. -:clear-unit burrow burrow ...: - Remove all units from the burrows. -:clear-tiles burrow burrow ...: - Remove all tiles from the burrows. -:set-units target-burrow src-burrow ...: - Clear target, and adds units from source burrows. -:add-units target-burrow src-burrow ...: - Add units from the source burrows to the target. -:remove-units target-burrow src-burrow ...: - Remove units in source burrows from the target. -:set-tiles target-burrow src-burrow ...: - Clear target and adds tiles from the source burrows. -:add-tiles target-burrow src-burrow ...: - Add tiles from the source burrows to the target. -:remove-tiles target-burrow src-burrow ...: - Remove tiles in source burrows from the target. - - For these three options, in place of a source burrow it is - possible to use one of the following keywords: ABOVE_GROUND, - SUBTERRANEAN, INSIDE, OUTSIDE, LIGHT, DARK, HIDDEN, REVEALED - -Features: - -:auto-grow: When a wall inside a burrow with a name ending in '+' is dug - out, the burrow is extended to newly-revealed adjacent walls. - This final '+' may be omitted in burrow name args of commands above. - Digging 1-wide corridors with the miner inside the burrow is SLOW. - -.. _changeitem: - -changeitem -========== -Allows changing item material and base quality. By default the item currently -selected in the UI will be changed (you can select items in the 'k' list -or inside containers/inventory). By default change is only allowed if materials -is of the same subtype (for example wood<->wood, stone<->stone etc). But since -some transformations work pretty well and may be desired you can override this -with 'force'. Note that some attributes will not be touched, possibly resulting -in weirdness. To get an idea how the RAW id should look like, check some items -with 'info'. Using 'force' might create items which are not touched by -crafters/haulers. - -Options: - -:info: Don't change anything, print some info instead. -:here: Change all items at the cursor position. Requires in-game cursor. -:material, m: Change material. Must be followed by valid material RAW id. -:quality, q: Change base quality. Must be followed by number (0-5). -:force: Ignore subtypes, force change to new material. - -Examples: - -``changeitem m INORGANIC:GRANITE here`` - Change material of all items under the cursor to granite. -``changeitem q 5`` - Change currently selected item to masterpiece quality. - -.. _changelayer: - -changelayer -=========== -Changes material of the geology layer under cursor to the specified inorganic -RAW material. Can have impact on all surrounding regions, not only your embark! -By default changing stone to soil and vice versa is not allowed. By default -changes only the layer at the cursor position. Note that one layer can stretch -across lots of z levels. By default changes only the geology which is linked -to the biome under the cursor. That geology might be linked to other biomes -as well, though. Mineral veins and gem clusters will stay on the map. Use -`changevein` for them. - -tl;dr: You will end up with changing quite big areas in one go, especially if -you use it in lower z levels. Use with care. - -Options: - -:all_biomes: Change selected layer for all biomes on your map. - Result may be undesirable since the same layer can AND WILL - be on different z-levels for different biomes. Use the tool - 'probe' to get an idea how layers and biomes are distributed - on your map. -:all_layers: Change all layers on your map (only for the selected biome - unless 'all_biomes' is added). - Candy mountain, anyone? Will make your map quite boring, - but tidy. -:force: Allow changing stone to soil and vice versa. !!THIS CAN HAVE - WEIRD EFFECTS, USE WITH CARE!! - Note that soil will not be magically replaced with stone. - You will, however, get a stone floor after digging so it - will allow the floor to be engraved. - Note that stone will not be magically replaced with soil. - You will, however, get a soil floor after digging so it - could be helpful for creating farm plots on maps with no - soil. -:verbose: Give some details about what is being changed. -:trouble: Give some advice about known problems. - -Examples: - -``changelayer GRANITE`` - Convert layer at cursor position into granite. -``changelayer SILTY_CLAY force`` - Convert layer at cursor position into clay even if it's stone. -``changelayer MARBLE all_biomes all_layers`` - Convert all layers of all biomes which are not soil into marble. - -.. note:: - - * If you use changelayer and nothing happens, try to pause/unpause the game - for a while and try to move the cursor to another tile. Then try again. - If that doesn't help try temporarily changing some other layer, undo your - changes and try again for the layer you want to change. Saving - and reloading your map might also help. - * You should be fine if you only change single layers without the use - of 'force'. Still it's advisable to save your game before messing with - the map. - * When you force changelayer to convert soil to stone you might experience - weird stuff (flashing tiles, tiles changed all over place etc). - Try reverting the changes manually or even better use an older savegame. - You did save your game, right? - -.. _changevein: - -changevein -========== -Changes material of the vein under cursor to the specified inorganic RAW -material. Only affects tiles within the current 16x16 block - for veins and -large clusters, you will need to use this command multiple times. - -Example: - -``changevein NATIVE_PLATINUM`` - Convert vein at cursor position into platinum ore. - -.. _cleanconst: - -cleanconst -========== -Cleans up construction materials. - -This utility alters all constructions on the map so that they spawn their -building component when they are disassembled, allowing their actual -build items to be safely deleted. This can improve FPS in extreme situations. - -.. _deramp: - -deramp -====== -Removes all ramps designated for removal from the map. This is useful for -replicating the old channel digging designation. It also removes any and -all 'down ramps' that can remain after a cave-in (you don't have to designate -anything for that to happen). - -.. _dig: -.. _digv: -.. _digvx: -.. _digl: -.. _diglx: - -dig -=== -This plugin makes many automated or complicated dig patterns easy. - -Basic commands: - -:digv: Designate all of the selected vein for digging. -:digvx: Also cross z-levels, digging stairs as needed. Alias for ``digv x``. -:digl: Like ``digv``, for layer stone. Also supports an ``undo`` option - to remove designations, for if you accidentally set 50 levels at once. -:diglx: Also cross z-levels, digging stairs as needed. Alias for ``digl x``. - -:dfhack-keybind:`digv` - -.. note:: - - All commands implemented by the `dig` plugin (listed by ``ls dig``) support - specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``, - where ``#`` is a number from 1 to 7. If a priority is not specified, the - priority selected in-game is used as the default. - -.. _digcircle: - -digcircle -========= -A command for easy designation of filled and hollow circles. -It has several types of options. - -Shape: - -:hollow: Set the circle to hollow (default) -:filled: Set the circle to filled -:#: Diameter in tiles (default = 0, does nothing) - -Action: - -:set: Set designation (default) -:unset: Unset current designation -:invert: Invert designations already present - -Designation types: - -:dig: Normal digging designation (default) -:ramp: Ramp digging -:ustair: Staircase up -:dstair: Staircase down -:xstair: Staircase up/down -:chan: Dig channel - -After you have set the options, the command called with no options -repeats with the last selected parameters. - -Examples: - -``digcircle filled 3`` - Dig a filled circle with diameter = 3. -``digcircle`` - Do it again. - -.. _digexp: - -digexp -====== -This command is for :wiki:`exploratory mining `. - -There are two variables that can be set: pattern and filter. - -Patterns: - -:diag5: diagonals separated by 5 tiles -:diag5r: diag5 rotated 90 degrees -:ladder: A 'ladder' pattern -:ladderr: ladder rotated 90 degrees -:clear: Just remove all dig designations -:cross: A cross, exactly in the middle of the map. - -Filters: - -:all: designate whole z-level -:hidden: designate only hidden tiles of z-level (default) -:designated: Take current designation and apply pattern to it. - -After you have a pattern set, you can use ``expdig`` to apply it again. - -Examples: - -``expdig diag5 hidden`` - Designate the diagonal 5 patter over all hidden tiles -``expdig`` - Apply last used pattern and filter -``expdig ladder designated`` - Take current designations and replace them with the ladder pattern - -.. _digFlood: - -digFlood -======== -Automatically digs out specified veins as they are discovered. It runs once -every time a dwarf finishes a dig job. It will only dig out appropriate tiles -that are adjacent to the finished dig job. To add a vein type, use ``digFlood 1 [type]``. -This will also enable the plugin. To remove a vein type, use ``digFlood 0 [type] 1`` -to disable, then remove, then re-enable. - -Usage: - -:help digflood: detailed help message -:digFlood 0: disable the plugin -:digFlood 1: enable the plugin -:digFlood 0 MICROCLINE COAL_BITUMINOUS 1: - disable plugin, remove microcline and bituminous coal from monitoring, then re-enable the plugin -:digFlood CLEAR: remove all inorganics from monitoring -:digFlood digAll1: ignore the monitor list and dig any vein -:digFlood digAll0: disable digAll mode - -.. _digtype: - -digtype -======= -For every tile on the map of the same vein type as the selected tile, -this command designates it to have the same designation as the -selected tile. If the selected tile has no designation, they will be -dig designated. -If an argument is given, the designation of the selected tile is -ignored, and all appropriate tiles are set to the specified -designation. - -Options: - -:dig: -:channel: -:ramp: -:updown: up/down stairs -:up: up stairs -:down: down stairs -:clear: clear designation - -.. _filltraffic: - -filltraffic -=========== -Set traffic designations using flood-fill starting at the cursor. -See also `alltraffic`, `restrictice`, and `restrictliquids`. Options: - -:H: High Traffic -:N: Normal Traffic -:L: Low Traffic -:R: Restricted Traffic -:X: Fill across z-levels. -:B: Include buildings and stockpiles. -:P: Include empty space. - -Example: - -``filltraffic H`` - When used in a room with doors, it will set traffic to HIGH in just that room. - -.. _getplants: - -getplants -========= -This tool allows plant gathering and tree cutting by RAW ID. Specify the types -of trees to cut down and/or shrubs to gather by their plant names, separated -by spaces. - -Options: - -:``-t``: Tree: Select trees only (exclude shrubs) -:``-s``: Shrub: Select shrubs only (exclude trees) -:``-f``: Farming: Designate only shrubs that yield seeds for farming. Implies -s -:``-c``: Clear: Clear designations instead of setting them -:``-x``: eXcept: Apply selected action to all plants except those specified (invert - selection) -:``-a``: All: Select every type of plant (obeys ``-t``/``-s``/``-f``) -:``-v``: Verbose: Lists the number of (un)designations per plant -:``-n *``: Number: Designate up to * (an integer number) plants of each species - -Specifying both ``-t`` and ``-s`` or ``-f`` will have no effect. If no plant IDs are -specified, all valid plant IDs will be listed, with ``-t``, ``-s``, and ``-f`` -restricting the list to trees, shrubs, and farmable shrubs, respectively. - -.. note:: - - DF is capable of determining that a shrub has already been picked, leaving - an unusable structure part behind. This plugin does not perform such a check - (as the location of the required information has not yet been identified). - This leads to some shrubs being designated when they shouldn't be, causing a - plant gatherer to walk there and do nothing (except clearing the - designation). See :issue:`1479` for details. - - The implementation another known deficiency: it's incapable of detecting that - raw definitions that specify a seed extraction reaction for the structural part - but has no other use for it cannot actually yield any seeds, as the part is - never used (parts of :bug:`6940`, e.g. Red Spinach), even though DF - collects it, unless there's a workshop reaction to do it (which there isn't - in vanilla). - -.. _infiniteSky: - -infiniteSky -=========== -Automatically allocates new z-levels of sky at the top of the map as you build up, -or on request allocates many levels all at once. - -Usage: - -``infiniteSky n`` - Raise the sky by n z-levels. -``infiniteSky enable/disable`` - Enables/disables monitoring of constructions. If you build anything in the second to highest z-level, it will allocate one more sky level. This is so you can continue to build stairs upward. - -.. warning:: - - :issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. - Saving and loading after creating new z-levels should fix the problem. - -.. _liquids: - -liquids -======= -Allows adding magma, water and obsidian to the game. It replaces the normal -dfhack command line and can't be used from a hotkey. Settings will be remembered -as long as dfhack runs. Intended for use in combination with the command -``liquids-here`` (which can be bound to a hotkey). See also :issue:`80`. - -.. warning:: - - Spawning and deleting liquids can mess up pathing data and - temperatures (creating heat traps). You've been warned. - -.. note:: - - `gui/liquids` is an in-game UI for this script. - -Settings will be remembered until you quit DF. You can call `liquids-here` to execute -the last configured action, which is useful in combination with keybindings. - -Usage: point the DF cursor at a tile you want to modify and use the commands. - -If you only want to add or remove water or magma from one tile, -`source` may be easier to use. - -Commands --------- -Misc commands: - -:q: quit -:help, ?: print this list of commands -:: put liquid - -Modes: - -:m: switch to magma -:w: switch to water -:o: make obsidian wall instead -:of: make obsidian floors -:rs: make a river source -:f: flow bits only -:wclean: remove salt and stagnant flags from tiles - -Set-Modes and flow properties (only for magma/water): - -:s+: only add mode -:s.: set mode -:s-: only remove mode -:f+: make the spawned liquid flow -:f.: don't change flow state (read state in flow mode) -:f-: make the spawned liquid static - -Permaflow (only for water): - -:pf.: don't change permaflow state -:pf-: make the spawned liquid static -:pf[NS][EW]: make the spawned liquid permanently flow -:0-7: set liquid amount - -Brush size and shape: - -:p, point: Single tile -:r, range: Block with cursor at bottom north-west (any place, any size) -:block: DF map block with cursor in it (regular spaced 16x16x1 blocks) -:column: Column from cursor, up through free space -:flood: Flood-fill water tiles from cursor (only makes sense with wclean) - -.. _liquids-here: - -liquids-here ------------- -Run the liquid spawner with the current/last settings made in liquids (if no -settings in liquids were made it paints a point of 7/7 magma by default). - -Intended to be used as keybinding. Requires an active in-game cursor. - -.. _plant: - -plant -===== -A tool for creating shrubs, growing, or getting rid of them. - -Subcommands: - -:create: Creates a new sapling under the cursor. Takes a raw ID as argument - (e.g. TOWER_CAP). The cursor must be located on a dirt or grass floor tile. -:grow: Turns saplings into trees; under the cursor if a sapling is selected, - or every sapling on the map if the cursor is hidden. - -For mass effects, use one of the additional options: - -:shrubs: affect all shrubs on the map -:trees: affect all trees on the map -:all: affect every plant! - -.. _regrass: - -regrass -======= -Regrows all the grass. Not much to it ;) - -.. _restrictice: - -restrictice -=========== -Restrict traffic on all tiles on top of visible ice. -See also `alltraffic`, `filltraffic`, and `restrictliquids`. - -.. _restrictliquids: - -restrictliquids -=============== -Restrict traffic on all visible tiles with liquid. -See also `alltraffic`, `filltraffic`, and `restrictice`. - -.. _tiletypes: - -tiletypes -========= -Can be used for painting map tiles and is an interactive command, much like -`liquids`. Some properties of existing tiles can be looked up with `probe`. If -something goes wrong, `fixveins` may help. - -The tool works with two set of options and a brush. The brush determines which -tiles will be processed. First set of options is the filter, which can exclude -some of the tiles from the brush by looking at the tile properties. The second -set of options is the paint - this determines how the selected tiles are -changed. - -Both paint and filter can have many different properties including things like -general shape (WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, -etc.), state of 'designated', 'hidden' and 'light' flags. - -The properties of filter and paint can be partially defined. This means that -you can for example turn all stone fortifications into floors, preserving the -material:: - - filter material STONE - filter shape FORTIFICATION - paint shape FLOOR - -Or turn mineral vein floors back into walls:: - - filter shape FLOOR - filter material MINERAL - paint shape WALL - -The tool also allows tweaking some tile flags:: - - paint hidden 1 - paint hidden 0 - -This will hide previously revealed tiles (or show hidden with the 0 option). - -More recently, the tool supports changing the base material of the tile to -an arbitrary stone from the raws, by creating new veins as required. Note -that this mode paints under ice and constructions, instead of overwriting -them. To enable, use:: - - paint stone MICROCLINE - -This mode is incompatible with the regular ``material`` setting, so changing -it cancels the specific stone selection:: - - paint material ANY - -Since different vein types have different drop rates, it is possible to choose -which one to use in painting:: - - paint veintype CLUSTER_SMALL - -When the chosen type is ``CLUSTER`` (the default), the tool may automatically -choose to use layer stone or lava stone instead of veins if its material matches -the desired one. - -Any paint or filter option (or the entire paint or filter) can be disabled entirely by using the ANY keyword:: - - paint hidden ANY - paint shape ANY - filter material any - filter shape any - filter any - -You can use several different brushes for painting tiles: - -:point: a single tile -:range: a rectangular range -:column: a column ranging from current cursor to the first solid tile above -:block: a DF map block - 16x16 tiles, in a regular grid - -Example:: - - range 10 10 1 - -This will change the brush to a rectangle spanning 10x10 tiles on one z-level. -The range starts at the position of the cursor and goes to the east, south and -up. - -For more details, use ``tiletypes help``. - -.. _tiletypes-command: - -tiletypes-command ------------------ -Runs tiletypes commands, separated by ``;``. This makes it possible to change -tiletypes modes from a hotkey or via dfhack-run. - -Example:: - - tiletypes-command p any ; p s wall ; p sp normal - -This resets the paint filter to unsmoothed walls. - -.. _tiletypes-here: - -tiletypes-here --------------- -Apply the current tiletypes options at the in-game cursor position, including -the brush. Can be used from a hotkey. - -Options: - -:``-c``, ``--cursor ,,``: - Use the specified map coordinates instead of the current cursor position. If - this option is specified, then an active game map cursor is not necessary. -:``-h``, ``--help``: - Show command help text. -:``-q``, ``--quiet``: - Suppress non-error status output. - -.. _tiletypes-here-point: - -tiletypes-here-point --------------------- -Apply the current tiletypes options at the in-game cursor position to a single -tile. Can be used from a hotkey. - -This command supports the same options as `tiletypes-here` above. - -.. _tubefill: - -tubefill -======== -Fills all the adamantine veins again. Veins that were hollow will be left -alone. - -Options: - -:hollow: fill in naturally hollow veins too - -Beware that filling in hollow veins will trigger a demon invasion on top of -your miner when you dig into the region that used to be hollow. - - - -================= -Mods and Cheating -================= - -.. contents:: - :local: - -.. _add-spatter: - -add-spatter -=========== -This plugin makes reactions with names starting with ``SPATTER_ADD_`` -produce contaminants on the items instead of improvements. The plugin is -intended to give some use to all those poisons that can be bought from caravans, -so they're immune to being washed away by water or destroyed by `clean`. - -.. _adv-bodyswap: - -adv-bodyswap -============ -This allows taking control over your followers and other creatures in adventure -mode. For example, you can make them pick up new arms and armor and equip them -properly. - -Usage: - -* When viewing unit details, body-swaps into that unit. -* In the main adventure mode screen, reverts transient swap. - -:dfhack-keybind:`adv-bodyswap` - -.. _createitem: - -createitem -========== -Allows creating new items of arbitrary types and made of arbitrary materials. A -unit must be selected in-game to use this command. By default, items created are -spawned at the feet of the selected unit. - -Specify the item and material information as you would indicate them in -custom reaction raws, with the following differences: - -* Separate the item and material with a space rather than a colon -* If the item has no subtype, the ``:NONE`` can be omitted -* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or ``EGG``, - specify a ``CREATURE:CASTE`` pair instead of a material token. -* If the item is a ``PLANT_GROWTH``, specify a ``PLANT_ID:GROWTH_ID`` pair - instead of a material token. - -Corpses, body parts, and prepared meals cannot be created using this tool. - -To obtain the item and material tokens of an existing item, run -``createitem inspect``. Its output can be passed directly as arguments to -``createitem`` to create new matching items, as long as the item type is -supported. - -Examples: - -* Create 2 pairs of steel gauntlets:: - - createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2 - -* Create tower-cap logs:: - - createitem WOOD PLANT_MAT:TOWER_CAP:WOOD - -* Create bilberries:: - - createitem PLANT_GROWTH BILBERRY:FRUIT - -For more examples, :wiki:`see this wiki page `. - -To change where new items are placed, first run the command with a -destination type while an appropriate destination is selected. - -Options: - -:floor: Subsequent items will be placed on the floor beneath the selected unit's feet. -:item: Subsequent items will be stored inside the currently selected item. -:building: Subsequent items will become part of the currently selected building. - Good for loading traps; do not use with workshops (or deconstruct to use the item). - -.. _dig-now: - -dig-now -======= - -Instantly completes non-marker dig designations, modifying tile shapes and -creating boulders, ores, and gems as if a miner were doing the mining or -engraving. By default, the entire map is processed and boulder generation -follows standard game rules, but the behavior is configurable. - -Note that no units will get mining or engraving experience for the dug/engraved -tiles. - -Trees and roots are not currently handled by this plugin and will be skipped. -Requests for engravings are also skipped since they would depend on the skill -and creative choices of individual engravers. Other types of engraving (i.e. -smoothing and track carving) are handled. - -Usage:: - - dig-now [ []] [] - -Where the optional ```` pair can be used to specify the coordinate bounds -within which ``dig-now`` will operate. If they are not specified, ``dig-now`` -will scan the entire map. If only one ```` is specified, only the tile at -that coordinate is processed. - -Any ```` parameters can either be an ``,,`` triple (e.g. -``35,12,150``) or the string ``here``, which means the position of the active -game cursor should be used. - -Examples: - -``dig-now`` - Dig designated tiles according to standard game rules. - -``dig-now --clean`` - Dig designated tiles, but don't generate any boulders, ores, or gems. - -``dig-now --dump here`` - Dig tiles and dump all generated boulders, ores, and gems at the tile under - the game cursor. - -Options: - -:``-c``, ``--clean``: - Don't generate any boulders, ores, or gems. Equivalent to - ``--percentages 0,0,0,0``. -:``-d``, ``--dump ``: - Dump any generated items at the specified coordinates. If the tile at those - coordinates is open space or is a wall, items will be generated on the - closest walkable tile below. -:``-e``, ``--everywhere``: - Generate a boulder, ore, or gem for every tile that can produce one. - Equivalent to ``--percentages 100,100,100,100``. -:``-h``, ``--help``: - Show quick usage help text. -:``-p``, ``--percentages ,,,``: - Set item generation percentages for each of the tile categories. The - ``vein`` category includes both the large oval clusters and the long stringy - mineral veins. Default is ``25,33,100,100``. -:``-z``, ``--cur-zlevel``: - Restricts the bounds to the currently visible z-level. - -.. _diggingInvaders: - -diggingInvaders -=============== -Makes invaders dig or destroy constructions to get to your dwarves. - -To enable/disable the pluging, use: ``diggingInvaders (1|enable)|(0|disable)`` - -Basic usage: - -:add GOBLIN: registers the race GOBLIN as a digging invader. Case-sensitive. -:remove GOBLIN: unregisters the race GOBLIN as a digging invader. Case-sensitive. -:now: makes invaders try to dig now, if plugin is enabled -:clear: clears all digging invader races -:edgesPerTick n: makes the pathfinding algorithm work on at most n edges per tick. - Set to 0 or lower to make it unlimited. - -You can also use ``diggingInvaders setCost (race) (action) n`` to set the -pathing cost of particular action, or ``setDelay`` to set how long it takes. -Costs and delays are per-tile, and the table shows default values. - -============================== ======= ====== ================================= -Action Cost Delay Notes -============================== ======= ====== ================================= -``walk`` 1 0 base cost in the path algorithm -``destroyBuilding`` 2 1,000 delay adds to the job_completion_timer of destroy building jobs that are assigned to invaders -``dig`` 10,000 1,000 digging soil or natural stone -``destroyRoughConstruction`` 1,000 1,000 constructions made from boulders -``destroySmoothConstruction`` 100 100 constructions made from blocks or bars -============================== ======= ====== ================================= - - -.. _fastdwarf: - -fastdwarf -========= -Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly -and perform tasks quickly. Teledwarf makes dwarves move instantaneously, -but do jobs at the same speed. - -:fastdwarf 0: disables both (also ``0 0``) -:fastdwarf 1: enables speedydwarf and disables teledwarf (also ``1 0``) -:fastdwarf 2: sets a native debug flag in the game memory that implements an - even more aggressive version of speedydwarf. -:fastdwarf 0 1: disables speedydwarf and enables teledwarf -:fastdwarf 1 1: enables both - -See `superdwarf` for a per-creature version. - -.. _forceequip: - -forceequip -========== -Forceequip moves local items into a unit's inventory. It 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 (such as crowns) to any unit. - -For more information run ``forceequip help``. See also `modtools/equip-item`. - -.. _generated-creature-renamer: - -generated-creature-renamer -========================== -Automatically renames generated creatures, such as forgotten beasts, titans, -etc, to have raw token names that match the description given in-game. - -The ``list-generated`` command can be used to list the token names of all -generated creatures in a given save, with an optional ``detailed`` argument -to show the accompanying description. - -The ``save-generated-raws`` command will save a sample creature graphics file in -the Dwarf Fortress root directory, to use as a start for making a graphics set -for generated creatures using the new names that they get with this plugin. - -The new names are saved with the save, and the plugin, when enabled, only runs once -per save, unless there's an update. - -.. _lair: - -lair -==== -This command allows you to mark the map as a monster lair, preventing item -scatter on abandon. When invoked as ``lair reset``, it does the opposite. - -Unlike `reveal`, this command doesn't save the information about tiles - you -won't be able to restore state of real monster lairs using ``lair reset``. - -Options: - -:lair: Mark the map as monster lair -:lair reset: Mark the map as ordinary (not lair) - -.. _misery: - -misery -====== -When enabled, fake bad thoughts will be added to all dwarves. - -Usage: - -:misery enable n: enable misery with optional magnitude n. If specified, n must - be positive. -:misery n: same as "misery enable n" -:misery enable: same as "misery enable 1" -:misery disable: stop adding new negative thoughts. This will not remove - existing negative thoughts. Equivalent to "misery 0". -:misery clear: remove fake thoughts, even after saving and reloading. Does - not change factor. - -.. _mode: - -mode -==== -This command lets you see and change the game mode directly. - -.. warning:: - - Only use ``mode`` after making a backup of your save! - - Not all combinations are good for every situation and most of them will - produce undesirable results. There are a few good ones though. - -Examples: - - * You are in fort game mode, managing your fortress and paused. - * You switch to the arena game mode, *assume control of a creature* and then - * switch to adventure game mode(1). - You just lost a fortress and gained an adventurer. Alternatively: - - * You are in fort game mode, managing your fortress and paused at the esc menu. - * You switch to the adventure game mode, assume control of a creature, then save or retire. - * You just created a returnable mountain home and gained an adventurer. - -.. _power-meter: - -power-meter -=========== -The power-meter plugin implements a modified pressure plate that detects power being -supplied to gear boxes built in the four adjacent N/S/W/E tiles. - -The configuration front-end is implemented by `gui/power-meter`. - -.. _siege-engine: - -siege-engine -============ -Siege engines in DF haven't been updated since the game was 2D, and can -only aim in four directions. To make them useful above-ground, -this plugin allows you to: - -* link siege engines to stockpiles -* restrict operator skill levels (like workshops) -* load any object into a catapult, not just stones -* aim at a rectangular area in any direction, and across Z-levels - -The front-end is implemented by `gui/siege-engine`. - -.. _steam-engine: - -steam-engine -============ -The steam-engine plugin detects custom workshops with STEAM_ENGINE in -their token, and turns them into real steam engines. - -The vanilla game contains only water wheels and windmills as sources of -power, but windmills give relatively little power, and water wheels require -flowing water, which must either be a real river and thus immovable and -limited in supply, or actually flowing and thus laggy. - -Compared to the :wiki:`water reactor ` -exploit, steam engines make a lot of sense! - -Construction ------------- -The workshop needs water as its input, which it takes via a -passable floor tile below it, like usual magma workshops do. -The magma version also needs magma. - -Due to DFHack limits, the workshop will collapse over true open space. -However down stairs are passable but support machines, so you can use them. - -After constructing the building itself, machines can be connected -to the edge tiles that look like gear boxes. Their exact position -is extracted from the workshop raws. - -Like with collapse above, due to DFHack limits the workshop -can only immediately connect to machine components built AFTER it. -This also means that engines cannot be chained without intermediate -axles built after both engines. - -Operation ---------- -In order to operate the engine, queue the Stoke Boiler job (optionally -on repeat). A furnace operator will come, possibly bringing a bar of fuel, -and perform it. As a result, a "boiling water" item will appear -in the :kbd:`t` view of the workshop. - -.. note:: - - The completion of the job will actually consume one unit - of the appropriate liquids from below the workshop. This means - that you cannot just raise 7 units of magma with a piston and - have infinite power. However, liquid consumption should be slow - enough that water can be supplied by a pond zone bucket chain. - -Every such item gives 100 power, up to a limit of 300 for coal, -and 500 for a magma engine. The building can host twice that -amount of items to provide longer autonomous running. When the -boiler gets filled to capacity, all queued jobs are suspended; -once it drops back to 3+1 or 5+1 items, they are re-enabled. - -While the engine is providing power, steam is being consumed. -The consumption speed includes a fixed 10% waste rate, and -the remaining 90% are applied proportionally to the actual -load in the machine. With the engine at nominal 300 power with -150 load in the system, it will consume steam for actual -300*(10% + 90%*150/300) = 165 power. - -Masterpiece mechanism and chain will decrease the mechanical -power drawn by the engine itself from 10 to 5. Masterpiece -barrel decreases waste rate by 4%. Masterpiece piston and pipe -decrease it by further 4%, and also decrease the whole steam -use rate by 10%. - -Explosions ----------- -The engine must be constructed using barrel, pipe and piston -from fire-safe, or in the magma version magma-safe metals. - -During operation weak parts get gradually worn out, and -eventually the engine explodes. It should also explode if -toppled during operation by a building destroyer, or a -tantruming dwarf. - -Save files ----------- -It should be safe to load and view engine-using fortresses -from a DF version without DFHack installed, except that in such -case the engines won't work. However actually making modifications -to them, or machines they connect to (including by pulling levers), -can easily result in inconsistent state once this plugin is -available again. The effects may be as weird as negative power -being generated. - -.. _strangemood: - -strangemood -=========== -Creates a strange mood job the same way the game itself normally does it. - -Options: - -:-force: Ignore normal strange mood preconditions (no recent mood, minimum - moodable population, artifact limit not reached). -:-unit: Make the strange mood strike the selected unit instead of picking - one randomly. Unit eligibility is still enforced. -:-type : Force the mood to be of a particular type instead of choosing randomly based on happiness. - Valid values for T are "fey", "secretive", "possessed", "fell", and "macabre". -:-skill S: Force the mood to use a specific skill instead of choosing the highest moodable skill. - Valid values are "miner", "carpenter", "engraver", "mason", "tanner", "weaver", - "clothier", "weaponsmith", "armorsmith", "metalsmith", "gemcutter", "gemsetter", - "woodcrafter", "stonecrafter", "metalcrafter", "glassmaker", "leatherworker", - "bonecarver", "bowyer", and "mechanic". - -Known limitations: if the selected unit is currently performing a job, the mood will not be started. - -============== -Plugin Lua API -============== - -Some plugins consist solely of native libraries exposed to Lua. They are listed -in the `lua-api` file under `lua-plugins`: - -* `building-hacks` -* `cxxrandom` -* `eventful` -* `luasocket` -* `map-render` -* `pathable` -* `xlsxreader` - -=========== -UI Upgrades -=========== - -.. note:: - In order to avoid user confusion, as a matter of policy all GUI tools - display the word :guilabel:`DFHack` on the screen somewhere while active. - - When that is not appropriate because they merely add keybinding hints to - existing DF screens, they deliberately use red instead of green for the key. - -.. contents:: - :local: - - -.. _automaterial: - -automaterial -============ -This makes building constructions (walls, floors, fortifications, etc) a little bit -easier by saving you from having to trawl through long lists of materials each time -you place one. - -Firstly, 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 should mean you can place most constructions without having to search for your -preferred material type. - -.. 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 as autoselect. 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 - the construction will be placed and you -will be back in the construction menu as if you did it manually. - -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. - -.. _automelt: - -automelt -======== -When automelt is enabled for a stockpile, any meltable items placed -in it will be designated to be melted. -This plugin adds an option to the :kbd:`q` menu when `enabled `. - -.. _autotrade: - -autotrade -========= -When autotrade is enabled for a stockpile, any items placed in it will be -designated to be taken to the Trade Depot whenever merchants are on the map. -This plugin adds an option to the :kbd:`q` menu when `enabled `. - -.. _buildingplan: - -buildingplan -============ -When active (via ``enable buildingplan``), this plugin adds a planning mode for -building placement. You can then place furniture, constructions, and other buildings -before the required materials are available, and they will be created in a suspended -state. Buildingplan will periodically scan for appropriate items, and the jobs will -be unsuspended when the items are available. - -This is very useful when combined with `workflow` - you can set a constraint -to always have one or two doors/beds/tables/chairs/etc available, and place -as many as you like. The plugins then take over and fulfill the orders, -with minimal space dedicated to stockpiles. - -.. _buildingplan-filters: - -Item filtering --------------- - -While placing a building, you can set filters for what materials you want the building made -out of, what quality you want the component items to be, and whether you want the items to -be decorated. - -If a building type takes more than one item to construct, use :kbd:`Ctrl`:kbd:`Left` and -:kbd:`Ctrl`:kbd:`Right` to select the item that you want to set filters for. Any filters that -you set will be used for all buildings of the selected type placed from that point onward -(until you set a new filter or clear the current one). Buildings placed before the filters -were changed will keep the filter values that were set when the building was placed. - -For example, you can be sure that all your constructed walls are the same color by setting -a filter to accept only certain types of stone. - -Quickfort mode --------------- - -If you use the external Python Quickfort to apply building blueprints instead of the native -DFHack `quickfort` script, you must enable Quickfort mode. This temporarily enables -buildingplan for all building types and adds an extra blank screen after every building -placement. This "dummy" screen is needed for Python Quickfort to interact successfully with -Dwarf Fortress. - -Note that Quickfort mode is only for compatibility with the legacy Python Quickfort. The -DFHack `quickfort` script does not need Quickfort mode to be enabled. The `quickfort` script -will successfully integrate with buildingplan as long as the buildingplan plugin is enabled. - -.. _buildingplan-settings: - -Global settings ---------------- - -The buildingplan plugin has several global settings that can be set from the UI (:kbd:`G` -from any building placement screen, for example: :kbd:`b`:kbd:`a`:kbd:`G`). These settings -can also be set from the ``DFHack#`` prompt once a map is loaded (or from your -``onMapLoad.init`` file) with the syntax:: - - buildingplan set - -and displayed with:: - - buildingplan set - -The available settings are: - -+----------------+---------+-----------+---------------------------------------+ -| Setting | Default | Persisted | Description | -+================+=========+===========+=======================================+ -| all_enabled | false | no | Enable planning mode for all building | -| | | | types. | -+----------------+---------+-----------+---------------------------------------+ -| blocks | true | yes | Allow blocks, boulders, logs, or bars | -+----------------+---------+ | to be matched for generic "building | -| boulders | true | | material" items | -+----------------+---------+ | | -| logs | true | | | -+----------------+---------+ | | -| bars | false | | | -+----------------+---------+-----------+---------------------------------------+ -| quickfort_mode | false | no | Enable compatibility mode for the | -| | | | legacy Python Quickfort (not required | -| | | | for DFHack quickfort) | -+----------------+---------+-----------+---------------------------------------+ - -For example, to ensure you only use blocks when a "building material" item is required, you -could add this to your ``onMapLoad.init`` file:: - - on-new-fortress buildingplan set boulders false; buildingplan set logs false - -Persisted settings (i.e. ``blocks``, ``boulders``, ``logs``, and ``bars``) are saved with -your game, so you only need to set them to the values you want once. - -.. _command-prompt: - -command-prompt -============== -An in-game DFHack terminal, where you can enter other commands. - -:dfhack-keybind:`command-prompt` - -Usage: ``command-prompt [entry]`` - -If called with an entry, it starts with that text filled in. -Most useful for developers, who can set a keybinding to open -a laungage interpreter for lua or Ruby by starting with the -`:lua ` or `:rb ` commands. - -Otherwise somewhat similar to `gui/quickcmd`. - -.. image:: images/command-prompt.png - -.. _confirm: - -confirm -======= -Implements several confirmation dialogs for potentially destructive actions -(for example, seizing goods from traders or deleting hauling routes). - -Usage: - -:enable confirm: Enable all confirmations; alias ``confirm enable all``. - Replace with ``disable`` to disable. -:confirm help: List available confirmation dialogues. -:confirm enable option1 [option2...]: - Enable (or disable) specific confirmation dialogues. - -.. _debug: - -debug -===== -Manager for DFHack runtime debug prints. Debug prints are grouped by plugin name, -category name and print level. Levels are ``trace``, ``debug``, ``info``, -``warning`` and ``error``. - -The runtime message printing is controlled using filters. Filters set the -visible messages of all matching categories. Matching uses regular expression syntax, -which allows listing multiple alternative matches or partial name matches. -This syntax is a C++ version of the ECMA-262 grammar (Javascript regular expressions). -Details of differences can be found at -https://en.cppreference.com/w/cpp/regex/ecmascript - -Persistent filters are stored in ``dfhack-config/runtime-debug.json``. -Oldest filters are applied first. That means a newer filter can override the -older printing level selection. - -Usage: ``debugfilter [subcommand] [parameters...]`` - -The following subcommands are supported: - -help ----- -Give overall help or a detailed help for a subcommand. - -Usage: ``debugfilter help [subcommand]`` - -category --------- -List available debug plugin and category names. - -Usage: ``debugfilter category [plugin regex] [category regex]`` - -The list can be filtered using optional regex parameters. If filters aren't -given then the it uses ``"."`` regex which matches any character. The regex -parameters are good way to test regex before passing them to ``set``. - -filter ------- -List active and passive debug print level changes. - -Usage: ``debugfilter filter [id]`` - -Optional ``id`` parameter is the id listed as first column in the filter list. -If id is given then the command shows information for the given filter only in -multi line format that is better format if filter has long regex. - -set ---- -Creates a new debug filter to set category printing levels. - -Usage: ``debugfilter set [level] [plugin regex] [category regex]`` - -Adds a filter that will be deleted when DF process exists or plugin is unloaded. - -Usage: ``debugfilter set persistent [level] [plugin regex] [category regex]`` - -Stores the filter in the configuration file to until ``unset`` is used to remove -it. - -Level is the minimum debug printing level to show in log. - -* ``trace``: Possibly very noisy messages which can be printed many times per second - -* ``debug``: Messages that happen often but they should happen only a couple of times per second - -* ``info``: Important state changes that happen rarely during normal execution - -* ``warning``: Enabled by default. Shows warnings about unexpected events which code managed to handle correctly. - -* ``error``: Enabled by default. Shows errors which code can't handle without user intervention. - -unset ------ -Delete a space separated list of filters - -Usage: ``debugfilter unset [id...]`` - -disable -------- -Disable a space separated list of filters but keep it in the filter list - -Usage: ``debugfilter disable [id...]`` - -enable ------- -Enable a space sperate list of filters - -Usage: ``debugfilter enable [id...]`` - -.. _embark-assistant: - -embark-assistant -================ - -This plugin provides embark site selection help. It has to be run with the -``embark-assistant`` command while the pre-embark screen is displayed and shows -extended (and correct(?)) resource information for the embark rectangle as well -as normally undisplayed sites in the current embark region. It also has a site -selection tool with more options than DF's vanilla search tool. For detailed -help invoke the in game info screen. - -.. _embark-tools: - -embark-tools -============ -A collection of embark-related tools. Usage and available tools:: - - embark-tools enable/disable tool [tool]... - -:anywhere: Allows embarking anywhere (including sites, mountain-only biomes, - and oceans). Use with caution. -:mouse: Implements mouse controls (currently in the local embark region only) -:sand: Displays an indicator when sand is present in the currently-selected - area, similar to the default clay/stone indicators. -:sticky: Maintains the selected local area while navigating the world map - -.. _follow: - -follow -====== -Makes the game view follow the currently highlighted unit after you exit from the -current menu or cursor mode. Handy for watching dwarves running around. Deactivated -by moving the view manually. - -.. _hotkeys: - -hotkeys -======= -Opens an in-game screen showing which DFHack keybindings are -active in the current context. See also `hotkey-notes`. - -.. image:: images/hotkeys.png - -:dfhack-keybind:`hotkeys` - -.. _manipulator: - -manipulator -=========== -An in-game equivalent to the popular program Dwarf Therapist. - -To activate, open the unit screen and press :kbd:`l`. - -.. image:: images/manipulator.png - -The far left column displays the unit's Happiness (color-coded based on its -value), Name, Profession/Squad, and the right half of the screen displays each -dwarf's labor settings and skill levels (0-9 for Dabbling through Professional, -A-E for Great through Grand Master, and U-Z for Legendary through Legendary+5). - -Cells with teal backgrounds denote skills not controlled by labors, e.g. -military and social skills. - -.. image:: images/manipulator2.png - -Press :kbd:`t` to toggle between Profession, Squad, and Job views. - -.. image:: images/manipulator3.png - -Use the arrow keys or number pad to move the cursor around, holding :kbd:`Shift` to -move 10 tiles at a time. - -Press the Z-Up (:kbd:`<`) and Z-Down (:kbd:`>`) keys to move quickly between labor/skill -categories. The numpad Z-Up and Z-Down keys seek to the first or last unit -in the list. :kbd:`Backspace` seeks to the top left corner. - -Press Enter to toggle the selected labor for the selected unit, or Shift+Enter -to toggle all labors within the selected category. - -Press the :kbd:`+`:kbd:`-` keys to sort the unit list according to the currently selected -skill/labor, and press the :kbd:`*`:kbd:`/` keys to sort the unit list by Name, Profession/Squad, -Happiness, or Arrival order (using :kbd:`Tab` to select which sort method to use here). - -With a unit selected, you can press the :kbd:`v` key to view its properties (and -possibly set a custom nickname or profession) or the :kbd:`c` key to exit -Manipulator and zoom to its position within your fortress. - -The following mouse shortcuts are also available: - -* Click on a column header to sort the unit list. Left-click to sort it in one - direction (descending for happiness or labors/skills, ascending for name, - profession or squad) and right-click to sort it in the opposite direction. -* Left-click on a labor cell to toggle that labor. Right-click to move the - cursor onto that cell instead of toggling it. -* Left-click on a unit's name, profession or squad to view its properties. -* Right-click on a unit's name, profession or squad to zoom to it. - -Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit -directly to the main dwarf mode screen. - -Professions ------------ - -The manipulator plugin supports saving professions: a named set of labors that can be -quickly applied to one or multiple dwarves. - -To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using -the custom profession name of the dwarf, or the default for that dwarf if no custom profession -name has been set. - -To apply a profession, either highlight a single dwarf or select multiple with -:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for -the selected dwarves will be reset to the labors of the chosen profession. - -Professions are saved as human-readable text files in the "professions" folder -within the DF folder, and can be edited or deleted there. - -.. _mousequery: - -mousequery -========== -Adds mouse controls to the DF interface, e.g. click-and-drag designations. - -Options: - -:plugin: enable/disable the entire plugin -:rbutton: enable/disable right mouse button -:track: enable/disable moving cursor in build and designation mode -:edge: enable/disable active edge scrolling (when on, will also enable tracking) -:live: enable/disable query view when unpaused -:delay: Set delay when edge scrolling in tracking mode. Omit amount to display current setting. - -Usage:: - - mousequery [plugin] [rbutton] [track] [edge] [live] [enable|disable] - -.. _nopause: - -nopause -======= -Disables pausing (both manual and automatic) with the exception of pause forced -by `reveal` ``hell``. This is nice for digging under rivers. - -.. _rename: - -rename -====== -Allows renaming various things. Use `gui/rename` for an in-game interface. - -Options: - -``rename squad "name"`` - Rename squad by index to 'name'. -``rename hotkey \"name\"`` - Rename hotkey by index. This allows assigning - longer commands to the DF hotkeys. -``rename unit "nickname"`` - Rename a unit/creature highlighted in the DF user interface. -``rename unit-profession "custom profession"`` - Change proffession name of the highlighted unit/creature. -``rename building "name"`` - Set a custom name for the selected building. - The building must be one of stockpile, workshop, furnace, trap, - siege engine or an activity zone. - -.. _rendermax: - -rendermax -========= -A collection of renderer replacing/enhancing filters. For better effect try changing the -black color in palette to non totally black. See :forums:`128487` for more info. - -Options: - -:trippy: Randomizes the color of each tiles. Used for fun, or testing. -:light: Enable lighting engine. -:light reload: Reload the settings file. -:light sun |cycle: Set time to (in hours) or set it to df time cycle. -:occlusionON, occlusionOFF: Show debug occlusion info. -:disable: Disable any filter that is enabled. - -An image showing lava and dragon breath. Not pictured here: sunlight, shining items/plants, -materials that color the light etc... - -.. image:: images/rendermax.png - -.. _resume: - -resume -====== -Allows automatic resumption of suspended constructions, along with colored -UI hints for construction status. - -.. _rb: -.. _ruby: - -ruby -==== -Ruby language plugin, which evaluates the following arguments as a ruby string. -Best used as ``:rb [string]``, for the special parsing mode. Alias ``rb_eval``. - -.. comment - the link target "search" is reserved for the Sphinx search page -.. _search-plugin: - -search -====== -The search plugin adds search to the Stocks, Animals, Trading, Stockpile, -Noble (assignment candidates), Military (position candidates), Burrows -(unit list), Rooms, Announcements, Job List and Unit List screens. - -.. image:: images/search.png - -Searching works the same way as the search option in :guilabel:`Move to Depot`. -You will see the Search option displayed on screen with a hotkey (usually :kbd:`s`). -Pressing it lets you start typing a query and the relevant list will start -filtering automatically. - -Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing the now -filtered list, which still functions as normal. You can clear the filter -by either going back into search mode and backspacing to delete it, or -pressing the "shifted" version of the search hotkey while browsing the -list (e.g. if the hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any -filter). - -Leaving any screen automatically clears the filter. - -In the Trade screen, the actual trade will always only act on items that -are actually visible in the list; the same effect applies to the Trade -Value numbers displayed by the screen. Because of this, the :kbd:`t` key is -blocked while search is active, so you have to reset the filters first. -Pressing :kbd:`Alt`:kbd:`C` will clear both search strings. - -In the stockpile screen the option only appears if the cursor is in the -rightmost list: - -.. image:: images/search-stockpile.png - -Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only -on items actually shown in the rightmost list, so it is possible to select -only fat or tallow by forbidding fats, then searching for fat/tallow, and -using Permit Fats again while the list is filtered. - -.. _sort: -.. _sort-items: - -sort-items -========== -Sort the visible item list:: - - sort-items order [order...] - -Sort the item list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. - -Item order examples:: - - description material wear type quality - -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` - -.. _sort-units: - -sort-units -========== -Sort the visible unit list:: - - sort-units order [order...] - -Sort the unit list using the given sequence of comparisons. -The ``<`` prefix for an order makes undefined values sort first. -The ``>`` prefix reverses the sort order for defined values. - -Unit order examples:: - - name age arrival squad squad_position profession - -The orderings are defined in ``hack/lua/plugins/sort/*.lua`` - -:dfhack-keybind:`sort-units` - -.. _stocksettings: -.. _stockpiles: - -stockpiles -========== -Offers the following commands to save and load stockpile settings. -See `gui/stockpiles` for an in-game interface. - -:copystock: Copies the parameters of the currently highlighted stockpile to the custom - stockpile settings and switches to custom stockpile placement mode, effectively - allowing you to copy/paste stockpiles easily. - :dfhack-keybind:`copystock` - -: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. e.g.: ``savestock food_settings.dfstock`` - -:loadstock: Loads a saved stockpile settings file and applies it to the currently selected - stockpile. e.g.: ``loadstock food_settings.dfstock`` - -To use savestock and loadstock, use the :kbd:`q` command to highlight a stockpile. -Then run savestock giving it a descriptive filename. Then, in a different (or -the same!) gameworld, you can highlight any stockpile with :kbd:`q` then execute the -``loadstock`` command passing it the name of that file. The settings will be -applied to that stockpile. - -Note that files are relative to the DF folder, so put your files there or in a -subfolder for easy access. Filenames should not have spaces. Generated materials, -divine metals, etc are not saved as they are different in every world. - -.. _stocks: - -stocks -====== -Replaces the DF stocks screen with an improved version. - -:dfhack-keybind:`stocks` - - -.. _title-folder: - -title-folder -============= -Displays the DF folder name in the window title bar when enabled. - -.. _title-version: - -title-version -============= -Displays the DFHack version on DF's title screen when enabled. - -.. _trackstop: - -trackstop -========= -Adds a :kbd:`q` menu for track stops, which is completely blank by default. -This allows you to view and/or change the track stop's friction and dump -direction settings, using the keybindings from the track stop building interface. diff --git a/docs/Remote.rst b/docs/Remote.rst index c41a14058..6f276cdb6 100644 --- a/docs/Remote.rst +++ b/docs/Remote.rst @@ -1,7 +1,7 @@ .. _remote: ======================= -DFHack Remote Interface +DFHack remote interface ======================= DFHack provides a remote access interface that external tools can connect to and @@ -75,10 +75,11 @@ from other (non-C++) languages, including: - `RemoteClientDF-Net `_ for C# - `dfhackrpc `_ for Go -- `dfhack-remote `_ for JavaScript +- `dfhack-remote `__ for JavaScript - `dfhack-client-qt `_ for C++ with Qt - `dfhack-client-python `_ for Python (adapted from :forums:`"Blendwarf" <178089>`) - `dfhack-client-java `_ for Java +- `dfhack-remote `__ for Rust Protocol description @@ -102,8 +103,6 @@ ID Method Input Output 1 RunCommand dfproto.CoreRunCommandRequest dfproto.EmptyMessage === ============ =============================== ======================= - - Conversation flow ----------------- diff --git a/docs/Removed.rst b/docs/Removed.rst index 5d68b2133..f9bf1c62e 100644 --- a/docs/Removed.rst +++ b/docs/Removed.rst @@ -10,6 +10,34 @@ work (e.g. links from the `changelog`). :local: :depth: 1 +.. _deteriorateclothes: + +deteriorateclothes +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=clothes``. + +.. _deterioratecorpses: + +deterioratecorpses +================== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=corpses``. + +.. _deterioratefood: + +deterioratefood +=============== +Replaced by the new combined `deteriorate` script. Run +``deteriorate --types=food``. + +.. _devel/unforbidall: + +devel/unforbidall +================= +Replaced by the `unforbid` script. Run ``unforbid all --quiet`` to match the +behavior of the original ``devel/unforbidall`` script. + .. _digfort: digfort @@ -20,6 +48,44 @@ existing .csv files. Just move them to the ``blueprints`` folder in your DF installation, and instead of ``digfort file.csv``, run ``quickfort run file.csv``. +.. _fix-armory: + +fix-armory +========== +Allowed the military to store equipment in barracks containers. Removed because +it required a binary patch to DF in order to function, and no such patch has +existed since DF 0.34.11. + +.. _fix/build-location: + +fix/build-location +================== +The corresponding DF :bug:`5991` was fixed in DF 0.40.05. + +.. _fix/diplomats: + +fix/diplomats +============= +The corresponding DF :bug:`3295` was fixed in DF 0.40.05. + +.. _fix/fat-dwarves: + +fix/fat-dwarves +=============== +The corresponding DF :bug:`5971` was fixed in DF 0.40.05. + +.. _fix/feeding-timers: + +fix/feeding-timers +================== +The corresponding DF :bug:`2606` was fixed in DF 0.40.12. + +.. _fix/merchants: + +fix/merchants +============= +Humans can now make trade agreements. This fix is no longer necessary. + .. _fortplan: fortplan @@ -30,8 +96,30 @@ script instead. You can use your existing .csv files. Just move them to the ``blueprints`` folder in your DF installation, and instead of ``fortplan file.csv`` run ``quickfort run file.csv``. +.. _gui/assign-rack: + +gui/assign-rack +=============== +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/hack-wish: + +gui/hack-wish +============= +Replaced by `gui/create-item`. + +.. _gui/no-dfhack-init: + +gui/no-dfhack-init +================== +Tool that warned the user when the ``dfhack.init`` file did not exist. Now that +``dfhack.init`` is autogenerated in ``dfhack-config/init``, this warning is no +longer necessary. + .. _warn-stuck-trees: warn-stuck-trees ================ -The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01. +The corresponding DF :bug:`9252` was fixed in DF 0.44.01. diff --git a/docs/Scripts.rst b/docs/Scripts.rst deleted file mode 100644 index 419385582..000000000 --- a/docs/Scripts.rst +++ /dev/null @@ -1,19 +0,0 @@ -.. _scripts-index: - -############## -DFHack Scripts -############## - -Lua or ruby scripts placed in the :file:`hack/scripts/` directory -are considered for execution as if they were native DFHack commands. - -The following pages document all the scripts in the DFHack standard library. - -.. toctree:: - :maxdepth: 2 - - /docs/_auto/base - /docs/_auto/devel - /docs/_auto/fix - /docs/_auto/gui - /docs/_auto/modtools diff --git a/docs/Structures-intro.rst b/docs/Structures-intro.rst index 48aa571c8..9d304949f 100644 --- a/docs/Structures-intro.rst +++ b/docs/Structures-intro.rst @@ -18,7 +18,7 @@ layout changes, and will need to be recompiled for every new DF version. Addresses of DF global objects and vtables are stored in a separate file, :file:`symbols.xml`. Since these are only absolute addresses, they do not need -to be compiled in to DFHack code, and are instead loaded at runtime. This makes +to be compiled into DFHack code, and are instead loaded at runtime. This makes fixes and additions to global addresses possible without recompiling DFHack. In an installed copy of DFHack, this file can be found at the root of the ``hack`` folder. diff --git a/docs/Support.rst b/docs/Support.rst deleted file mode 100644 index 00e055baa..000000000 --- a/docs/Support.rst +++ /dev/null @@ -1,34 +0,0 @@ -.. _support: - -=============== -Getting Support -=============== - -DFHack has several ways to get help online, including: - -- The `DFHack Discord server `__ -- The ``#dfhack`` IRC channel on `Libera `__ -- GitHub: - - for bugs, use the :issue:`issue tracker <>` - - for more open-ended questions, use the `discussion board - `__. Note that this is a - relatively-new feature as of 2021, but maintainers should still be - notified of any discussions here. -- The `DFHack thread on the Bay 12 Forum `__ - -Some additional, but less DFHack-specific, places where questions may be answered include: - -- The `/r/dwarffortress `_ questions thread on Reddit -- If you are using a starter pack, the relevant thread on the `Bay 12 Forum `__ - - see the :wiki:`DF Wiki ` for a list of these threads - -When reaching out to any support channels regarding problems with DFHack, please -remember to provide enough details for others to identify the issue. For -instance, specific error messages (copied text or screenshots) are helpful, as -well as any steps you can follow to reproduce the problem. Sometimes, log output -from ``stderr.log`` in the DF folder can point to the cause of issues as well. - -Some common questions may also be answered in documentation, including: - -- This documentation (`online here `__; search functionality available `here `) -- :wiki:`The DF wiki <>` diff --git a/docs/Tags.rst b/docs/Tags.rst new file mode 100644 index 000000000..2ee18de48 --- /dev/null +++ b/docs/Tags.rst @@ -0,0 +1,48 @@ +:orphan: + +.. _tag-list: + +DFHack tool tags +================ + +A tool often has at least one tag per group, encompassing when you use the tool, +why you might want to use it, and what kind of thing you're trying to affect. + +See https://docs.google.com/spreadsheets/d/1hiDlo8M_bB_1jE-5HRs2RrrA_VZ4cRu9VXaTctX_nwk/edit#gid=1774645373 +for the tag assignment spreadsheet. + +"when" tags +----------- +- `adventure `: Tools that are useful while in adventure mode. Note that some tools only tagged with "fort" might also work in adventure mode, but not always in expected ways. Feel free to experiment, though! +- `dfhack `: Tools that you use to run DFHack commands or interact with the DFHack library. This tag also includes tools that help you manage the DF game itself (e.g. settings, saving, etc.) +- `embark `: Tools that are useful while on the fort embark screen or while creating an adventurer. +- `fort `: Tools that are useful while in fort mode. +- `legends `: Tools that are useful while in legends mode. + +"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. +- `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. +- `dev `: Tools that are useful when developing scripts or mods. +- `fps `: Tools that help you manage FPS drop. +- `gameplay `: Tools that introduce new gameplay elements. +- `inspection `: Tools that let you view information that is otherwise difficult to find. +- `productivity `: Tools that help you do things that you could do manually, but using the tool is better and faster. + +"what" tags +----------- +- `animals `: Tools that interact with animals. +- `buildings `: Tools that interact with buildings and furniture. +- `graphics `: Tools that interact with game graphics. +- `interface `: Tools that interact with or extend the DF user interface. +- `items `: Tools that interact with in-game items. +- `jobs `: Tools that interact with jobs. +- `labors `: Tools that deal with labor assignment. +- `map `: Tools that interact with the game map. +- `military `: Tools that interact with the military. +- `plants `: Tools that interact with trees, shrubs, and crops. +- `stockpiles `: Tools that interact with stockpiles. +- `units `: Tools that interact with units. +- `workorders `: Tools that interact with workorders. diff --git a/docs/Tools.rst b/docs/Tools.rst new file mode 100644 index 000000000..401276d7f --- /dev/null +++ b/docs/Tools.rst @@ -0,0 +1,63 @@ +.. _tools: + +DFHack tools +============ + +DFHack has **a lot** of tools. This page attempts to make it clearer what they +are, how they work, and how to find the ones you want. + +.. contents:: Contents + :local: + +What tools are and how they work +-------------------------------- + +DFHack is a Dwarf Fortress memory access and modification framework, so DFHack +tools normally access Dwarf Fortress internals and make some specific changes. + +Some tools just make a targeted change when you run them, like `unforbid`, which +scans through all your items and removes the ``forbidden`` flag from each of +them. + +Some tools need to be enabled, and then they run in the background and make +changes to the game on your behalf, like `autobutcher`, which monitors your +livestock population and automatically marks excess animals for butchering. + +And some tools just exist to give you information that is otherwise hard to +come by, like `gui/petitions`, which shows you the active petitions for +guildhalls and temples that you have agreed to. + +Finding the tool you need +------------------------- + +DFHack tools are tagged with categories to make them easier to find. These +categories are listed in the next few sections. Note that a tool can belong to +more than one category. If you already know what you're looking for, try the +`search` or Ctrl-F on this page. If you'd like to see the full list of tools in +one flat list, please refer to the `annotated index `. + +DFHack tools by game mode +------------------------- + +.. include:: tags/bywhen.rst + +DFHack tools by theme +--------------------- + +.. include:: tags/bywhy.rst + +DFHack tools by what they affect +-------------------------------- + +.. include:: tags/bywhat.rst + +All DFHack tools alphabetically +------------------------------- + +.. toctree:: + :glob: + :maxdepth: 1 + :titlesonly: + + tools/* + tools/*/* diff --git a/docs/_auto/.gitignore b/docs/_auto/.gitignore deleted file mode 100644 index 30d85567b..000000000 --- a/docs/_auto/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.rst diff --git a/docs/_changelogs/.gitignore b/docs/_changelogs/.gitignore deleted file mode 100644 index 2211df63d..000000000 --- a/docs/_changelogs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.txt diff --git a/docs/api/index.rst b/docs/api/index.rst index 1fe03e9a8..6afa9d6d4 100644 --- a/docs/api/index.rst +++ b/docs/api/index.rst @@ -1,5 +1,5 @@ ==================== -DFHack API Reference +DFHack API reference ==================== .. toctree:: diff --git a/docs/build-pdf.sh b/docs/build-pdf.sh deleted file mode 100755 index 76908b49b..000000000 --- a/docs/build-pdf.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -# usage: -# ./build-pdf.sh -# SPHINX=/path/to/sphinx-build ./build-pdf.sh -# JOBS=3 ./build-pdf.sh ... -# all command-line arguments are passed directly to sphinx-build - run -# ``sphinx-build --help`` for a list, or see -# https://www.sphinx-doc.org/en/master/man/sphinx-build.html - -cd $(dirname "$0") -cd .. - -sphinx=sphinx-build -if [ -n "$SPHINX" ]; then - sphinx=$SPHINX -fi - -if [ -z "$JOBS" ]; then - JOBS=2 -fi - -"$sphinx" -M latexpdf . ./docs/pdf -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" diff --git a/docs/build.py b/docs/build.py new file mode 100755 index 000000000..bfb6780b2 --- /dev/null +++ b/docs/build.py @@ -0,0 +1,101 @@ +#!/usr/bin/env python3 + +# for help, run: python3 build.py --help + +import argparse +import os +import subprocess +import sys + +class SphinxOutputFormat: + def __init__(self, name, pre_args): + self.name = str(name) + self.pre_args = tuple(pre_args) + + @property + def args(self): + output_dir = os.path.join('docs', self.name) + artifacts_dir = os.path.join('build', 'docs', self.name) # for artifacts not part of the final documentation + os.makedirs(artifacts_dir, mode=0o755, exist_ok=True) + return [ + *self.pre_args, + '.', # source dir + output_dir, + '-d', artifacts_dir, + '-w', os.path.join(artifacts_dir, 'sphinx-warnings.txt'), + ] + +OUTPUT_FORMATS = { + 'html': SphinxOutputFormat('html', pre_args=['-b', 'html']), + 'text': SphinxOutputFormat('text', pre_args=['-b', 'text']), + 'pdf': SphinxOutputFormat('pdf', pre_args=['-M', 'latexpdf']), + 'xml': SphinxOutputFormat('xml', pre_args=['-b', 'xml']), + 'pseudoxml': SphinxOutputFormat('pseudoxml', pre_args=['-b', 'pseudoxml']), +} + +def _parse_known_args(parser, source_args): + # pass along any arguments after '--' + ignored_args = [] + if '--' in source_args: + source_args, ignored_args = source_args[:source_args.index('--')], source_args[source_args.index('--')+1:] + args, forward_args = parser.parse_known_args(source_args) + forward_args += ignored_args + return args, forward_args + +def parse_args(source_args): + def output_format(s): + if s in OUTPUT_FORMATS: + return s + raise ValueError + + parser = argparse.ArgumentParser(usage='%(prog)s [{} ...] [options] [--] [sphinx_options]'.format('|'.join(OUTPUT_FORMATS.keys())), description=''' + DFHack wrapper around sphinx-build. + + Any unrecognized options are passed directly to sphinx-build, as well as any + options following a '--' argument, if specified. + ''') + parser.add_argument('format', nargs='*', type=output_format, action='append', + help='Documentation format(s) to build - choose from {}'.format(', '.join(OUTPUT_FORMATS.keys()))) + parser.add_argument('-E', '--clean', action='store_true', + help='Re-read all input files') + parser.add_argument('--sphinx', type=str, default=os.environ.get('SPHINX', 'sphinx-build'), + help='Sphinx executable to run [environment variable: SPHINX; default: "sphinx-build"]') + parser.add_argument('-j', '--jobs', type=str, default=os.environ.get('JOBS', 'auto'), + help='Number of Sphinx threads to run [environment variable: JOBS; default: "auto"]') + parser.add_argument('--debug', action='store_true', + help='Log commands that are run, etc.') + parser.add_argument('--offline', action='store_true', + help='Disable network connections') + args, forward_args = _parse_known_args(parser, source_args) + + # work around weirdness with list args + args.format = args.format[0] + if not args.format: + args.format = ['html'] + + return args, forward_args + +if __name__ == '__main__': + os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + if not os.path.isfile('conf.py'): + print('Could not find conf.py', file=sys.stderr) + exit(1) + + args, forward_args = parse_args(sys.argv[1:]) + + sphinx_env = os.environ.copy() + if args.offline: + sphinx_env['DFHACK_DOCS_BUILD_OFFLINE'] = '1' + + for format_name in args.format: + command = [args.sphinx] + OUTPUT_FORMATS[format_name].args + ['-j', args.jobs] + if args.clean: + command += ['-E'] + command += forward_args + + if args.debug: + print('Building:', format_name) + print('Running:', command) + subprocess.run(command, check=True, env=sphinx_env) + + print('') diff --git a/docs/build.sh b/docs/build.sh deleted file mode 100755 index 95a97e539..000000000 --- a/docs/build.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/sh - -# usage: -# ./build.sh -# SPHINX=/path/to/sphinx-build ./build.sh -# JOBS=3 ./build.sh ... -# all command-line arguments are passed directly to sphinx-build - run -# ``sphinx-build --help`` for a list, or see -# https://www.sphinx-doc.org/en/master/man/sphinx-build.html - -cd $(dirname "$0") -cd .. - -sphinx=sphinx-build -if [ -n "$SPHINX" ]; then - sphinx=$SPHINX -fi - -if [ -z "$JOBS" ]; then - JOBS=2 -fi - -"$sphinx" -a -b html . ./docs/html -w ./docs/_sphinx-warnings.txt -j "$JOBS" "$@" diff --git a/docs/builtins/alias.rst b/docs/builtins/alias.rst new file mode 100644 index 000000000..69ca42ba0 --- /dev/null +++ b/docs/builtins/alias.rst @@ -0,0 +1,36 @@ +alias +===== + +.. dfhack-tool:: + :summary: Configure helper aliases for other DFHack commands. + :tags: dfhack + +Aliases are resolved immediately after built-in commands, which means that an +alias cannot override a built-in command, but can override a command implemented +by a plugin or script. + +Usage +----- + +``alias list`` + Lists all configured aliases +``alias add [arguments...]`` + Adds an alias +``alias replace [arguments...]`` + Replaces an existing alias with a new command, or adds the alias if it does + not already exist +``alias delete `` + Removes the specified alias + +Aliases can be given additional arguments when created and invoked, which will +be passed to the underlying command in order. + +Example +------- + +:: + + [DFHack]# alias add pargs devel/print-args example + [DFHack]# pargs text + example + text diff --git a/docs/builtins/cls.rst b/docs/builtins/cls.rst new file mode 100644 index 000000000..a5fc391cd --- /dev/null +++ b/docs/builtins/cls.rst @@ -0,0 +1,16 @@ +cls +=== + +.. dfhack-tool:: + :summary: Clear the terminal screen. + :tags: dfhack + +Can also be invoked as ``clear``. Note that this command does not delete command +history. It just clears the text on the screen. + +Usage +----- + +:: + + cls diff --git a/docs/builtins/devel/dump-rpc.rst b/docs/builtins/devel/dump-rpc.rst new file mode 100644 index 000000000..957233b62 --- /dev/null +++ b/docs/builtins/devel/dump-rpc.rst @@ -0,0 +1,15 @@ +devel/dump-rpc +============== + +.. dfhack-tool:: + :summary: Dump RPC endpoint info. + :tags: dev + +Write RPC endpoint information to the specified file. + +Usage +----- + +:: + + devel/dump-rpc diff --git a/docs/builtins/die.rst b/docs/builtins/die.rst new file mode 100644 index 000000000..3b9a08380 --- /dev/null +++ b/docs/builtins/die.rst @@ -0,0 +1,15 @@ +die +=== + +.. dfhack-tool:: + :summary: Instantly exit DF without saving. + :tags: dfhack + +Use to exit DF quickly and safely. + +Usage +----- + +:: + + die diff --git a/docs/builtins/disable.rst b/docs/builtins/disable.rst new file mode 100644 index 000000000..7c64f4b39 --- /dev/null +++ b/docs/builtins/disable.rst @@ -0,0 +1,15 @@ +disable +======= + +.. dfhack-tool:: + :summary: Deactivate a DFHack tool that has some persistent effect. + :tags: dfhack + +See the `enable` command for more info. + +Usage +----- + +:: + + disable [ ...] diff --git a/docs/builtins/enable.rst b/docs/builtins/enable.rst new file mode 100644 index 000000000..7af10a9f3 --- /dev/null +++ b/docs/builtins/enable.rst @@ -0,0 +1,33 @@ +enable +====== + +.. dfhack-tool:: + :summary: Activate a DFHack tool that has some persistent effect. + :tags: dfhack + +Many plugins and scripts can be in a distinct enabled or disabled state. Some +of them activate and deactivate automatically depending on the contents of the +world raws. Others store their state in world data. However a number of them +have to be enabled globally, and the init file is the right place to do it. + +Most such plugins or scripts support the built-in ``enable`` and `disable` +commands. Calling them at any time without arguments prints a list of enabled +and disabled plugins, and shows whether that can be changed through the same +commands. Passing plugin names to these commands will enable or disable the +specified plugins. + +Usage +----- + +:: + + enable + enable [ ...] + +Examples +-------- + +``enable manipulator`` + Enable the ``manipulator`` plugin. +``enable manipulator search`` + Enable multiple plugins at once. diff --git a/docs/builtins/fpause.rst b/docs/builtins/fpause.rst new file mode 100644 index 000000000..ed5a40cad --- /dev/null +++ b/docs/builtins/fpause.rst @@ -0,0 +1,15 @@ +fpause +====== + +.. dfhack-tool:: + :summary: Forces DF to pause. + :tags: dfhack + +This is useful when your FPS drops below 1 and you lose control of the game. + +Usage +----- + +:: + + fpause diff --git a/docs/builtins/help.rst b/docs/builtins/help.rst new file mode 100644 index 000000000..a744e8d69 --- /dev/null +++ b/docs/builtins/help.rst @@ -0,0 +1,29 @@ +help +==== + +.. dfhack-tool:: + :summary: Display help about a command or plugin. + :tags: dfhack + +Can also be invoked as ``?`` or ``man`` (short for "manual"). + +Usage +----- + +:: + + help|?|man + help|?|man + +Examples +-------- + +:: + + help blueprint + man blueprint + +Both examples above will display the help text for the `blueprint` command. + +Some commands also take ``help`` or ``?`` as an option on their command line +for the same effect -- e.g. ``blueprint help``. diff --git a/docs/builtins/hide.rst b/docs/builtins/hide.rst new file mode 100644 index 000000000..037679d69 --- /dev/null +++ b/docs/builtins/hide.rst @@ -0,0 +1,18 @@ +hide +==== + +.. dfhack-tool:: + :summary: Hide the DFHack terminal window. + :tags: dfhack + +You can show it again with the `show` command, though you'll need to use it from +a `keybinding` set beforehand or the in-game `command-prompt`. + +Only available on Windows. + +Usage +----- + +:: + + hide diff --git a/docs/builtins/keybinding.rst b/docs/builtins/keybinding.rst new file mode 100644 index 000000000..6d1565509 --- /dev/null +++ b/docs/builtins/keybinding.rst @@ -0,0 +1,61 @@ +keybinding +========== + +.. dfhack-tool:: + :summary: Create hotkeys that will run DFHack commands. + :tags: dfhack + +Like any other command, it can be used at any time from the console, but +bindings are not remembered between runs of the game unless re-created in +`dfhack.init`. + +Hotkeys can be any combinations of Ctrl/Alt/Shift with A-Z, 0-9, F1-F12, or +\` (the key below the :kbd:`Esc` key. + +Usage +----- + +``keybinding`` + Show some useful information, including the current game context. +``keybinding list `` + List bindings active for the key combination. +``keybinding clear [...]`` + Remove bindings for the specified keys. +``keybinding add "cmdline" ["cmdline"...]`` + Add bindings for the specified key. +``keybinding set "cmdline" ["cmdline"...]`` + Clear, and then add bindings for the specified key. + +The ```` parameter above has the following **case-sensitive** syntax:: + + [Ctrl-][Alt-][Shift-]KEY[@context[|context...]] + +where the ``KEY`` part can be any recognized key and [] denote optional parts. + +When multiple commands are bound to the same key combination, DFHack selects +the first applicable one. Later ``add`` commands, and earlier entries within one +``add`` command have priority. Commands that are not specifically intended for +use as a hotkey are always considered applicable. + +The ``context`` part in the key specifier above can be used to explicitly +restrict the UI state where the binding would be applicable. + +Only bindings with a ``context`` tag that either matches the current context +fully, or is a prefix ending at a ``/`` boundary would be considered for +execution, i.e. when in context ``foo/bar/baz``, keybindings restricted to any +of ``@foo/bar/baz``, ``@foo/bar``, ``@foo``, or none will be active. + +Multiple contexts can be specified by separating them with a pipe (``|``) - for +example, ``@foo|bar|baz/foo`` would match anything under ``@foo``, ``@bar``, or +``@baz/foo``. + +Interactive commands like `liquids` cannot be used as hotkeys. + +Examples +-------- + +``keybinding add Alt-F1 hotkeys`` + Bind Alt-F1 to run the `hotkeys` command on any screen at any time. +``keybinding add Alt-F@dwarfmode gui/quickfort`` + Bind Alt-F to run `gui/quickfort`, but only when on a screen that shows the + main map. diff --git a/docs/builtins/kill-lua.rst b/docs/builtins/kill-lua.rst new file mode 100644 index 000000000..4cb3e203c --- /dev/null +++ b/docs/builtins/kill-lua.rst @@ -0,0 +1,18 @@ +kill-lua +======== + +.. dfhack-tool:: + :summary: Gracefully stop any currently-running Lua scripts. + :tags: dfhack + +Use this command to stop a misbehaving script that appears to be stuck. + +Usage +----- + +:: + + kill-lua + kill-lua force + +Use ``kill-lua force`` if just ``kill-lua`` doesn't seem to work. diff --git a/docs/builtins/load.rst b/docs/builtins/load.rst new file mode 100644 index 000000000..a91316262 --- /dev/null +++ b/docs/builtins/load.rst @@ -0,0 +1,19 @@ +load +==== + +.. dfhack-tool:: + :summary: Load and register a plugin library. + :tags: dfhack + +Also see `unload` and `reload` for related actions. + +Usage +----- + +:: + + load [ ...] + load -a|--all + +You can load individual named plugins or all plugins at once. Note that plugins +are disabled after loading/reloading until you explicitly `enable` them. diff --git a/docs/builtins/ls.rst b/docs/builtins/ls.rst new file mode 100644 index 000000000..cd6bc4126 --- /dev/null +++ b/docs/builtins/ls.rst @@ -0,0 +1,44 @@ +ls +== + +.. dfhack-tool:: + :summary: List available DFHack commands. + :tags: dfhack + +In order to group related commands, each command is associated with a list of +tags. You can filter the listed commands by a tag or a substring of the +command name. Can also be invoked as ``dir``. + +Usage +----- + +``ls []`` + Lists all available commands and the tags associated with them. +``ls []`` + Shows only commands that have the given tag. Use the `tags` command to see + the list of available tags. +``ls []`` + Shows commands that include the given string. E.g. ``ls quick`` will show + all the commands with "quick" in their names. If the string is also the + name of a tag, then it will be interpreted as a tag name. + +Examples +-------- + +``ls quick`` + List all commands that match the substring "quick". +``ls adventure`` + List all commands with the ``adventure`` tag. +``ls --dev trigger`` + List all commands, including developer and modding commands, that match the + substring "trigger". + +Options +------- + +``--notags`` + Don't print out the tags associated with each command. +``--dev`` + Include commands intended for developers and modders. +``--exclude [,...]`` + Exclude commands that match any of the given strings. diff --git a/docs/builtins/plug.rst b/docs/builtins/plug.rst new file mode 100644 index 000000000..8df378d3c --- /dev/null +++ b/docs/builtins/plug.rst @@ -0,0 +1,16 @@ +plug +==== + +.. dfhack-tool:: + :summary: List available plugins and whether they are enabled. + :tags: dfhack + +Usage +----- + +:: + + plug [ [ ...]] + +If run with parameters, it lists only the named plugins. Otherwise it will list +all available plugins. diff --git a/docs/builtins/reload.rst b/docs/builtins/reload.rst new file mode 100644 index 000000000..9ee9061a2 --- /dev/null +++ b/docs/builtins/reload.rst @@ -0,0 +1,21 @@ +reload +====== + +.. dfhack-tool:: + :summary: Reload a loaded plugin. + :tags: dfhack + +Developers use this command to reload a plugin that they are actively modifying. +Also see `load` and `unload` for related actions. + +Usage +----- + +:: + + reload [ ...] + reload -a|--all + +You can reload individual named plugins or all plugins at once. Note that +plugins are disabled after loading/reloading until you explicitly `enable` +them. diff --git a/docs/builtins/sc-script.rst b/docs/builtins/sc-script.rst new file mode 100644 index 000000000..f66aeb412 --- /dev/null +++ b/docs/builtins/sc-script.rst @@ -0,0 +1,26 @@ +sc-script +========= + +.. dfhack-tool:: + :summary: Run commands when game state changes occur. + :tags: dfhack + +This is similar to the static `init-files` but is slightly more flexible since +it can be set dynamically. + +Usage +----- + +``sc-script [help]`` + Show the list of valid event names. +``sc-script list []`` + List the currently registered files for all events or the specified event. +``sc-script add|remove [ ...]`` + Register or unregister a file to be run for the specified event. + +Example +------- + +``sc-script add SC_MAP_LOADED spawn_extra_monsters.init`` + Registers the ``spawn_extra_monsters.init`` file to be run whenever a new + map is loaded. diff --git a/docs/builtins/script.rst b/docs/builtins/script.rst new file mode 100644 index 000000000..0c4ca8c3d --- /dev/null +++ b/docs/builtins/script.rst @@ -0,0 +1,26 @@ +script +====== + +.. dfhack-tool:: + :summary: Execute a batch file of DFHack commands. + :tags: dfhack + +It reads a text file and runs each line as a DFHack command as if it had been +typed in by the user -- treating the input like `an init file `. + +Some other tools, such as `autobutcher` and `workflow`, export their settings as +the commands to create them - which can later be reloaded with ``script``. + +Usage +----- + +:: + + script + +Example +------- + +``script startup.txt`` + Executes the commands in ``startup.txt``, which exists in your DF game + directory. diff --git a/docs/builtins/show.rst b/docs/builtins/show.rst new file mode 100644 index 000000000..cbfa3a386 --- /dev/null +++ b/docs/builtins/show.rst @@ -0,0 +1,19 @@ +show +==== + +.. dfhack-tool:: + :summary: Unhides the DFHack terminal window. + :tags: dfhack + +Useful if you have hidden the terminal with `hide` and you want it back. Since +the terminal window won't be available to run this command, you'll need to use +it from a `keybinding` set beforehand or the in-game `command-prompt`. + +Only available on Windows. + +Usage +----- + +:: + + show diff --git a/docs/builtins/tags.rst b/docs/builtins/tags.rst new file mode 100644 index 000000000..698323ffe --- /dev/null +++ b/docs/builtins/tags.rst @@ -0,0 +1,26 @@ +tags +==== + +.. dfhack-tool:: + :summary: List the categories of DFHack tools or the tools with those tags. + :tags: dfhack + +DFHack tools are labeled with tags so you can find groups of related commands. +This builtin command lists the tags that you can explore, or, if called with the +name of a tag, lists the tools that have that tag. + +Usage +----- + +``tags`` + List the categories of DFHack tools and a description of those categories. +``tags `` + List the tools that are tagged with the given tag. + +Examples +-------- + +``tags`` + List the defined tags. +``tags design`` + List all the tools that have the ``design`` tag. diff --git a/docs/builtins/type.rst b/docs/builtins/type.rst new file mode 100644 index 000000000..1bf86e588 --- /dev/null +++ b/docs/builtins/type.rst @@ -0,0 +1,17 @@ +type +==== + +.. dfhack-tool:: + :summary: Describe how a command is implemented. + :tags: dfhack + +DFHack commands can be provided by plugins, scripts, or by the core library +itself. The ``type`` command can tell you which is the source of a particular +command. + +Usage +----- + +:: + + type diff --git a/docs/builtins/unload.rst b/docs/builtins/unload.rst new file mode 100644 index 000000000..6fa52ea9e --- /dev/null +++ b/docs/builtins/unload.rst @@ -0,0 +1,18 @@ +unload +====== + +.. dfhack-tool:: + :summary: Unload a plugin from memory. + :tags: dfhack + +Also see `load` and `reload` for related actions. + +Usage +----- + +:: + + unload [ ...] + unload -a|--all + +You can unload individual named plugins or all plugins at once. diff --git a/docs/changelog.txt b/docs/changelog.txt index 26329f0d1..9eb067d50 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -34,57 +34,225 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: # Future ## New Plugins -- `spectate`: automates the following of dwarves more often than not based on job zlevel activity levels, sometimes randomly though. -## Removed +## Fixes +- `automaterial`: fix the cursor jumping up a z level when clicking quickly after box select +- `gui/create-item`: prevent materials list filter from intercepting sublist hotkeys +- `mousequery`: fix the cursor jumping up z levels sometimes when using TWBT +- `tiletypes`: no longer resets dig priority to the default when updating other properties of a tile +- `automaterial`: fix rendering errors with box boundary markers +- Core: fix the segmentation fault with the REPORT event in EventManager +- Core: fix the new JOB_STARTED event only sending each event once, to the first handler listed + +## Misc Improvements +- `blueprint`: new ``--smooth`` option for recording all smoothed floors and walls instead of just the ones that require smoothing for later carving +- `blueprint`: record built constructions in blueprints +- `blueprint`: record stockpile/building/zone names in blueprints +- `blueprint`: record room sizes in blueprints +- `blueprint`: generate meta blueprints to reduce the number of blueprints you have to apply +- `blueprint`: support splitting the output file into phases grouped by when they can be applied +- `blueprint`: when splitting output files, number them so they sort into the order you should apply them in +- `ls`: indent tag listings and wrap them in the right column for better readability +- `ls`: new ``--exclude`` option for hiding matched scripts from the output. this can be especially useful for modders who don't want their mod scripts to be included in ``ls`` output. +- `digtype`: new ``-z`` option for digtype to restrict designations to the current z-level and down +- UX: List widgets now have mouse-interactive scrollbars +- UX: You can now hold down the mouse button on a scrollbar to make it scroll multiple times. +- UX: You can now drag the scrollbar to scroll to a specific spot +- `overlay`: reduce the size of the "DFHack Launcher" button +- Constructions module: ``findAtTile`` now uses a binary search intead of a linear search. +- `spectate`: new ``auto-unpause`` option for auto-dismissal of announcement pause events (e.g. sieges). +- `spectate`: new ``auto-disengage`` option for auto-disengagement of plugin through player interaction whilst unpaused. +- `spectate`: new ``focus-jobs`` option for following a dwarf after their job has finished (when disabled). +- `spectate`: new ``tick-threshold``, option for specifying the change interval (maximum follow time when focus-jobs is enabled) +- `spectate`: added persistent configuration of the plugin settings +- `gui/cp437-table`: new global keybinding for the clickable on-screen keyboard for players with keyboard layouts that prevent them from using certain keys: Ctrl-Shift-K + +## Documentation +- `spectate`: improved documentation of features and functionality + +## API +- ``Gui::anywhere_hotkey``: for plugin commands bound to keybindings that can be invoked on any screen +- ``Lua::PushInterfaceKeys()``: transforms viewscreen ``feed()`` keys into something that can be interpreted by lua-based widgets +- ``Lua::Push()``: now handles maps with otherwise supported keys and values +- Constructions module: added ``insert()`` to insert constructions into the game's sorted list. +- MiscUtils: moved the following string transformation functions from ``uicommon.h``: ``int_to_string``, ``ltrim``, ``rtrim``, and ``trim``; added ``string_to_int`` + +## Lua +- ``widgets.Scrollbar``: new scrollbar widget that can be paired with an associated scrollable widget. Integrated with ``widgets.Label`` and ``widgets.List``. +- ``widgets.List``: new ``getIdxUnderMouse()`` function for detecting the list index under the active mouse cursor +- ``widgets.List``: shift-clicking now triggers the ``submit2`` attribute function if it is defined +- ``dfhack.constructions.findAtTile()``: exposed preexisting function to Lua. +- ``dfhack.constructions.insert()``: exposed new function to Lua. +- ``widgets.Panel``: new ``frame_style`` and ``frame_title`` attributes for drawing frames around groups of widgets +- ``widgets.EditField`` now allows other widgets to process characters that the ``on_char`` callback rejects. +- ``gui.Screen.show()`` now returns ``self`` as a convenience +- ``gui.View.getMousePos()`` now takes an optional ``ViewRect`` parameter in case the caller wants to get the mouse pos relative to a rect that is not the frame_body (such as the frame_rect) + +# 0.47.05-r7 + +## New Plugins +- `autonestbox`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autonestbox start`` to ``enable autonestbox``. +- `autobutcher`: split off from `zone` into its own plugin. Note that to enable, the command has changed from ``autobutcher start`` to ``enable autobutcher``. +- `overlay`: display a "DFHack" button in the lower left corner that you can click to start the new GUI command launcher. The `dwarfmonitor` weather display had to be moved to make room for the button. If you are seeing the weather indicator rendered over the overlay button, please remove the ``dfhack-config/dwarfmonitor.json`` file to fix the weather indicator display offset. + +## New Internal Commands +- `tags`: new built-in command to list the tool category tags and their definitions. tags associated with each tool are visible in the tool help and in the output of `ls`. + +## Fixes +- `autochop`: designate largest trees for chopping first, instead of the smallest +- ``dfhack.run_script``: ensure the arguments passed to scripts are always strings. This allows other scripts to call ``run_script`` with numeric args and it won't break parameter parsing. +- `dig-now`: Fix direction of smoothed walls when adjacent to a door or floodgate +- ``job.removeJob()``: ensure jobs are removed from the world list when they are canceled +- `quickfort`: `Dreamfort ` blueprint set: declare the hospital zone before building the coffer; otherwise DF fails to stock the hospital with materials +- ``dfhack.buildings.findCivzonesAt``: no longer return duplicate civzones after loading a save with existing civzones + +## Misc Improvements +- Init scripts: ``dfhack.init`` and other init scripts have moved to ``dfhack-config/init/``. If you have customized your ``dfhack.init`` file and want to keep your changes, please move the part that you have customized to the new location at ``dfhack-config/init/dfhack.init``. If you do not have changes that you want to keep, do not copy anything, and the new defaults will be used automatically. +- History files: ``dfhack.history``, ``tiletypes.history``, ``lua.history``, and ``liquids.history`` have moved to the ``dfhack-config`` directory. If you'd like to keep the contents of your current history files, please move them to ``dfhack-config``. +- `do-job-now`: new global keybinding for boosting the priority of the jobs associated with the selected building/work order/unit/item etc.: Alt-N +- `gui/workorder-details`: new keybinding on the workorder details screen: ``D`` +- `keybinding`: support backquote (\`) as a hotkey (and assign the hotkey to the new `gui/launcher` interface) +- `ls`: can now filter tools by substring or tag. note that dev scripts are hidden by default. pass the ``--dev`` option to show them. +- `manipulator`: add a library of useful default professions +- `manipulator`: move professions configuration from ``professions/`` to ``dfhack-config/professions/`` to keep it together with other dfhack configuration. If you have saved professions that you would like to keep, please manually move them to the new folder. +- ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. +- `orders`: added useful library of manager orders. see them with ``orders list`` and import them with, for example, ``orders import library/basic`` +- `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. +- `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. +- UX: You can now move the cursor around in DFHack text fields in ``gui/`` scripts (e.g. `gui/blueprint`, `gui/quickfort`, or `gui/gm-editor`). You can move the cursor by clicking where you want it to go with the mouse or using the Left/Right arrow keys. Ctrl+Left/Right will move one word at a time, and Alt+Left/Right will move to the beginning/end of the text. +- UX: You can now click on the hotkey hint text in many ``gui/`` script windows to activate the hotkey, like a button. Not all scripts have been updated to use the clickable widget yet, but you can try it in `gui/blueprint` or `gui/quickfort`. +- UX: Label widget scroll icons are replaced with scrollbars that represent the percentage of text on the screen and move with the position of the visible text, just like web browser scrollbars. +- `quickfort`: `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients + +## Documentation +- Added `modding-guide` +- Update all DFHack tool documentation (300+ pages) with standard syntax formatting, usage examples, and overall clarified text. +- Group DFHack tools by `tag ` so similar tools are grouped and easy to find + +## API +- Removed "egg" ("eggy") hook support (Linux only). The only remaining method of hooking into DF is by interposing SDL calls, which has been the method used by all binary releases of DFHack. +- Removed ``Engravings`` module (C++-only). Access ``world.engravings`` directly instead. +- Removed ``Notes`` module (C++-only). Access ``ui.waypoints.points`` directly instead. +- Removed ``Windows`` module (C++-only) - unused. +- ``Constructions`` module (C++-only): removed ``t_construction``, ``isValid()``, ``getCount()``, ``getConstruction()``, and ``copyConstruction()``. Access ``world.constructions`` directly instead. +- ``Gui::getSelectedItem()``, ``Gui::getAnyItem()``: added support for the artifacts screen +- ``Units::teleport()``: now sets ``unit.idle_area`` to discourage units from walking back to their original location (or teleporting back, if using `fastdwarf`) + +## Lua +- History: added ``dfhack.getCommandHistory(history_id, history_filename)`` and ``dfhack.addCommandToHistory(history_id, history_filename, command)`` so gui scripts can access a commandline history without requiring a terminal. +- Added ``dfhack.screen.hideGuard()``: exposes the C++ ``Screen::Hide`` to Lua +- ``helpdb``: database and query interface for DFHack tool help text +- ``tile-material``: fix the order of declarations. The ``GetTileMat`` function now returns the material as intended (always returned nil before). Also changed the license info, with permission of the original author. +- ``utils.df_expr_to_ref()``: fixed some errors that could occur when navigating tables +- ``widgets.EditField``: new ``onsubmit2`` callback attribute is called when the user hits Shift-Enter. +- ``widgets.EditField``: new function: ``setCursor(position)`` sets the input cursor. +- ``widgets.EditField``: new attribute: ``ignore_keys`` lets you ignore specified characters if you want to use them as hotkeys +- ``widgets.FilteredList``: new attribute: ``edit_ignore_keys`` gets passed to the filter EditField as ``ignore_keys`` +- ``widgets.Label``: ``scroll`` function now interprets the keywords ``+page``, ``-page``, ``+halfpage``, and ``-halfpage`` in addition to simple positive and negative numbers. +- ``widgets.HotkeyLabel``: clicking on the widget will now call ``on_activate()``. +- ``widgets.CycleHotkeyLabel``: clicking on the widget will now cycle the options and trigger ``on_change()``. This also applies to the ``ToggleHotkeyLabel`` subclass. + +# 0.47.05-r6 + +## Fixes +- `eventful`: fix ``eventful.registerReaction`` to correctly pass ``call_native`` argument thus allowing canceling vanilla item creation. Updated related documentation. +- `eventful`: renamed NEW_UNIT_ACTIVE event to UNIT_NEW_ACTIVE to match the ``EventManager`` event name +- `eventful`: fixed UNIT_NEW_ACTIVE event firing too often +- ``job.removeJob()``: fixes regression in DFHack 0.47.05-r5 where items/buildings associated with the job were not getting disassociated when the job is removed. Now `build-now` can build buildings and `gui/mass-remove` can cancel building deconstruction again +- ``widgets.CycleHotkeyLabel``: allow initial option values to be specified as an index instead of an option value + +## Misc Improvements +- `confirm`: added a confirmation dialog for removing manager orders +- `confirm`: allow players to pause the confirmation dialog until they exit the current screen +- `dfhack-examples-guide`: refine food preparation orders so meal types are chosen intelligently according to the amount of meals that exist and the number of aviailable items to cook with +- `dfhack-examples-guide`: reduce required stock of dye for "Dye cloth" orders +- `dfhack-examples-guide`: fix material conditions for making jugs and pots +- `dfhack-examples-guide`: make wooden jugs by default to differentiate them from other stone tools. this allows players to more easily select jugs out with a properly-configured stockpile (i.e. the new ``woodentools`` alias) +- `quickfort-alias-guide`: new aliases: ``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the `search-plugin` plugin to alter the settings for a filtered list of item types when configuring stockpiles +- `quickfort-alias-guide`: new aliases: ``stonetools`` and ``woodentools``. the ``jugs`` alias is deprecated. please use ``stonetools`` instead, which is the same as the old ``jugs`` alias. +- `quickfort-alias-guide`: new aliases: ``usablehair``, ``permitusablehair``, and ``forbidusablehair`` alter settings for the types of hair/wool that can be made into cloth: sheep, llama, alpaca, and troll. The ``craftrefuse`` aliases have been altered to use this alias as well. +- `quickfort-alias-guide`: new aliases: ``forbidthread``, ``permitthread``, ``forbidadamantinethread``, ``permitadamantinethread``, ``forbidcloth``, ``permitcloth``, ``forbidadamantinecloth``, and ``permitadamantinecloth`` give you more control how adamantine-derived items are stored +- `quickfort`: `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern +- `quickfort`: `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. + +## API +- ``word_wrap``: argument ``bool collapse_whitespace`` converted to enum ``word_wrap_whitespace_mode mode``, with valid modes ``WSMODE_KEEP_ALL``, ``WSMODE_COLLAPSE_ALL``, and ``WSMODE_TRIM_LEADING``. + +## Lua +- ``gui.View``: all ``View`` subclasses (including all ``Widgets``) can now acquire keyboard focus with the new ``View:setFocus()`` function. See docs for details. +- ``materials.ItemTraitsDialog``: new dialog to edit item traits (where "item" is part of a job or work order or similar). The list of traits is the same as in vanilla work order conditions "``t`` change traits". +- ``widgets.EditField``: the ``key_sep`` string is now configurable +- ``widgets.EditField``: can now display an optional string label in addition to the activation key +- ``widgets.EditField``: views that have an ``EditField`` subview no longer need to manually manage the ``EditField`` activation state and input routing. This is now handled automatically by the new ``gui.View`` keyboard focus subsystem. +- ``widgets.HotkeyLabel``: the ``key_sep`` string is now configurable + +# 0.47.05-r5 + +## New Plugins +- `spectate`: "spectator mode" -- automatically follows dwarves doing things in your fort + +## New Tweaks +- `tweak`: ``partial-items`` displays percentage remaining for partially-consumed items such as hospital cloth ## Fixes -- `autofarm` removed restriction on only planting 'discovered' plants -- `luasocket` (and others): return correct status code when closing socket connections +- `autofarm`: removed restriction on only planting "discovered" plants +- `cxxrandom`: fixed exception when calling ``bool_distribution`` +- `luasocket`: return correct status code when closing socket connections so clients can know when to retry ## Misc Improvements +- `autochop`: only designate the amount of trees required to reach ``max_logs`` +- `autochop`: preferably designate larger trees over smaller ones +- `blueprint`: ``track`` phase renamed to ``carve`` +- `blueprint`: carved fortifications and (optionally) engravings are now captured in generated blueprints +- `cursecheck`: new option, ``--ids`` prints creature and race IDs of the cursed creature +- `debug`: DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin +- `debug`: script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` +- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `dfhack-examples-guide`: ``onMapLoad_dreamfort.init`` remove "cheaty" commands and new tweaks that are now in the default ``dfhack.init-example`` file +- ``dfhack.init-example``: recently-added tweaks added to example ``dfhack.init`` file - `dig-now`: handle fortification carving - `EventManager`: add new event type ``JOB_STARTED``, triggered when a job first gains a worker -- `EventManager`: add new event type ``NEW_UNIT_ACTIVE``, triggered when a new unit appears on the active list -- `EventManager`: now each registered handler for an event can have its own frequency instead of all handlers using the lowest frequency of all handlers -- `stocks`: allow search terms to match the full item label, even when the label is truncated for length -- `dfhack-examples-guide`: add mugs to ``basic`` manager orders +- `EventManager`: add new event type ``UNIT_NEW_ACTIVE``, triggered when a new unit appears on the active list - `gui/create-item`: Added "(chain)" annotation text for armours with the [CHAIN_METAL_TEXT] flag set -- DFHack log messages now have configurable headers (e.g. timestamp, origin plugin name, etc.) via the ``debugfilter`` command of the `debug` plugin -- Script execution log messages (e.g. "Loading script: dfhack_extras.init" can now be controlled with the ``debugfilter`` command. To hide the messages, add this line to your ``dfhack.init`` file: ``debugfilter set Warning core script`` -- `manipulator`: Tweak colors to make the cursor easier to locate -- `autochop`: only designate the amount of trees required to reach ``max_logs`` -- `autochop`: preferably designate larger trees over smaller ones -- `blueprint`: ``track`` phase renamed to ``carve``. carved fortifications and (optionally) engravings are now captured in blueprints -- `tweak` stable-cursor: Keep the cursor stable even when the viewport moves a small amount +- `manipulator`: tweak colors to make the cursor easier to locate +- `stocks`: allow search terms to match the full item label, even when the label is truncated for length +- `tweak`: ``stable-cursor`` now keeps the cursor stable even when the viewport moves a small amount ## Documentation -- Add more examples to the plugin skeleton files so they are more informative for a newbie -- Lua API.rst added: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` -- Update download link and installation instructions for Visual C++ 2015 build tools on Windows -- Updated information regarding obtaining a compatible Windows build environment -- `confirm`: Correct the command name in the plugin help text -- ``Quickfort Blueprint Editing Guide``: added screenshots to the Dreamfort case study and overall clarified text -- Document DFHack `lua-string` (``startswith``, ``endswith``, ``split``, ``trim``, ``wrap``, and ``escape_pattern``). +- add more examples to the plugin example skeleton files so they are more informative for a newbie +- `confirm`: correct the command name in the plugin help text +- `cxxrandom`: added usage examples +- ``Lua API.rst``: added ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- `lua-string`: document DFHack string extensions (``startswith()``, ``endswith()``, ``split()``, ``trim()``, ``wrap()``, and ``escape_pattern()``) +- `quickfort-blueprint-guide`: added screenshots to the Dreamfort case study and overall clarified text +- `remote-client-libs`: add new Rust client library +- update download link and installation instructions for Visual C++ 2015 build tools on Windows +- update information regarding obtaining a compatible Windows build environment ## API -- Added functions reverse-engineered from ambushing unit code: ``Units::isHidden``, ``Units::isFortControlled``, ``Units::getOuterContainerRef``, ``Items::getOuterContainerRef`` +- add functions reverse-engineered from ambushing unit code: ``Units::isHidden()``, ``Units::isFortControlled()``, ``Units::getOuterContainerRef()``, ``Items::getOuterContainerRef()`` +- ``Job::removeJob()``: use the job cancel vmethod graciously provided by The Toady One in place of a synthetic method derived from reverse engineering ## Lua -- ``widgets.FilteredList`` now allows all punctuation to be typed into the filter and can match search keys that start with punctuation. -- ``widgets.ListBox``: minimum height of dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) -- Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- `custom-raw-tokens`: library for accessing tokens added to raws by mods +- ``dfhack.units``: Lua wrappers for functions reverse-engineered from ambushing unit code: ``isHidden(unit)``, ``isFortControlled(unit)``, ``getOuterContainerRef(unit)``, ``getOuterContainerRef(item)`` +- ``dialogs``: ``show*`` functions now return a reference to the created dialog +- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface (where the default designation mode is ``DesignateChopTrees``) - ``dwarfmode.MenuOverlay``: if ``sidebar_mode`` attribute is set, automatically manage entering a specific sidebar mode on show and restoring the previous sidebar mode on dismiss -- ``dwarfmode.enterSidebarMode()``: passing ``df.ui_sidebar_mode.DesignateMine`` now always results in you entering ``DesignateMine`` mode and not ``DesignateChopTrees``, even when you looking at the surface where the default designation mode is ``DesignateChopTrees`` -- New string class function: ``string:escape_pattern()`` escapes regex special characters within a string -- ``widgets.Panel``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts -- ``widgets.ResizingPanel``: new ``Panel`` subclass that automatically recalculates it's own frame height based on the size, position, and visibility of its subviews -- ``widgets.TooltipLabel``: new ``Label`` subclass that provides tooltip-like behavior -- ``widgets.HotkeyLabel``: new ``Label`` subclass that displays and reacts to hotkeys -- ``widgets.CycleHotkeyLabel``: new ``Label`` subclass that allows users to cycle through a list of options by pressing a hotkey -- ``widgets.ToggleHotkeyLabel``: new ``CycleHotkeyLabel`` subclass that toggles between ``On`` and ``Off`` states -- ``safe_index`` now properly handles lua sparse tables that are indexed by numbers -- ``widgets``: unset values in ``frame_inset``-table default to ``0`` +- ``dwarfmode.MenuOverlay``: new class function ``renderMapOverlay`` to assist with painting tiles over the visible map +- ``ensure_key``: new global function for retrieving or dynamically creating Lua table mappings +- ``safe_index``: now properly handles lua sparse tables that are indexed by numbers +- ``string``: new function ``escape_pattern()`` escapes regex special characters within a string +- ``widgets``: unset values in ``frame_inset`` table default to ``0`` +- ``widgets``: ``FilteredList`` class now allows all punctuation to be typed into the filter and can match search keys that start with punctuation +- ``widgets``: minimum height of ``ListBox`` dialog is now calculated correctly when there are no items in the list (e.g. when a filter doesn't match anything) +- ``widgets``: if ``autoarrange_subviews`` is set, ``Panel``\s will now automatically lay out widgets vertically according to their current height. This allows you to have widgets dynamically change height or become visible/hidden and you don't have to worry about recalculating frame layouts +- ``widgets``: new class ``ResizingPanel`` (subclass of ``Panel``) automatically recalculates its own frame height based on the size, position, and visibility of its subviews +- ``widgets``: new class ``HotkeyLabel`` (subclass of ``Label``) that displays and reacts to hotkeys +- ``widgets``: new class ``CycleHotkeyLabel`` (subclass of ``Label``) allows users to cycle through a list of options by pressing a hotkey +- ``widgets``: new class ``ToggleHotkeyLabel`` (subclass of ``CycleHotkeyLabel``) toggles between ``On`` and ``Off`` states +- ``widgets``: new class ``WrappedLabel`` (subclass of ``Label``) provides autowrapping of text +- ``widgets``: new class ``TooltipLabel`` (subclass of ``WrappedLabel``) provides tooltip-like behavior # 0.47.05-r4 @@ -114,7 +282,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ## API - ``Buildings::findCivzonesAt()``: lookups now complete in constant time instead of linearly scanning through all civzones in the game -- ``Job::remove_postings()``: use the job cancel vmethod graciously provided by The Toady One in place of a synthetic method derived from reverse engineering ## Lua - ``argparse.processArgsGetopt()``: you can now have long form parameters that are not an alias for a short form parameter. For example, you can now have a parameter like ``--longparam`` without needing to have an equivalent one-letter ``-l`` param. @@ -962,7 +1129,7 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: - `caravan`: documented (new in 0.44.10-alpha1) - `deathcause`: added "slaughtered" to descriptions - `fix/retrieve-units`: now re-adds units to active list to counteract `fix/dead-units` -- `item-descriptions`: fixed several grammatical errors +- ``item-descriptions``: fixed several grammatical errors - `modtools/create-unit`: - added quantity argument - now selects a caste at random if none is specified diff --git a/docs/guides/examples-guide.rst b/docs/guides/examples-guide.rst index 409ceaa00..bcb7c77ec 100644 --- a/docs/guides/examples-guide.rst +++ b/docs/guides/examples-guide.rst @@ -1,8 +1,8 @@ .. _config-examples-guide: .. _dfhack-examples-guide: -DFHack Example Configuration File Index -======================================= +DFHack config file examples +=========================== The :source:`hack/examples ` folder contains ready-to-use examples of various DFHack configuration files. You can use them by copying them @@ -14,8 +14,8 @@ The ``init/`` subfolder ----------------------- The :source:`init/ ` subfolder contains useful DFHack -`init-files` that you can copy into your main Dwarf Fortress folder -- the same -directory as ``dfhack.init``. +`init-files` that you can copy into your :file:`dfhack-config/init` folder -- +the same directory as ``dfhack.init``. .. _onMapLoad-dreamfort-init: @@ -28,14 +28,14 @@ it is useful (and customizable) for any fort. It includes the following config: - Calls `ban-cooking` for items that have important alternate uses and should not be cooked. This configuration is only set when a fortress is first started, so later manual changes will not be overridden. -- Automates calling of various fort maintenance and `scripts-fix`, like - `cleanowned` and `fix/stuckdoors`. +- Automates calling of various fort maintenance scripts, like `cleanowned` and + `fix/stuckdoors`. - Keeps your manager orders intelligently ordered with `orders` ``sort`` so no orders block other orders from ever getting completed. - Periodically enqueues orders to shear and milk shearable and milkable pets. - Sets up `autofarm` to grow 30 units of every crop, except for pig tails, which is set to 150 units to support the textile industry. -- Sets up `seedwatch` to keep 30 of every type of seed. +- Sets up `seedwatch` to protect 30 of every type of seed. - Configures `prioritize` to automatically boost the priority of important and time-sensitive tasks that could otherwise get ignored in busy forts, like hauling food, tanning hides, storing items in vehicles, pulling levers, and @@ -47,225 +47,3 @@ it is useful (and customizable) for any fort. It includes the following config: fortress is first started, so any later changes you make to autobutcher settings won't be overridden. - Enables `automelt`, `tailor`, `zone`, `nestboxes`, and `autonestbox`. - -The ``orders/`` subfolder -------------------------- - -The :source:`orders/ ` subfolder contains manager orders -that, along with the ``onMapLoad_dreamfort.init`` file above, allow a fort to be -self-sustaining. Copy them to your ``dfhack-config/orders/`` folder and import -as required with the `orders` DFHack plugin. - -:source:`basic.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -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 -- bags of leather, cloth, silk, and yarn -- crafts and totems from otherwise unusable by-products -- mechanisms/cages -- splints/crutches -- lye/soap -- ash/potash -- beds/wheelbarrows/minecarts -- scrolls - -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. - -:source:`furnace.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection creates basic items that require heat. It is separated out from -``basic.json`` to give players the opportunity to set up magma furnaces first in -order to save resources. It handles: - -- charcoal (including smelting of bituminous coal and lignite) -- pearlash -- sand -- green/clear/crystal glass -- adamantine processing -- item melting - -Orders are missing for plaster powder until DF `bug 11803 -`_ is fixed. - -:source:`military.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds high-volume smelting jobs for military-grade metal ores and -produces weapons and armor: - -- leather backpacks/waterskins/cloaks/quivers/armor -- bone/wooden bolts -- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and - their dependencies) -- bronze/bismuth bronze/copper bolts -- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, - with checks to ensure only the best available materials are being used - -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), -these orders will automatically upgrade your military equipment to masterwork. -Make sure you have a lot of fuel (or magma forges and furnaces) before you turn -``automelt`` on, though! - -This file should only be imported, of course, if you need to equip a military. - -:source:`smelting.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection adds smelting jobs for all ores. It includes handling the ores -already managed by ``military.json``, but has lower limits. This ensures all -ores will be covered if a player imports ``smelting`` but not ``military``, but -the higher-volume ``military`` orders will take priority if both are imported. - -:source:`rockstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This collection of orders keeps a small stock of all types of rock furniture. -This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or -other rooms with `buildingplan` and your masons will make sure there is always -stock on hand to fulfill the plans. - -:source:`glassstock.json ` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Similar to ``rockstock`` above, this collection keeps a small stock of all types -of glass furniture. If you have a functioning glass industry, this is more -sustainable than ``rockstock`` since you can never run out of sand. If you have -plenty of rock and just want the variety, you can import both ``rockstock`` and -``glassstock`` to get a mixture of rock and glass furnishings in your fort. - -There are a few items that ``glassstock`` produces that ``rockstock`` does not, -since there are some items that can not be made out of rock, for example: - -- tubes and corkscrews for building magma-safe screw pumps -- windows -- terrariums (as an alternative to wooden cages) - -The ``professions/`` subfolder ------------------------------- - -The :source:`professions/ ` subfolder contains -professions, or sets of related labors, that you can assign to your dwarves with -the DFHack `manipulator` plugin. Copy them into the ``professions/`` -subdirectory under the main Dwarf Fortress folder (you may have to create this -subdirectory) and assign them to your dwarves in the manipulator UI, accessible -from the ``units`` screen via the :kbd:`l` hotkey. Make sure that the -``manipulator`` plugin is enabled in your ``dfhack.init`` file! You can assign a -profession to a dwarf by selecting the dwarf in the ``manipulator`` UI and -hitting :kbd:`p`. The list of professions that you copied into the -``professions/`` folder will show up for you to choose from. This is very useful -for assigning roles to new migrants to ensure that all the tasks in your fort -have adequate numbers of dwarves attending to them. - -If you'd rather use Dwarf Therapist to manage your labors, it is easy to import -these professions to DT and use them there. Simply assign the professions you -want to import to a dwarf. Once you have assigned a profession to at least one -dwarf, you can select "Import Professions from DF" in the DT "File" menu. The -professions will then be available for use in DT. - -In the charts below, the "At Start" and "Max" columns indicate the approximate -number of dwarves of each profession that you are likely to need at the start of -the game and how many you are likely to need in a mature fort. - -============= ======== ===== ================================================= -Profession At Start Max Description -============= ======== ===== ================================================= -Chef 0 3 Buchery, Tanning, and Cooking. It is important to - focus just a few dwarves on cooking since - well-crafted meals make dwarves very happy. They - are also an excellent trade good. -Craftsdwarf 0 4-6 All labors used at Craftsdwarf's workshops, - Glassmaker's workshops, and kilns. -Doctor 0 2-4 The full suite of medical labors, plus Animal - Caretaking for those using the dwarfvet plugin. -Farmer 1 4 Food- and animal product-related labors. This - profession also has the ``Alchemist`` labor - enabled since they need to focus on food-related - jobs, though you might want to disable - ``Alchemist`` for your first farmer until there - are actual farming duties to perform. -Fisherdwarf 0 0-1 Fishing and fish cleaning. If you assign this - profession to any dwarf, be prepared to be - inundated with fish. Fisherdwarves *never stop - fishing*. Be sure to also run ``prioritize -a - PrepareRawFish ExtractFromRawFish`` (or use the - ``onMapLoad_dreamfort.init`` file above) or else - caught fish will just be left to rot. -Hauler 0 >20 All hauling labors plus Siege Operating, Mechanic - (so haulers can assist in reloading traps) and - Architecture (so haulers can help build massive - windmill farms and pump stacks). As you - accumulate enough Haulers, you can turn off - hauling labors for other dwarves so they can - focus on their skilled tasks. You may also want - to restrict your Mechanic's workshops to only - skilled mechanics so your haulers don't make - low-quality mechanisms. -Laborer 0 10-12 All labors that don't improve quality with skill, - such as Soapmaking and furnace labors. -Marksdwarf 0 10-30 Similar to Hauler. See the description for - Meleedwarf below for more details. -Mason 2 2-4 Masonry, Gem Cutting/Encrusting, and - Architecture. In the early game, you may need to - run "`prioritize` ConstructBuilding" to get your - masons to build wells and bridges if they are too - busy crafting stone furniture. -Meleedwarf 0 20-50 Similar to Hauler, but without most civilian - labors. This profession is separate from Hauler - so you can find your military dwarves easily. - Meleedwarves and Marksdwarves have Mechanics and - hauling labors enabled so you can temporarily - deactivate your military after sieges and allow - your military dwarves to help clean up. -Migrant 0 0 You can assign this profession to new migrants - temporarily while you sort them into professions. - Like Marksdwarf and Meleedwarf, the purpose of - this profession is so you can find your new - dwarves more easily. -Miner 2 2-10 Mining and Engraving. This profession also has - the ``Alchemist`` labor enabled, which disables - hauling for those using the `autohauler` plugin. - Once the need for Miners tapers off in the late - game, dwarves with this profession make good - military dwarves, wielding their picks as - weapons. -Outdoorsdwarf 1 2-4 Carpentry, Bowyery, Woodcutting, Animal Training, - Trapping, Plant Gathering, Beekeeping, and Siege - Engineering. -Smith 0 2-4 Smithing labors. You may want to specialize your - Smiths to focus on a single smithing skill to - maximize equipment quality. -StartManager 1 0 All skills not covered by the other starting - professions (Miner, Mason, Outdoorsdwarf, and - Farmer), plus a few overlapping skills to - assist in critical tasks at the beginning of the - game. Individual labors should be turned off as - migrants are assigned more specialized - professions that cover them, and the StartManager - dwarf can eventually convert to some other - profession. -Tailor 0 2 Textile industry labors: Dying, Leatherworking, - Weaving, and Clothesmaking. -============= ======== ===== ================================================= - -A note on autohauler -~~~~~~~~~~~~~~~~~~~~ - -These profession definitions are designed to work well with or without the -`autohauler` plugin (which helps to keep your dwarves focused on skilled labors -instead of constantly being distracted by hauling). If you do want to use -autohauler, adding the following lines to your ``onMapLoad.init`` file will -configure it to let the professions manage the "Feed water to civilians" and -"Recover wounded" labors instead of enabling those labors for all hauling -dwarves:: - - on-new-fortress enable autohauler - on-new-fortress autohauler FEED_WATER_CIVILIANS allow - on-new-fortress autohauler RECOVER_WOUNDED allow diff --git a/docs/guides/index.rst b/docs/guides/index.rst index 7208b5276..96c5688d2 100644 --- a/docs/guides/index.rst +++ b/docs/guides/index.rst @@ -1,11 +1,14 @@ =========== -User Guides +User guides =========== These pages are detailed guides covering DFHack tools. .. toctree:: :maxdepth: 1 - :glob: - * + /docs/guides/examples-guide + /docs/guides/modding-guide + /docs/guides/quickfort-library-guide + /docs/guides/quickfort-user-guide + /docs/guides/quickfort-alias-guide diff --git a/docs/guides/modding-guide.rst b/docs/guides/modding-guide.rst new file mode 100644 index 000000000..da33a5a9d --- /dev/null +++ b/docs/guides/modding-guide.rst @@ -0,0 +1,504 @@ +.. _modding-guide: + +DFHack modding guide +==================== + +.. highlight:: lua + +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 `__. + +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. + +A mod-maker's development environment +------------------------------------- + +While you're writing your mod, you need a place to store your in-development +scripts that will: + +- be directly runnable by DFHack +- not get lost when you upgrade DFHack + +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. + +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 + +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. ``data/save/*/raw/scripts/`` +3. ``raw/scripts/`` +4. ``hack/scripts/`` + +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. + +Your first script +----------------- + +So! It's time to write your first script. This section will walk you through how +to make a script that will get the pronoun type of the currently selected unit. + +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 ``unit`` is ``nil``, we don't want the script to run anymore:: + + if not unit then + return + end + +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:: + + 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. + +Exploring DF structures +----------------------- + +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 +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. + +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 +------------------ + +The common method for injecting new behaviour into the game is to define a +callback function and get it called when something interesting happens. DFHack +provides two libraries for this, ``repeat-util`` and `eventful `. +``repeat-util`` is used to run a function once per a configurable number of +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! + +``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 +being completed or a projectile moving. + +To get something to run once per tick, we can call +``repeat-util.scheduleEvery()``. First, we load the module:: + + local repeatUtil = require('repeat-util') + +Both ``repeat-util`` and ``eventful`` require keys for registered callbacks. You +should use something unique, like your mod name:: + + local modId = "callback-example-mod" + +Then, we pass the key, amount of time units between function calls, what the +time units are, and finally the callback function itself:: + + repeatUtil.scheduleEvery(modId, 1, "ticks", function() + -- Do something like iterating over all active units and + -- check for something interesting + for _, unit in ipairs(df.global.world.units.active) do + ... + end + end) + +``eventful`` is slightly more involved. First get the module:: + + local eventful = require('plugins.eventful') + +``eventful`` contains a table for each event which you populate with functions. +Each function in the table is then called with the appropriate arguments when +the event occurs. So, for example, to print the position of a moving (item) +projectile:: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + print(projectile.cur_pos.x, projectile.cur_pos.y, + projectile.cur_pos.z) + end + +Check out the `full list of supported events ` to see what else +you can react to with ``eventful``. + +Now, you may have noticed that you won't be able to register multiple callbacks +with a single key named after your mod. You can, of course, call all the +functions you want from a single registered callback. Alternately, you can +create multiple callbacks using different keys, using your mod ID as a key name +prefix. If you do register multiple callbacks, though, there are no guarantees +about the call order. + +Custom raw tokens +----------------- + +.. highlight:: none + +In this section, we are going to use `custom raw tokens ` +applied to a reaction to transfer the material of a reagent to a product as a +handle improvement (like on artifact buckets), and then we are going to see how +you could make boots that make units go faster when worn. + +First, let's define a custom crossbow with its own custom reaction. The +crossbow:: + + [ITEM_WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE] + [NAME:crossbow:crossbows] + [SIZE:600] + [SKILL:HAMMER] + [RANGED:CROSSBOW:BOLT] + [SHOOT_FORCE:4000] + [SHOOT_MAXVEL:800] + [TWO_HANDED:0] + [MINIMUM_SIZE:17500] + [MATERIAL_SIZE:4] + [ATTACK:BLUNT:10000:4000:bash:bashes:NO_SUB:1250] + [ATTACK_PREPARE_AND_RECOVER:3:3] + [SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER:2] custom token (you'll see) + +The reaction to make it (you would add the reaction and not the weapon to an +entity raw):: + + [REACTION:MAKE_SIEGE_CROSSBOW] + [NAME:make siege crossbow] + [BUILDING:BOWYER:NONE] + [SKILL:BOWYER] + [REAGENT:mechanism 1:2:TRAPPARTS:NONE:NONE:NONE] + [REAGENT:bar:150:BAR:NONE:NONE:NONE] + [METAL_ITEM_MATERIAL] + [REAGENT:handle 1:1:BLOCKS:NONE:NONE:NONE] wooden handles + [ANY_PLANT_MATERIAL] + [REAGENT:handle 2:1:BLOCKS:NONE:NONE:NONE] + [ANY_PLANT_MATERIAL] + [SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT:1] + another custom token + [PRODUCT:100:1:WEAPON:ITEM_WEAPON_CROSSBOW_SIEGE:GET_MATERIAL_FROM_REAGENT:bar:NONE] + +So, we are going to use the ``eventful`` module to make it so that (after the +script is run) when this crossbow is crafted, it will have two handles, each +with the material given by the block reagents. + +.. highlight:: lua + +First, require the modules we are going to use:: + + local eventful = require("plugins.eventful") + local customRawTokens = require("custom-raw-tokens") + +Now, let's make a callback (we'll be defining the body of this function soon):: + + local modId = "siege-crossbow-mod" + eventful.onReactionComplete[modId] = function(reaction, + reactionProduct, unit, inputItems, inputReagents, + outputItems) + +First, we check to see if it the reaction that just happened is relevant to this +callback:: + + if not customRawTokens.getToken(reaction, + "SIEGE_CROSSBOW_MOD_TRANSFER_HANDLE_MATERIAL_TO_PRODUCT_IMPROVEMENT") + then + return + end + +Then, we get the product number listed. Next, for every reagent, if the reagent +name starts with "handle" then we get the corresponding item, and... + +:: + + for i, reagent in ipairs(inputReagents) do + if reagent.code:startswith('handle') then + -- Found handle reagent + local item = inputItems[i] + +...We then add a handle improvement to the listed product within our loop:: + + local new = df.itemimprovement_itemspecificst:new() + new.mat_type, new.mat_index = item.mat_type, item.mat_index + new.type = df.itemimprovement_specific_type.HANDLE + outputItems[productNumber - 1].improvements:insert('#', new) + +This works well as long as you don't have multiple stacks filling up one +reagent. + +Let's also make some code to modify the fire rate of our siege crossbow:: + + eventful.onProjItemCheckMovement[modId] = function(projectile) + if projectile.distance_flown > 0 then + -- don't make this adjustment more than once + return + end + + local firer = projectile.firer + if not firer then + return + end + + local weapon = df.item.find(projectile.bow_id) + if not weapon then + return + end + + local multiplier = tonumber(customRawTokens.getToken( + weapon.subtype, + "SIEGE_CROSSBOW_MOD_FIRE_RATE_MULTIPLIER")) or 1 + firer.counters.think_counter = math.floor( + firer.counters.think_counter * multiplier) + end + +.. highlight:: none + +Now, let's see how we could make some "pegasus boots". First, let's define the +item in the raws:: + + [ITEM_SHOES:ITEM_SHOES_BOOTS_PEGASUS] + [NAME:pegasus boot:pegasus boots] + [ARMORLEVEL:1] + [UPSTEP:1] + [METAL_ARMOR_LEVELS] + [LAYER:OVER] + [COVERAGE:100] + [LAYER_SIZE:25] + [LAYER_PERMIT:15] + [MATERIAL_SIZE:2] + [METAL] + [LEATHER] + [HARD] + [PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK:5] custom raw token + (you don't have to comment the custom token every time, + but it does clarify what it is) + +.. highlight:: lua + +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 movement +timer:: + + for _, unit in ipairs(df.global.world.units.active) do + local amount = 0 + for _, entry in ipairs(unit.inventory) do + if entry.mode == df.unit_inventory_item.T_mode.Worn then + local reduction = customRawTokens.getToken( + entry.item, + 'PEGASUS_BOOTS_MOD_MOVEMENT_TIMER_REDUCTION_PER_TICK') + amount = amount + (tonumber(reduction) or 0) + end + end + end + + -- Subtract amount from movement timer if currently moving + dfhack.units.addMoveTimer(-amount) + +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:: + + raw/init.d/example-mod.lua + raw/objects/... + raw/scripts/example-mod.lua + raw/scripts/example-mod/... + README.md + +Let's go through that line by line. + +* A short (one-line) script in ``raw/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 ``raw/scripts/`` that handles enabling and disabling your + mod. +* A subfolder for your mod under ``raw/scripts/`` will contain all the internal + scripts and/or modules used by your 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. + +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``:: + + +/path/to/mymods/example-mod/raw/scripts/ + +Ok, you're all set up! Now, let's take a look at an example +``raw/scripts/example-mod.lua`` file:: + + -- 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 + + 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') + + enabled = enabled or false + local modId = 'example-mod' + + if not dfhack_flags.enable then + print(usage) + print() + print(('Example mod is currently '):format( + enabled and 'enabled' or 'disabled')) + return + end + + if dfhack_flags.enable_state then + -- do any initialization your internal scripts might require + moduleA.onLoad() + moduleB.onLoad() + + -- multiple functions in the same repeat callback + repeatUtil.scheduleEvery(modId .. ' every tick', 1, 'ticks', function() + moduleA.every1Tick() + moduleB.every1Tick() + end) + + -- one function per repeat callback (you can put them in the + -- above format if you prefer) + repeatUtil.scheduleEvery(modId .. ' 100 frames', 1, 'frames', + moduleD.every100Frames) + + -- multiple functions in the same eventful callback + eventful.onReactionComplete[modId] = function(reaction, + reaction_product, unit, input_items, input_reagents, + output_items) + -- pass the event's parameters to the listeners + moduleB.onReactionComplete(reaction, reaction_product, + unit, input_items, input_reagents, output_items) + moduleC.onReactionComplete(reaction, reaction_product, + unit, input_items, input_reagents, output_items) + end + + -- one function per eventful callback (you can put them in the + -- above format if you prefer) + eventful.onProjItemCheckMovement[modId] = moduleD.onProjItemCheckMovement + eventful.onProjUnitCheckMovement[modId] = moduleD.onProjUnitCheckMovement + + print('Example mod enabled') + enabled = true + else + -- call any shutdown functions your internal scripts might require + moduleA.onUnload() + + repeatUtil.cancel(modId .. ' every ticks') + repeatUtil.cancel(modId .. ' 100 frames') + + eventful.onReactionComplete[modId] = nil + eventful.onProjItemCheckMovement[modId] = nil + eventful.onProjUnitCheckMovement[modId] = nil + + print('Example mod disabled') + 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 +``raw/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:: + + --@ 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 + local function usedByOnTick(unit) + -- ... + end + + function onTick() -- exported + for _,unit in ipairs(df.global.world.units.all) do + usedByOnTick(unit) + 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! diff --git a/docs/guides/quickfort-alias-guide.rst b/docs/guides/quickfort-alias-guide.rst index 37de7603b..a89e6f338 100644 --- a/docs/guides/quickfort-alias-guide.rst +++ b/docs/guides/quickfort-alias-guide.rst @@ -1,10 +1,11 @@ .. _quickfort-alias-guide: -Quickfort Keystroke Alias Guide -=============================== +Quickfort keystroke alias reference +=================================== Aliases allow you to use simple words to represent complicated key sequences -when configuring buildings and stockpiles in quickfort ``#query`` blueprints. +when configuring buildings and stockpiles in quickfort ``#query`` and +``#config`` blueprints. For example, say you have the following ``#build`` and ``#place`` blueprints:: @@ -79,7 +80,7 @@ sequence, potentially with other aliases. If the alias is the only text in the cell, the alias name is matched and its expansion is used. If the alias has other keys before or after it, the alias name must be surrounded in curly brackets (:kbd:`{` and :kbd:`}`). An alias can be surrounded in curly brackets -even if it is the only text in the cell, it just isn't necesary. For example, +even if it is the only text in the cell, it just isn't necessary. For example, the following blueprint uses the ``aliasname`` alias by itself in the first two rows and uses it as part of a longer sequence in the third row:: @@ -453,7 +454,7 @@ be used for either the ``quantum_enable`` or ``route_enable`` sub-aliases. Experienced Dwarf Fortress players may be wondering how the same aliases can work in both contexts since the keys for entering the configuration screen differ. Fear not! There is some sub-alias magic at work here. If you define -your own stockpile configuraiton aliases, you can use the magic yourself by +your own stockpile configuration aliases, you can use the magic yourself by building your aliases on the ``*prefix`` aliases described later in this guide. @@ -521,6 +522,9 @@ give10right give move togglesequence togglesequence2 +forbidsearch search +permitsearch search +togglesearch search masterworkonly prefix artifactonly prefix togglemasterwork prefix @@ -588,6 +592,12 @@ four adjacent items:: dye: {foodprefix}b{Right}{Down 11}{Right}{Down 28}{togglesequence 4}^ +``forbidsearch``, ``permitsearch``, and ``togglesearch`` use the DFHack +`search-plugin` plugin to forbid or permit a filtered list, or toggle the first +(or only) item in the list. Specify the search string in the ``search`` +sub-alias. Be sure to move the cursor over to the right column before invoking +these aliases. The search filter will be cleared before this alias completes. + Finally, the ``masterwork`` and ``artifact`` group of aliases configure the corresponding allowable core quality for the stockpile categories that have them. This alias is used to implement category-specific aliases below, like @@ -642,7 +652,7 @@ sheetprefix enablesheet disablesheet Then, for each item category, there are aliases that manipulate interesting subsets of that category: -* Exclusive aliases forbid everthing within a category and then enable only +* Exclusive aliases forbid everything within a category and then enable only the named item type (or named class of items) * ``forbid*`` aliases forbid the named type and leave the rest of the stockpile untouched. @@ -724,13 +734,16 @@ shells forbidshells permitshells teeth forbidteeth permitteeth horns forbidhorns permithorns hair forbidhair permithair +usablehair forbidusablehair permitusablehair craftrefuse forbidcraftrefuse permitcraftrefuse =========== ================== ================== Notes: -* ``craftrefuse`` includes everything a craftsdwarf can use: skulls, bones, - shells, teeth, horns, and hair. +* ``usablehair`` Only hair and wool that can make usable clothing is included, + i.e. from sheep, llamas, alpacas, and trolls. +* ``craftrefuse`` includes everything a craftsdwarf or tailor can use: skulls, + bones, shells, teeth, horns, and "usable" hair/wool (defined above). Stone stockpile adjustments ``````````````````````````` @@ -802,7 +815,8 @@ Finished goods stockpile adjustments ======================= ============================= ============================= Exclusive Forbid Permit ======================= ============================= ============================= -jugs +stonetools +woodentools crafts forbidcrafts permitcrafts goblets forbidgoblets permitgoblets masterworkfinishedgoods forbidmasterworkfinishedgoods permitmasterworkfinishedgoods @@ -812,17 +826,18 @@ artifactfinishedgoods forbidartifactfinishedgoods permitartifactfinishedgo Cloth stockpile adjustments ``````````````````````````` -+------------------+ -| Exclusive | -+==================+ -| thread | -+------------------+ -| adamantinethread | -+------------------+ -| cloth | -+------------------+ -| adamantinecloth | -+------------------+ +================ ====================== ====================== +Exclusive Forbid Permit +================ ====================== ====================== +thread forbidthread permitthread +adamantinethread forbidadamantinethread permitadamantinethread +cloth forbidcloth permitcloth +adamantinecloth forbidadamantinecloth permitadamantinecloth +================ ====================== ====================== + +Notes: + +* ``thread`` and ``cloth`` refers to all materials that are not adamantine. Weapon stockpile adjustments ```````````````````````````` diff --git a/docs/guides/quickfort-library-guide.rst b/docs/guides/quickfort-library-guide.rst index 366272052..157bb10a1 100644 --- a/docs/guides/quickfort-library-guide.rst +++ b/docs/guides/quickfort-library-guide.rst @@ -1,16 +1,16 @@ .. _blueprint-library-guide: .. _quickfort-library-guide: -Blueprint Library Index -======================= +Quickfort blueprint library +=========================== This guide contains a high-level overview of the blueprints available in the -:source:`quickfort blueprint library `. You can list -library blueprints by running ``quickfort list --library`` or by hitting -:kbd:`Alt`:kbd:`l` in the ``quickfort gui`` interactive dialog. +: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. +blueprints do before you run them. Also, if you use `gui/quickfort`, you will +get a live preview of which tiles will be modified by the blueprint before you +apply it to your map. Whole fort blueprint sets ------------------------- @@ -34,7 +34,7 @@ automate basic fort needs, such as food, booze, and item production. It can function by itself or as the core of a larger, more ambitious fortress. Read the high-level walkthrough by running ``quickfort run library/dreamfort.csv`` and list the walkthroughs for the individual levels by running ``quickfort list -l -dreamfort -m notes`` or ``quickfort gui -l dreamfort notes``. +dreamfort -m notes`` or ``gui/quickfort dreamfort notes``. Dreamfort blueprints are available for easy viewing and copying `online `__. @@ -47,14 +47,13 @@ and a convenient `checklist `__ from which you can copy the ``quickfort`` commands. -You can download a fully built Dreamfort-based fort from `dffd -`__, load it, and explore it -interactively. +If you like, you can download a fully built Dreamfort-based fort from +:dffd:`dffd <15434>`, load it, and explore it interactively. Visual overview ``````````````` -Here are some annotated screenshots of the major levels (or click `here +Here are annotated screenshots of the major Dreamfort levels (or click `here `__ for a slideshow). @@ -136,7 +135,8 @@ The Quick Fortress is an updated version of the example fortress that came with inspired DFHack quickfort). While it is not a complete fortress by itself, it is much simpler than Dreamfort and is good for a first introduction to `quickfort` blueprints. Read its walkthrough with ``quickfort run -library/quickfortress.csv``. +library/quickfortress.csv`` or view the blueprints `online +`__. Layout helpers -------------- @@ -194,19 +194,19 @@ Extra blueprints that are useful in specific situations. - :source:`library/embark.csv ` - :source:`library/pump_stack.csv ` -Light Aquifer Tap +Light aquifer tap ~~~~~~~~~~~~~~~~~ The aquifer tap helps you create a safe, everlasting source of fresh water from -a light aquifer. See the step-by-step guide, including informaton on how to +a light aquifer. See the step-by-step guide, including information on how to create a drainage system so your dwarves don't drown when digging the tap, by running ``quickfort run library/aquifer_tap.csv -n /help``. You can see how to nullify the water pressure (so you don't flood your fort) in the `Dreamfort screenshot above `. -Blueprint spreadsheet also available -`online `__ +The blueprint spreadsheet is also available +`online `__. Post-embark ~~~~~~~~~~~ @@ -216,12 +216,12 @@ blueprint that builds important starting workshops (mason, carpenter, mechanic, and craftsdwarf) and a ``#place`` blueprint that lays down a pattern of useful starting stockpiles. -Pump Stack +Pump stack ~~~~~~~~~~ -The pump stack blueprints help you move water and magma up to move convenient +The pump stack blueprints help you move water and magma up to more convenient locations in your fort. See the step-by-step guide for using it by running ``quickfort run library/pump_stack.csv -n /help``. -Blueprint spreadsheet also available -`online `__ +The blueprint spreadsheet is also available +`online `__. diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index 5225aea34..6f606c43a 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1,8 +1,8 @@ .. _quickfort-blueprint-guide: .. _quickfort-user-guide: -Quickfort Blueprint Editing Guide -================================= +Quickfort blueprint creation guide +================================== `Quickfort ` is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, @@ -29,9 +29,8 @@ 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 your DFHack installation. Browse them on your computer or -:source:`online `, or run ``quickfort list -l`` at the -``[DFHack]#`` prompt to list them, and then ``quickfort run`` to apply them to -your fort! +: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 the easiest way to make a quickfort blueprint is to build your plan "for real" @@ -191,8 +190,8 @@ dug-out area:: Cw Cw Cw # # # # # # -Note my generosity -- in addition to the bed (:kbd:`b`) I've built a chest -(:kbd:`c`) here for the dwarf as well. You must use the full series of keys +Note my generosity -- in addition to the bed (:kbd:`b`) I've built a container +(:kbd:`h`) here for the dwarf as well. You must use the full series of keys needed to build something in each cell, e.g. :kbd:`C`:kbd:`w` indicates we should enter DF's constructions submenu (:kbd:`C`) and select walls (:kbd:`w`). @@ -246,7 +245,7 @@ If there weren't an alias named ``booze`` then the literal characters spell those aliases correctly! You can save a lot of time and effort by using aliases instead of adding all -key seqences directly to your blueprints. For more details, check out the +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 @@ -684,7 +683,7 @@ three vertical tiles like this:: ` end here ` # # # # # -Then to carve the cross, you'd do a horizonal segment:: +Then to carve the cross, you'd do a horizontal segment:: ` ` ` # start here ` end here # @@ -741,7 +740,7 @@ Or you could use the aliases to specify tile by tile:: # # # # The aliases can also be used to designate a solid block of track. This is -epecially useful for obliterating low-quality engravings so you can re-smooth +especially useful for obliterating low-quality engravings so you can re-smooth and re-engrave with higher quality. For example, you could use the following sequence of blueprints to ensure a 10x10 floor area contains only masterwork engravings:: @@ -1158,7 +1157,7 @@ blueprint:: "#meta label(help) message(This is the help text for the blueprint set contained in this file. - First, make sure that you embark in...) blueprint set walkthough" + First, make sure that you embark in...) blueprint set walkthrough" could more naturally be written as a ``#notes`` blueprint:: @@ -1418,15 +1417,10 @@ Tips and tricks Caveats and limitations ----------------------- -- If you use the ``jugs`` alias in your ``#query``-mode blueprints, be aware - that there is no way to differentiate jugs from other types of tools in the - game. Therefore, ``jugs`` stockpiles will also take nest boxes, scroll - rollers, and other tools. The only workaround is not to have other tools - lying around in your fort. - -- Likewise for the ``bags`` alias. The game does not differentiate between - empty and full bags, so you'll get bags of gypsum power in your "bags" - stockpile unless you are careful to assign all your gypsum to your hospital. +- If you use the the ``bags`` alias, be aware that the game does not + differentiate between empty and full bags. Therefore, you can get bags of + gypsum power in your "bags" stockpile unless you are careful to assign all + your gypsum to your hospital. - Weapon traps and upright spear/spike traps can currently only be built with a single weapon. @@ -1467,8 +1461,7 @@ parts that might not be obvious just from looking at them. If you haven't built Dreamfort before, maybe try an embark in a flat area and take it for a spin! It will help put the following sections in context. There is also a pre-built Dreamfort available for download on -`dffd `__ if you just want an -interactive reference. +:dffd:`dffd <15434>` if you just want an interactive reference. Dreamfort organization and packaging ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1746,7 +1739,7 @@ priorities `. Use dig priorities to control ramp creation. We can `ensure `__ -the bottom level is carved out before the layer above is channelled by assigning +the bottom level is carved out before the layer above is channeled by assigning the channel designations lower priorities (the ``h5``\s in the third layer -- scroll down). diff --git a/docs/images/stonesense-roadtruss.jpg b/docs/images/stonesense-roadtruss.jpg new file mode 100644 index 000000000..19e71dd70 Binary files /dev/null and b/docs/images/stonesense-roadtruss.jpg differ diff --git a/docs/index-dev.rst b/docs/index-dev.rst index 5a03cbedf..38c458bfe 100644 --- a/docs/index-dev.rst +++ b/docs/index-dev.rst @@ -1,5 +1,5 @@ ======================== -DFHack Development Guide +DFHack development guide ======================== These are pages relevant to people developing for DFHack. diff --git a/docs/plugins/3dveins.rst b/docs/plugins/3dveins.rst new file mode 100644 index 000000000..96fbcf836 --- /dev/null +++ b/docs/plugins/3dveins.rst @@ -0,0 +1,37 @@ +3dveins +======= + +.. dfhack-tool:: + :summary: Rewrite layer veins to expand in 3D space. + :tags: 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 +reported by `prospect`. + +Usage +----- + +:: + + 3dveins [verbose] + +The ``verbose`` option prints out extra information to the console. + +Example +------- + +:: + + 3dveins + +New veins are generated using natural-looking 3D Perlin noise in order to +produce a layout that flows smoothly between z-levels. The vein distribution is +based on the world seed, so running the command for the second time should +produce no change. It is best to run it just once immediately after embark. + +This command is intended as only a cosmetic change, so it takes care to exactly +preserve the mineral counts reported by ``prospect all``. The amounts of layer +stones may slightly change in some cases if vein mass shifts between z layers. + +The only undo option is to restore your save from backup. diff --git a/docs/plugins/RemoteFortressReader.rst b/docs/plugins/RemoteFortressReader.rst new file mode 100644 index 000000000..c70adc608 --- /dev/null +++ b/docs/plugins/RemoteFortressReader.rst @@ -0,0 +1,24 @@ +RemoteFortressReader +==================== + +.. dfhack-tool:: + :summary: Backend for Armok Vision. + :tags: dev graphics + :no-command: + +.. dfhack-command:: RemoteFortressReader_version + :summary: Print the loaded RemoteFortressReader version. + +.. dfhack-command:: load-art-image-chunk + :summary: Gets an art image chunk by index. + +This plugin provides an API for realtime remote fortress visualization. See +:forums:`Armok Vision <146473>`. + +Usage +----- + +``RemoteFortressReader_version`` + Print the loaded RemoteFortressReader version. +``load-art-image-chunk `` + Gets an art image chunk by index, loading from disk if necessary. diff --git a/docs/plugins/add-spatter.rst b/docs/plugins/add-spatter.rst new file mode 100644 index 000000000..1ce32fcbe --- /dev/null +++ b/docs/plugins/add-spatter.rst @@ -0,0 +1,17 @@ +add-spatter +=========== + +.. dfhack-tool:: + :summary: Make tagged reactions produce contaminants. + :tags: adventure fort gameplay items + :no-command: + +Give some use to all those poisons that can be bought from caravans! The plugin +automatically enables itself when you load a world with reactions that include +names starting with ``SPATTER_ADD_``, so there are no commands to run to use it. +These reactions will then produce contaminants on items instead of improvements. +The contaminants are immune to being washed away by water or destroyed by +`clean`. + +You must have a mod installed that adds the appropriate tokens in order for this +plugin to do anything. diff --git a/docs/plugins/autobutcher.rst b/docs/plugins/autobutcher.rst new file mode 100644 index 000000000..f288f9073 --- /dev/null +++ b/docs/plugins/autobutcher.rst @@ -0,0 +1,111 @@ +autobutcher +=========== + +.. dfhack-tool:: + :summary: Automatically butcher excess livestock. + :tags: fort auto fps animals + +This plugin monitors how many pets you have of each gender and age and assigns +excess livestock for slaughter. It requires that you add the target race(s) to a +watch list. Units will be ignored if they are: + +* Untamed +* Nicknamed (for custom protection; you can use the `rename` ``unit`` tool + individually, or `zone` ``nick`` for groups) +* Caged, if and only if the cage is defined as a room (to protect zoos) +* Trained for war or hunting + +Creatures who will not reproduce (because they're not interested in the +opposite sex or have been gelded) will be butchered before those who will. +Older adults and younger children will be butchered first if the population +is above the target (defaults are: 1 male kid, 5 female kids, 1 male adult, +5 female adults). Note that you may need to set a target above 1 to have a +reliable breeding population due to asexuality etc. See `fix-ster` if this is a +problem. + +Usage +----- + +``enable autobutcher`` + Start processing livestock according to the configuration. Note that + no races are watched by default. You have to add the ones you want to + monitor with ``autobutcher watch``, ``autobutcher target`` or + ``autobutcher autowatch``. +``autobutcher autowatch`` + Automatically add all new races (animals you buy from merchants, tame + yourself, or get from migrants) to the watch list using the default target + counts. +``autobutcher noautowatch`` + Stop auto-adding new races to the watch list. +``autobutcher target all|new| [ ...]`` + Set target counts for the specified races: + - fk = number of female kids + - mk = number of male kids + - fa = number of female adults + - ma = number of female adults + If you specify ``all``, then this command will set the counts for all races + on your current watchlist (including the races which are currently set to + 'unwatched') and sets the new default for future watch commands. If you + specify ``new``, then this command just sets the new default counts for + future watch commands without changing your current watchlist. Otherwise, + all space separated races listed will be modified (or added to the watchlist + if they aren't there already). +``autobutcher ticks `` + Change the number of ticks between scanning cycles when the plugin is + enabled. By default, a cycle happens every 6000 ticks (about 8 game days). +``autobutcher watch all| [ ...]`` + Start watching the listed races. If they aren't already in your watchlist, + then they will be added with the default target counts. If you specify the + keyword ``all``, then all races in your watchlist that are currently marked + as unwatched will become watched. +``autobutcher unwatch all| [ ...]`` + Stop watching the specified race(s) (or all races on your watchlist if + ``all`` is given). The current target settings will be remembered. +``autobutcher forget all| [ ...]`` + Unwatch the specified race(s) (or all races on your watchlist if ``all`` is + given) and forget target settings for it/them. +``autobutcher [list]`` + Print status and current settings, including the watchlist. This is the + default command if autobutcher is run without parameters. +``autobutcher list_export`` + Print commands required to set the current settings in another fort. + +To see a list of all races, run this command: + + devel/query --table df.global.world.raws.creatures.all --search ^creature_id --maxdepth 1 + +Though not all the races listed there are tameable/butcherable. + +.. note:: + + Settings and watchlist are stored in the savegame, so you can have different + settings for each save. If you want to copy your watchlist to another, + savegame, you can export the commands required to recreate your settings. + + To export, open an external terminal in the DF directory, and run + ``dfhack-run autobutcher list_export > filename.txt``. To import, load your + new save and run ``script filename.txt`` in the DFHack terminal. + +Examples +-------- + +Keep at most 7 kids (4 female, 3 male) and at most 3 adults (2 female, 1 male) +for turkeys. Once the kids grow up, the oldest adults will get slaughtered. +Excess kids will get slaughtered starting the the youngest to allow that the +older ones grow into adults:: + + autobutcher target 4 3 2 1 BIRD_TURKEY + +Configure useful limits for dogs, cats, geese (for eggs, leather, and bones), +alpacas, sheep, and llamas (for wool), and pigs (for milk and meat). All other +unnamed tame units will be marked for slaughter as soon as they arrive in your +fortress:: + + enable autobutcher + autobutcher target 2 2 2 2 DOG + autobutcher target 1 1 2 2 CAT + autobutcher target 50 50 14 2 BIRD_GOOSE + autobutcher target 2 2 4 2 ALPACA SHEEP LLAMA + autobutcher target 5 5 6 2 PIG + autobutcher target 0 0 0 0 new + autobutcher autowatch diff --git a/docs/plugins/autochop.rst b/docs/plugins/autochop.rst new file mode 100644 index 000000000..6b51b85cb --- /dev/null +++ b/docs/plugins/autochop.rst @@ -0,0 +1,27 @@ +autochop +======== + +.. dfhack-tool:: + :summary: Auto-harvest trees when low on stockpiled logs. + :tags: fort auto plants + :no-command: + +This plugin can designate trees for chopping when your stocks are low on logs. + +Usage +----- + +:: + + enable autochop + +Then, open the settings menu with :kbd:`c` from the designations menu (the +option appears when you have "Chop Down Trees" selected with :kbd:`d`-:kbd:`t`). + +Set your desired thresholds and enable autochopping with :kbd:`a`. + +You can also restrict autochopping to specific burrows. Highlight a burrow name +with the Up/Down arrow keys and hit :kbd:`Enter` to mark it as the autochop +burrrow. + +Autochop checks your stock of logs and designates trees once every in game day. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst new file mode 100644 index 000000000..40fd4d996 --- /dev/null +++ b/docs/plugins/autoclothing.rst @@ -0,0 +1,32 @@ +autoclothing +============ + +.. dfhack-tool:: + :summary: Automatically manage clothing work orders. + :tags: fort auto workorders + +This command allows you to set how many of each clothing type every citizen +should have. + +Usage +----- + +:: + + autoclothing + autoclothing [quantity] + +``material`` can be "cloth", "silk", "yarn", or "leather". The ``item`` can be +anything your civilization can produce, such as "dress" or "mitten". + +When invoked without parameters, it shows a summary of all managed clothing +orders. When invoked with a material and item, but without a quantity, it shows +the current configuration for that material and item. + +Examples +-------- + +``autoclothing cloth "short skirt" 10`` + Sets the desired number of cloth short skirts available per citizen to 10. +``autoclothing cloth dress`` + Displays the currently set number of cloth dresses chosen per citizen. diff --git a/docs/plugins/autodump.rst b/docs/plugins/autodump.rst new file mode 100644 index 000000000..03e10db9c --- /dev/null +++ b/docs/plugins/autodump.rst @@ -0,0 +1,71 @@ +autodump +======== + +.. dfhack-tool:: + :summary: Automatically set items in a stockpile to be dumped. + :tags: fort armok fps productivity items stockpiles + :no-command: + +.. dfhack-command:: autodump + :summary: Teleports items marked for dumping to the cursor position. + +.. dfhack-command:: autodump-destroy-here + :summary: Destroy items marked for dumping under the cursor. + +.. dfhack-command:: autodump-destroy-item + :summary: Destroys the selected item. + +When `enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``autodump`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be dumped. + +When invoked as a command, it can instantly move all unforbidden items marked +for dumping to the tile under the cursor. After moving the items, the dump flag +is unset and the forbid flag is set, just as if it had been dumped normally. Be +aware that dwarves that are en route to pick up the item for dumping may still +come and move the item to your dump zone. + +The cursor must be placed on a floor tile so the items can be dumped there. + +Usage +----- + +:: + + enable autodump + autodump [] + autodump-destroy-here + autodump-destroy-item + +``autodump-destroy-here`` is an alias for ``autodump destroy-here`` and is +intended for use as a keybinding. + +``autodump-destroy-item`` destroys only the selected item. The item may be +selected in the :kbd:`k` list or in the container item list. If called again +before the game is resumed, cancels destruction of the item. + +Options +------- + +``destroy`` + Destroy instead of dumping. Doesn't require a cursor. If ``autodump`` is + called again with this option before the game is resumed, it cancels + pending destroy actions. +``destroy-here`` + Destroy items marked for dumping under the cursor. +``visible`` + Only process items that are not hidden. +``hidden`` + Only process hidden items. +``forbidden`` + Only process forbidden items (default: only unforbidden). + +Examples +-------- + +``autodump`` + Teleports items marked for dumping to the cursor position. +``autodump destroy`` + Destroys all unforbidden items marked for dumping +``autodump-destroy-item`` + Destroys the selected item. diff --git a/docs/plugins/autofarm.rst b/docs/plugins/autofarm.rst new file mode 100644 index 000000000..8896c3117 --- /dev/null +++ b/docs/plugins/autofarm.rst @@ -0,0 +1,37 @@ +autofarm +======== + +.. dfhack-tool:: + :summary: Automatically manage farm crop selection. + :tags: fort auto plants + +Periodically scan your plant stocks and assign crops to your farm plots based on +which plant stocks are low (as long as you have the appropriate seeds). The +target threshold for each crop type is configurable. + +Usage +----- + +``enable autofarm`` + Enable the plugin and start managing crop assignment. +``autofarm runonce`` + Updates all farm plots once, without enabling the plugin. +``autofarm status`` + Prints status information, including any defined thresholds. +``autofarm default `` + Sets the default threshold. +``autofarm threshold [ ...]`` + Sets thresholds of individual plant types. + +You can find the identifiers for the crop types in your world by running the +following command:: + + lua "for _,plant in ipairs(df.global.world.raws.plants.all) do if plant.flags.SEED then print(plant.id) end end" + +Examples +-------- + +``autofarm default 30`` + Set the default threshold to 30. +``autofarm threshold 150 MUSHROOM_HELMET_PLUMP GRASS_TAIL_PIG`` + Set the threshold for Plump Helmets and Pig Tails to 150 diff --git a/docs/plugins/autogems.rst b/docs/plugins/autogems.rst new file mode 100644 index 000000000..d4112c6c0 --- /dev/null +++ b/docs/plugins/autogems.rst @@ -0,0 +1,26 @@ +autogems +======== + +.. dfhack-tool:: + :summary: Automatically cut rough gems. + :tags: fort auto workorders + :no-command: + +.. dfhack-command:: autogems-reload + :summary: Reloads the autogems configuration file. + +Automatically cut rough gems. This plugin periodically scans your stocks of +rough gems and creates manager orders for cutting them at a Jeweler's Workshop. + +Usage +----- + +``enable autogems`` + Enables the plugin and starts autocutting gems according to its + configuration. +``autogems-reload`` + Reloads the autogems configuration file. You might need to do this if you + have manually modified the contents while the game is running. + +Run `gui/autogems` for a configuration UI, or access the ``Auto Cut Gems`` +option from the Current Workshop Orders screen (:kbd:`o`-:kbd:`W`). diff --git a/docs/plugins/autohauler.rst b/docs/plugins/autohauler.rst new file mode 100644 index 000000000..08c84327a --- /dev/null +++ b/docs/plugins/autohauler.rst @@ -0,0 +1,57 @@ +autohauler +========== + +.. dfhack-tool:: + :summary: Automatically manage hauling labors. + :tags: fort auto labors + +Similar to `autolabor`, but instead of managing all labors, autohauler only +addresses hauling labors, leaving the assignment of skilled labors entirely up +to you. You can use the in-game `manipulator` UI or an external tool like Dwarf +Therapist to do so. + +Idle dwarves who are not on active military duty will be assigned the hauling +labors; everyone else (including those currently hauling) will have the hauling +labors removed. This is to encourage every dwarf to do their assigned skilled +labors whenever possible, but resort to hauling when those jobs are not +available. This also implies that the user will have a very tight skill +assignment, with most skilled labors only being assigned to just a few dwarves +and almost every non-military dwarf having at least one skilled labor assigned. + +Autohauler allows a skill to be used as a flag to exempt a dwarf from +autohauler's effects. By default, this is the unused ALCHEMIST labor, but it +can be changed by the user. + +Usage +----- + +``enable autohauler`` + Start managing hauling labors. This is normally all you need to do. + Autohauler works well on default settings. +``autohauler status`` + Show autohauler status and status of fort dwarves. +``autohauler haulers`` + Set whether a particular labor should be assigned to haulers. +``autohauler allow|forbid`` + Set whether a particular labor should mark a dwarf as exempt from hauling. + By default, only the ``ALCHEMIST`` labor is set to ``forbid``. +``autohauler reset-all| reset`` + Reset a particular labor (or all labors) to their default + haulers/allow/forbid state. +``autohauler list`` + Show the active configuration for all labors. +``autohauler frameskip `` + Set the number of frames between runs of autohauler. +``autohauler debug`` + In the next cycle, output the state of every dwarf. + +Examples +-------- + +``autohauler HAUL_STONE haulers`` + Set stone hauling as a hauling labor (this is already the default). +``autohauler RECOVER_WOUNDED allow`` + Allow the "Recover wounded" labor to be manually assigned by the player. By + default it is automatically given to haulers. +``autohauler MINE forbid`` + Don't assign hauling labors to dwarves with the Mining labor enabled. diff --git a/docs/plugins/autolabor.rst b/docs/plugins/autolabor.rst new file mode 100644 index 000000000..63b830dfe --- /dev/null +++ b/docs/plugins/autolabor.rst @@ -0,0 +1,92 @@ +autolabor +========= + +.. dfhack-tool:: + :summary: Automatically manage dwarf labors. + :tags: fort auto labors + +Autolabor attempts to keep as many dwarves as possible busy while allowing +dwarves to specialize in specific skills. + +Autolabor frequently checks how many jobs of each type are available and sets +labors proportionally in order to get them all done quickly. Labors with +equipment -- mining, hunting, and woodcutting -- which are abandoned if labors +change mid-job, are handled slightly differently to minimize churn. + +Dwarves on active military duty or dwarves assigned to burrows are left +untouched by autolabor. + +.. warning:: + + autolabor will override any manual changes you make to labors while it is + enabled, including through other tools such as Dwarf Therapist. + +Usage +----- + +:: + + enable autolabor + +Anything beyond this is optional - autolabor works well with the default +settings. Once you have enabled it in a fortress, it stays enabled until you +explicitly disable it, even if you save and reload your game. + +By default, each labor is assigned to between 1 and 200 dwarves (2-200 for +mining). 33% of the workforce become haulers, who handle all hauling jobs as +well as cleaning, pulling levers, recovering wounded, removing constructions, +and filling ponds. Other jobs are automatically assigned as described above. +Each of these settings can be adjusted. + +Jobs are rarely assigned to nobles with responsibilities for meeting diplomats +or merchants, never to the chief medical dwarf, and less often to the bookkeeper +and manager. + +Hunting is never assigned without a butchery, and fishing is never assigned +without a fishery. + +For each labor, a preference order is calculated based on skill, excluding those +who can't do the job. Dwarves who are masters of a skill are deprioritized for +other skills. The labor is then added to the best dwarves for that +labor, then to additional dwarfs that meet any of these conditions: + +* The dwarf is idle and there are no idle dwarves assigned to this labor +* The dwarf has non-zero skill associated with the labor +* The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. + +We stop assigning dwarves when we reach the maximum allowed. + +Examples +-------- + +``autolabor MINE 5`` + Keep at least 5 dwarves with mining enabled. +``autolabor CUT_GEM 1 1`` + Keep exactly 1 dwarf with gem cutting enabled. +``autolabor COOK 1 1 3`` + Keep 1 dwarf with cooking enabled, selected only from the top 3. +``autolabor FEED_WATER_CIVILIANS haulers`` + Have haulers feed and water wounded dwarves. +``autolabor CUTWOOD disable`` + Turn off autolabor for wood cutting. + +Advanced usage +-------------- + +``autolabor list`` + List current status of all labors. Use this command to see the IDs for all + labors. +``autolabor status`` + Show basic status information. +``autolabor [] []`` + Set range of dwarves assigned to a labor, optionally specifying the size of + the pool of most skilled dwarves that will ever be considered for this + labor. +``autolabor haulers`` + Set a labor to be handled by hauler dwarves. +``autolabor disable`` + Turn off autolabor for a specific labor. +``autolabor reset-all| reset`` + Return a labor (or all labors) to the default handling. + +See `autolabor-artisans` for a differently-tuned setup. diff --git a/docs/plugins/automaterial.rst b/docs/plugins/automaterial.rst new file mode 100644 index 000000000..ac1b3a1da --- /dev/null +++ b/docs/plugins/automaterial.rst @@ -0,0 +1,53 @@ +automaterial +============ + +.. dfhack-tool:: + :summary: Sorts building materials by recent usage. + :tags: 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/automelt.rst b/docs/plugins/automelt.rst new file mode 100644 index 000000000..12e3412e2 --- /dev/null +++ b/docs/plugins/automelt.rst @@ -0,0 +1,18 @@ +automelt +======== + +.. dfhack-tool:: + :summary: Quickly designate items to be melted. + :tags: fort productivity items stockpiles + :no-command: + +When `enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``automelt`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be melted. + +Usage +----- + +:: + + enable automelt diff --git a/docs/plugins/autonestbox.rst b/docs/plugins/autonestbox.rst new file mode 100644 index 000000000..27074fe1c --- /dev/null +++ b/docs/plugins/autonestbox.rst @@ -0,0 +1,33 @@ +autonestbox +=========== + +.. dfhack-tool:: + :summary: Auto-assign egg-laying female pets to nestbox zones. + :tags: fort auto animals + +To use this feature, you must create pen/pasture zones on the same tiles as +built nestboxes. If the pen is bigger than 1x1, the nestbox must be in the top +left corner. Only 1 unit will be assigned per pen, regardless of the size. Egg +layers who are also grazers will be ignored, since confining them to a 1x1 +pasture is not a good idea. Only tame and domesticated own units are processed +since pasturing half-trained wild egg layers could destroy your neat nestbox +zones when they revert to wild. + +Note that the age of the units is not checked, so you might get some egg-laying +kids assigned to the nestbox zones. Most birds grow up quite fast, though, so +they should be adults and laying eggs soon enough. + +Usage +----- + +``enable autonestbox`` + Start checking for unpastured egg-layers and assigning them to nestbox + zones. +``autonestbox`` + Print current status. +``autonestbox now`` + Run a scan and assignment cycle right now. Does not require that the plugin + is enabled. +``autonestbox ticks `` + Change the number of ticks between scan and assignment cycles when the + plugin is enabled. The default is 6000 (about 8 days). diff --git a/docs/plugins/autotrade.rst b/docs/plugins/autotrade.rst new file mode 100644 index 000000000..f355d40ef --- /dev/null +++ b/docs/plugins/autotrade.rst @@ -0,0 +1,18 @@ +autotrade +========= + +.. dfhack-tool:: + :summary: Quickly designate items to be traded. + :tags: fort productivity items stockpiles + :no-command: + +When `enabled `, this plugin adds an option to the :kbd:`q` menu for +stockpiles. When the ``autotrade`` option is selected for the stockpile, any +items placed in the stockpile will automatically be designated to be traded. + +Usage +----- + +:: + + enable autotrade diff --git a/docs/plugins/blueprint.rst b/docs/plugins/blueprint.rst new file mode 100644 index 000000000..6c38a6152 --- /dev/null +++ b/docs/plugins/blueprint.rst @@ -0,0 +1,155 @@ +blueprint +========= + +.. dfhack-tool:: + :summary: Record a live game map in a quickfort blueprint. + :tags: fort design buildings map stockpiles + +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`` +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 +requested width and height. + +Usage +----- + +:: + + blueprint [] [ []] [] + blueprint gui [ []] [] + +Examples +-------- + +``blueprint gui`` + Runs `gui/blueprint`, the GUI frontend, where all configuration for a + ``blueprint`` command can be set visually and interactively. +``blueprint 30 40 bedrooms`` + Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting + from the active cursor on the current z-level. Blueprints are written to + ``bedrooms.csv`` in the ``blueprints`` directory. +``blueprint 30 40 bedrooms dig --cursor 108,100,150`` + Generates only the ``#dig`` blueprint in the ``bedrooms.csv`` file, and + the start of the blueprint area is set to a specific coordinate instead of + using the in-game cursor position. + +Positional parameters +--------------------- + +``width`` + Width of the area (in tiles) to translate. +``height`` + Height of the area (in tiles) to translate. +``depth`` + Number of z-levels to translate. Positive numbers go *up* from the cursor + and negative numbers go *down*. Defaults to 1 if not specified, indicating + that the blueprint should only include the current z-level. +``name`` + Base name for blueprint files created in the ``blueprints`` directory. If no + name is specified, "blueprint" is used by default. The string must contain + some characters other than numbers so the name won't be confused with the + optional ``depth`` parameter. + +Phases +------ + +If you want to generate blueprints only for specific phases, add their names to +the commandline, anywhere after the blueprint base name. You can list multiple +phases; just separate them with a space. + +``dig`` + Generate quickfort ``#dig`` blueprints for digging natural stone. +``carve`` + Generate quickfort ``#dig`` blueprints for smoothing and carving. +``construct`` + Generate quickfort ``#build`` blueprints for constructions (e.g. flooring + and walls). +``build`` + Generate quickfort ``#build`` blueprints for buildings (including + furniture). +``place`` + Generate quickfort ``#place`` blueprints for placing stockpiles. +``zone`` + Generate quickfort ``#zone`` blueprints for designating zones. +``query`` + Generate quickfort ``#query`` blueprints for configuring stockpiles and + naming buildings. +``rooms`` + Generate quickfort ``#query`` blueprints for defining rooms. + +If no phases are specified, phases are autodetected. For example, a ``#place`` +blueprint will be created only if there are stockpiles in the blueprint area. + +Options +------- + +``-c``, ``--cursor ,,`` + Use the specified map coordinates instead of the current cursor position for + the upper left corner of the blueprint range. If this option is specified, + then an active game map cursor is not necessary. +``-e``, ``--engrave`` + Record engravings in the ``carve`` phase. If this option is not specified, + engravings are ignored. +``-f``, ``--format `` + Select the output format of the generated files. See the `Output formats`_ + section below for options. If not specified, the output format defaults to + "minimal", which will produce a small, fast ``.csv`` file. +``--nometa`` + `Meta blueprints ` let you apply all blueprints that can be + replayed at the same time (without unpausing the game) with a single + command. This usually reduces the number of `quickfort` commands you need to + run to rebuild your fort from about 6 to 2 or 3. If you would rather just + have the low-level blueprints, this flag will prevent meta blueprints from + being generated and any low-level blueprints from being + `hidden ` from the ``quickfort list`` command. +``-s``, ``--playback-start ,,`` + Specify the column and row offsets (relative to the upper-left corner of the + blueprint, which is ``1,1``) where the player should put the cursor when the + blueprint is played back with `quickfort`, in + `quickfort start marker ` format, for example: + ``10,10,central stairs``. If there is a space in the comment, you will need + to surround the parameter string in double quotes: + ``"-s10,10,central stairs"`` or ``--playback-start "10,10,central stairs"`` + or ``"--playback-start=10,10,central stairs"``. +``--smooth`` + Record all smooth tiles in the ``smooth`` phase. If this parameter is not + specified, only tiles that will later be carved into fortifications or + engraved will be smoothed. +``-t``, ``--splitby `` + Split blueprints into multiple files. See the `Splitting output into + multiple files`_ section below for details. If not specified, defaults to + "none", which will create a standard quickfort + `multi-blueprint ` file. + +Output formats +-------------- + +Here are the values that can be passed to the ``--format`` flag: + +``minimal`` + Creates ``.csv`` files with minimal file size that are fast to read and + write. This is the default. +``pretty`` + Makes the blueprints in the ``.csv`` files easier to read and edit with a + text editor by adding extra spacing and alignment markers. + +Splitting output into multiple files +------------------------------------ + +The ``--splitby`` flag can take any of the following values: + +``none`` + Writes all blueprints into a single file. This is the standard format for + quickfort fortress blueprint bundles and is the default. +``group`` + Creates one file per group of blueprints that can be played back at the same + time (without have to unpause the game and let dwarves fulfill jobs between + blueprint runs). +``phase`` + Creates a separate file for each phase. Implies ``--nometa`` since meta + blueprints can't combine blueprints that are in separate files. diff --git a/docs/plugins/building-hacks.rst b/docs/plugins/building-hacks.rst new file mode 100644 index 000000000..12f49fbbe --- /dev/null +++ b/docs/plugins/building-hacks.rst @@ -0,0 +1,9 @@ +building-hacks +============== + +.. dfhack-tool:: + :summary: Provides a Lua API for creating powered workshops. + :tags: fort gameplay buildings + :no-command: + +See `building-hacks-api` for more details. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst new file mode 100644 index 000000000..ef159443d --- /dev/null +++ b/docs/plugins/buildingplan.rst @@ -0,0 +1,92 @@ +buildingplan +============ + +.. dfhack-tool:: + :summary: Plan building construction before you have materials. + :tags: fort design buildings + +This plugin adds a planning mode for building placement. You can then place +furniture, constructions, and other buildings before the required materials are +available, and they will be created in a suspended state. Buildingplan will +periodically scan for appropriate items, and the jobs will be unsuspended when +the items are available. + +This is very useful when combined with manager work orders or `workflow` -- you +can set a constraint to always have one or two doors/beds/tables/chairs/etc. +available, and place as many as you like. Materials are used to build the +planned buildings as they are produced, with minimal space dedicated to +stockpiles. + +Usage +----- + +:: + + enable buildingplan + buildingplan set + buildingplan set true|false + +Running ``buildingplan set`` without parameters displays the current settings. + +.. _buildingplan-settings: + +Global settings +--------------- + +The buildingplan plugin has global settings that can be set from the UI +(:kbd:`G` from any building placement screen, for example: +:kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the +``buildingplan set`` command. The available settings are: + +``all_enabled`` (default: false) + Enable planning mode for all building types. +``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) + Allow blocks, boulders, logs, or bars to be matched for generic "building + material" items. +``quickfort_mode`` (default: false) + Enable compatibility mode for the legacy Python Quickfort (this setting is + not required for DFHack `quickfort`) + +The settings for ``blocks``, ``boulders``, ``logs``, and ``bars`` are saved with +your fort, so you only have to set them once and they will be persisted in your +save. + +If you normally embark with some blocks on hand for early workshops, you might +want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to +always configure buildingplan to just use blocks for buildings and +constructions:: + + on-new-fortress buildingplan set boulders false; buildingplan set logs false + +.. _buildingplan-filters: + +Item filtering +-------------- + +While placing a building, you can set filters for what materials you want the +building made out of, what quality you want the component items to be, and +whether you want the items to be decorated. + +If a building type takes more than one item to construct, use +:kbd:`Ctrl`:kbd:`Left` and :kbd:`Ctrl`:kbd:`Right` to select the item that you +want to set filters for. Any filters that you set will be used for all buildings +of the selected type placed from that point onward (until you set a new filter +or clear the current one). Buildings placed before the filters were changed will +keep the filter values that were set when the building was placed. + +For example, you can be sure that all your constructed walls are the same color +by setting a filter to accept only certain types of stone. + +Quickfort mode +-------------- + +If you use the external Python Quickfort to apply building blueprints instead of +the native DFHack `quickfort` script, you must enable Quickfort mode. This +temporarily enables buildingplan for all building types and adds an extra blank +screen after every building placement. This "dummy" screen is needed for Python +Quickfort to interact successfully with Dwarf Fortress. + +Note that Quickfort mode is only for compatibility with the legacy Python +Quickfort. The DFHack `quickfort` script does not need this Quickfort mode to be +enabled. The `quickfort` script will successfully integrate with buildingplan as +long as the buildingplan plugin itself is enabled. diff --git a/docs/plugins/burrows.rst b/docs/plugins/burrows.rst new file mode 100644 index 000000000..fb3876e97 --- /dev/null +++ b/docs/plugins/burrows.rst @@ -0,0 +1,54 @@ +burrows +======= + +.. dfhack-tool:: + :summary: Auto-expand burrows as you dig. + :tags: fort auto design productivity map units + :no-command: + +.. dfhack-command:: burrow + :summary: Quickly add units/tiles to burrows. + +When a wall inside a burrow with a name ending in ``+`` is dug out, the burrow +will be extended to newly-revealed adjacent walls. + +Usage +----- + +``burrow enable auto-grow`` + When a wall inside a burrow with a name ending in '+' is dug out, the burrow + will be extended to newly-revealed adjacent walls. This final '+' may be + omitted in burrow name args of other ``burrow`` commands. Note that digging + 1-wide corridors with the miner inside the burrow is SLOW. +``burrow disable auto-grow`` + Disables auto-grow processing. +``burrow clear-unit [ ...]`` + Remove all units from the named burrows. +``burrow clear-tiles [ ...]`` + Remove all tiles from the named burrows. +``burrow set-units target-burrow [ ...]`` + Clear all units from the target burrow, then add units from the named source + burrows. +``burrow add-units target-burrow [ ...]`` + Add units from the source burrows to the target. +``burrow remove-units target-burrow [ ...]`` + Remove units in source burrows from the target. +``burrow set-tiles target-burrow [ ...]`` + Clear target burrow tiles and add tiles from the names source burrows. +``burrow add-tiles target-burrow [ ...]`` + Add tiles from the source burrows to the target. +``burrow remove-tiles target-burrow [ ...]`` + Remove tiles in source burrows from the target. + +In place of a source burrow, you can use one of the following keywords: + +- ``ABOVE_GROUND`` +- ``SUBTERRANEAN`` +- ``INSIDE`` +- ``OUTSIDE`` +- ``LIGHT`` +- ``DARK`` +- ``HIDDEN`` +- ``REVEALED`` + +to add tiles with the given properties. diff --git a/docs/plugins/changeitem.rst b/docs/plugins/changeitem.rst new file mode 100644 index 000000000..f7418238a --- /dev/null +++ b/docs/plugins/changeitem.rst @@ -0,0 +1,44 @@ +changeitem +========== + +.. dfhack-tool:: + :summary: Change item material or base quality. + :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 +since some transformations work pretty well and may be desired you can override +this with ``force``. Note that forced changes can possibly result in items that +crafters and haulers refuse to touch. + +Usage +----- + +``changeitem info`` + Show details about the selected item. Does not change the item. You can use + this command to discover RAW ids for existing items. +``changeitem []`` + Change the item selected in the ``k`` list or inside a container/inventory. +``changeitem here []`` + Change all items at the cursor position. Requires in-game cursor. + +Examples +-------- + +``changeitem here m INORGANIC:GRANITE`` + Change material of all stone items under the cursor to granite. +``changeitem q 5`` + Change currently selected item to masterpiece quality. + +Options +------- + +``m``, ``material `` + Change material. Must be followed by valid material RAW id. +``s``, ``subtype `` + Change subtype. Must be followed by a valid subtype RAW id." +``q``, ``quality `` + Change base quality. Must be followed by number (0-5) with 0 being no quality + and 5 being masterpiece quality. +``force`` + Ignore subtypes and force the change to the new material. diff --git a/docs/plugins/changelayer.rst b/docs/plugins/changelayer.rst new file mode 100644 index 000000000..f986a1e3d --- /dev/null +++ b/docs/plugins/changelayer.rst @@ -0,0 +1,76 @@ +changelayer +=========== + +.. dfhack-tool:: + :summary: Change the material of an entire geology layer. + :tags: fort armok map + +Note that one layer can stretch across many z-levels, and changes to the geology +layer will affect all surrounding regions, not just your embark! Mineral veins +and gem clusters will not be affected. Use `changevein` if you want to modify +those. + +tl;dr: You will end up with changing large areas in one go, especially if you +use it in lower z levels. Use this command with care! + +Usage +----- + +:: + + changelayer [] + +When run without options, ``changelayer`` will: + +- only affect the geology layer at the current cursor position +- only affect the biome that covers the current cursor position +- not allow changing stone to soil and vice versa + +You can use the `probe` command on various tiles around your map to find valid +material RAW ids and to get an idea how layers and biomes are distributed. + +Examples +-------- + +``changelayer GRANITE`` + Convert the layer at the cursor position into granite. +``changelayer SILTY_CLAY force`` + Convert the layer at the cursor position into clay, even if it's stone. +``changelayer MARBLE all_biomes all_layers`` + Convert all layers of all biomes which are not soil into marble. + +.. note:: + + * If you use changelayer and nothing happens, try to pause/unpause the game + for a while and move the cursor to another tile. Then try again. If that + doesn't help, then try to temporarily change some other layer, undo your + changes, and try again for the layer you want to change. Saving and + reloading your map also sometimes helps. + * You should be fine if you only change single layers without the use + of 'force'. Still, it's advisable to save your game before messing with + the map. + * When you force changelayer to convert soil to stone, you might see some + weird stuff (flashing tiles, tiles changed all over place etc). Try + reverting the changes manually or even better use an older savegame. You + did save your game, right? + +Options +------- + +``all_biomes`` + Change the corresponding geology layer for all biomes on your map. Be aware + that the same geology layer can AND WILL be on different z-levels for + different biomes. +``all_layers`` + Change all geology layers on your map (only for the selected biome unless + ``all_biomes`` is also specified). Candy mountain, anyone? Will make your map + quite boring, but tidy. +``force`` + Allow changing stone to soil and vice versa. **THIS CAN HAVE WEIRD EFFECTS, + USE WITH CARE AND SAVE FIRST**. Note that soil will not be magically replaced + with stone. You will, however, get a stone floor after digging, so it will + allow the floor to be engraved. Similarly, stone will not be magically + replaced with soil, but you will get a soil floor after digging, so it could + be helpful for creating farm plots on maps with no soil. +``verbose`` + Output details about what is being changed. diff --git a/docs/plugins/changevein.rst b/docs/plugins/changevein.rst new file mode 100644 index 000000000..4e4512bb1 --- /dev/null +++ b/docs/plugins/changevein.rst @@ -0,0 +1,26 @@ +changevein +========== + +.. dfhack-tool:: + :summary: Change the material of a mineral inclusion. + :tags: fort armok map + +You can change a vein to any inorganic material RAW id. Note that this command +only affects tiles within the current 16x16 block - for large veins and +clusters, you will need to use this command multiple times. + +You can use the `probe` command to discover the material RAW ids for existing +veins that you want to duplicate. + +Usage +----- + +:: + + changevein + +Example +------- + +``changevein NATIVE_PLATINUM`` + Convert vein at cursor position into platinum ore. diff --git a/docs/plugins/cleanconst.rst b/docs/plugins/cleanconst.rst new file mode 100644 index 000000000..12b1db0e5 --- /dev/null +++ b/docs/plugins/cleanconst.rst @@ -0,0 +1,18 @@ +cleanconst +========== + +.. dfhack-tool:: + :summary: Cleans up construction materials. + :tags: fort fps buildings + +This tool alters all constructions on the map so that they spawn their building +component when they are disassembled, allowing their actual build items to be +safely deleted. This can improve FPS when you have many constructions on the +map. + +Usage +----- + +:: + + cleanconst diff --git a/docs/plugins/cleaners.rst b/docs/plugins/cleaners.rst new file mode 100644 index 000000000..14a25d62f --- /dev/null +++ b/docs/plugins/cleaners.rst @@ -0,0 +1,57 @@ +.. _clean: +.. _spotclean: + +cleaners +======== + +.. dfhack-tool:: + :summary: Provides commands for cleaning spatter from the map. + :tags: adventure fort armok fps items map units + :no-command: + +.. dfhack-command:: clean + :summary: Removes contaminants. + +.. dfhack-command:: spotclean + :summary: Remove all contaminants from the tile under the cursor. + +This plugin provides commands that clean the splatter that get scattered all +over the map and that clings to your items and units. In an old fortress, +cleaning with this tool can significantly reduce FPS lag! It can also spoil your +!!FUN!!, so think before you use it. + +Usage +----- + +:: + + clean all|map|items|units|plants [] + spotclean + +By default, cleaning the map leaves mud and snow alone. Note that cleaning units +includes hostiles, and that cleaning items removes poisons from weapons. + +``spotclean`` works like ``clean map snow mud``, removing all contaminants from +the tile under the cursor. This is ideal if you just want to clean a specific +tile but don't want the `clean` command to remove all the glorious blood from +your entranceway. + +Examples +-------- + +``clean all`` + Clean everything that can be cleaned (except mud and snow). +``clean all mud item snow`` + Removes all spatter, including mud, leaves, and snow from map tiles. + +Options +------- + +When cleaning the map, you can specify extra options for extra cleaning: + +``mud`` + Also remove mud. +``item`` + Also remove item spatter, like fallen leaves and flowers. +``snow`` + Also remove snow coverings. diff --git a/docs/plugins/cleanowned.rst b/docs/plugins/cleanowned.rst new file mode 100644 index 000000000..57094d14b --- /dev/null +++ b/docs/plugins/cleanowned.rst @@ -0,0 +1,41 @@ +cleanowned +========== + +.. dfhack-tool:: + :summary: Confiscates and dumps garbage owned by dwarves. + :tags: fort productivity items + +This tool gets dwarves to give up ownership of scattered items and items with +heavy wear and then marks those items for dumping. Now you can finally get your +dwarves to give up their rotten food and tattered loincloths and go get new +ones! + +Usage +----- + +:: + + cleanowned [] [dryrun] + +When run without parameters, ``cleanowned`` will confiscate and dump rotten +items and owned food that is left behind on the floor. Specify the ``dryrun`` +parameter to just print out what would be done, but don't actually confiscate +anything. + +You can confiscate additional types of items by adding them to the commandline: + +``scattered`` + Confiscate/dump all items scattered on the floor. +``x`` + Confiscate/dump items with wear level 'x' (lightly worn) and more. +``X`` + Confiscate/dump items with wear level 'X' (heavily worn) and more. + +Or you can confiscate all owned items by specifying ``all``. + +Example +------- + +``cleanowned scattered X`` + Confiscate and dump rotten and dropped food, garbage on the floors, and any + worn items with 'X' damage and above. diff --git a/docs/plugins/command-prompt.rst b/docs/plugins/command-prompt.rst new file mode 100644 index 000000000..c4b464a80 --- /dev/null +++ b/docs/plugins/command-prompt.rst @@ -0,0 +1,22 @@ +command-prompt +============== + +.. dfhack-tool:: + :summary: An in-game DFHack terminal where you can run other commands. + :tags: dfhack + +Usage +----- + +:: + + command-prompt [entry] + +If called with parameters, it starts with that text in the command edit area. +This is most useful for developers, who can set a keybinding to open a language +interpreter for lua or Ruby by starting with the `:lua ` or `:rb ` +portions of the command already filled in. + +Also see `gui/quickcmd` for a different take on running commands from the UI. + +.. image:: ../images/command-prompt.png diff --git a/docs/plugins/confirm.rst b/docs/plugins/confirm.rst new file mode 100644 index 000000000..6679cf4e3 --- /dev/null +++ b/docs/plugins/confirm.rst @@ -0,0 +1,20 @@ +confirm +======= + +.. dfhack-tool:: + :summary: Adds confirmation dialogs for destructive actions. + :tags: fort interface + +Now you can get the chance to avoid seizing goods from traders or deleting a +hauling route in case you hit the key accidentally. + +Usage +----- + +``enable confirm``, ``confirm enable all`` + Enable all confirmation options. Replace with ``disable`` to disable all. +``confirm enable option1 [option2...]`` + Enable (or ``disable``) specific confirmation dialogs. + +When run without parameters, ``confirm`` will report which confirmation dialogs +are currently enabled. diff --git a/docs/plugins/createitem.rst b/docs/plugins/createitem.rst new file mode 100644 index 000000000..b29e41521 --- /dev/null +++ b/docs/plugins/createitem.rst @@ -0,0 +1,54 @@ +createitem +========== + +.. dfhack-tool:: + :summary: Create arbitrary 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 +the feet of the selected unit. + +Specify the item and material information as you would indicate them in custom +reaction raws, with the following differences: + +* Separate the item and material with a space rather than a colon +* If the item has no subtype, the ``:NONE`` can be omitted +* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or + ``EGG``, then specify a ``CREATURE:CASTE`` pair instead of a material token. +* If the item is a ``PLANT_GROWTH``, specify a ``PLANT_ID:GROWTH_ID`` pair + instead of a material token. + +Corpses, body parts, and prepared meals cannot be created using this tool. + +Usage +----- + +``createitem []`` + Create copies (default is 1) of the specified item made out of the + specified material. +``createitem inspect`` + Obtain the item and material tokens of an existing item. Its output can be + used directly as arguments to ``createitem`` to create new matching items + (as long as the item type is supported). +``createitem floor|item|building`` + Subsequently created items will be placed on the floor beneath the selected + unit's, inside the selected item, or as part of the selected building. + +.. note:: + + ``createitem building`` is good for loading traps, but if you use it with + workshops, you will have to deconstruct the workshop to access the item. + +Examples +-------- + +``createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2`` + Create 2 pairs of steel gauntlets (that is, 2 left gauntlets and 2 right + gauntlets). +``createitem WOOD PLANT_MAT:TOWER_CAP:WOOD 100`` + Create 100 tower-cap logs. +``createitem PLANT_GROWTH BILBERRY:FRUIT`` + Create a single bilberry. + +For more examples, :wiki:`the wiki `. diff --git a/docs/plugins/cursecheck.rst b/docs/plugins/cursecheck.rst new file mode 100644 index 000000000..94012919b --- /dev/null +++ b/docs/plugins/cursecheck.rst @@ -0,0 +1,61 @@ +cursecheck +========== + +.. dfhack-tool:: + :summary: Check for cursed creatures. + :tags: fort armok inspection units + +This command checks a single map tile (or the whole map/world) for cursed +creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). + +With an active in-game cursor, only the selected tile will be checked. Without a +cursor, the whole map will be checked. + +By default, you will just see the count of cursed creatures in case you just +want to find out if you have any of them running around in your fort. Dead and +passive creatures (ghosts who were put to rest, killed vampires, etc.) are +ignored. Undead skeletons, corpses, bodyparts and the like are all thrown into +the curse category "zombie". Anonymous zombies and resurrected body parts will +show as "unnamed creature". + +Usage +----- + +:: + + cursecheck [] + +Examples +-------- + +- ``cursecheck`` + Display a count of cursed creatures on the map (or under the cursor). +- ``cursecheck detail all`` + Give detailed info about all cursed creatures including deceased ones. +- ``cursecheck nick`` + Give a nickname to all living/active cursed creatures. + +.. note:: + + If you do a full search (with the option "all") former ghosts will show up + with the cursetype "unknown" because their ghostly flag is not set. + + If you see any living/active creatures with a cursetype of "unknown", then + it is most likely a new type of curse introduced by a mod. + +Options +------- + +``detail`` + Print full name, date of birth, date of curse, and some status info (some + vampires might use fake identities in-game, though). +``nick`` + Set the type of curse as nickname (does not always show up in-game; some + vamps don't like nicknames). +``ids`` + Print the creature and race IDs. +``all`` + Include dead and passive cursed creatures (this can result in quite a long + list after having !!FUN!! with necromancers). +``verbose`` + Print all curse tags (if you really want to know it all). diff --git a/docs/plugins/cxxrandom.rst b/docs/plugins/cxxrandom.rst new file mode 100644 index 000000000..19788e4a4 --- /dev/null +++ b/docs/plugins/cxxrandom.rst @@ -0,0 +1,9 @@ +cxxrandom +========= + +.. dfhack-tool:: + :summary: Provides a Lua API for random distributions. + :tags: dev + :no-command: + +See `cxxrandom-api` for details. diff --git a/docs/plugins/debug.rst b/docs/plugins/debug.rst new file mode 100644 index 000000000..0c5a587fb --- /dev/null +++ b/docs/plugins/debug.rst @@ -0,0 +1,77 @@ +debug +===== + +.. dfhack-tool:: + :summary: Provides commands for controlling debug log verbosity. + :tags: dev + :no-command: + +.. dfhack-command:: debugfilter + :summary: Configure verbosity of DFHack debug output. + +Debug output is grouped by plugin name, category name, and verbosity level. + +The verbosity levels are: + +- ``Trace`` + Possibly very noisy messages which can be printed many times per second. +- ``Debug`` + Messages that happen often but they should happen only a couple of times per + second. +- ``Info`` + Important state changes that happen rarely during normal execution. +- ``Warning`` + Enabled by default. Shows warnings about unexpected events which code + managed to handle correctly. +- ``Error`` + Enabled by default. Shows errors which code can't handle without user + intervention. + +The runtime message printing is controlled using filters. Filters set the +visible messages of all matching categories. Matching uses regular expression +syntax, which allows listing multiple alternative matches or partial name +matches. This syntax is a C++ version of the ECMA-262 grammar (Javascript +regular expressions). Details of differences can be found at +https://en.cppreference.com/w/cpp/regex/ecmascript + +Persistent filters are stored in ``dfhack-config/runtime-debug.json``. Oldest +filters are applied first. That means a newer filter can override the older +printing level selection. + +Usage +----- + +``debugfilter category [] []`` + List available debug plugin and category names. If filters aren't given + then all plugins/categories are matched. This command is a good way to test + regex parameters before you pass them to ``set``. +``debugfilter filter []`` + List active and passive debug print level changes. The optional ``id`` + parameter is the id listed as the first column in the filter list. If ``id`` + is given, then the command shows extended information for the given filter + only. +``debugfilter set [] [] []`` + Create a new debug filter to set category verbosity levels. This filter + will not be saved when the DF process exists or the plugin is unloaded. +``debugfilter set persistent [] [] []`` + Store the filter in the configuration file to until ``unset`` is used to + remove it. +``debugfilter unset [ ...]`` + Delete a space separated list of filters. +``debugfilter disable [ ...]`` + Disable a space separated list of filters but keep it in the filter list. +``debugfilter enable [ ...]`` + Enable a space separated list of filters. +``debugfilter header [enable] | [disable] [ ...]`` + Control which header metadata is shown along with each log message. Run it + without parameters to see the list of configurable elements. Include an + ``enable`` or ``disable`` keyword to change whether specific elements are + shown. + +Example +------- + +``debugfilter set Warning core script`` + Hide script execution log messages (e.g. "Loading script: + dfhack-config/dfhack.init"), which are normally output at Info verbosity + in the "core" plugin with the "script" category. diff --git a/docs/plugins/deramp.rst b/docs/plugins/deramp.rst new file mode 100644 index 000000000..fdc0f7619 --- /dev/null +++ b/docs/plugins/deramp.rst @@ -0,0 +1,15 @@ +deramp +====== + +.. dfhack-tool:: + :summary: Removes all ramps designated for removal from the map. + :tags: fort armok map + +It also removes any "floating" down ramps that can remain after a cave-in. + +Usage +----- + +:: + + deramp diff --git a/docs/plugins/dig-now.rst b/docs/plugins/dig-now.rst new file mode 100644 index 000000000..43ed6ce8f --- /dev/null +++ b/docs/plugins/dig-now.rst @@ -0,0 +1,67 @@ +dig-now +======= + +.. dfhack-tool:: + :summary: Instantly complete dig designations. + :tags: fort armok map + +This tool will magically complete non-marker dig designations, modifying tile +shapes and creating boulders, ores, and gems as if a miner were doing the mining +or engraving. By default, the entire map is processed and boulder generation +follows standard game rules, but the behavior is configurable. + +Note that no units will get mining or engraving experience for the dug/engraved +tiles. + +Trees and roots are not currently handled by this plugin and will be skipped. +Requests for engravings are also skipped since they would depend on the skill +and creative choices of individual engravers. Other types of engraving (i.e. +smoothing and track carving) are handled. + +Usage +----- + +:: + + dig-now [ []] [] + +Where the optional ```` pair can be used to specify the coordinate bounds +within which ``dig-now`` will operate. If they are not specified, ``dig-now`` +will scan the entire map. If only one ```` is specified, only the tile at +that coordinate is processed. + +Any ```` parameters can either be an ``,,`` triple (e.g. +``35,12,150``) or the string ``here``, which means the position of the active +game cursor should be used. You can use the `position` command to get the +current cursor position if you need it. + +Examples +-------- + +``dig-now`` + Dig designated tiles according to standard game rules. +``dig-now --clean`` + Dig all designated tiles, but don't generate any boulders, ores, or gems. +``dig-now --dump here`` + Dig tiles and teleport all generated boulders, ores, and gems to the tile + under the game cursor. + +Options +------- + +``-c``, ``--clean`` + Don't generate any boulders, ores, or gems. Equivalent to + ``--percentages 0,0,0,0``. +``-d``, ``--dump `` + Dump any generated items at the specified coordinates. If the tile at those + coordinates is open space or is a wall, items will be generated on the + closest walkable tile below. +``-e``, ``--everywhere`` + Generate a boulder, ore, or gem for every tile that can produce one. + Equivalent to ``--percentages 100,100,100,100``. +``-p``, ``--percentages ,,,`` + Set item generation percentages for each of the tile categories. The + ``vein`` category includes both the large oval clusters and the long stringy + mineral veins. Default is ``25,33,100,100``. +``-z``, ``--cur-zlevel`` + Restricts the bounds to the currently visible z-level. diff --git a/docs/plugins/dig.rst b/docs/plugins/dig.rst new file mode 100644 index 000000000..a10db97df --- /dev/null +++ b/docs/plugins/dig.rst @@ -0,0 +1,181 @@ +.. _digv: +.. _digtype: + +dig +=== + +.. dfhack-tool:: + :summary: Provides commands for designating tiles for digging. + :tags: fort design productivity map + :no-command: + +.. dfhack-command:: digv + :summary: Designate all of the selected vein for digging. + +.. dfhack-command:: digvx + :summary: Dig a vein across z-levels, digging stairs as needed. + +.. dfhack-command:: digl + :summary: Dig all of the selected layer stone. + +.. dfhack-command:: diglx + :summary: Dig layer stone across z-levels, digging stairs as needed. + +.. dfhack-command:: digcircle + :summary: Designate circles. + +.. dfhack-command:: digtype + :summary: Designate all vein tiles of the same type as the selected tile. + +.. dfhack-command:: digexp + :summary: Designate dig patterns for exploratory mining. + +This plugin provides commands to make complicated dig patterns easy. + +Usage +----- + +``digv [x] [-p]`` + Designate all of the selected vein for digging. +``digvx [-p]`` + Dig a vein across z-levels, digging stairs as needed. This is an alias for + ``digv x``. +``digl [x] [undo] [-p]`` + Dig all of the selected layer stone. If ``undo`` is specified, removes the + designation instead (for if you accidentally set 50 levels at once). +``diglx [-p]`` + Dig layer stone across z-levels, digging stairs as needed. This is an alias + for ``digl x``. +``digcircle [] [] [] [] [-p]`` + Designate circles. The diameter is the number of tiles across the center of + the circle that you want to dig. See the `digcircle`_ section below for + options. +``digtype [] [-p] [-z]`` + Designate all vein tiles of the same type as the selected tile. See the + `digtype`_ section below for options. +``digexp [] [] [-p]`` + Designate dig patterns for exploratory mining. See the `digexp`_ section + below for options. + +All commands support specifying the priority of the dig designations with +``-p``, where the number is from 1 to 7. If a priority is not specified, +the priority selected in-game is used as the default. + +Examples +-------- + +``digcircle filled 3 -p2`` + Dig a filled circle with a diameter of 3 tiles at dig priority 2. +``digcircle`` + Do it again (previous parameters are reused). +``expdig diag5 hidden`` + Designate the diagonal 5 pattern over all hidden tiles on the current + z-level. +``expdig ladder designated`` + Take existing designations on the current z-level and replace them with the + ladder pattern. +``expdig`` + Do it again (previous parameters are reused). + +digcircle +--------- + +The ``digcircle`` command can accept up to one option of each type below. + +Solidity options: + +``hollow`` + Designates hollow circles (default). +``filled`` + Designates filled circles. + +Action options: + +``set`` + Set designation (default). +``unset`` + Unset current designation. +``invert`` + Invert designations already present. + +Designation options: + +``dig`` + Normal digging designation (default). +``ramp`` + Dig ramps. +``ustair`` + Dig up staircases. +``dstair`` + Dig down staircases. +``xstair`` + Dig up/down staircases. +``chan`` + Dig channels. + +After you have set the options, the command called with no options repeats with +the last selected parameters. + +digtype +------- + +For every tile on the map of the same vein type as the selected tile, this +command designates it to have the same designation as the selected tile. If the +selected tile has no designation, they will be dig designated. + +If an argument is given, the designation of the selected tile is ignored, and +all appropriate tiles are set to the specified designation. + +Designation options: + +``dig`` + Normal digging designation. +``channel`` + Dig channels. +``ramp`` + Dig ramps. +``updown`` + Dig up/down staircases. +``up`` + Dig up staircases. +``down`` + Dig down staircases. +``clear`` + Clear any designations. + +You can also pass a ``-z`` option, which restricts designations to the current +z-level and down. This is useful when you don't want to designate tiles on the +same z-levels as your carefully dug fort above. + +digexp +------ + +This command is for :wiki:`exploratory mining `. + +There are two variables that can be set: pattern and filter. + +Patterns: + +``diag5`` + Diagonals separated by 5 tiles. +``diag5r`` + The diag5 pattern rotated 90 degrees. +``ladder`` + A 'ladder' pattern. +``ladderr`` + The ladder pattern rotated 90 degrees. +``cross`` + A cross, exactly in the middle of the map. +``clear`` + Just remove all dig designations. + +Filters: + +``hidden`` + Designate only hidden tiles of z-level (default) +``all`` + Designate the whole z-level. +``designated`` + Take current designation and apply the selected pattern to it. + +After you have a pattern set, you can use ``expdig`` to apply it again. diff --git a/docs/plugins/digFlood.rst b/docs/plugins/digFlood.rst new file mode 100644 index 000000000..57d4584a8 --- /dev/null +++ b/docs/plugins/digFlood.rst @@ -0,0 +1,42 @@ +digFlood +======== + +.. dfhack-tool:: + :summary: Digs out veins as they are discovered. + :tags: 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 +mining jobs. Note that it will *only* dig out tiles that are adjacent to a +just-finished dig job, so if you want to autodig a vein that has already been +discovered, you may need to manually designate one tile of the tile for digging +to get started. + +Usage +----- + +``enable digflood`` + Enable the plugin. +``digflood 1 [ ...]`` + Start monitoring for the specified vein types. +``digFlood 0 [ ...] 1`` + Stop monitoring for the specified vein types. Note the required ``1`` at the + end. +``digFlood CLEAR`` + Remove all inorganics from monitoring. +``digFlood digAll1`` + Ignore the monitor list and dig any vein. +``digFlood digAll0`` + Disable digAll mode. + +You can get the list of valid vein types with this command:: + + lua "for i,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(i, mat.id) end end" + +Examples +-------- + +``digFlood 1 MICROCLINE COAL_BITUMINOUS`` + Automatically dig microcline and bituminous coal veins. +``digFlood 0 MICROCLINE 1`` + Stop automatically digging microcline. diff --git a/docs/plugins/diggingInvaders.rst b/docs/plugins/diggingInvaders.rst new file mode 100644 index 000000000..ace04e9fc --- /dev/null +++ b/docs/plugins/diggingInvaders.rst @@ -0,0 +1,60 @@ +diggingInvaders +=============== + +.. dfhack-tool:: + :summary: Invaders dig and destroy to get to your dwarves. + :tags: fort gameplay military units + +Usage +----- + +``enable diggingInvaders`` + Enable the plugin. +``diggingInvaders add `` + Register the specified race as a digging invader. +``diggingInvaders remove `` + Unregisters the specified race as a digging invader. +``diggingInvaders now`` + Makes invaders try to dig now (if the plugin is enabled). +``diggingInvaders clear`` + Clears the registry of digging invader races. +``diggingInvaders edgesPerTick `` + Makes the pathfinding algorithm work on at most n edges per tick. Set to 0 + or lower to make it unlimited. +``diggingInvaders setCost `` + Set the pathing cost per tile for a particular action. This determines what + invaders consider to be the shortest path to their target. +``diggingInvaders setDelay `` + Set the time cost (in ticks) for performing a particular action. This + determines how long it takes for invaders to get to their target. + +Note that the race is case-sensitive. You can get a list of races for your world +with this command:: + + devel/query --table df.global.world.raws.creatures.all --search creature_id --maxdepth 1 --maxlength 5000 + +but in general, the race is what you'd expect, just capitalized (e.g. ``GOBLIN`` +or ``ELF``). + +Actions: + +``walk`` + Default cost: 1, default delay: 0. This is the base cost for the pathing + algorithm. +``destroyBuilding`` + Default cost: 2, default delay: 1,000, +``dig`` + Default cost: 10,000, default delay: 1,000. This is for digging soil or + natural stone. +``destroyRoughConstruction`` + Default cost: 1,000, default delay: 1,000. This is for destroying + constructions made from boulders. +``destroySmoothConstruction`` + Default cost: 100, default delay: 100. This is for destroying constructions + made from blocks or bars. + +Example +------- + +``diggingInvaders add GOBLIN`` + Registers members of the GOBLIN race as a digging invader. diff --git a/docs/plugins/dwarfmonitor.rst b/docs/plugins/dwarfmonitor.rst new file mode 100644 index 000000000..79472ae58 --- /dev/null +++ b/docs/plugins/dwarfmonitor.rst @@ -0,0 +1,94 @@ +dwarfmonitor +============ + +.. dfhack-tool:: + :summary: Measure fort happiness and efficiency. + :tags: fort inspection jobs units + +It can also show heads-up display widgets with live fort statistics. + +Usage +----- + +``enable dwarfmonitor`` + Enable the plugin. +``dwarfmonitor enable `` + Start tracking a specific facet of fortress life. The ``mode`` can be + "work", "misery", "date", "weather", or "all". This will show the + corresponding on-screen widgets, if applicable. +``dwarfmonitor disable `` + Stop monitoring ``mode`` and disable corresponding widgets. +``dwarfmonitor stats`` + Show statistics summary. +``dwarfmonitor prefs`` + Show summary of dwarf preferences. +``dwarfmonitor reload`` + Reload the widget configuration file (``dfhack-config/dwarfmonitor.json``). + +Widget configuration +-------------------- + +The following types of widgets (defined in +:file:`hack/lua/plugins/dwarfmonitor.lua`) can be displayed on the main fortress +mode screen: + +``misery`` + Show overall happiness levels of all dwarves. +``date`` + Show the in-game date. +``weather`` + Show current weather (e.g. rain/snow). +``cursor`` + Show the current mouse cursor position. + +The file :file:`dfhack-config/dwarfmonitor.json` can be edited to control the +positions and settings of all widgets. This file should contain a JSON object +with the key ``widgets`` containing an array of objects: + +.. code-block:: lua + + { + "widgets": [ + { + "type": "widget type (weather, misery, etc.)", + "x": X coordinate, + "y": Y coordinate + <...additional options...> + } + ] + } + +X and Y coordinates begin at zero (in the upper left corner of the screen). +Negative coordinates will be treated as distances from the lower right corner, +beginning at 1 - e.g. an x coordinate of 0 is the leftmost column, while an x +coordinate of -1 is the rightmost column. + +By default, the x and y coordinates given correspond to the leftmost tile of +the widget. Including an ``anchor`` option set to ``right`` will cause the +rightmost tile of the widget to be located at this position instead. + +Some widgets support additional options: + +* ``date`` widget: + + * ``format``: specifies the format of the date. The following characters + are replaced (all others, such as punctuation, are not modified) + + * ``Y`` or ``y``: The current year + * ``M``: The current month, zero-padded if necessary + * ``m``: The current month, *not* zero-padded + * ``D``: The current day, zero-padded if necessary + * ``d``: The current day, *not* zero-padded + + The default date format is ``Y-M-D``, per the ISO8601_ standard. + + .. _ISO8601: https://en.wikipedia.org/wiki/ISO_8601 + +* ``cursor`` widget: + + * ``format``: Specifies the format. ``X``, ``x``, ``Y``, and ``y`` are + replaced with the corresponding cursor coordinates, while all other + characters are unmodified. + * ``show_invalid``: If set to ``true``, the mouse coordinates will both be + displayed as ``-1`` when the cursor is outside of the DF window; otherwise, + nothing will be displayed. diff --git a/docs/plugins/dwarfvet.rst b/docs/plugins/dwarfvet.rst new file mode 100644 index 000000000..fa165250d --- /dev/null +++ b/docs/plugins/dwarfvet.rst @@ -0,0 +1,23 @@ +dwarfvet +======== + +.. dfhack-tool:: + :summary: Allows animals to be treated at animal hospitals. + :tags: 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 +a hospital that is also an animal training zone. Dwarfs with the Animal +Caretaker labor enabled will come to the hospital to treat animals. Normal +medical skills are used (and trained), but no experience is given to the Animal +Caretaker skill itself. + +Usage +----- + +``enable dwarfvet`` + Enables the plugin. +``dwarfvet report`` + Reports all zones that the game considers animal hospitals. +``dwarfvet report-usage`` + Reports on animals currently being treated. diff --git a/docs/plugins/embark-assistant.rst b/docs/plugins/embark-assistant.rst new file mode 100644 index 000000000..c7a3f778e --- /dev/null +++ b/docs/plugins/embark-assistant.rst @@ -0,0 +1,26 @@ +embark-assistant +================ + +.. dfhack-tool:: + :summary: Embark site selection support. + :tags: 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 +normally undisplayed sites in the current embark region. You will also have +access to a site selection tool with far more options than DF's vanilla search +tool. + +If you enable the plugin, you'll also be able to invoke ``embark-assistant`` +with the :kbd:`A` key on the pre-embark screen. + +Usage +----- + +:: + + enable embark-assistant + embark-assistant + +Note the site selection tool requires a display height of at least 46 lines to +display properly. diff --git a/docs/plugins/embark-tools.rst b/docs/plugins/embark-tools.rst new file mode 100644 index 000000000..781d66e90 --- /dev/null +++ b/docs/plugins/embark-tools.rst @@ -0,0 +1,34 @@ +embark-tools +============ + +.. dfhack-tool:: + :summary: Extend the embark screen functionality. + :tags: embark fort interface + +Usage +----- + +:: + + enable embark-tools + embark-tools enable|disable all + embark-tools enable|disable [ ...] + +Available tools are: + +``anywhere`` + Allows embarking anywhere (including sites, mountain-only biomes, and + oceans). Use with caution. +``mouse`` + Implements mouse controls (currently in the local embark region only). +``sand`` + Displays an indicator when sand is present in the currently-selected area, + similar to the default clay/stone indicators. +``sticky`` + Maintains the selected local area while navigating the world map. + +Example +------- + +``embark-tools enable all`` + Enable all embark screen extensions. diff --git a/docs/plugins/eventful.rst b/docs/plugins/eventful.rst new file mode 100644 index 000000000..e89480413 --- /dev/null +++ b/docs/plugins/eventful.rst @@ -0,0 +1,9 @@ +eventful +======== + +.. dfhack-tool:: + :summary: Provides a Lua API for reacting to in-game events. + :tags: dev gameplay + :no-command: + +See `eventful-api` for details. diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst new file mode 100644 index 000000000..fb0524c3b --- /dev/null +++ b/docs/plugins/fastdwarf.rst @@ -0,0 +1,38 @@ +fastdwarf +========= + +.. dfhack-tool:: + :summary: Dwarves teleport and/or finish jobs instantly. + :tags: fort armok units + +Usage +----- + +:: + + enable fastdwarf + fastdwarf [] + +Examples +-------- + +``fastdwarf 1`` + Make all your dwarves move and work at maximum speed. +``fastdwarf 1 1`` + In addition to working at maximum speed, dwarves also teleport to their + destinations. + +Options +------- + +Speed modes: + +:0: Dwarves move and work at normal rates. +:1: Dwarves move and work at maximum speed. +:2: ALL units move (and work) at maximum speed, including creatures and + hostiles. + +Tele modes: + +:0: No teleportation. +:1: Dwarves teleport to their destinations. diff --git a/docs/plugins/filltraffic.rst b/docs/plugins/filltraffic.rst new file mode 100644 index 000000000..beb57dccd --- /dev/null +++ b/docs/plugins/filltraffic.rst @@ -0,0 +1,53 @@ +.. _restrictice: +.. _restrictliquids: + +filltraffic +=========== + +.. dfhack-tool:: + :summary: Set traffic designations using flood-fill starting at the cursor. + :tags: fort design productivity map + +.. dfhack-command:: alltraffic + :summary: Set traffic designations for every single tile of the map. + +.. dfhack-command:: restrictice + :summary: Restrict traffic on all tiles on top of visible ice. + +.. dfhack-command:: restrictliquids + :summary: Restrict traffic on all visible tiles with liquid. + +Usage +----- + +:: + + filltraffic [] + alltraffic + restrictice + restrictliquids + +For ``filltraffic``, flood filling stops at walls and doors. + +Examples +-------- + +``filltraffic H`` + When used in a room with doors, it will set traffic to HIGH in just that + room. + +Options +------- + +Traffic designations: + +:H: High Traffic. +:N: Normal Traffic. +:L: Low Traffic. +:R: Restricted Traffic. + +Filltraffic extra options: + +:X: Fill across z-levels. +:B: Include buildings and stockpiles. +:P: Include empty space. diff --git a/docs/plugins/fix-unit-occupancy.rst b/docs/plugins/fix-unit-occupancy.rst new file mode 100644 index 000000000..0559d4a2f --- /dev/null +++ b/docs/plugins/fix-unit-occupancy.rst @@ -0,0 +1,41 @@ +fix-unit-occupancy +================== + +.. dfhack-tool:: + :summary: Fix phantom unit occupancy issues. + :tags: fort bugfix map + +If you see "unit blocking tile" messages that you can't account for +(:bug:`3499`), this tool can help. + +Usage +----- + +:: + + enable fix-unit-occupancy + fix-unit-occupancy [here] [-n] + fix-unit-occupancy interval + +When run without arguments (or with just the ``here`` or ``-n`` parameters), +the fix just runs once. You can also have it run periodically by enabling the +plugin. + +Examples +-------- + +``fix-unit-occupancy`` + Run once and fix all occupancy issues on the map. +``fix-unit-occupancy -n`` + Report on, but do not fix, all occupancy issues on the map. + +Options +------- + +``here`` + Only operate on the tile at the cursor. +``-n`` + Report issues, but do not write any changes to the map. +``interval `` + Set how often the plugin will check for and fix issues when it is enabled. + The default is 1200 ticks, or 1 game day. diff --git a/docs/plugins/fixveins.rst b/docs/plugins/fixveins.rst new file mode 100644 index 000000000..616f84fae --- /dev/null +++ b/docs/plugins/fixveins.rst @@ -0,0 +1,16 @@ +fixveins +======== + +.. dfhack-tool:: + :summary: Restore missing mineral inclusions. + :tags: fort bugfix map + +This tool can also remove invalid references to mineral inclusions if you broke +your embark with tools like `tiletypes`. + +Usage +----- + +:: + + fixveins diff --git a/docs/plugins/flows.rst b/docs/plugins/flows.rst new file mode 100644 index 000000000..bbb2c6661 --- /dev/null +++ b/docs/plugins/flows.rst @@ -0,0 +1,16 @@ +flows +===== + +.. dfhack-tool:: + :summary: Counts map blocks with flowing liquids. + :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. + +Usage +----- + +:: + + flows diff --git a/docs/plugins/follow.rst b/docs/plugins/follow.rst new file mode 100644 index 000000000..0bf1e4d0f --- /dev/null +++ b/docs/plugins/follow.rst @@ -0,0 +1,17 @@ +follow +====== + +.. dfhack-tool:: + :summary: Make the screen follow the selected unit. + :tags: 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 +moving the cursor manually. + +Usage +----- + +:: + + follow diff --git a/docs/plugins/forceequip.rst b/docs/plugins/forceequip.rst new file mode 100644 index 000000000..26ab378ee --- /dev/null +++ b/docs/plugins/forceequip.rst @@ -0,0 +1,104 @@ +forceequip +========== + +.. dfhack-tool:: + :summary: Move items into a unit's inventory. + :tags: 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 +(such as crowns) to any unit. Make sure the unit you want to equip is standing +on the target items, which must be on the ground and be unforbidden. If multiple +units are standing on the same tile, the first one will be equipped. + +The most reliable way to set up the environment for this command is to pile +target items on a tile of floor with a garbage dump activity zone or the +`autodump` command, then walk/pasture a unit (or use `gui/teleport`) on top of +the items. Be sure to unforbid the items that you want to work with! + +.. note:: + + Weapons are not currently supported. + +Usage +----- + +:: + + forceequip [] + +As mentioned above, this plugin can be used to equip items onto units (such as +animals) who cannot normally equip gear. There's an important caveat here: such +creatures will automatically drop inappropriate gear almost immediately (within +10 game ticks). If you want them to retain their equipment, you must forbid it +AFTER using forceequip to get it into their inventory. This technique can also +be used to clothe dwarven infants, but only if you're able to separate them from +their mothers. + +By default, the ``forceequip`` command will attempt to abide by game rules as +closely as possible. For instance, it will skip any item which is flagged for +use in a job, and will not equip more than one piece of clothing/armor onto any +given body part. These restrictions can be overridden via options, but doing so +puts you at greater risk of unexpected consequences. For instance, a dwarf who +is wearing three breastplates will not be able to move very quickly. + +Items equipped by this plugin DO NOT become owned by the recipient. Adult +dwarves are free to adjust their own wardrobe, and may promptly decide to doff +your gear in favour of their owned items. Animals, as described above, will tend +to discard ALL clothing immediately unless it is manually forbidden. Armor items +seem to be an exception: an animal will tend to retain an equipped suit of mail +even if you neglect to forbid it. + +Please note that armored animals are quite vulnerable to ranged attacks. Unlike +dwarves, animals cannot block, dodge, or deflect arrows, and they are slowed by +the weight of their armor. + +Examples +-------- + +``forceequip`` + Attempts to equip all of the clothing and armor under the cursor onto the + unit under the cursor, following game rules regarding which item can be + equipped on which body part and only equipping 1 item onto each body part. + Items owned by other dwarves are ignored. +``forceequip v bp QQQ`` + List the bodyparts of the selected unit. +``forceequip bp LH`` + Equips an appropriate item onto the unit's left hand. +``forceequip m bp LH`` + Equips ALL appropriate items onto the unit's left hand. The unit may end up + wearing a dozen left-handed mittens. Use with caution, and remember that + dwarves tend to drop extra items ASAP. +``forceequip i bp NECK`` + Equips an item around the unit's neck, ignoring appropriateness + restrictions. If there's a millstone or an albatross carcass sitting on the + same square as the targeted unit, then there's a good chance that it will + end up around his neck. For precise control, remember that you can + selectively forbid some of the items that are piled on the ground. +``forceequip s`` + Equips the item currently selected in the k menu, if possible. +``forceequip s m i bp HD`` + Equips the selected item onto the unit's head. Ignores all restrictions and + conflicts. If you know exactly what you want to equip, and exactly where you + want it to go, then this is the most straightforward and reliable option. + +Options +------- + +``i``, ``ignore`` + Bypasses the usual item eligibility checks (such as "Never equip gear + belonging to another dwarf" and "Nobody is allowed to equip a Hive". +``m``, ``multi`` + Bypasses the 1-item-per-bodypart limit. Useful for equipping both a mitten + and a gauntlet on the same hand (or twelve breastplates on the upper body). +``m2``, ``m3``, ``m4`` + Modifies the 1-item-per-bodypart limit, allowing each part to receive 2, 3, + or 4 pieces of gear. +``s``, ``selected`` + Equip only the item currently selected in the k menu and ignore all other + items in the tile. +``bp``, ``bodypart `` + Specify which body part should be equipped. +``v``, ``verbose`` + Provide detailed narration and error messages, including listing available + body parts when an invalid ``bodypart`` code is specified. diff --git a/docs/plugins/generated-creature-renamer.rst b/docs/plugins/generated-creature-renamer.rst new file mode 100644 index 000000000..68302dbf5 --- /dev/null +++ b/docs/plugins/generated-creature-renamer.rst @@ -0,0 +1,32 @@ +generated-creature-renamer +========================== + +.. dfhack-tool:: + :summary: Automatically renames generated creatures. + :tags: adventure fort legends units + :no-command: + +.. dfhack-command:: list-generated + :summary: List the token names of all generated creatures. + +.. dfhack-command:: save-generated-raws + :summary: Export a creature graphics file for modding. + +Now, forgotten beasts, titans, necromancer experiments, etc. will have raw token +names that match the description given in-game instead of unreadable generated +strings. + +Usage +----- + +``enable generated-creature-renamer`` + Rename generated creatures when a world is loaded. +``list-generated [detailed]`` + List the token names of all generated creatures in the loaded save. If + ``detailed`` is specified, then also show the accompanying description. +``save-generated-raws`` + Save a sample creature graphics file in the Dwarf Fortress root directory to + use as a start for making a graphics set for generated creatures using the + new names that they get with this plugin. + +The new names are saved with the world. diff --git a/docs/plugins/getplants.rst b/docs/plugins/getplants.rst new file mode 100644 index 000000000..05bac71c9 --- /dev/null +++ b/docs/plugins/getplants.rst @@ -0,0 +1,57 @@ +getplants +========= + +.. dfhack-tool:: + :summary: Designate trees for chopping and shrubs for gathering. + :tags: fort productivity plants + +Specify the types of trees to cut down and/or shrubs to gather by their plant +names. + +Usage +----- + +``getplants [-t|-s|-f]`` + List valid tree/shrub ids, optionally restricted to the specified type. +``getplants [ ...] []`` + Designate trees/shrubs of the specified types for chopping/gathering. + +Examples +-------- + +``getplants`` + List all valid IDs. +``getplants -f -a`` + Gather all plants on the map that yield seeds for farming. +``getplants NETHER_CAP -n 10`` + Designate 10 nether cap trees for chopping. + +Options +------- + +``-t`` + Tree: Select trees only (exclude shrubs). +``-s`` + Shrub: Select shrubs only (exclude trees). +``-f`` + Farming: Designate only shrubs that yield seeds for farming. +``-a`` + All: Select every type of plant (obeys ``-t``/``-s``/``-f``). +``-c`` + Clear: Clear designations instead of setting them. +``-x`` + eXcept: Apply selected action to all plants except those specified (invert + selection). +``-v`` + Verbose: Lists the number of (un)designations per plant. +``-n `` + Number: Designate up to the specified number of plants of each species. + +.. note:: + + DF is capable of determining that a shrub has already been picked, leaving + an unusable structure part behind. This plugin does not perform such a check + (as the location of the required information has not yet been identified). + This leads to some shrubs being designated when they shouldn't be, causing a + plant gatherer to walk there and do nothing (except clearing the + designation). See :issue:`1479` for details. diff --git a/docs/plugins/hotkeys.rst b/docs/plugins/hotkeys.rst new file mode 100644 index 000000000..6780efca1 --- /dev/null +++ b/docs/plugins/hotkeys.rst @@ -0,0 +1,18 @@ +hotkeys +======= + +.. dfhack-tool:: + :summary: Show all dfhack keybindings for the current context. + :tags: dfhack + +The command opens an in-game screen showing which DFHack keybindings are active +in the current context. See also `hotkey-notes`. + +Usage +----- + +:: + + hotkeys + +.. image:: ../images/hotkeys.png diff --git a/docs/plugins/infiniteSky.rst b/docs/plugins/infiniteSky.rst new file mode 100644 index 000000000..c15f87e26 --- /dev/null +++ b/docs/plugins/infiniteSky.rst @@ -0,0 +1,26 @@ +infiniteSky +=========== + +.. dfhack-tool:: + :summary: Automatically allocate new z-levels of sky + :tags: fort 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 +at your command. + +Usage +----- + +``enable infiniteSky`` + Enables monitoring of constructions. If you build anything in the second + highest z-level, it will allocate one more sky level. You can build stairs + up as high as you like! +``infiniteSky []`` + Raise the sky by n z-levels. If run without parameters, raises the sky by + one z-level. + +.. warning:: + + :issue:`Sometimes <254>` new z-levels disappear and cause cave-ins. + Saving and loading after creating new z-levels should fix the problem. diff --git a/docs/plugins/isoworldremote.rst b/docs/plugins/isoworldremote.rst new file mode 100644 index 000000000..284794f96 --- /dev/null +++ b/docs/plugins/isoworldremote.rst @@ -0,0 +1,9 @@ +isoworldremote +============== + +.. dfhack-tool:: + :summary: Provides a remote API used by Isoworld. + :tags: dev graphics + :no-command: + +See `remote` for related remote APIs. diff --git a/docs/plugins/jobutils.rst b/docs/plugins/jobutils.rst new file mode 100644 index 000000000..9d083d92f --- /dev/null +++ b/docs/plugins/jobutils.rst @@ -0,0 +1,60 @@ +.. _job: + +jobutils +======== + +.. dfhack-tool:: + :summary: Provides commands for interacting with jobs. + :tags: fort inspection jobs + :no-command: + +.. dfhack-command:: job + :summary: Inspect or modify details of workshop jobs. + +.. dfhack-command:: job-duplicate + :summary: Duplicates the highlighted job. + +.. dfhack-command:: job-material + :summary: Alters the material of the selected job. + +Usage +----- + +``job`` + Print details of the current job. The job can be selected in a workshop or + the unit/jobs screen. +``job list`` + Print details of all jobs in the selected workshop. +``job item-material `` + Replace the exact material id in the job item. +``job item-type `` + Replace the exact item type id in the job item. +``job-duplicate`` + Duplicates the highlighted job. Must be in :kbd:`q` mode and have a workshop + or furnace building selected. +``job-material `` + Alters the material of the selected job (in :kbd:`q` mode) or jumps to the + selected material when choosing the building component of a planned building + (in :kbd:`b` mode). Note that this form of the command can only handle + inorganic materials. + +Use the ``job`` and ``job list`` commands to discover the type and material ids +for existing jobs, or use the following commands to see the full lists:: + + lua @df.item_type + lua "for i,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(i, mat.id) end end" + +Examples +-------- + +``job-material GNEISS`` + Change the selected "Construct rock Coffin" job at a Mason's workshop to + "Construct gneiss coffin". +``job item-material 2 MARBLE`` + Change the selected "Construct Traction Bench" job (which has three source + items: a table, a mechanism, and a chain) to specifically use a marble + mechanism. +``job item-type 2 TABLE`` + Change the selected "Encrust furniture with blue jade" job (which has two + source items: a cut gem and a piece of improvable furniture) to specifically + use a table instead of just any furniture. diff --git a/docs/plugins/labormanager.rst b/docs/plugins/labormanager.rst new file mode 100644 index 000000000..e3443280d --- /dev/null +++ b/docs/plugins/labormanager.rst @@ -0,0 +1,132 @@ +labormanager +============ + +.. dfhack-tool:: + :summary: Automatically manage dwarf labors. + :tags: 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 +dwarves busy as possible, labormanager instead strives to get jobs done as +quickly as possible. + +Labormanager frequently scans the current job list, current list of dwarves, and +the map to determine how many dwarves need to be assigned to what labors in +order to meet all current labor needs without starving any particular type of +job. + +Dwarves on active military duty or dwarves assigned to burrows are left +untouched. + +.. warning:: + + As with autolabor, labormanager will override any manual changes you make to + labors while it is enabled, including through other tools such as Dwarf + Therapist. Do not run both autolabor and labormanager at the same time! + +Usage +----- + +:: + + enable labormanager + +Anything beyond this is optional - labormanager works well with the default +settings. Once you have enabled it in a fortress, it stays enabled until you +explicitly disable it, even if you save and reload your game. + +The default priorities for each labor vary (some labors are higher priority by +default than others). The way the plugin works is that, once it determines how +many jobs of each labor are needed, it then sorts them by adjusted priority. +(Labors other than hauling have a bias added to them based on how long it's been +since they were last used to prevent job starvation.) The labor with the highest +priority is selected, the "best fit" dwarf for that labor is assigned to that +labor, and then its priority is *halved*. This process is repeated until either +dwarves or labors run out. + +Because there is no easy way to detect how many haulers are actually needed at +any moment, the plugin always ensures that at least one dwarf is assigned to +each of the hauling labors, even if no hauling jobs are detected. At least one +dwarf is always assigned to construction removing and cleaning because these +jobs also cannot be easily detected. Lever pulling is always assigned to +everyone. Any dwarves for which there are no jobs will be assigned hauling, +lever pulling, and cleaning labors. If you use animal trainers, note that +labormanager will misbehave if you assign specific trainers to specific animals; +results are only guaranteed if you use "any trainer". + +Labormanager also sometimes assigns extra labors to currently busy dwarfs so +that when they finish their current job, they will go off and do something +useful instead of standing around waiting for a job. + +There is special handling to ensure that at least one dwarf is assigned to haul +food whenever food is detected left in a place where it will rot if not stored. +This will cause a dwarf to go idle if you have no stockpiles to haul food to. + +Dwarves who are unable to work (child, in the military, wounded, handless, +asleep, in a meeting) are entirely excluded from labor assignment. Any dwarf +explicitly assigned to a burrow will also be completely ignored by labormanager. + +The fitness algorithm for assigning jobs to dwarves generally attempts to favor +dwarves who are more skilled over those who are less skilled. It also tries to +avoid assigning female dwarfs with children to jobs that are "outside", favors +assigning "outside" jobs to dwarfs who are carrying a tool that could be used as +a weapon, and tries to minimize how often dwarves have to reequip. + +Labormanager automatically determines medical needs and reserves health care +providers as needed. Note that this may cause idling if you have injured dwarves +but no or inadequate hospital facilities. + +Hunting is never assigned without a butchery, and fishing is never assigned +without a fishery, and neither of these labors is assigned unless specifically +enabled (see below). + +The method by which labormanager determines what labor is needed for a +particular job is complicated and, in places, incomplete. In some situations, +labormanager will detect that it cannot determine what labor is required. It +will, by default, pause and print an error message on the dfhack console, +followed by the message "LABORMANAGER: Game paused so you can investigate the +above message.". If this happens, please open an :issue:`` on GitHub, +reporting the lines that immediately preceded this message. You can tell +labormanager to ignore this error and carry on by running +``labormanager pause-on-error no``, but be warned that some job may go undone in +this situation. + +Examples +-------- + +``labormanager priority BREWER 500`` + Boost the priority of brewing jobs. +``labormanager max FISH 1`` + Only assign fishing to one dwarf at a time. Note that you also have to run + ``labormanager allow-fishing`` for any dwarves to be assigned fishing at + all. + +Advanced usage +-------------- + +``labormanager list`` + Show current priorities and current allocation stats. Use this command to + see the IDs for all labors. +``labormanager status`` + Show basic status information. +``labormanager priority `` + Set the priority value for labor to . +``labormanager max `` + Set the maximum number of dwarves that can be assigned to a labor. +``labormanager max none`` + Unrestrict the number of dwarves that can be assigned to a labor. +``labormanager max disable`` + Don't manage the specified labor. Dwarves who you have manually enabled this + labor on will be less likely to have managed labors assigned to them. +``labormanager reset-all|reset `` + Return a labor (or all labors) to the default priority. +``labormanager allow-fishing|forbid-fishing`` + Allow/disallow fisherdwarves. *Warning* Allowing fishing tends to result in + most of the fort going fishing. Fishing is forbidden by default. +``labormanager allow-hunting|forbid-hunting`` + Allow/disallow hunterdwarves. *Warning* Allowing hunting tends to result in + as many dwarves going hunting as you have crossbows. Hunting is forbidden by + default. +``labormanager pause-on-error yes|no`` + Make labormanager pause/continue if the labor inference engine fails. See + the above section for details. diff --git a/docs/plugins/lair.rst b/docs/plugins/lair.rst new file mode 100644 index 000000000..9bded57ab --- /dev/null +++ b/docs/plugins/lair.rst @@ -0,0 +1,19 @@ +lair +==== + +.. dfhack-tool:: + :summary: Mark the map as a monster lair. + :tags: fort armok map + +This avoids item scatter when the fortress is abandoned. + +Usage +----- + +``lair`` + Mark the map as a monster lair. +``lair reset`` + Mark the map as ordinary (not lair). + +This command doesn't save the information about tiles - you won't be able to +restore the state of a real monster lairs using ``lair reset``. diff --git a/docs/plugins/liquids.rst b/docs/plugins/liquids.rst new file mode 100644 index 000000000..5bc24d04f --- /dev/null +++ b/docs/plugins/liquids.rst @@ -0,0 +1,84 @@ +.. _liquids-here: + +liquids +======= + +.. dfhack-tool:: + :summary: Place magma, water or obsidian. + :tags: adventure fort armok map + +.. dfhack-command:: liquids-here + :summary: Spawn liquids on the selected tile. + +Place magma, water or obsidian. See `gui/liquids` for an in-game interface for +this functionality. + +Also, if you only want to add or remove water or magma from a single tile, the +`source` script may be easier to use. + +Usage +----- + +``liquids`` + Start the interactive terminal settings interpreter. This command must be + called from the DFHack terminal and not from any in-game interface. +``liquids-here`` + Run the liquid spawner with the current/last settings made in ``liquids`` + (if no settings in ``liquids`` were made, then it paints a point of 7/7 + magma by default). This command is intended to be used as keybinding, and it + requires an active in-game cursor. + +.. warning:: + + Spawning and deleting liquids can mess up pathing data and temperatures + (creating heat traps). You've been warned. + +Interactive interpreter +----------------------- + +The interpreter replaces the normal dfhack command line and can't be used from a +hotkey. Settings will be remembered as long as dfhack runs. It is intended for +use in combination with the command ``liquids-here`` (which *can* be bound to a +hotkey). + +You can enter the following commands at the prompt. + +Misc commands: + +:q: quit +:help, ?: print this list of commands +:: put liquid + +Modes: + +:m: switch to magma +:w: switch to water +:o: make obsidian wall instead +:of: make obsidian floors +:rs: make a river source +:f: flow bits only +:wclean: remove salt and stagnant flags from tiles + +Set-Modes and flow properties (only for magma/water): + +:s+: only add mode +:s.: set mode +:s-: only remove mode +:f+: make the spawned liquid flow +:f.: don't change flow state (read state in flow mode) +:f-: make the spawned liquid static + +Permaflow (only for water): + +:pf.: don't change permaflow state +:pf-: make the spawned liquid static +:pf[NS][EW]: make the spawned liquid permanently flow +:0-7: set liquid amount + +Brush size and shape: + +:p, point: Single tile +:r, range: Block with cursor at bottom north-west (any place, any size) +:block: DF map block with cursor in it (regular spaced 16x16x1 blocks) +:column: Column from cursor, up through free space +:flood: Flood-fill water tiles from cursor (only makes sense with wclean) diff --git a/docs/plugins/luasocket.rst b/docs/plugins/luasocket.rst new file mode 100644 index 000000000..4b5b18540 --- /dev/null +++ b/docs/plugins/luasocket.rst @@ -0,0 +1,9 @@ +luasocket +========= + +.. dfhack-tool:: + :summary: Provides a Lua API for accessing network sockets. + :tags: dev + :no-command: + +See `luasocket-api` for details. diff --git a/docs/plugins/manipulator.rst b/docs/plugins/manipulator.rst new file mode 100644 index 000000000..f333f3be0 --- /dev/null +++ b/docs/plugins/manipulator.rst @@ -0,0 +1,181 @@ +manipulator +=========== + +.. dfhack-tool:: + :summary: An in-game labor management interface. + :tags: fort productivity labors + :no-command: + +It is equivalent to the popular Dwarf Therapist utility. + +To activate, open the unit screen and press :kbd:`l`. + +Usage +----- + +:: + + enable manipulator + +.. image:: ../images/manipulator.png + +The far left column displays the unit's name, happiness (color-coded based on +its value), profession, or squad, and the right half of the screen displays each +dwarf's labor settings and skill levels (0-9 for Dabbling through Professional, +A-E for Great through Grand Master, and U-Z for Legendary through Legendary+5). + +Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills. + +.. image:: ../images/manipulator2.png + +Press :kbd:`t` to toggle between Profession, Squad, and Job views. + +.. image:: ../images/manipulator3.png + +Use the arrow keys or number pad to move the cursor around, holding :kbd:`Shift` to +move 10 tiles at a time. + +Press the Z-Up (:kbd:`<`) and Z-Down (:kbd:`>`) keys to move quickly between labor/skill +categories. The numpad Z-Up and Z-Down keys seek to the first or last unit +in the list. :kbd:`Backspace` seeks to the top left corner. + +Press Enter to toggle the selected labor for the selected unit, or Shift+Enter +to toggle all labors within the selected category. + +Press the :kbd:`+`:kbd:`-` keys to sort the unit list according to the currently selected +skill/labor, and press the :kbd:`*`:kbd:`/` keys to sort the unit list by Name, Profession/Squad, +Happiness, or Arrival order (using :kbd:`Tab` to select which sort method to use here). + +With a unit selected, you can press the :kbd:`v` key to view its properties (and +possibly set a custom nickname or profession) or the :kbd:`c` key to exit +Manipulator and zoom to its position within your fortress. + +The following mouse shortcuts are also available: + +* Click on a column header to sort the unit list. Left-click to sort it in one + direction (descending for happiness or labors/skills, ascending for name, + profession or squad) and right-click to sort it in the opposite direction. +* Left-click on a labor cell to toggle that labor. Right-click to move the + cursor onto that cell instead of toggling it. +* Left-click on a unit's name, profession or squad to view its properties. +* Right-click on a unit's name, profession or squad to zoom to it. + +Pressing :kbd:`Esc` normally returns to the unit screen, but :kbd:`Shift`:kbd:`Esc` would exit +directly to the main dwarf mode screen. + +Professions +----------- + +The manipulator plugin supports saving professions: a named set of labors that can be +quickly applied to one or multiple dwarves. + +To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using +the custom profession name of the dwarf, or the default profession name for that dwarf if no +custom profession name has been set. + +To apply a profession, either highlight a single dwarf or select multiple with +:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for +the selected dwarves will be reset to the labors of the chosen profession and +the custom profession names for those dwarves will be set to the applied +profession. + +Professions are saved as human-readable text files in the +``dfhack-config/professions`` folder within the DF folder, and can be edited or +deleted there. + +The professions library +~~~~~~~~~~~~~~~~~~~~~~~ + +The manipulator plugin comes with a library of professions that you can assign +to your dwarves. + +If you'd rather use Dwarf Therapist to manage your labors, it is easy to import +these professions to DT and use them there. Simply assign the professions you +want to import to a dwarf. Once you have assigned a profession to at least one +dwarf, you can select "Import Professions from DF" in the DT "File" menu. The +professions will then be available for use in DT. + +In the list below, the "needed" range indicates the approximate number of +dwarves of each profession that you are likely to need at the start of the game +and how many you are likely to need in a mature fort. These are just +approximations. Your playstyle may demand more or fewer of each profession. + +- ``Chef`` (needed: 0, 3) + Butchery, Tanning, and Cooking. It is important to focus just a few dwarves + on cooking since well-crafted meals make dwarves very happy. They are also + an excellent trade good. +- ``Craftsdwarf`` (needed: 0, 4-6) + All labors used at Craftsdwarf's workshops, Glassmaker's workshops, and + kilns. +- ``Doctor`` (needed: 0, 2-4) + The full suite of medical labors, plus Animal Caretaking for those using + the `dwarfvet` plugin. +- ``Farmer`` (needed 1, 4) + Food- and animal product-related labors. +- ``Fisherdwarf`` (needed 0, 0-1) + Fishing and fish cleaning. If you assign this profession to any dwarf, be + prepared to be inundated with fish. Fisherdwarves *never stop fishing*. Be + sure to also run ``prioritize -a PrepareRawFish ExtractFromRawFish`` or else + caught fish will just be left to rot. +- ``Hauler`` (needed 0, >20) + All hauling labors plus Siege Operating, Mechanic (so haulers can assist in + reloading traps) and Architecture (so haulers can help build massive + windmill farms and pump stacks). As you accumulate enough Haulers, you can + turn off hauling labors for other dwarves so they can focus on their skilled + tasks. You may also want to restrict your Mechanic's workshops to only + skilled mechanics so your unskilled haulers don't make low-quality + mechanisms. +- ``Laborer`` (needed 0, 10-12) + All labors that don't improve quality with skill, such as Soapmaking and + furnace labors. +- ``Marksdwarf`` (needed 0, 10-30) + Similar to ``Hauler``. See the description for ``Meleedwarf`` below for more + details. +- ``Mason`` (needed 2, 2-4) + Masonry and Gem Cutting/Encrusting. +- ``Meleedwarf`` (needed 0, 20-50) + Similar to ``Hauler``, but without most civilian labors. This profession is + separate from ``Hauler`` so you can find your military dwarves easily. + ``Meleedwarves`` and ``Marksdwarves`` have Mechanics and hauling labors + enabled so you can temporarily deactivate your military after sieges and + allow your military dwarves to help clean up and reset traps. +- ``Migrant`` (needed 0, 0) + You can assign this profession to new migrants temporarily while you sort + them into professions. Like ``Marksdwarf`` and ``Meleedwarf``, the purpose + of this profession is so you can find your new dwarves more easily. +- ``Miner`` (needed 2, 2-10) + Mining and Engraving. This profession also has the ``Alchemist`` labor + enabled, which disables hauling for those using the `autohauler` plugin. + Once the need for Miners tapers off in the late game, dwarves with this + profession make good military dwarves, wielding their picks as weapons. +- ``Outdoorsdwarf`` (needed 1, 2-4) + Carpentry, Bowyery, Woodcutting, Animal Training, Trapping, Plant Gathering, + Beekeeping, and Siege Engineering. +- ``Smith`` (needed 0, 2-4) + Smithing labors. You may want to specialize your Smiths to focus on a single + smithing skill to maximize equipment quality. +- ``StartManager`` (needed 1, 0) + All skills not covered by the other starting professions (``Miner``, + ``Mason``, ``Outdoorsdwarf``, and ``Farmer``), plus a few overlapping skills + to assist in critical tasks at the beginning of the game. Individual labors + should be turned off as migrants are assigned more specialized professions + that cover them, and the StartManager dwarf can eventually convert to some + other profession. +- ``Tailor`` (needed 0, 2) + Textile industry labors: Dying, Leatherworking, Weaving, and Clothesmaking. + +A note on autohauler +~~~~~~~~~~~~~~~~~~~~ + +These profession definitions are designed to work well with or without the +`autohauler` plugin (which helps to keep your dwarves focused on skilled labors +instead of constantly being distracted by hauling). If you do want to use +autohauler, adding the following lines to your ``onMapLoad.init`` file will +configure it to let the professions manage the "Feed water to civilians" and +"Recover wounded" labors instead of enabling those labors for all hauling +dwarves:: + + on-new-fortress enable autohauler + on-new-fortress autohauler FEED_WATER_CIVILIANS allow + on-new-fortress autohauler RECOVER_WOUNDED allow diff --git a/docs/plugins/map-render.rst b/docs/plugins/map-render.rst new file mode 100644 index 000000000..935fc1010 --- /dev/null +++ b/docs/plugins/map-render.rst @@ -0,0 +1,9 @@ +map-render +========== + +.. dfhack-tool:: + :summary: Provides a Lua API for re-rendering portions of the map. + :tags: dev graphics + :no-command: + +See `map-render-api` for details. diff --git a/docs/plugins/misery.rst b/docs/plugins/misery.rst new file mode 100644 index 000000000..b3634d2ac --- /dev/null +++ b/docs/plugins/misery.rst @@ -0,0 +1,19 @@ +misery +====== + +.. dfhack-tool:: + :summary: Increase the intensity of negative dwarven thoughts. + :tags: fort armok units + +When enabled, negative thoughts that your dwarves have will multiply by the +specified factor. + +Usage +----- + +``enable misery`` + Start multiplying negative thoughts. +``misery `` + Change the multiplicative factor of bad thoughts. The default is ``2``. +``misery clear`` + Clear away negative thoughts added by ``misery``. diff --git a/docs/plugins/mode.rst b/docs/plugins/mode.rst new file mode 100644 index 000000000..33173a5d3 --- /dev/null +++ b/docs/plugins/mode.rst @@ -0,0 +1,39 @@ +mode +==== + +.. dfhack-tool:: + :summary: See and change the game mode. + :tags: armok dev gameplay + +.. warning:: + + Only use ``mode`` after making a backup of your save! + + Not all combinations are good for every situation and most of them will + produce undesirable results. There are a few good ones though. + +Usage +----- + +``mode`` + Print the current game mode. +``mode set`` + Enter an interactive commandline menu where you can set the game mode. + +Examples +-------- + +Scenario 1: + +* You are in fort game mode, managing your fortress and paused. +* You switch to the arena game mode, *assume control of a creature* and then +* switch to adventure game mode. + +You just lost a fortress and gained an adventurer. + +Scenario 2: + +* You are in fort game mode, managing your fortress and paused at the Esc menu. +* You switch to the adventure game mode, assume control of a creature, then save or retire. + +You just created a returnable mountain home and gained an adventurer. diff --git a/docs/plugins/mousequery.rst b/docs/plugins/mousequery.rst new file mode 100644 index 000000000..12f4092bd --- /dev/null +++ b/docs/plugins/mousequery.rst @@ -0,0 +1,41 @@ +mousequery +========== + +.. dfhack-tool:: + :summary: Adds mouse controls to the DF interface. + :tags: 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 +designations, or click and drag to move the map around. + +Usage +----- + +:: + + enable mousequery + mousequery [rbutton|track|edge|live] [enable|disable] + mousequery drag [left|right|disable] + mousequery delay [] + +:rbutton: When the right mouse button is clicked, cancel out of menus or + scroll the main map if you r-click near an edge. +:track: Move the cursor with the mouse instead of the cursor keys when you + are in build or designation modes. +:edge: Scroll the map when you move the cursor to a map edge. See ``delay`` + below. If enabled also enables ``track``. +:delay: Set delay in milliseconds for map edge scrolling. Omit the amount to + display the current setting. +:live: Display information in the lower right corner of the screen about + the items/building/tile under the cursor, even while unpaused. + +Examples +-------- + +``mousequery rbutton enable`` + Enable using the right mouse button to cancel out of menus and scroll the + map. +``mousequery delay 300`` + When run after ``mousequery edge enable``, sets the edge scrolling delay to + 300ms. diff --git a/docs/plugins/nestboxes.rst b/docs/plugins/nestboxes.rst new file mode 100644 index 000000000..3c07cc30c --- /dev/null +++ b/docs/plugins/nestboxes.rst @@ -0,0 +1,18 @@ +nestboxes +========= + +.. dfhack-tool:: + :summary: Protect fertile eggs incubating in a nestbox. + :tags: fort auto animals + :no-command: + +This plugin will automatically scan for and forbid fertile eggs incubating in a +nestbox so that dwarves won't come to collect them for eating. The eggs will +hatch normally, even when forbidden. + +Usage +----- + +:: + + enable nestboxes diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst new file mode 100644 index 000000000..298800c96 --- /dev/null +++ b/docs/plugins/orders.rst @@ -0,0 +1,140 @@ +orders +====== + +.. dfhack-tool:: + :summary: Manage manager orders. + :tags: fort productivity workorders + +Usage +----- + +``orders orders list`` + Shows the list of previously exported orders, including the orders library. +``orders export `` + Saves all the current manager orders in a file. +``orders import `` + Imports the specified manager orders. Note this adds to your current set of + manager orders. It will not clear the orders that already exist. +``orders clear`` + Deletes all manager orders in the current embark. +``orders sort`` + Sorts current manager orders by repeat frequency so repeating orders don't + prevent one-time orders from ever being completed. The sorting order is: + one-time orders first, then yearly, seasonally, monthly, and finally, daily. + +You can keep your orders automatically sorted by adding the following command to +your ``onMapLoad.init`` file:: + + repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] + +Exported orders are saved in the ``dfhack-config/orders`` directory, where you +can view, edit, and delete them, if desired. + +Examples +-------- + +``orders export myorders`` + Export the current manager orders to a file named + ``dfhack-config/orders/myorders.json``. +``orders import library/basic`` + Import manager orders from the library that keep your fort stocked with + basic essentials. + +The orders library +------------------ + +DFHack comes with a library of useful manager orders that are ready for import: + +:source:`library/basic ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +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 +- bags of leather, cloth, silk, and yarn +- crafts and totems from otherwise unusable by-products +- mechanisms/cages +- splints/crutches +- lye/soap +- ash/potash +- beds/wheelbarrows/minecarts +- scrolls + +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. + +:source:`library/furnace ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection creates basic items that require heat. It is separated out from +``library/basic`` to give players the opportunity to set up magma furnaces first +in order to save resources. It handles: + +- charcoal (including smelting of bituminous coal and lignite) +- pearlash +- sand +- green/clear/crystal glass +- adamantine processing +- item melting + +Orders are missing for plaster powder until DF :bug:`11803` is fixed. + +:source:`library/military ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds high-volume smelting jobs for military-grade metal ores and +produces weapons and armor: + +- leather backpacks/waterskins/cloaks/quivers/armor +- bone/wooden bolts +- smelting for platinum, silver, steel, bronze, bismuth bronze, and copper (and + their dependencies) +- bronze/bismuth bronze/copper bolts +- platinum/silver/steel/iron/bismuth bronze/bronze/copper weapons and armor, + with checks to ensure only the best available materials are being used + +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), +these orders will automatically upgrade your military equipment to masterwork. +Make sure you have a lot of fuel (or magma forges and furnaces) before you turn +``automelt`` on, though! + +This file should only be imported, of course, if you need to equip a military. + +:source:`library/smelting ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection adds smelting jobs for all ores. It includes handling the ores +already managed by ``library/military``, but has lower limits. This ensures all +ores will be covered if a player imports ``library/smelting`` but not +``library/military``, but the higher-volume ``library/military`` orders will +take priority if both are imported. + +:source:`library/rockstock ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This collection of orders keeps a small stock of all types of rock furniture. +This allows you to do ad-hoc furnishings of guildhalls, libraries, temples, or +other rooms with `buildingplan` and your masons will make sure there is always +stock on hand to fulfill the plans. + +:source:`library/glassstock ` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Similar to ``library/rockstock`` above, this collection keeps a small stock of +all types of glass furniture. If you have a functioning glass industry, this is +more sustainable than ``library/rockstock`` since you can never run out of sand. +If you have plenty of rock and just want the variety, you can import both +``library/rockstock`` and ``library/glassstock`` to get a mixture of rock and +glass furnishings in your fort. + +There are a few items that ``library/glassstock`` produces that +``library/rockstock`` does not, since there are some items that can not be made +out of rock, for example: + +- tubes and corkscrews for building magma-safe screw pumps +- windows +- terrariums (as an alternative to wooden cages) diff --git a/docs/plugins/overlay.rst b/docs/plugins/overlay.rst new file mode 100644 index 000000000..9416fba31 --- /dev/null +++ b/docs/plugins/overlay.rst @@ -0,0 +1,20 @@ +overlay +======= + +.. dfhack-tool:: + :summary: Provide an on-screen clickable DFHack launcher button. + :tags: dfhack interface + +This tool places a small button in the lower left corner of the screen that you +can click to run DFHack commands with `gui/launcher`. + +If you would rather always run `gui/launcher` with the hotkeys, or just don't +want the DFHack button on-screen, just disable the plugin with +``disable overlay``. + +Usage +----- + +:: + + enable overlay diff --git a/docs/plugins/pathable.rst b/docs/plugins/pathable.rst new file mode 100644 index 000000000..f4b89683b --- /dev/null +++ b/docs/plugins/pathable.rst @@ -0,0 +1,10 @@ +pathable +======== + +.. dfhack-tool:: + :summary: Marks tiles that are reachable from the cursor. + :tags: dev inspection map + :no-command: + +This plugin provides a Lua API, but no direct commands. See `pathable-api` for +details. diff --git a/docs/plugins/petcapRemover.rst b/docs/plugins/petcapRemover.rst new file mode 100644 index 000000000..62959e731 --- /dev/null +++ b/docs/plugins/petcapRemover.rst @@ -0,0 +1,33 @@ +petcapRemover +============= + +.. dfhack-tool:: + :summary: Modify the pet population cap. + :tags: fort 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 +allows removing these restrictions and setting your own thresholds. Pets still +require PET or PET_EXOTIC tags in order to reproduce. In order to make +population more stable and avoid sudden population booms as you go below the +raised population cap, this plugin counts pregnancies toward the new population +cap. It can still go over, but only in the case of multiple births. + +Usage +----- + +``enable petcapRemover`` + Enables the plugin and starts running with default settings. +``petcapRemover cap `` + Set the new population cap per species to the specified value. If set to 0, + then there is no cap (good luck with all those animals!). The default cap + is 100. +``petcapRemover`` + Impregnate female pets that have access to a compatible male, up to the + population cap. +``petcapRemover every `` + Set how often the plugin will cause pregnancies. The default frequency is + every 10,000 ticks (a little over 8 game days). +``petcapRemover pregtime `` + Sets the pregnancy duration to the specified number of ticks. The default + value is 200,000 ticks, which is the natural pet pregnancy duration. diff --git a/docs/plugins/plants.rst b/docs/plugins/plants.rst new file mode 100644 index 000000000..f0a4f4af5 --- /dev/null +++ b/docs/plugins/plants.rst @@ -0,0 +1,33 @@ +.. _plant: + +plants +====== + +.. dfhack-tool:: + :summary: Provides commands that interact with plants. + :tags: adventure fort armok map plants + :no-command: + +.. dfhack-command:: plant + :summary: Create a plant or make an existing plant grow up. + +Usage +----- + +``plant create `` + Creates a new plant of the specified type at the active cursor position. + The cursor must be on a dirt or grass floor tile. +``plant grow`` + Grows saplings into trees. If the cursor is active, it only affects the + sapling under the cursor. If no cursor is active, it affect all saplings + on the map. + +To see the full list of plant ids, run the following command:: + + devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 + +Example +------- + +``plant create TOWER_CAP`` + Create a Tower Cap sapling at the cursor position. diff --git a/docs/plugins/power-meter.rst b/docs/plugins/power-meter.rst new file mode 100644 index 000000000..701a656b5 --- /dev/null +++ b/docs/plugins/power-meter.rst @@ -0,0 +1,11 @@ +power-meter +=========== + +.. dfhack-tool:: + :summary: Allow pressure plates to measure power. + :tags: fort gameplay buildings + :no-command: + +If you run `gui/power-meter` while building a pressure plate, the pressure +plate can be modified to detect power being supplied to gear boxes built in the +four adjacent N/S/W/E tiles. diff --git a/docs/plugins/probe.rst b/docs/plugins/probe.rst new file mode 100644 index 000000000..f9941bc48 --- /dev/null +++ b/docs/plugins/probe.rst @@ -0,0 +1,26 @@ +probe +===== + +.. dfhack-tool:: + :summary: Display low-level properties of the selected tile. + :tags: adventure fort inspection buildings map units + +.. dfhack-command:: bprobe + :summary: Display low-level properties of the selected building. + +.. dfhack-command:: cprobe + :summary: Display low-level properties of the selected unit. + +Usage +----- + +``probe`` + Displays properties of the tile selected with :kbd:`k`. Some of these + properties can be passed into `tiletypes`. +``bprobe`` + Displays properties of the building selected with :kbd:`q` or :kbd:`t`. + For deeper inspection of the building, see `gui/gm-editor`. +``cprobe`` + Displays properties of the unit selected with :kbd:`v`. It also displays the + IDs of any worn items. For deeper inspection of the unit and inventory items, + see `gui/gm-unit` and `gui/gm-editor`. diff --git a/docs/plugins/prospector.rst b/docs/plugins/prospector.rst new file mode 100644 index 000000000..4628d11c8 --- /dev/null +++ b/docs/plugins/prospector.rst @@ -0,0 +1,69 @@ +.. _prospect: + +prospector +========== + +.. dfhack-tool:: + :summary: Provides commands that help you analyze natural resources. + :tags: embark fort armok inspection map + :no-command: + +.. dfhack-command:: prospect + :summary: Shows a summary of resources that exist on the map. + +It can also calculate an estimate of resources available in the selected embark +area. + +Usage +----- + +:: + + prospect [all|hell] [] + +By default, only the visible part of the map is scanned. Include the ``all`` +keyword if you want ``prospect`` to scan the whole map as if it were revealed. +Use ``hell`` instead of ``all`` if you also want to see the Z range of HFS +tubes in the 'features' report section. + +Examples +-------- + +``prospect all`` + Shows the entire report for the entire map. + +``prospect hell --show layers,ores,veins`` + Shows only the layers, ores, and other vein stone report sections, and + includes information on HFS tubes (if run on a fortress map and not the + pre-embark screen). + +``prospect all -sores`` + Show only information about ores for the pre-embark or fortress map report. + +Options +------- + +``-s``, ``--show `` + Shows only the named comma-separated list of report sections. Report section + names are: summary, liquids, layers, features, ores, gems, veins, shrubs, + and trees. If run during pre-embark, only the layers, ores, gems, and veins + report sections are available. +``-v``, ``--values`` + Includes material value in the output. Most useful for the 'gems' report + section. + +Pre-embark estimate +------------------- + +If prospect is called during the embark selection screen, it displays an +estimate of layer stone availability. If the ``all`` keyword is specified, it +also estimates ores, gems, and vein material. The estimate covers all tiles of +the embark rectangle. + +.. note:: + + The results of pre-embark prospect are an *estimate*, and can at best be + expected to be somewhere within +/- 30% of the true amount; sometimes it + does a lot worse. In particular, it is not clear how to precisely compute + how many soil layers there will be in a given embark tile, so it can report + a whole extra layer, or omit one that is actually present. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst new file mode 100644 index 000000000..5512a428b --- /dev/null +++ b/docs/plugins/regrass.rst @@ -0,0 +1,18 @@ +regrass +======= + +.. dfhack-tool:: + :summary: Regrow all the grass. + :tags: adventure fort armok animals map + +Use this command if your grazers have eaten everything down to the dirt. + +Usage +----- + +:: + + regrass [max] + +Specify the 'max' keyword to pack more grass onto a tile than what the game +normally allows to give your grazers extra chewing time. diff --git a/docs/plugins/rename.rst b/docs/plugins/rename.rst new file mode 100644 index 000000000..137cf56a4 --- /dev/null +++ b/docs/plugins/rename.rst @@ -0,0 +1,33 @@ +rename +====== + +.. dfhack-tool:: + :summary: Easily rename things. + :tags: adventure fort productivity buildings stockpiles units + +Use `gui/rename` for an in-game interface. + +Usage +----- + +``rename squad ""`` + Rename the indicated squad. The ordinal is the number that corresponds to + the list of squads in the squads menu (:kbd:`s`). The first squad is ordinal + ``1``. +``rename hotkey ""`` + Rename the indicated hotkey. The ordinal the the number that corresponds to + the list of hotkeys in the hotkeys menu (:kbd:`H`). The first hotkey is + ordinal ``1``. +``rename unit ""`` + Give the selected unit the given nickname. +``rename unit-profession ""`` + Give the selected unit the given profession name. +``rename building ""`` + Set a custom name to the selected building. The building must be a + stockpile, workshop, furnace, trap, siege engine, or activity zone. + +Example +------- + +``rename squad 1 "The Whiz Bonkers"`` + Rename the first squad to The Whiz Bonkers. diff --git a/docs/plugins/rendermax.rst b/docs/plugins/rendermax.rst new file mode 100644 index 000000000..ee909fd9a --- /dev/null +++ b/docs/plugins/rendermax.rst @@ -0,0 +1,35 @@ +rendermax +========= + +.. dfhack-tool:: + :summary: Modify the map lighting. + :tags: adventure fort gameplay graphics + +This plugin provides a collection of OpenGL lighting filters that affect how the +map is drawn to the screen. + +Usage +----- + +``rendermax light`` + Light the map tiles realistically. Outside tiles are light during the day + and dark at night. Inside tiles are always dark unless a nearby unit is + lighting it up, as if they were carrying torches. +``rendermax light sun |cycle`` + Set the outside lighting to correspond with the specified day hour (1-24), + or specify ``cycle`` to have the lighting follow the sun (which is the + default). +``rendermax light reload`` + Reload the lighting settings file. +``rendermax trippy`` + Randomize the color of each tile. Used for fun, or testing. +``rendermax disable`` + Disable any ``rendermax`` lighting filters that are currently active. + +An image showing lava and dragon breath. Not pictured here: sunlight, shining +items/plants, materials that color the light etc. + +.. image:: ../images/rendermax.png + +For better visibility, try changing the black color in palette to non totally +black. See :forums:`128487` for more info. diff --git a/docs/plugins/resume.rst b/docs/plugins/resume.rst new file mode 100644 index 000000000..af3fe161d --- /dev/null +++ b/docs/plugins/resume.rst @@ -0,0 +1,23 @@ +resume +====== + +.. dfhack-tool:: + :summary: Color planned buildings based on their suspend status. + :tags: fort productivity interface jobs + :no-command: + +.. dfhack-command:: resume + :summary: Resume all suspended building jobs. + +When enabled, this plugin will display a colored 'X' over suspended buildings. +When run as a command, it can resume all suspended building jobs, allowing you +to quickly recover if a bunch of jobs were suspended due to the workers getting +scared off by wildlife or items temporarily blocking building sites. + +Usage +----- + +:: + + enable resume + resume all diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst new file mode 100644 index 000000000..bbcc07b6b --- /dev/null +++ b/docs/plugins/reveal.rst @@ -0,0 +1,55 @@ +.. _revflood: + +reveal +====== + +.. dfhack-tool:: + :summary: Reveals the map. + :tags: adventure fort armok inspection map + +.. dfhack-command:: unreveal + :summary: Hides previously hidden tiles again. + +.. dfhack-command:: revforget + :summary: Discard records about what was visible before revealing the map. + +.. dfhack-command:: revtoggle + :summary: Switch between reveal and unreveal. + +.. dfhack-command:: revflood + :summary: Hide everything, then reveal tiles with a path to the cursor. + +.. dfhack-command:: nopause + :summary: Disable pausing. + +This reveals all z-layers in fort mode. It also works in adventure mode, but any +of its effects are negated once you move. When you use it this way, you don't +need to run ``unreveal`` to hide the map again. + +Usage +----- + +``reveal [hell|demon]`` + Reveal the whole map. If ``hell`` is specified, also reveal HFS areas, but + you are required to run ``unreveal`` before unpausing is allowed in order + to prevent the demons from spawning. If you really want to unpause with hell + revealed, specify ``demon`` instead of ``hell``. +``unreveal`` + Reverts the effects of ``reveal``. +``revtoggle`` + Switches between ``reveal`` and ``unreveal``. Convenient to bind to a + hotkey. +``revforget`` + Discard info about what was visible before revealing the map. Only useful + 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 + :bug:`1871`. +``nopause 1|0`` + Disables pausing (both manual and automatic) with the exception of the pause + forced by `reveal` ``hell``. This is nice for digging under rivers. Use + ``nopause 1`` to prevent pausing and ``nopause 0`` to allow pausing like + normal. diff --git a/docs/plugins/ruby.rst b/docs/plugins/ruby.rst new file mode 100644 index 000000000..f1fafbb36 --- /dev/null +++ b/docs/plugins/ruby.rst @@ -0,0 +1,31 @@ +.. _rb: + +ruby +==== + +.. dfhack-tool:: + :summary: Allow Ruby scripts to be executed as DFHack commands. + :tags: dev + :no-command: + +.. dfhack-command:: rb + :summary: Eval() a ruby string. + +.. dfhack-command:: rb_eval + :summary: Eval() a ruby string. + +Usage +----- + +:: + + enable ruby + rb "ruby expression" + rb_eval "ruby expression" + :rb ruby expression + +Example +------- + +``:rb puts df.unit_find(:selected).name`` + Print the name of the selected unit. diff --git a/docs/plugins/search.rst b/docs/plugins/search.rst new file mode 100644 index 000000000..e588e3196 --- /dev/null +++ b/docs/plugins/search.rst @@ -0,0 +1,52 @@ +.. _search-plugin: + +search +====== + +.. dfhack-tool:: + :summary: Adds search capabilities to the UI. + :tags: fort productivity interface + :no-command: + +Search options are added to the Stocks, Animals, Trading, Stockpile, Noble +assignment candidates), Military (position candidates), Burrows (unit list), +Rooms, Announcements, Job List, and Unit List screens all get hotkeys that allow +you to dynamically filter the displayed lists. + +Usage +----- + +:: + + enable search + +.. image:: ../images/search.png + +Searching works the same way as the search option in :guilabel:`Move to Depot`. +You will see the Search option displayed on screen with a hotkey +(usually :kbd:`s`). Pressing it lets you start typing a query and the relevant +list will start filtering automatically. + +Pressing :kbd:`Enter`, :kbd:`Esc` or the arrow keys will return you to browsing +the now filtered list, which still functions as normal. You can clear the filter +by either going back into search mode and backspacing to delete it, or pressing +the "shifted" version of the search hotkey while browsing the list (e.g. if the +hotkey is :kbd:`s`, then hitting :kbd:`Shift`:kbd:`s` will clear any filter). + +Leaving any screen automatically clears the filter. + +In the Trade screen, the actual trade will always only act on items that are +actually visible in the list; the same effect applies to the Trade Value numbers +displayed by the screen. Because of this, the :kbd:`t` key is blocked while +search is active, so you have to reset the filters first. Pressing +:kbd:`Alt`:kbd:`C` will clear both search strings. + +In the stockpile screen the option only appears if the cursor is in the +rightmost list: + +.. image:: ../images/search-stockpile.png + +Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only on items +actually shown in the rightmost list, so it is possible to select only fat or +tallow by forbidding fats, then searching for fat/tallow, and using Permit Fats +again while the list is filtered. diff --git a/docs/plugins/seedwatch.rst b/docs/plugins/seedwatch.rst new file mode 100644 index 000000000..b41f3a977 --- /dev/null +++ b/docs/plugins/seedwatch.rst @@ -0,0 +1,46 @@ +seedwatch +========= + +.. dfhack-tool:: + :summary: Manages seed and plant cooking based on seed stock levels. + :tags: fort auto plants + +Each seed type can be assigned a target. If the number of seeds of that type +falls below that target, then the plants and seeds of that type will be excluded +from cookery. If the number rises above the target + 20, then cooking will be +allowed. + +The plugin needs a fortress to be loaded and will deactivate automatically +otherwise. You have to reactivate with ``enable seedwatch`` after you load a +fort. + +Usage +----- + +``enable seedwatch`` + Start managing seed and plant cooking. By default, no types are watched. + You have to add them with further ``seedwatch`` commands. +``seedwatch `` + Adds the specified type to the watchlist (if it's not already there) and + sets the target number of seeds to the specified number. You can pass the + keyword ``all`` instead of a specific type to set the target for all types. +``seedwatch `` + Removes the specified type from the watch list. +``seedwatch clear`` + Clears all types from the watch list. +``seedwatch info`` + Display whether seedwatch is enabled and prints out the watch list. + +To print out a list of all plant types, you can run this command:: + + devel/query --table df.global.world.raws.plants.all --search ^id --maxdepth 1 + +Examples +-------- + +``seedwatch all 30`` + Adds all seeds to the watch list and sets the targets to 30. +``seedwatch MUSHROOM_HELMET_PLUMP 50`` + Add Plump Helmets to the watch list and sets the target to 50. +``seedwatch MUSHROOM_HELMET_PLUMP`` + removes Plump Helmets from the watch list. diff --git a/docs/plugins/showmood.rst b/docs/plugins/showmood.rst new file mode 100644 index 000000000..e12770341 --- /dev/null +++ b/docs/plugins/showmood.rst @@ -0,0 +1,13 @@ +showmood +======== + +.. dfhack-tool:: + :summary: Shows all items needed for the active strange mood. + :tags: fort armok inspection jobs units + +Usage +----- + +:: + + showmood diff --git a/docs/plugins/siege-engine.rst b/docs/plugins/siege-engine.rst new file mode 100644 index 000000000..d01fc524c --- /dev/null +++ b/docs/plugins/siege-engine.rst @@ -0,0 +1,25 @@ +siege-engine +============ + +.. dfhack-tool:: + :summary: Extend the functionality and usability of siege engines. + :tags: fort gameplay buildings + :no-command: + +Siege engines in DF haven't been updated since the game was 2D, and can only aim +in four directions. To make them useful above-ground, this plugin allows you to: + +* link siege engines to stockpiles +* restrict operator skill levels (like workshops) +* load any object into a catapult, not just stones +* aim at a rectangular area in any direction, and across Z-levels + +Usage +----- + +:: + + enable siege-engine + +You can use the new features by selecting a built siege engine and running +`gui/siege-engine`. diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst new file mode 100644 index 000000000..f5bce9bae --- /dev/null +++ b/docs/plugins/sort.rst @@ -0,0 +1,60 @@ +sort +==== + +.. dfhack-tool:: + :summary: Sort lists shown in the DF interface. + :tags: fort productivity interface + :no-command: + +.. dfhack-command:: sort-items + :summary: Sort the visible item list. + +.. dfhack-command:: sort-units + :summary: Sort the visible unit list. + +Usage +----- + +:: + + sort-items [ ...] + sort-units [ ...] + +Both commands sort the visible list using the given sequence of comparisons. +Each property can be prefixed with a ``<`` or ``>`` character to indicate +whether elements that don't have the given property defined go first or last +(respectively) in the sorted list. + +Examples +-------- + +``sort-items material type quality`` + Sort a list of items by material, then by type, then by quality +``sort-units profession name`` + Sort a list of units by profession, then by name + +Properties +---------- + +Items can be sorted by the following properties: + +- ``type`` +- ``description`` +- ``base_quality`` +- ``quality`` +- ``improvement`` +- ``wear`` +- ``material`` + +Units can be sorted by the following properties: + +- ``name`` +- ``age`` +- ``arrival`` +- ``noble`` +- ``profession`` +- ``profession_class`` +- ``race`` +- ``squad`` +- ``squad_position`` +- ``happiness`` diff --git a/docs/plugins/spectate.rst b/docs/plugins/spectate.rst new file mode 100644 index 000000000..1ca3efeb8 --- /dev/null +++ b/docs/plugins/spectate.rst @@ -0,0 +1,48 @@ +spectate +======== + +.. dfhack-tool:: + :summary: Automatically follow productive dwarves. + :tags: fort interface + +Usage +----- + +:: + + enable spectate + spectate set + spectate enable|disable + + +When enabled, the plugin will automatically switch which dwarf is being +followed periodically, preferring dwarves on z-levels with the highest +job activity. + +Changes to plugin settings will be saved per world. Whether the plugin itself +is enabled or not is not saved. + +Examples +-------- + +``spectate`` + The plugin reports its configured status. + +``spectate enable auto-unpause`` + Enable the spectate plugin to automatically dismiss pause events caused + by the game. Siege events are one example of such a game event. + +``spectate set tick-threshold 50`` + Set the tick interval the followed dwarf can be changed at back to its + default value. + +Features +-------- +:focus-jobs: Toggle whether the plugin should always be following a job. (default: disabled) +:auto-unpause: Toggle auto-dismissal of game pause events. (default: disabled) +:auto-disengage: Toggle auto-disengagement of plugin through player intervention while unpaused. (default: disabled) + +Settings +-------- +:tick-threshold: Set the plugin's tick interval for changing the followed dwarf. + Acts as a maximum follow time when used with focus-jobs enabled. (default: 50) diff --git a/docs/plugins/steam-engine.rst b/docs/plugins/steam-engine.rst new file mode 100644 index 000000000..6560ae714 --- /dev/null +++ b/docs/plugins/steam-engine.rst @@ -0,0 +1,89 @@ +steam-engine +============ + +.. dfhack-tool:: + :summary: Allow modded steam engine buildings to function. + :tags: fort gameplay buildings + :no-command: + +The steam-engine plugin detects custom workshops with the string +``STEAM_ENGINE`` in their token, and turns them into real steam engines! + +The plugin auto-enables itself when it detects the relevant tags in the world +raws. It does not need to be enabled with the `enable` command. + +Rationale +--------- +The vanilla game contains only water wheels and windmills as sources of power, +but windmills give relatively little power, and water wheels require flowing +water, which must either be a real river and thus immovable and +limited in supply, or actually flowing and thus laggy. + +Compared to the +:wiki:`dwarven water reactor ` exploit, +steam engines make a lot of sense! + +Construction +------------ +The workshop needs water as its input, which it takes via a passable floor tile +below it, like usual magma workshops do. The magma version also needs magma. + +Due to DF game limits, the workshop will collapse over true open space. However, +down stairs are passable but support machines, so you can use them. + +After constructing the building itself, machines can be connected to the edge +tiles that look like gear boxes. Their exact position is extracted from the +workshop raws. + +Like with collapse above, due to DF game limits the workshop can only +immediately connect to machine components built AFTER it. This also means that +engines cannot be chained without intermediate axles built after both engines. + +Operation +--------- +In order to operate the engine, queue the Stoke Boiler job (optionally on +repeat). A furnace operator will come, possibly bringing a bar of fuel, and +perform it. As a result, a "boiling water" item will appear in the :kbd:`t` +view of the workshop. + +.. note:: + + The completion of the job will actually consume one unit + of the appropriate liquids from below the workshop. This means + that you cannot just raise 7 units of magma with a piston and + have infinite power. However, liquid consumption should be slow + enough that water can be supplied by a pond zone bucket chain. + +Every such item gives 100 power, up to a limit of 300 for coal, or 500 for a +magma engine. The building can host twice that amount of items to provide longer +autonomous running. When the boiler gets filled to capacity, all queued jobs are +suspended. Once it drops back to 3+1 or 5+1 items, they are re-enabled. + +While the engine is providing power, steam is being consumed. The consumption +speed includes a fixed 10% waste rate, and the remaining 90% is applied +proportionally to the actual load in the machine. With the engine at nominal 300 +power with 150 load in the system, it will consume steam for actual +300*(10% + 90%*150/300) = 165 power. + +A masterpiece mechanism and chain will decrease the mechanical power drawn by +the engine itself from 10 to 5. A masterpiece barrel decreases waste rate by 4%. +A masterpiece piston and pipe decrease it by further 4%, and also decrease the +whole steam use rate by 10%. + +Explosions +---------- +The engine must be constructed using barrel, pipe, and piston from fire-safe, +or, in the magma version, magma-safe metals. + +During operation, weak parts gradually wear out, and eventually the engine +explodes. It should also explode if toppled during operation by a building +destroyer or a tantruming dwarf. + +Save files +---------- +It should be safe to load and view engine-using fortresses from a DF version +without DFHack installed, except that in such case the engines, of course, won't +work. However actually making modifications to them or machines they connect to +(including by pulling levers) can easily result in inconsistent state once this +plugin is available again. The effects may be as weird as negative power being +generated. diff --git a/docs/plugins/stockflow.rst b/docs/plugins/stockflow.rst new file mode 100644 index 000000000..9ce91eaca --- /dev/null +++ b/docs/plugins/stockflow.rst @@ -0,0 +1,35 @@ +stockflow +========= + +.. dfhack-tool:: + :summary: Queue manager jobs based on free space in stockpiles. + :tags: 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 +space. + +When the plugin is enabled, the :kbd:`q` menu of each stockpile will have two +new options: + +* :kbd:`j`: Select a job to order, from an interface like the manager's screen. +* :kbd:`J`: Cycle between several options for how many such jobs to order. + +Whenever the bookkeeper updates stockpile records, new work orders will +be placed on the manager's queue for each such selection, reduced by the +number of identical orders already in the queue. + +This plugin is similar to `workflow`, but uses stockpiles to manage job triggers +instead of abstract stock quantities. + +Usage +----- + +``enable stockflow`` + Enable the plugin. +``stockflow status`` + Display whether the plugin is enabled. +``stockflow list`` + List any work order settings for your stockpiles. +``stockflow fast`` + Enqueue orders once per day instead of waiting for the bookkeeper. diff --git a/docs/plugins/stockpiles.rst b/docs/plugins/stockpiles.rst new file mode 100644 index 000000000..e7f02f970 --- /dev/null +++ b/docs/plugins/stockpiles.rst @@ -0,0 +1,53 @@ +.. _stocksettings: + +stockpiles +========== + +.. dfhack-tool:: + :summary: Import and export stockpile settings. + :tags: fort design productivity stockpiles + :no-command: + +.. dfhack-command:: copystock + :summary: Copies the configuration of the selected stockpile. + +.. dfhack-command:: savestock + :summary: Exports the configuration of the selected stockpile. + +.. dfhack-command:: loadstock + :summary: Imports the configuration of the selected stockpile. + +When the plugin is enabled, the :kbd:`q` menu of each stockpile will have an +option for saving or loading the stockpile settings. See `gui/stockpiles` for +an in-game interface. + +Usage +----- + +``enable stockpiles`` + Add a hotkey that you can hit to easily save and load settings from + stockpiles selected in :kbd:`q` mode. +``copystock`` + Copies the parameters of the currently highlighted stockpile to the custom + stockpile settings and switches to custom stockpile placement mode, + effectively allowing you to copy/paste stockpiles easily. +``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. + +Filenames with spaces are not supported. Generated materials, divine metals, +etc. are not saved as they are different in every world. + +Examples +-------- + +``savestock food_settings.dfstock`` + Export the stockpile settings for the stockpile currently selected in + :kbd:`q` mode to a file named ``food_settings.dfstock``. +``loadstock food_settings.dfstock`` + Set the selected stockpile settings to those saved in the + ``food_settings.dfstock`` file. diff --git a/docs/plugins/stocks.rst b/docs/plugins/stocks.rst new file mode 100644 index 000000000..03eb3a72c --- /dev/null +++ b/docs/plugins/stocks.rst @@ -0,0 +1,24 @@ +stocks +====== + +.. dfhack-tool:: + :summary: Enhanced fortress stock management interface. + :tags: fort productivity items + +When the plugin is enabled, two new hotkeys become available: + +* :kbd:`e` on the vanilla DF stocks screen (:kbd:`z` and then select Stocks) + will launch the fortress-wide stock management screen. +* :kbd:`i` when a stockpile is selected in :kbd:`q` mode will launch the + stockpile inventory management screen. + +Usage +----- + +:: + + enable stocks + stocks show + +Running ``stocks show`` will bring you to the fortress-wide stock management +screen from wherever you are. diff --git a/docs/plugins/stonesense.rst b/docs/plugins/stonesense.rst new file mode 100644 index 000000000..23c20ef0a --- /dev/null +++ b/docs/plugins/stonesense.rst @@ -0,0 +1,82 @@ +stonesense +========== + +.. dfhack-tool:: + :summary: A 3D isometric visualizer. + :tags: adventure fort graphics map + +.. dfhack-command:: ssense + :summary: An alias for stonesense. + +Usage +----- + +``stonesense`` or ``ssense`` + Open the visualiser in a new window. +``ssense overlay`` + Overlay DF window, replacing the map area. + +The viewer window has read-only access to the game, and can follow the game view +or be moved independently. Configuration for stonesense can be set in the +``stonesense/init.txt`` file in your DF game directory. If the window refresh +rate is too low, change ``SEGMENTSIZE_Z`` to ``2`` in this file, and if you are +unable to see the edges of the map with the overlay active, try decreasing the +value for ``SEGMENTSIZE_XY`` -- normal values are ``50`` to ``80``, depending +on your screen resolution. + +If you replace the map section of your DF window with ``ssense overlay``, be +aware that it's not (yet) suitable for use as your only interface. Use DF's +``[PRINT_MODE:2D]`` init option (in ``data/init/init.txt``) for stability. + +.. figure:: ../images/stonesense-roadtruss.jpg + :align: center + :target: http://www.bay12forums.com/smf/index.php?topic=48172.msg3198664#msg3198664 + + The above-ground part of the fortress *Roadtruss*. + +Controls +-------- +Mouse controls are hard-coded and cannot be changed. + +:Left click: Move debug cursor (if available) +:Right click: Recenter screen +:Scrollwheel: Move up and down +:Ctrl-Scroll: Increase/decrease Z depth shown + +Follow mode makes the Stonesense view follow the location of the DF +window. The offset can be adjusted by holding :kbd:`Ctrl` while using the +keyboard window movement keys. When you turn on cursor follow mode, the +Stonesense debug cursor will follow the DF cursor when the latter exists. + +You can take screenshots with :kbd:`F5`, larger screenshots with +:kbd:`Ctrl`:kbd:`F5`, and screenshot the whole map at full resolution with +:kbd:`Ctrl`:kbd:`Shift`:kbd:`F5`. Screenshots are saved to the DF directory. +Note that feedback is printed to the DFHack console, and you may need +to zoom out before taking very large screenshots. + +See ``stonesense/keybinds.txt`` to learn or set keybindings, including +zooming, changing the dimensions of the rendered area, toggling various +views, fog, and rotation. Here's the important section: + +.. include:: ../../plugins/stonesense/resources/keybinds.txt + :literal: + :end-before: VALID ACTIONS: + +Known Issues +------------ +If Stonesense gives an error saying that it can't load +:file:`creatures/large_256/*.png`, your video card cannot handle the high +detail sprites used. Either open :file:`creatures/init.txt` and remove the +line containing that folder, or :dffd:`use these smaller sprites <6096>`. + +Stonesense requires working graphics acceleration, and we recommend +at least a dual core CPU to avoid slowing down your game of DF. + +Useful links +------------ +- :forums:`Official Stonesense thread <106497>` for feedback, + questions, requests or bug reports +- :forums:`Screenshots thread <48172>` +- :wiki:`Main wiki page ` +- :wiki:`How to add content ` +- `Stonesense on Github `_ diff --git a/docs/plugins/strangemood.rst b/docs/plugins/strangemood.rst new file mode 100644 index 000000000..7aabda93d --- /dev/null +++ b/docs/plugins/strangemood.rst @@ -0,0 +1,44 @@ +strangemood +=========== + +.. dfhack-tool:: + :summary: Trigger a strange mood. + :tags: fort armok units + +Usage +----- + +:: + + stangemood [] + +Examples +-------- + +``strangemood -force -unit -type secretive -skill armorsmith`` + Trigger a strange mood for the selected unit that will cause them to become + a legendary armorsmith. + +Options +------- + +``-force`` + Ignore normal strange mood preconditions (no recent mood, minimum moodable + population, artifact limit not reached, etc.). +``-unit`` + Make the strange mood strike the selected unit instead of picking one + randomly. Unit eligibility is still enforced (unless ``-force`` is also + specified). +``-type `` + Force the mood to be of a particular type instead of choosing randomly based + on happiness. Valid values are "fey", "secretive", "possessed", "fell", and + "macabre". +``-skill `` + Force the mood to use a specific skill instead of choosing the highest + moodable skill. Valid values are "miner", "carpenter", "engraver", "mason", + "tanner", "weaver", "clothier", "weaponsmith", "armorsmith", "metalsmith", + "gemcutter", "gemsetter", "woodcrafter", "stonecrafter", "metalcrafter", + "glassmaker", "leatherworker", "bonecarver", "bowyer", and "mechanic". + +Known limitations: if the selected unit is currently performing a job, the mood +will not be triggered. diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst new file mode 100644 index 000000000..af7d253b2 --- /dev/null +++ b/docs/plugins/tailor.rst @@ -0,0 +1,41 @@ +tailor +====== + +.. dfhack-tool:: + :summary: Automatically keep your dwarves in fresh clothing. + :tags: fort auto workorders + +Whenever the bookkeeper updates stockpile records, this plugin will scan the +fort. If there are fresh cloths available, dwarves who are wearing tattered +clothing will have their rags confiscated (in the same manner as the +`cleanowned` tool) so that they'll reequip with replacement clothes. + +If there are not enough clothes available, manager orders will be generated +to manufacture some more. ``tailor`` will intelligently create orders using +raw materials that you have on hand in the fort. For example, if you have +lots of silk, but no cloth, then ``tailor`` will order only silk clothing to +be made. + +Usage +----- + +:: + + enable tailor + tailor status + tailor materials [ ...] + +By default, ``tailor`` will prefer using materials in this order:: + + silk cloth yarn leather + +but you can use the ``tailor materials`` command to restrict which materials +are used, and in what order. + +Example +------- + +``tailor materials silk cloth yarn`` + Restrict the materials used for automatically manufacturing clothing to + silk, cloth, and yarn, preferred in that order. This saves leather for + other uses, like making armor. diff --git a/docs/plugins/tiletypes.rst b/docs/plugins/tiletypes.rst new file mode 100644 index 000000000..7b93dd6de --- /dev/null +++ b/docs/plugins/tiletypes.rst @@ -0,0 +1,160 @@ +.. _tiletypes-here: +.. _tiletypes-here-point: + +tiletypes +========= + +.. dfhack-tool:: + :summary: Paints tiles of specified types onto the map. + :tags: adventure fort armok map + +.. dfhack-command:: tiletypes-command + :summary: Run tiletypes commands. + +.. dfhack-command:: tiletypes-here + :summary: Paint map tiles starting from the cursor. + +.. dfhack-command:: tiletypes-here-point + :summary: Paint the map tile under the cursor. + +You can use the `probe` command to discover properties of existing tiles that +you'd like to copy. If you accidentally paint over a vein that you want back, +`fixveins` may help. + +The tool works with a brush, a filter, and a paint specification. The brush +determines the shape of the area to affect, the filter selects which tiles to +affect, and the paint specification determines how to affect those tiles. + +Both paint and filter can have many different properties, like general shape +(WALL, FLOOR, etc.), general material (SOIL, STONE, MINERAL, etc.), specific +materials (MICROCLINE, MARBLE, etc.), state of 'designated', 'hidden', and +'light' flags, and many others. + +Usage +----- + +``tiletypes`` + Start the interactive terminal prompt where you can iteratively modify + the brush, filter, and paint specification and get help on syntax + elements. When in the interactive prompt, type ``quit`` to get out. +``tiletypes-command [; ...]`` + Run ``tiletypes`` commands from outside the interactive prompt. You can + use this form from hotkeys or `dfhack-run` to set specific tiletypes + properties. You can run multiple commands on one line by separating them + with :literal:`\ ; \ ` -- that's a semicolon with a space on either side. + See the Commands_ section below for an overview of commands you can run. +``tiletypes-here []`` + Apply the current options set in ``tiletypes`` and/or ``tiletypes-command`` + at the in-game cursor position, including the brush. Can be used from a + hotkey. +``tiletypes-here-point []`` + Apply the current options set in ``tiletypes`` and/or ``tiletypes-command`` + at the in-game cursor position to a single tile (ignoring brush settings). + Can be used from a hotkey. + +Examples +-------- + +``tiletypes-command filter material STONE ; f shape WALL ; paint shape FLOOR`` + Turn all stone walls into floors, preserving the material. +``tiletypes-command p any ; p s wall ; p sp normal`` + Clear the paint specification and set it to unsmoothed walls. +``tiletypes-command f any ; p stone marble ; p sh wall ; p sp normal ; r 10 10`` + Prepare to paint a 10x10 area of marble walls, ready for harvesting for + flux. +``tiletypes-command f any ; f designated 1 ; p any ; p hidden 0 ; block ; run`` + Set the filter to match designated tiles, the paint specification to unhide + them, and the brush to cover all tiles in the current block. Then run itThis is useful + for unhiding tiles you wish to dig out of an aquifer so the game doesn't + pause and undesignate adjacent tiles every time a new damp tile is + "discovered". + +Options +------- + +``-c``, ``--cursor ,,`` + Use the specified map coordinates instead of the current cursor position. If + this option is specified, then an active game map cursor is not necessary. +``-q``, ``--quiet`` + Suppress non-error status output. + +Commands +-------- + +Commands can set the brush or modify the filter or paint options. When at the +interactive ``tiletypes>`` prompt, the command ``run`` (or hitting enter on an +empty line) will apply the current filter and paint specification with the +current brush at the current cursor position. The command ``quit`` will exit. + +Brush commands +`````````````` + +``p``, ``point`` + Use the point brush. +``r``, ``range []`` + Use the range brush with the specified width, height, and depth. If not + specified, depth is 1, meaning just the current z-level. The range starts at + the position of the cursor and goes to the east, south and up (towards the + sky). +``block`` + Use the block brush, which includes all tiles in the 16x16 block that + includes the cursor. +``column`` + Use the column brush, which ranges from the current cursor position to the + first solid tile above it. This is useful for filling the empty space in a + cavern. + +Filter and paint commands +````````````````````````` + +The general forms for modifying the filter or paint specification are: + +``f``, ``filter `` + Modify the filter. +``p``, ``paint `` + Modify the paint specification. + +The options identify the property of the tile and the value of that property: + +``any`` + Reset to default (no filter/paint). +``s``, ``sh``, ``shape `` + Tile shape information. Run ``:lua @df.tiletype_shape`` to see valid shapes, + or use a shape of ``any`` to clear the current setting. +``m``, ``mat``, ``material `` + Tile material information. Run ``:lua @df.tiletype_material`` to see valid + materials, or use a material of ``any`` to clear the current setting. +``sp``, ``special `` + Tile special information. Run ``:lua @df.tiletype_special`` to see valid + special values, or use a special value of ``any`` to clear the current + setting. +``v``, ``var``, ``variant `` + Tile variant information. Run ``:lua @df.tiletype_variant`` to see valid + variant values, or use a variant value of ``any`` to clear the current + setting. +``a``, ``all [] [] [] []`` + Set values for any or all of shape, material, special, and/or variant, in + any order. +``d``, ``designated 0|1`` + Only useful for the filter, since you can't "paint" designations. +``h``, ``hidden 0|1`` + Whether a tile is hidden. A value of ``0`` means "revealed". +``l``, ``light 0|1`` + Whether a tile is marked as "Light". A value of ``0`` means "dark". +``st``, ``subterranean 0|1`` + Whether a tile is marked as "Subterranean". +``sv``, ``skyview 0|1`` + Whether a tile is marked as "Outside". A value of ``0`` means "inside". +``aqua``, ``aquifer 0|1`` + Whether a tile is marked as an aquifer. +``stone `` + Set a particular type of stone, creating veins as required. To see a list of + valid stone types, run: ``:lua for _,mat in ipairs(df.global.world.raws.inorganics) do if mat.material.flags.IS_STONE and not mat.material.flags.NO_STONE_STOCKPILE then print(mat.id) end end`` + Note that this command paints under ice and constructions, instead of + overwriting them. Also note that specifying a specific ``stone`` will cancel + out anything you have specified for ``material``, and vice-versa. +``veintype `` + Set a particular vein type for the ``stone`` option to take advantage of the + different boulder drop rates. To see valid vein types, run + ``:lua @df.inclusion_type``, or use vein type ``CLUSTER`` to reset to the + default. diff --git a/docs/plugins/title-folder.rst b/docs/plugins/title-folder.rst new file mode 100644 index 000000000..a6c7ee63c --- /dev/null +++ b/docs/plugins/title-folder.rst @@ -0,0 +1,14 @@ +title-folder +============= + +.. dfhack-tool:: + :summary: Displays the DF folder name in the window title bar. + :tags: interface + :no-command: + +Usage +----- + +:: + + enable title-folder diff --git a/docs/plugins/title-version.rst b/docs/plugins/title-version.rst new file mode 100644 index 000000000..307a4da18 --- /dev/null +++ b/docs/plugins/title-version.rst @@ -0,0 +1,14 @@ +title-version +============= + +.. dfhack-tool:: + :summary: Displays the DFHack version on DF's title screen. + :tags: interface + :no-command: + +Usage +----- + +:: + + enable title-version diff --git a/docs/plugins/trackstop.rst b/docs/plugins/trackstop.rst new file mode 100644 index 000000000..3ca47f465 --- /dev/null +++ b/docs/plugins/trackstop.rst @@ -0,0 +1,19 @@ +trackstop +========= + +.. dfhack-tool:: + :summary: Add dynamic configuration options for track stops. + :tags: fort gameplay buildings + :no-command: + +When enabled, this plugin adds a :kbd:`q` menu for track stops, which is +completely blank in vanilla DF. This allows you to view and/or change the track +stop's friction and dump direction settings, using the keybindings from the +track stop building interface. + +Usage +----- + +:: + + enable trackstop diff --git a/docs/plugins/tubefill.rst b/docs/plugins/tubefill.rst new file mode 100644 index 000000000..80282f6d9 --- /dev/null +++ b/docs/plugins/tubefill.rst @@ -0,0 +1,19 @@ +tubefill +======== + +.. dfhack-tool:: + :summary: Replenishes mined-out adamantine. + :tags: fort armok map + +Veins that were originally hollow will be left alone. + +Usage +----- + +:: + + tubefill [hollow] + +Specify ``hollow`` to fill in naturally hollow veins too, but be aware that this +will trigger a demon invasion on top of your miner when you dig into the region +that used to be hollow. You have been warned! diff --git a/docs/plugins/tweak.rst b/docs/plugins/tweak.rst new file mode 100644 index 000000000..aa1ad578b --- /dev/null +++ b/docs/plugins/tweak.rst @@ -0,0 +1,140 @@ +tweak +===== + +.. dfhack-tool:: + :summary: A collection of tweaks and bugfixes. + :tags: adventure fort armok bugfix fps interface + +Usage +----- + +:: + + tweak [disable] + +Run the ``tweak`` command to run the tweak or enable its effects. For tweaks +that have persistent effects, append the ``disable`` keyword to disable them. + +One-shot commands: + +``clear-missing`` + Remove the missing status from the selected unit. This allows engraving + slabs for ghostly, but not yet found, creatures. +``clear-ghostly`` + Remove the ghostly status from the selected unit and mark it as dead. This + allows getting rid of bugged ghosts which do not show up in the engraving + slab menu at all, even after using ``clear-missing``. It works, but is + potentially very dangerous - so use with care. Probably (almost certainly) + it does not have the same effects like a proper burial. You've been warned. +``fixmigrant`` + Remove the resident/merchant flag from the selected unit. Intended to fix + bugged migrants/traders who stay at the map edge and don't enter your fort. + Only works for dwarves (or generally the player's race in modded games). + Do NOT abuse this for 'real' caravan merchants (if you really want to kidnap + them, use ``tweak makeown`` instead, otherwise they will have their clothes + set to forbidden). +``makeown`` + Force selected unit to become a member of your fort. Can be abused to grab + caravan merchants and escorts, even if they don't belong to the player's + race. Foreign sentients (humans, elves) can be put to work, but you can't + assign rooms to them and they don't show up in labor management programs + (like `manipulator` or Dwarf Therapist) because the game treats them like + pets. Grabbing draft animals from a caravan can result in weirdness + (animals go insane or berserk and are not flagged as tame), but you are + allowed to mark them for slaughter. Grabbing wagons results in some funny + spam, then they are scuttled. + +Commands that persist until disabled or DF quits: + +.. comment: please sort these alphabetically + +``adamantine-cloth-wear`` + Prevents adamantine clothing from wearing out while being worn + (:bug:`6481`). +``advmode-contained`` + Fixes custom reactions with container inputs in advmode + (:bug:`6202`) in advmode. The issue is that the screen tries to force you to + select the contents separately from the container. This forcefully skips + child reagents. +``block-labors`` + Prevents labors that can't be used from being toggled. +``burrow-name-cancel`` + Implements the "back" option when renaming a burrow, which currently does + nothing in vanilla DF (:bug:`1518`). +``cage-butcher`` + Adds an option to butcher units when viewing cages with :kbd:`q`. +``civ-view-agreement`` + Fixes overlapping text on the "view agreement" screen. +``condition-material`` + Fixes a crash in the work order condition material list (:bug:`9905`). +``craft-age-wear`` + Fixes crafted items not wearing out over time (:bug:`6003`). With this + tweak, items made from cloth and leather will gain a level of wear every 20 + years. +``do-job-now`` + Adds a job priority toggle to the jobs list. +``embark-profile-name`` + Allows the use of lowercase letters when saving embark profiles. +``eggs-fertile`` + Displays a fertility indicator on nestboxes. +``farm-plot-select`` + Adds "Select all" and "Deselect all" options to farm plot menus. +``fast-heat`` + Improves temperature update performance by ensuring that 1 degree of item + temperature is crossed in no more than specified number of frames when + updating from the environment temperature. This reduces the time it takes + for ``tweak stable-temp`` to stop updates again when equilibrium is + disturbed. +``fast-trade`` + Makes Shift-Down in the Move Goods to Depot and Trade screens toggle the + current item (fully, in case of a stack), and scroll down one line. Shift-Up + undoes the last Shift-Down by scrolling up one line and then toggle the item. +``fps-min`` + Fixes the in-game minimum FPS setting (:bug:`6277`). +``hide-priority`` + Adds an option to hide designation priority indicators. +``hotkey-clear`` + Adds an option to clear currently-bound hotkeys (in the :kbd:`H` menu). +``import-priority-category`` + When meeting with a liaison, makes Shift+Left/Right arrow adjust all items + in category when discussing an import agreement with the liaison. +``kitchen-prefs-all`` + Adds an option to toggle cook/brew for all visible items in kitchen + preferences. +``kitchen-prefs-color`` + Changes color of enabled items to green in kitchen preferences. +``kitchen-prefs-empty`` + Fixes a layout issue with empty kitchen tabs (:bug:`9000`). +``max-wheelbarrow`` + Allows assigning more than 3 wheelbarrows to a stockpile. +``military-color-assigned`` + Color squad candidates already assigned to other squads in yellow/green to + make them stand out more in the list. + + .. image:``../images/tweak-mil-color.png + +``military-stable-assign`` + Preserve list order and cursor position when assigning to squad, i.e. stop + the rightmost list of the Positions page of the military screen from + constantly resetting to the top. +``nestbox-color`` + Makes built nestboxes use the color of their material. +``partial-items`` + Displays percentages on partially-consumed items such as hospital cloth. +``pausing-fps-counter`` + Replace fortress mode FPS counter with one that stops counting when paused. +``reaction-gloves`` + Fixes reactions to produce gloves in sets with correct handedness + (:bug:`6273`). +``shift-8-scroll`` + Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of + scrolling the map. +``stable-cursor`` + Saves the exact cursor position between t/q/k/d/b/etc menus of fortress + mode, if the map view is near enough to its previous position. +``stone-status-all`` + Adds an option to toggle the economic status of all stones. +``title-start-rename`` + Adds a safe rename option to the title screen "Start Playing" menu. +``tradereq-pet-gender`` + Displays pet genders on the trade request screen. diff --git a/docs/plugins/workNow.rst b/docs/plugins/workNow.rst new file mode 100644 index 000000000..55004bca2 --- /dev/null +++ b/docs/plugins/workNow.rst @@ -0,0 +1,22 @@ +workNow +======= + +.. dfhack-tool:: + :summary: Reduce the time that dwarves idle after completing a job. + :tags: 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 new file mode 100644 index 000000000..f085a40da --- /dev/null +++ b/docs/plugins/workflow.rst @@ -0,0 +1,143 @@ +workflow +======== + +.. dfhack-tool:: + :summary: Manage automated item production rules. + :tags: fort auto jobs + +.. dfhack-command:: fix-job-postings + :summary: Fixes crashes caused by old versions of workflow. + +Manage repeat jobs according to stock levels. `gui/workflow` provides a simple +front-end integrated in the game UI. + +When the plugin is enabled, it protects all repeat jobs from removal. If they do +disappear due to any cause (raw materials not available, manual removal by the +player, etc.), they are immediately re-added to their workshop and suspended. + +If any constraints on item amounts are set, repeat jobs that produce that kind +of item are automatically suspended and resumed as the item amount goes above or +below the limit. + +There is a good amount of overlap between this plugin and the vanilla manager +workorders, and both systems have their advantages. Vanilla manager workorders +can be more expressive about when to enqueue jobs. For example, you can gate the +activation of a vanilla workorder based on availability of raw materials, which +you cannot do in ``workflow``. However, ``workflow`` is often more convenient +for quickly keeping a small stock of various items on hand without having to +configure all the vanilla manager options. Also see the `orders` plugin for +a library of manager orders that may make managing your stocks even more +convenient than ``workflow`` can. + +Usage +----- + +``enable workflow`` + Start monitoring for and managing workshop jobs that are set to repeat. +``workflow enable|disable drybuckets`` + Enables/disables automatic emptying of abandoned water buckets. +``workflow enable|disable auto-melt`` + Enables/disables automatic resumption of repeat melt jobs when there are + objects to melt. +``workflow count [gap]`` + Set a constraint, counting every stack as 1 item. If a gap is specified, + stocks are allowed to dip that many items below the target before relevant + jobs are resumed. +``workflow amount [gap]`` + Set a constraint, counting all items within stacks. If a gap is specified, + stocks are allowed to dip that many items below the target before relevant + jobs are resumed. +``workflow unlimit `` + Delete a constraint. +``workflow unlimit-all`` + Delete all constraints. +``workflow jobs`` + List workflow-controlled jobs (if in a workshop, filtered by it). +``workflow list`` + List active constraints, and their job counts. +``workflow list-commands`` + List active constraints as workflow commands that re-create them; this list + can be copied to a file, and then reloaded using the `script` built-in + command. +``fix-job-postings [dry-run]`` + Fixes crashes caused by the version of workflow released with DFHack + 0.40.24-r4. It will be run automatically if needed. If your save has never + been run with this version, you will never need this command. Specify the + ``dry-run`` keyword to see what this command would do without making any + changes to game state. + +Examples +-------- + +Keep metal bolts within 900-1000, and wood/bone within 150-200:: + + workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100 + workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50 + +Keep the number of prepared food & drink stacks between 90 and 120:: + + workflow count FOOD 120 30 + workflow count DRINK 120 30 + +Make sure there are always 25-30 empty bins/barrels/bags:: + + workflow count BIN 30 + workflow count BARREL 30 + workflow count BOX/CLOTH,SILK,YARN 30 + +Make sure there are always 15-20 coal and 25-30 copper bars:: + + workflow count BAR//COAL 20 + workflow count BAR//COPPER 30 + +Produce 15-20 gold crafts:: + + workflow count CRAFTS//GOLD 20 + +Collect 15-20 sand bags and clay boulders:: + + workflow count POWDER_MISC/SAND 20 + workflow count BOULDER/CLAY 20 + +Make sure there are always 80-100 units of dimple dye:: + + workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 + +.. note:: + + In order for this to work, you have to set the material of the PLANT input + on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the + `job item-material ` command. Otherwise the plugin won't be able to + deduce the output material. + +Maintain 10-100 locally-made crafts of exceptional quality:: + + workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 + +Constraint format +----------------- + +The constraint spec consists of 4 parts, separated with ``/`` characters:: + + ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] + +The first part is mandatory and specifies the item type and subtype, using the +raw tokens for items (the same syntax used for custom reaction inputs). For more +information, see :wiki:`this wiki page `. + +The subsequent parts are optional: + +- A generic material spec constrains the item material to one of the hard-coded + generic classes, which currently include:: + + PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN + METAL STONE SAND GLASS CLAY MILK + +- A specific material spec chooses the material exactly, using the raw syntax + for reaction input materials, e.g. ``INORGANIC:IRON``, although for + convenience it also allows just ``IRON``, or ``ACACIA:WOOD`` etc. See the + link above for more details on the unabbreviated raw syntax. + +- A comma-separated list of miscellaneous flags, which currently can be used to + ignore imported items (``LOCAL``) or items below a certain quality (1-5, with + 5 being masterwork). diff --git a/docs/plugins/xlsxreader.rst b/docs/plugins/xlsxreader.rst new file mode 100644 index 000000000..3d6cc0ac8 --- /dev/null +++ b/docs/plugins/xlsxreader.rst @@ -0,0 +1,9 @@ +xlsxreader +========== + +.. dfhack-tool:: + :summary: Provides a Lua API for reading xlsx files. + :tags: dev + :no-command: + +See `xlsxreader-api` for details. diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst new file mode 100644 index 000000000..0f77e1d3e --- /dev/null +++ b/docs/plugins/zone.rst @@ -0,0 +1,159 @@ +zone +==== + +.. dfhack-tool:: + :summary: Manage activity zones, cages, and the animals therein. + :tags: fort productivity animals buildings + +Usage +----- + +``enable zone`` + Add helpful filters to the pen/pasture sidebar menu (e.g. show only caged + grazers). +``zone set`` + Set zone or cage under cursor as default for future ``assign`` or ``tocages`` + commands. +``zone assign [] []`` + Assign unit(s) to the zone with the given ID, or to the most recent pen or + pit marked with the ``set`` command. If no filters are set, then a unit must + be selected in the in-game ui. +``zone unassign []`` + Unassign selected creature from its zone. +``zone nick []`` + Assign the given nickname to the selected animal or the animals matched by + the given filter. +``zone remnick []`` + Remove nicknames from the selected animal or the animals matched by the + given filter. +``zone enumnick []`` + Assign enumerated nicknames (e.g. "Hen 1", "Hen 2"...). +``zone tocages []`` + Assign unit(s) to cages that have been built inside the pasture selected + with the ``set`` command. +``zone uinfo []`` + Print info about unit(s). If no filters are set, then a unit must be + selected in the in-game ui. +``zone zinfo`` + Print info about the zone(s) and any buildings under the cursor. + +Examples +-------- + +Before any ``assign`` or ``tocages`` examples can be used, you must first move +the cursor over a pen/pasture or pit zone and run ``zone set`` to select the +zone. + +``zone assign all own ALPACA minage 3 maxage 10`` + Assign all of your alpacas who are between 3 and 10 years old to the + selected pasture. +``zone assign all own caged grazer nick ineedgrass`` + Assign all of your grazers who are sitting in cages on stockpiles (e.g. + after buying them from merchants) to the selected pasture and give them the + nickname 'ineedgrass'. +``zone assign all own not grazer not race CAT`` + Assign all of your animals who are not grazers (excluding cats) to the + selected pasture. + " zone assign all own milkable not grazer\n" +``zone assign all own female milkable not grazer`` + Assign all of your non-grazing milkable creatures to the selected pasture or + cage. +``zone assign all own race DWARF maxage 2`` + Throw all useless kids into a pit :) They'll be fine I'm sure. +``zone nick donttouchme`` + Nicknames all units in the current default zone or cage to 'donttouchme'. + This is especially useful for protecting a group of animals assigned to a + pasture or cage from being "processed" by `autobutcher`. +``zone tocages count 50 own tame male not grazer`` + Stuff up to 50 of your tame male animals who are not grazers into cages + built on the current default zone. + +Filters +------- + +:all: Process all units. +:count : Process only up to n units. +:unassigned: Not assigned to zone, chain or built cage. +:minage : Minimum age. Must be followed by a number. +:maxage : Maximum age. Must be followed by a number. +:not: Negates the next filter keyword. All of the keywords documented + below are negatable. +:race: Must be followed by a race RAW ID (e.g. BIRD_TURKEY, ALPACA, + etc). +:caged: In a built cage. +:own: From own civilization. You'll usually want to include this + filter. +:war: Trained war creature. +:hunting: Trained hunting creature. +:tamed: Creature is tame. +:trained: Creature is trained. Finds war/hunting creatures as well as + creatures who have a training level greater than 'domesticated'. + If you want to specifically search for war/hunting creatures + use ``war`` or ``hunting``. +:trainablewar: Creature can be trained for war (and is not already trained for + war/hunt). +:trainablehunt: Creature can be trained for hunting (and is not already trained + for war/hunt). +:male: Creature is male. +:female: Creature is female. +:egglayer: Race lays eggs. If you want units who actually lay eggs, also + specify ``female``. +:grazer: Race is a grazer. +:milkable: Race is milkable. If you want units who actually can be milked, + also specify ``female``. +:merchant: Is a merchant / belongs to a merchant. Should only be used for + pitting or slaughtering, not for stealing animals. + +Usage with single units +----------------------- +One convenient way to use the zone tool is to bind the commands ``zone assign`` +and ``zone set`` to hotkeys. Place the in-game cursor over a pen/pasture or pit +and use the ``zone set`` hotkey to mark it. Then you can select units on the map +(in 'v' or 'k' mode), in the unit list or from inside cages and use the +``zone assign`` hotkey to assign them to their new home. Allows pitting your own +dwarves, by the way. + +Matching with filters +--------------------- +All filters can be used together with the ``assign`` and ``tocages`` commands. + +Note that it's not possible to reassign units who are inside built cages or +chained, though this likely won't matter because if you have gone to the trouble +of creating a zoo or chaining a creature, you probably wouldn't want them +reassigned anyways. Also, ``zone`` will avoid caging owned pets because the owner +uncages them after a while which results in infinite hauling back and forth. + +Most filters should include an ``own`` element (which implies ``tame``) unless +you want to use ``zone assign`` for pitting hostiles. The ``own`` filter ignores +dwarves unless you explicitly specify ``race DWARF`` (so it's safe to use +``assign all own`` to one big pasture if you want to have all your animals in +the same place). + +The ``egglayer`` and ``milkable`` filters should be used together with +``female`` unless you want the males of the race included. Merchants and their +animals are ignored unless you specify ``merchant`` (pitting them should be no +problem, but stealing and pasturing their animals is not a good idea since +currently they are not properly added to your own stocks; slaughtering them +should work). + +Most filters can be negated (e.g. ``not grazer`` -> race is not a grazer). + +Mass-renaming +------------- + +Using the ``nick`` command, you can set the same nickname for multiple units. +If used without ``assign``, ``all``, or ``count``, it will rename all units in +the current default target zone. Combined with ``assign``, ``all``, or ``count`` +(and likely further optional filters) it will rename units matching the filter +conditions. + +Cage zones +---------- + +The ``tocages`` command assigns units to a set of cages, for example a room next +to your butcher shop(s). Units will be spread evenly among available cages to +optimize hauling to and butchering from them. For this to work you need to build +cages and then place one pen/pasture activity zone above them, covering all +cages you want to use. Then use ``zone set`` (like with ``assign``) and run +``zone tocages ``. ``tocages`` can be used together with ``nick`` or +``remnick`` to adjust nicknames while assigning to cages. diff --git a/docs/sphinx_extensions/dfhack/changelog.py b/docs/sphinx_extensions/dfhack/changelog.py index f7405d8b1..0cd732988 100644 --- a/docs/sphinx_extensions/dfhack/changelog.py +++ b/docs/sphinx_extensions/dfhack/changelog.py @@ -6,7 +6,7 @@ import sys from sphinx.errors import ExtensionError, SphinxError, SphinxWarning -from dfhack.util import DFHACK_ROOT, DOCS_ROOT +from dfhack.util import DFHACK_ROOT, DOCS_ROOT, write_file_if_changed CHANGELOG_PATHS = ( 'docs/changelog.txt', @@ -172,7 +172,7 @@ def consolidate_changelog(all_entries): def print_changelog(versions, all_entries, path, replace=True, prefix=''): # all_entries: version -> section -> entry - with open(path, 'w') as f: + with write_file_if_changed(path) as f: def write(line): if replace: line = replace_text(line, REPLACEMENTS) @@ -238,8 +238,10 @@ def generate_changelog(all=False): consolidate_changelog(stable_entries) consolidate_changelog(dev_entries) - print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, '_auto/news.rst')) - print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, '_auto/news-dev.rst')) + os.makedirs(os.path.join(DOCS_ROOT, 'changelogs'), mode=0o755, exist_ok=True) + + print_changelog(versions, stable_entries, os.path.join(DOCS_ROOT, 'changelogs/news.rst')) + print_changelog(versions, dev_entries, os.path.join(DOCS_ROOT, 'changelogs/news-dev.rst')) if all: for version in versions: @@ -251,10 +253,10 @@ def generate_changelog(all=False): else: version_entries = {version: dev_entries[version]} print_changelog([version], version_entries, - os.path.join(DOCS_ROOT, '_changelogs/%s-github.txt' % version), + os.path.join(DOCS_ROOT, 'changelogs/%s-github.txt' % version), replace=False) print_changelog([version], version_entries, - os.path.join(DOCS_ROOT, '_changelogs/%s-reddit.txt' % version), + os.path.join(DOCS_ROOT, 'changelogs/%s-reddit.txt' % version), replace=False, prefix='> ') @@ -264,7 +266,7 @@ def cli_entrypoint(): import argparse parser = argparse.ArgumentParser() parser.add_argument('-a', '--all', action='store_true', - help='Print changelogs for all versions to docs/_changelogs') + help='Print changelogs for all versions to docs/changelogs') parser.add_argument('-c', '--check', action='store_true', help='Check that all entries are printed') args = parser.parse_args() @@ -272,9 +274,9 @@ def cli_entrypoint(): entries = generate_changelog(all=args.all) if args.check: - with open(os.path.join(DOCS_ROOT, '_auto/news.rst')) as f: + with open(os.path.join(DOCS_ROOT, 'changelogs/news.rst')) as f: content_stable = f.read() - with open(os.path.join(DOCS_ROOT, '_auto/news-dev.rst')) as f: + with open(os.path.join(DOCS_ROOT, 'changelogs/news-dev.rst')) as f: content_dev = f.read() for entry in entries: for description in entry.children: diff --git a/docs/sphinx_extensions/dfhack/tool_docs.py b/docs/sphinx_extensions/dfhack/tool_docs.py new file mode 100644 index 000000000..836bab217 --- /dev/null +++ b/docs/sphinx_extensions/dfhack/tool_docs.py @@ -0,0 +1,330 @@ +# useful references: +# https://www.sphinx-doc.org/en/master/extdev/appapi.html +# https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html +# https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html#rst-directives + +from collections import defaultdict +import logging +import os +import re +from typing import Dict, Iterable, List, Optional, Tuple, Type + +import docutils.nodes as nodes +from docutils.nodes import Node +import docutils.parsers.rst.directives as rst_directives +import sphinx +import sphinx.addnodes as addnodes +import sphinx.directives +from sphinx.domains import Domain, Index, IndexEntry +from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import process_index_entry + +import dfhack.util + + +logger = sphinx.util.logging.getLogger(__name__) + + +def get_label_class(builder: sphinx.builders.Builder) -> Type[nodes.Inline]: + if builder.format == 'text': + return nodes.inline + else: + return nodes.strong + +def make_labeled_paragraph(label: Optional[str]=None, content: Optional[str]=None, + label_class=nodes.strong, content_class=nodes.inline) -> nodes.paragraph: + p = nodes.paragraph('', '') + if label is not None: + p += [ + label_class('', '{}:'.format(label)), + nodes.inline('', ' '), + ] + if content is not None: + p += content_class('', content) + return p + +def make_summary(builder: sphinx.builders.Builder, summary: str) -> nodes.paragraph: + para = nodes.paragraph('', '') + if builder.format == 'text': + # It might be clearer to block indent instead of just indenting the + # first line, but this is clearer than nothing. + para += nodes.inline(text=' ') + para += nodes.inline(text=summary) + return para + +_KEYBINDS = {} +_KEYBINDS_RENDERED = set() # commands whose keybindings have been rendered + +def scan_keybinds(root, files, keybindings): + """Add keybindings in the specified files to the + given keybindings dict. + """ + for file in files: + with open(os.path.join(root, file)) as f: + lines = [l.replace('keybinding add', '').strip() for l in f.readlines() + if l.startswith('keybinding add')] + for k in lines: + first, command = k.split(' ', 1) + bind, context = (first.split('@') + [''])[:2] + if ' ' not in command: + command = command.replace('"', '') + tool = command.split(' ')[0].replace('"', '') + keybindings[tool] = keybindings.get(tool, []) + [ + (command, bind.split('-'), context)] + +def scan_all_keybinds(root_dir): + """Get the implemented keybinds, and return a dict of + {tool: [(full_command, keybinding, context), ...]}. + """ + keybindings = dict() + for root, _, files in os.walk(root_dir): + scan_keybinds(root, files, keybindings) + return keybindings + + +def render_dfhack_keybind(command, builder: sphinx.builders.Builder) -> List[nodes.paragraph]: + _KEYBINDS_RENDERED.add(command) + out = [] + if command not in _KEYBINDS: + return out + for keycmd, key, ctx in _KEYBINDS[command]: + n = make_labeled_paragraph('Keybinding', label_class=get_label_class(builder)) + for k in key: + if builder.format == 'text': + k = '[{}]'.format(k) + n += nodes.inline(k, k, classes=['kbd']) + if keycmd != command: + n += nodes.inline(' -> ', ' -> ') + n += nodes.literal(keycmd, keycmd, classes=['guilabel']) + if ctx: + n += nodes.inline(' in ', ' in ') + n += nodes.literal(ctx, ctx) + out.append(n) + return out + + +def check_missing_keybinds(): + # FIXME: _KEYBINDS_RENDERED is empty in the parent process under parallel builds + # consider moving to a sphinx Domain to solve this properly + for missing_command in sorted(set(_KEYBINDS.keys()) - _KEYBINDS_RENDERED): + logger.warning('Undocumented keybindings for command: %s', missing_command) + + +_anchor_pattern = re.compile(r'^\d+') + +def to_anchor(name: str) -> str: + name = name.lower() + name = name.replace('/', '-') + name = re.sub(_anchor_pattern, '', name) + return name + +class DFHackToolDirectiveBase(sphinx.directives.ObjectDescription): + has_content = False + required_arguments = 0 + optional_arguments = 1 + + def get_tool_name_from_docname(self): + parts = self.env.docname.split('/') + if 'tools' in parts: + return '/'.join(parts[parts.index('tools') + 1:]) + else: + return parts[-1] + + def get_name_or_docname(self): + if self.arguments: + return self.arguments[0] + return self.get_tool_name_from_docname() + + def add_index_entries(self, name) -> None: + docname = self.env.docname + anchor = to_anchor(self.get_tool_name_from_docname()) + tags = self.env.domaindata['tag-repo']['doctags'][docname] + indexdata = (name, self.options.get('summary', ''), '', docname, anchor, 0) + self.env.domaindata['all']['objects'].append(indexdata) + for tag in tags: + self.env.domaindata[tag]['objects'].append(indexdata) + + @staticmethod + def wrap_box(*children: List[nodes.Node]) -> nodes.Admonition: + return nodes.topic('', *children, classes=['dfhack-tool-summary']) + + def make_labeled_paragraph(self, *args, **kwargs): + # convenience wrapper to set label_class to the desired builder-specific node type + kwargs.setdefault('label_class', get_label_class(self.env.app.builder)) + return make_labeled_paragraph(*args, **kwargs) + + def render_content(self) -> List[nodes.Node]: + raise NotImplementedError + + def run(self): + return [self.wrap_box(*self.render_content())] + + +class DFHackToolDirective(DFHackToolDirectiveBase): + option_spec = { + 'tags': dfhack.util.directive_arg_str_list, + 'no-command': rst_directives.flag, + 'summary': rst_directives.unchanged, + } + + def render_content(self) -> List[nodes.Node]: + tag_paragraph = self.make_labeled_paragraph('Tags') + tags = self.options.get('tags', []) + self.env.domaindata['tag-repo']['doctags'][self.env.docname] = tags + for tag in tags: + tag_paragraph += [ + addnodes.pending_xref(tag, nodes.inline(text=tag), **{ + 'reftype': 'ref', + 'refdomain': 'std', + 'reftarget': tag + '-tag-index', + 'refexplicit': True, + 'refwarn': True, + }), + nodes.inline(text=' | '), + ] + tag_paragraph.pop() + + ret_nodes = [tag_paragraph] + if 'no-command' in self.options: + self.add_index_entries(self.get_name_or_docname() + ' (plugin)') + ret_nodes += [make_summary(self.env.app.builder, self.options.get('summary', ''))] + return ret_nodes + + def run(self): + out = DFHackToolDirectiveBase.run(self) + if 'no-command' not in self.options: + out += [self.wrap_box(*DFHackCommandDirective.render_content(self))] + return out + + +class DFHackCommandDirective(DFHackToolDirectiveBase): + option_spec = { + 'summary': rst_directives.unchanged_required, + } + + def render_content(self) -> List[nodes.Node]: + command = self.get_name_or_docname() + self.add_index_entries(command) + return [ + self.make_labeled_paragraph('Command', command, content_class=nodes.literal), + make_summary(self.env.app.builder, self.options.get('summary', '')), + *render_dfhack_keybind(command, builder=self.env.app.builder), + ] + + +class TagRepoDomain(Domain): + name = 'tag-repo' + label = 'Holds tag associations per document' + initial_data = {'doctags': {}} + + def merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: + self.data['doctags'].update(otherdata['doctags']) + + +def get_tags(): + groups = {} + group_re = re.compile(r'"([^"]+)"') + tag_re = re.compile(r'- `([^ ]+) <[^>]+>`: (.*)') + with open(os.path.join(dfhack.util.DOCS_ROOT, 'Tags.rst')) as f: + lines = f.readlines() + for line in lines: + line = line.strip() + m = re.match(group_re, line) + if m: + group = m.group(1) + groups[group] = [] + continue + m = re.match(tag_re, line) + if m: + tag = m.group(1) + desc = m.group(2) + groups[group].append((tag, desc)) + return groups + + +def tag_domain_get_objects(self): + for obj in self.data['objects']: + yield(obj) + +def tag_domain_merge_domaindata(self, docnames: List[str], otherdata: Dict) -> None: + seen = set() + objs = self.data['objects'] + for obj in objs: + seen.add(obj[0]) + for obj in otherdata['objects']: + if obj[0] not in seen: + objs.append(obj) + objs.sort() + +def tag_index_generate(self, docnames: Optional[Iterable[str]] = None) -> Tuple[List[Tuple[str, List[IndexEntry]]], bool]: + content = defaultdict(list) + for name, desc, _, docname, anchor, _ in self.domain.data['objects']: + first_letter = name[0].lower() + extra, descr = desc, '' + if self.domain.env.app.builder.format == 'html': + extra, descr = '', desc + content[first_letter].append( + IndexEntry(name, 0, docname, anchor, extra, '', descr)) + return (sorted(content.items()), False) + +def register_index(app, tag, title): + domain_class = type(tag+'Domain', (Domain, ), { + 'name': tag, + 'label': 'Container domain for tag: ' + tag, + 'initial_data': {'objects': []}, + 'merge_domaindata': tag_domain_merge_domaindata, + 'get_objects': tag_domain_get_objects, + }) + index_class = type(tag+'Index', (Index, ), { + 'name': 'tag-index', + 'localname': title, + 'shortname': tag, + 'generate': tag_index_generate, + }) + app.add_domain(domain_class) + app.add_index_to_domain(tag, index_class) + +def init_tag_indices(app): + os.makedirs(os.path.join(dfhack.util.DOCS_ROOT, 'tags'), mode=0o755, exist_ok=True) + tag_groups = get_tags() + for tag_group in tag_groups: + group_file_path = os.path.join(dfhack.util.DOCS_ROOT, 'tags', 'by{group}.rst'.format(group=tag_group)) + with dfhack.util.write_file_if_changed(group_file_path) as topidx: + for tag_tuple in tag_groups[tag_group]: + tag, desc = tag_tuple[0], tag_tuple[1] + topidx.write(('- `{name} <{name}-tag-index>`\n').format(name=tag)) + topidx.write((' {desc}\n').format(desc=desc)) + register_index(app, tag, desc) + + +def update_index_titles(app): + for domain in app.env.domains.values(): + for index in domain.indices: + if index.shortname == 'all': + continue + if app.builder.format == 'html': + index.localname = '"%s" tag index

%s

' % (index.shortname, index.localname) + else: + index.localname = '"%s" tag index - %s' % (index.shortname, index.localname) + +def register(app): + app.add_directive('dfhack-tool', DFHackToolDirective) + app.add_directive('dfhack-command', DFHackCommandDirective) + update_index_titles(app) + _KEYBINDS.update(scan_all_keybinds(os.path.join(dfhack.util.DFHACK_ROOT, 'data', 'init'))) + +def setup(app): + app.connect('builder-inited', register) + + app.add_domain(TagRepoDomain) + register_index(app, 'all', 'Index of DFHack tools') + init_tag_indices(app) + + # TODO: re-enable once detection is corrected + # app.connect('build-finished', lambda *_: check_missing_keybinds()) + + return { + 'version': '0.1', + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } diff --git a/docs/sphinx_extensions/dfhack/util.py b/docs/sphinx_extensions/dfhack/util.py index 71a432da4..91f0accbe 100644 --- a/docs/sphinx_extensions/dfhack/util.py +++ b/docs/sphinx_extensions/dfhack/util.py @@ -1,3 +1,5 @@ +import contextlib +import io import os DFHACK_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) @@ -5,3 +7,34 @@ DOCS_ROOT = os.path.join(DFHACK_ROOT, 'docs') if not os.path.isdir(DOCS_ROOT): raise ReferenceError('docs root not found: %s' % DOCS_ROOT) + + +@contextlib.contextmanager +def write_file_if_changed(path): + with io.StringIO() as buffer: + yield buffer + new_contents = buffer.getvalue() + + try: + with open(path, 'r') as infile: + old_contents = infile.read() + except IOError: + old_contents = None + + if old_contents != new_contents: + with open(path, 'w') as outfile: + outfile.write(new_contents) + + +# directive argument helpers (supplementing docutils.parsers.rst.directives) +def directive_arg_str_list(argument): + """ + Converts a space- or comma-separated list of values into a Python list + of strings. + (Directive option conversion function.) + """ + if ',' in argument: + entries = argument.split(',') + else: + entries = argument.split() + return [entry.strip() for entry in entries] diff --git a/docs/styles/dfhack.css b/docs/styles/dfhack.css index 9b6e523ef..c1a00ed90 100644 --- a/docs/styles/dfhack.css +++ b/docs/styles/dfhack.css @@ -60,3 +60,22 @@ div.body { span.pre { overflow-wrap: break-word; } + +div.dfhack-tool-summary, +aside.dfhack-tool-summary { + margin: 10px 0; + padding: 10px 15px; + background-color: #EEE; +} + +div.dfhack-tool-summary p, +aside.dfhack-tool-summary p { + margin-top: 0; + margin-bottom: 0.5em; + line-height: 1em; +} + +div.dfhack-tool-summary p:last-child, +aside.dfhack-tool-summary p:last-child { + margin-bottom: 0; +} diff --git a/docs/styles/sphinx5.css b/docs/styles/sphinx5.css new file mode 100644 index 000000000..385569510 --- /dev/null +++ b/docs/styles/sphinx5.css @@ -0,0 +1,3 @@ +dl.field-list > dt:after { + display: none; +} diff --git a/index.rst b/index.rst index 2e5454f40..a54ea87f5 100644 --- a/index.rst +++ b/index.rst @@ -15,10 +15,9 @@ Quick Links * `Downloads `_ * `Installation guide ` -* `Source code `_ - (**important:** read `compile` before attempting to build from source) -* `Bay 12 forums thread `_ -* `Bug tracker `_ +* `Getting help ` +* :source:`Source code <>` + (**important:** read `compile` before attempting to build from source.) User Manual =========== @@ -28,10 +27,8 @@ User Manual /docs/Introduction /docs/Installing - /docs/Support /docs/Core - /docs/Plugins - /docs/Scripts + /docs/Tools /docs/guides/index - /docs/index-about /docs/index-dev + /docs/index-about diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b134b30df..a62eb2fe0 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -126,19 +126,11 @@ set(MAIN_SOURCES_DARWIN Process-darwin.cpp ) -set(MAIN_SOURCES_LINUX_EGGY - ${CONSOLE_SOURCES} - Hooks-egg.cpp - PlugLoad-linux.cpp - Process-linux.cpp -) - set(MODULE_HEADERS include/modules/Buildings.h include/modules/Burrows.h include/modules/Constructions.h include/modules/Designations.h - include/modules/Engravings.h include/modules/EventManager.h include/modules/Filesystem.h include/modules/Graphic.h @@ -150,7 +142,6 @@ set(MODULE_HEADERS include/modules/MapCache.h include/modules/Maps.h include/modules/Materials.h - include/modules/Notes.h include/modules/Once.h include/modules/Persistence.h include/modules/Random.h @@ -166,7 +157,6 @@ set(MODULE_SOURCES modules/Burrows.cpp modules/Constructions.cpp modules/Designations.cpp - modules/Engravings.cpp modules/EventManager.cpp modules/Filesystem.cpp modules/Graphic.cpp @@ -177,7 +167,6 @@ set(MODULE_SOURCES modules/MapCache.cpp modules/Maps.cpp modules/Materials.cpp - modules/Notes.cpp modules/Once.cpp modules/Persistence.cpp modules/Random.cpp @@ -185,7 +174,6 @@ set(MODULE_SOURCES modules/Screen.cpp modules/Translation.cpp modules/Units.cpp - modules/Windows.cpp modules/World.cpp ) @@ -223,10 +211,7 @@ list(APPEND PROJECT_SOURCES ${MAIN_SOURCES}) list(APPEND PROJECT_SOURCES ${MODULE_SOURCES}) if(UNIX) - option(BUILD_EGGY "Make DFHack strangely egg-shaped." OFF) - if(BUILD_EGGY) - list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) - elseif(APPLE) + if(APPLE) list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) else() list(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) @@ -388,15 +373,9 @@ add_executable(dfhack-run dfhack-run.cpp) add_executable(binpatch binpatch.cpp) target_link_libraries(binpatch dfhack-md5) -if(BUILD_EGGY) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "egg" ) -else() - if(WIN32) - set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) - endif() -endif() - if(WIN32) + # name the resulting library SDL.dll on Windows + set_target_properties(dfhack PROPERTIES OUTPUT_NAME "SDL" ) set_target_properties(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) set_target_properties(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) else() @@ -429,10 +408,10 @@ elseif(UNIX) target_link_libraries(dfhack SDL) endif() -target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_lib_static dfhack-version ${PROJECT_LIBS}) +target_link_libraries(dfhack protobuf-lite clsocket lua jsoncpp_static dfhack-version ${PROJECT_LIBS}) set_target_properties(dfhack PROPERTIES INTERFACE_LINK_LIBRARIES "") -target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_lib_static) +target_link_libraries(dfhack-client protobuf-lite clsocket jsoncpp_static) target_link_libraries(dfhack-run dfhack-client) if(APPLE) @@ -453,30 +432,19 @@ if(UNIX) DESTINATION .) endif() else() - if(NOT BUILD_EGGY) - # On windows, copy the renamed SDL so DF can still run. - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll - DESTINATION ${DFHACK_LIBRARY_DESTINATION}) - endif() + # On windows, copy the renamed SDL so DF can still run. + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/windows/win${DFHACK_BUILD_ARCH}/SDLreal.dll + DESTINATION ${DFHACK_LIBRARY_DESTINATION}) endif() # install the main lib -if(NOT BUILD_EGGY) - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) -else() - install(TARGETS dfhack - LIBRARY DESTINATION ${DFHACK_EGGY_DESTINATION} - RUNTIME DESTINATION ${DFHACK_EGGY_DESTINATION}) -endif() +install(TARGETS dfhack + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) # install the offset file install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) -# install the example autoexec file -install(FILES ../dfhack.init-example ../onLoad.init-example - DESTINATION ${DFHACK_BINARY_DESTINATION}) install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} diff --git a/library/ColorText.cpp b/library/ColorText.cpp index 7f417e0a8..d528cb4c7 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -52,6 +52,7 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +using namespace std; using namespace DFHack; #include "tinythread.h" diff --git a/library/Core.cpp b/library/Core.cpp index 10f5057f3..083a6223f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -53,8 +53,6 @@ using namespace std; #include "modules/Filesystem.h" #include "modules/Gui.h" #include "modules/World.h" -#include "modules/Graphic.h" -#include "modules/Windows.h" #include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" @@ -103,6 +101,7 @@ size_t loadScriptFiles(Core* core, color_ostream& out, const vector namespace DFHack { +DBG_DECLARE(core,keybinding,DebugCategory::LINFO); DBG_DECLARE(core,script,DebugCategory::LINFO); class MainThread { @@ -277,65 +276,6 @@ static string dfhack_version_desc() return s.str(); } -static std::string getScriptHelp(std::string path, std::string helpprefix) -{ - ifstream script(path.c_str()); - - if (script.good()) - { - std::string help; - if (getline(script, help) && - help.substr(0,helpprefix.length()) == helpprefix) - { - help = help.substr(helpprefix.length()); - while (help.size() && help[0] == ' ') - help = help.substr(1); - return help; - } - } - - return "No help available."; -} - -static void listScripts(PluginManager *plug_mgr, std::map &pset, std::string path, bool all, std::string prefix = "") -{ - std::vector files; - Filesystem::listdir(path, files); - - path += '/'; - for (size_t i = 0; i < files.size(); i++) - { - if (hasEnding(files[i], ".lua")) - { - string help = getScriptHelp(path + files[i], "--"); - string key = prefix + files[i].substr(0, files[i].size()-4); - if (pset.find(key) == pset.end()) { - pset[key] = help; - } - } - else if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() && hasEnding(files[i], ".rb")) - { - string help = getScriptHelp(path + files[i], "#"); - string key = prefix + files[i].substr(0, files[i].size()-3); - if (pset.find(key) == pset.end()) { - pset[key] = help; - } - } - else if (all && !files[i].empty() && files[i][0] != '.' && files[i] != "internal" && files[i] != "test") - { - listScripts(plug_mgr, pset, path+files[i]+"/", all, prefix+files[i]+"/"); - } - } -} - -static void listAllScripts(map &pset, bool all) -{ - vector paths; - Core::getInstance().getScriptPaths(&paths); - for (string path : paths) - listScripts(Core::getInstance().getPluginManager(), pset, path, all); -} - namespace { struct ScriptArgs { const string *pcmd; @@ -442,59 +382,52 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } -// List of built in commands -static const std::set built_in_commands = { - "ls" , - "help" , - "type" , - "load" , - "unload" , - "reload" , - "enable" , - "disable" , - "plug" , - "keybinding" , - "alias" , - "fpause" , - "cls" , - "die" , - "kill-lua" , - "script" , - "hide" , - "show" , - "sc-script" -}; +bool is_builtin(color_ostream &con, const string &command) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); -static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) -{ - std::vector possible; + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "is_builtin")) { + con.printerr("Failed to load helpdb Lua code\n"); + return false; + } - // Check for possible built in commands to autocomplete first - for (auto const &command : built_in_commands) - if (command.substr(0, first.size()) == first) - possible.push_back(command); + Lua::Push(L, command); - auto plug_mgr = Core::getInstance().getPluginManager(); - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - const Plugin * plug = it->second; - for (size_t j = 0; j < plug->size(); j++) - { - const PluginCommand &pcmd = (*plug)[j]; - if (pcmd.isHotkeyCommand()) - continue; - if (pcmd.name.substr(0, first.size()) == first) - possible.push_back(pcmd.name); - } + if (!Lua::SafeCall(con, L, 1, 1)) { + con.printerr("Failed Lua call to helpdb.is_builtin.\n"); + return false; } - bool all = (first.find('/') != std::string::npos); + return lua_toboolean(L, -1); +} + +void get_commands(color_ostream &con, vector &commands) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "get_commands")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + if (!Lua::SafeCall(con, L, 0, 1)) { + con.printerr("Failed Lua call to helpdb.get_commands.\n"); + } - std::map scripts; - listAllScripts(scripts, all); - for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - if (iter->first.substr(0, first.size()) == first) - possible.push_back(iter->first); + Lua::GetVector(L, commands); +} + +static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) +{ + std::vector commands, possible; + + for (auto &command : commands) + if (command.substr(0, first.size()) == first) + possible.push_back(command); if (possible.size() == 1) { @@ -650,49 +583,80 @@ static std::string sc_event_name (state_change_event id) { return "SC_UNKNOWN"; } -string getBuiltinCommand(std::string cmd) -{ - std::string builtin = ""; +void help_helper(color_ostream &con, const string &entry_name) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); - // Check our list of builtin commands from the header - if (built_in_commands.count(cmd)) - builtin = cmd; + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic(con, L, "helpdb", "help")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } - // Check for some common aliases for built in commands - else if (cmd == "?" || cmd == "man") - builtin = "help"; + Lua::Push(L, entry_name); - else if (cmd == "dir") - builtin = "ls"; + if (!Lua::SafeCall(con, L, 1, 0)) { + con.printerr("Failed Lua call to helpdb.help.\n"); + } +} - else if (cmd == "clear") - builtin = "cls"; +void tags_helper(color_ostream &con, const string &tag) { + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); - else if (cmd == "devel/dump-rpc") - builtin = "devel/dump-rpc"; + if (!lua_checkstack(L, 1) || + !Lua::PushModulePublic(con, L, "helpdb", "tags")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + Lua::Push(L, tag); - return builtin; + if (!Lua::SafeCall(con, L, 1, 0)) { + con.printerr("Failed Lua call to helpdb.tags.\n"); + } } -void ls_helper(color_ostream &con, const string &name, const string &desc) -{ - const size_t help_line_length = 80 - 22 - 5; - const string padding = string(80 - help_line_length, ' '); - vector lines; - con.print(" %-22s - ", name.c_str()); - word_wrap(&lines, desc, help_line_length); +void ls_helper(color_ostream &con, const vector ¶ms) { + vector filter; + bool skip_tags = false; + bool show_dev_commands = false; + string exclude_strs = ""; - // print first line, then any additional lines preceded by padding - for (size_t i = 0; i < lines.size(); i++) - con.print("%s%s\n", i ? padding.c_str() : "", lines[i].c_str()); -} + bool in_exclude = false; + for (auto str : params) { + if (in_exclude) + exclude_strs = str; + else if (str == "--notags") + skip_tags = true; + else if (str == "--dev") + show_dev_commands = true; + else if (str == "--exclude") + in_exclude = true; + else + filter.push_back(str); + } -void ls_helper(color_ostream &con, const PluginCommand &pcmd) -{ - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - ls_helper(con, pcmd.name, pcmd.description); - con.reset_color(); + CoreSuspender suspend; + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 5) || + !Lua::PushModulePublic(con, L, "helpdb", "ls")) { + con.printerr("Failed to load helpdb Lua code\n"); + return; + } + + Lua::PushVector(L, filter); + Lua::Push(L, skip_tags); + Lua::Push(L, show_dev_commands); + Lua::Push(L, exclude_strs); + + if (!Lua::SafeCall(con, L, 4, 0)) { + con.printerr("Failed Lua call to helpdb.ls.\n"); + } } command_result Core::runCommand(color_ostream &con, const std::string &first_, vector &parts) @@ -706,700 +670,587 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v return CR_FAILURE; } + if (first.empty()) + return CR_NOT_IMPLEMENTED; + + if (first.find('\\') != std::string::npos) + { + con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", first.c_str()); + for (size_t i = 0; i < first.size(); i++) + { + if (first[i] == '\\') + first[i] = '/'; + } + } + + // let's see what we actually got command_result res; - if (!first.empty()) + if (first == "help" || first == "man" || first == "?") { - if(first.find('\\') != std::string::npos) + if(!parts.size()) { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", first.c_str()); - for (size_t i = 0; i < first.size(); i++) + if (con.is_console()) { - if (first[i] == '\\') - first[i] = '/'; - } + con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" + "Some basic editing capabilities are included (single-line text editing).\n" + "The console also has a command history - you can navigate it with Up and Down keys.\n" + "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" + "by clicking on the program icon in the top bar of the window.\n\n"); + } + con.print("Here are some basic commands to get you started:\n" + " help|?|man - This text.\n" + " help - Usage help for the given plugin, command, or script.\n" + " tags - List the tags that the DFHack tools are grouped by.\n" + " ls|dir [] - List commands, optionally filtered by a tag or substring.\n" + " Optional parameters:\n" + " --notags: skip printing tags for each command.\n" + " --dev: include commands intended for developers and modders.\n" + " cls|clear - Clear the console.\n" + " fpause - Force DF to pause.\n" + " die - Force DF to close immediately, without saving.\n" + " keybinding - Modify bindings of commands to in-game key shortcuts.\n" + "\n" + "See more commands by running 'ls'.\n\n" + ); + + con.print("DFHack version %s\n", dfhack_version_desc().c_str()); } - - // let's see what we actually got - string builtin = getBuiltinCommand(first); - if (builtin == "help") + else { - if(!parts.size()) + help_helper(con, parts[0]); + } + } + else if (first == "tags") + { + tags_helper(con, parts.size() ? parts[0] : ""); + } + else if (first == "load" || first == "unload" || first == "reload") + { + bool all = false; + bool load = (first == "load"); + bool unload = (first == "unload"); + if (parts.size()) + { + for (auto p = parts.begin(); p != parts.end(); p++) { - if (con.is_console()) + if (p->size() && (*p)[0] == '-') { - con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" - "Some basic editing capabilities are included (single-line text editing).\n" - "The console also has a command history - you can navigate it with Up and Down keys.\n" - "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" - "by clicking on the program icon in the top bar of the window.\n\n"); + if (p->find('a') != string::npos) + all = true; } - con.print("Basic commands:\n" - " help|?|man - This text.\n" - " help COMMAND - Usage help for the given command.\n" - " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " keybinding - Modify bindings of commands to keys\n" - "Plugin management (useful for developers):\n" - " plug [PLUGIN|v] - List plugin state and description.\n" - " load PLUGIN|-all - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|-all - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|-all - Reload a plugin or all loaded plugins.\n" - ); - - con.print("\nDFHack version %s\n", dfhack_version_desc().c_str()); } - else if (parts.size() == 1) + if (all) { - if (getBuiltinCommand(parts[0]).size()) - { - con << parts[0] << ": built-in command; Use `ls`, `help`, or check hack/Readme.html for more information" << std::endl; - return CR_NOT_IMPLEMENTED; - } - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (plug) { - for (size_t j = 0; j < plug->size();j++) - { - const PluginCommand & pcmd = (plug->operator[](j)); - if (pcmd.name != parts[0]) - continue; - - if (pcmd.isHotkeyCommand()) - con.color(COLOR_CYAN); - con.print("%s: %s\n",pcmd.name.c_str(), pcmd.description.c_str()); - con.reset_color(); - if (!pcmd.usage.empty()) - con << "Usage:\n" << pcmd.usage << flush; - return CR_OK; - } - } - string file = findScript(parts[0] + ".lua"); - if ( file != "" ) { - string help = getScriptHelp(file, "--"); - con.print("%s: %s\n", parts[0].c_str(), help.c_str()); - return CR_OK; - } - if (plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) { - file = findScript(parts[0] + ".rb"); - if ( file != "" ) { - string help = getScriptHelp(file, "#"); - con.print("%s: %s\n", parts[0].c_str(), help.c_str()); - return CR_OK; - } - } - con.printerr("Unknown command: %s\n", parts[0].c_str()); - return CR_FAILURE; + if (load) + plug_mgr->loadAll(); + else if (unload) + plug_mgr->unloadAll(); + else + plug_mgr->reloadAll(); + return CR_OK; } - else + for (auto p = parts.begin(); p != parts.end(); p++) { - con.printerr("not implemented yet\n"); - return CR_NOT_IMPLEMENTED; + if (!p->size() || (*p)[0] == '-') + continue; + if (load) + plug_mgr->load(*p); + else if (unload) + plug_mgr->unload(*p); + else + plug_mgr->reload(*p); } } - else if (builtin == "load" || builtin == "unload" || builtin == "reload") + else + con.printerr("%s: no arguments\n", first.c_str()); + } + else if( first == "enable" || first == "disable" ) + { + CoreSuspender suspend; + bool enable = (first == "enable"); + + if(parts.size()) { - bool all = false; - bool load = (builtin == "load"); - bool unload = (builtin == "unload"); - if (parts.size()) + for (size_t i = 0; i < parts.size(); i++) { - for (auto p = parts.begin(); p != parts.end(); p++) + std::string part = parts[i]; + if (part.find('\\') != std::string::npos) { - if (p->size() && (*p)[0] == '-') + con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); + for (size_t j = 0; j < part.size(); j++) { - if (p->find('a') != string::npos) - all = true; + if (part[j] == '\\') + part[j] = '/'; } } - if (all) - { - if (load) - plug_mgr->loadAll(); - else if (unload) - plug_mgr->unloadAll(); - else - plug_mgr->reloadAll(); - return CR_OK; - } - for (auto p = parts.begin(); p != parts.end(); p++) - { - if (!p->size() || (*p)[0] == '-') - continue; - if (load) - plug_mgr->load(*p); - else if (unload) - plug_mgr->unload(*p); - else - plug_mgr->reload(*p); - } - } - else - con.printerr("%s: no arguments\n", builtin.c_str()); - } - else if( builtin == "enable" || builtin == "disable" ) - { - CoreSuspender suspend; - bool enable = (builtin == "enable"); - if(parts.size()) - { - for (size_t i = 0; i < parts.size(); i++) - { - std::string part = parts[i]; - if (part.find('\\') != std::string::npos) - { - con.printerr("Replacing backslashes with forward slashes in \"%s\"\n", part.c_str()); - for (size_t j = 0; j < part.size(); j++) - { - if (part[j] == '\\') - part[j] = '/'; - } - } - - Plugin * plug = (*plug_mgr)[part]; + Plugin * plug = (*plug_mgr)[part]; - if(!plug) - { - std::string lua = findScript(part + ".lua"); - if (lua.size()) - { - res = enableLuaScript(con, part, enable); - } - else - { - res = CR_NOT_FOUND; - con.printerr("No such plugin or Lua script: %s\n", part.c_str()); - } - } - else if (!plug->can_set_enabled()) + if(!plug) + { + std::string lua = findScript(part + ".lua"); + if (lua.size()) { - res = CR_NOT_IMPLEMENTED; - con.printerr("Cannot %s plugin: %s\n", builtin.c_str(), part.c_str()); + res = enableLuaScript(con, part, enable); } else { - res = plug->set_enabled(con, enable); - - if (res != CR_OK || plug->is_enabled() != enable) - con.printerr("Could not %s plugin: %s\n", builtin.c_str(), part.c_str()); + res = CR_NOT_FOUND; + con.printerr("No such plugin or Lua script: %s\n", part.c_str()); } } - - return res; - } - else - { - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - Plugin * plug = it->second; - if (!plug->can_be_enabled()) continue; - - con.print( - "%20s\t%-3s%s\n", - (plug->getName()+":").c_str(), - plug->is_enabled() ? "on" : "off", - plug->can_set_enabled() ? "" : " (controlled elsewhere)" - ); - } - } - } - else if (builtin == "ls" || builtin == "dir") - { - bool all = false; - if (parts.size() && parts[0] == "-a") - { - all = true; - vector_erase_at(parts, 0); - } - if(parts.size()) - { - string & plugname = parts[0]; - const Plugin * plug = (*plug_mgr)[plugname]; - if(!plug) - { - con.printerr("There's no plugin called %s!\n", plugname.c_str()); - } - else if (plug->getState() != Plugin::PS_LOADED) + else if (!plug->can_set_enabled()) { - con.printerr("Plugin %s is not loaded.\n", plugname.c_str()); - } - else if (!plug->size()) - { - con.printerr("Plugin %s is loaded but does not implement any commands.\n", plugname.c_str()); - } - else for (size_t j = 0; j < plug->size();j++) - { - ls_helper(con, plug->operator[](j)); - } - } - else - { - con.print( - "builtin:\n" - " help|?|man - This text or help specific to a plugin.\n" - " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" - " cls|clear - Clear the console.\n" - " fpause - Force DF to pause.\n" - " die - Force DF to close immediately\n" - " kill-lua - Stop an active Lua script\n" - " keybinding - Modify bindings of commands to keys\n" - " script FILENAME - Run the commands specified in a file.\n" - " sc-script - Automatically run specified scripts on state change events\n" - " plug [PLUGIN|v] - List plugin state and detailed description.\n" - " load PLUGIN|-all [...] - Load a plugin by name or load all possible plugins.\n" - " unload PLUGIN|-all [...] - Unload a plugin or all loaded plugins.\n" - " reload PLUGIN|-all [...] - Reload a plugin or all loaded plugins.\n" - " enable/disable PLUGIN [...] - Enable or disable a plugin if supported.\n" - " type COMMAND - Display information about where a command is implemented\n" - "\n" - "plugins:\n" - ); - std::set out; - for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) - { - const Plugin * plug = it->second; - if(!plug->size()) - continue; - for (size_t j = 0; j < plug->size();j++) - { - const PluginCommand & pcmd = (plug->operator[](j)); - out.insert(sortable(pcmd.isHotkeyCommand(),pcmd.name,pcmd.description)); - } - } - for(auto iter = out.begin();iter != out.end();iter++) - { - if ((*iter).recolor) - con.color(COLOR_CYAN); - ls_helper(con, iter->name, iter->description); - con.reset_color(); + res = CR_NOT_IMPLEMENTED; + con.printerr("Cannot %s plugin: %s\n", first.c_str(), part.c_str()); } - std::map scripts; - listAllScripts(scripts, all); - if (!scripts.empty()) + else { - con.print("\nscripts:\n"); - for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) - ls_helper(con, iter->first, iter->second); + res = plug->set_enabled(con, enable); + + if (res != CR_OK || plug->is_enabled() != enable) + con.printerr("Could not %s plugin: %s\n", first.c_str(), part.c_str()); } } + + return res; } - else if (builtin == "plug") + else { - const char *header_format = "%30s %10s %4s %8s\n"; - const char *row_format = "%30s %10s %4i %8s\n"; - con.print(header_format, "Name", "State", "Cmds", "Enabled"); - - plug_mgr->refresh(); for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { Plugin * plug = it->second; - if (!plug) - continue; - if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) - continue; - color_value color; - switch (plug->getState()) - { - case Plugin::PS_LOADED: - color = COLOR_RESET; - break; - case Plugin::PS_UNLOADED: - case Plugin::PS_UNLOADING: - color = COLOR_YELLOW; - break; - case Plugin::PS_LOADING: - color = COLOR_LIGHTBLUE; - break; - case Plugin::PS_BROKEN: - color = COLOR_LIGHTRED; - break; - default: - color = COLOR_LIGHTMAGENTA; - break; - } - con.color(color); - con.print(row_format, - plug->getName().c_str(), - Plugin::getStateDescription(plug->getState()), - plug->size(), - (plug->can_be_enabled() - ? (plug->is_enabled() ? "enabled" : "disabled") - : "n/a") + if (!plug->can_be_enabled()) continue; + + con.print( + "%20s\t%-3s%s\n", + (plug->getName()+":").c_str(), + plug->is_enabled() ? "on" : "off", + plug->can_set_enabled() ? "" : " (controlled elsewhere)" ); - con.color(COLOR_RESET); } } - else if (builtin == "type") + } + else if (first == "ls" || first == "dir") + { + ls_helper(con, parts); + } + else if (first == "plug") + { + const char *header_format = "%30s %10s %4s %8s\n"; + const char *row_format = "%30s %10s %4i %8s\n"; + con.print(header_format, "Name", "State", "Cmds", "Enabled"); + + plug_mgr->refresh(); + for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it) { - if (!parts.size()) - { - con.printerr("type: no argument\n"); - return CR_WRONG_USAGE; - } - con << parts[0]; - string builtin_cmd = getBuiltinCommand(parts[0]); - string lua_path = findScript(parts[0] + ".lua"); - string ruby_path = findScript(parts[0] + ".rb"); - Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); - if (builtin_cmd.size()) - { - con << " is a built-in command"; - if (builtin_cmd != parts[0]) - con << " (aliased to " << builtin_cmd << ")"; - con << std::endl; - } - else if (IsAlias(parts[0])) - { - con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; - } - else if (plug) - { - con << " is a command implemented by the plugin " << plug->getName() << std::endl; - } - else if (lua_path.size()) - { - con << " is a Lua script: " << lua_path << std::endl; - } - else if (ruby_path.size()) - { - con << " is a Ruby script: " << ruby_path << std::endl; - } - else + Plugin * plug = it->second; + if (!plug) + continue; + if (parts.size() && std::find(parts.begin(), parts.end(), plug->getName()) == parts.end()) + continue; + color_value color; + switch (plug->getState()) { - con << " is not a recognized command." << std::endl; - plug = plug_mgr->getPluginByName(parts[0]); - if (plug) - con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; - return CR_FAILURE; + case Plugin::PS_LOADED: + color = COLOR_RESET; + break; + case Plugin::PS_UNLOADED: + case Plugin::PS_UNLOADING: + color = COLOR_YELLOW; + break; + case Plugin::PS_LOADING: + color = COLOR_LIGHTBLUE; + break; + case Plugin::PS_BROKEN: + color = COLOR_LIGHTRED; + break; + default: + color = COLOR_LIGHTMAGENTA; + break; } + con.color(color); + con.print(row_format, + plug->getName().c_str(), + Plugin::getStateDescription(plug->getState()), + plug->size(), + (plug->can_be_enabled() + ? (plug->is_enabled() ? "enabled" : "disabled") + : "n/a") + ); + con.color(COLOR_RESET); } - else if (builtin == "keybinding") + } + else if (first == "type") + { + if (!parts.size()) { - if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) - { - std::string keystr = parts[1]; - if (parts[0] == "set") - ClearKeyBindings(keystr); - for (int i = parts.size()-1; i >= 2; i--) - { - if (!AddKeyBinding(keystr, parts[i])) { - con.printerr("Invalid key spec: %s\n", keystr.c_str()); - break; - } - } - } - else if (parts.size() >= 2 && parts[0] == "clear") - { - for (size_t i = 1; i < parts.size(); i++) - { - if (!ClearKeyBindings(parts[i])) { - con.printerr("Invalid key spec: %s\n", parts[i].c_str()); - break; - } - } - } - else if (parts.size() == 2 && parts[0] == "list") - { - std::vector list = ListKeyBindings(parts[1]); - if (list.empty()) - con << "No bindings." << endl; - for (size_t i = 0; i < list.size(); i++) - con << " " << list[i] << endl; - } - else - { - con << "Usage:" << endl - << " keybinding list " << endl - << " keybinding clear [@context]..." << endl - << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl - << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl - << "Later adds, and earlier items within one command have priority." << endl - << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, or Enter)." << endl - << "Context may be used to limit the scope of the binding, by" << endl - << "requiring the current context to have a certain prefix." << endl - << "Current UI context is: " - << Gui::getFocusString(Core::getTopViewscreen()) << endl; - } + con.printerr("type: no argument\n"); + return CR_WRONG_USAGE; } - else if (builtin == "alias") + con << parts[0]; + bool builtin = is_builtin(con, parts[0]); + string lua_path = findScript(parts[0] + ".lua"); + string ruby_path = findScript(parts[0] + ".rb"); + Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); + if (builtin) { - if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) - { - const string &name = parts[1]; - vector cmd(parts.begin() + 2, parts.end()); - if (!AddAlias(name, cmd, parts[0] == "replace")) - { - con.printerr("Could not add alias %s - already exists\n", name.c_str()); - return CR_FAILURE; - } - } - else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) + con << " is a built-in command"; + con << std::endl; + } + else if (IsAlias(parts[0])) + { + con << " is an alias: " << GetAliasCommand(parts[0]) << std::endl; + } + else if (plug) + { + con << " is a command implemented by the plugin " << plug->getName() << std::endl; + } + else if (lua_path.size()) + { + con << " is a Lua script: " << lua_path << std::endl; + } + else if (ruby_path.size()) + { + con << " is a Ruby script: " << ruby_path << std::endl; + } + else + { + con << " is not a recognized command." << std::endl; + plug = plug_mgr->getPluginByName(parts[0]); + if (plug) + con << "Plugin " << parts[0] << " exists and implements " << plug->size() << " commands." << std::endl; + return CR_FAILURE; + } + } + else if (first == "keybinding") + { + if (parts.size() >= 3 && (parts[0] == "set" || parts[0] == "add")) + { + std::string keystr = parts[1]; + if (parts[0] == "set") + ClearKeyBindings(keystr); + for (int i = parts.size()-1; i >= 2; i--) { - if (!RemoveAlias(parts[1])) - { - con.printerr("Could not remove alias %s\n", parts[1].c_str()); - return CR_FAILURE; + if (!AddKeyBinding(keystr, parts[i])) { + con.printerr("Invalid key spec: %s\n", keystr.c_str()); + break; } } - else if (parts.size() >= 1 && (parts[0] == "list")) + } + else if (parts.size() >= 2 && parts[0] == "clear") + { + for (size_t i = 1; i < parts.size(); i++) { - auto aliases = ListAliases(); - for (auto p : aliases) - { - con << p.first << ": " << join_strings(" ", p.second) << endl; + if (!ClearKeyBindings(parts[i])) { + con.printerr("Invalid key spec: %s\n", parts[i].c_str()); + break; } } - else + } + else if (parts.size() == 2 && parts[0] == "list") + { + std::vector list = ListKeyBindings(parts[1]); + if (list.empty()) + con << "No bindings." << endl; + for (size_t i = 0; i < list.size(); i++) + con << " " << list[i] << endl; + } + else + { + con << "Usage:" << endl + << " keybinding list " << endl + << " keybinding clear [@context]..." << endl + << " keybinding set [@context] \"cmdline\" \"cmdline\"..." << endl + << " keybinding add [@context] \"cmdline\" \"cmdline\"..." << endl + << "Later adds, and earlier items within one command have priority." << endl + << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << endl + << "Context may be used to limit the scope of the binding, by" << endl + << "requiring the current context to have a certain prefix." << endl + << "Current UI context is: " + << Gui::getFocusString(Core::getTopViewscreen()) << endl; + } + } + else if (first == "alias") + { + if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) + { + const string &name = parts[1]; + vector cmd(parts.begin() + 2, parts.end()); + if (!AddAlias(name, cmd, parts[0] == "replace")) { - con << "Usage: " << endl - << " alias add|replace " << endl - << " alias delete|clear " << endl - << " alias list" << endl; + con.printerr("Could not add alias %s - already exists\n", name.c_str()); + return CR_FAILURE; } } - else if (builtin == "fpause") + else if (parts.size() >= 2 && (parts[0] == "delete" || parts[0] == "clear")) { - World::SetPauseState(true); - if (auto scr = Gui::getViewscreenByType()) + if (!RemoveAlias(parts[1])) { - scr->worldgen_paused = true; + con.printerr("Could not remove alias %s\n", parts[1].c_str()); + return CR_FAILURE; } - con.print("The game was forced to pause!\n"); } - else if (builtin == "cls") + else if (parts.size() >= 1 && (parts[0] == "list")) { - if (con.is_console()) - ((Console&)con).clear(); - else + auto aliases = ListAliases(); + for (auto p : aliases) { - con.printerr("No console to clear.\n"); - return CR_NEEDS_CONSOLE; + con << p.first << ": " << join_strings(" ", p.second) << endl; } } - else if (builtin == "die") + else { - std::_Exit(666); + con << "Usage: " << endl + << " alias add|replace " << endl + << " alias delete|clear " << endl + << " alias list" << endl; } - else if (builtin == "kill-lua") + } + else if (first == "fpause") + { + World::SetPauseState(true); + if (auto scr = Gui::getViewscreenByType()) { - bool force = false; - for (auto it = parts.begin(); it != parts.end(); ++it) - { - if (*it == "force") - force = true; - } - if (!Lua::Interrupt(force)) - { - con.printerr( - "Failed to register hook. This can happen if you have" - " lua profiling or coverage monitoring enabled. Use" - " 'kill-lua force' to force, but this may disable" - " profiling and coverage monitoring.\n"); - } + scr->worldgen_paused = true; } - else if (builtin == "script") + con.print("The game was forced to pause!\n"); + } + else if (first == "cls" || first == "clear") + { + if (con.is_console()) + ((Console&)con).clear(); + else { - if(parts.size() == 1) - { - loadScriptFile(con, parts[0], false); - } - else - { - con << "Usage:" << endl - << " script " << endl; - return CR_WRONG_USAGE; - } + con.printerr("No console to clear.\n"); + return CR_NEEDS_CONSOLE; } - else if (builtin=="hide") + } + else if (first == "die") + { + std::_Exit(666); + } + else if (first == "kill-lua") + { + bool force = false; + for (auto it = parts.begin(); it != parts.end(); ++it) + { + if (*it == "force") + force = true; + } + if (!Lua::Interrupt(force)) + { + con.printerr( + "Failed to register hook. This can happen if you have" + " lua profiling or coverage monitoring enabled. Use" + " 'kill-lua force' to force, but this may disable" + " profiling and coverage monitoring.\n"); + } + } + else if (first == "script") + { + if(parts.size() == 1) { - if (!getConsole().hide()) + loadScriptFile(con, parts[0], false); + } + else + { + con << "Usage:" << endl + << " script " << endl; + return CR_WRONG_USAGE; + } + } + else if (first == "hide") + { + if (!getConsole().hide()) + { + con.printerr("Could not hide console\n"); + return CR_FAILURE; + } + return CR_OK; + } + else if (first == "show") + { + if (!getConsole().show()) + { + con.printerr("Could not show console\n"); + return CR_FAILURE; + } + return CR_OK; + } + else if (first == "sc-script") + { + if (parts.empty() || parts[0] == "help" || parts[0] == "?") + { + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; + con << "Valid event names (SC_ prefix is optional):" << endl; + for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) { - con.printerr("Could not hide console\n"); - return CR_FAILURE; + std::string name = sc_event_name((state_change_event)i); + if (name != "SC_UNKNOWN") + con << " " << name << endl; } return CR_OK; } - else if (builtin=="show") + else if (parts[0] == "list") { - if (!getConsole().show()) + if(parts.size() < 2) + parts.push_back(""); + if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) { - con.printerr("Could not show console\n"); - return CR_FAILURE; + con << "Unrecognized event name: " << parts[1] << endl; + return CR_WRONG_USAGE; + } + for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) + { + if (!parts[1].size() || (it->event == sc_event_id(parts[1]))) + { + con.print("%s (%s): %s%s\n", sc_event_name(it->event).c_str(), + it->save_specific ? "save-specific" : "global", + it->save_specific ? "/raw/" : "/", + it->path.c_str()); + } } return CR_OK; } - else if (builtin == "sc-script") + else if (parts[0] == "add") { - if (parts.empty() || parts[0] == "help" || parts[0] == "?") + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - con << "Valid event names (SC_ prefix is optional):" << endl; - for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) - { - std::string name = sc_event_name((state_change_event)i); - if (name != "SC_UNKNOWN") - con << " " << name << endl; - } - return CR_OK; + con << "Usage: sc-script add EVENT path-to-script [-save]" << endl; + return CR_WRONG_USAGE; } - else if (parts[0] == "list") + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) { - if(parts.size() < 2) - parts.push_back(""); - if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) - { - con << "Unrecognized event name: " << parts[1] << endl; - return CR_WRONG_USAGE; - } - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (!parts[1].size() || (it->event == sc_event_id(parts[1]))) - { - con.print("%s (%s): %s%s\n", sc_event_name(it->event).c_str(), - it->save_specific ? "save-specific" : "global", - it->save_specific ? "/raw/" : "/", - it->path.c_str()); - } - } - return CR_OK; + con << "Unrecognized event: " << parts[1] << endl; + return CR_FAILURE; } - else if (parts[0] == "add") + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript script(evt, parts[2], save_specific); + for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script add EVENT path-to-script [-save]" << endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) + if (script == *it) { - con << "Unrecognized event: " << parts[1] << endl; + con << "Script already registered" << endl; return CR_FAILURE; } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript script(evt, parts[2], save_specific); - for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) - { - if (script == *it) - { - con << "Script already registered" << endl; - return CR_FAILURE; - } - } - state_change_scripts.push_back(script); - return CR_OK; } - else if (parts[0] == "remove") + state_change_scripts.push_back(script); + return CR_OK; + } + else if (parts[0] == "remove") + { + if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) + { + con << "Usage: sc-script remove EVENT path-to-script [-save]" << endl; + return CR_WRONG_USAGE; + } + state_change_event evt = sc_event_id(parts[1]); + if (evt == SC_UNKNOWN) { - if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) - { - con << "Usage: sc-script remove EVENT path-to-script [-save]" << endl; - return CR_WRONG_USAGE; - } - state_change_event evt = sc_event_id(parts[1]); - if (evt == SC_UNKNOWN) - { - con << "Unrecognized event: " << parts[1] << endl; - return CR_FAILURE; - } - bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); - StateChangeScript tmp(evt, parts[2], save_specific); - auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); - if (it != state_change_scripts.end()) - { - state_change_scripts.erase(it); - return CR_OK; - } - else - { - con << "Unrecognized script" << endl; - return CR_FAILURE; - } + con << "Unrecognized event: " << parts[1] << endl; + return CR_FAILURE; + } + bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); + StateChangeScript tmp(evt, parts[2], save_specific); + auto it = std::find(state_change_scripts.begin(), state_change_scripts.end(), tmp); + if (it != state_change_scripts.end()) + { + state_change_scripts.erase(it); + return CR_OK; } else { - con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; - return CR_WRONG_USAGE; + con << "Unrecognized script" << endl; + return CR_FAILURE; } } - else if (builtin == "devel/dump-rpc") + else { - if (parts.size() == 1) - { - std::ofstream file(parts[0]); - CoreService core; - core.dumpMethods(file); + con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; + return CR_WRONG_USAGE; + } + } + else if (first == "devel/dump-rpc") + { + if (parts.size() == 1) + { + std::ofstream file(parts[0]); + CoreService core; + core.dumpMethods(file); - for (auto & it : *plug_mgr) - { - Plugin * plug = it.second; - if (!plug) - continue; + for (auto & it : *plug_mgr) + { + Plugin * plug = it.second; + if (!plug) + continue; - std::unique_ptr svc(plug->rpc_connect(con)); - if (!svc) - continue; + std::unique_ptr svc(plug->rpc_connect(con)); + if (!svc) + continue; - file << "// Plugin: " << plug->getName() << endl; - svc->dumpMethods(file); - } - } - else - { - con << "Usage: devel/dump-rpc \"filename\"" << endl; - return CR_WRONG_USAGE; + file << "// Plugin: " << plug->getName() << endl; + svc->dumpMethods(file); } } - else if (RunAlias(con, first, parts, res)) + else + { + con << "Usage: devel/dump-rpc \"filename\"" << endl; + return CR_WRONG_USAGE; + } + } + else if (RunAlias(con, first, parts, res)) + { + return res; + } + else + { + res = plug_mgr->InvokeCommand(con, first, parts); + if (res == CR_WRONG_USAGE) { - return res; + help_helper(con, first); } - else + else if (res == CR_NOT_IMPLEMENTED) { - res = plug_mgr->InvokeCommand(con, first, parts); - if(res == CR_NOT_IMPLEMENTED) + string completed; + string filename = findScript(first + ".lua"); + bool lua = filename != ""; + if ( !lua ) { + filename = findScript(first + ".rb"); + } + if ( lua ) + res = runLuaScript(con, first, parts); + else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) + res = runRubyScript(con, plug_mgr, filename, parts); + else if ( try_autocomplete(con, first, completed) ) + res = CR_NOT_IMPLEMENTED; + else + con.printerr("%s is not a recognized command.\n", first.c_str()); + if (res == CR_NOT_IMPLEMENTED) { - string completed; - string filename = findScript(first + ".lua"); - bool lua = filename != ""; - if ( !lua ) { - filename = findScript(first + ".rb"); - } - if ( lua ) - res = runLuaScript(con, first, parts); - else if ( filename != "" && plug_mgr->ruby && plug_mgr->ruby->is_enabled() ) - res = runRubyScript(con, plug_mgr, filename, parts); - else if ( try_autocomplete(con, first, completed) ) - res = CR_NOT_IMPLEMENTED; - else - con.printerr("%s is not a recognized command.\n", first.c_str()); - if (res == CR_NOT_IMPLEMENTED) + Plugin *p = plug_mgr->getPluginByName(first); + if (p) { - Plugin *p = plug_mgr->getPluginByName(first); - if (p) - { - con.printerr("%s is a plugin ", first.c_str()); - if (p->getState() == Plugin::PS_UNLOADED) - con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", - first.c_str()); - else if (p->size()) - con.printerr("that implements %zi commands - see \"ls %s\" for details\n", - p->size(), first.c_str()); - else - con.printerr("but does not implement any commands\n"); - } + con.printerr("%s is a plugin ", first.c_str()); + if (p->getState() == Plugin::PS_UNLOADED) + con.printerr("that is not loaded - try \"load %s\" or check stderr.log\n", + first.c_str()); + else if (p->size()) + con.printerr("that implements %zi commands - see \"help %s\" for details\n", + p->size(), first.c_str()); + else + con.printerr("but does not implement any commands\n"); } } - else if (res == CR_NEEDS_CONSOLE) - con.printerr("%s needs interactive console to work.\n", first.c_str()); - return res; } - - return CR_OK; + else if (res == CR_NEEDS_CONSOLE) + con.printerr("%s needs an interactive console to work.\n" + "Please run this command from the DFHack terminal.\n", first.c_str()); + return res; } - return CR_NOT_IMPLEMENTED; + return CR_OK; } bool Core::loadScriptFile(color_ostream &out, string fname, bool silent) @@ -1450,13 +1301,12 @@ static void run_dfhack_init(color_ostream &out, Core *core) return; } + // load baseline defaults + core->loadScriptFile(out, "dfhack-config/init/default.dfhack.init", false); + + // load user overrides std::vector prefixes(1, "dfhack"); - size_t count = loadScriptFiles(core, out, prefixes, "."); - if (!count || !Filesystem::isfile("dfhack.init")) - { - core->runCommand(out, "gui/no-dfhack-init"); - core->loadScriptFile(out, "dfhack.init-example", false); - } + loadScriptFiles(core, out, prefixes, "dfhack-config/init"); } // Load dfhack.init in a dedicated thread (non-interactive console mode) @@ -1472,12 +1322,14 @@ void fInitthread(void * iodata) // A thread function... for the interactive console. void fIOthread(void * iodata) { + static const char * HISTORY_FILE = "dfhack-config/dfhack.history"; + IODATA * iod = ((IODATA*) iodata); Core * core = iod->core; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; CommandHistory main_history; - main_history.load("dfhack.history"); + main_history.load(HISTORY_FILE); Console & con = core->getConsole(); if (plug_mgr == 0) @@ -1518,7 +1370,7 @@ void fIOthread(void * iodata) { // a proper, non-empty command was entered main_history.add(command); - main_history.save("dfhack.history"); + main_history.save(HISTORY_FILE); } auto rv = core->runCommand(con, command); @@ -1564,7 +1416,6 @@ Core::Core() : last_local_map_ptr = NULL; last_pause_state = false; top_viewscreen = NULL; - screen_window = NULL; color_ostream::log_errors_to_stderr = true; @@ -1835,8 +1686,6 @@ bool Core::Init() cerr << "Starting DF input capture thread.\n"; // set up hotkey capture d->hotkeythread = std::thread(fHKthread, (void *) temp); - screen_window = new Windows::top_level_window(); - screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; modstate = 0; @@ -1980,14 +1829,6 @@ bool Core::isSuspended(void) return ownerThread.load() == std::this_thread::get_id(); } -int Core::TileUpdate() -{ - if(!started) - return false; - screen_window->paint(); - return true; -} - void Core::doUpdate(color_ostream &out, bool first_update) { Lua::Core::Reset(out, "DF code execution"); @@ -2226,7 +2067,11 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve auto i = table.find(event); if ( i != table.end() ) { const std::vector& set = i->second; - loadScriptFiles(this, out, set, "." ); + + // load baseline defaults + this->loadScriptFile(out, "dfhack-config/init/default." + set[0] + ".init", false); + + loadScriptFiles(this, out, set, "dfhack-config/init"); loadScriptFiles(this, out, set, rawFolder); loadScriptFiles(this, out, set, rawFolder + "objects/"); } @@ -2544,20 +2389,36 @@ bool Core::SelectHotkey(int sym, int modifiers) std::string cmd; + DEBUG(keybinding).print("checking hotkeys for sym=%d, modifiers=%x\n", sym, modifiers); + { std::lock_guard lock(HotkeyMutex); // Check the internal keybindings std::vector &bindings = key_bindings[sym]; for (int i = bindings.size()-1; i >= 0; --i) { - if (bindings[i].modifiers != modifiers) + auto &binding = bindings[i]; + DEBUG(keybinding).print("examining hotkey with commandline: '%s'\n", binding.cmdline.c_str()); + + if (binding.modifiers != modifiers) { + DEBUG(keybinding).print("skipping keybinding due to modifiers mismatch: 0x%x != 0x%x\n", + binding.modifiers, modifiers); continue; - if (!bindings[i].focus.empty() && - !prefix_matches(bindings[i].focus, Gui::getFocusString(screen))) + } + string focusString = Gui::getFocusString(screen); + if (!binding.focus.empty() && !prefix_matches(binding.focus, focusString)) { + DEBUG(keybinding).print("skipping keybinding due to focus string mismatch: '%s' !~ '%s'\n", + focusString.c_str(), binding.focus.c_str()); continue; - if (!plug_mgr->CanInvokeHotkey(bindings[i].command[0], screen)) + } + if (!plug_mgr->CanInvokeHotkey(binding.command[0], screen)) { + DEBUG(keybinding).print("skipping keybinding due to hotkey guard rejection (command: '%s')\n", + binding.command[0].c_str()); continue; - cmd = bindings[i].cmdline; + } + + cmd = binding.cmdline; + DEBUG(keybinding).print("matched hotkey\n"); break; } @@ -2621,6 +2482,9 @@ static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string if (keyspec.size() == 1 && keyspec[0] >= 'A' && keyspec[0] <= 'Z') { *psym = SDL::K_a + (keyspec[0]-'A'); return true; + } else if (keyspec.size() == 1 && keyspec[0] == '`') { + *psym = SDL::K_BACKQUOTE; + return true; } else if (keyspec.size() == 1 && keyspec[0] >= '0' && keyspec[0] <= '9') { *psym = SDL::K_0 + (keyspec[0]-'0'); return true; @@ -2922,5 +2786,4 @@ TYPE * Core::get##TYPE() \ } MODULE_GETTER(Materials); -MODULE_GETTER(Notes); MODULE_GETTER(Graphic); diff --git a/library/Debug.cpp b/library/Debug.cpp index 7ac981d30..9b13af168 100644 --- a/library/Debug.cpp +++ b/library/Debug.cpp @@ -198,7 +198,7 @@ DebugCategory::cstring_ref DebugCategory::plugin() const noexcept //! standards only provide runtime checks if an atomic type is lock free struct failIfEnumAtomicIsNotLockFree { failIfEnumAtomicIsNotLockFree() { - std::atomic test; + std::atomic test(DebugCategory::LINFO); if (test.is_lock_free()) return; std::cerr << __FILE__ << ':' << __LINE__ diff --git a/library/Hooks-egg.cpp b/library/Hooks-egg.cpp deleted file mode 100644 index c98cf5da2..000000000 --- a/library/Hooks-egg.cpp +++ /dev/null @@ -1,92 +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 "Core.h" -#include "Hooks.h" -#include - -// hook - called before rendering -DFhackCExport int egg_init(void) -{ - // reroute stderr - freopen("stderr.log", "w", stderr); - // we don't reroute stdout until we figure out if this should be done at all - // See: Console-linux.cpp - fprintf(stderr,"dfhack: hooking successful\n"); - return true; -} - -// hook - called before rendering -DFhackCExport int egg_shutdown(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Shutdown(); -} - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.Update(); -} -// hook - called before rendering -DFhackCExport int egg_prerender(void) -{ - DFHack::Core & c = DFHack::Core::getInstance(); - return c.TileUpdate(); -} - -// hook - called for each SDL event, returns 0 when the event has been consumed. 1 otherwise -DFhackCExport int egg_sdl_event(SDL::Event* event) -{ - // if the event is valid, intercept - if( event != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - return c.DFH_SDL_Event(event); - } - return true; -} - -// return this if you want to kill the event. -const int curses_error = -1; -// hook - ncurses event, -1 signifies error. -DFhackCExport int egg_curses_event(int orig_return) -{ - /* - if(orig_return != -1) - { - DFHack::Core & c = DFHack::Core::getInstance(); - int out; - return c.ncurses_wgetch(orig_return,); - }*/ - return orig_return; -} diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 101b645fd..9f713244d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -68,6 +68,7 @@ distribution. #include "df/activity_entry.h" #include "df/activity_event.h" +#include "df/enabler.h" #include "df/job.h" #include "df/job_item.h" #include "df/building.h" @@ -78,9 +79,6 @@ distribution. #include "df/identity.h" #include "df/nemesis_record.h" #include "df/historical_figure.h" -#include "df/historical_entity.h" -#include "df/entity_position.h" -#include "df/entity_position_assignment.h" #include "df/histfig_entity_link_positionst.h" #include "df/plant_raw.h" #include "df/creature_raw.h" @@ -95,7 +93,6 @@ distribution. #include "df/unit_misc_trait.h" #include "df/proj_itemst.h" #include "df/itemdef.h" -#include "df/enabler.h" #include "df/feature_init.h" #include "df/plant.h" #include "df/specific_ref.h" @@ -117,68 +114,6 @@ using Random::PerlinNoise3D; void dfhack_printerr(lua_State *S, const std::string &str); -void Lua::Push(lua_State *state, const Units::NoblePosition &pos) -{ - lua_createtable(state, 0, 3); - Lua::PushDFObject(state, pos.entity); - lua_setfield(state, -2, "entity"); - Lua::PushDFObject(state, pos.assignment); - lua_setfield(state, -2, "assignment"); - Lua::PushDFObject(state, pos.position); - lua_setfield(state, -2, "position"); -} - -void Lua::Push(lua_State *state, df::coord pos) -{ - lua_createtable(state, 0, 3); - lua_pushinteger(state, pos.x); - lua_setfield(state, -2, "x"); - lua_pushinteger(state, pos.y); - lua_setfield(state, -2, "y"); - lua_pushinteger(state, pos.z); - lua_setfield(state, -2, "z"); -} - -void Lua::Push(lua_State *state, df::coord2d pos) -{ - lua_createtable(state, 0, 2); - lua_pushinteger(state, pos.x); - lua_setfield(state, -2, "x"); - lua_pushinteger(state, pos.y); - lua_setfield(state, -2, "y"); -} - -int Lua::PushPosXYZ(lua_State *state, df::coord pos) -{ - if (!pos.isValid()) - { - lua_pushnil(state); - return 1; - } - else - { - lua_pushinteger(state, pos.x); - lua_pushinteger(state, pos.y); - lua_pushinteger(state, pos.z); - return 3; - } -} - -int Lua::PushPosXY(lua_State *state, df::coord2d pos) -{ - if (!pos.isValid()) - { - lua_pushnil(state); - return 1; - } - else - { - lua_pushinteger(state, pos.x); - lua_pushinteger(state, pos.y); - return 2; - } -} - static df::coord2d CheckCoordXY(lua_State *state, int base, bool vararg = false) { df::coord2d p; @@ -1359,6 +1294,38 @@ static void OpenRandom(lua_State *state) lua_pop(state, 1); } + +/********************************* +* Commandline history repository * +**********************************/ + +static std::map commandHistories; + +static CommandHistory * ensureCommandHistory(std::string id, + std::string src_file) { + if (!commandHistories.count(id)) { + commandHistories[id].load(src_file.c_str()); + } + return &commandHistories[id]; +} + +static int getCommandHistory(lua_State *state) +{ + std::string id = lua_tostring(state, 1); + std::string src_file = lua_tostring(state, 2); + std::vector entries; + ensureCommandHistory(id, src_file)->getEntries(entries); + Lua::PushVector(state, entries); + return 1; +} + +static void addCommandToHistory(std::string id, std::string src_file, + std::string command) { + CommandHistory *history = ensureCommandHistory(id, src_file); + history->add(command); + history->save(src_file.c_str()); +} + /************************ * Wrappers for C++ API * ************************/ @@ -1450,6 +1417,12 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP_VERSION_FUNC(gitXmlMatch, git_xml_match), WRAP_VERSION_FUNC(isRelease, is_release), WRAP_VERSION_FUNC(isPrerelease, is_prerelease), + WRAP(addCommandToHistory), + { NULL, NULL } +}; + +static const luaL_Reg dfhack_funcs[] = { + { "getCommandHistory", getCommandHistory }, { NULL, NULL } }; @@ -1475,6 +1448,16 @@ static int gui_getDwarfmodeViewDims(lua_State *state) return 1; } +static int gui_getMousePos(lua_State *L) +{ + auto pos = Gui::getMousePos(); + if (pos.isValid()) + Lua::Push(L, pos); + else + lua_pushnil(L); + return 1; +} + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getCurViewscreen), WRAPM(Gui, getFocusString), @@ -1507,6 +1490,7 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { static const luaL_Reg dfhack_gui_funcs[] = { { "getDwarfmodeViewDims", gui_getDwarfmodeViewDims }, + { "getMousePos", gui_getMousePos }, { NULL, NULL } }; @@ -2205,6 +2189,7 @@ static const luaL_Reg dfhack_buildings_funcs[] = { static const LuaWrapper::FunctionReg dfhack_constructions_module[] = { WRAPM(Constructions, designateNew), + WRAPM(Constructions, insert), { NULL, NULL } }; @@ -2217,8 +2202,16 @@ static int constructions_designateRemove(lua_State *L) return 2; } +static int constructions_findAtTile(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Constructions::findAtTile(pos)); + return 1; +} + static const luaL_Reg dfhack_constructions_funcs[] = { { "designateRemove", constructions_designateRemove }, + { "findAtTile", constructions_findAtTile }, { NULL, NULL } }; @@ -2234,18 +2227,12 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = { static int screen_getMousePos(lua_State *L) { - auto pos = Screen::getMousePos(); - lua_pushinteger(L, pos.x); - lua_pushinteger(L, pos.y); - return 2; + return Lua::PushPosXY(L, Screen::getMousePos()); } static int screen_getWindowSize(lua_State *L) { - auto pos = Screen::getWindowSize(); - lua_pushinteger(L, pos.x); - lua_pushinteger(L, pos.y); - return 2; + return Lua::PushPosXY(L, Screen::getWindowSize()); } static int screen_paintTile(lua_State *L) @@ -2322,6 +2309,21 @@ static int screen_findGraphicsTile(lua_State *L) } } +static int screen_hideGuard(lua_State *L) { + df::viewscreen *screen = dfhack_lua_viewscreen::get_pointer(L, 1, false); + luaL_checktype(L, 2, LUA_TFUNCTION); + + // remove screen from the stack so it doesn't get returned as an output + lua_remove(L, 1); + + Screen::Hide hideGuard(screen, Screen::Hide::RESTORE_AT_TOP); + + int nargs = lua_gettop(L) - 1; + lua_call(L, nargs, LUA_MULTRET); + + return lua_gettop(L); +} + namespace { int screen_show(lua_State *L) @@ -2424,6 +2426,7 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, + CWRAP(hideGuard, screen_hideGuard), CWRAP(show, screen_show), CWRAP(dismiss, screen_dismiss), CWRAP(isDismissed, screen_isDismissed), @@ -3023,6 +3026,99 @@ static int internal_findScript(lua_State *L) return 1; } +static int internal_listPlugins(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + int i = 1; + lua_newtable(L); + for (auto it = plugins->begin(); it != plugins->end(); ++it) + { + lua_pushinteger(L, i++); + lua_pushstring(L, it->first.c_str()); + lua_settable(L, -3); + } + return 1; +} + +static int internal_listCommands(lua_State *L) +{ + auto plugins = Core::getInstance().getPluginManager(); + + const char *name = luaL_checkstring(L, 1); + + auto plugin = plugins->getPluginByName(name); + if (!plugin) + { + lua_pushnil(L); + return 1; + } + + size_t num_commands = plugin->size(); + lua_newtable(L); + for (size_t i = 0; i < num_commands; ++i) + { + lua_pushinteger(L, i + 1); + lua_pushstring(L, (*plugin)[i].name.c_str()); + lua_settable(L, -3); + } + return 1; +} + +static const PluginCommand * getPluginCommand(const char * command) +{ + auto plugins = Core::getInstance().getPluginManager(); + auto plugin = plugins->getPluginByCommand(command); + if (!plugin) + { + return NULL; + } + + size_t num_commands = plugin->size(); + for (size_t i = 0; i < num_commands; ++i) + { + if ((*plugin)[i].name == command) + return &(*plugin)[i]; + } + + // not found (somehow) + return NULL; +} + +static int internal_getCommandHelp(lua_State *L) +{ + const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1)); + if (!pc) + { + lua_pushnil(L); + return 1; + } + + std::string help = pc->description; + if (help.size() && help[help.size()-1] != '.') + help += "."; + if (pc->usage.size()) + help += "\n" + pc->usage; + lua_pushstring(L, help.c_str()); + return 1; +} + +static int internal_getCommandDescription(lua_State *L) +{ + const PluginCommand *pc = getPluginCommand(luaL_checkstring(L, 1)); + if (!pc) + { + lua_pushnil(L); + return 1; + } + + std::string help = pc->description; + if (help.size() && help[help.size()-1] != '.') + help += "."; + lua_pushstring(L, help.c_str()); + return 1; +} + static int internal_threadid(lua_State *L) { std::stringstream ss; @@ -3079,6 +3175,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, + { "adjustOffset", internal_adjustOffset }, { "getMemRanges", internal_getMemRanges }, { "patchMemory", internal_patchMemory }, @@ -3094,6 +3191,10 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "removeScriptPath", internal_removeScriptPath }, { "getScriptPaths", internal_getScriptPaths }, { "findScript", internal_findScript }, + { "listPlugins", internal_listPlugins }, + { "listCommands", internal_listCommands }, + { "getCommandHelp", internal_getCommandHelp }, + { "getCommandDescription", internal_getCommandDescription }, { "threadid", internal_threadid }, { "md5File", internal_md5file }, { NULL, NULL } @@ -3113,6 +3214,7 @@ void OpenDFHackApi(lua_State *state) OpenRandom(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); + luaL_setfuncs(state, dfhack_funcs, 0); OpenModule(state, "gui", dfhack_gui_module, dfhack_gui_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index cef46c053..3cd6a1f7d 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -41,6 +41,7 @@ distribution. #include "modules/World.h" #include "modules/Gui.h" #include "modules/Job.h" +#include "modules/Screen.h" #include "modules/Translation.h" #include "modules/Units.h" @@ -51,11 +52,15 @@ distribution. #include "DFHackVersion.h" #include "PluginManager.h" +#include "df/building.h" +#include "df/enabler.h" +#include "df/entity_position.h" +#include "df/entity_position_assignment.h" +#include "df/historical_entity.h" +#include "df/item.h" #include "df/job.h" #include "df/job_item.h" -#include "df/building.h" #include "df/unit.h" -#include "df/item.h" #include "df/world.h" #include @@ -81,6 +86,126 @@ inline void AssertCoreSuspend(lua_State *state) assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended()); } +/* + * Lua Push methods + */ + +void DFHack::Lua::Push(lua_State *state, const Units::NoblePosition &pos) +{ + lua_createtable(state, 0, 3); + Lua::PushDFObject(state, pos.entity); + lua_setfield(state, -2, "entity"); + Lua::PushDFObject(state, pos.assignment); + lua_setfield(state, -2, "assignment"); + Lua::PushDFObject(state, pos.position); + lua_setfield(state, -2, "position"); +} + +void DFHack::Lua::Push(lua_State *state, df::coord pos) +{ + lua_createtable(state, 0, 3); + lua_pushinteger(state, pos.x); + lua_setfield(state, -2, "x"); + lua_pushinteger(state, pos.y); + lua_setfield(state, -2, "y"); + lua_pushinteger(state, pos.z); + lua_setfield(state, -2, "z"); +} + +void DFHack::Lua::Push(lua_State *state, df::coord2d pos) +{ + lua_createtable(state, 0, 2); + lua_pushinteger(state, pos.x); + lua_setfield(state, -2, "x"); + lua_pushinteger(state, pos.y); + lua_setfield(state, -2, "y"); +} + +void DFHack::Lua::GetVector(lua_State *state, std::vector &pvec) +{ + lua_pushnil(state); // first key + while (lua_next(state, 1) != 0) + { + pvec.push_back(lua_tostring(state, -1)); + lua_pop(state, 1); // remove value, leave key + } +} + +void DFHack::Lua::PushInterfaceKeys(lua_State *L, + const std::set &keys) { + lua_createtable(L, 0, keys.size() + 5); + + for (auto &key : keys) + { + if (auto name = enum_item_raw_key(key)) + lua_pushstring(L, name); + else + lua_pushinteger(L, key); + + lua_pushboolean(L, true); + lua_rawset(L, -3); + + int charval = Screen::keyToChar(key); + if (charval >= 0) + { + lua_pushinteger(L, charval); + lua_setfield(L, -2, "_STRING"); + } + } + + if (df::global::enabler) { + if (df::global::enabler->mouse_lbut_down) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_L"); + } + if (df::global::enabler->mouse_rbut_down) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_R"); + } + if (df::global::enabler->mouse_lbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_L_DOWN"); + df::global::enabler->mouse_lbut = 0; + } + if (df::global::enabler->mouse_rbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_R_DOWN"); + df::global::enabler->mouse_rbut = 0; + } + } +} + +int DFHack::Lua::PushPosXYZ(lua_State *state, df::coord pos) +{ + if (!pos.isValid()) + { + lua_pushnil(state); + return 1; + } + else + { + lua_pushinteger(state, pos.x); + lua_pushinteger(state, pos.y); + lua_pushinteger(state, pos.z); + return 3; + } +} + +int DFHack::Lua::PushPosXY(lua_State *state, df::coord2d pos) +{ + if (!pos.isValid()) + { + lua_pushnil(state); + return 1; + } + else + { + lua_pushinteger(state, pos.x); + lua_pushinteger(state, pos.y); + return 2; + } +} + /* * Public DF object reference handling API */ @@ -1145,7 +1270,7 @@ bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state, return false; if (!hfile) - hfile = "lua.history"; + hfile = "dfhack-config/lua.history"; if (!prompt) prompt = "lua"; diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index 56af85afe..b959e756e 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -168,32 +168,62 @@ std::string to_search_normalized(const std::string &str) return result; } -bool word_wrap(std::vector *out, const std::string &str, size_t line_length) +bool word_wrap(std::vector *out, const std::string &str, size_t line_length, + word_wrap_whitespace_mode mode) { - out->clear(); - std::istringstream input(str); - std::string out_line; - std::string word; - if (input >> word) + if (line_length == 0) + line_length = SIZE_MAX; + + std::string line; + size_t break_pos = 0; + bool ignore_whitespace = false; + + for (auto &c : str) { - out_line += word; - // size_t remaining = line_length - std::min(line_length, word.length()); - while (input >> word) + if (c == '\n') + { + out->push_back(line); + line.clear(); + break_pos = 0; + ignore_whitespace = (mode == WSMODE_TRIM_LEADING); + continue; + } + + if (isspace(c)) + { + if (ignore_whitespace || (mode == WSMODE_COLLAPSE_ALL && break_pos == line.length())) + continue; + + line.push_back((mode == WSMODE_COLLAPSE_ALL) ? ' ' : c); + break_pos = line.length(); + } + else { - if (out_line.length() + word.length() + 1 <= line_length) + line.push_back(c); + ignore_whitespace = false; + } + + if (line.length() > line_length) + { + if (break_pos > 0) { - out_line += ' '; - out_line += word; + // Break before last space, and skip that space + out->push_back(line.substr(0, break_pos - 1)); } else { - out->push_back(out_line); - out_line = word; + // Single word is too long, just break it + out->push_back(line.substr(0, line_length)); + break_pos = line_length; } + line = line.substr(break_pos); + break_pos = 0; + ignore_whitespace = (mode == WSMODE_TRIM_LEADING); } - if (out_line.length()) - out->push_back(out_line); } + if (line.length()) + out->push_back(line); + return true; } diff --git a/library/PlugLoad-windows.cpp b/library/PlugLoad-windows.cpp index 96c2e900a..848d25f50 100644 --- a/library/PlugLoad-windows.cpp +++ b/library/PlugLoad-windows.cpp @@ -35,7 +35,6 @@ distribution. #include #include "tinythread.h" -#include "modules/Graphic.h" #include "../plugins/uicommon.h" /* diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index a1b1bd293..223abfeac 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -503,8 +503,6 @@ command_result Plugin::invoke(color_ostream &out, const std::string & command, s { cr = cmd.function(out, parameters); } - if (cr == CR_WRONG_USAGE && !cmd.usage.empty()) - out << "Usage:\n" << cmd.usage << flush; break; } } diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index c934754b0..734b80702 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -61,6 +61,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "json/json.h" +using namespace std; using namespace DFHack; using dfproto::CoreTextNotification; diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 520d91061..75ee62f01 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -36,6 +36,7 @@ distribution. #include "MiscUtils.h" +using namespace std; using namespace DFHack; /* diff --git a/library/include/Console.h b/library/include/Console.h index 0882ba449..39a19b152 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -32,6 +32,7 @@ distribution. #include #include #include +#include namespace tthread { class mutex; @@ -44,7 +45,7 @@ namespace DFHack class CommandHistory { public: - CommandHistory(std::size_t capacity = 100) + CommandHistory(std::size_t capacity = 5000) { this->capacity = capacity; } @@ -114,6 +115,12 @@ namespace DFHack { history.pop_front(); } + /// adds the current list of entries to the given vector + void getEntries(std::vector &entries) + { + for (auto &entry : history) + entries.push_back(entry); + } private: std::size_t capacity; std::deque history; diff --git a/library/include/Core.h b/library/include/Core.h index 1648ae113..531d1b581 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -58,7 +58,6 @@ namespace DFHack class Process; class Module; class Materials; - class Notes; struct VersionInfo; class VersionInfoFactory; class PluginManager; @@ -69,10 +68,6 @@ namespace DFHack namespace Lua { namespace Core { DFHACK_EXPORT void Reset(color_ostream &out, const char *where); } } - namespace Windows - { - class df_window; - } namespace Screen { @@ -126,12 +121,6 @@ namespace DFHack friend int ::SDL_Init(uint32_t flags); friend int ::wgetch(WINDOW * w); #endif - friend int ::egg_init(void); - friend int ::egg_shutdown(void); - friend int ::egg_tick(void); - friend int ::egg_prerender(void); - friend int ::egg_sdl_event(SDL::Event* event); - friend int ::egg_curses_event(int orig_return); public: /// Get the single Core instance or make one. static Core& getInstance() @@ -146,8 +135,6 @@ namespace DFHack /// get the materials module Materials * getMaterials(); - /// get the notes module - Notes * getNotes(); /// get the graphic module Graphic * getGraphic(); /// sets the current hotkey command @@ -193,7 +180,6 @@ namespace DFHack std::unique_ptr p; std::shared_ptr vinfo; - DFHack::Windows::df_window * screen_window; static void print(const char *format, ...) Wformat(printf,1,2); static void printerr(const char *format, ...) Wformat(printf,1,2); @@ -213,7 +199,6 @@ namespace DFHack bool Init(); int Update (void); - int TileUpdate (void); int Shutdown (void); int DFH_SDL_Event(SDL::Event* event); bool ncurses_wgetch(int in, int & out); @@ -242,7 +227,6 @@ namespace DFHack struct { Materials * pMaterials; - Notes * pNotes; Graphic * pGraphic; } s_mods; std::vector> allModules; diff --git a/library/include/DFHack.h b/library/include/DFHack.h index 8a094cf86..0a5183adc 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -54,7 +54,6 @@ distribution. // DFHack modules #include "modules/Buildings.h" -#include "modules/Engravings.h" #include "modules/Materials.h" #include "modules/Constructions.h" #include "modules/Units.h" diff --git a/library/include/Debug.h b/library/include/Debug.h index 63811a51e..4cad178dc 100644 --- a/library/include/Debug.h +++ b/library/include/Debug.h @@ -326,7 +326,7 @@ public: DFHack::DebugCategory::LDEBUG, ## __VA_ARGS__) /*! - * Open a line for error level debug output if enabled + * Open a line for info level debug output if enabled * * Important debug messages when some rarely changed state changes. Example * would be when a debug category filtering level changes. diff --git a/library/include/Hooks.h b/library/include/Hooks.h index f5ef7079c..d17b96acf 100644 --- a/library/include/Hooks.h +++ b/library/include/Hooks.h @@ -47,7 +47,6 @@ namespace SDL // these functions are here because they call into DFHack::Core and therefore need to // be declared as friend functions/known #ifdef _DARWIN -#include "modules/Graphic.h" DFhackCExport int DFH_SDL_NumJoysticks(void); DFhackCExport void DFH_SDL_Quit(void); DFhackCExport int DFH_SDL_PollEvent(SDL::Event* event); @@ -75,21 +74,3 @@ DFhackCExport void * SDL_GetVideoSurface(void); DFhackCExport int SDL_SemWait(vPtr sem); DFhackCExport int SDL_SemPost(vPtr sem); - -// hook - called early from DF's main() -DFhackCExport int egg_init(void); - -// hook - called before rendering -DFhackCExport int egg_shutdown(void); - -// hook - called for each game tick (or more often) -DFhackCExport int egg_tick(void); - -// hook - called before rendering -DFhackCExport int egg_prerender(void); - -// hook - called for each SDL event, can filter both the event and the return value -DFhackCExport int egg_sdl_event(SDL::Event* event); - -// hook - ncurses event. return -1 to consume -DFhackCExport int egg_curses_event(int orig_return); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index df89d184f..e4245f09a 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -30,6 +30,8 @@ distribution. #include #include +#include "df/interfacest.h" + #include "ColorText.h" #include "DataDefs.h" @@ -321,6 +323,8 @@ namespace DFHack {namespace Lua { Push(L, val); lua_setfield(L, idx, name); } + DFHACK_EXPORT void PushInterfaceKeys(lua_State *L, const std::set &keys); + template void PushVector(lua_State *state, const T &pvec, bool addn = false) { @@ -339,6 +343,8 @@ namespace DFHack {namespace Lua { } } + DFHACK_EXPORT void GetVector(lua_State *state, std::vector &pvec); + DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); @@ -350,6 +356,13 @@ namespace DFHack {namespace Lua { lua_settable(state, -3); } + template + void Push(lua_State *L, const std::map &pmap) { + lua_createtable(L, 0, pmap.size()); + for (auto &entry : pmap) + TableInsert(L, entry.first, entry.second); + } + 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/MiscUtils.h b/library/include/MiscUtils.h index 7a5f050a2..d089640ef 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -24,8 +24,10 @@ distribution. #pragma once #include "Export.h" +#include #include #include +#include #include #include #include @@ -33,10 +35,6 @@ distribution. #include #include -using std::ostream; -using std::stringstream; -using std::endl; - #if defined(_MSC_VER) #define DFHACK_FUNCTION_SIG __FUNCSIG__ #elif defined(__GNUC__) @@ -83,9 +81,9 @@ using std::make_unique; } template -void print_bits ( T val, ostream& out ) +void print_bits ( T val, std::ostream& out ) { - stringstream strs; + std::stringstream strs; T n_bits = sizeof ( val ) * CHAR_BIT; int cnt; for ( unsigned i = 0; i < n_bits; ++i ) @@ -93,24 +91,24 @@ void print_bits ( T val, ostream& out ) cnt = i/10; strs << cnt << " "; } - strs << endl; + strs << std::endl; for ( unsigned i = 0; i < n_bits; ++i ) { cnt = i%10; strs << cnt << " "; } - strs << endl; + strs << std::endl; for ( unsigned i = 0; i < n_bits; ++i ) { strs << "--"; } - strs << endl; + strs << std::endl; for ( unsigned i = 0; i < n_bits; ++i ) { strs<< !!( val & 1 ) << " "; val >>= 1; } - strs << endl; + strs << std::endl; out << strs.str(); } @@ -389,9 +387,48 @@ DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str); DFHACK_EXPORT std::string to_search_normalized(const std::string &str); +static inline std::string int_to_string(const int n) { + std::ostringstream ss; + ss << n; + return ss.str(); +} + +static inline int string_to_int(const std::string s, int default_ = 0) { + try { + return std::stoi(s); + } + catch (std::exception&) { + return default_; + } +} + +// trim from start +static inline std::string <rim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](char x){ return !std::isspace(x); })); + return s; +} + +// trim from end +static inline std::string &rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](char x){ return !std::isspace(x); }).base(), s.end()); + return s; +} + +// trim from both ends +static inline std::string &trim(std::string &s) { + return ltrim(rtrim(s)); +} + +enum word_wrap_whitespace_mode { + WSMODE_KEEP_ALL, + WSMODE_COLLAPSE_ALL, + WSMODE_TRIM_LEADING +}; + DFHACK_EXPORT bool word_wrap(std::vector *out, const std::string &str, - size_t line_length = 80); + size_t line_length = 80, + word_wrap_whitespace_mode mode = WSMODE_KEEP_ALL); inline bool bits_match(unsigned required, unsigned ok, unsigned mask) { diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 5c3c149a2..c99e7b328 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -33,7 +33,6 @@ namespace DFHack { class Module; std::unique_ptr createMaterials(); - std::unique_ptr createNotes(); std::unique_ptr createGraphic(); } #endif diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index f168d7e47..6bef8a74c 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -97,6 +97,10 @@ namespace DFHack /// create a command with a name, description, function pointer to its code /// and saying if it needs an interactive terminal /// Most commands shouldn't require an interactive terminal! + /// Note that the description and usage fields are only used for + /// out-of-tree plugins that do not have rendered help installed in + /// the hack/docs directory. Help for all internal plugins comes from + /// the rendered .rst files. PluginCommand(const char * _name, const char * _description, command_function function_, @@ -297,6 +301,7 @@ namespace DFHack { // Predefined hotkey guards DFHACK_EXPORT bool default_hotkey(df::viewscreen *); + DFHACK_EXPORT bool anywhere_hotkey(df::viewscreen *); DFHACK_EXPORT bool dwarfmode_hotkey(df::viewscreen *); DFHACK_EXPORT bool cursor_hotkey(df::viewscreen *); } diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 5dbc81d0d..8e5c82cb2 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -116,7 +116,7 @@ namespace DFHack //This is a static string, overwritten with every call! //Support values > 2 even though they should never happen. //Copy string if it will be used. - inline char * getStr() const + inline const char * getStr() const { static char str[16]; diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index 3831d4bb1..b152381c4 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -42,25 +42,11 @@ namespace DFHack { namespace Constructions { -// "Simplified" copy of construction -struct t_construction { - df::coord pos; - df::item_type item_type; - int16_t item_subtype; - int16_t mat_type; - int32_t mat_index; - df::construction_flags flags; - int16_t original_tile; - // Pointer to original object, in case you want to modify it - df::construction *origin; -}; -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyConstruction (const int32_t index, t_construction &out); -DFHACK_EXPORT df::construction * getConstruction (const int32_t index); DFHACK_EXPORT df::construction * findAtTile(df::coord pos); +DFHACK_EXPORT bool insert(df::construction * constr); + DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type, df::item_type item = df::item_type::NONE, int mat_index = -1); diff --git a/library/include/modules/Engravings.h b/library/include/modules/Engravings.h deleted file mode 100644 index bf30c62a8..000000000 --- a/library/include/modules/Engravings.h +++ /dev/null @@ -1,63 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#ifndef CL_MOD_ENGRAVINGS -#define CL_MOD_ENGRAVINGS -/* -* DF engravings -*/ -#include "Export.h" -#include "DataDefs.h" -#include "df/engraving.h" -/** - * \defgroup grp_engraving Engraving module parts - * @ingroup grp_modules - */ -namespace DFHack -{ -namespace Engravings -{ -// "Simplified" copy of engraving -struct t_engraving { - int32_t artist; - int32_t masterpiece_event; - int32_t skill_rating; - df::coord pos; - df::engraving_flags flags; - int8_t tile; - int32_t art_id; - int16_t art_subid; - df::item_quality quality; - // Pointer to original object, in case you want to modify it - df::engraving *origin; -}; - -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT bool copyEngraving (const int32_t index, t_engraving &out); -DFHACK_EXPORT df::engraving * getEngraving (const int32_t index); -} -} -#endif diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 455032fea..a0ae27889 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -126,10 +126,11 @@ namespace DFHack DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true, df::unit *unit1 = NULL, df::unit *unit2 = NULL); /* - * Cursor and window coords + * Cursor and window map coords */ DFHACK_EXPORT df::coord getViewportPos(); DFHACK_EXPORT df::coord getCursorPos(); + DFHACK_EXPORT df::coord getMousePos(); static const int AREA_MAP_WIDTH = 23; static const int MENU_WIDTH = 30; @@ -162,8 +163,6 @@ namespace DFHack DFHACK_EXPORT bool getDesignationCoords (int32_t &x, int32_t &y, int32_t &z); DFHACK_EXPORT bool setDesignationCoords (const int32_t x, const int32_t y, const int32_t z); - DFHACK_EXPORT bool getMousePos (int32_t & x, int32_t & y); - // The distance from the z-level of the tile at map coordinates (x, y) to the closest ground z-level below // Defaults to 0, unless overriden by plugins DFHACK_EXPORT int getDepthAt (int32_t x, int32_t y); diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 70ba0a858..522746fb0 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -267,7 +267,7 @@ public: { return index_tile(designation,p); } - bool setDesignationAt(df::coord2d p, df::tile_designation des, int32_t priority = 4000) + bool setDesignationAt(df::coord2d p, df::tile_designation des, int32_t priority = 0) { if(!valid) return false; dirty_designations = true; @@ -276,6 +276,12 @@ public: index_tile(designation,p) = des; if((des.bits.dig || des.bits.smooth) && block) { block->flags.bits.designated = true; + // if priority is not specified, keep the existing priority if it + // is set. otherwise default to 4000. + if (priority <= 0) + priority = priorityAt(p); + if (priority <= 0) + priority = 4000; setPriorityAt(p, priority); } return true; @@ -554,8 +560,8 @@ class DFHACK_EXPORT MapCache Block * b= BlockAtTile(tilecoord); return b ? b->DesignationAt(tilecoord) : df::tile_designation(); } - // priority is optional, only set if >= 0 - bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = 4000) + // if priority is 0, it is kept unchanged if previously set, otherwise 4000 + bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = 0) { if (Block *b = BlockAtTile(tilecoord)) { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index bc6298601..225efae17 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -233,7 +233,8 @@ extern DFHACK_EXPORT bool ReadGeology(std::vector > *layer_ /** * Get pointers to features of a block */ -extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature * local, t_feature * global); +extern DFHACK_EXPORT bool ReadFeatures(int32_t x, int32_t y, int32_t z, t_feature * local, t_feature * global); +extern DFHACK_EXPORT bool ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature * local, t_feature * global); // todo: deprecate me /** * Get pointers to features of an already read block */ @@ -242,7 +243,7 @@ extern DFHACK_EXPORT bool ReadFeatures(df::map_block * block,t_feature * local, /** * Get a pointer to a specific global feature directly. - */ +*/ DFHACK_EXPORT df::feature_init *getGlobalInitFeature(int32_t index); /** * Get a pointer to a specific local feature directly. rgn_coord is in the world region grid. @@ -261,9 +262,11 @@ extern DFHACK_EXPORT bool GetGlobalFeature(t_feature &feature, int32_t index); */ /// get size of the map in blocks -extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); +extern DFHACK_EXPORT void getSize(int32_t& x, int32_t& y, int32_t& z); +extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z); // todo: deprecate me /// get size of the map in tiles -extern DFHACK_EXPORT void getTileSize(uint32_t& x, uint32_t& y, uint32_t& z); +extern DFHACK_EXPORT void getTileSize(int32_t& x, int32_t& y, int32_t& z); +extern DFHACK_EXPORT void getTileSize(uint32_t& x, uint32_t& y, uint32_t& z); // todo: deprecate me /// get the position of the map on world map extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z); @@ -333,7 +336,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, ); /// remove a block event from the block by address -extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); +extern DFHACK_EXPORT bool RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event * which ); +extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); // todo: deprecate me DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); diff --git a/library/include/modules/Notes.h b/library/include/modules/Notes.h deleted file mode 100644 index 14bb9db84..000000000 --- a/library/include/modules/Notes.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#ifndef CL_MOD_NOTES -#define CL_MOD_NOTES -/** - * \defgroup grp_notes In game notes (and routes) - * @ingroup grp_notes - */ -#include "Export.h" -#include "Module.h" - -#include -#include - -#ifdef __cplusplus -namespace DFHack -{ -#endif - /** - * Game's structure for a note. - * \ingroup grp_notes - */ - struct t_note - { - // First note created has id 0, second has id 1, etc. Not affected - // by lower id notes being deleted. - uint32_t id; // 0 - uint8_t symbol; // 4 - uint8_t unk1; // alignment padding? - uint16_t foreground; // 6 - uint16_t background; // 8 - uint16_t unk2; // alignment padding? - - std::string name; // C - std::string text; // 10 - - uint16_t x; // 14 - uint16_t y; // 16 - uint16_t z; // 18 - - // Is there more? - }; - -#ifdef __cplusplus - - /** - * The notes module - allows reading DF in-game notes - * \ingroup grp_modules - * \ingroup grp_notes - */ - class DFHACK_EXPORT Notes : public Module - { - public: - Notes(); - ~Notes(){}; - bool Finish() - { - return true; - } - std::vector* notes; - }; - -} -#endif // __cplusplus - -#endif diff --git a/library/include/modules/Renderer.h b/library/include/modules/Renderer.h index 11abd7c1d..65a9694ab 100644 --- a/library/include/modules/Renderer.h +++ b/library/include/modules/Renderer.h @@ -5,6 +5,13 @@ #pragma once namespace DFHack { namespace Renderer { + // If the the 'x' parameter points to this value, then the 'y' parameter will + // be interpreted as a boolean flag for whether to return map coordinates (false) + // or text tile coordinates (true). Only TWBT implements this logic, and this + // sentinel value can be removed once DF provides an API for retrieving the + // two sets of coordinates. + DFHACK_EXPORT extern const int32_t GET_MOUSE_COORDS_SENTINEL; + struct DFHACK_EXPORT renderer_wrap : public df::renderer { void set_to_null(); void copy_from_parent(); diff --git a/library/include/modules/Windows.h b/library/include/modules/Windows.h deleted file mode 100644 index f9b282cf3..000000000 --- a/library/include/modules/Windows.h +++ /dev/null @@ -1,270 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#include "Export.h" -#include - -namespace DFHack -{ -namespace Windows -{ - /* - * DF window stuffs - */ - enum df_color - { - black, - blue, - green, - cyan, - red, - magenta, - brown, - lgray, - dgray, - lblue, - lgreen, - lcyan, - lred, - lmagenta, - yellow, - white - // maybe add transparency? - }; - - // The tile format DF uses internally - struct df_screentile - { - uint8_t symbol; - uint8_t foreground; ///< df_color - uint8_t background; ///< df_color - uint8_t bright; - }; - - - // our silly painter things and window things follow. - class df_window; - struct df_tilebuf - { - df_screentile * data; - unsigned int width; - unsigned int height; - }; - - DFHACK_EXPORT df_screentile *getScreenBuffer(); - - class DFHACK_EXPORT painter - { - friend class df_window; - public: - df_screentile* get(unsigned int x, unsigned int y) - { - if(x >= width || y >= height) - return 0; - return &buffer[x*height + y]; - }; - bool set(unsigned int x, unsigned int y, df_screentile tile ) - { - if(x >= width || y >= height) - return false; - buffer[x*height + y] = tile; - return true; - } - df_color foreground (df_color change = (df_color) -1) - { - if(change != -1) - current_foreground = change; - return current_foreground; - } - df_color background (df_color change = (df_color) -1) - { - if(change != -1) - current_background = change; - return current_background; - } - void bright (bool change) - { - current_bright = change; - } - bool bright () - { - return current_bright; - } - void printStr(std::string & str, bool wrap = false) - { - for ( auto iter = str.begin(); iter != str.end(); iter++) - { - auto elem = *iter; - if(cursor_y >= (int)height) - break; - if(wrap) - { - if(cursor_x >= (int)width) - cursor_x = wrap_column; - } - df_screentile & tile = buffer[cursor_x * height + cursor_y]; - tile.symbol = elem; - tile.foreground = current_foreground; - tile.background = current_background; - tile.bright = current_bright; - cursor_x++; - } - } - void set_wrap (int new_column) - { - wrap_column = new_column; - } - void gotoxy(unsigned int x, unsigned int y) - { - cursor_x = x; - cursor_y = y; - } - void reset() - { - cursor_x = 0; - cursor_y = 0; - current_background = black; - current_foreground = white; - current_bright = false; - wrap_column = 0; - } - private: - painter (df_window * orig, df_screentile * buf, unsigned int width, unsigned int height) - { - origin = orig; - this->width = width; - this->height = height; - this->buffer = buf; - reset(); - } - df_window* origin; - unsigned int width; - unsigned int height; - df_screentile* buffer; - // current paint cursor position - int cursor_x; - int cursor_y; - int wrap_column; - // current foreground color - df_color current_foreground; - // current background color - df_color current_background; - // make bright? - bool current_bright; - }; - - class DFHACK_EXPORT df_window - { - friend class painter; - public: - df_window(int x, int y, unsigned int width, unsigned int height); - virtual ~df_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) = 0; - virtual void paint () = 0; - virtual painter * lock(); - bool unlock (painter * painter); - virtual bool addChild(df_window *); - virtual df_tilebuf getBuffer() = 0; - public: - df_screentile* buffer; - unsigned int width; - unsigned int height; - protected: - df_window * parent; - std::vector children; - int left; - int top; - // FIXME: FAKE - bool locked; - painter * current_painter; - }; - - class DFHACK_EXPORT top_level_window : public df_window - { - public: - top_level_window(); - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_); - virtual void paint (); - virtual painter * lock(); - virtual df_tilebuf getBuffer(); - }; - class DFHACK_EXPORT buffered_window : public df_window - { - public: - buffered_window(int x, int y, unsigned int width, unsigned int height):df_window(x,y,width, height) - { - buffer = new df_screentile[width*height]; - }; - virtual ~buffered_window() - { - delete buffer; - } - virtual void blit_to_parent () - { - df_tilebuf par = parent->getBuffer(); - for(unsigned xi = 0; xi < width; xi++) - { - for(unsigned yi = 0; yi < height; yi++) - { - unsigned parx = left + xi; - unsigned pary = top + yi; - if(pary >= par.height) continue; - if(parx >= par.width) continue; - par.data[parx * par.height + pary] = buffer[xi * height + yi]; - } - } - } - virtual df_tilebuf getBuffer() - { - df_tilebuf buf; - buf.data = buffer; - buf.width = width; - buf.height = height; - return buf; - }; - }; - class DFHACK_EXPORT dfhack_dummy : public buffered_window - { - public: - dfhack_dummy(int x, int y):buffered_window(x,y,6,1){}; - virtual bool move (int left_, int top_, unsigned int width_, unsigned int height_) - { - top = top_; - left = left_; - return true; - } - virtual void paint () - { - painter * p = lock(); - p->bright(true); - p->background(black); - p->foreground(white); - std::string dfhack = "DFHack"; - p->printStr(dfhack); - blit_to_parent(); - } - }; -} -} diff --git a/library/lua/argparse.lua b/library/lua/argparse.lua index ee170c190..e094bbb57 100644 --- a/library/lua/argparse.lua +++ b/library/lua/argparse.lua @@ -154,11 +154,20 @@ function numberList(arg, arg_name, list_length) return strings end --- throws if val is not a nonnegative integer; otherwise returns val -local function check_nonnegative_int(val, arg_name) +function positiveInt(arg, arg_name) + local val = tonumber(arg) + if not val or val <= 0 or val ~= math.floor(val) then + arg_error(arg_name, + 'expected positive integer; got "%s"', tostring(arg)) + end + return val +end + +function nonnegativeInt(arg, arg_name) + local val = tonumber(arg) if not val or val < 0 or val ~= math.floor(val) then arg_error(arg_name, - 'expected non-negative integer; got "%s"', tostring(val)) + 'expected non-negative integer; got "%s"', tostring(arg)) end return val end @@ -177,9 +186,9 @@ function coords(arg, arg_name, skip_validation) return cursor end local numbers = numberList(arg, arg_name, 3) - local pos = xyz2pos(check_nonnegative_int(numbers[1]), - check_nonnegative_int(numbers[2]), - check_nonnegative_int(numbers[3])) + local pos = xyz2pos(nonnegativeInt(numbers[1]), + nonnegativeInt(numbers[2]), + nonnegativeInt(numbers[3])) if not skip_validation and not dfhack.maps.isValidTilePos(pos) then arg_error(arg_name, 'specified coordinates not on current map: "%s"', arg) diff --git a/library/lua/custom-raw-tokens.lua b/library/lua/custom-raw-tokens.lua new file mode 100644 index 000000000..6c19deb72 --- /dev/null +++ b/library/lua/custom-raw-tokens.lua @@ -0,0 +1,361 @@ +--[[ +custom-raw-tokens +Allows for reading custom tokens added to raws by mods +by Tachytaenius (wolfboyft) + +Yes, non-vanilla raw tokens do quietly print errors into the error log but the error log gets filled with garbage anyway + +NOTE: This treats plant growths similarly to creature castes but there is no way to deselect a growth, so don't put a token you want to apply to a whole plant after any growth definitions +]] + +local _ENV = mkmodule("custom-raw-tokens") + +local customRawTokensCache = {} +dfhack.onStateChange.customRawTokens = function(code) + if code == SC_WORLD_UNLOADED then + customRawTokensCache = {} + end +end + +local function doToken(cacheTable, token, iter) + local args, lenArgs = {}, 0 + for arg in iter do + lenArgs = lenArgs + 1 + args[lenArgs] = arg + end + if lenArgs == 0 then + cacheTable[token] = true + return true + else + cacheTable[token] = args + return table.unpack(args) + end +end + +local function getSubtype(item) + if item:getSubtype() == -1 then return nil end -- number + return dfhack.items.getSubtypeDef(item:getType(), item:getSubtype()) -- struct +end + +local rawStringsFieldNames = { + [df.inorganic_raw] = "str", + [df.plant_raw] = "raws", + [df.creature_raw] = "raws", + [df.itemdef_weaponst] = "raw_strings", + [df.itemdef_trapcompst] = "raw_strings", + [df.itemdef_toyst] = "raw_strings", + [df.itemdef_toolst] = "raw_strings", + [df.itemdef_instrumentst] = "raw_strings", + [df.itemdef_armorst] = "raw_strings", + [df.itemdef_ammost] = "raw_strings", + [df.itemdef_siegeammost] = "raw_strings", + [df.itemdef_glovesst] = "raw_strings", + [df.itemdef_shoesst] = "raw_strings", + [df.itemdef_shieldst] = "raw_strings", + [df.itemdef_helmst] = "raw_strings", + [df.itemdef_pantsst] = "raw_strings", + [df.itemdef_foodst] = "raw_strings", + [df.entity_raw] = "raws", + [df.language_word] = "str", + [df.language_symbol] = "str", + [df.language_translation] = "str", + [df.reaction] = "raw_strings", + [df.interaction] = "str" +} + +local function getTokenCore(typeDefinition, token) + -- Have we got a table for this item subtype/reaction/whatever? + -- tostring is needed here because the same raceDefinition key won't give the same value every time + local thisTypeDefCache = ensure_key(customRawTokensCache, tostring(typeDefinition)) + + -- Have we already extracted and stored this custom raw token for this type definition? + local tokenData = thisTypeDefCache[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + else + return tokenData + end + end + + -- Get data anew + local success, dftype = pcall(function() return typeDefinition._type end) + local rawStrings = typeDefinition[rawStringsFieldNames[dftype]] + if not success or not rawStrings then + error("Expected a raw type definition or instance in argument 1") + end + local currentTokenIterator + for _, rawString in ipairs(rawStrings) do -- e.g. "[CUSTOM_TOKEN:FOO:2]" + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") -- iterate over all the text between colons between the brackets + if token == iter() then + currentTokenIterator = iter -- we return for last instance of token if multiple instances are present + end + end + if currentTokenIterator then + return doToken(thisTypeDefCache, token, currentTokenIterator) + end + -- Not present + thisTypeDefCache[token] = false + return false +end + +local function getRaceCasteTokenCore(raceDefinition, casteNumber, token) + -- Have we got tables for this race/caste pair? + local thisRaceDefCache = ensure_key(customRawTokensCache, tostring(raceDefinition)) + local thisRaceDefCacheCaste = ensure_key(thisRaceDefCache, casteNumber) + + -- Have we already extracted and stored this custom raw token for this race/caste pair? + local tokenData = thisRaceDefCacheCaste[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + elseif tokenData == false and casteNumber ~= -1 then + return getRaceCasteTokenCore(raceDefinition, -1, token) + else + return tokenData + end + end + + -- Get data anew. Here we have to track what caste is currently being written to + local casteId, thisCasteActive + if casteNumber ~= -1 then + casteId = raceDefinition.caste[casteNumber].caste_id + thisCasteActive = false + else + thisCasteActive = true + end + local currentTokenIterator + for _, rawString in ipairs(raceDefinition.raws) do + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") + local rawStringToken = iter() + if rawStringToken == "CASTE" or rawStringToken == "SELECT_CASTE" or rawStringToken == "SELECT_ADDITIONAL_CASTE" or rawStringToken == "USE_CASTE" then + local newCaste = iter() + if newCaste then + thisCasteActive = newCaste == casteId or rawStringToken == "SELECT_CASTE" and newCaste == "ALL" + end + elseif thisCasteActive and token == rawStringToken then + currentTokenIterator = iter + end + end + if currentTokenIterator then + return doToken(thisRaceDefCache, token, currentTokenIterator) + end + thisRaceDefCacheCaste[token] = false + if casteNumber == -1 then + return false -- Don't get into an infinite loop! + end + -- Not present, try with no caste + return getRaceCasteTokenCore(raceDefinition, -1, token) +end + +local function getPlantGrowthTokenCore(plantDefinition, growthNumber, token) + -- Have we got tables for this plant/growth pair? + local thisPlantDefCache = ensure_key(customRawTokensCache, tostring(plantDefinition)) + local thisPlantDefCacheGrowth = ensure_key(thisPlantDefCache, growthNumber) + + -- Have we already extracted and stored this custom raw token for this plant/growth pair? + local tokenData = thisPlantDefCacheGrowth[token] + if tokenData ~= nil then + if type(tokenData) == "table" then + return table.unpack(tokenData) + elseif tokenData == false and growthNumber ~= -1 then + return getPlantGrowthTokenCore(plantDefinition, -1, token) + else + return tokenData + end + end + + -- Get data anew. Here we have to track what growth is currently being written to + local growthId, thisGrowthActive + if growthNumber ~= -1 then + growthId = plantDefinition.growths[growthNumber].id + thisGrowthActive = false + else + thisGrowthActive = true + end + local currentTokenIterator + for _, rawString in ipairs(plantDefinition.raws) do + local noBrackets = rawString.value:sub(2, -2) + local iter = noBrackets:gmatch("[^:]*") + local rawStringToken = iter() + if rawStringToken == "GROWTH" then + local newGrowth = iter() + if newGrowth then + thisGrowthActive = newGrowth == growthId + end + elseif thisGrowthActive and token == rawStringToken then + currentTokenIterator = iter + end + end + if currentTokenIterator then + return doToken(thisPlantDefCache, token, currentTokenIterator) + end + thisPlantDefCacheGrowth[token] = false + if growthNumber == -1 then + return false + end + return getPlantGrowthTokenCore(plantDefinition, -1, token) +end + +--[[ +Function signatures: +getToken(rawStruct, token) +getToken(rawStructInstance, token) +getToken(raceDefinition, casteNumber, token) +getToken(raceDefinition, casteString, token) +getToken(plantDefinition, growthNumber, token) +getToken(plantDefinition, growthString, token) +]] + +local function getTokenArg1RaceDefinition(raceDefinition, b, c) + local casteNumber, token + if not c then + -- 2 arguments + casteNumber = -1 + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + token = b + elseif type(b) == "number" then + -- 3 arguments, casteNumber + assert(b == -1 or b < #raceDefinition.caste and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw") + casteNumber = b + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + else + -- 3 arguments, casteString + assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a caste name or number present in the creature raw") + local casteString = b + for i, v in ipairs(raceDefinition.caste) do + if v.caste_id == casteString then + casteNumber = i + break + end + end + assert(casteNumber, "Invalid argument 2 to getToken, caste name \"" .. casteString .. "\" not found") + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + end + return getRaceCasteTokenCore(raceDefinition, casteNumber, token) +end + +local function getTokenArg1PlantDefinition(plantDefinition, b, c) + local growthNumber, token + if not c then + -- 2 arguments + growthNumber = -1 + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + token = b + elseif type(b) == "number" then + -- 3 arguments, growthNumber + assert(b == -1 or b < #plantDefinition.growths and math.floor(b) == b and b >= 0, "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw") + growthNumber = b + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + else + -- 3 arguments, growthString + assert(type(b) == "string", "Invalid argument 2 to getToken, must be -1 or a growth name or number present in the plant raw") + local growthString = b + for i, v in ipairs(plantDefinition.growths) do + if v.id == growthString then + growthNumber = i + break + end + end + assert(growthNumber, "Invalid argument 2 to getToken, growth name \"" .. growthString .. "\" not found") + assert(type(c) == "string", "Invalid argument 3 to getToken, must be a string") + token = c + end + return getPlantGrowthTokenCore(plantDefinition, growthNumber, token) +end + +local function getTokenArg1Else(userdata, token) + assert(type(token) == "string", "Invalid argument 2 to getToken, must be a string") + local rawStruct + if df.is_instance(df.historical_entity, userdata) then + rawStruct = userdata.entity_raw + elseif df.is_instance(df.item, userdata) then + rawStruct = getSubtype(userdata) + elseif df.is_instance(df.job, userdata) then + if job.job_type == df.job_type.CustomReaction then + for i, v in ipairs(df.global.world.raws.reactions.reactions) do + if job.reaction_name == v.code then + rawStruct = v + break + end + end + end + elseif df.is_instance(df.proj_itemst, userdata) then + if not userdata.item then return false end + if df.is_instance(df.item_plantst, userdata.item) or df.is_instance(df.item_plant_growthst, userdata.item) then + -- use plant behaviour from getToken + return getToken(userdata.item, token) + end + rawStruct = userdata.item and userdata.item.subtype + elseif df.is_instance(df.proj_unitst, userdata) then + if not usertdata.unit then return false end + -- special return so do tag here + local unit = userdata.unit + return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token) + elseif df.is_instance(df.building_workshopst, userdata) or df.is_instance(df.building_furnacest, userdata) then + rawStruct = df.building_def.find(userdata.custom_type) + elseif df.is_instance(df.interaction_instance, userdata) then + rawStruct = df.global.world.raws.interactions[userdata.interaction_id] + else + -- Assume raw struct *is* argument 1 + rawStruct = userdata + end + if not rawStruct then return false end + return getTokenCore(rawStruct, token) +end + +function getToken(from, b, c) + -- Argument processing + assert(from and type(from) == "userdata", "Expected userdata for argument 1 to getToken") + if df.is_instance(df.creature_raw, from) then + -- Signatures from here: + -- getToken(raceDefinition, casteNumber, token) + -- getToken(raceDefinition, casteString, token) + return getTokenArg1RaceDefinition(from, b, c) + elseif df.is_instance(df.unit, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local unit, token = from, b + return getRaceCasteTokenCore(df.global.world.raws.creatures.all[unit.race], unit.caste, token) + elseif df.is_instance(df.plant_raw, from) then + -- Signatures from here: + -- getToken(plantDefinition, growthNumber, token) + -- getToken(plantDefinition, growthString, token) + return getTokenArg1PlantDefinition(from, b, c) + elseif df.is_instance(df.plant, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = df.global.world.raws.plants.all[from.material], -1, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + elseif df.is_instance(df.item_plantst, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + local matInfo = dfhack.matinfo.decode(from) + if matInfo.mode ~= "plant" then return false end + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = matInfo.plant, -1, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + elseif df.is_instance(df.item_plant_growthst, from) then + -- Signatures from here: + -- getToken(rawStructInstance, token) + local matInfo = dfhack.matinfo.decode(from) + if matInfo.mode ~= "plant" then return false end + assert(type(b) == "string", "Invalid argument 2 to getToken, must be a string") + local plantDefinition, plantGrowthNumber, token = matInfo.plant, from.growth_print, b + return getPlantGrowthTokenCore(plantDefinition, plantGrowthNumber, token) + else + -- Signatures from here: + -- getToken(rawStruct, token) + -- getToken(rawStructInstance, token) + return getTokenArg1Else(from, b) + end +end + +return _ENV diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 26e20f748..4a46040af 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,4 +1,4 @@ --- Common startup file for all dfhack plugins with lua support +-- Common startup file for all dfhack scripts and plugins with lua support -- The global dfhack table is already created by C++ init code. -- Setup the global environment. @@ -385,6 +385,13 @@ function safe_index(obj,idx,...) end end +function ensure_key(t, key, default_value) + if t[key] == nil then + t[key] = (default_value ~= nil) and default_value or {} + end + return t[key] +end + -- String class extentions -- prefix is a literal string, not a pattern @@ -432,6 +439,7 @@ end -- multiple lines. If width is not specified, 72 is used. function string:wrap(width) width = width or 72 + if width <= 0 then error('expected width > 0; got: '..tostring(width)) end local wrapped_text = {} for line in self:gmatch('[^\n]*') do local line_start_pos = 1 @@ -644,8 +652,9 @@ function Script:needs_update() return (not self.env) or self.mtime ~= dfhack.filesystem.mtime(self.path) end function Script:get_flags() - if self.flags_mtime ~= dfhack.filesystem.mtime(self.path) then - self.flags_mtime = dfhack.filesystem.mtime(self.path) + local mtime = dfhack.filesystem.mtime(self.path) + if self.flags_mtime ~= mtime then + self.flags_mtime = mtime self._flags = {} local f = io.open(self.path) local contents = f:read('*all') @@ -780,7 +789,11 @@ function dfhack.run_script_with_env(envVars, name, flags, ...) end scripts[file].env = env scripts[file].run = script_code - return script_code(...), env + local args = {...} + for i,v in ipairs(args) do + args[i] = tostring(v) -- ensure passed parameters are strings + end + return script_code(table.unpack(args)), env end function dfhack.current_script_name() @@ -806,36 +819,7 @@ end function dfhack.script_help(script_name, extension) script_name = script_name or dfhack.current_script_name() - extension = extension or 'lua' - local full_name = script_name .. '.' .. extension - local path = dfhack.internal.findScript(script_name .. '.' .. extension) - or error("Could not find script: " .. full_name) - local begin_seq, end_seq - if extension == 'rb' then - begin_seq = '=begin' - end_seq = '=end' - else - begin_seq = '[====[' - end_seq = ']====]' - end - local f = io.open(path) or error("Could not open " .. path) - local in_help = false - local help = '' - for line in f:lines() do - if line:endswith(begin_seq) then - in_help = true - elseif in_help then - if line:endswith(end_seq) then - break - end - if line ~= script_name and line ~= ('='):rep(#script_name) then - help = help .. line .. '\n' - end - end - end - f:close() - help = help:gsub('^\n+', ''):gsub('\n+$', '') - return help + return require('helpdb').get_entry_long_help(script_name) end local function _run_command(args, use_console) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index a4541a6d8..e69237003 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -96,7 +96,7 @@ function compute_frame_rect(wavail,havail,spec,xgap,ygap) return rect end -local function parse_inset(inset) +function parse_inset(inset) local l,r,t,b if type(inset) == 'table' then l,r = inset.l or inset.x or 0, inset.r or inset.x or 0 @@ -376,10 +376,21 @@ View.ATTRS { active = true, visible = true, view_id = DEFAULT_NIL, + on_focus = DEFAULT_NIL, + on_unfocus = DEFAULT_NIL, } function View:init(args) self.subviews = {} + self.focus_group = {self} + self.focus = false +end + +local function inherit_focus_group(view, focus_group) + for _,child in ipairs(view.subviews) do + inherit_focus_group(child, focus_group) + end + view.focus_group = focus_group end function View:addviews(list) @@ -388,6 +399,31 @@ function View:addviews(list) local sv = self.subviews for _,obj in ipairs(list) do + -- absorb the focus groups of new children + for _,focus_obj in ipairs(obj.focus_group) do + table.insert(self.focus_group, focus_obj) + end + -- if the child's focus group has a focus owner, absorb it if we don't + -- already have one. otherwise keep the focus owner we have and clear + -- the focus of the child. + if obj.focus_group.cur then + if not self.focus_group.cur then + self.focus_group.cur = obj.focus_group.cur + else + obj.focus_group.cur:setFocus(false) + end + end + -- overwrite the child's focus_group hierarchy with ours + inherit_focus_group(obj, self.focus_group) + -- if we don't have a focus owner, give it to the new child if they want + if not self.focus_group.cur and obj:getPreferredFocusState() then + obj:setFocus(true) + end + + -- set ourselves as the parent view of the new child + obj.parent_view = self + + -- add the subview to our child list table.insert(sv, obj) local id = obj.view_id @@ -405,13 +441,40 @@ function View:addviews(list) end end +-- should be overridden by widgets that care about capturing keyboard focus +-- (e.g. widgets.EditField) +function View:getPreferredFocusState() + return false +end + +function View:setFocus(focus) + if focus then + if self.focus then return end -- nothing to do if we already have focus + if self.focus_group.cur then + -- steal focus from current owner + self.focus_group.cur:setFocus(false) + end + self.focus_group.cur = self + self.focus = true + if self.on_focus then + self.on_focus() + end + elseif self.focus then + self.focus = false + self.focus_group.cur = nil + if self.on_unfocus then + self.on_unfocus() + end + end +end + function View:getWindowSize() local rect = self.frame_body return rect.width, rect.height end -function View:getMousePos() - local rect = self.frame_body +function View:getMousePos(view_rect) + local rect = view_rect or self.frame_body local x,y = dscreen.getMousePos() if rect and rect:inClipGlobalXY(x,y) then return rect:localXY(x,y) @@ -476,12 +539,45 @@ end function View:onRenderBody(dc) end +-- Returns whether we should invoke the focus owner's onInput() function from +-- the given view's inputToSubviews() function. That is, returns true if: +-- - the view is not itself the focus owner since that would be an infinite loop +-- - the view is not a descendent of the focus owner (same as above) +-- - the focus owner and all of its ancestors are visible and active, since if +-- the focus owner is not (directly or transitively) visible or active, then +-- it shouldn't be getting input. +local function should_send_input_to_focus_owner(view, focus_owner) + local iter = view + while iter do + if iter == focus_owner then + return false + end + iter = iter.parent_view + end + iter = focus_owner + while iter do + if not iter.visible or not iter.active then + return false + end + iter = iter.parent_view + end + return true +end + function View:inputToSubviews(keys) local children = self.subviews + -- give focus owner first dibs on the input + local focus_owner = self.focus_group.cur + if focus_owner and should_send_input_to_focus_owner(self, focus_owner) and + focus_owner:onInput(keys) then + return true + end + for i=#children,1,-1 do local child = children[i] - if child.visible and child.active and child:onInput(keys) then + if child.visible and child.active and child ~= focus_owner and + child:onInput(keys) then return true end end @@ -545,6 +641,7 @@ function Screen:show(parent) if not dscreen.show(self, parent.child) then error('Could not show screen') end + return self end function Screen:onAboutToShow(parent) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 5d2a35197..952e8f734 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -36,7 +36,13 @@ end function MessageBox:getWantedFrameSize() local label = self.subviews.label local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) - return math.max(width, label:getTextWidth()), label:getTextHeight() + local text_area_width = label:getTextWidth() + if label.frame_inset then + -- account for scroll icons + text_area_width = text_area_width + (label.frame_inset.l or 0) + text_area_width = text_area_width + (label.frame_inset.r or 0) + end + return math.max(width, text_area_width), label:getTextHeight() end function MessageBox:onRenderFrame(dc,rect) @@ -69,22 +75,26 @@ function MessageBox:onInput(keys) end function showMessage(title, text, tcolor, on_close) - MessageBox{ + local mb = MessageBox{ frame_title = title, text = text, text_pen = tcolor, on_close = on_close - }:show() + } + mb:show() + return mb end function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) - MessageBox{ + local mb = MessageBox{ frame_title = title, text = text, text_pen = tcolor, on_accept = on_accept, on_cancel = on_cancel, - }:show() + } + mb:show() + return mb end InputBox = defclass(InputBox, MessageBox) @@ -133,7 +143,7 @@ function InputBox:onInput(keys) end function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) - InputBox{ + local ib = InputBox{ frame_title = title, text = text, text_pen = tcolor, @@ -141,7 +151,9 @@ function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_wi on_input = on_input, on_cancel = on_cancel, frame_width = min_width, - }:show() + } + ib:show() + return ib end ListBox = defclass(ListBox, MessageBox) @@ -158,6 +170,7 @@ ListBox.ATTRS{ on_select2 = DEFAULT_NIL, select2_hint = DEFAULT_NIL, row_height = DEFAULT_NIL, + list_frame_inset = DEFAULT_NIL, } function ListBox:preinit(info) @@ -198,8 +211,9 @@ function ListBox:init(info) if cb then cb(obj, sel) end end, on_submit2 = on_submit2, - frame = { l = 0, r = 0 }, - row_height = info.row_height, + frame = { l = 0, r = 0}, + frame_inset = self.list_frame_inset, + row_height = self.row_height, } } end @@ -230,7 +244,7 @@ function ListBox:onInput(keys) end function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) - ListBox{ + local lb = ListBox{ frame_title = title, text = text, text_pen = tcolor, @@ -239,7 +253,9 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_ on_cancel = on_cancel, frame_width = min_width, with_filter = filter, - }:show() + } + lb:show() + return lb end return _ENV diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index b79f7a9b7..db80554cd 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -104,7 +104,7 @@ function getCursorPos() end function setCursorPos(cursor) - df.global.cursor = cursor + df.global.cursor = copyall(cursor) end function clearCursorPos() @@ -443,11 +443,7 @@ MenuOverlay.ATTRS { sidebar_mode = DEFAULT_NIL, } -function MenuOverlay:computeFrame(parent_rect) - return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) -end - -function MenuOverlay:onAboutToShow(parent) +function MenuOverlay:init() if not dfhack.isMapLoaded() then -- sidebar menus are only valid when a fort map is loaded error('A fortress map must be loaded.') @@ -471,7 +467,13 @@ function MenuOverlay:onAboutToShow(parent) enterSidebarMode(self.sidebar_mode) end +end +function MenuOverlay:computeFrame(parent_rect) + return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) +end + +function MenuOverlay:onAboutToShow(parent) self:updateLayout() if not self.df_layout.menu then error("The menu panel of dwarfmode is not visible") @@ -502,6 +504,55 @@ function MenuOverlay:render(dc) MenuOverlay.super.render(self, dc) end end + +-- Framework for managing rendering over the map area. This function is intended +-- to be called from a subclass's onRenderBody() function. +-- +-- get_overlay_char_fn takes a coordinate position and an is_cursor boolean and +-- returns the char to render at that position and, optionally, the foreground +-- and background colors to use to draw the char. If nothing should be rendered +-- at that position, the function should return nil. If no foreground color is +-- specified, it defaults to COLOR_GREEN. If no background color is specified, +-- it defaults to COLOR_BLACK. +-- +-- bounds_rect has elements {x1, x2, y1, y2} in global map coordinates (not +-- screen coordinates). The rect is intersected with the visible map viewport to +-- get the range over which get_overlay_char_fn is called. If bounds_rect is not +-- specified, the entire viewport is scanned. +-- +-- example call from a subclass: +-- function MyMenuOverlaySubclass:onRenderBody() +-- local function get_overlay_char(pos) +-- return safe_index(self.overlay_chars, pos.z, pos.y, pos.x), COLOR_RED +-- end +-- self:renderMapOverlay(get_overlay_char, self.overlay_bounds) +-- end +function MenuOverlay:renderMapOverlay(get_overlay_char_fn, bounds_rect) + local vp = self:getViewport() + local rect = gui.ViewRect{rect=vp, + clip_view=bounds_rect and gui.ViewRect{rect=bounds_rect} or nil} + + -- nothing to do if the viewport is completely separate from the bounds_rect + if rect:isDefunct() then return end + + local dc = gui.Painter.new(self.df_layout.map) + local z = df.global.window_z + local cursor = getCursorPos() + for y=rect.clip_y1,rect.clip_y2 do + for x=rect.clip_x1,rect.clip_x2 do + local pos = xyz2pos(x, y, z) + local overlay_char, fg_color, bg_color = get_overlay_char_fn( + pos, same_xy(cursor, pos)) + if not overlay_char then goto continue end + local stile = vp:tileToScreen(pos) + dc:map(true):seek(stile.x, stile.y): + pen(fg_color or COLOR_GREEN, bg_color or COLOR_BLACK): + char(overlay_char):map(false) + ::continue:: + end + end +end + --fakes a "real" workshop sidebar menu, but on exactly selected workshop WorkshopOverlay = defclass(WorkshopOverlay, MenuOverlay) WorkshopOverlay.focus_path="WorkshopOverlay" diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 8894b36af..1fbce4377 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -56,6 +56,7 @@ function MaterialDialog:init(info) frame = { l = 0, r = 0, t = 4, b = 2 }, icon_width = 2, on_submit = self:callback('onSubmitItem'), + edit_on_char=function(c) return c:match('%l') end, }, widgets.Label{ text = { { @@ -343,4 +344,360 @@ function ItemTypeDialog(args) return dlg.ListBox(args) end +function ItemTraitsDialog(args) + local job_item_flags_map = {} + for i = 1, 5 do + if not df['job_item_flags'..i] then break end + for _, f in ipairs(df['job_item_flags'..i]) do + if f then + job_item_flags_map[f] = 'flags'..i + end + end + end + local job_item_flags = {} + for k, _ in pairs(job_item_flags_map) do + job_item_flags[#job_item_flags + 1] = k + end + table.sort(job_item_flags) + -------------------------------------- + local tool_uses = {} + for i, _ in ipairs(df.tool_uses) do + tool_uses[#tool_uses + 1] = df.tool_uses[i] + end + local restore_none = false + if tool_uses[1] == 'NONE' then + restore_none = true + table.remove(tool_uses, 1) + end + table.sort(tool_uses) + if restore_none then + table.insert(tool_uses, 1, 'NONE') + end + -------------------------------------- + local set_ore_ix = {} + for i, raw in ipairs(df.global.world.raws.inorganics) do + for _, ix in ipairs(raw.metal_ore.mat_index) do + set_ore_ix[ix] = true + end + end + local ores = {} + for ix in pairs(set_ore_ix) do + local raw = df.global.world.raws.inorganics[ix] + ores[#ores+1] = {mat_index = ix, name = raw.material.state_name.Solid} + end + table.sort(ores, function(a,b) return a.name < b.name end) + + -------------------------------------- + -- CALCIUM_CARBONATE, CAN_GLAZE, FAT, FLUX, + -- GYPSUM, PAPER_PLANT, PAPER_SLURRY, TALLOW, WAX + local reaction_classes_set = {} + for ix,reaction in ipairs(df.global.world.raws.reactions.reactions) do + if #reaction.reagents > 0 then + for _, r in ipairs(reaction.reagents) do + if r.reaction_class and r.reaction_class ~= '' then + reaction_classes_set[r.reaction_class] = true + end + end + end --if + end + local reaction_classes = {} + for k in pairs(reaction_classes_set) do + reaction_classes[#reaction_classes + 1] = k + end + table.sort(reaction_classes) + -------------------------------------- + -- PRESS_LIQUID_MAT, TAN_MAT, BAG_ITEM etc + local product_materials_set = {} + for ix,reaction in ipairs(df.global.world.raws.reactions.reactions) do + if #reaction.products > 0 then + --for _, p in ipairs(reaction.products) do + -- in the list in work order conditions there is no SEED_MAT. + -- I think it's because the game doesn't iterate over all products. + local p = reaction.products[0] + local mat = p.get_material + if mat and mat.product_code ~= '' then + product_materials_set[mat.product_code] = true + end + --end + end --if + end + local product_materials = {} + for k in pairs(product_materials_set) do + product_materials[#product_materials + 1] = k + end + table.sort(product_materials) + --==================================-- + + local function set_search_keys(choices) + for _, choice in ipairs(choices) do + if not choice.search_key then + if type(choice.text) == 'table' then + local search_key = {} + for _, token in ipairs(choice.text) do + search_key[#search_key+1] = string.lower(token.text or '') + end + choice.search_key = table.concat(search_key, ' ') + elseif choice.text then + choice.search_key = string.lower(choice.text) + end + end + end + end + + args.text = args.prompt or 'Type or select an item trait' + args.text_pen = COLOR_WHITE + args.with_filter = true + args.icon_width = 2 + args.dismiss_on_select = false + + local pen_active = COLOR_LIGHTCYAN + local pen_active_d = COLOR_CYAN + local pen_not_active = COLOR_LIGHTRED + local pen_not_active_d = COLOR_RED + local pen_action = COLOR_WHITE + local pen_action_d = COLOR_GREY + + local job_item = args.job_item + local choices = {} + + local pen_cb = function(args, fnc) + if not (args and fnc) then + return COLOR_YELLOW + end + return fnc(args) and pen_active or pen_not_active + end + local pen_d_cb = function(args, fnc) + if not (args and fnc) then + return COLOR_YELLOW + end + return fnc(args) and pen_active_d or pen_not_active_d + end + local icon_cb = function(args, fnc) + if not (args and fnc) then + return '\19' -- ‼ + end + -- '\251' is a checkmark + -- '\254' is a square + return fnc(args) and '\251' or '\254' + end + + if not args.hide_none then + table.insert(choices, { + icon = '!', + text = {{text = args.none_caption or 'none', pen = pen_action, dpen = pen_action_d}}, + reset_all_traits = true + }) + end + + local isActiveFlag = function (obj) + return obj.job_item[obj.ffield][obj.flag] + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset flags', pen = pen_action, dpen = pen_action_d}}, + reset_flags = true + }) + for _, flag in ipairs(job_item_flags) do + local ffield = job_item_flags_map[flag] + local text = 'is ' .. (value and '' or 'any ') .. string.lower(flag) + local args = {job_item=job_item, ffield=ffield, flag=flag} + table.insert(choices, { + icon = curry(icon_cb, args, isActiveFlag), + text = {{text = text, + pen = curry(pen_cb, args, isActiveFlag), + dpen = curry(pen_d_cb, args, isActiveFlag), + }}, + ffield = ffield, flag = flag + }) + end + + local isActiveTool = function (args) + return df.tool_uses[args.tool_use] == args.job_item.has_tool_use + end + for _, tool_use in ipairs(tool_uses) do + if tool_use == 'NONE' then + table.insert(choices, { + icon = '!', + text = {{text = 'unset use', pen = pen_action, dpen = pen_action_d}}, + tool_use = tool_use + }) + else + local args = {job_item = job_item, tool_use=tool_use} + table.insert(choices, { + icon = ' ', + text = {{text = 'has use ' .. tool_use, + pen = curry(pen_cb, args, isActiveTool), + dpen = curry(pen_d_cb, args, isActiveTool), + }}, + tool_use = tool_use + }) + end + end + + local isActiveOre = function(args) + return (args.job_item.metal_ore == args.mat_index) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset ore', pen = pen_action, dpen = pen_action_d}}, + ore_ix = -1 + }) + for _, ore in ipairs(ores) do + local args = {job_item = job_item, mat_index=ore.mat_index} + table.insert(choices, { + icon = ' ', + text = {{text = 'ore of ' .. ore.name, + pen = curry(pen_cb, args, isActiveOre), + dpen = curry(pen_d_cb, args, isActiveOre), + }}, + ore_ix = ore.mat_index + }) + end + + local isActiveReactionClass = function(args) + return (args.job_item.reaction_class == args.reaction_class) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset reaction class', pen = pen_action, dpen = pen_action_d}}, + reaction_class = '' + }) + for _, reaction_class in ipairs(reaction_classes) do + local args = {job_item = job_item, reaction_class=reaction_class} + table.insert(choices, { + icon = ' ', + text = {{text = 'reaction class ' .. reaction_class, + pen = curry(pen_cb, args, isActiveReactionClass), + dpen = curry(pen_d_cb, args, isActiveReactionClass) + }}, + reaction_class = reaction_class + }) + end + + local isActiveProduct = function(args) + return (args.job_item.has_material_reaction_product == args.product_materials) + end + table.insert(choices, { + icon = '!', + text = {{text = 'unset producing', pen = pen_action, dpen = pen_action_d}}, + product_materials = '' + }) + for _, product_materials in ipairs(product_materials) do + local args = {job_item = job_item, product_materials=product_materials} + table.insert(choices, { + icon = ' ', + text = {{text = product_materials .. '-producing', + pen = curry(pen_cb, args, isActiveProduct), + dpen = curry(pen_d_cb, args, isActiveProduct) + }}, + product_materials = product_materials + }) + end + + set_search_keys(choices) + args.choices = choices + + if args.on_select then + local cb = args.on_select + args.on_select = function(idx, obj) + return cb(obj) + end + else + local function toggleFlag(obj, ffield, flag) + local job_item = obj.job_item + job_item[ffield][flag] = not job_item[ffield][flag] + end + + local function toggleToolUse(obj, tool_use) + local job_item = obj.job_item + tool_use = df.tool_uses[tool_use] + if job_item.has_tool_use == tool_use then + job_item.has_tool_use = df.tool_uses.NONE + else + job_item.has_tool_use = tool_use + end + end + + local function toggleMetalOre(obj, ore_ix) + local job_item = obj.job_item + if job_item.metal_ore == ore_ix then + job_item.metal_ore = -1 + else + job_item.metal_ore = ore_ix + end + end + + function toggleReactionClass(obj, reaction_class) + local job_item = obj.job_item + if job_item.reaction_class == reaction_class then + job_item.reaction_class = '' + else + job_item.reaction_class = reaction_class + end + end + + local function toggleProductMaterial(obj, product_materials) + local job_item = obj.job_item + if job_item.has_material_reaction_product == product_materials then + job_item.has_material_reaction_product = '' + else + job_item.has_material_reaction_product = product_materials + end + end + + local function unsetFlags(obj) + local job_item = obj.job_item + for flag, ffield in pairs(job_item_flags_map) do + if job_item[ffield][flag] then + toggleFlag(obj, ffield, flag) + end + end + end + + local function setTrait(obj, sel) + if sel.ffield then + --print('toggle flag', sel.ffield, sel.flag) + toggleFlag(obj, sel.ffield, sel.flag) + elseif sel.reset_flags then + --print('reset every flag') + unsetFlags(obj) + elseif sel.tool_use then + --print('toggle tool_use', sel.tool_use) + toggleToolUse(obj, sel.tool_use) + elseif sel.ore_ix then + --print('toggle ore', sel.ore_ix) + toggleMetalOre(obj, sel.ore_ix) + elseif sel.reaction_class then + --print('toggle reaction class', sel.reaction_class) + toggleReactionClass(obj, sel.reaction_class) + elseif sel.product_materials then + --print('toggle product materials', sel.product_materials) + toggleProductMaterial(obj, sel.product_materials) + elseif sel.reset_all_traits then + --print('reset every trait') + -- flags + unsetFlags(obj) + -- tool use + toggleToolUse(obj, 'NONE') + -- metal ore + toggleMetalOre(obj, -1) + -- reaction class + toggleReactionClass(obj, '') + -- producing + toggleProductMaterial(obj, '') + else + print('unknown sel') + printall(sel) + error('Selected entry in ItemTraitsDialog was of unknown type') + end + end + + args.on_select = function(idx, choice) + setTrait(args, choice) + end + end + + return dlg.ListBox(args) +end + return _ENV diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 7a83346b1..2fab12f70 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -73,6 +73,8 @@ end Panel = defclass(Panel, Widget) Panel.ATTRS { + frame_style = DEFAULT_NIL, -- as in gui.FramedScreen + frame_title = DEFAULT_NIL, -- as in gui.FramedScreen on_render = DEFAULT_NIL, on_layout = DEFAULT_NIL, autoarrange_subviews = false, -- whether to automatically lay out subviews @@ -87,6 +89,12 @@ function Panel:onRenderBody(dc) if self.on_render then self.on_render(dc) end end +function Panel:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset, + self.frame_style and 1 or 0) +end + function Panel:postComputeFrame(body) if self.on_layout then self.on_layout(body) end end @@ -112,6 +120,13 @@ function Panel:postUpdateLayout() self:updateSubviewLayout() end +function Panel:onRenderFrame(dc, rect) + Panel.super.onRenderFrame(self, dc, rect) + if not self.frame_style then return end + local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 + gui.paint_frame(x1, y1, x2, y2, self.frame_style, self.frame_title) +end + ------------------- -- ResizingPanel -- ------------------- @@ -121,16 +136,26 @@ ResizingPanel = defclass(ResizingPanel, Panel) -- adjust our frame dimensions according to positions and sizes of our subviews function ResizingPanel:postUpdateLayout(frame_body) local w, h = 0, 0 - for _,subview in ipairs(self.subviews) do - if subview.visible then - w = math.max(w, (subview.frame.l or 0) + - (subview.frame.w or frame_body.width)) - h = math.max(h, (subview.frame.t or 0) + - (subview.frame.h or frame_body.height)) + for _,s in ipairs(self.subviews) do + if s.visible then + w = math.max(w, (s.frame and s.frame.l or 0) + + (s.frame and s.frame.w or frame_body.width)) + h = math.max(h, (s.frame and s.frame.t or 0) + + (s.frame and s.frame.h or frame_body.height)) end end + if self.frame_style then + w = w + 2 + h = h + 2 + end if not self.frame then self.frame = {} end + local oldw, oldh = self.frame.w, self.frame.h self.frame.w, self.frame.h = w, h + if not self._updateLayoutGuard and (oldw ~= w or oldh ~= h) then + self._updateLayoutGuard = true -- protect against infinite loops + self:updateLayout() -- our frame has changed, we need to fully refresh + end + self._updateLayoutGuard = nil end ----------- @@ -178,55 +203,340 @@ end EditField = defclass(EditField, Widget) EditField.ATTRS{ + label_text = DEFAULT_NIL, text = '', text_pen = DEFAULT_NIL, on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, on_submit = DEFAULT_NIL, + on_submit2 = DEFAULT_NIL, key = DEFAULT_NIL, + key_sep = DEFAULT_NIL, + modal = false, + ignore_keys = DEFAULT_NIL, } +function EditField:preinit(init_table) + init_table.frame = init_table.frame or {} + init_table.frame.h = init_table.frame.h or 1 +end + +function EditField:init() + local function on_activate() + self.saved_text = self.text + self:setFocus(true) + end + + self.start_pos = 1 + self.cursor = #self.text + 1 + + self:addviews{HotkeyLabel{frame={t=0,l=0}, + key=self.key, + key_sep=self.key_sep, + label=self.label_text, + on_activate=self.key and on_activate or nil}} +end + +function EditField:getPreferredFocusState() + return not self.key +end + +function EditField:setCursor(cursor) + if not cursor or cursor > #self.text then + self.cursor = #self.text + 1 + return + end + self.cursor = math.max(1, cursor) +end + +function EditField:setText(text, cursor) + self.text = text + self:setCursor(cursor) +end + +function EditField:postUpdateLayout() + self.text_offset = self.subviews[1]:getTextWidth() +end + function EditField:onRenderBody(dc) dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) - local cursor = '_' - if not self.active or gui.blink_visible(300) then - cursor = ' ' - end - local txt = self.text .. cursor - local dx = dc.x - if self.key then - dc:key_string(self.key, '') + local cursor_char = '_' + if not self.active or not self.focus or gui.blink_visible(300) then + cursor_char = (self.cursor > #self.text) and ' ' or + self.text:sub(self.cursor, self.cursor) end - dx = dc.x - dx - local max_width = dc.width - dx + local txt = self.text:sub(1, self.cursor - 1) .. cursor_char .. + self.text:sub(self.cursor + 1) + local max_width = dc.width - self.text_offset + self.start_pos = 1 if #txt > max_width then - txt = string.char(27)..string.sub(txt, #txt-max_width+2) + -- get the substring in the vicinity of the cursor + max_width = max_width - 2 + local half_width = math.floor(max_width/2) + local start_pos = math.max(1, self.cursor-half_width) + local end_pos = math.min(#txt, self.cursor+half_width-1) + if self.cursor + half_width > #txt then + start_pos = #txt - (max_width - 1) + end + if self.cursor - half_width <= 1 then + end_pos = max_width + 1 + end + self.start_pos = start_pos > 1 and start_pos - 1 or start_pos + txt = ('%s%s%s'):format(start_pos == 1 and '' or string.char(27), + txt:sub(start_pos, end_pos), + end_pos == #txt and '' or string.char(26)) end - dc:string(txt) + dc:advance(self.text_offset):string(txt) + dc:string((' '):rep(dc.clip_x2 - dc.x)) end function EditField:onInput(keys) - if self.on_submit and keys.SELECT then - self.on_submit(self.text) + if not self.focus then + -- only react to our hotkey + return self:inputToSubviews(keys) + end + + if self.ignore_keys then + for _,ignore_key in ipairs(self.ignore_keys) do + if keys[ignore_key] then return false end + end + end + + if self.key and keys.LEAVESCREEN then + local old = self.text + self:setText(self.saved_text) + if self.on_change and old ~= self.saved_text then + self.on_change(self.text, old) + end + self:setFocus(false) return true + end + + if keys.SELECT then + if self.key then + self:setFocus(false) + end + if self.on_submit then + self.on_submit(self.text) + return true + end + return not not self.key + elseif keys.SEC_SELECT then + if self.key then + self:setFocus(false) + end + if self.on_submit2 then + self.on_submit2(self.text) + return true + end + return not not self.key + elseif keys._MOUSE_L then + local mouse_x, mouse_y = self:getMousePos() + if mouse_x then + self:setCursor(self.start_pos + mouse_x) + return true + end elseif keys._STRING then local old = self.text if keys._STRING == 0 then - self.text = string.sub(old, 1, #old-1) + -- handle backspace + local del_pos = self.cursor - 1 + if del_pos > 0 then + self:setText(old:sub(1, del_pos-1) .. old:sub(del_pos+1), + del_pos) + end else local cv = string.char(keys._STRING) if not self.on_char or self.on_char(cv, old) then - self.text = old .. cv + self:setText(old:sub(1,self.cursor-1)..cv..old:sub(self.cursor), + self.cursor + 1) + elseif self.on_char then + return self.modal end end if self.on_change and self.text ~= old then self.on_change(self.text, old) end return true + elseif keys.CURSOR_LEFT then + self:setCursor(self.cursor - 1) + return true + elseif keys.A_MOVE_W_DOWN then -- Ctrl-Left (end of prev word) + local _, prev_word_end = self.text:sub(1, self.cursor-1): + find('.*[%w_%-][^%w_%-]') + self:setCursor(prev_word_end or 1) + return true + elseif keys.A_CARE_MOVE_W then -- Alt-Left (home) + self:setCursor(1) + return true + elseif keys.CURSOR_RIGHT then + self:setCursor(self.cursor + 1) + return true + elseif keys.A_MOVE_E_DOWN then -- Ctrl-Right (beginning of next word) + local _,next_word_start = self.text:find('[^%w_%-][%w_%-]', self.cursor) + self:setCursor(next_word_start) + return true + elseif keys.A_CARE_MOVE_E then -- Alt-Right (end) + self:setCursor() + return true + end + + -- if we're modal, then unconditionally eat all the input + return self.modal +end + +--------------- +-- Scrollbar -- +--------------- + +SCROLL_INITIAL_DELAY_MS = 300 +SCROLL_DELAY_MS = 20 + +Scrollbar = defclass(Scrollbar, Widget) + +Scrollbar.ATTRS{ + fg = COLOR_LIGHTGREEN, + bg = COLOR_CYAN, + on_scroll = DEFAULT_NIL, +} + +function Scrollbar:preinit(init_table) + init_table.frame = init_table.frame or {} + init_table.frame.w = init_table.frame.w or 1 +end + +function Scrollbar:init() + self.last_scroll_ms = 0 + self.is_first_click = false + self.scroll_spec = nil + self.is_dragging = false -- index of the scrollbar tile that we're dragging + self:update(1, 1, 1) +end + +local function scrollbar_get_max_pos_and_height(scrollbar) + local frame_body = scrollbar.frame_body + local scrollbar_body_height = (frame_body and frame_body.height or 3) - 2 + + local height = math.max(1, math.floor( + (math.min(scrollbar.elems_per_page, scrollbar.num_elems) * scrollbar_body_height) / + scrollbar.num_elems)) + + return scrollbar_body_height - height, height +end + +-- calculate and cache the number of tiles of empty space above the top of the +-- scrollbar and the number of tiles the scrollbar should occupy to represent +-- the percentage of text that is on the screen. +-- if elems_per_page or num_elems are not specified, the last values passed to +-- Scrollbar:update() are used. +function Scrollbar:update(top_elem, elems_per_page, num_elems) + if not top_elem then error('must specify index of new top element') end + elems_per_page = elems_per_page or self.elems_per_page + num_elems = num_elems or self.num_elems + self.top_elem = top_elem + self.elems_per_page, self.num_elems = elems_per_page, num_elems + + local max_pos, height = scrollbar_get_max_pos_and_height(self) + local pos = (num_elems == elems_per_page) and 0 or + math.ceil(((top_elem-1) * max_pos) / + (num_elems - elems_per_page)) + + self.bar_offset, self.bar_height = pos, height +end + +local function scrollbar_do_drag(scrollbar) + local _,y = scrollbar.frame_body:localXY(dfhack.screen.getMousePos()) + 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) + local new_top_elem = math.floor(cur_pos * max_top / max_pos) + 1 + new_top_elem = math.max(1, math.min(new_top_elem, max_top)) + if new_top_elem ~= scrollbar.top_elem then + scrollbar.on_scroll(new_top_elem) end end +local function scrollbar_is_visible(scrollbar) + return scrollbar.elems_per_page < scrollbar.num_elems +end + +local UP_ARROW_CHAR = string.char(24) +local DOWN_ARROW_CHAR = string.char(25) +local NO_ARROW_CHAR = string.char(32) +local BAR_CHAR = string.char(7) +local BAR_BG_CHAR = string.char(179) + +function Scrollbar:onRenderBody(dc) + -- don't draw if all elements are visible + if not scrollbar_is_visible(self) then + return + end + -- render up arrow if we're not at the top + dc:seek(0, 0):char( + self.top_elem == 1 and NO_ARROW_CHAR or UP_ARROW_CHAR, self.fg, self.bg) + -- render scrollbar body + local starty = self.bar_offset + 1 + local endy = self.bar_offset + self.bar_height + for y=1,dc.height-2 do + dc:seek(0, y) + if y >= starty and y <= endy then + dc:char(BAR_CHAR, self.fg) + else + dc:char(BAR_BG_CHAR, self.bg) + end + end + -- render down arrow if we're not at the bottom + local last_visible_el = self.top_elem + self.elems_per_page - 1 + dc:seek(0, dc.height-1):char( + last_visible_el >= self.num_elems and NO_ARROW_CHAR or DOWN_ARROW_CHAR, + self.fg, self.bg) + if not self.on_scroll then return end + -- manage state for dragging and continuous scrolling + if self.is_dragging then + scrollbar_do_drag(self) + end + if df.global.enabler.mouse_lbut_down == 0 then + self.last_scroll_ms = 0 + self.is_dragging = false + self.scroll_spec = nil + return + end + if self.last_scroll_ms == 0 then return end + local now = dfhack.getTickCount() + local delay = self.is_first_click and + SCROLL_INITIAL_DELAY_MS or SCROLL_DELAY_MS + if now - self.last_scroll_ms >= delay then + self.is_first_click = false + self.on_scroll(self.scroll_spec) + self.last_scroll_ms = now + end +end + +function Scrollbar:onInput(keys) + if not keys._MOUSE_L_DOWN or not self.on_scroll + or not scrollbar_is_visible(self) then + return false + end + local _,y = self:getMousePos() + if not y then return false end + local scroll_spec = nil + if y == 0 then scroll_spec = 'up_small' + elseif y == self.frame_body.height - 1 then scroll_spec = 'down_small' + elseif y <= self.bar_offset then scroll_spec = 'up_large' + elseif y > self.bar_offset + self.bar_height then scroll_spec = 'down_large' + else + self.is_dragging = y - self.bar_offset + return true + end + self.scroll_spec = scroll_spec + self.on_scroll(scroll_spec) + -- reset continuous scroll state + self.is_first_click = true + self.last_scroll_ms = dfhack.getTickCount() + return true +end + ----------- -- Label -- ----------- @@ -359,12 +669,13 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) x = x + #keystr - if sep == '()' then + if sep:startswith('()') then if dc then dc:string(text) - dc:string(' ('):string(keystr,keypen):string(')') + dc:string(' ('):string(keystr,keypen) + dc:string(sep:sub(2)) end - x = x + 3 + x = x + 1 + #sep else if dc then dc:string(keystr,keypen):string(sep):string(text) @@ -416,7 +727,12 @@ Label.ATTRS{ } function Label:init(args) - self.start_line_num = 1 + self.scrollbar = Scrollbar{ + frame={r=0}, + on_scroll=self:callback('on_scrollbar')} + + self:addviews{self.scrollbar} + -- use existing saved text if no explicit text was specified. this avoids -- overwriting pre-formatted text that subclasses may have already set self:setText(args.text or self.text) @@ -425,7 +741,14 @@ function Label:init(args) end end +local function update_label_scrollbar(label) + local body_height = label.frame_body and label.frame_body.height or 1 + label.scrollbar:update(label.start_line_num, body_height, + label:getTextHeight()) +end + function Label:setText(text) + self.start_line_num = 1 self.text = text parse_label_text(self) @@ -433,6 +756,8 @@ function Label:setText(text) self.frame = self.frame or {} self.frame.h = self:getTextHeight() end + + update_label_scrollbar(self) end function Label:preUpdateLayout() @@ -442,6 +767,10 @@ function Label:preUpdateLayout() end end +function Label:postUpdateLayout() + update_label_scrollbar(self) +end + function Label:itemById(id) if self.text_ids then return self.text_ids[id] @@ -465,70 +794,119 @@ function Label:onRenderBody(dc) render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self)) end +function Label:on_scrollbar(scroll_spec) + local v = 0 + if tonumber(scroll_spec) then + v = scroll_spec - self.start_line_num + elseif scroll_spec == 'down_large' then + v = '+halfpage' + elseif scroll_spec == 'up_large' then + v = '-halfpage' + elseif scroll_spec == 'down_small' then + v = 1 + elseif scroll_spec == 'up_small' then + v = -1 + end + + self:scroll(v) +end + function Label:scroll(nlines) + if not nlines then return end + if type(nlines) == 'string' then + if nlines == '+page' then + nlines = self.frame_body.height + elseif nlines == '-page' then + nlines = -self.frame_body.height + elseif nlines == '+halfpage' then + nlines = math.ceil(self.frame_body.height/2) + elseif nlines == '-halfpage' then + nlines = -math.ceil(self.frame_body.height/2) + else + error(('unhandled scroll keyword: "%s"'):format(nlines)) + end + end local n = self.start_line_num + nlines n = math.min(n, self:getTextHeight() - self.frame_body.height + 1) n = math.max(n, 1) + nlines = n - self.start_line_num self.start_line_num = n + update_label_scrollbar(self) + return nlines end function Label:onInput(keys) if is_disabled(self) then return false end + if self:inputToSubviews(keys) then + return true + end if keys._MOUSE_L_DOWN and self:getMousePos() and self.on_click then self:on_click() + return true end if keys._MOUSE_R_DOWN and self:getMousePos() and self.on_rclick then self:on_rclick() + return true end for k,v in pairs(self.scroll_keys) do - if keys[k] then - if v == '+page' then - v = self.frame_body.height - elseif v == '-page' then - v = -self.frame_body.height - end - self:scroll(v) + if keys[k] and 0 ~= self:scroll(v) then + return true end end return check_text_keys(self, keys) end ------------------ --- TooltipLabel -- +-- WrappedLabel -- ------------------ -TooltipLabel = defclass(TooltipLabel, Label) +WrappedLabel = defclass(WrappedLabel, Label) -TooltipLabel.ATTRS{ - tooltip=DEFAULT_NIL, - show_tooltip=true, - indent=2, - text_pen=COLOR_GREY, +WrappedLabel.ATTRS{ + text_to_wrap=DEFAULT_NIL, + indent=0, } -function TooltipLabel:getWrappedTooltip() - local tooltip = getval(self.tooltip) - if type(tooltip) == 'table' then - tooltip = table.concat(tooltip, NEWLINE) +function WrappedLabel:getWrappedText(width) + -- 0 width can happen if the parent has 0 width + if not self.text_to_wrap or width <= 0 then return nil end + local text_to_wrap = getval(self.text_to_wrap) + if type(text_to_wrap) == 'table' then + text_to_wrap = table.concat(text_to_wrap, NEWLINE) end - return tooltip:wrap(self.frame_body.width - self.indent) -end - -function TooltipLabel:preUpdateLayout() - self.visible = getval(self.show_tooltip) + return text_to_wrap:wrap(width - self.indent) end -- we can't set the text in init() since we may not yet have a frame that we -- can get wrapping bounds from. -function TooltipLabel:postComputeFrame() +function WrappedLabel:postComputeFrame() + local wrapped_text = self:getWrappedText(self.frame_body.width-1) + if not wrapped_text then return end local text = {} - for _,line in ipairs(self:getWrappedTooltip():split(NEWLINE)) do + for _,line in ipairs(wrapped_text:split(NEWLINE)) do table.insert(text, {gap=self.indent, text=line}) + -- a trailing newline will get ignored so we don't have to manually trim table.insert(text, NEWLINE) end self:setText(text) end +------------------ +-- TooltipLabel -- +------------------ + +TooltipLabel = defclass(TooltipLabel, WrappedLabel) + +TooltipLabel.ATTRS{ + show_tooltip=DEFAULT_NIL, + indent=2, + text_pen=COLOR_GREY, +} + +function TooltipLabel:preUpdateLayout() + self.visible = getval(self.show_tooltip) +end + ----------------- -- HotkeyLabel -- ----------------- @@ -537,13 +915,23 @@ HotkeyLabel = defclass(HotkeyLabel, Label) HotkeyLabel.ATTRS{ key=DEFAULT_NIL, + key_sep=': ', label=DEFAULT_NIL, on_activate=DEFAULT_NIL, } function HotkeyLabel:init() - self:setText{{key=self.key, key_sep=': ', text=self.label, - on_activate=self.on_activate}} + self:setText{{key=self.key, key_sep=self.key_sep, text=self.label, + on_activate=self.on_activate}} +end + +function HotkeyLabel:onInput(keys) + if HotkeyLabel.super.onInput(self, keys) then + return true + elseif keys._MOUSE_L and self:getMousePos() then + self.on_activate() + return true + end end ---------------------- @@ -569,6 +957,11 @@ function CycleHotkeyLabel:init() break end end + if not self.option_idx then + if self.options[self.initial_option] then + self.option_idx = self.initial_option + end + end if not self.option_idx then error(('cannot find option with value or index: "%s"') :format(self.initial_option)) @@ -613,6 +1006,15 @@ function CycleHotkeyLabel:getOptionValue(option_idx) return option end +function CycleHotkeyLabel:onInput(keys) + if CycleHotkeyLabel.super.onInput(self, keys) then + return true + elseif keys._MOUSE_L and self:getMousePos() then + self:cycle() + return true + end +end + ----------------------- -- ToggleHotkeyLabel -- ----------------------- @@ -644,6 +1046,11 @@ List.ATTRS{ function List:init(info) self.page_top = 1 self.page_size = 1 + self.scrollbar = Scrollbar{ + frame={r=0}, + on_scroll=self:callback('on_scrollbar')} + + self:addviews{self.scrollbar} if info.choices then self:setChoices(info.choices, info.selected) @@ -708,13 +1115,21 @@ function List:postComputeFrame(body) self:moveCursor(0) end +local function update_list_scrollbar(list) + list.scrollbar:update(list.page_top, list.page_size, #list.choices) +end + +function List:postUpdateLayout() + update_list_scrollbar(self) +end + function List:moveCursor(delta, force_cb) - local page = math.max(1, self.page_size) local cnt = #self.choices if cnt < 1 then self.page_top = 1 self.selected = 1 + update_list_scrollbar(self) if force_cb and self.on_select then self.on_select(nil,nil) end @@ -737,14 +1152,42 @@ function List:moveCursor(delta, force_cb) end end + local buffer = 1 + math.min(4, math.floor(self.page_size/10)) + self.selected = 1 + off % cnt - self.page_top = 1 + page * math.floor((self.selected-1) / page) + if (self.selected - buffer) < self.page_top then + self.page_top = math.max(1, self.selected - buffer) + elseif (self.selected + buffer + 1) > (self.page_top + self.page_size) then + local max_page_top = cnt - self.page_size + 1 + self.page_top = math.max(1, + math.min(max_page_top, self.selected - self.page_size + buffer + 1)) + end + update_list_scrollbar(self) if (force_cb or delta ~= 0) and self.on_select then self.on_select(self:getSelected()) end end +function List:on_scrollbar(scroll_spec) + local v = 0 + if tonumber(scroll_spec) then + v = scroll_spec - self.page_top + elseif scroll_spec == 'down_large' then + v = math.ceil(self.page_size / 2) + elseif scroll_spec == 'up_large' then + v = -math.ceil(self.page_size / 2) + elseif scroll_spec == 'down_small' then + v = 1 + elseif scroll_spec == 'up_small' then + v = -1 + end + + local max_page_top = math.max(1, #self.choices - self.page_size + 1) + self.page_top = math.max(1, math.min(max_page_top, self.page_top + v)) + update_list_scrollbar(self) +end + function List:onRenderBody(dc) local choices = self.choices local top = self.page_top @@ -788,7 +1231,7 @@ function List:onRenderBody(dc) if obj.key then local keystr = gui.getKeyDisplay(obj.key) - ip = ip-2-#keystr + ip = ip-3-#keystr dc:seek(ip,y):pen(self.text_pen) dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') end @@ -800,6 +1243,15 @@ function List:onRenderBody(dc) end end +function List:getIdxUnderMouse() + if self.scrollbar:getMousePos() then return end + local _,mouse_y = self:getMousePos() + if mouse_y and #self.choices > 0 and + mouse_y < (#self.choices-self.page_top+1) * self.row_height then + return self.page_top + math.floor(mouse_y/self.row_height) + end +end + function List:submit() if self.on_submit and #self.choices > 0 then self.on_submit(self:getSelected()) @@ -813,12 +1265,26 @@ function List:submit2() end function List:onInput(keys) + if self:inputToSubviews(keys) then + return true + end if self.on_submit and keys.SELECT then self:submit() return true elseif self.on_submit2 and keys.SEC_SELECT then self:submit2() return true + elseif keys._MOUSE_L then + local idx = self:getIdxUnderMouse() + if idx then + self:setSelected(idx) + if dfhack.internal.getModifiers().shift then + self:submit2() + else + self:submit() + end + return true + end else for k,v in pairs(self.scroll_keys) do if keys[k] then @@ -857,16 +1323,25 @@ FilteredList = defclass(FilteredList, Widget) FilteredList.ATTRS { edit_below = false, edit_key = DEFAULT_NIL, + edit_ignore_keys = DEFAULT_NIL, + edit_on_char = DEFAULT_NIL, } function FilteredList:init(info) + local on_char = self:callback('onFilterChar') + if self.edit_on_char then + on_char = function(c, text) + return self.edit_on_char(c, text) and self:onFilterChar(c, text) + end + end + self.edit = EditField{ text_pen = info.edit_pen or info.cursor_pen, frame = { l = info.icon_width, t = 0, h = 1 }, on_change = self:callback('onFilterChange'), - on_char = self:callback('onFilterChar'), + on_char = on_char, key = self.edit_key, - active = (self.edit_key == nil), + ignore_keys = self.edit_ignore_keys, } self.list = List{ frame = { t = 2 }, @@ -911,19 +1386,6 @@ function FilteredList:init(info) end end -function FilteredList:onInput(keys) - if self.edit_key and keys[self.edit_key] and not self.edit.active then - self.edit.active = true - return true - elseif keys.LEAVESCREEN and self.edit.active then - self.edit.active = false - return true - else - return self:inputToSubviews(keys) - end -end - - function FilteredList:getChoices() return self.choices end @@ -934,7 +1396,7 @@ end function FilteredList:setChoices(choices, pos) choices = choices or {} - self.edit.text = '' + self.edit:setText('') self.list:setChoices(choices, pos) self.choices = self.list.choices self.not_found.visible = (#choices == 0) @@ -976,7 +1438,9 @@ function FilteredList:setFilter(filter, pos) local cidx = nil filter = filter or '' - self.edit.text = filter + if filter ~= self.edit.text then + self.edit:setText(filter) + end if filter ~= '' then local tokens = filter:split() diff --git a/library/lua/helpdb.lua b/library/lua/helpdb.lua new file mode 100644 index 000000000..5af41c929 --- /dev/null +++ b/library/lua/helpdb.lua @@ -0,0 +1,822 @@ +-- The help text database and query interface. +-- +-- Help text is read from the rendered text in hack/docs/docs/. If no rendered +-- text exists, it is read from the script sources (for scripts) or the string +-- passed to the PluginCommand initializer (for plugins). +-- +-- There should be one help file for each plugin that contains a summary for the +-- plugin itself and help for all the commands that plugin provides (if any). +-- Each script should also have one documentation file. +-- +-- The database is lazy-loaded when an API method is called. It rechecks its +-- help sources for updates if an API method has not been called in the last +-- 60 seconds. + +local _ENV = mkmodule('helpdb') + +local argparse = require('argparse') + +local MAX_STALE_MS = 60000 + +-- paths +local RENDERED_PATH = 'hack/docs/docs/tools/' +local TAG_DEFINITIONS = 'hack/docs/docs/Tags.txt' + +-- used when reading help text embedded in script sources +local SCRIPT_DOC_BEGIN = '[====[' +local SCRIPT_DOC_END = ']====]' +local SCRIPT_DOC_BEGIN_RUBY = '=begin' +local SCRIPT_DOC_END_RUBY = '=end' + +-- enums +local ENTRY_TYPES = { + BUILTIN='builtin', + PLUGIN='plugin', + COMMAND='command' +} + +local HELP_SOURCES = { + RENDERED='rendered', -- from the installed, rendered help text + PLUGIN='plugin', -- from the plugin source code + SCRIPT='script', -- from the script source code + STUB='stub', -- from a generated stub +} + +-- builtin command names, with aliases mapped to their canonical form +local BUILTINS = { + ['?']='help', + alias=true, + clear='cls', + cls=true, + ['devel/dump-rpc']=true, + die=true, + dir='ls', + disable=true, + enable=true, + fpause=true, + help=true, + hide=true, + keybinding=true, + ['kill-lua']=true, + ['load']=true, + ls=true, + man='help', + plug=true, + reload=true, + script=true, + ['sc-script']=true, + show=true, + tags=true, + ['type']=true, + unload=true, +} + +--------------------------------------------------------------------------- +-- data structures +--------------------------------------------------------------------------- + +-- help text database, keys are a subset of the entry database +-- entry name -> { +-- help_source (element of HELP_SOURCES), +-- short_help (string), +-- long_help (string), +-- tags (set), +-- source_timestamp (mtime, 0 for non-files), +-- source_path (string, nil for non-files) +-- } +local textdb = {} + +-- entry database, points to text in textdb +-- entry name -> { +-- entry_types (set of ENTRY_TYPES), +-- short_help (string, if not nil then overrides short_help in text_entry), +-- text_entry (string) +-- } +-- +-- entry_types is a set because plugin commands can also be the plugin names. +local entrydb = {} + + +-- tag name -> list of entry names +-- Tags defined in the TAG_DEFINITIONS file that have no associated db entries +-- will have an empty list. +local tag_index = {} + +--------------------------------------------------------------------------- +-- data ingestion +--------------------------------------------------------------------------- + +local function get_rendered_path(entry_name) + return RENDERED_PATH .. entry_name .. '.txt' +end + +local function has_rendered_help(entry_name) + return dfhack.filesystem.mtime(get_rendered_path(entry_name)) ~= -1 +end + +local DEFAULT_HELP_TEMPLATE = [[ +%s +%s + +No help available. +]] +local function make_default_entry(entry_name, help_source, kwargs) + local default_long_help = DEFAULT_HELP_TEMPLATE:format( + entry_name, ('='):rep(#entry_name)) + return { + help_source=help_source, + short_help='No help available.', + long_help=default_long_help, + tags={}, + source_timestamp=kwargs.source_timestamp or 0, + source_path=kwargs.source_path} +end + +-- updates the short_text, the long_text, and the tags in the given entry based +-- on the text returned from the iterator. +-- if defined, opts can have the following fields: +-- begin_marker (string that marks the beginning of the help text; all text +-- before this marker is ignored) +-- end_marker (string that marks the end of the help text; text will stop +-- being parsed after this marker is seen) +-- no_header (don't try to find the entity name at the top of the help text) +-- first_line_is_short_help (if set, then read the short help text from the +-- first commented line of the script instead of +-- using the first sentence of the long help text. +-- value is the comment character.) +local function update_entry(entry, iterator, opts) + opts = opts or {} + local lines, tags = {}, '' + local first_line_is_short_help = opts.first_line_is_short_help + local begin_marker_found,header_found = not opts.begin_marker,opts.no_header + local tags_found, short_help_found = false, opts.skip_short_help + local in_tags, in_short_help = false, false + for line in iterator do + if not short_help_found and first_line_is_short_help then + line = line:trim() + local _,_,text = line:find('^'..first_line_is_short_help..'%s*(.*)') + if not text then + -- if no first-line short help found, fall back to getting the + -- first sentence of the help text. + first_line_is_short_help = false + else + if not text:endswith('.') then + text = text .. '.' + end + entry.short_help = text + short_help_found = true + goto continue + end + end + if not begin_marker_found then + local _, endpos = line:find(opts.begin_marker, 1, true) + if endpos == #line then + begin_marker_found = true + end + goto continue + end + if opts.end_marker then + local _, endpos = line:find(opts.end_marker, 1, true) + if endpos == #line then + break + end + end + if not header_found and line:find('%w') then + header_found = true + elseif in_tags then + if #line == 0 then + in_tags = false + else + tags = tags .. line + end + elseif not tags_found and line:find('^[*]*Tags:[*]*') then + _,_,tags = line:trim():find('[*]*Tags:[*]* *(.*)') + in_tags, tags_found = true, true + elseif not short_help_found and + line:find('^%s*%w') and not line:find('^%w+:') then + if in_short_help then + entry.short_help = entry.short_help .. ' ' .. line + else + entry.short_help = line:trim() + end + local sentence_end = entry.short_help:find('.', 1, true) + if sentence_end then + entry.short_help = entry.short_help:sub(1, sentence_end) + short_help_found = true + else + in_short_help = true + end + end + table.insert(lines, line) + ::continue:: + end + entry.tags = {} + for _,tag in ipairs(tags:split('[ ,|]+')) do + if #tag > 0 and tag_index[tag] then + entry.tags[tag] = true + end + end + if #lines > 0 then + entry.long_help = table.concat(lines, '\n') + end +end + +-- create db entry based on parsing sphinx-rendered help text +local function make_rendered_entry(old_entry, entry_name, kwargs) + local source_path = get_rendered_path(entry_name) + local source_timestamp = dfhack.filesystem.mtime(source_path) + if old_entry and old_entry.help_source == HELP_SOURCES.RENDERED and + old_entry.source_timestamp >= source_timestamp then + -- we already have the latest info + return old_entry + end + kwargs.source_path, kwargs.source_timestamp = source_path, source_timestamp + local entry = make_default_entry(entry_name, HELP_SOURCES.RENDERED, kwargs) + local ok, lines = pcall(io.lines, source_path) + if not ok then + return entry + end + update_entry(entry, lines) + return entry +end + +-- create db entry based on the help text in the plugin source (used by +-- out-of-tree plugins) +local function make_plugin_entry(old_entry, entry_name, kwargs) + if old_entry and old_entry.source == HELP_SOURCES.PLUGIN then + -- we can't tell when a plugin is reloaded, so we can either choose to + -- always refresh or never refresh. let's go with never for now for + -- performance. + return old_entry + end + local entry = make_default_entry(entry_name, HELP_SOURCES.PLUGIN, kwargs) + local long_help = dfhack.internal.getCommandHelp(entry_name) + if long_help and #long_help:trim() > 0 then + update_entry(entry, long_help:trim():gmatch('[^\n]*'), {no_header=true}) + end + return entry +end + +-- create db entry based on the help text in the script source (used by +-- out-of-tree scripts) +local function make_script_entry(old_entry, entry_name, kwargs) + local source_path = kwargs.source_path + local source_timestamp = dfhack.filesystem.mtime(source_path) + if old_entry and old_entry.source == HELP_SOURCES.SCRIPT and + old_entry.source_path == source_path and + old_entry.source_timestamp >= source_timestamp then + -- we already have the latest info + return old_entry + end + kwargs.source_timestamp, kwargs.entry_type = source_timestamp + local entry = make_default_entry(entry_name, HELP_SOURCES.SCRIPT, kwargs) + local ok, lines = pcall(io.lines, source_path) + if not ok then + return entry + end + local is_rb = source_path:endswith('.rb') + update_entry(entry, lines, + {begin_marker=(is_rb and SCRIPT_DOC_BEGIN_RUBY or SCRIPT_DOC_BEGIN), + end_marker=(is_rb and SCRIPT_DOC_END_RUBY or SCRIPT_DOC_END), + first_line_is_short_help=(is_rb and '#' or '%-%-')}) + return entry +end + +-- updates the dbs (and associated tag index) with a new entry if the entry_name +-- doesn't already exist in the dbs. +local function update_db(old_db, entry_name, text_entry, help_source, kwargs) + if entrydb[entry_name] then + -- already in db (e.g. from a higher-priority script dir); skip + return + end + entrydb[entry_name] = { + entry_types=kwargs.entry_types, + short_help=kwargs.short_help, + text_entry=text_entry + } + if entry_name ~= text_entry then + return + end + + local text_entry, old_entry = nil, old_db[entry_name] + if help_source == HELP_SOURCES.RENDERED then + text_entry = make_rendered_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.PLUGIN then + text_entry = make_plugin_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.SCRIPT then + text_entry = make_script_entry(old_entry, entry_name, kwargs) + elseif help_source == HELP_SOURCES.STUB then + text_entry = make_default_entry(entry_name, HELP_SOURCES.STUB, kwargs) + else + error('unhandled help source: ' .. help_source) + end + textdb[entry_name] = text_entry +end + +-- add the builtin commands to the db +local function scan_builtins(old_db) + local entry_types = {[ENTRY_TYPES.BUILTIN]=true, [ENTRY_TYPES.COMMAND]=true} + for builtin,canonical in pairs(BUILTINS) do + if canonical == true then canonical = builtin end + update_db(old_db, builtin, canonical, + has_rendered_help(canonical) and + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + {entry_types=entry_types}) + end + -- easter egg: replace underline for 'die' help with tombstones + textdb.die.long_help = textdb.die.long_help:gsub('=', string.char(239)) +end + +-- scan for enableable plugins and plugin-provided commands and add their help +-- to the db +local function scan_plugins(old_db) + local plugin_names = dfhack.internal.listPlugins() + for _,plugin in ipairs(plugin_names) do + local commands = dfhack.internal.listCommands(plugin) + local includes_plugin = false + for _,command in ipairs(commands) do + local kwargs = {entry_types={[ENTRY_TYPES.COMMAND]=true}} + if command == plugin then + kwargs.entry_types[ENTRY_TYPES.PLUGIN]=true + includes_plugin = true + end + kwargs.short_help = dfhack.internal.getCommandDescription(command) + update_db(old_db, command, plugin, + has_rendered_help(plugin) and + HELP_SOURCES.RENDERED or HELP_SOURCES.PLUGIN, + kwargs) + end + if not includes_plugin then + update_db(old_db, plugin, plugin, + has_rendered_help(plugin) and + HELP_SOURCES.RENDERED or HELP_SOURCES.STUB, + {entry_types={[ENTRY_TYPES.PLUGIN]=true}}) + end + end +end + +-- scan for scripts and add their help to the db +local function scan_scripts(old_db) + local entry_types = {[ENTRY_TYPES.COMMAND]=true} + 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 f.isdir or + (not f.path:endswith('.lua') and not f.path:endswith('.rb')) or + f.path:startswith('test/') or + f.path:startswith('internal/') then + goto continue + end + local dot_index = f.path:find('%.[^.]*$') + local entry_name = f.path:sub(1, dot_index - 1) + local source_path = script_path .. '/' .. f.path + update_db(old_db, entry_name, entry_name, + has_rendered_help(entry_name) and + HELP_SOURCES.RENDERED or HELP_SOURCES.SCRIPT, + {entry_types=entry_types, source_path=source_path}) + ::continue:: + end + ::skip_path:: + end +end + +-- read tags and descriptions from the TAG_DEFINITIONS file and add them all +-- to tag_index, initizlizing each entry with an empty list. +local function initialize_tags() + local tag, desc, in_desc = nil, nil, false + local ok, lines = pcall(io.lines, TAG_DEFINITIONS) + if not ok then return end + for line in lines do + if in_desc then + line = line:trim() + if #line == 0 then + in_desc = false + goto continue + end + desc = desc .. ' ' .. line + tag_index[tag].description = desc + else + _,_,tag,desc = line:find('^%* (%w+)[^:]*: (.+)') + if not tag then goto continue end + tag_index[tag] = {description=desc} + in_desc = true + end + ::continue:: + end +end + +local function index_tags() + for entry_name,entry in pairs(entrydb) do + for tag in pairs(textdb[entry.text_entry].tags) do + -- ignore unknown tags + if tag_index[tag] then + table.insert(tag_index[tag], entry_name) + end + end + end +end + +-- ensures the db is up to date by scanning all help sources. does not do +-- anything if it has already been run within the last MAX_STALE_MS milliseconds +local last_refresh_ms = 0 +local function ensure_db() + local now_ms = dfhack.getTickCount() + if now_ms - last_refresh_ms <= MAX_STALE_MS then return end + last_refresh_ms = now_ms + + local old_db = textdb + textdb, entrydb, tag_index = {}, {}, {} + + initialize_tags() + scan_builtins(old_db) + scan_plugins(old_db) + scan_scripts(old_db) + index_tags() +end + +local function parse_blocks(text) + local blocks = {} + for line in text:gmatch('[^\n]*') do + local _,indent = line:find('^ *') + table.insert(blocks, {line=line:trim(), indent=indent}) + end + return blocks +end + +local function format_block(line, indent, width) + local wrapped = line:wrap(width - indent) + if indent == 0 then return wrapped end + local padding = (' '):rep(indent) + local indented_lines = {} + for line in wrapped:gmatch('[^\n]*') do + table.insert(indented_lines, padding .. line) + end + return table.concat(indented_lines, '\n') +end + +-- wraps the unwrapped source help at the specified width, preserving block +-- indents +local function rewrap(text, width) + local formatted_blocks = {} + for _,block in ipairs(parse_blocks(text)) do + table.insert(formatted_blocks, + format_block(block.line, block.indent, width)) + end + return table.concat(formatted_blocks, '\n') +end + +--------------------------------------------------------------------------- +-- get API +--------------------------------------------------------------------------- + +-- converts strings into single-element lists containing that string +local function normalize_string_list(l) + if not l or #l == 0 then return nil end + if type(l) == 'string' then + return {l} + end + return l +end + +local function has_keys(str, dict) + if not str or #str == 0 then + return false + end + for _,s in ipairs(normalize_string_list(str)) do + if not dict[s] then + return false + end + end + return true +end + +-- returns whether the given string (or list of strings) is an entry (are all +-- entries) in the db +function is_entry(str) + ensure_db() + return has_keys(str, entrydb) +end + +local function get_db_property(entry_name, property) + ensure_db() + if not entrydb[entry_name] then + error(('helpdb entry not found: "%s"'):format(entry_name)) + end + return entrydb[entry_name][property] or + textdb[entrydb[entry_name].text_entry][property] +end + +function get_entry_types(entry) + return get_db_property(entry, 'entry_types') +end + +-- returns the ~54 char summary blurb associated with the entry +function get_entry_short_help(entry) + return get_db_property(entry, 'short_help') +end + +-- returns the full help documentation associated with the entry, optionally +-- wrapped to the specified width (80 if not specified). +function get_entry_long_help(entry, width) + return rewrap(get_db_property(entry, 'long_help'), width or 80) +end + +-- returns the set of tags associated with the entry +function get_entry_tags(entry) + return get_db_property(entry, 'tags') +end + +-- returns whether the given string (or list of strings) matches a tag name +function is_tag(str) + ensure_db() + return has_keys(str, tag_index) +end + +local function set_to_sorted_list(set) + local list = {} + for item in pairs(set) do + table.insert(list, item) + end + table.sort(list) + return list +end + +-- returns the defined tags in alphabetical order +function get_tags() + ensure_db() + return set_to_sorted_list(tag_index) +end + +function get_tag_data(tag) + ensure_db() + if not tag_index[tag] then + error(('helpdb tag not found: "%s"'):format(tag)) + end + return tag_index[tag] +end + +--------------------------------------------------------------------------- +-- search API +--------------------------------------------------------------------------- + +-- returns a list of path elements in reverse order +local function chunk_for_sorting(str) + local parts = str:split('/') + local chunks = {} + for i=1,#parts do + chunks[#parts - i + 1] = parts[i] + end + return chunks +end + +-- sorts by last path component, then by parent path components. +-- something comes before nothing. +-- e.g. gui/autofarm comes immediately before autofarm +function sort_by_basename(a, b) + local a = chunk_for_sorting(a) + local b = chunk_for_sorting(b) + local i = 1 + while a[i] do + if not b[i] then + return true + end + if a[i] ~= b[i] then + return a[i] < b[i] + end + i = i + 1 + end + return false +end + +-- returns true if all filter elements are matched (i.e. any of the tags AND +-- any of the strings AND any of the entry_types) +local function matches(entry_name, filter) + if filter.tag then + local matched = false + local tags = get_db_property(entry_name, 'tags') + for _,tag in ipairs(filter.tag) do + if tags[tag] then + matched = true + break + end + end + if not matched then + return false + end + end + if filter.entry_type then + local matched = false + local etypes = get_db_property(entry_name, 'entry_types') + for _,etype in ipairs(filter.entry_type) do + if etypes[etype] then + matched = true + break + end + end + if not matched then + return false + end + end + if filter.str then + local matched = false + for _,str in ipairs(filter.str) do + if entry_name:find(str, 1, true) then + matched = true + break + end + end + if not matched then + return false + end + end + return true +end + +local function matches_any(entry_name, filters) + for _,filter in ipairs(filters) do + if matches(entry_name, filter) then + return true + end + end + return false +end + +-- normalizes the lists in the filter and returns nil if no filter elements are +-- populated +local function normalize_filter_map(f) + if not f then return nil end + local filter = {} + filter.str = normalize_string_list(f.str) + filter.tag = normalize_string_list(f.tag) + filter.entry_type = normalize_string_list(f.entry_type) + if not filter.str and not filter.tag and not filter.entry_type then + return nil + end + return filter +end + +local function normalize_filter_list(fs) + if not fs then return nil end + local filter_list = {} + for _,f in ipairs(#fs > 0 and fs or {fs}) do + table.insert(filter_list, normalize_filter_map(f)) + end + if #filter_list == 0 then return nil end + return filter_list +end + +-- returns a list of entry names, alphabetized by their last path component, +-- with populated path components coming before null path components (e.g. +-- autobutcher will immediately follow gui/autobutcher). +-- the optional include and exclude filter params are maps (or lists of maps) +-- with the following elements: +-- str - if a string, filters by the given substring. if a table of strings, +-- includes entry names that match any of the given substrings. +-- tag - if a string, filters by the given tag name. if a table of strings, +-- includes entries that match any of the given tags. +-- entry_type - if a string, matches entries of the given type. if a table of +-- strings, includes entries that match any of the given types. valid +-- types are: "builtin", "plugin", "command". note that many plugin +-- commands have the same name as the plugin, so those entries will +-- match both "plugin" and "command" types. +-- filter elements in a map are ANDed together (e.g. if both str and tag are +-- specified, the match is on any of the str elements AND any of the tag +-- elements). If lists of maps are passed, the maps are ORed (that is, the match +-- succeeds if any of the filters match). +function search_entries(include, exclude) + ensure_db() + include = normalize_filter_list(include) + exclude = normalize_filter_list(exclude) + local entries = {} + for entry in pairs(entrydb) do + if (not include or matches_any(entry, include)) and + (not exclude or not matches_any(entry, exclude)) then + table.insert(entries, entry) + end + end + table.sort(entries, sort_by_basename) + return entries +end + +-- returns a list of all commands. used by Core's autocomplete functionality. +function get_commands() + local include = {entry_type=ENTRY_TYPES.COMMAND} + return search_entries(include) +end + +function is_builtin(command) + return is_entry(command) and get_entry_types(command)[ENTRY_TYPES.BUILTIN] +end + +--------------------------------------------------------------------------- +-- print API (outputs to console) +--------------------------------------------------------------------------- + +-- implements the 'help' builtin command +function help(entry) + ensure_db() + if not entrydb[entry] then + dfhack.printerr(('No help entry found for "%s"'):format(entry)) + return + end + print(get_entry_long_help(entry)) +end + +-- prints col1text (width 21), a one space gap, and col2 (width 58) +-- if col1text is longer than 21 characters, col2text is printed starting on the +-- next line. if col2text is longer than 58 characters, it is wrapped. col2text +-- lines on lines below the col1text output are indented by one space further +-- than the col2text on the first line. +local COL1WIDTH, COL2WIDTH = 20, 58 +local function print_columns(col1text, col2text) + col2text = col2text:wrap(COL2WIDTH) + local wrapped_col2 = {} + for line in col2text:gmatch('[^'..NEWLINE..']*') do + table.insert(wrapped_col2, line) + end + local col2_start_line = 1 + if #col1text > COL1WIDTH then + print(col1text) + else + print(('%-'..COL1WIDTH..'s %s'):format(col1text, wrapped_col2[1])) + col2_start_line = 2 + end + for i=col2_start_line,#wrapped_col2 do + print(('%'..COL1WIDTH..'s %s'):format(' ', wrapped_col2[i])) + end +end + +-- prints the requested entries to the console. include and exclude filters are +-- defined as in search_entries() above. +local function list_entries(skip_tags, include, exclude) + local entries = search_entries(include, exclude) + for _,entry in ipairs(entries) do + local short_help = get_entry_short_help(entry) + if not skip_tags then + local tags = set_to_sorted_list(get_entry_tags(entry)) + if #tags > 0 then + local taglist = table.concat(tags, ', ') + short_help = short_help .. NEWLINE .. 'tags: ' .. taglist + end + end + print_columns(entry, short_help) + end + if #entries == 0 then + print('No matches.') + end +end + +-- wraps the list_entries() API to provide a more convenient interface for Core +-- to implement the 'ls' builtin command. +-- filter_str - if a tag name (or a list of tag names), will filter by that +-- tag/those tags. otherwise, will filter as a substring/list of +-- substrings +-- skip_tags - whether to skip printing tag info +-- show_dev_commands - if true, will include scripts in the modtools/ and +-- devel/ directories. otherwise those scripts will be +-- excluded +-- exclude_strs - comma-separated list of strings. entries are excluded if +-- they match any of the strings. +function ls(filter_str, skip_tags, show_dev_commands, exclude_strs) + local include = {entry_type={ENTRY_TYPES.COMMAND}} + if is_tag(filter_str) then + include.tag = filter_str + else + include.str = filter_str + end + local excludes = {} + if exclude_strs and #exclude_strs > 0 then + table.insert(excludes, {str=argparse.stringList(exclude_strs)}) + end + if not show_dev_commands then + table.insert(excludes, {tag='dev'}) + end + list_entries(skip_tags, include, excludes) +end + +local function list_tags() + local tags = get_tags() + for _,tag in ipairs(tags) do + print_columns(tag, get_tag_data(tag).description) + end +end + +-- implements the 'tags' builtin command +function tags(tag) + if tag and #tag > 0 and not is_tag(tag) then + dfhack.printerr(('unrecognized tag: "%s"'):format(tag)) + end + + if not is_tag(tag) then + list_tags() + return + end + + local skip_tags = true + local include = {entry_type={ENTRY_TYPES.COMMAND}, tag=tag} + list_entries(skip_tags, include) +end + +return _ENV diff --git a/library/lua/test_util/mock.lua b/library/lua/test_util/mock.lua index c60646b77..8d253cc10 100644 --- a/library/lua/test_util/mock.lua +++ b/library/lua/test_util/mock.lua @@ -32,12 +32,17 @@ function _patch_impl(patches_raw, callback, restore_only) end --[[ + +Replaces `table[key]` with `value`, calls `callback()`, then restores the +original value of `table[key]`. + Usage: patch(table, key, value, callback) patch({ {table, key, value}, {table2, key2, value2}, }, callback) + ]] function mock.patch(...) local args = {...} @@ -57,12 +62,18 @@ function mock.patch(...) end --[[ + +Restores the original value of `table[key]` after calling `callback()`. + +Equivalent to: patch(table, key, table[key], callback) + Usage: restore(table, key, callback) restore({ {table, key}, {table2, key2}, }, callback) + ]] function mock.restore(...) local args = {...} @@ -81,9 +92,19 @@ function mock.restore(...) return _patch_impl(patches, callback, true) end -function mock.func(...) +--[[ + +Returns a callable object that tracks the arguments it is called with, then +passes those arguments to `callback()`. + +The returned object has the following properties: +- `call_count`: the number of times the object has been called +- `call_args`: a table of function arguments (shallow-copied) corresponding + to each time the object was called + +]] +function mock.observe_func(callback) local f = { - return_values = {...}, call_count = 0, call_args = {}, } @@ -101,11 +122,36 @@ function mock.func(...) end end table.insert(self.call_args, args) - return table.unpack(self.return_values) + return callback(...) end, }) return f end +--[[ + +Returns a callable object similar to `mock.observe_func()`, but which when +called, only returns the given `return_value`(s) with no additional side effects. + +Intended for use by `patch()`. + +Usage: + func(return_value [, return_value2 ...]) + +See `observe_func()` for a description of the return value. + +The return value also has an additional `return_values` field, which is a table +of values returned when the object is called. This can be modified. + +]] +function mock.func(...) + local f + f = mock.observe_func(function() + return table.unpack(f.return_values) + end) + f.return_values = {...} + return f +end + return mock diff --git a/library/lua/tile-material.lua b/library/lua/tile-material.lua index 0e5565d09..ca8b25030 100644 --- a/library/lua/tile-material.lua +++ b/library/lua/tile-material.lua @@ -1,25 +1,7 @@ -- tile-material: Functions to help retrieve the material for a tile. --[[ -Copyright 2015-2016 Milo Christiansen - -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. +Original code provided by Milo Christiansen in 2015 under the MIT license. Relicensed under the ZLib license to align with the rest of DFHack, with his permission. ]] local _ENV = mkmodule("tile-material") @@ -85,73 +67,6 @@ local function fixedMat(id) end end --- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table covers the common case of returning plant materials for plant tiles and other --- materials for the remaining tiles. -BasicMats = { - [df.tiletype_material.AIR] = nil, -- Empty - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.MAGMA] = nil, -- SMR - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, - [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? -} - --- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will ignore plants, returning layer materials (or nil for trees) instead. -NoPlantMats = { - [df.tiletype_material.SOIL] = GetLayerMat, - [df.tiletype_material.STONE] = GetLayerMat, - [df.tiletype_material.FEATURE] = GetFeatureMat, - [df.tiletype_material.LAVA_STONE] = GetLavaStone, - [df.tiletype_material.MINERAL] = GetVeinMat, - [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), - [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, - [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, - [df.tiletype_material.GRASS_DARK] = GetLayerMat, - [df.tiletype_material.GRASS_DRY] = GetLayerMat, - [df.tiletype_material.GRASS_DEAD] = GetLayerMat, - [df.tiletype_material.PLANT] = GetLayerMat, - [df.tiletype_material.CAMPFIRE] = GetLayerMat, - [df.tiletype_material.FIRE] = GetLayerMat, - [df.tiletype_material.ASHES] = GetLayerMat, - [df.tiletype_material.DRIFTWOOD] = GetLayerMat, - [df.tiletype_material.POOL] = GetLayerMat, - [df.tiletype_material.BROOK] = GetLayerMat, - [df.tiletype_material.ROOT] = GetLayerMat, -} - --- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular --- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. -OnlyPlantMats = { - [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, - [df.tiletype_material.GRASS_DARK] = GetGrassMat, - [df.tiletype_material.GRASS_DRY] = GetGrassMat, - [df.tiletype_material.GRASS_DEAD] = GetGrassMat, - [df.tiletype_material.PLANT] = GetShrubMat, - [df.tiletype_material.TREE] = GetTreeMat, - [df.tiletype_material.MUSHROOM] = GetTreeMat, -} - -- GetLayerMat returns the layer material for the given tile. -- AFAIK this will never return nil. function GetLayerMat(x, y, z) @@ -349,6 +264,73 @@ function GetFeatureMat(x, y, z) return nil end +-- BasicMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table covers the common case of returning plant materials for plant tiles and other +-- materials for the remaining tiles. +BasicMats = { + [df.tiletype_material.AIR] = nil, -- Empty + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.HFS] = nil, -- Eerie Glowing Pit + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.MAGMA] = nil, -- SMR + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, + [df.tiletype_material.UNDERWORLD_GATE] = nil, -- I guess this is for the gates found in vaults? +} + +-- NoPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will ignore plants, returning layer materials (or nil for trees) instead. +NoPlantMats = { + [df.tiletype_material.SOIL] = GetLayerMat, + [df.tiletype_material.STONE] = GetLayerMat, + [df.tiletype_material.FEATURE] = GetFeatureMat, + [df.tiletype_material.LAVA_STONE] = GetLavaStone, + [df.tiletype_material.MINERAL] = GetVeinMat, + [df.tiletype_material.FROZEN_LIQUID] = fixedMat("WATER:NONE"), + [df.tiletype_material.CONSTRUCTION] = GetConstructionMat, + [df.tiletype_material.GRASS_LIGHT] = GetLayerMat, + [df.tiletype_material.GRASS_DARK] = GetLayerMat, + [df.tiletype_material.GRASS_DRY] = GetLayerMat, + [df.tiletype_material.GRASS_DEAD] = GetLayerMat, + [df.tiletype_material.PLANT] = GetLayerMat, + [df.tiletype_material.CAMPFIRE] = GetLayerMat, + [df.tiletype_material.FIRE] = GetLayerMat, + [df.tiletype_material.ASHES] = GetLayerMat, + [df.tiletype_material.DRIFTWOOD] = GetLayerMat, + [df.tiletype_material.POOL] = GetLayerMat, + [df.tiletype_material.BROOK] = GetLayerMat, + [df.tiletype_material.ROOT] = GetLayerMat, +} + +-- OnlyPlantMats is a matspec table to pass to GetTileMatSpec or GetTileTypeMat. This particular +-- matspec table will return nil for any non-plant tile. Plant tiles return the plant material. +OnlyPlantMats = { + [df.tiletype_material.GRASS_LIGHT] = GetGrassMat, + [df.tiletype_material.GRASS_DARK] = GetGrassMat, + [df.tiletype_material.GRASS_DRY] = GetGrassMat, + [df.tiletype_material.GRASS_DEAD] = GetGrassMat, + [df.tiletype_material.PLANT] = GetShrubMat, + [df.tiletype_material.TREE] = GetTreeMat, + [df.tiletype_material.MUSHROOM] = GetTreeMat, +} + -- GetTileMat will return the material of the specified tile as determined by its tile type and the -- world geology data, etc. -- The returned material should exactly match the material reported by DF except in cases where is diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 99ca87c6e..00e31929f 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -608,18 +608,20 @@ df_env = df_shortcut_env() function df_expr_to_ref(expr) expr = expr:gsub('%["(.-)"%]', function(field) return '.' .. field end) :gsub('%[\'(.-)\'%]', function(field) return '.' .. field end) - :gsub('%[(%d+)]', function(field) return '.' .. field end) + :gsub('%[(%-?%d+)%]', function(field) return '.' .. field end) local parts = expr:split('.', true) local obj = df_env[parts[1]] for i = 2, #parts do local key = tonumber(parts[i]) or parts[i] - local cur = obj[key] - if i == #parts and ((type(cur) ~= 'userdata') or - type(cur) == 'userdata' and getmetatable(cur) == nil) then - obj = obj:_field(key) - else - obj = obj[key] + if i == #parts then + local ok, ret = pcall(function() + return obj:_field(key) + end) + if ok then + return ret + end end + obj = obj[key] end return obj end diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index dd8d5f0e9..1918771ae 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -308,9 +308,8 @@ df::building *Buildings::findAtTile(df::coord pos) static unordered_map corner1; static unordered_map corner2; -static void cacheBuilding(df::building *building) { +static void cacheBuilding(df::building *building, bool is_civzone) { int32_t id = building->id; - bool is_civzone = !building->isSettingOccupancy(); df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); @@ -344,7 +343,7 @@ static void cacheNewCivzones() { auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; int32_t idx = df::building::binsearch_index(vec, id); if (idx > -1) - cacheBuilding(vec[idx]); + cacheBuilding(vec[idx], true); } nextCivzone = nextBuildingId; } @@ -1311,8 +1310,9 @@ void Buildings::updateBuildings(color_ostream&, void* ptr) if (building) { - if (!corner1.count(id)) - cacheBuilding(building); + bool is_civzone = !building->isSettingOccupancy(); + if (!corner1.count(id) && !is_civzone) + cacheBuilding(building, false); } else if (corner1.count(id)) { diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 9cec2eab9..407e95f76 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -51,47 +51,21 @@ using namespace DFHack; using namespace df::enums; using df::global::world; -bool Constructions::isValid() -{ - return (world != NULL); -} - -uint32_t Constructions::getCount() -{ - return world->constructions.size(); -} - -df::construction * Constructions::getConstruction(const int32_t index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->constructions[index]; -} df::construction * Constructions::findAtTile(df::coord pos) { - for (auto it = world->constructions.begin(); it != world->constructions.end(); ++it) { - if ((*it)->pos == pos) - return *it; + int index = binsearch_index(world->constructions, pos); + if (index == -1) { + return NULL; } - return NULL; + return world->constructions[index]; } -bool Constructions::copyConstruction(const int32_t index, t_construction &out) +bool Constructions::insert(df::construction * constr) { - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->constructions[index]; - - out.pos = out.origin->pos; - out.item_type = out.origin->item_type; - out.item_subtype = out.origin->item_subtype; - out.mat_type = out.origin->mat_type; - out.mat_index = out.origin->mat_index; - out.flags = out.origin->flags; - out.original_tile = out.origin->original_tile; - return true; + bool toInsert; + insert_into_vector(world->constructions, &df::construction::pos, constr, &toInsert); + return toInsert; } bool Constructions::designateNew(df::coord pos, df::construction_type type, diff --git a/library/modules/Engravings.cpp b/library/modules/Engravings.cpp deleted file mode 100644 index c2e0e6fce..000000000 --- a/library/modules/Engravings.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "MemAccess.h" -#include "Types.h" -#include "Core.h" - -#include "modules/Engravings.h" -#include "df/world.h" - -using namespace DFHack; -using df::global::world; - -bool Engravings::isValid() -{ - return (world != NULL); -} - -uint32_t Engravings::getCount() -{ - return world->engravings.size(); -} - -df::engraving * Engravings::getEngraving(int index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->engravings[index]; -} - -bool Engravings::copyEngraving(const int32_t index, t_engraving &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->engravings[index]; - - out.artist = out.origin->artist; - out.masterpiece_event = out.origin->masterpiece_event; - out.skill_rating = out.origin->skill_rating; - out.pos = out.origin->pos; - out.flags = out.origin->flags; - out.tile = out.origin->tile; - out.art_id = out.origin->art_id; - out.art_subid = out.origin->art_subid; - out.quality = out.origin->quality; - return true; -} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 70908aada..16f21ffdd 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -199,6 +199,9 @@ static int32_t lastJobId = -1; //job completed static unordered_map prevJobs; +//active units +static unordered_set activeUnits; + //unit death static unordered_set livingUnits; @@ -256,6 +259,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event buildings.clear(); constructions.clear(); equipmentLog.clear(); + activeUnits.clear(); Buildings::clearBuildings(out); lastReport = -1; @@ -314,6 +318,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } lastSyndromeTime = -1; for (auto unit : df::global::world->units.all) { + if (Units::isActive(unit)) { + activeUnits.emplace(unit->id); + } for (auto syndrome : unit->syndromes.active) { int32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; if ( startTime > lastSyndromeTime ) @@ -437,13 +444,13 @@ static void manageJobStartedEvent(color_ostream& out) { // iterate event handler callbacks multimap copy(handlers[EventType::JOB_STARTED].begin(), handlers[EventType::JOB_STARTED].end()); - for (auto &key_value : copy) { - auto &handler = key_value.second; - for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) { - df::job* job = link->item; - // the jobs must have a worker to start - if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { - startedJobs.emplace(job->id); + for (df::job_list_link* link = df::global::world->jobs.list.next; link != nullptr; link = link->next) { + df::job* job = link->item; + if (job && Job::getWorker(job) && !startedJobs.count(job->id)) { + startedJobs.emplace(job->id); + for (auto &key_value : copy) { + auto &handler = key_value.second; + // the jobs must have a worker to start handler.eventHandler(out, job); } } @@ -592,7 +599,6 @@ static void manageNewUnitActiveEvent(color_ostream& out) { if (!df::global::world) return; - static unordered_set activeUnits; multimap copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); // iterate event handler callbacks for (auto &key_value : copy) { @@ -600,6 +606,7 @@ static void manageNewUnitActiveEvent(color_ostream& out) { for (df::unit* unit : df::global::world->units.active) { int32_t id = unit->id; if (!activeUnits.count(id)) { + activeUnits.emplace(id); handler.eventHandler(out, (void*) intptr_t(id)); // intptr_t() avoids cast from smaller type warning } } @@ -897,7 +904,10 @@ static void manageReportEvent(color_ostream& out) { std::vector& reports = df::global::world->status.reports; size_t idx = df::report::binsearch_index(reports, lastReport, false); // returns the index to the key equal to or greater than the key provided - idx = reports[idx]->id == lastReport ? idx + 1 : idx; // we need the index after (where the new stuff is) + while (idx < reports.size() && reports[idx]->id <= lastReport) { + idx++; + } + // returns the index to the key equal to or greater than the key provided for ( ; idx < reports.size(); idx++ ) { df::report* report = reports[idx]; diff --git a/library/modules/Filesystem.cpp b/library/modules/Filesystem.cpp index e0d0bc8c2..a182ac062 100644 --- a/library/modules/Filesystem.cpp +++ b/library/modules/Filesystem.cpp @@ -246,6 +246,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, int err = Filesystem::listdir(prefixed_path, curdir_files); if (err) return err; + bool out_of_depth = false; for (auto file = curdir_files.begin(); file != curdir_files.end(); ++file) { if (*file == "." || *file == "..") @@ -254,6 +255,12 @@ static int listdir_recursive_impl (std::string prefix, std::string path, std::string path_file = path + *file; if (Filesystem::isdir(prefixed_file)) { + 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) @@ -264,7 +271,7 @@ static int listdir_recursive_impl (std::string prefix, std::string path, files.insert(std::pair(include_prefix ? prefixed_file : path_file, false)); } } - return 0; + return out_of_depth ? -1 : 0; } int Filesystem::listdir_recursive (std::string dir, std::map &files, diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 963b2ecd6..7f2097f43 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -106,6 +106,7 @@ using namespace DFHack; #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitst.h" #include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_treasurelistst.h" #include "df/viewscreen_workquota_conditionst.h" #include "df/viewscreen_workshop_profilest.h" #include "df/world.h" @@ -629,6 +630,10 @@ bool Gui::default_hotkey(df::viewscreen *top) return false; } +bool Gui::anywhere_hotkey(df::viewscreen *) { + return true; +} + bool Gui::dwarfmode_hotkey(df::viewscreen *top) { // Require the main dwarf mode screen @@ -1181,6 +1186,13 @@ df::item *Gui::getAnyItem(df::viewscreen *top) return NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_treasurelistst, top)) + { + if (world) + return vector_get(world->items.other[df::items_other_id::ANY_ARTIFACT], screen->sel_idx); + return NULL; + } + if (auto dfscreen = dfhack_viewscreen::try_cast(top)) return dfscreen->getSelectedItem(); @@ -1823,17 +1835,22 @@ bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t return true; } -bool Gui::getMousePos (int32_t & x, int32_t & y) +// returns the map coordinates that the mouse cursor is over +df::coord Gui::getMousePos() { - if (gps) { - x = gps->mouse_x; - y = gps->mouse_y; - } - else { - x = -1; - y = -1; + df::coord pos; + if (gps && gps->mouse_x > -1) { + // return invalid coords if the cursor is not over the map + DwarfmodeDims dims = getDwarfmodeViewDims(); + if (gps->mouse_x < dims.map_x1 || gps->mouse_x > dims.map_x2 || + gps->mouse_y < dims.map_y1 || gps->mouse_y > dims.map_y2) { + return pos; + } + pos = getViewportPos(); + pos.x += gps->mouse_x - 1; + pos.y += gps->mouse_y - 1; } - return (x == -1) ? false : true; + return pos; } int getDepthAt_default (int32_t x, int32_t y) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index ac800cffc..ff158caa0 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -298,10 +298,10 @@ void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int c } } -void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *ref) { - if (!ref) return; +void DFHack::Job::disconnectJobItem(df::job *job, df::job_item_ref *item_ref) { + if (!item_ref) return; - auto item = ref->item; + auto item = item_ref->item; if (!item) return; //Work backward through the specific refs & remove/delete all specific refs to this job @@ -360,10 +360,33 @@ bool DFHack::Job::removeJob(df::job* job) { using df::global::world; CHECK_NULL_POINTER(job); - // call the job cancel vmethod graciously provided by The Toady One. - // job_handler::cancel_job calls job::~job, and then deletes job (this has been confirmed by disassembly) - // this method cannot fail; it will either delete the job or crash/corrupt DF + // cancel_job below does not clean up all refs, so we have to do some work + + // manually handle DESTROY_BUILDING jobs (cancel_job doesn't handle them) + if (job->job_type == df::job_type::DestroyBuilding) { + for (auto &genRef : job->general_refs) { + disconnectJobGeneralRef(job, genRef); + if (genRef) delete genRef; + } + job->general_refs.resize(0); + // remove the job from the world + job->list_link->prev->next = job->list_link->next; + delete job->list_link; + delete job; + return true; + } + + // clean up item refs and delete them + for (auto &item_ref : job->items) { + disconnectJobItem(job, item_ref); + if (item_ref) delete item_ref; + } + job->items.resize(0); + + // call the job cancel vmethod graciously provided by The Toady One. + // job_handler::cancel_job calls job::~job, and then deletes job (this has + // been confirmed by disassembly). world->jobs.cancel_job(job); return true; diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index ce3039ecf..b88a078b1 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -303,7 +303,7 @@ int32_t MapExtras::Block::priorityAt(df::coord2d pos) bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority) { - if (!block || priority < 0) + if (!block || priority <= 0) return false; auto event = getPriorityEvent(block, true); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 889a40104..e046faa0e 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -107,9 +107,9 @@ bool Maps::IsValid () } // getter for map size in blocks -void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) +inline void getSizeInline (int32_t& x, int32_t& y, int32_t& z) { - if (!IsValid()) + if (!Maps::IsValid()) { x = y = z = 0; return; @@ -118,14 +118,38 @@ void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) y = world->map.y_count_block; z = world->map.z_count_block; } +void Maps::getSize (int32_t& x, int32_t& y, int32_t& z) +{ + getSizeInline(x, y, z); +} +void Maps::getSize (uint32_t& x, uint32_t& y, uint32_t& z) //todo: deprecate me +{ + int32_t sx, sy, sz; + getSizeInline(sx, sy, sz); + x = uint32_t(sx); + y = uint32_t(sy); + z = uint32_t(sz); +} // getter for map size in tiles -void Maps::getTileSize (uint32_t& x, uint32_t& y, uint32_t& z) +inline void getTileSizeInline (int32_t& x, int32_t& y, int32_t& z) { - getSize(x, y, z); + getSizeInline(x, y, z); x *= 16; y *= 16; } +void Maps::getTileSize (int32_t& x, int32_t& y, int32_t& z) +{ + getTileSizeInline(x, y, z); +} +void Maps::getTileSize (uint32_t& x, uint32_t& y, uint32_t& z) //todo: deprecate me +{ + int32_t sx, sy, sz; + getTileSizeInline(sx, sy, sz); + x = uint32_t(sx); + y = uint32_t(sy); + z = uint32_t(sz); +} // getter for map position void Maps::getPosition (int32_t& x, int32_t& y, int32_t& z) @@ -375,12 +399,20 @@ bool GetLocalFeature(t_feature &feature, df::coord2d rgn_pos, int32_t index) return true; } -bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature *local, t_feature *global) +inline bool ReadFeaturesInline(int32_t x, int32_t y, int32_t z, t_feature *local, t_feature *global) { - df::map_block *block = getBlock(x,y,z); + df::map_block* block = Maps::getBlock(x, y, z); if (!block) return false; - return ReadFeatures(block, local, global); + return Maps::ReadFeatures(block, local, global); +} +bool Maps::ReadFeatures(int32_t x, int32_t y, int32_t z, t_feature *local, t_feature *global) +{ + return ReadFeaturesInline(x, y, z, local, global); +} +bool Maps::ReadFeatures(uint32_t x, uint32_t y, uint32_t z, t_feature *local, t_feature *global) //todo: deprecate me +{ + return ReadFeaturesInline(int32_t(x), int32_t(y), int32_t(z), local, global); } bool Maps::ReadFeatures(df::map_block * block, t_feature * local, t_feature * global) @@ -477,12 +509,11 @@ bool Maps::SortBlockEvents(df::map_block *block, return true; } -bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which) +inline bool RemoveBlockEventInline(int32_t x, int32_t y, int32_t z, df::block_square_event * which) { - df::map_block * block = getBlock(x,y,z); + df::map_block* block = Maps::getBlock(x, y, z); if (!block) return false; - int idx = linear_index(block->block_events, which); if (idx >= 0) { @@ -493,6 +524,14 @@ bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square else return false; } +inline bool Maps::RemoveBlockEvent(int32_t x, int32_t y, int32_t z, df::block_square_event * which) +{ + return RemoveBlockEventInline(x, y, z, which); +} +bool Maps::RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which) //todo: deprecate me +{ + return RemoveBlockEventInline(int32_t(x), int32_t(y), int32_t(z), which); +} static df::coord2d biome_offsets[9] = { df::coord2d(-1,-1), df::coord2d(0,-1), df::coord2d(1,-1), @@ -658,8 +697,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( x == 0 && y == 0 ) continue; df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); - df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); - if ( shape1 == tiletype_shape::WALL ) { + df::tiletype_shape shape3 = ENUM_ATTR(tiletype,shape,*type); + if ( shape3 == tiletype_shape::WALL ) { foundWall = true; x = 2; break; @@ -695,8 +734,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( x == 0 && y == 0 ) continue; df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); - df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); - if ( shape1 == tiletype_shape::WALL ) { + df::tiletype_shape shape3 = ENUM_ATTR(tiletype,shape,*type); + if ( shape3 == tiletype_shape::WALL ) { foundWall = true; x = 2; break; @@ -785,162 +824,160 @@ misrepresented as being the original software. 3. This notice may not be removed or altered from any source distribution. */ -namespace { - //----------------------------------------------------------------------------// - // Utility function - // - //----------------------------------------------------------------------------// - std::pair check_tropicality(df::region_map_entry& region, - int y_pos - ) +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +static std::pair check_tropicality(df::region_map_entry& region, + int y_pos +) +{ + int flip_latitude = df::global::world->world_data->flip_latitude; + + bool is_possible_tropical_area_by_latitude = false; + bool is_tropical_area_by_latitude = false; + + if (flip_latitude == -1) // NO POLES { - int flip_latitude = df::global::world->world_data->flip_latitude; + // If there're no poles, tropical area is determined by temperature + is_possible_tropical_area_by_latitude = region.temperature >= 75; + is_tropical_area_by_latitude = region.temperature >= 85; + } - bool is_possible_tropical_area_by_latitude = false; - bool is_tropical_area_by_latitude = false; + else + { + int v6 = 0; - if (flip_latitude == -1) // NO POLES + df::world_data* wdata = df::global::world->world_data; + + if (flip_latitude == 0) // NORTH POLE ONLY { - // If there're no poles, tropical area is determined by temperature - is_possible_tropical_area_by_latitude = region.temperature >= 75; - is_tropical_area_by_latitude = region.temperature >= 85; + v6 = y_pos; } - else + else if (flip_latitude == 1) // SOUTH_POLE ONLY { - int v6 = 0; - - df::world_data* wdata = df::global::world->world_data; - - if (flip_latitude == 0) // NORTH POLE ONLY - { - v6 = y_pos; - } - - else if (flip_latitude == 1) // SOUTH_POLE ONLY - { - v6 = df::global::world->world_data->world_height - y_pos - 1; - } + v6 = df::global::world->world_data->world_height - y_pos - 1; + } - else if (flip_latitude == 2) // BOTH POLES + else if (flip_latitude == 2) // BOTH POLES + { + if (y_pos < wdata->world_height / 2) + v6 = 2 * y_pos; + else { - if (y_pos < wdata->world_height / 2) - v6 = 2 * y_pos; - else - { - v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; - if (v6 < 0) - v6 = 0; - if (v6 >= wdata->world_height) - v6 = wdata->world_height - 1; - } + v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1; + if (v6 < 0) + v6 = 0; + if (v6 >= wdata->world_height) + v6 = wdata->world_height - 1; } - - if (wdata->world_height == 17) - v6 *= 16; - else if (wdata->world_height == 33) - v6 *= 8; - else if (wdata->world_height == 65) - v6 *= 4; - else if (wdata->world_height == 129) - v6 *= 2; - - is_possible_tropical_area_by_latitude = v6 > 170; - is_tropical_area_by_latitude = v6 >= 200; } - return std::pair(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); + if (wdata->world_height == 17) + v6 *= 16; + else if (wdata->world_height == 33) + v6 *= 8; + else if (wdata->world_height == 65) + v6 *= 4; + else if (wdata->world_height == 129) + v6 *= 2; + + is_possible_tropical_area_by_latitude = v6 > 170; + is_tropical_area_by_latitude = v6 >= 200; } + return std::pair(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} - //----------------------------------------------------------------------------// - // Utility function - // - // return some unknow parameter as a percentage - //----------------------------------------------------------------------------// - int get_region_parameter(int y, - int x - ) + +//----------------------------------------------------------------------------// +// Utility function +// +// return some unknow parameter as a percentage +//----------------------------------------------------------------------------// +static int get_region_parameter(int y, + int x +) +{ + int world_height = df::global::world->world_data->world_height; + if (world_height > 65) // Medium and large worlds { - int world_height = df::global::world->world_data->world_height; - if (world_height > 65) // Medium and large worlds - { - // access to region 2D array - df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; - int flip_latitude = df::global::world->world_data->flip_latitude; - int rainfall = region.rainfall; - int result; - int y_pos = y; - int ypos = y_pos; - - if (flip_latitude == -1) // NO POLES - return 100; - - else if (flip_latitude == 1) // SOUTH POLE - ypos = world_height - y_pos - 1; - else if (flip_latitude == 2) // NORTH & SOUTH POLE - { - if (ypos < world_height / 2) - ypos *= 2; - else - { - ypos = world_height + 2 * (world_height / 2 - ypos) - 1; - if (ypos < 0) - ypos = 0; - if (ypos >= world_height) - ypos = world_height - 1; - } - } + // access to region 2D array + df::region_map_entry& region = df::global::world->world_data->region_map[x][y]; + int flip_latitude = df::global::world->world_data->flip_latitude; + int rainfall = region.rainfall; + int result; + int y_pos = y; + int ypos = y_pos; + + if (flip_latitude == -1) // NO POLES + return 100; - int latitude; // 0 - 256 (size of a large world) - switch (world_height) + else if (flip_latitude == 1) // SOUTH POLE + ypos = world_height - y_pos - 1; + else if (flip_latitude == 2) // NORTH & SOUTH POLE + { + if (ypos < world_height / 2) + ypos *= 2; + else { - case 17: // Pocket world - latitude = 16 * ypos; - break; - case 33: // Smaller world - latitude = 8 * ypos; - break; - case 65: // Small world - latitude = 4 * ypos; - break; - case 129: // Medium world - latitude = 2 * ypos; - break; - default: // Large world - latitude = ypos; - break; + ypos = world_height + 2 * (world_height / 2 - ypos) - 1; + if (ypos < 0) + ypos = 0; + if (ypos >= world_height) + ypos = world_height - 1; } + } - // latitude > 220 - if ((latitude - 171) > 49) - return 100; + int latitude; // 0 - 256 (size of a large world) + switch (world_height) + { + case 17: // Pocket world + latitude = 16 * ypos; + break; + case 33: // Smaller world + latitude = 8 * ypos; + break; + case 65: // Small world + latitude = 4 * ypos; + break; + case 129: // Medium world + latitude = 2 * ypos; + break; + default: // Large world + latitude = ypos; + break; + } + // latitude > 220 + if ((latitude - 171) > 49) + return 100; - // Latitude between 191 and 200 - if ((latitude > 190) && (latitude < 201)) - return 0; - // Latitude between 201 and 220 - if ((latitude > 190) && (latitude >= 201)) - result = rainfall + 16 * (latitude - 207); - else - // Latitude between 0 and 190 - result = (16 * (184 - latitude) - rainfall); + // Latitude between 191 and 200 + if ((latitude > 190) && (latitude < 201)) + return 0; - if (result < 0) - return 0; + // Latitude between 201 and 220 + if ((latitude > 190) && (latitude >= 201)) + result = rainfall + 16 * (latitude - 207); + else + // Latitude between 0 and 190 + result = (16 * (184 - latitude) - rainfall); - if (result > 100) - return 100; + if (result < 0) + return 0; - return result; - } + if (result > 100) + return 100; - return 100; + return result; } + + return 100; } diff --git a/library/modules/Notes.cpp b/library/modules/Notes.cpp deleted file mode 100644 index 04fb59e4d..000000000 --- a/library/modules/Notes.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "Types.h" -#include "Error.h" -#include "MemAccess.h" -#include "MiscUtils.h" -#include "ModuleFactory.h" -#include "Core.h" -#include "modules/Notes.h" -#include -#include "df/ui.h" -using namespace DFHack; - -std::unique_ptr DFHack::createNotes() -{ - return dts::make_unique(); -} - -// FIXME: not even a wrapper now -Notes::Notes() -{ - notes = (std::vector*) &df::global::ui->waypoints.points; -} diff --git a/library/modules/Renderer.cpp b/library/modules/Renderer.cpp index 474dc3656..b746a149c 100644 --- a/library/modules/Renderer.cpp +++ b/library/modules/Renderer.cpp @@ -9,6 +9,8 @@ using DFHack::Renderer::renderer_wrap; static renderer_wrap *original_renderer = NULL; +const int32_t Renderer::GET_MOUSE_COORDS_SENTINEL = 0xcd1aa471; + bool init() { if (!original_renderer) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 5001a34c0..ebcc3f229 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -31,6 +31,7 @@ distribution. #include using namespace std; +#include "modules/Renderer.h" #include "modules/Screen.h" #include "modules/GuiHooks.h" #include "MemAccess.h" @@ -75,12 +76,14 @@ using std::string; * Screen painting API. */ +// returns text grid coordinates, even if the game map is scaled differently df::coord2d Screen::getMousePos() { - if (!gps || (enabler && !enabler->tracking_on)) + int32_t x = Renderer::GET_MOUSE_COORDS_SENTINEL, y = (int32_t)true; + if (!enabler || !enabler->renderer->get_mouse_coords(&x, &y)) { return df::coord2d(-1, -1); - - return df::coord2d(gps->mouse_x, gps->mouse_y); + } + return df::coord2d(x, y); } df::coord2d Screen::getWindowSize() @@ -746,50 +749,7 @@ int dfhack_lua_viewscreen::do_input(lua_State *L) } lua_pushvalue(L, -2); - - lua_createtable(L, 0, keys->size()+3); - - for (auto it = keys->begin(); it != keys->end(); ++it) - { - auto key = *it; - - if (auto name = enum_item_raw_key(key)) - lua_pushstring(L, name); - else - lua_pushinteger(L, key); - - lua_pushboolean(L, true); - lua_rawset(L, -3); - - int charval = Screen::keyToChar(key); - if (charval >= 0) - { - lua_pushinteger(L, charval); - lua_setfield(L, -2, "_STRING"); - } - } - - if (enabler && enabler->tracking_on) - { - if (enabler->mouse_lbut_down) { - lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_L"); - } - if (enabler->mouse_rbut_down) { - lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_R"); - } - if (enabler->mouse_lbut) { - lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_L_DOWN"); - enabler->mouse_lbut = 0; - } - if (enabler->mouse_rbut) { - lua_pushboolean(L, true); - lua_setfield(L, -2, "_MOUSE_R_DOWN"); - enabler->mouse_rbut = 0; - } - } + Lua::PushInterfaceKeys(L, *keys); lua_call(L, 2, 0); self->update_focus(L, -1); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 8e9f624ea..32b9f28e5 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -174,6 +174,7 @@ bool Units::teleport(df::unit *unit, df::coord target_pos) // move unit to destination unit->pos = target_pos; + unit->idle_area = target_pos; // move unit's riders (including babies) to destination if (unit->flags1.bits.ridden) diff --git a/library/modules/Windows.cpp b/library/modules/Windows.cpp deleted file mode 100644 index af5368e50..000000000 --- a/library/modules/Windows.cpp +++ /dev/null @@ -1,118 +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 "Export.h" -#include "Module.h" -#include "BitArray.h" -#include - -#include "DataDefs.h" -#include "df/init.h" -#include "df/ui.h" -#include -#include "modules/Windows.h" - -using namespace DFHack; -using df::global::gps; - -Windows::df_screentile *Windows::getScreenBuffer() -{ - if (!gps) return NULL; - return (df_screentile *) gps->screen; -} - -Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height) -:buffer(0), width(width), height(height), parent(0), left(x), top(y), current_painter(NULL) -{ - buffer = 0; -}; -Windows::df_window::~df_window() -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - delete *iter; - } - children.clear(); -}; -Windows::painter * Windows::df_window::lock() -{ - locked = true; - current_painter = new Windows::painter(this,buffer,width, height); - return current_painter; -}; - -bool Windows::df_window::addChild( df_window * child) -{ - children.push_back(child); - child->parent = this; - return true; -} - -bool Windows::df_window::unlock (painter * painter) -{ - if(current_painter == painter) - { - delete current_painter; - current_painter = 0; - locked = false; - return true; - } - return false; -} - -Windows::top_level_window::top_level_window() : df_window(0,0,gps ? gps->dimx : 80,gps ? gps->dimy : 25) -{ - buffer = 0; -} - -bool Windows::top_level_window::move (int left_, int top_, unsigned int width_, unsigned int height_) -{ - width = width_; - height = height_; - // what if we are painting already? Is that possible? - return true; -}; - -Windows::painter * Windows::top_level_window::lock() -{ - buffer = getScreenBuffer(); - return df_window::lock(); -} - -void Windows::top_level_window::paint () -{ - for(auto iter = children.begin();iter != children.end();iter++) - { - (*iter)->paint(); - } -}; - -Windows::df_tilebuf Windows::top_level_window::getBuffer() -{ - df_tilebuf buf; - buf.data = getScreenBuffer(); - buf.height = df::global::gps->dimy; - buf.width = df::global::gps->dimx; - return buf; -} diff --git a/library/xml b/library/xml index b8d48430a..ea78ed8bf 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b8d48430aa13570a668464ce40294a589f1f0b1f +Subproject commit ea78ed8bf70c3e75b8fba90cdc61cab34788899e diff --git a/onLoad.init-example b/onLoad.init-example deleted file mode 100644 index 1d32f07c9..000000000 --- a/onLoad.init-example +++ /dev/null @@ -1 +0,0 @@ -repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ] diff --git a/plugins/3dveins.cpp b/plugins/3dveins.cpp index 929ee24f6..eaf741caf 100644 --- a/plugins/3dveins.cpp +++ b/plugins/3dveins.cpp @@ -52,12 +52,9 @@ command_result cmd_3dveins(color_ostream &out, std::vector & param DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "3dveins", "Rewrites the veins to make them extend in 3D space.", - cmd_3dveins, false, - " Run this after embark to change all veins on the map to a shape\n" - " that consistently spans Z levels. The operation preserves the\n" - " mineral counts reported by prospect.\n" - )); + "3dveins", + "Rewrites the veins to make them extend in 3D space.", + cmd_3dveins)); return CR_OK; } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 22983d617..c9438e04e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -82,16 +82,17 @@ if(BUILD_SUPPORTED) dfhack_plugin(3dveins 3dveins.cpp) dfhack_plugin(add-spatter add-spatter.cpp) - # dfhack_plugin(advtools advtools.cpp) + dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp) dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autofarm autofarm.cpp) - dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(autohauler autohauler.cpp) dfhack_plugin(autolabor autolabor.cpp) dfhack_plugin(automaterial automaterial.cpp LINK_LIBRARIES lua) dfhack_plugin(automelt automelt.cpp) + dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) dfhack_plugin(autotrade autotrade.cpp) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) @@ -109,7 +110,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) dfhack_plugin(deramp deramp.cpp) - dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(dig dig.cpp) dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua) dfhack_plugin(digFlood digFlood.cpp) @@ -121,7 +122,6 @@ if(BUILD_SUPPORTED) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(filltraffic filltraffic.cpp) - dfhack_plugin(fix-armory fix-armory.cpp) dfhack_plugin(fix-unit-occupancy fix-unit-occupancy.cpp) dfhack_plugin(fixveins fixveins.cpp) dfhack_plugin(flows flows.cpp) @@ -129,7 +129,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(forceequip forceequip.cpp) dfhack_plugin(generated-creature-renamer generated-creature-renamer.cpp) dfhack_plugin(getplants getplants.cpp) - dfhack_plugin(hotkeys hotkeys.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) @@ -143,12 +143,13 @@ if(BUILD_SUPPORTED) dfhack_plugin(mode mode.cpp) dfhack_plugin(mousequery mousequery.cpp) dfhack_plugin(nestboxes nestboxes.cpp) - dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_lib_static) + dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) + dfhack_plugin(overlay overlay.cpp) 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) + 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,7 +163,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) dfhack_plugin(steam-engine steam-engine.cpp) - dfhack_plugin(spectate spectate.cpp) + add_subdirectory(spectate) dfhack_plugin(stockflow stockflow.cpp LINK_LIBRARIES lua) add_subdirectory(stockpiles) dfhack_plugin(stocks stocks.cpp) @@ -177,7 +178,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua) dfhack_plugin(workNow workNow.cpp) dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read_STATIC zip expat) - dfhack_plugin(zone zone.cpp LINK_LIBRARIES lua) + dfhack_plugin(zone zone.cpp) # If you are adding a plugin that you do not intend to commit to the DFHack repo, # see instructions for adding "external" plugins at the end of this file. @@ -186,9 +187,20 @@ endif() # this is the skeleton plugin. If you want to make your own, make a copy and then change it option(BUILD_SKELETON "Build the skeleton plugin." OFF) if(BUILD_SKELETON) - add_subdirectory(skeleton) + dfhack_plugin(skeleton examples/skeleton.cpp) endif() +macro(subdirlist result subdir) + file(GLOB children ABSOLUTE ${subdir}/ ${subdir}/*/) + set(dirlist "") + foreach(child ${children}) + if(IS_DIRECTORY ${child}) + file(RELATIVE_PATH child ${CMAKE_CURRENT_SOURCE_DIR}/${subdir} ${child}) + list(APPEND dirlist ${child}) + endif() + endforeach() + set(${result} ${dirlist}) +endmacro() # To add "external" plugins without committing them to the DFHack repo: # @@ -204,7 +216,7 @@ endif() # 4. build DFHack as normal. The plugins you added will be built as well. if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") - file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt" + set(content_str "# Add external plugins here - this file is ignored by git # Recommended: use add_subdirectory() for folders that you have created within @@ -212,9 +224,14 @@ if(NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") # See the end of /plugins/CMakeLists.txt for more details. ") + subdirlist(SUBDIRS external) + foreach(subdir ${SUBDIRS}) + set(content_str "${content_str}add_subdirectory(${subdir})\n") + endforeach() + file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt" ${content_str}) endif() -include("${CMAKE_CURRENT_SOURCE_DIR}/external/CMakeLists.txt") +add_subdirectory(external) # for backwards compatibility if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists.custom.txt") diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp deleted file mode 100644 index a1965cba0..000000000 --- a/plugins/advtools.cpp +++ /dev/null @@ -1,827 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" -#include "MiscUtils.h" -#include "modules/World.h" -#include "modules/Translation.h" -#include "modules/Materials.h" -#include "modules/Maps.h" -#include "modules/Items.h" -#include "modules/Gui.h" -#include "modules/Units.h" - -#include "DataDefs.h" -#include "df/world.h" -#include "df/ui_advmode.h" -#include "df/item.h" -#include "df/unit.h" -#include "df/unit_inventory_item.h" -#include "df/unit_relationship_type.h" -#include "df/map_block.h" -#include "df/nemesis_record.h" -#include "df/historical_figure.h" -#include "df/general_ref_is_nemesisst.h" -#include "df/general_ref_contains_itemst.h" -#include "df/general_ref_contained_in_itemst.h" -#include "df/general_ref_unit_holderst.h" -#include "df/general_ref_building_civzone_assignedst.h" -#include "df/material.h" -#include "df/craft_material_class.h" -#include "df/viewscreen_optionst.h" -#include "df/viewscreen_dungeonmodest.h" -#include "df/viewscreen_dungeon_monsterstatusst.h" -#include "df/nemesis_flags.h" - -#include - -using namespace DFHack; -using namespace df::enums; - -using df::nemesis_record; -using df::historical_figure; - -using namespace DFHack::Translation; -/* -advtools -======== -A package of different adventure mode tools. Usage: - -:list-equipped [all]: List armor and weapons equipped by your companions. - If all is specified, also lists non-metal clothing. -:metal-detector [all-types] [non-trader]: - Reveal metal armor and weapons in shops. The options - disable the checks on item type and being in shop. -*/ - -DFHACK_PLUGIN("advtools"); -REQUIRE_GLOBAL(world); -REQUIRE_GLOBAL(ui_advmode); - -/********************* - * PLUGIN INTERFACE * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top); - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters); -command_result adv_tools (color_ostream &out, std::vector & parameters); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - if (!ui_advmode) - return CR_OK; - - commands.push_back(PluginCommand( - "advtools", "Adventure mode tools.", - adv_tools, false, - " list-equipped [all]\n" - " List armor and weapons equipped by your companions.\n" - " If all is specified, also lists non-metal clothing.\n" - " metal-detector [all-types] [non-trader]\n" - " Reveal metal armor and weapons in shops. The options\n" - " disable the checks on item type and being in shop.\n" - )); - - commands.push_back(PluginCommand( - "adv-bodyswap", "Change the adventurer unit.", - adv_bodyswap, bodyswap_hotkey, - " - When viewing unit details, body-swaps into that unit.\n" - " - In the main adventure mode screen, reverts transient swap.\n" - "Options:\n" - " force\n" - " Allow swapping into non-companion units.\n" - " permanent\n" - " Permanently change the unit to be the adventurer.\n" - " Otherwise it will revert if adv-bodyswap is called\n" - " in the main screen, or if the main menu, Fast Travel\n" - " or Sleep/Wait screen is opened.\n" - " noinherit\n" - " In permanent mode, don't reassign companions to the new unit.\n" - )); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap); - -DFHACK_PLUGIN_IS_ENABLED(in_transient_swap); - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_WORLD_LOADED: - case SC_WORLD_UNLOADED: - in_transient_swap = false; - break; - default: - break; - } - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - // Revert transient swaps before trouble happens - if (in_transient_swap) - { - auto screen = Core::getTopViewscreen(); - bool revert = false; - - if (strict_virtual_cast(screen)) - { - using namespace df::enums::ui_advmode_menu; - - switch (ui_advmode->menu) - { - case Travel: - // was also Sleep, now equivalent - revert = true; - break; - default: - break; - } - } - else if (strict_virtual_cast(screen)) - { - // Options may mean save game - revert = true; - } - - if (revert) - { - getPlayerNemesis(out, true); - in_transient_swap = false; - } - } - - return CR_OK; -} - -/********************* - * UTILITY FUNCTIONS * - *********************/ - -static bool bodyswap_hotkey(df::viewscreen *top) -{ - return !!virtual_cast(top) || - !!virtual_cast(top); -} - -bool bodySwap(color_ostream &out, df::unit *player) -{ - if (!player) - { - out.printerr("Unit to swap is NULL\n"); - return false; - } - - auto &vec = world->units.active; - - int idx = linear_index(vec, player); - if (idx < 0) - { - out.printerr("Unit to swap not found: %d\n", player->id); - return false; - } - - if (idx != 0) - std::swap(vec[0], vec[idx]); - - return true; -} - -df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) -{ - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (!real_nemesis || !real_nemesis->unit) - { - out.printerr("Invalid player nemesis id: %d\n", ui_advmode->player_id); - return NULL; - } - - if (restore_swap) - { - df::unit *ctl = world->units.active[0]; - auto ctl_nemesis = Units::getNemesis(ctl); - - if (ctl_nemesis != real_nemesis) - { - if (!bodySwap(out, real_nemesis->unit)) - return NULL; - - auto name = TranslateName(&real_nemesis->unit->name, false); - out.print("Returned into the body of %s.\n", name.c_str()); - } - - real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; - in_transient_swap = false; - } - - return real_nemesis; -} - -void changeGroupLeader(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &cvec = new_nemesis->companions; - - // Swap companions - cvec.swap(old_nemesis->companions); - - vector_erase_at(cvec, linear_index(cvec, new_nemesis->id)); - insert_into_vector(cvec, old_nemesis->id); - - // Update follow - new_nemesis->group_leader_id = -1; - new_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = -1; - - for (unsigned i = 0; i < cvec.size(); i++) - { - auto nm = df::nemesis_record::find(cvec[i]); - if (!nm) - continue; - - nm->group_leader_id = new_nemesis->id; - if (nm->unit) - nm->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; - } -} - -void copyAcquaintances(df::nemesis_record *new_nemesis, df::nemesis_record *old_nemesis) -{ - auto &svec = old_nemesis->unit->adventurer_knows; - auto &tvec = new_nemesis->unit->adventurer_knows; - - for (unsigned i = 0; i < svec.size(); i++) - insert_into_vector(tvec, svec[i]); - - insert_into_vector(tvec, old_nemesis->unit_id); -} - -void sortCompanionNemesis(std::vector *list, int player_id = -1) -{ - std::map table; - std::vector output; - - output.reserve(list->size()); - - if (player_id < 0) - { - auto real_nemesis = vector_get(world->nemesis.all, ui_advmode->player_id); - if (real_nemesis) - player_id = real_nemesis->id; - } - - // Index records; find the player - for (size_t i = 0; i < list->size(); i++) - { - auto item = (*list)[i]; - if (item->id == player_id) - output.push_back(item); - else - table[item->figure->id] = item; - } - - // Pull out the items by the persistent sort order - auto &order_vec = ui_advmode->companions.all_histfigs; - for (size_t i = 0; i < order_vec.size(); i++) - { - auto it = table.find(order_vec[i]); - if (it == table.end()) - continue; - output.push_back(it->second); - table.erase(it); - } - - // The remaining ones in reverse id order - for (auto it = table.rbegin(); it != table.rend(); ++it) - output.push_back(it->second); - - list->swap(output); -} - -void listCompanions(color_ostream &out, std::vector *list, bool units = true) -{ - nemesis_record *player = getPlayerNemesis(out, false); - if (!player) - return; - - list->push_back(player); - - for (size_t i = 0; i < player->companions.size(); i++) - { - auto item = nemesis_record::find(player->companions[i]); - if (item && (item->unit || !units)) - list->push_back(item); - } -} - -std::string getUnitNameProfession(df::unit *unit) -{ - std::string name = TranslateName(&unit->name, false) + ", "; - if (unit->custom_profession.empty()) - name += ENUM_ATTR_STR(profession, caption, unit->profession); - else - name += unit->custom_profession; - return name; -} - -enum InventoryMode { - INV_HAULED, - INV_WEAPON, - INV_WORN, - INV_IN_CONTAINER -}; - -typedef std::pair inv_item; - -static void listContainerInventory(std::vector *list, df::item *container) -{ - auto &refs = container->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - if (!strict_virtual_cast(ref)) - continue; - - df::item *child = ref->getItem(); - if (!child) continue; - - list->push_back(inv_item(child, INV_IN_CONTAINER)); - listContainerInventory(list, child); - } -} - -void listUnitInventory(std::vector *list, df::unit *unit) -{ - auto &items = unit->inventory; - for (size_t i = 0; i < items.size(); i++) - { - auto item = items[i]; - InventoryMode mode; - - switch (item->mode) { - case df::unit_inventory_item::Hauled: - mode = INV_HAULED; - break; - case df::unit_inventory_item::Weapon: - mode = INV_WEAPON; - break; - default: - mode = INV_WORN; - } - - list->push_back(inv_item(item->item, mode)); - listContainerInventory(list, item->item); - } -} - -bool isShopItem(df::item *item) -{ - for (size_t k = 0; k < item->general_refs.size(); k++) - { - auto ref = item->general_refs[k]; - if (virtual_cast(ref)) - return true; - } - - return false; -} - -bool isWeaponArmor(df::item *item) -{ - using namespace df::enums::item_type; - - switch (item->getType()) { - case HELM: - case ARMOR: - case WEAPON: - case AMMO: - case GLOVES: - case PANTS: - case SHOES: - return true; - default: - return false; - } -} - -int containsMetalItems(df::item *item, bool all, bool non_trader, bool rec = false) -{ - int cnt = 0; - - auto &refs = item->general_refs; - for (size_t i = 0; i < refs.size(); i++) - { - auto ref = refs[i]; - - if (strict_virtual_cast(ref)) - return 0; - if (!rec && strict_virtual_cast(ref)) - return 0; - - if (strict_virtual_cast(ref)) - { - df::item *child = ref->getItem(); - if (!child) continue; - - cnt += containsMetalItems(child, all, non_trader, true); - } - } - - if (!non_trader && !isShopItem(item)) - return cnt; - if (!all && !isWeaponArmor(item)) - return cnt; - - MaterialInfo minfo(item); - if (minfo.getCraftClass() != craft_material_class::Metal) - return cnt; - - return ++cnt; -} - -void joinCounts(std::map &counts) -{ - for (auto it = counts.begin(); it != counts.end(); it++) - { - df::coord pt = it->first; - while (pt.x > 0 && counts.count(pt - df::coord(1,0,0))) - pt.x--; - while (pt.y > 0 &&counts.count(pt - df::coord(0,1,0))) - pt.y--; - while (pt.x < 0 && counts.count(pt + df::coord(1,0,0))) - pt.x++; - while (pt.y < 0 &&counts.count(pt + df::coord(0,1,0))) - pt.y++; - - if (pt == it->first) - continue; - - counts[pt] += it->second; - it->second = 0; - } -} - -/********************* - * FORMATTING * - *********************/ - -static void printCompanionHeader(color_ostream &out, size_t i, df::unit *unit) -{ - out.color(COLOR_GREY); - - if (i < 28) - out << char('a'+i); - else - out << i; - - out << ": " << getUnitNameProfession(unit); - if (Units::isDead(unit)) - out << " (DEAD)"; - if (Units::isGhost(unit)) - out << " (GHOST)"; - out << endl; - - out.reset_color(); -} - -static size_t formatSize(std::vector *out, const std::map in, size_t *cnt) -{ - size_t len = 0; - - for (auto it = in.begin(); it != in.end(); ++it) - { - std::string line = it->first; - if (it->second != 1) - line += stl_sprintf(" [%d]", it->second); - len = std::max(len, line.size()); - out->push_back(line); - } - - if (out->empty()) - { - out->push_back("(none)"); - len = 6; - } - - if (cnt) - *cnt = std::max(*cnt, out->size()); - - return len; -} - -static std::string formatDirection(df::coord delta) -{ - std::string ns, ew, dir; - - if (delta.x > 0) - ew = "E"; - else if (delta.x < 0) - ew = "W"; - - if (delta.y > 0) - ns = "S"; - else if (delta.y < 0) - ns = "N"; - - if (abs(delta.x) > abs(delta.y)*5) - dir = ew; - else if (abs(delta.y) > abs(delta.x)*5) - dir = ns; - else if (abs(delta.x) > abs(delta.y)*2) - dir = ew + ns + ew; - else if (abs(delta.y) > abs(delta.x)*2) - dir = ns + ns + ew; - else if (delta.x || delta.y) - dir = ns + ew; - else - dir = "***"; - - int dist = (int)sqrt((double)(delta.x*delta.x + delta.y*delta.y)); - return stl_sprintf("%d away %s %+d", dist, dir.c_str(), delta.z); -} - -static void printEquipped(color_ostream &out, df::unit *unit, bool all) -{ - std::vector items; - listUnitInventory(&items, unit); - - std::map head, body, legs, weapons; - - for (auto it = items.begin(); it != items.end(); ++it) - { - df::item *item = it->first; - - // Skip non-worn non-weapons - ItemTypeInfo iinfo(item); - - bool is_weapon = (it->second == INV_WEAPON || iinfo.type == item_type::AMMO); - if (!(is_weapon || it->second == INV_WORN)) - continue; - - // Skip non-metal, unless all - MaterialInfo minfo(item); - df::craft_material_class mclass = minfo.getCraftClass(); - - bool is_metal = (mclass == craft_material_class::Metal); - if (!(is_weapon || all || is_metal)) - continue; - - // Format the name - std::string name; - if (is_metal) - name = minfo.toString() + " "; - else if (mclass != craft_material_class::None) - name = toLower(ENUM_KEY_STR(craft_material_class,mclass)) + " "; - name += iinfo.toString(); - - // Add to the right table - int count = item->getStackSize(); - - if (is_weapon) - { - weapons[name] += count; - continue; - } - - switch (iinfo.type) { - case item_type::HELM: - head[name] += count; - break; - case item_type::ARMOR: - case item_type::GLOVES: - case item_type::BACKPACK: - case item_type::QUIVER: - body[name] += count; - break; - case item_type::PANTS: - case item_type::SHOES: - legs[name] += count; - break; - default: - weapons[name] += count; - } - } - - std::vector cols[4]; - size_t sizes[4]; - size_t lines = 0; - - sizes[0] = formatSize(&cols[0], head, &lines); - sizes[1] = formatSize(&cols[1], body, &lines); - sizes[2] = formatSize(&cols[2], legs, &lines); - sizes[3] = formatSize(&cols[3], weapons, &lines); - - for (size_t i = 0; i < lines; i++) - { - for (int j = 0; j < 4; j++) - { - size_t sz = std::max(sizes[j], size_t(18)); - out << "| " << std::left << std::setw(sz) << vector_get(cols[j],i) << " "; - } - - out << "|" << std::endl; - } -} - -/********************* - * COMMANDS * - *********************/ - -command_result adv_bodyswap (color_ostream &out, std::vector & parameters) -{ - // HOTKEY COMMAND; CORE IS SUSPENDED - bool force = false; - bool permanent = false; - bool no_make_leader = false; - - for (unsigned i = 0; i < parameters.size(); i++) - { - auto &item = parameters[i]; - - if (item == "force") - force = true; - else if (item == "permanent") - permanent = true; - else if (item == "noinherit") - no_make_leader = true; - else - return CR_WRONG_USAGE; - } - - // Get the real player; undo previous transient swap - auto real_nemesis = getPlayerNemesis(out, true); - if (!real_nemesis) - return CR_FAILURE; - - // Get the unit to swap to - auto new_unit = Gui::getSelectedUnit(out, true); - auto new_nemesis = Units::getNemesis(new_unit); - - if (!new_nemesis) - { - if (new_unit) - { - out.printerr("Cannot swap into a non-historical unit.\n"); - return CR_FAILURE; - } - - return CR_OK; - } - - if (new_nemesis == real_nemesis) - return CR_OK; - - // Verify it's a companion - if (!force && linear_index(real_nemesis->companions, new_nemesis->id) < 0) - { - out.printerr("This is not your companion - use force to bodyswap.\n"); - return CR_FAILURE; - } - - // Swap - if (!bodySwap(out, new_nemesis->unit)) - return CR_FAILURE; - - auto name = TranslateName(&new_nemesis->unit->name, false); - out.print("Swapped into the body of %s.\n", name.c_str()); - - // Permanently re-link everything - if (permanent) - { - using namespace df::enums::nemesis_flags; - - ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); - - // Flag 0 appears to be the 'active adventurer' flag, and - // the player_id field above seems to be computed using it - // when a savegame is loaded. - // Also, unless this is set, it is impossible to retire. - real_nemesis->flags.set(ACTIVE_ADVENTURER, false); - new_nemesis->flags.set(ACTIVE_ADVENTURER, true); - - real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer - new_nemesis->flags.set(ADVENTURER, true); // blue color in legends - - // Reassign companions and acquaintances - if (!no_make_leader) - { - changeGroupLeader(new_nemesis, real_nemesis); - copyAcquaintances(new_nemesis, real_nemesis); - } - } - else - { - in_transient_swap = true; - - // Make the player unit follow around to avoid bad consequences - // if it is unloaded before the transient swap is reverted. - real_nemesis->unit->relationship_ids[df::unit_relationship_type::GroupLeader] = new_nemesis->unit_id; - } - - return CR_OK; -} - -command_result adv_tools (color_ostream &out, std::vector & parameters) -{ - if (parameters.empty()) - return CR_WRONG_USAGE; - - CoreSuspender suspend; - - const auto &command = parameters[0]; - if (command == "list-equipped") - { - bool all = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all") - all = true; - else - return CR_WRONG_USAGE; - } - - std::vector list; - - listCompanions(out, &list); - sortCompanionNemesis(&list); - - for (size_t i = 0; i < list.size(); i++) - { - auto item = list[i]; - auto unit = item->unit; - - printCompanionHeader(out, i, unit); - printEquipped(out, unit, all); - } - - return CR_OK; - } - else if (command == "metal-detector") - { - bool all = false, non_trader = false; - for (size_t i = 1; i < parameters.size(); i++) - { - if (parameters[i] == "all-types") - all = true; - else if (parameters[i] == "non-trader") - non_trader = true; - else - return CR_WRONG_USAGE; - } - - auto *player = getPlayerNemesis(out, false); - if (!player) - return CR_FAILURE; - - df::coord player_pos = player->unit->pos; - - int total = 0; - std::map counts; - - auto &items = world->items.all; - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - - int num = containsMetalItems(item, all, non_trader); - if (!num) - continue; - - df::map_block *block = Maps::getTileBlock(item->pos); - if (!block) - continue; - - total += num; - counts[(item->pos - player_pos)/10] += num; - - auto &designations = block->designation; - auto &dgn = designations[item->pos.x%16][item->pos.y%16]; - - dgn.bits.hidden = 0; // revealed - dgn.bits.pile = 1; // visible - } - - joinCounts(counts); - - out.print("%d items of metal merchandise found in the vicinity.\n", total); - for (auto it = counts.begin(); it != counts.end(); it++) - { - if (!it->second) - continue; - - df::coord delta = it->first * 10; - out.print(" %s: %d\n", formatDirection(delta).c_str(), it->second); - } - - return CR_OK; - } - else - return CR_WRONG_USAGE; -} diff --git a/plugins/autobutcher.cpp b/plugins/autobutcher.cpp new file mode 100644 index 000000000..81ec0c5df --- /dev/null +++ b/plugins/autobutcher.cpp @@ -0,0 +1,1144 @@ +// full automation of marking live-stock for slaughtering +// races can be added to a watchlist and it can be set how many male/female kids/adults are left alive +// adding to the watchlist can be automated as well. +// config for autobutcher (state and sleep setting) is saved the first time autobutcher is started +// config for watchlist entries is saved when they are created or modified + +#include +#include +#include +#include + +#include "df/building_cagest.h" +#include "df/creature_raw.h" +#include "df/world.h" + +#include "Core.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::endl; +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autobutcher"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(autobutcher, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autobutcher, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static const string WATCHLIST_CONFIG_KEY_PREFIX = string(plugin_name) + "/watchlist/"; +static PersistentDataItem config; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, + CONFIG_AUTOWATCH = 2, + CONFIG_DEFAULT_FK = 3, + CONFIG_DEFAULT_MK = 4, + CONFIG_DEFAULT_FA = 5, + CONFIG_DEFAULT_MA = 6, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +struct WatchedRace; +// vector of races handled by autobutcher +// the name is a bit misleading since entries can be set to 'unwatched' +// to ignore them for a while but still keep the target count settings +static unordered_map watched_races; +static unordered_map race_to_id; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static void init_autobutcher(color_ostream &out); +static void cleanup_autobutcher(color_ostream &out); +static command_result df_autobutcher(color_ostream &out, vector ¶meters); +static void autobutcher_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + plugin_name, + "Automatically butcher excess livestock.", + df_autobutcher)); + return CR_OK; +} + +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 != is_enabled) { + is_enabled = enable; + DEBUG(status,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(status,out).print("shutting down %s\n", plugin_name); + cleanup_autobutcher(out); + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + set_config_bool(CONFIG_AUTOWATCH, false); + set_config_val(CONFIG_DEFAULT_FK, 5); + set_config_val(CONFIG_DEFAULT_MK, 1); + set_config_val(CONFIG_DEFAULT_FA, 5); + set_config_val(CONFIG_DEFAULT_MA, 1); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + + // load the persisted watchlist + init_autobutcher(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; + } + cleanup_autobutcher(out); + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + autobutcher_cycle(out); + return CR_OK; +} + +///////////////////////////////////////////////////// +// autobutcher config logic +// + +struct autobutcher_options { + // whether to display help + bool help = false; + + // the command to run. + string command; + + // the set of (unverified) races that the command should affect, and whether + // "all" or "new" was specified as the race + vector races; + bool races_all = false; + bool races_new = false; + + // params for the "target" command + int32_t fk = -1; + int32_t mk = -1; + int32_t fa = -1; + int32_t ma = -1; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; + + // non-virtual destructor so offsetof() still works for the fields + ~autobutcher_options() { + for (auto str : races) + delete str; + } +}; +static const struct_field_info autobutcher_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autobutcher_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "command", offsetof(autobutcher_options, command), df::identity_traits::get(), 0, 0 }, + { struct_field_info::STL_VECTOR_PTR, "races", offsetof(autobutcher_options, races), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "races_all", offsetof(autobutcher_options, races_all), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "races_new", offsetof(autobutcher_options, races_new), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fk", offsetof(autobutcher_options, fk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "mk", offsetof(autobutcher_options, mk), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "fa", offsetof(autobutcher_options, fa), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ma", offsetof(autobutcher_options, ma), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autobutcher_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autobutcher_options::_identity(sizeof(autobutcher_options), &df::allocator_fn, NULL, "autobutcher_options", NULL, autobutcher_options_fields); + +static bool get_options(color_ostream &out, + autobutcher_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autobutcher", "parse_commandline")) { + out.printerr("Failed to load autobutcher Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static void doMarkForSlaughter(df::unit *unit) { + unit->flags2.bits.slaughter = 1; +} + +// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case +// (assuming that the value from there indicates in which tick of the current year the unit was born) +static bool compareUnitAgesYounger(df::unit *i, df::unit *j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if (age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i < age_j; +} + +static bool compareUnitAgesOlder(df::unit* i, df::unit* j) { + int32_t age_i = (int32_t)Units::getAge(i, true); + int32_t age_j = (int32_t)Units::getAge(j, true); + if(age_i == 0 && age_j == 0) { + age_i = i->birth_time; + age_j = j->birth_time; + } + return age_i > age_j; +} + +enum unit_ptr_index { + fk_index = 0, + mk_index = 1, + fa_index = 2, + ma_index = 3 +}; + +struct WatchedRace { +public: + PersistentDataItem rconfig; + + int raceId; + bool isWatched; // if true, autobutcher will process this race + + // target amounts + unsigned fk; // max female kids + unsigned mk; // max male kids + unsigned fa; // max female adults + unsigned ma; // max male adults + + // amounts of protected (not butcherable) units + unsigned fk_prot; + unsigned fa_prot; + unsigned mk_prot; + unsigned ma_prot; + + // butcherable units + vector unit_ptr[4]; + + // priority butcherable units + vector prot_ptr[4]; + + WatchedRace(color_ostream &out, int id, bool watch, unsigned _fk, unsigned _mk, unsigned _fa, unsigned _ma) { + raceId = id; + isWatched = watch; + fk = _fk; + mk = _mk; + fa = _fa; + ma = _ma; + fk_prot = fa_prot = mk_prot = ma_prot = 0; + + DEBUG(status,out).print("creating new WatchedRace: id=%d, watched=%s, fk=%u, mk=%u, fa=%u, ma=%u\n", + id, watch ? "true" : "false", fk, mk, fa, ma); + } + + WatchedRace(color_ostream &out, const PersistentDataItem &p) + : WatchedRace(out, p.ival(0), p.ival(1), p.ival(2), p.ival(3), p.ival(4), p.ival(5)) { + rconfig = p; + } + + ~WatchedRace() { + ClearUnits(); + } + + void UpdateConfig(color_ostream &out) { + if(!rconfig.isValid()) { + string keyname = WATCHLIST_CONFIG_KEY_PREFIX + Units::getRaceNameById(raceId); + rconfig = World::GetPersistentData(keyname, NULL); + } + if(rconfig.isValid()) { + rconfig.ival(0) = raceId; + rconfig.ival(1) = isWatched; + rconfig.ival(2) = fk; + rconfig.ival(3) = mk; + rconfig.ival(4) = fa; + rconfig.ival(5) = ma; + } + else { + ERR(status,out).print("could not create persistent key for race: %s", + Units::getRaceNameById(raceId).c_str()); + } + } + + void RemoveConfig(color_ostream &out) { + if(!rconfig.isValid()) + return; + World::DeletePersistentData(rconfig); + } + + void SortUnitsByAge() { + sort(unit_ptr[fk_index].begin(), unit_ptr[fk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[mk_index].begin(), unit_ptr[mk_index].end(), compareUnitAgesOlder); + sort(unit_ptr[fa_index].begin(), unit_ptr[fa_index].end(), compareUnitAgesYounger); + sort(unit_ptr[ma_index].begin(), unit_ptr[ma_index].end(), compareUnitAgesYounger); + sort(prot_ptr[fk_index].begin(), prot_ptr[fk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[mk_index].begin(), prot_ptr[mk_index].end(), compareUnitAgesOlder); + sort(prot_ptr[fa_index].begin(), prot_ptr[fa_index].end(), compareUnitAgesYounger); + sort(prot_ptr[ma_index].begin(), prot_ptr[ma_index].end(), compareUnitAgesYounger); + } + + void PushUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[fk_index].push_back(unit); + else + unit_ptr[fa_index].push_back(unit); + } + else //treat sex n/a like it was male + { + if(Units::isBaby(unit) || Units::isChild(unit)) + unit_ptr[mk_index].push_back(unit); + else + unit_ptr[ma_index].push_back(unit); + } + } + + void PushPriorityUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[fk_index].push_back(unit); + else + prot_ptr[fa_index].push_back(unit); + } + else { + if(Units::isBaby(unit) || Units::isChild(unit)) + prot_ptr[mk_index].push_back(unit); + else + prot_ptr[ma_index].push_back(unit); + } + } + + void PushProtectedUnit(df::unit *unit) { + if(Units::isFemale(unit)) { + if(Units::isBaby(unit) || Units::isChild(unit)) + fk_prot++; + else + fa_prot++; + } + else { //treat sex n/a like it was male + if(Units::isBaby(unit) || Units::isChild(unit)) + mk_prot++; + else + ma_prot++; + } + } + + void ClearUnits() { + fk_prot = fa_prot = mk_prot = ma_prot = 0; + for (size_t i = 0; i < 4; i++) { + unit_ptr[i].clear(); + prot_ptr[i].clear(); + } + } + + int ProcessUnits(vector& unit_ptr, vector& unit_pri_ptr, unsigned prot, unsigned goal) { + int subcount = 0; + while (unit_pri_ptr.size() && (unit_ptr.size() + unit_pri_ptr.size() + prot > goal)) { + df::unit *unit = unit_pri_ptr.back(); + doMarkForSlaughter(unit); + unit_pri_ptr.pop_back(); + subcount++; + } + while (unit_ptr.size() && (unit_ptr.size() + prot > goal)) { + df::unit *unit = unit_ptr.back(); + doMarkForSlaughter(unit); + unit_ptr.pop_back(); + subcount++; + } + return subcount; + } + + int ProcessUnits() { + SortUnitsByAge(); + int slaughter_count = 0; + slaughter_count += ProcessUnits(unit_ptr[fk_index], prot_ptr[fk_index], fk_prot, fk); + slaughter_count += ProcessUnits(unit_ptr[mk_index], prot_ptr[mk_index], mk_prot, mk); + slaughter_count += ProcessUnits(unit_ptr[fa_index], prot_ptr[fa_index], fa_prot, fa); + slaughter_count += ProcessUnits(unit_ptr[ma_index], prot_ptr[ma_index], ma_prot, ma); + ClearUnits(); + return slaughter_count; + } +}; + +static void init_autobutcher(color_ostream &out) { + if (!race_to_id.size()) { + const size_t num_races = world->raws.creatures.all.size(); + for(size_t i = 0; i < num_races; ++i) + race_to_id.emplace(Units::getRaceNameById(i), i); + } + + std::vector watchlist; + World::GetPersistentData(&watchlist, WATCHLIST_CONFIG_KEY_PREFIX, true); + for (auto & p : watchlist) { + DEBUG(status,out).print("Reading from save: %s\n", p.key().c_str()); + WatchedRace *w = new WatchedRace(out, p); + watched_races.emplace(w->raceId, w); + } +} + +static void cleanup_autobutcher(color_ostream &out) { + DEBUG(status,out).print("cleaning %s state\n", plugin_name); + race_to_id.clear(); + for (auto w : watched_races) + delete w.second; + watched_races.clear(); +} + +static void autobutcher_export(color_ostream &out); +static void autobutcher_status(color_ostream &out); +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts); +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts); + +static command_result df_autobutcher(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + autobutcher_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.command == "now") { + autobutcher_cycle(out); + } + else if (opts.command == "autowatch") { + set_config_bool(CONFIG_AUTOWATCH, true); + } + else if (opts.command == "noautowatch") { + set_config_bool(CONFIG_AUTOWATCH, false); + } + else if (opts.command == "list_export") { + autobutcher_export(out); + } + else if (opts.command == "target") { + autobutcher_target(out, opts); + } + else if (opts.command == "watch" || + opts.command == "unwatch" || + opts.command == "forget") { + autobutcher_modify_watchlist(out, opts); + } + else if (opts.command == "ticks") { + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else { + autobutcher_status(out); + } + + return CR_OK; +} + +// helper for sorting the watchlist alphabetically +static bool compareRaceNames(WatchedRace* i, WatchedRace* j) { + string name_i = Units::getRaceNamePluralById(i->raceId); + string name_j = Units::getRaceNamePluralById(j->raceId); + + return name_i < name_j; +} + +// sort watchlist alphabetically +static vector getSortedWatchList() { + vector list; + for (auto w : watched_races) { + list.push_back(w.second); + } + sort(list.begin(), list.end(), compareRaceNames); + return list; +} + +static void autobutcher_export(color_ostream &out) { + out << "enable autobutcher" << endl; + out << "autobutcher ticks " << get_config_val(CONFIG_CYCLE_TICKS) << endl; + out << "autobutcher " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "no") + << "autowatch" << endl; + out << "autobutcher target" + << " " << get_config_val(CONFIG_DEFAULT_FK) + << " " << get_config_val(CONFIG_DEFAULT_MK) + << " " << get_config_val(CONFIG_DEFAULT_FA) + << " " << get_config_val(CONFIG_DEFAULT_MA) + << " new" << endl; + + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + string name = raw->creature_id; + out << "autobutcher target" + << " " << w->fk + << " " << w->mk + << " " << w->fa + << " " << w->ma + << " " << name << endl; + if (w->isWatched) + out << "autobutcher watch " << name << endl; + } +} + +static void autobutcher_status(color_ostream &out) { + out << "autobutcher is " << (is_enabled ? "" : "not ") << "enabled\n"; + if (is_enabled) + out << " running every " << get_config_val(CONFIG_CYCLE_TICKS) << " game ticks\n"; + out << " " << (get_config_bool(CONFIG_AUTOWATCH) ? "" : "not ") << "autowatching for new races\n"; + + out << "\ndefault setting for new races:" + << " fk=" << get_config_val(CONFIG_DEFAULT_FK) + << " mk=" << get_config_val(CONFIG_DEFAULT_MK) + << " fa=" << get_config_val(CONFIG_DEFAULT_FA) + << " ma=" << get_config_val(CONFIG_DEFAULT_MA) + << endl << endl; + + if (!watched_races.size()) { + out << "not currently watching any races. to find out how to add some, run:\n help autobutcher" << endl; + return; + } + + out << "monitoring races: " << endl; + for (auto w : getSortedWatchList()) { + df::creature_raw *raw = world->raws.creatures.all[w->raceId]; + out << " " << Units::getRaceNamePluralById(w->raceId) << " \t"; + out << "(" << raw->creature_id; + out << " fk=" << w->fk + << " mk=" << w->mk + << " fa=" << w->fa + << " ma=" << w->ma; + if (!w->isWatched) + out << "; autobutchering is paused"; + out << ")" << endl; + } +} + +static void autobutcher_target(color_ostream &out, const autobutcher_options &opts) { + if (opts.races_new) { + DEBUG(status,out).print("setting targets for new races to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); + set_config_val(CONFIG_DEFAULT_FK, opts.fk); + set_config_val(CONFIG_DEFAULT_MK, opts.mk); + set_config_val(CONFIG_DEFAULT_FA, opts.fa); + set_config_val(CONFIG_DEFAULT_MA, opts.ma); + } + + if (opts.races_all) { + DEBUG(status,out).print("setting targets for all races on watchlist to fk=%u, mk=%u, fa=%u, ma=%u\n", + opts.fk, opts.mk, opts.fa, opts.ma); + for (auto w : watched_races) { + w.second->fk = opts.fk; + w.second->mk = opts.mk; + w.second->fa = opts.fa; + w.second->ma = opts.ma; + w.second->UpdateConfig(out); + } + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + int id = race_to_id[*race]; + WatchedRace *w; + if (!watched_races.count(id)) { + w = new WatchedRace(out, id, true, opts.fk, opts.mk, opts.fa, opts.ma); + watched_races.emplace(id, w); + } else { + w = watched_races[id]; + w->fk = opts.fk; + w->mk = opts.mk; + w->fa = opts.fa; + w->ma = opts.ma; + } + w->UpdateConfig(out); + } +} + +static void autobutcher_modify_watchlist(color_ostream &out, const autobutcher_options &opts) { + unordered_set ids; + + if (opts.races_all) { + for (auto w : watched_races) + ids.emplace(w.first); + } + + for (auto race : opts.races) { + if (!race_to_id.count(*race)) { + out.printerr("race not found: '%s'", race->c_str()); + continue; + } + ids.emplace(race_to_id[*race]); + } + + for (int id : ids) { + if (opts.command == "watch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, true, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (!watched_races[id]->isWatched) { + DEBUG(status,out).print("watching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = true; + } + } + else if (opts.command == "unwatch") { + if (!watched_races.count(id)) { + watched_races.emplace(id, + new WatchedRace(out, id, false, + get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), + get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA))); + } + else if (watched_races[id]->isWatched) { + DEBUG(status,out).print("unwatching: %s\n", opts.command.c_str()); + watched_races[id]->isWatched = false; + } + } + else if (opts.command == "forget") { + if (watched_races.count(id)) { + DEBUG(status,out).print("forgetting: %s\n", opts.command.c_str()); + watched_races[id]->RemoveConfig(out); + delete watched_races[id]; + watched_races.erase(id); + } + continue; + } + watched_races[id]->UpdateConfig(out); + } +} + +///////////////////////////////////////////////////// +// cycle logic +// + +// check if contained in item (e.g. animals in cages) +static bool isContainedInItem(df::unit *unit) { + for (auto gref : unit->general_refs) { + if (gref->getType() == df::general_ref_type::CONTAINED_IN_ITEM) { + return true; + } + } + return false; +} + +// found a unit with weird position values on one of my maps (negative and in the thousands) +// it didn't appear in the animal stocks screen, but looked completely fine otherwise (alive, tame, own, etc) +// maybe a rare bug, but better avoid assigning such units to zones or slaughter etc. +static bool hasValidMapPos(df::unit *unit) { + return unit->pos.x >= 0 && unit->pos.y >= 0 && unit->pos.z >= 0 + && unit->pos.x < world->map.x_count + && unit->pos.y < world->map.y_count + && unit->pos.z < world->map.z_count; +} + +// built cage defined as room (supposed to detect zoo cages) +static bool isInBuiltCageRoom(df::unit *unit) { + for (auto building : world->buildings.all) { + // !!! building->isRoom() returns true if the building can be made a room but currently isn't + // !!! except for coffins/tombs which always return false + // !!! using the bool is_room however gives the correct state/value + if (!building->is_room || building->getType() != df::building_type::Cage) + continue; + + df::building_cagest* cage = (df::building_cagest*)building; + for (auto cu : cage->assigned_units) + if (cu == unit->id) return true; + } + return false; +} + +static void autobutcher_cycle(color_ostream &out) { + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // check if there is anything to watch before walking through units vector + if (!get_config_bool(CONFIG_AUTOWATCH)) { + bool watching = false; + for (auto w : watched_races) { + if (w.second->isWatched) { + watching = true; + break; + } + } + if (!watching) + return; + } + + for (auto unit : world->units.all) { + // this check is now divided into two steps, squeezed autowatch into the middle + // first one ignores completely inappropriate units (dead, undead, not belonging to the fort, ...) + // then let autowatch add units to the watchlist which will probably start breeding (owned pets, war animals, ...) + // then process units counting those which can't be butchered (war animals, named pets, ...) + // so that they are treated as "own stock" as well and count towards the target quota + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMarkedForSlaughter(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + WatchedRace *w; + if (watched_races.count(unit->race)) { + w = watched_races[unit->race]; + } + else if (!get_config_bool(CONFIG_AUTOWATCH)) { + continue; + } + else { + w = new WatchedRace(out, unit->race, true, get_config_val(CONFIG_DEFAULT_FK), + get_config_val(CONFIG_DEFAULT_MK), get_config_val(CONFIG_DEFAULT_FA), + get_config_val(CONFIG_DEFAULT_MA)); + w->UpdateConfig(out); + watched_races.emplace(unit->race, w); + + string announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(unit->race); + Gui::showAnnouncement(announce, 2, false); + } + + if (w->isWatched) { + // don't butcher protected units, but count them as stock as well + // this way they count towards target quota, so if you order that you want 1 female adult cat + // and have 2 cats, one of them being a pet, the other gets butchered + if( Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name) + w->PushProtectedUnit(unit); + else if ( Units::isGay(unit) + || Units::isGelded(unit)) + w->PushPriorityUnit(unit); + else + w->PushUnit(unit); + } + } + + for (auto w : watched_races) { + int slaughter_count = w.second->ProcessUnits(); + if (slaughter_count) { + std::stringstream ss; + ss << slaughter_count; + string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); + Gui::showAnnouncement(announce, 2, false); + } + } +} + +///////////////////////////////////// +// API functions to control autobutcher with a lua script + +// abuse WatchedRace struct for counting stocks (since it sorts by gender and age) +// calling method must delete pointer! +static WatchedRace * checkRaceStocksTotal(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksProtected(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if ( !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name ) + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherable(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if (unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + w->PushUnit(unit); + } + return w; +} + +WatchedRace * checkRaceStocksButcherFlag(color_ostream &out, int race) { + WatchedRace * w = new WatchedRace(out, race, true, 0, 0, 0, 0); + for (auto unit : world->units.all) { + if(unit->race != race) + continue; + + if ( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draft animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + ) + continue; + + if (!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + if (Units::isMarkedForSlaughter(unit)) + w->PushUnit(unit); + } + return w; +} + +static bool autowatch_isEnabled() { + return get_config_bool(CONFIG_AUTOWATCH); +} + +static unsigned autobutcher_getSleep(color_ostream &out) { + return get_config_val(CONFIG_CYCLE_TICKS); +} + +static void autobutcher_setSleep(color_ostream &out, unsigned ticks) { + + set_config_val(CONFIG_CYCLE_TICKS, ticks); +} + +static void autowatch_setEnabled(color_ostream &out, bool enable) { + DEBUG(status,out).print("auto-adding to watchlist %s\n", enable ? "started" : "stopped"); + set_config_bool(CONFIG_AUTOWATCH, enable); +} + +// set all data for a watchlist race in one go +// if race is not already on watchlist it will be added +// params: (id, fk, mk, fa, ma, watched) +static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsigned fk, unsigned mk, unsigned fa, unsigned ma, bool watched) { + if (watched_races.count(id)) { + DEBUG(status,out).print("updating watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->fk = fk; + w->mk = mk; + w->fa = fa; + w->ma = ma; + w->isWatched = watched; + w->UpdateConfig(out); + return; + } + + DEBUG(status,out).print("creating new watchlist entry\n"); + WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma); + w->UpdateConfig(out); + watched_races.emplace(id, w); + + string announce; + announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(id); + Gui::showAnnouncement(announce, 2, false); +} + +// remove entry from watchlist +static void autobutcher_removeFromWatchList(color_ostream &out, unsigned id) { + if (watched_races.count(id)) { + DEBUG(status,out).print("removing watchlist entry\n"); + WatchedRace * w = watched_races[id]; + w->RemoveConfig(out); + watched_races.erase(id); + } +} + +// set default target values for new races +static void autobutcher_setDefaultTargetNew(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + set_config_val(CONFIG_DEFAULT_FK, fk); + set_config_val(CONFIG_DEFAULT_MK, mk); + set_config_val(CONFIG_DEFAULT_FA, fa); + set_config_val(CONFIG_DEFAULT_MA, ma); +} + +// set default target values for ALL races (update watchlist and set new default) +static void autobutcher_setDefaultTargetAll(color_ostream &out, unsigned fk, unsigned mk, unsigned fa, unsigned ma) { + for (auto w : watched_races) { + w.second->fk = fk; + w.second->mk = mk; + w.second->fa = fa; + w.second->ma = ma; + w.second->UpdateConfig(out); + } + autobutcher_setDefaultTargetNew(out, fk, mk, fa, ma); +} + +static void autobutcher_butcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || Units::isMerchant(unit) // ignore merchants' draught animals + || Units::isForest(unit) // ignore merchants' caged animals + || !Units::isOwnCiv(unit) + || !Units::isTame(unit) + || Units::isWar(unit) // ignore war dogs etc + || Units::isHunter(unit) // ignore hunting dogs etc + // ignore creatures in built cages which are defined as rooms to leave zoos alone + // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || Units::isAvailableForAdoption(unit) + || unit->name.has_name + ) + continue; + + // found a bugged unit which had invalid coordinates but was not in a cage. + // marking it for slaughter didn't seem to have negative effects, but you never know... + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + doMarkForSlaughter(unit); + } +} + +// remove butcher flag for all units of a given race +static void autobutcher_unbutcherRace(color_ostream &out, int id) { + for (auto unit : world->units.all) { + if(unit->race != id) + continue; + + if( !Units::isActive(unit) + || Units::isUndead(unit) + || !Units::isMarkedForSlaughter(unit) + ) + continue; + + if(!isContainedInItem(unit) && !hasValidMapPos(unit)) + continue; + + unit->flags2.bits.slaughter = 0; + } +} + +// push autobutcher settings on lua stack +static int autobutcher_getSettings(lua_State *L) { + lua_newtable(L); + int ctable = lua_gettop(L); + Lua::SetField(L, get_config_bool(CONFIG_IS_ENABLED), ctable, "enable_autobutcher"); + Lua::SetField(L, get_config_bool(CONFIG_AUTOWATCH), ctable, "enable_autowatch"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FK), ctable, "fk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MK), ctable, "mk"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_FA), ctable, "fa"); + Lua::SetField(L, get_config_val(CONFIG_DEFAULT_MA), ctable, "ma"); + Lua::SetField(L, get_config_val(CONFIG_CYCLE_TICKS), ctable, "sleep"); + return 1; +} + +// push the watchlist vector as nested table on the lua stack +static int autobutcher_getWatchList(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + + lua_newtable(L); + int entry_index = 0; + for (auto wr : watched_races) { + lua_newtable(L); + int ctable = lua_gettop(L); + + WatchedRace * w = wr.second; + int id = w->raceId; + Lua::SetField(L, id, ctable, "id"); + Lua::SetField(L, w->isWatched, ctable, "watched"); + Lua::SetField(L, Units::getRaceNamePluralById(id), ctable, "name"); + Lua::SetField(L, w->fk, ctable, "fk"); + Lua::SetField(L, w->mk, ctable, "mk"); + Lua::SetField(L, w->fa, ctable, "fa"); + Lua::SetField(L, w->ma, ctable, "ma"); + + WatchedRace *tally = checkRaceStocksTotal(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_total"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_total"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_total"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_total"); + delete tally; + + tally = checkRaceStocksProtected(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_protected"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_protected"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_protected"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_protected"); + delete tally; + + tally = checkRaceStocksButcherable(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherable"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherable"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherable"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherable"); + delete tally; + + tally = checkRaceStocksButcherFlag(*out, id); + Lua::SetField(L, tally->unit_ptr[fk_index].size(), ctable, "fk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[mk_index].size(), ctable, "mk_butcherflag"); + Lua::SetField(L, tally->unit_ptr[fa_index].size(), ctable, "fa_butcherflag"); + Lua::SetField(L, tally->unit_ptr[ma_index].size(), ctable, "ma_butcherflag"); + delete tally; + + lua_rawseti(L, -2, ++entry_index); + } + + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(autowatch_isEnabled), + DFHACK_LUA_FUNCTION(autowatch_setEnabled), + DFHACK_LUA_FUNCTION(autobutcher_getSleep), + DFHACK_LUA_FUNCTION(autobutcher_setSleep), + DFHACK_LUA_FUNCTION(autobutcher_setWatchListRace), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetNew), + DFHACK_LUA_FUNCTION(autobutcher_setDefaultTargetAll), + DFHACK_LUA_FUNCTION(autobutcher_butcherRace), + DFHACK_LUA_FUNCTION(autobutcher_unbutcherRace), + DFHACK_LUA_FUNCTION(autobutcher_removeFromWatchList), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(autobutcher_getSettings), + DFHACK_LUA_COMMAND(autobutcher_getWatchList), + DFHACK_LUA_END +}; diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index 04ae8af71..acb8959b9 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.cpp @@ -185,18 +185,6 @@ private: static WatchedBurrows watchedBurrows; -static int string_to_int(string s, int default_ = 0) -{ - try - { - return std::stoi(s); - } - catch (std::exception&) - { - return default_; - } -} - static void save_config() { config_autochop.val() = watchedBurrows.getSerialisedIds(); @@ -313,7 +301,7 @@ static int do_chop_designation(bool chop, bool count_only, int *skipped = nullpt { int count = 0; int estimated_yield = get_log_count(); - multimap trees_by_size; + multimap> trees_by_size; if (skipped) { @@ -935,10 +923,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "autochop", "Auto-harvest trees when low on stockpiled logs", - df_autochop, false, - "Opens the automated chopping control screen. Specify 'debug' to forcibly save settings.\n" - )); + "autochop", + "Auto-harvest trees when low on stockpiled logs.", + df_autochop)); initialize(); return CR_OK; diff --git a/plugins/autoclothing.cpp b/plugins/autoclothing.cpp index d19e647d5..1535b5eb8 100644 --- a/plugins/autoclothing.cpp +++ b/plugins/autoclothing.cpp @@ -1,5 +1,3 @@ - -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" #include #include @@ -25,6 +23,8 @@ #include "df/creature_raw.h" #include "df/world.h" +using std::endl; + using namespace DFHack; using namespace DFHack::Items; using namespace DFHack::Units; @@ -82,7 +82,7 @@ struct ClothingRequirement std::string Serialize() { - stringstream stream; + std::stringstream stream; stream << ENUM_KEY_STR(job_type, jobType) << " "; stream << ENUM_KEY_STR(item_type,itemType) << " "; stream << item_subtype << " "; @@ -93,7 +93,7 @@ struct ClothingRequirement void Deserialize(std::string s) { - stringstream stream(s); + std::stringstream stream(s); std::string loadedJob; stream >> loadedJob; FOR_ENUM_ITEMS(job_type, job) @@ -140,7 +140,7 @@ struct ClothingRequirement std::string ToReadableLabel() { - stringstream stream; + std::stringstream stream; stream << bitfield_to_string(material_category) << " "; std::string adjective = ""; std::string name = ""; @@ -184,16 +184,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector [number]\n" - "Example:\n" - " autoclothing cloth \"short skirt\" 10\n" - " Sets the desired number of cloth short skirts available per citizen to 10.\n" - " autoclothing cloth dress\n" - " Displays the currently set number of cloth dresses chosen per citizen.\n" - )); + "autoclothing", + "Automatically manage clothing work orders", + autoclothing)); return CR_OK; } diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 054d28f77..fb215c26b 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -275,31 +275,19 @@ command_result df_autodump_destroy_item(color_ostream &out, vector & pa DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "autodump", "Teleport items marked for dumping to the cursor.", - df_autodump, false, - " This utility lets you quickly move all items designated to be dumped.\n" - " Items are instantly moved to the cursor position, the dump flag is unset,\n" - " and the forbid flag is set, as if it had been dumped normally.\n" - " Be aware that any active dump item tasks still point at the item.\n" - "Options:\n" - " destroy - instead of dumping, destroy the items instantly.\n" - " destroy-here - only affect the tile under cursor.\n" - " visible - only process items that are not hidden.\n" - " hidden - only process hidden items.\n" - " forbidden - only process forbidden items (default: only unforbidden).\n" - )); + "autodump", + "Teleport items marked for dumping to the cursor.", + df_autodump)); commands.push_back(PluginCommand( - "autodump-destroy-here", "Destroy items marked for dumping under cursor.", - df_autodump_destroy_here, Gui::cursor_hotkey, - " Identical to autodump destroy-here, but intended for use as keybinding.\n" - )); + "autodump-destroy-here", + "Destroy items marked for dumping under cursor.", + df_autodump_destroy_here, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "autodump-destroy-item", "Destroy the selected item.", - df_autodump_destroy_item, Gui::any_item_hotkey, - " Destroy the selected item. The item may be selected\n" - " in the 'k' list, or inside a container. If called\n" - " again before the game is resumed, cancels destroy.\n" - )); + "autodump-destroy-item", + "Destroy the selected item.", + df_autodump_destroy_item, + Gui::any_item_hotkey)); return CR_OK; } diff --git a/plugins/autofarm.cpp b/plugins/autofarm.cpp index 2dafdee49..1a02b6a06 100644 --- a/plugins/autofarm.cpp +++ b/plugins/autofarm.cpp @@ -38,15 +38,6 @@ DFHACK_PLUGIN("autofarm"); DFHACK_PLUGIN_IS_ENABLED(enabled); -const char* tagline = "Automatically handle crop selection in farm plots based on current plant stocks."; -const char* usage = ( - "``enable autofarm``: Enables the plugin\n" - "``autofarm runonce``: Updates farm plots (one-time only)\n" - "``autofarm status``: Prints status information\n" - "``autofarm default 30``: Sets the default threshold\n" - "``autofarm threshold 150 helmet_plump tail_pig``: Sets thresholds\n" - ); - class AutoFarm { private: std::map thresholds; @@ -330,7 +321,7 @@ public: void status(color_ostream& out) { - out << (enabled ? "Running." : "Stopped.") << '\n'; + out << "Autofarm is " << (enabled ? "Active." : "Stopped.") << '\n'; for (auto& lc : lastCounts) { auto plant = world->raws.plants.all[lc.first]; @@ -355,10 +346,9 @@ DFhackCExport command_result plugin_init(color_ostream& out, std::vector ()); return CR_OK; diff --git a/plugins/autogems.cpp b/plugins/autogems.cpp index fcd8fcdc7..d296caf7f 100644 --- a/plugins/autogems.cpp +++ b/plugins/autogems.cpp @@ -41,19 +41,6 @@ typedef int32_t mat_index; typedef std::map gem_map; bool running = false; -const char *tagline = "Creates a new Workshop Order setting, automatically cutting rough gems."; -const char *usage = ( - " enable autogems\n" - " Enable the plugin.\n" - " disable autogems\n" - " Disable the plugin.\n" - "\n" - "While enabled, the Current Workshop Orders screen (o-W) have a new option:\n" - " g: Auto Cut Gems\n" - "\n" - "While this option is enabled, jobs will be created in Jeweler's Workshops\n" - "to cut any accessible rough gems.\n" -); std::set blacklist; void add_task(mat_index gem_type, df::building_workshopst *workshop) { @@ -385,11 +372,8 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "autogems-reload", - "Reload autogems config file", - cmd_reload_config, - false, - "Reload autogems config file" - )); + "Reload autogems config file.", + cmd_reload_config)); return CR_OK; } diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 35bf90ca1..b52e9cfb5 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -712,45 +712,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autohauler allow\n" - " Allow hauling if a specific labor is enabled.\n" - " autohauler forbid\n" - " Forbid hauling if a specific labor is enabled.\n" - " autohauler reset\n" - " Return a labor to the default handling.\n" - " autohauler reset-all\n" - " Return all labors to the default handling.\n" - " autohauler frameskip \n" - " Set the number of frames between runs of autohauler.\n" - " autohauler list\n" - " List current status of all labors.\n" - " autohauler status\n" - " Show basic status information.\n" - " autohauler debug\n" - " In the next cycle, will output the state of every dwarf.\n" - "Function:\n" - " When enabled, autohauler periodically checks your dwarves and assigns\n" - " hauling jobs to idle dwarves while removing them from busy dwarves.\n" - " This plugin, in contrast to autolabor, is explicitly designed to be\n" - " used alongside Dwarf Therapist.\n" - " Warning: autohauler will override any manual changes you make to\n" - " hauling labors while it is enabled...but why would you make them?\n" - "Examples:\n" - " autohauler HAUL_STONE haulers\n" - " Set stone hauling as a hauling labor.\n" - " autohauler BOWYER allow\n" - " Allow hauling when the bowyer labor is enabled.\n" - " autohauler MINE forbid\n" - " Forbid hauling while the mining labor is disabled." - )); + "autohauler", + "Automatically manage hauling labors.", + autohauler)); // Initialize plugin labor lists init_state(out); diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d6921f1ac..15c6903b4 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -686,48 +686,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector [] []\n" - " Set number of dwarves assigned to a labor.\n" - " autolabor haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autolabor disable\n" - " Turn off autolabor for a specific labor.\n" - " autolabor reset\n" - " Return a labor to the default handling.\n" - " autolabor reset-all\n" - " Return all labors to the default handling.\n" - " autolabor list\n" - " List current status of all labors.\n" - " autolabor status\n" - " Show basic status information.\n" - "Function:\n" - " When enabled, autolabor periodically checks your dwarves and enables or\n" - " disables labors. It tries to keep as many dwarves as possible busy but\n" - " also tries to have dwarves specialize in specific skills.\n" - " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled.\n" - " To prevent particular dwarves from being managed by autolabor, put them\n" - " in any burrow.\n" - " To restrict the assignment of a labor to only the top most skilled\n" - " dwarves, add a talent pool number .\n" - "Examples:\n" - " autolabor MINE 2\n" - " Keep at least 2 dwarves with mining enabled.\n" - " autolabor CUT_GEM 1 1\n" - " Keep exactly 1 dwarf with gemcutting enabled.\n" - " autolabor COOK 1 1 3\n" - " Keep 1 dwarf with cooking enabled, selected only from the top 3.\n" - " autolabor FEED_WATER_CIVILIANS haulers\n" - " Have haulers feed and water wounded dwarves.\n" - " autolabor CUTWOOD disable\n" - " Turn off autolabor for wood cutting.\n" - )); + "autolabor", + "Automatically manage dwarf labors.", + autolabor)); init_state(); @@ -873,7 +834,7 @@ static void assign_labor(unit_labor::unit_labor labor, } int pool = labor_infos[labor].talent_pool(); - if (pool < 200 && candidates.size() > 1 && abs(pool) < candidates.size()) + if (pool < 200 && candidates.size() > 1 && size_t(abs(pool)) < candidates.size()) { // Sort in descending order std::sort(candidates.begin(), candidates.end(), [&](const int lhs, const int rhs) -> bool { diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index b1b83d09e..24f2d8e2a 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -14,6 +14,7 @@ // DF data structure definition headers #include "DataDefs.h" +#include "Debug.h" #include "MiscUtils.h" #include "TileTypes.h" #include "df/build_req_choice_genst.h" @@ -52,6 +53,10 @@ REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(ui_build_selector); +namespace DFHack { + DBG_DECLARE(automaterial,log,DebugCategory::LINFO); +} + struct MaterialDescriptor { df::item_type item_type; @@ -94,7 +99,7 @@ static bool show_box_selection = true; static bool hollow_selection = false; static deque box_select_materials; -#define SELECTION_IGNORE_TICKS 10 +#define SELECTION_IGNORE_TICKS 1 static int ignore_selection = SELECTION_IGNORE_TICKS; static map last_used_material; @@ -719,7 +724,6 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest 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; @@ -732,12 +736,13 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest if (!Gui::getCursorCoords(box_second.x, box_second.y, box_second.z)) return; - int32_t xD = (box_second.x > box_first.x) ? 1 : -1; - int32_t 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) - { + 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; @@ -963,6 +968,11 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest 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(); } @@ -1269,9 +1279,13 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest label << "Selection: " << dX << "x" << dY; OutputString(COLOR_WHITE, x, ++y, label.str(), true, left_margin); - int cx = box_first.x; - int cy = box_first.y; - OutputString(COLOR_BROWN, cx, cy, "X", false, 0, 0, true /* map */); + 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; } diff --git a/plugins/automelt.cpp b/plugins/automelt.cpp index 852b324d6..4bbb727d7 100644 --- a/plugins/automelt.cpp +++ b/plugins/automelt.cpp @@ -250,19 +250,6 @@ IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(melt_hook, render); -static command_result automelt_cmd(color_ostream &out, vector & parameters) -{ - if (!parameters.empty()) - { - if (parameters.size() == 1 && toLower(parameters[0])[0] == 'v') - { - out << "Automelt" << endl << "Version: " << PLUGIN_VERSION << endl; - } - } - - return CR_OK; -} - DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) @@ -296,15 +283,12 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back( - PluginCommand( - "automelt", "Automatically melt metal items in marked stockpiles.", - automelt_cmd, false, "")); - return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + // ensure we disengage our hooks + plugin_enable(out, false); return CR_OK; } diff --git a/plugins/autonestbox.cpp b/plugins/autonestbox.cpp new file mode 100644 index 000000000..8b1ee5244 --- /dev/null +++ b/plugins/autonestbox.cpp @@ -0,0 +1,410 @@ +// - full automation of handling mini-pastures over nestboxes: +// go through all pens, check if they are empty and placed over a nestbox +// find female tame egg-layer who is not assigned to another pen and assign it to nestbox pasture +// maybe check for minimum age? it's not that useful to fill nestboxes with freshly hatched birds +// state and sleep setting is saved the first time autonestbox is started (to avoid writing stuff if the plugin is never used) + +#include +#include + +#include "df/building_cagest.h" +#include "df/building_civzonest.h" +#include "df/building_nest_boxst.h" +#include "df/general_ref_building_civzone_assignedst.h" +#include "df/world.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Persistence.h" +#include "modules/Units.h" +#include "modules/World.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("autonestbox"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(autonestbox, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(autonestbox, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +static bool did_complain = false; // avoids message spam +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static command_result df_autonestbox(color_ostream &out, vector ¶meters); +static void autonestbox_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand( + plugin_name, + "Auto-assign egg-laying female pets to nestbox zones.", + df_autonestbox)); + return CR_OK; +} + +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 != is_enabled) { + is_enabled = enable; + DEBUG(status,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + did_complain = false; + 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 (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + autonestbox_cycle(out); + return CR_OK; +} + +///////////////////////////////////////////////////// +// configuration interface +// + +struct autonestbox_options { + // whether to display help + bool help = false; + + // whether to run a cycle right now + bool now = false; + + // how many ticks to wait between automatic cycles, -1 means unset + int32_t ticks = -1; + + static struct_identity _identity; +}; +static const struct_field_info autonestbox_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(autonestbox_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "now", offsetof(autonestbox_options, now), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ticks", offsetof(autonestbox_options, ticks), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity autonestbox_options::_identity(sizeof(autonestbox_options), &df::allocator_fn, NULL, "autonestbox_options", NULL, autonestbox_options_fields); + +static bool get_options(color_ostream &out, + autonestbox_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.autonestbox", "parse_commandline")) { + out.printerr("Failed to load autonestbox Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +static command_result df_autonestbox(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + autonestbox_options opts; + if (!get_options(out, opts, parameters) || opts.help) + return CR_WRONG_USAGE; + + if (opts.ticks > -1) { + set_config_val(CONFIG_CYCLE_TICKS, opts.ticks); + INFO(status,out).print("New cycle timer: %d ticks.\n", opts.ticks); + } + else if (opts.now) { + autonestbox_cycle(out); + } + else { + out << "autonestbox is " << (is_enabled ? "" : "not ") << "running" << std::endl; + } + return CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +static bool isEmptyPasture(df::building *building) { + if (!Buildings::isPenPasture(building)) + return false; + df::building_civzonest *civ = (df::building_civzonest *)building; + return (civ->assigned_units.size() == 0); +} + +static bool isFreeNestboxAtPos(int32_t x, int32_t y, int32_t z) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::NestBox + && building->x1 == x + && building->y1 == y + && building->z == z) { + df::building_nest_boxst *nestbox = (df::building_nest_boxst *)building; + if (nestbox->claimed_by == -1 && nestbox->contained_items.size() == 1) { + return true; + } + } + } + return false; +} + +static df::building* findFreeNestboxZone() { + for (auto building : world->buildings.all) { + if (isEmptyPasture(building) && + Buildings::isActive(building) && + isFreeNestboxAtPos(building->x1, building->y1, building->z)) { + return building; + } + } + return NULL; +} + +static bool isInBuiltCage(df::unit *unit) { + for (auto building : world->buildings.all) { + if (building->getType() == df::building_type::Cage) { + df::building_cagest* cage = (df::building_cagest *)building; + for (auto unitid : cage->assigned_units) { + if (unitid == unit->id) + return true; + } + } + } + return false; +} + +// check if assigned to pen, pit, (built) cage or chain +// note: BUILDING_CAGED is not set for animals (maybe it's used for dwarves who get caged as sentence) +// animals in cages (no matter if built or on stockpile) get the ref CONTAINED_IN_ITEM instead +// removing them from cages on stockpiles is no problem even without clearing the ref +// and usually it will be desired behavior to do so. +static bool isAssigned(df::unit *unit) { + for (auto ref : unit->general_refs) { + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED + || rtype == df::general_ref_type::BUILDING_CAGED + || rtype == df::general_ref_type::BUILDING_CHAIN + || (rtype == df::general_ref_type::CONTAINED_IN_ITEM && isInBuiltCage(unit))) { + return true; + } + } + return false; +} + +static bool isFreeEgglayer(df::unit *unit) +{ + return Units::isActive(unit) && !Units::isUndead(unit) + && Units::isFemale(unit) + && Units::isTame(unit) + && Units::isOwnCiv(unit) + && Units::isEggLayer(unit) + && !isAssigned(unit) + && !Units::isGrazer(unit) // exclude grazing birds because they're messy + && !Units::isMerchant(unit) // don't steal merchant mounts + && !Units::isForest(unit); // don't steal birds from traders, they hate that +} + +static df::unit * findFreeEgglayer() { + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + return unit; + } + return NULL; +} + +static df::general_ref_building_civzone_assignedst * createCivzoneRef() { + static bool vt_initialized = false; + + // after having run successfully for the first time it's safe to simply create the object + if (vt_initialized) { + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + + // being called for the first time, need to initialize the vtable + for (auto creature : world->units.all) { + for (auto ref : creature->general_refs) { + if (ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { + if (strict_virtual_cast(ref)) { + vt_initialized = true; + // !! calling new() doesn't work, need _identity.instantiate() instead !! + return (df::general_ref_building_civzone_assignedst *) + df::general_ref_building_civzone_assignedst::_identity.instantiate(); + } + } + } + } + return NULL; +} + +static bool assignUnitToZone(color_ostream &out, df::unit *unit, df::building *building) { + // try to get a fresh civzone ref + df::general_ref_building_civzone_assignedst *ref = createCivzoneRef(); + if (!ref) { + ERR(cycle,out).print("Could not find a clonable activity zone reference!" + " You need to manually pen/pasture/pit at least one creature" + " before autonestbox can function.\n"); + return false; + } + + ref->building_id = building->id; + unit->general_refs.push_back(ref); + + df::building_civzonest *civz = (df::building_civzonest *)building; + civz->assigned_units.push_back(unit->id); + + INFO(cycle,out).print("Unit %d (%s) assigned to nestbox zone %d (%s)\n", + unit->id, Units::getRaceName(unit).c_str(), + building->id, building->name.c_str()); + + return true; +} + +static size_t countFreeEgglayers() { + size_t count = 0; + for (auto unit : world->units.all) { + if (isFreeEgglayer(unit)) + ++count; + } + return count; +} + +static size_t assign_nestboxes(color_ostream &out) { + size_t processed = 0; + df::building *free_building = NULL; + df::unit *free_unit = NULL; + do { + free_building = findFreeNestboxZone(); + free_unit = findFreeEgglayer(); + if (free_building && free_unit) { + if (!assignUnitToZone(out, free_unit, free_building)) { + DEBUG(cycle,out).print("Failed to assign unit to building.\n"); + return processed; + } + DEBUG(cycle,out).print("assigned unit %d to zone %d\n", + free_unit->id, free_building->id); + ++processed; + } + } while (free_unit && free_building); + + if (free_unit && !free_building) { + static size_t old_count = 0; + size_t freeEgglayers = countFreeEgglayers(); + // avoid spamming the same message + if (old_count != freeEgglayers) + did_complain = false; + old_count = freeEgglayers; + if (!did_complain) { + std::stringstream ss; + ss << freeEgglayers; + string announce = "Not enough free nestbox zones found! You need " + ss.str() + " more."; + Gui::showAnnouncement(announce, 6, true); + out << announce << std::endl; + did_complain = true; + } + } + return processed; +} + +static void autonestbox_cycle(color_ostream &out) { + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running autonestbox cycle\n"); + + size_t processed = assign_nestboxes(out); + if (processed > 0) { + std::stringstream ss; + ss << processed << " nestboxes were assigned."; + string announce = ss.str(); + DEBUG(cycle,out).print("%s\n", announce.c_str()); + Gui::showAnnouncement(announce, 2, false); + out << announce << std::endl; + // can complain again + // (might lead to spamming the same message twice, but catches the case + // where for example 2 new egglayers hatched right after 2 zones were created and assigned) + did_complain = false; + } +} diff --git a/plugins/blueprint.cpp b/plugins/blueprint.cpp index 1401333eb..509b1e6bc 100644 --- a/plugins/blueprint.cpp +++ b/plugins/blueprint.cpp @@ -5,7 +5,6 @@ * Written by cdombroski. */ -#include #include #include @@ -13,6 +12,7 @@ #include "DataDefs.h" #include "DataFuncs.h" #include "DataIdentity.h" +#include "Debug.h" #include "LuaTools.h" #include "PluginManager.h" #include "TileTypes.h" @@ -46,6 +46,10 @@ using namespace DFHack; DFHACK_PLUGIN("blueprint"); REQUIRE_GLOBAL(world); +namespace DFHack { + DBG_DECLARE(blueprint,log); +} + struct blueprint_options { // whether to display help bool help = false; @@ -58,6 +62,9 @@ struct blueprint_options { // for it. string format; + // whether to skip generating meta blueprints + bool nometa = false; + // offset and comment to write in the quickfort start() modeline marker // if not set, coordinates are set to 0 and the comment will be empty df::coord2d playback_start = df::coord2d(0, 0); @@ -76,6 +83,8 @@ struct blueprint_options { // base name to use for generated files string name; + // whether to capture all smoothed tiles + bool smooth = false; // whether to capture engravings and smooth the tiles that will be engraved bool engrave = false; @@ -83,12 +92,14 @@ struct blueprint_options { bool auto_phase = false; // if not autodetecting, which phases to output - bool dig = false; + bool dig = false; bool carve = false; + bool construct = false; bool build = false; bool place = false; - bool zone = false; + bool zone = false; bool query = false; + bool rooms = false; static struct_identity _identity; }; @@ -96,6 +107,7 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "help", offsetof(blueprint_options, help), &df::identity_traits::identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "start", offsetof(blueprint_options, start), &df::coord::_identity, 0, 0 }, { struct_field_info::PRIMITIVE, "format", offsetof(blueprint_options, format), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "nometa", offsetof(blueprint_options, nometa), &df::identity_traits::identity, 0, 0 }, { struct_field_info::SUBSTRUCT, "playback_start", offsetof(blueprint_options, playback_start), &df::coord2d::_identity, 0, 0 }, { struct_field_info::PRIMITIVE, "playback_start_comment", offsetof(blueprint_options, playback_start_comment), df::identity_traits::get(), 0, 0 }, { struct_field_info::PRIMITIVE, "split_strategy", offsetof(blueprint_options, split_strategy), df::identity_traits::get(), 0, 0 }, @@ -103,14 +115,17 @@ static const struct_field_info blueprint_options_fields[] = { { struct_field_info::PRIMITIVE, "height", offsetof(blueprint_options, height), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "depth", offsetof(blueprint_options, depth), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "name", offsetof(blueprint_options, name), df::identity_traits::get(), 0, 0 }, + { struct_field_info::PRIMITIVE, "smooth", offsetof(blueprint_options, smooth), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "engrave", offsetof(blueprint_options, engrave), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "auto_phase", offsetof(blueprint_options, auto_phase), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "dig", offsetof(blueprint_options, dig), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "carve", offsetof(blueprint_options, carve), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "construct", offsetof(blueprint_options, construct), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "build", offsetof(blueprint_options, build), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "place", offsetof(blueprint_options, place), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "zone", offsetof(blueprint_options, zone), &df::identity_traits::identity, 0, 0 }, { struct_field_info::PRIMITIVE, "query", offsetof(blueprint_options, query), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "rooms", offsetof(blueprint_options, rooms), &df::identity_traits::identity, 0, 0 }, { struct_field_info::END } }; struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::allocator_fn, NULL, "blueprint_options", NULL, blueprint_options_fields); @@ -118,7 +133,10 @@ struct_identity blueprint_options::_identity(sizeof(blueprint_options), &df::all command_result blueprint(color_ostream &, vector &); DFhackCExport command_result plugin_init(color_ostream &, vector &commands) { - commands.push_back(PluginCommand("blueprint", "Record the structure of a live game map in a quickfort blueprint file", blueprint, false)); + commands.push_back( + PluginCommand("blueprint", + "Record a live game map in a quickfort blueprint.", + blueprint)); return CR_OK; } @@ -126,11 +144,36 @@ DFhackCExport command_result plugin_shutdown(color_ostream &) { return CR_OK; } +struct blueprint_processor; struct tile_context { + blueprint_processor *processor; bool pretty = false; df::building* b = NULL; }; +typedef vector bp_row; // index is x coordinate +typedef map bp_area; // key is y coordinate +typedef map bp_volume; // key is z coordinate + +typedef const char * (get_tile_fn)(const df::coord &pos, + const tile_context &ctx); +typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx); + +struct blueprint_processor { + bp_volume mapdata; + const string mode; + const string phase; + const bool force_create; + get_tile_fn * const get_tile; + init_ctx_fn * const init_ctx; + std::set seen; + blueprint_processor(const string &mode, const string &phase, + bool force_create, get_tile_fn *get_tile, + init_ctx_fn *init_ctx) + : mode(mode), phase(phase), force_create(force_create), + get_tile(get_tile), init_ctx(init_ctx) { } +}; + // global engravings cache, cleared when the string cache is cleared struct PosHash { size_t operator()(const df::coord &c) const { @@ -216,8 +259,8 @@ static const char * get_tile_smooth_minimal(const df::coord &pos, return NULL; } -static const char * get_tile_smooth(const df::coord &pos, - const tile_context &tc) { +static const char * get_tile_smooth_with_engravings(const df::coord &pos, + const tile_context &tc) { const char * smooth_minimal = get_tile_smooth_minimal(pos, tc); if (smooth_minimal) return smooth_minimal; @@ -241,6 +284,30 @@ static const char * get_tile_smooth(const df::coord &pos, return NULL; } +static const char * get_tile_smooth_all(const df::coord &pos, + const tile_context &tc) { + const char * smooth_minimal = get_tile_smooth_minimal(pos, tc); + if (smooth_minimal) + return smooth_minimal; + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt) + return NULL; + + switch (tileShape(*tt)) + { + case tiletype_shape::FLOOR: + case tiletype_shape::WALL: + if (tileSpecial(*tt) == tiletype_special::SMOOTH) + return "s"; + break; + default: + break; + } + + return NULL; +} + static const char * get_track_str(const char *prefix, df::tiletype tt) { TileDirection tdir = tileDirection(tt); @@ -302,6 +369,108 @@ static const char * get_tile_carve(const df::coord &pos, const tile_context &tc) return NULL; } +static const char * get_construction_str(df::building *b) { + df::building_constructionst *cons = + virtual_cast(b); + if (!cons) + return "~"; + + switch (cons->type) { + case construction_type::Fortification: return "CF"; + case construction_type::Wall: return "Cw"; + case construction_type::Floor: return "Cf"; + case construction_type::UpStair: return "Cu"; + case construction_type::DownStair: return "Cd"; + case construction_type::UpDownStair: return "Cx"; + case construction_type::Ramp: return "Cr"; + case construction_type::TrackN: return "trackN"; + case construction_type::TrackS: return "trackS"; + case construction_type::TrackE: return "trackE"; + case construction_type::TrackW: return "trackW"; + case construction_type::TrackNS: return "trackNS"; + case construction_type::TrackNE: return "trackNE"; + case construction_type::TrackNW: return "trackNW"; + case construction_type::TrackSE: return "trackSE"; + case construction_type::TrackSW: return "trackSW"; + case construction_type::TrackEW: return "trackEW"; + case construction_type::TrackNSE: return "trackNSE"; + case construction_type::TrackNSW: return "trackNSW"; + case construction_type::TrackNEW: return "trackNEW"; + case construction_type::TrackSEW: return "trackSEW"; + case construction_type::TrackNSEW: return "trackNSEW"; + case construction_type::TrackRampN: return "trackrampN"; + case construction_type::TrackRampS: return "trackrampS"; + case construction_type::TrackRampE: return "trackrampE"; + case construction_type::TrackRampW: return "trackrampW"; + case construction_type::TrackRampNS: return "trackrampNS"; + case construction_type::TrackRampNE: return "trackrampNE"; + case construction_type::TrackRampNW: return "trackrampNW"; + case construction_type::TrackRampSE: return "trackrampSE"; + case construction_type::TrackRampSW: return "trackrampSW"; + case construction_type::TrackRampEW: return "trackrampEW"; + case construction_type::TrackRampNSE: return "trackrampNSE"; + case construction_type::TrackRampNSW: return "trackrampNSW"; + case construction_type::TrackRampNEW: return "trackrampNEW"; + case construction_type::TrackRampSEW: return "trackrampSEW"; + case construction_type::TrackRampNSEW: return "trackrampNSEW"; + case construction_type::NONE: + default: + return "~"; + } +} + +static const char * get_constructed_track_str(df::tiletype *tt, + const char * base) { + TileDirection dir = tileDirection(*tt); + if (!dir.whole) + return "~"; + + std::ostringstream str; + str << base; + if (dir.north) str << "N"; + if (dir.south) str << "S"; + if (dir.east) str << "E"; + if (dir.west) str << "W"; + + return cache(str); +} + +static const char * get_constructed_floor_str(df::tiletype *tt) { + if (tileSpecial(*tt) != df::tiletype_special::TRACK) + return "Cf"; + return get_constructed_track_str(tt, "track"); +} + +static const char * get_constructed_ramp_str(df::tiletype *tt) { + if (tileSpecial(*tt) != df::tiletype_special::TRACK) + return "Cr"; + return get_constructed_track_str(tt, "trackramp"); +} + +static const char * get_tile_construct(const df::coord &pos, + const tile_context &ctx) { + if (ctx.b && ctx.b->getType() == building_type::Construction) + return get_construction_str(ctx.b); + + df::tiletype *tt = Maps::getTileType(pos); + if (!tt || tileMaterial(*tt) != df::tiletype_material::CONSTRUCTION) + return NULL; + + switch (tileShape(*tt)) { + case tiletype_shape::WALL: return "Cw"; + case tiletype_shape::FLOOR: return get_constructed_floor_str(tt); + case tiletype_shape::RAMP: return get_constructed_ramp_str(tt); + case tiletype_shape::FORTIFICATION: return "CF"; + case tiletype_shape::STAIR_UP: return "Cu"; + case tiletype_shape::STAIR_DOWN: return "Cd"; + case tiletype_shape::STAIR_UPDOWN: return "Cx"; + default: + return "~"; + } + + return NULL; +} + static pair get_building_size(const df::building *b) { return pair(b->x2 - b->x1 + 1, b->y2 - b->y1 + 1); } @@ -431,56 +600,6 @@ static const char * get_furnace_str(df::building *b) { } } -static const char * get_construction_str(df::building *b) { - df::building_constructionst *cons = - virtual_cast(b); - if (!cons) - return "~"; - - switch (cons->type) { - case construction_type::Fortification: return "CF"; - case construction_type::Wall: return "Cw"; - case construction_type::Floor: return "Cf"; - case construction_type::UpStair: return "Cu"; - case construction_type::DownStair: return "Cd"; - case construction_type::UpDownStair: return "Cx"; - case construction_type::Ramp: return "Cr"; - case construction_type::TrackN: return "trackN"; - case construction_type::TrackS: return "trackS"; - case construction_type::TrackE: return "trackE"; - case construction_type::TrackW: return "trackW"; - case construction_type::TrackNS: return "trackNS"; - case construction_type::TrackNE: return "trackNE"; - case construction_type::TrackNW: return "trackNW"; - case construction_type::TrackSE: return "trackSE"; - case construction_type::TrackSW: return "trackSW"; - case construction_type::TrackEW: return "trackEW"; - case construction_type::TrackNSE: return "trackNSE"; - case construction_type::TrackNSW: return "trackNSW"; - case construction_type::TrackNEW: return "trackNEW"; - case construction_type::TrackSEW: return "trackSEW"; - case construction_type::TrackNSEW: return "trackNSEW"; - case construction_type::TrackRampN: return "trackrampN"; - case construction_type::TrackRampS: return "trackrampS"; - case construction_type::TrackRampE: return "trackrampE"; - case construction_type::TrackRampW: return "trackrampW"; - case construction_type::TrackRampNS: return "trackrampNS"; - case construction_type::TrackRampNE: return "trackrampNE"; - case construction_type::TrackRampNW: return "trackrampNW"; - case construction_type::TrackRampSE: return "trackrampSE"; - case construction_type::TrackRampSW: return "trackrampSW"; - case construction_type::TrackRampEW: return "trackrampEW"; - case construction_type::TrackRampNSE: return "trackrampNSE"; - case construction_type::TrackRampNSW: return "trackrampNSW"; - case construction_type::TrackRampNEW: return "trackrampNEW"; - case construction_type::TrackRampSEW: return "trackrampSEW"; - case construction_type::TrackRampNSEW: return "trackrampNSEW"; - case construction_type::NONE: - default: - return "~"; - } -} - static const char * get_trap_str(df::building *b) { df::building_trapst *trap = virtual_cast(b); if (!trap) @@ -592,6 +711,7 @@ static const char * get_build_keys(const df::coord &pos, bool at_center = static_cast(pos.x) == ctx.b->centerx && static_cast(pos.y) == ctx.b->centery; + // building_type::Construction is handled by the construction phase switch(ctx.b->getType()) { case building_type::Armorstand: return "a"; @@ -636,8 +756,6 @@ static const char * get_build_keys(const df::coord &pos, return "y"; case building_type::WindowGem: return "Y"; - case building_type::Construction: - return get_construction_str(ctx.b); case building_type::Shop: return do_block_building(ctx, "z", at_center); case building_type::AnimalTrap: @@ -891,10 +1009,82 @@ static const char * get_tile_zone(const df::coord &pos, return add_expansion_syntax(zone, get_zone_keys(zone)); } -static const char * get_tile_query(const df::coord &, const tile_context &ctx) { +// surrounds the given string in quotes and replaces internal double quotes (") +// with double double quotes ("") (as per the csv spec) +static string csv_quote(const string &str) { + std::ostringstream outstr; + outstr << "\""; + + size_t start = 0; + auto end = str.find('"'); + while (end != std::string::npos) { + outstr << str.substr(start, end - start); + outstr << "\"\""; + start = end + 1; + end = str.find('"', start); + } + outstr << str.substr(start, end) << "\""; + + return outstr.str(); +} + +static const char * get_tile_query(const df::coord &pos, + const tile_context &ctx) { + string bld_name, zone_name; + auto & seen = ctx.processor->seen; + + if (ctx.b && !seen.count(ctx.b)) { + bld_name = ctx.b->name; + seen.emplace(ctx.b); + } + + vector civzones; + if (Buildings::findCivzonesAt(&civzones, pos)) { + auto civzone = civzones.back(); + if (!seen.count(civzone)) { + zone_name = civzone->name; + seen.emplace(civzone); + } + } + + if (!bld_name.size() && !zone_name.size()) + return NULL; + + std::ostringstream str; + if (bld_name.size()) + str << "{givename name=" + csv_quote(bld_name) + "}"; + if (zone_name.size()) + str << "{namezone name=" + csv_quote(zone_name) + "}"; + + return cache(csv_quote(str.str())); +} + +static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) { if (!ctx.b || !ctx.b->is_room) return NULL; - return "r+"; + + // get the maximum distance from the center of the building + df::building_extents &room = ctx.b->room; + int32_t x1 = room.x; + int32_t x2 = room.x + room.width - 1; + int32_t y1 = room.y; + int32_t y2 = room.y + room.height - 1; + + int32_t dimx = std::max(ctx.b->centerx - x1, x2 - ctx.b->centerx); + int32_t dimy = std::max(ctx.b->centery - y1, y2 - ctx.b->centery); + int32_t max_dim = std::max(dimx, dimy); + + switch (max_dim) { + case 0: return "r---&"; + case 1: return "r--&"; + case 2: return "r-&"; + case 3: return "r&"; + case 4: return "r+&"; + } + + std::ostringstream str; + str << "r{+ " << (max_dim - 3) << "}&"; + return cache(str); } static bool create_output_dir(color_ostream &out, @@ -915,11 +1105,12 @@ static bool create_output_dir(color_ostream &out, static bool get_filename(string &fname, color_ostream &out, blueprint_options opts, // copy because we can't const - const string &phase) { + const string &phase, + int32_t ordinal) { auto L = Lua::Core::State; Lua::StackUnwinder top(L); - if (!lua_checkstack(L, 3) || + if (!lua_checkstack(L, 4) || !Lua::PushModulePublic( out, L, "plugins.blueprint", "get_filename")) { out.printerr("Failed to load blueprint Lua code\n"); @@ -928,8 +1119,9 @@ static bool get_filename(string &fname, Lua::Push(L, &opts); Lua::Push(L, phase); + Lua::Push(L, ordinal); - if (!Lua::SafeCall(out, L, 2, 1)) { + if (!Lua::SafeCall(out, L, 3, 1)) { out.printerr("Failed Lua call to get_filename\n"); return false; } @@ -944,27 +1136,31 @@ static bool get_filename(string &fname, return true; } -typedef vector bp_row; // index is x coordinate -typedef map bp_area; // key is y coordinate -typedef map bp_volume; // key is z coordinate +// returns true if we could interface with lua and could verify that the given +// phase is a meta phase +static bool is_meta_phase(color_ostream &out, + blueprint_options opts, // copy because we can't const + const string &phase) { + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); -typedef const char * (get_tile_fn)(const df::coord &pos, - const tile_context &ctx); -typedef void (init_ctx_fn)(const df::coord &pos, tile_context &ctx); + if (!lua_checkstack(L, 3) || + !Lua::PushModulePublic( + out, L, "plugins.blueprint", "is_meta_phase")) { + out.printerr("Failed to load blueprint Lua code\n"); + return false; + } -struct blueprint_processor { - bp_volume mapdata; - const string mode; - const string phase; - const bool force_create; - get_tile_fn * const get_tile; - init_ctx_fn * const init_ctx; - blueprint_processor(const string &mode, const string &phase, - bool force_create, get_tile_fn *get_tile, - init_ctx_fn *init_ctx) - : mode(mode), phase(phase), force_create(force_create), - get_tile(get_tile), init_ctx(init_ctx) { } -}; + Lua::Push(L, &opts); + Lua::Push(L, phase); + + if (!Lua::SafeCall(out, L, 2, 1)) { + out.printerr("Failed Lua call to is_meta_phase\n"); + return false; + } + + return lua_toboolean(L, -1); +} static void write_minimal(ofstream &ofile, const blueprint_options &opts, const bp_volume &mapdata) { @@ -1022,8 +1218,8 @@ static void write_pretty(ofstream &ofile, const blueprint_options &opts, } } -static string get_modeline(const blueprint_options &opts, const string &mode, - const string &phase) { +static string get_modeline(color_ostream &out, const blueprint_options &opts, + const string &mode, const string &phase) { std::ostringstream modeline; modeline << "#" << mode << " label(" << phase << ")"; if (opts.playback_start.x > 0) { @@ -1034,6 +1230,8 @@ static string get_modeline(const blueprint_options &opts, const string &mode, } modeline << ")"; } + if (is_meta_phase(out, opts, phase)) + modeline << " hidden()"; return modeline.str(); } @@ -1042,15 +1240,15 @@ static bool write_blueprint(color_ostream &out, std::map &output_files, const blueprint_options &opts, const blueprint_processor &processor, - bool pretty) { + bool pretty, int32_t ordinal) { string fname; - if (!get_filename(fname, out, opts, processor.phase)) + if (!get_filename(fname, out, opts, processor.phase, ordinal)) return false; if (!output_files.count(fname)) output_files[fname] = new ofstream(fname, ofstream::trunc); ofstream &ofile = *output_files[fname]; - ofile << get_modeline(opts, processor.mode, processor.phase) << endl; + ofile << get_modeline(out, opts, processor.mode, processor.phase) << endl; if (pretty) write_pretty(ofile, opts, processor.mapdata); @@ -1060,6 +1258,28 @@ static bool write_blueprint(color_ostream &out, return true; } +static void write_meta_blueprint(color_ostream &out, + std::map &output_files, + const blueprint_options &opts, + const std::vector & meta_phases, + int32_t ordinal) { + string fname; + get_filename(fname, out, opts, meta_phases.front(), ordinal); + ofstream &ofile = *output_files[fname]; + + ofile << "#meta label("; + for (string phase : meta_phases) { + ofile << phase; + if (phase != meta_phases.back()) + ofile << "_"; + } + ofile << ")" << endl; + + for (string phase : meta_phases) { + ofile << "/" << phase << endl; + } +} + static void ensure_building(const df::coord &pos, tile_context &ctx) { if (ctx.b) return; @@ -1078,7 +1298,7 @@ static void add_processor(vector &processors, static bool do_transform(color_ostream &out, const df::coord &start, const df::coord &end, - const blueprint_options &opts, + blueprint_options opts, // copy so we can munge it vector &filenames) { // empty map instances to pass to emplace() below static const bp_area EMPTY_AREA; @@ -1093,11 +1313,17 @@ static bool do_transform(color_ostream &out, vector processors; + get_tile_fn* smooth_get_tile_fn = get_tile_smooth_minimal; + if (opts.engrave) smooth_get_tile_fn = get_tile_smooth_with_engravings; + if (opts.smooth) smooth_get_tile_fn = get_tile_smooth_all; + add_processor(processors, opts, "dig", "dig", opts.dig, get_tile_dig); add_processor(processors, opts, "dig", "smooth", opts.carve, - opts.engrave ? get_tile_smooth : get_tile_smooth_minimal); + smooth_get_tile_fn); add_processor(processors, opts, "dig", "carve", opts.carve, opts.engrave ? get_tile_carve : get_tile_carve_minimal); + add_processor(processors, opts, "build", "construct", opts.construct, + get_tile_construct, ensure_building); add_processor(processors, opts, "build", "build", opts.build, get_tile_build, ensure_building); add_processor(processors, opts, "place", "place", opts.place, @@ -1105,6 +1331,8 @@ static bool do_transform(color_ostream &out, add_processor(processors, opts, "zone", "zone", opts.zone, get_tile_zone); add_processor(processors, opts, "query", "query", opts.query, get_tile_query, ensure_building); + add_processor(processors, opts, "query", "rooms", opts.rooms, + get_tile_rooms, ensure_building); if (processors.empty()) { out.printerr("no phases requested! nothing to do!\n"); @@ -1123,6 +1351,7 @@ static bool do_transform(color_ostream &out, tile_context ctx; ctx.pretty = pretty; for (blueprint_processor &processor : processors) { + ctx.processor = &processor; if (processor.init_ctx) processor.init_ctx(pos, ctx); const char *tile_str = processor.get_tile(pos, ctx); @@ -1142,13 +1371,36 @@ static bool do_transform(color_ostream &out, } } + std::vector meta_phases; + for (blueprint_processor &processor : processors) { + if (processor.mapdata.empty() && !processor.force_create) + continue; + if (is_meta_phase(out, opts, processor.phase)) + meta_phases.push_back(processor.phase); + } + if (meta_phases.size() <= 1) + opts.nometa = true; + + bool in_meta = false; + int32_t ordinal = 0; std::map output_files; for (blueprint_processor &processor : processors) { if (processor.mapdata.empty() && !processor.force_create) continue; - if (!write_blueprint(out, output_files, opts, processor, pretty)) + bool meta_phase = is_meta_phase(out, opts, processor.phase); + if (!in_meta) + ++ordinal; + if (in_meta && !meta_phase) { + write_meta_blueprint(out, output_files, opts, meta_phases, ordinal); + ++ordinal; + } + in_meta = meta_phase; + if (!write_blueprint(out, output_files, opts, processor, pretty, + ordinal)) break; } + if (in_meta) + write_meta_blueprint(out, output_files, opts, meta_phases, ordinal); for (auto &it : output_files) { filenames.push_back(it.first); @@ -1184,23 +1436,11 @@ static bool get_options(color_ostream &out, return true; } -static void print_help(color_ostream &out) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(out, L, "plugins.blueprint", "print_help") || - !Lua::SafeCall(out, L, 0, 0)) - { - out.printerr("Failed to load blueprint Lua code\n"); - } -} - // returns whether blueprint generation was successful. populates files with the // names of the files that were generated -static bool do_blueprint(color_ostream &out, - const vector ¶meters, - vector &files) { +static command_result do_blueprint(color_ostream &out, + const vector ¶meters, + vector &files) { CoreSuspender suspend; if (parameters.size() >= 1 && parameters[0] == "gui") { @@ -1218,13 +1458,12 @@ static bool do_blueprint(color_ostream &out, blueprint_options options; if (!get_options(out, options, parameters) || options.help) { - print_help(out); - return options.help; + return CR_WRONG_USAGE; } if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); - return false; + return CR_FAILURE; } // start coordinates can come from either the commandline or the map cursor @@ -1233,13 +1472,13 @@ static bool do_blueprint(color_ostream &out, if (!Gui::getCursorCoords(start)) { out.printerr("Can't get cursor coords! Make sure you specify the" " --cursor parameter or have an active cursor in DF.\n"); - return false; + return CR_FAILURE; } } if (!Maps::isValidTilePos(start)) { out.printerr("Invalid start position: %d,%d,%d\n", start.x, start.y, start.z); - return false; + return CR_FAILURE; } // end coords are one beyond the last processed coordinate. note that @@ -1262,7 +1501,7 @@ static bool do_blueprint(color_ostream &out, bool ok = do_transform(out, start, end, options, files); cache(NULL); - return ok; + return ok ? CR_OK : CR_FAILURE; } // entrypoint when called from Lua. returns the names of the generated files @@ -1281,7 +1520,7 @@ static int run(lua_State *L) { color_ostream *out = Lua::GetOutput(L); if (!out) out = &Core::getInstance().getConsole(); - if (do_blueprint(*out, argv, files)) { + if (CR_OK == do_blueprint(*out, argv, files)) { Lua::PushVector(L, files); return 1; } @@ -1291,13 +1530,13 @@ static int run(lua_State *L) { command_result blueprint(color_ostream &out, vector ¶meters) { vector files; - if (do_blueprint(out, parameters, files)) { + command_result cr = do_blueprint(out, parameters, files); + if (cr == CR_OK) { out.print("Generated blueprint file(s):\n"); for (string &fname : files) out.print(" %s\n", fname.c_str()); - return CR_OK; } - return CR_FAILURE; + return cr; } DFHACK_PLUGIN_LUA_COMMANDS { diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp index 2c75d4622..635d99261 100644 --- a/plugins/building-hacks.cpp +++ b/plugins/building-hacks.cpp @@ -88,8 +88,8 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - info->produced = ref->anon_1; - info->consumed = ref->anon_2; + info->produced = ref->unk_1; + info->consumed = ref->unk_2; return true; } else @@ -118,14 +118,14 @@ struct work_hook : df::building_workshopst{ df::general_ref_creaturest* ref = static_cast(DFHack::Buildings::getGeneralRef(this, general_ref_type::CREATURE)); if (ref) { - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; } else { ref = df::allocate(); - ref->anon_1 = produced; - ref->anon_2 = consumed; + ref->unk_1 = produced; + ref->unk_2 = consumed; general_refs.push_back(ref); } } diff --git a/plugins/buildingplan.cpp b/plugins/buildingplan.cpp index 594eaf48f..b13c8daa3 100644 --- a/plugins/buildingplan.cpp +++ b/plugins/buildingplan.cpp @@ -1058,9 +1058,9 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back( - PluginCommand( - "buildingplan", "Plan building construction before you have materials", - buildingplan_cmd, false, "Run 'buildingplan debug [on|off]' to toggle debugging, or 'buildingplan version' to query the plugin version.")); + PluginCommand("buildingplan", + "Plan building construction before you have materials.", + buildingplan_cmd)); return CR_OK; } diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 3b1d3fe86..029b3c715 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -53,35 +53,10 @@ static void deinit_map(color_ostream &out); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "burrow", "Miscellaneous burrow control.", burrow, false, - " burrow enable options...\n" - " burrow disable options...\n" - " Enable or disable features of the plugin.\n" - " See below for a list and explanation.\n" - " burrow clear-units burrow burrow...\n" - " burrow clear-tiles burrow burrow...\n" - " Removes all units or tiles from the burrows.\n" - " burrow set-units target-burrow src-burrow...\n" - " burrow add-units target-burrow src-burrow...\n" - " burrow remove-units target-burrow src-burrow...\n" - " Adds or removes units in source burrows to/from the target\n" - " burrow. Set is equivalent to clear and add.\n" - " burrow set-tiles target-burrow src-burrow...\n" - " burrow add-tiles target-burrow src-burrow...\n" - " burrow remove-tiles target-burrow src-burrow...\n" - " Adds or removes tiles in source burrows to/from the target\n" - " burrow. In place of a source burrow it is possible to use\n" - " one of the following keywords:\n" - " ABOVE_GROUND, SUBTERRANEAN, INSIDE, OUTSIDE,\n" - " LIGHT, DARK, HIDDEN, REVEALED\n" - "Implemented features:\n" - " auto-grow\n" - " When a wall inside a burrow with a name ending in '+' is dug\n" - " out, the burrow is extended to newly-revealed adjacent walls.\n" - " This final '+' may be omitted in burrow name args of commands above.\n" - " Note: Digging 1-wide corridors with the miner inside the burrow is SLOW.\n" - )); + commands.push_back( + PluginCommand("burrow", + "Quick commands for burrow control.", + burrow)); if (Core::getInstance().isMapLoaded()) init_map(out); diff --git a/plugins/changeitem.cpp b/plugins/changeitem.cpp index 52d9e7ab9..49feaec79 100644 --- a/plugins/changeitem.cpp +++ b/plugins/changeitem.cpp @@ -37,37 +37,12 @@ REQUIRE_GLOBAL(world); command_result df_changeitem(color_ostream &out, vector & parameters); -const string changeitem_help = - "Changeitem allows to change some item attributes.\n" - "By default the item currently selected in the UI will be changed\n" - "(you can select items in the 'k' list or inside containers/inventory).\n" - "By default change is only allowed if materials is of the same subtype\n" - "(for example wood<->wood, stone<->stone etc). But since some transformations\n" - "work pretty well and may be desired you can override this with 'force'.\n" - "Note that some attributes will not be touched, possibly resulting in weirdness.\n" - "To get an idea how the RAW id should look like, check some items with 'info'.\n" - "Using 'force' might create items which are not touched by crafters/haulers.\n" - "Options:\n" - " info - don't change anything, print some item info instead\n" - " here - change all items at cursor position\n" - " material, m - change material. must be followed by material RAW id\n" - " subtype, s - change subtype. must be followed by correct RAW id\n" - " quality, q - change base quality. must be followed by number (0-5)\n" - " force - ignore subtypes, force change to new material.\n" - "Example:\n" - " changeitem m INORGANIC:GRANITE here\n" - " change material of all items under the cursor to granite\n" - " changeitem q 5\n" - " change currently selected item to masterpiece quality\n"; - - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "changeitem", "Change item attributes (material, quality).", - df_changeitem, false, - changeitem_help.c_str() - )); + "changeitem", + "Change item attributes (material, quality).", + df_changeitem)); return CR_OK; } @@ -130,8 +105,7 @@ command_result df_changeitem(color_ostream &out, vector & parameters) if (p == "help" || p == "?") { - out << changeitem_help << endl; - return CR_OK; + return CR_WRONG_USAGE; } else if (p == "here") { diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 79081c040..7520ac932 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -31,66 +31,14 @@ DFHACK_PLUGIN("changelayer"); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(cursor); -const string changelayer_help = - " Allows to change the material of whole geology layers.\n" - " Can have impact on all surrounding regions, not only your embark!\n" - " By default changing stone to soil and vice versa is not allowed.\n" - " By default changes only the layer at the cursor position.\n" - " Note that one layer can stretch across lots of z levels.\n" - " By default changes only the geology which is linked to the biome under the\n" - " cursor. That geology might be linked to other biomes as well, though.\n" - " Mineral veins and gem clusters will stay on the map.\n" - " Use 'changevein' for them.\n\n" - " tl;dr: You will end up with changing quite big areas in one go.\n\n" - "Options (first parameter MUST be the material id):\n" - " all_biomes - Change layer for all biomes on your map.\n" - " Result may be undesirable since the same layer\n" - " can AND WILL be on different z-levels for different biomes.\n" - " Use the tool 'probe' to get an idea how layers and biomes\n" - " are distributed on your map.\n" - " all_layers - Change all layers on your map.\n" - " Candy mountain, anyone?\n" - " Will make your map quite boring, but tidy.\n" - " force - Allow changing stone to soil and vice versa.\n" - " !!THIS CAN HAVE WEIRD EFFECTS, USE WITH CARE!!\n" - " Note that soil will not be magically replaced with stone.\n" - " You will, however, get a stone floor after digging so it\n" - " will allow the floor to be engraved.\n" - " Note that stone will not be magically replaced with soil.\n" - " You will, however, get a soil floor after digging so it\n" - " could be helpful for creating farm plots on maps with no soil.\n" - " verbose - Give some more details about what is being changed.\n" - " trouble - Give some advice for known problems.\n" - "Example:\n" - " changelayer GRANITE\n" - " Convert layer at cursor position into granite.\n" - " changelayer SILTY_CLAY force\n" - " Convert layer at cursor position into clay even if it's stone.\n" - " changelayer MARBLE allbiomes alllayers\n" - " Convert all layers of all biomes into marble.\n"; - -const string changelayer_trouble = - "Known problems with changelayer:\n\n" - " Nothing happens, the material stays the old.\n" - " Pause/unpause the game and/or move the cursor a bit. Then retry.\n" - " Try changing another layer, undo the changes and try again.\n" - " Try saving and loading the game.\n\n" - " Weird stuff happening after using the 'force' option.\n" - " Change former stone layers back to stone, soil back to soil.\n" - " If in doubt, use the 'probe' tool to find tiles with soil walls\n" - " and stone layer type or the other way round.\n"; - - command_result changelayer (color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "changelayer", "Change a whole geology layer.", - changelayer, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - changelayer_help.c_str() - )); + "changelayer", + "Change the material of an entire geology layer.", + changelayer)); return CR_OK; } @@ -120,13 +68,7 @@ command_result changelayer (color_ostream &out, std::vector & para { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("%s",changelayer_help.c_str()); - return CR_OK; - } - if(parameters[i] == "trouble") - { - out.print("%s",changelayer_trouble.c_str()); - return CR_OK; + return CR_WRONG_USAGE; } if(parameters[i] == "force") force = true; diff --git a/plugins/changevein.cpp b/plugins/changevein.cpp index 8612603fc..f531ca256 100644 --- a/plugins/changevein.cpp +++ b/plugins/changevein.cpp @@ -85,9 +85,8 @@ command_result df_changevein (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("changevein", - "Changes the material of a mineral inclusion.", - df_changevein, false, - "Syntax: changevein \n")); + "Change the material of a mineral inclusion.", + df_changevein)); return CR_OK; } diff --git a/plugins/cleanconst.cpp b/plugins/cleanconst.cpp index 84659c5d7..38149abd3 100644 --- a/plugins/cleanconst.cpp +++ b/plugins/cleanconst.cpp @@ -71,12 +71,9 @@ command_result df_cleanconst(color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "cleanconst", "Cleans up construction materials.", - df_cleanconst, false, - " This utility alters all constructions on the map so that they spawn their\n" - " building component when they are disassembled, allowing their actual\n" - " build items to be safely deleted.\n" - )); + "cleanconst", + "Cleans up construction materials.", + df_cleanconst)); return CR_OK; } diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 24694c6a2..00075e9d7 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -241,27 +241,13 @@ command_result clean (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "clean","Remove contaminants from tiles, items and creatures.", - clean, false, - " Removes contaminants from map tiles, items and creatures.\n" - "Options:\n" - " map - clean the map tiles\n" - " items - clean all items\n" - " units - clean all creatures\n" - " plants - clean all plants\n" - " all - clean everything.\n" - "More options for 'map':\n" - " snow - also remove snow\n" - " mud - also remove mud\n" - " item - also remove item spatters (e.g. leaves and flowers)\n" - "Example:\n" - " clean all mud snow item\n" - " Removes all spatter, including mud and snow from map tiles.\n" - )); + "clean", + "Remove contaminants from tiles, items, and creatures.", + clean)); commands.push_back(PluginCommand( - "spotclean","Cleans map tile under cursor.", - spotclean,Gui::cursor_hotkey - )); + "spotclean", + "Clean the map tile under the cursor.", + spotclean,Gui::cursor_hotkey)); return CR_OK; } diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index 90b0e743d..387dfdd92 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -31,22 +31,9 @@ command_result df_cleanowned (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "cleanowned", "Confiscates and dumps garbage owned by dwarfs.", - df_cleanowned, false, - " This tool lets you confiscate and dump all the garbage\n" - " dwarves ultimately accumulate.\n" - " By default, only rotten and dropped food is confiscated.\n" - "Options:\n" - " dryrun - don't actually do anything, just print what would be done.\n" - " scattered - confiscate owned items on the ground\n" - " all - confiscate everything\n" - " x - confiscate & dump 'x' and worse damaged items\n" - " X - confiscate & dump 'X' and worse damaged items\n" - "Example:\n" - " cleanowned scattered X\n" - " This will confiscate rotten and dropped food, garbage on the floors\n" - " and any worn items with 'X' damage and above.\n" - )); + "cleanowned", + "Confiscates and dumps garbage owned by dwarves.", + df_cleanowned)); return CR_OK; } @@ -160,7 +147,7 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) std::string description; item->getItemDescription(&description, 0); out.print( - "0x%p %s (wear %d)", + "%p %s (wear %d)", item, description.c_str(), item->getWear() diff --git a/plugins/command-prompt.cpp b/plugins/command-prompt.cpp index a987ea829..433b8fba3 100644 --- a/plugins/command-prompt.cpp +++ b/plugins/command-prompt.cpp @@ -1,23 +1,25 @@ -//command-prompt a one line command entry at the top of the screen for quick commands +// command-prompt: A one-line command entry at the top of the screen for quick commands #include "Core.h" +#include #include #include +#include #include -#include -#include #include +#include -#include #include +#include +#include #include #include +#include "df/enabler.h" +#include "df/graphic.h" #include "df/interface_key.h" #include "df/ui.h" -#include "df/graphic.h" -#include "df/enabler.h" using namespace DFHack; using namespace df::enums; @@ -36,8 +38,10 @@ class prompt_ostream:public buffered_color_ostream protected: void flush_proxy(); public: - prompt_ostream(viewscreen_commandpromptst* parent):parent_(parent){} - bool empty(){return buffer.empty();} + prompt_ostream(viewscreen_commandpromptst* parent) + : parent_(parent) + {} + bool empty() { return buffer.empty(); } }; class viewscreen_commandpromptst : public dfhack_viewscreen { public: @@ -48,7 +52,7 @@ public: } void render(); - void help() { } + void help() {} int8_t movies_okay() { return 0; } df::unit* getSelectedUnit() { return Gui::getAnyUnit(parent); } @@ -57,10 +61,11 @@ public: df::plant* getSelectedPlant() { return Gui::getAnyPlant(parent); } std::string getFocusString() { return "commandprompt"; } - viewscreen_commandpromptst(std::string entry):submitted(false), is_response(false) + viewscreen_commandpromptst(std::string entry) + : submitted(false), is_response(false) { - show_fps=gps->display_frames; - gps->display_frames=0; + show_fps = gps->display_frames; + gps->display_frames = 0; cursor_pos = entry.size(); frame = 0; history_idx = command_history.size(); @@ -76,7 +81,7 @@ public: } ~viewscreen_commandpromptst() { - gps->display_frames=show_fps; + gps->display_frames = show_fps; } void add_response(color_value v, std::string s) @@ -125,7 +130,7 @@ public: } protected: - std::list > responses; + std::list > responses; int cursor_pos; int history_idx; bool submitted; @@ -138,8 +143,8 @@ void prompt_ostream::flush_proxy() { if (buffer.empty()) return; - for(auto it=buffer.begin();it!=buffer.end();it++) - parent_->add_response(it->first,it->second); + for(auto it = buffer.begin(); it != buffer.end(); it++) + parent_->add_response(it->first, it->second); buffer.clear(); } void viewscreen_commandpromptst::render() @@ -154,25 +159,31 @@ void viewscreen_commandpromptst::render() auto dim = Screen::getWindowSize(); parent->render(); - if(is_response) + if (is_response) { - auto it=responses.begin(); - for(int i=0;isecond; - Screen::paintString(Screen::Pen(' ',it->first,0),0,i,cur_line.substr(0,cur_line.size()-1)); + std::vector lines; + word_wrap(&lines, response.second, dim.x); + for (auto &line : lines) + { + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, y, dim.x, y); + Screen::paintString(Screen::Pen(' ', response.first, 0), 0, y, line); + if (++y >= dim.y) + return; + } } } else { std::string entry = get_entry(); - Screen::fillRect(Screen::Pen(' ', 7, 0),0,0,dim.x,0); - Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0,"[DFHack]#"); + Screen::fillRect(Screen::Pen(' ', 7, 0), 0, 0, dim.x, 0); + Screen::paintString(Screen::Pen(' ', 7, 0), 0, 0, "[DFHack]#"); std::string cursor = (frame < enabler->gfps / 2) ? "_" : " "; - if(cursor_pos < (dim.x - 10)) + if (cursor_pos < dim.x - 10) { - Screen::paintString(Screen::Pen(' ', 7, 0), 10,0 , entry); + Screen::paintString(Screen::Pen(' ', 7, 0), 10, 0, entry); if (int16_t(entry.size()) > dim.x - 10) Screen::paintTile(Screen::Pen('\032', 7, 0), dim.x - 1, 0); if (cursor != " ") @@ -191,12 +202,12 @@ void viewscreen_commandpromptst::render() void viewscreen_commandpromptst::submit() { CoreSuspendClaimer suspend; - if(is_response) + if (is_response) { Screen::dismiss(this); return; } - if(submitted) + if (submitted) return; submitted = true; prompt_ostream out(this); @@ -204,11 +215,11 @@ void viewscreen_commandpromptst::submit() Screen::Hide hide_guard(this, Screen::Hide::RESTORE_AT_TOP); Core::getInstance().runCommand(out, get_entry()); } - if(out.empty() && responses.empty()) + if (out.empty() && responses.empty()) Screen::dismiss(this); else { - is_response=true; + is_response = true; } } void viewscreen_commandpromptst::feed(std::set *events) @@ -240,14 +251,14 @@ void viewscreen_commandpromptst::feed(std::set *events) for (auto it = events->begin(); it != events->end(); ++it) { auto key = *it; - if (key==interface_key::STRING_A000) //delete? + if (key == interface_key::STRING_A000) //delete? { - if(entry.size() && cursor_pos > 0) + if (entry.size() && cursor_pos > 0) { entry.erase(cursor_pos - 1, 1); cursor_pos--; } - if(size_t(cursor_pos) > entry.size()) + if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); continue; } @@ -261,34 +272,34 @@ void viewscreen_commandpromptst::feed(std::set *events) } } // Prevent number keys from moving cursor - if(events->count(interface_key::CURSOR_RIGHT)) + if (events->count(interface_key::CURSOR_RIGHT)) { cursor_pos++; if (size_t(cursor_pos) > entry.size()) cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_LEFT)) + else if (events->count(interface_key::CURSOR_LEFT)) { cursor_pos--; if (cursor_pos < 0) cursor_pos = 0; } - else if(events->count(interface_key::CURSOR_RIGHT_FAST)) + else if (events->count(interface_key::CURSOR_RIGHT_FAST)) { forward_word(); } - else if(events->count(interface_key::CURSOR_LEFT_FAST)) + else if (events->count(interface_key::CURSOR_LEFT_FAST)) { back_word(); } - else if(events->count(interface_key::CUSTOM_CTRL_A)) + else if (events->count(interface_key::CUSTOM_CTRL_A)) { cursor_pos = 0; } - else if(events->count(interface_key::CUSTOM_CTRL_E)) + else if (events->count(interface_key::CUSTOM_CTRL_E)) { cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_UP)) + else if (events->count(interface_key::CURSOR_UP)) { history_idx--; if (history_idx < 0) @@ -296,7 +307,7 @@ void viewscreen_commandpromptst::feed(std::set *events) entry = get_entry(); cursor_pos = entry.size(); } - else if(events->count(interface_key::CURSOR_DOWN)) + else if (events->count(interface_key::CURSOR_DOWN)) { if (size_t(history_idx) < command_history.size() - 1) { @@ -321,30 +332,28 @@ command_result show_prompt(color_ostream &out, std::vector & param return CR_OK; } std::string params; - for(size_t i=0;i(params), plugin_self); return CR_OK; } -bool hotkey_allow_all(df::viewscreen *top) -{ - return true; -} -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "command-prompt","Shows a command prompt on window.",show_prompt,hotkey_allow_all, - "command-prompt [entry] - shows a cmd prompt in df window. Entry is used for default prefix (e.g. ':lua')" - )); + "command-prompt", + "Allows you to run a DFHack command from in-game.", + show_prompt, + Gui::anywhere_hotkey)); return CR_OK; } -DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e) +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { return CR_OK; } -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +DFhackCExport command_result plugin_shutdown(color_ostream &out) { return CR_OK; } diff --git a/plugins/confirm.cpp b/plugins/confirm.cpp index a57ec45dd..5cef6376f 100644 --- a/plugins/confirm.cpp +++ b/plugins/confirm.cpp @@ -5,6 +5,7 @@ #include "Console.h" #include "Core.h" #include "DataDefs.h" +#include "Debug.h" #include "Error.h" #include "Export.h" #include "LuaTools.h" @@ -17,7 +18,9 @@ #include "df/building_tradedepotst.h" #include "df/general_ref.h" #include "df/general_ref_contained_in_itemst.h" +#include "df/interfacest.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_jobmanagementst.h" #include "df/viewscreen_justicest.h" #include "df/viewscreen_layer_militaryst.h" #include "df/viewscreen_locationsst.h" @@ -43,6 +46,15 @@ static map confirmations; string active_id; queue cmds; +// true when confirm is paused +bool paused = false; +// if set, confirm will unpause when this screen is no longer on the stack +df::viewscreen *paused_screen = NULL; + +namespace DFHack { + DBG_DECLARE(confirm,status); +} + template inline bool in_vector (std::vector &vec, FT item) { @@ -228,6 +240,18 @@ namespace conf_lua { lua_pushnil(L); return 1; } + int unpause(lua_State *) + { + DEBUG(status).print("unpausing\n"); + paused = false; + paused_screen = NULL; + return 0; + } + int get_paused (lua_State *L) + { + Lua::Push(L, paused); + return 1; + } } } @@ -246,6 +270,8 @@ DFHACK_PLUGIN_LUA_COMMANDS { CONF_LUA_CMD(get_ids), CONF_LUA_CMD(get_conf_data), CONF_LUA_CMD(get_active_id), + CONF_LUA_CMD(unpause), + CONF_LUA_CMD(get_paused), DFHACK_LUA_END }; @@ -280,7 +306,15 @@ public: return true; } bool feed (ikey_set *input) { - if (state == INACTIVE) + if (paused) + { + // we can only detect that we've left the screen by intercepting the + // ESC key + if (!paused_screen && input->count(df::interface_key::LEAVESCREEN)) + conf_lua::api::unpause(NULL); + return false; + } + else if (state == INACTIVE) { for (df::interface_key key : *input) { @@ -301,6 +335,18 @@ public: set_state(INACTIVE); else if (input->count(df::interface_key::SELECT)) set_state(SELECTED); + else if (input->count(df::interface_key::CUSTOM_P)) + { + DEBUG(status).print("pausing\n"); + paused = true; + // only record the screen when we're not at the top viewscreen + // since this screen will *always* be on the stack. for + // dwarfmode screens, use ESC detection to discover when to + // unpause + if (!df::viewscreen_dwarfmodest::_identity.is_instance(screen)) + paused_screen = screen; + set_state(INACTIVE); + } else if (input->count(df::interface_key::CUSTOM_S)) show_options(); return true; @@ -315,6 +361,8 @@ public: } void render() { static vector lines; + static const std::string pause_message = + "Pause confirmations until you exit this screen"; Screen::Pen corner_ul = Screen::Pen((char)201, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_ur = Screen::Pen((char)187, COLOR_GREY, COLOR_BLACK); Screen::Pen corner_dl = Screen::Pen((char)200, COLOR_GREY, COLOR_BLACK); @@ -328,7 +376,9 @@ public: for (string line : lines) max_length = std::max(max_length, line.size()); int width = max_length + 4; - int height = lines.size() + 4; + vector pause_message_lines; + word_wrap(&pause_message_lines, pause_message, max_length - 3); + int height = lines.size() + pause_message_lines.size() + 5; int x1 = (gps->dimx / 2) - (width / 2); int x2 = x1 + width - 1; int y1 = (gps->dimy / 2) - (height / 2); @@ -367,6 +417,14 @@ public: { Screen::paintString(Screen::Pen(' ', get_color(), COLOR_BLACK), x1 + 2, y1 + 2 + i, lines[i]); } + y = y1 + 3 + lines.size(); + for (size_t i = 0; i < pause_message_lines.size(); i++) + { + Screen::paintString(Screen::Pen(' ', COLOR_WHITE, COLOR_BLACK), x1 + 5, y + i, pause_message_lines[i]); + } + x = x1 + 2; + OutputString(COLOR_LIGHTRED, x, y, Screen::getKeyDisplay(df::interface_key::CUSTOM_P)); + OutputString(COLOR_WHITE, x, y, ":"); } else if (state == SELECTED) { @@ -481,6 +539,7 @@ DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest); DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst); DEFINE_CONFIRMATION(convict, viewscreen_justicest); +DEFINE_CONFIRMATION(order_remove, viewscreen_jobmanagementst); DFhackCExport command_result plugin_init (color_ostream &out, vector &commands) { @@ -488,13 +547,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, vector view; + while (screen) + { + if (screen == target_screen) + return true; + screen = screen->child; + } + + return false; +} + DFhackCExport command_result plugin_onupdate (color_ostream &out) { while (!cmds.empty()) @@ -538,6 +608,11 @@ DFhackCExport command_result plugin_onupdate (color_ostream &out) Core::getInstance().runCommand(out, cmds.front()); cmds.pop(); } + + // if the screen that we paused on is no longer on the stack, unpause + if (paused_screen && !screen_found(paused_screen)) + conf_lua::api::unpause(NULL); + return CR_OK; } diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index ffc2e1ca8..57dbf7244 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -48,27 +48,10 @@ command_result df_createitem (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("createitem", "Create arbitrary items.", df_createitem, false, - "Syntax: createitem [count]\n" - " - Item token for what you wish to create, as specified in custom\n" - " reactions. If the item has no subtype, omit the :NONE.\n" - " - The material you want the item to be made of, as specified\n" - " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" - " PET, and EGG, replace this with a creature ID and caste.\n" - " For PLANT_GROWTH, replace this with a plant ID and growth ID.\n" - " [count] - How many of the item you wish to create.\n" - "\n" - "To obtain the item and material of an existing item, run \n" - "'createitem inspect' with that item selected in-game.\n" - "\n" - "To use this command, you must select which unit will create the items.\n" - "By default, items created will be placed at that unit's feet.\n" - "To change this, run 'createitem '.\n" - "Valid destinations:\n" - "* floor - Place items on floor beneath maker's feet.\n" - "* item - Place items inside selected container.\n" - "* building - Place items inside selected building.\n" - )); + commands.push_back( + PluginCommand("createitem", + "Create arbitrary items.", + df_createitem)); return CR_OK; } @@ -273,7 +256,7 @@ command_result df_createitem (color_ostream &out, vector & parameters) if (parameters.size() == 3) { - stringstream ss(parameters[2]); + std::stringstream ss(parameters[2]); ss >> count; if (count < 1) { diff --git a/plugins/cursecheck.cpp b/plugins/cursecheck.cpp index 69cfbc275..007c81228 100644 --- a/plugins/cursecheck.cpp +++ b/plugins/cursecheck.cpp @@ -79,9 +79,10 @@ command_result cursecheck (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("cursecheck", + commands.push_back(PluginCommand( + "cursecheck", "Check for cursed creatures (undead, necromancers...)", - cursecheck, false )); + cursecheck)); return CR_OK; } @@ -146,6 +147,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) Gui::getCursorCoords(cursorX,cursorY,cursorZ); bool giveDetails = false; + bool giveUnitID = false; bool giveNick = false; bool ignoreDead = true; bool verbose = false; @@ -162,6 +164,7 @@ command_result cursecheck (color_ostream &out, vector & parameters) " By default dead and passive creatures (aka really dead) are ignored.\n" "Options:\n" " detail - show details (name and age shown ingame might differ)\n" + " ids - add creature and race IDs to be show on the output\n" " nick - try to set cursetype as nickname (does not always work)\n" " all - include dead and passive creatures\n" " verbose - show all curse tags (if you really want to know it all)\n" @@ -170,6 +173,8 @@ command_result cursecheck (color_ostream &out, vector & parameters) } if(parameters[i] == "detail") giveDetails = true; + if(parameters[i] == "ids") + giveUnitID = true; if(parameters[i] == "nick") giveNick = true; if(parameters[i] == "all") @@ -269,6 +274,11 @@ command_result cursecheck (color_ostream &out, vector & parameters) << bitfield_to_string(unit->curse.add_tags2) << endl; } } + + if (giveUnitID) + { + out.print("Creature %d, race %d (%x)\n", unit->id, unit->race, unit->race); + } } } diff --git a/plugins/cxxrandom.cpp b/plugins/cxxrandom.cpp index 159edaefc..12f043214 100644 --- a/plugins/cxxrandom.cpp +++ b/plugins/cxxrandom.cpp @@ -37,94 +37,82 @@ DFHACK_PLUGIN("cxxrandom"); #define PLUGIN_VERSION 2.0 color_ostream *cout = nullptr; -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { cout = &out; return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ +DFhackCExport command_result plugin_shutdown (color_ostream &out) { return CR_OK; } -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { return CR_OK; } +#define EK_ID_BASE (1ll << 40) class EnginesKeeper { private: - EnginesKeeper() {} - std::unordered_map m_engines; - uint16_t counter = 0; + EnginesKeeper() = default; + std::unordered_map m_engines; + uint64_t id_counter = EK_ID_BASE; public: - static EnginesKeeper& Instance() - { + static EnginesKeeper& Instance() { static EnginesKeeper instance; return instance; } - uint16_t NewEngine( uint64_t seed ) - { + uint64_t NewEngine( uint64_t seed ) { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_engines.count(id) == 0); std::mt19937_64 engine( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); - m_engines[++counter] = engine; - return counter; + m_engines[id] = engine; + return id; } - void DestroyEngine( uint16_t id ) - { + void DestroyEngine( uint64_t id ) { m_engines.erase( id ); } - void NewSeed( uint16_t id, uint64_t seed ) - { + void NewSeed( uint64_t id, uint64_t seed ) { CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); m_engines[id].seed( seed != 0 ? seed : std::chrono::system_clock::now().time_since_epoch().count() ); } - std::mt19937_64& RNG( uint16_t id ) - { + std::mt19937_64& RNG( uint64_t id ) { CHECK_INVALID_ARGUMENT( m_engines.find( id ) != m_engines.end() ); return m_engines[id]; } }; -uint16_t GenerateEngine( uint64_t seed ) -{ +uint64_t GenerateEngine( uint64_t seed ) { return EnginesKeeper::Instance().NewEngine( seed ); } -void DestroyEngine( uint16_t id ) -{ +void DestroyEngine( uint64_t id ) { EnginesKeeper::Instance().DestroyEngine( id ); } -void NewSeed( uint16_t id, uint64_t seed ) -{ +void NewSeed( uint64_t id, uint64_t seed ) { EnginesKeeper::Instance().NewSeed( id, seed ); } -int rollInt(uint16_t id, int min, int max) -{ +int rollInt(uint64_t id, int min, int max) { std::uniform_int_distribution ND(min, max); return ND(EnginesKeeper::Instance().RNG(id)); } -double rollDouble(uint16_t id, double min, double max) -{ +double rollDouble(uint64_t id, double min, double max) { std::uniform_real_distribution ND(min, max); return ND(EnginesKeeper::Instance().RNG(id)); } -double rollNormal(uint16_t id, double mean, double stddev) -{ +double rollNormal(uint64_t id, double mean, double stddev) { std::normal_distribution ND(mean, stddev); return ND(EnginesKeeper::Instance().RNG(id)); } -bool rollBool(uint16_t id, float p) -{ +bool rollBool(uint64_t id, float p) { std::bernoulli_distribution ND(p); return ND(EnginesKeeper::Instance().RNG(id)); } @@ -137,118 +125,104 @@ private: std::vector m_numbers; public: NumberSequence(){} - NumberSequence( int64_t start, int64_t end ) - { - for( int64_t i = start; i <= end; ++i ) - { + NumberSequence( int64_t start, int64_t end ) { + for( int64_t i = start; i <= end; ++i ) { m_numbers.push_back( i ); } } void Add( int64_t num ) { m_numbers.push_back( num ); } - void Reset() { m_numbers.clear(); } - int64_t Next() - { - if(m_position >= m_numbers.size()) - { + void Reset() { m_numbers.clear(); } + int64_t Next() { + if(m_position >= m_numbers.size()) { m_position = 0; } return m_numbers[m_position++]; } - void Shuffle( uint16_t id ) - { - std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG( id ) ); + void Shuffle( uint64_t engID ) { + std::shuffle( std::begin( m_numbers ), std::end( m_numbers ), EnginesKeeper::Instance().RNG(engID)); } - void Print() - { - for( auto v : m_numbers ) - { + void Print() { + for( auto v : m_numbers ) { cout->print( "%" PRId64 " ", v ); } } }; +#define SK_ID_BASE 0 + class SequenceKeeper { private: - SequenceKeeper() {} - std::unordered_map m_sequences; - uint16_t counter = 0; + SequenceKeeper() = default; + std::unordered_map m_sequences; + uint64_t id_counter = SK_ID_BASE; public: - static SequenceKeeper& Instance() - { + static SequenceKeeper& Instance() { static SequenceKeeper instance; return instance; } - uint16_t MakeNumSequence( int64_t start, int64_t end ) - { - m_sequences[++counter] = NumberSequence( start, end ); - return counter; - } - uint16_t MakeNumSequence() - { - m_sequences[++counter] = NumberSequence(); - return counter; - } - void DestroySequence( uint16_t id ) - { - m_sequences.erase( id ); - } - void AddToSequence( uint16_t id, int64_t num ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - m_sequences[id].Add( num ); - } - void Shuffle( uint16_t id, uint16_t rng_id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - m_sequences[id].Shuffle( rng_id ); - } - int64_t NextInSequence( uint16_t id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - return m_sequences[id].Next(); - } - void PrintSequence( uint16_t id ) - { - CHECK_INVALID_ARGUMENT( m_sequences.find( id ) != m_sequences.end() ); - auto seq = m_sequences[id]; + uint64_t MakeNumSequence( int64_t start, int64_t end ) { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_sequences.count(id) == 0); + m_sequences[id] = NumberSequence(start, end); + return id; + } + uint64_t MakeNumSequence() { + auto id = ++id_counter; + CHECK_INVALID_ARGUMENT(m_sequences.count(id) == 0); + m_sequences[id] = NumberSequence(); + return id; + } + void DestroySequence( uint64_t seqID ) { + m_sequences.erase(seqID); + } + void AddToSequence(uint64_t seqID, int64_t num ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + m_sequences[seqID].Add(num); + } + void Shuffle(uint64_t seqID, uint64_t engID ) { + uint64_t sid = seqID >= SK_ID_BASE ? seqID : engID; + uint64_t eid = engID >= EK_ID_BASE ? engID : seqID; + CHECK_INVALID_ARGUMENT(m_sequences.find(sid) != m_sequences.end()); + m_sequences[sid].Shuffle(eid); + } + int64_t NextInSequence( uint64_t seqID ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + return m_sequences[seqID].Next(); + } + void PrintSequence( uint64_t seqID ) { + CHECK_INVALID_ARGUMENT(m_sequences.find(seqID) != m_sequences.end()); + auto seq = m_sequences[seqID]; seq.Print(); } }; -uint16_t MakeNumSequence( int64_t start, int64_t end ) -{ - if( start == end ) - { +uint64_t MakeNumSequence( int64_t start, int64_t end ) { + if (start == end) { return SequenceKeeper::Instance().MakeNumSequence(); } - return SequenceKeeper::Instance().MakeNumSequence( start, end ); + return SequenceKeeper::Instance().MakeNumSequence(start, end); } -void DestroyNumSequence( uint16_t id ) -{ - SequenceKeeper::Instance().DestroySequence( id ); +void DestroyNumSequence( uint64_t seqID ) { + SequenceKeeper::Instance().DestroySequence(seqID); } -void AddToSequence( uint16_t id, int64_t num ) -{ - SequenceKeeper::Instance().AddToSequence( id, num ); +void AddToSequence(uint64_t seqID, int64_t num ) { + SequenceKeeper::Instance().AddToSequence(seqID, num); } -void ShuffleSequence( uint16_t rngID, uint16_t id ) -{ - SequenceKeeper::Instance().Shuffle( id, rngID ); +void ShuffleSequence(uint64_t seqID, uint64_t engID ) { + SequenceKeeper::Instance().Shuffle(seqID, engID); } -int64_t NextInSequence( uint16_t id ) -{ - return SequenceKeeper::Instance().NextInSequence( id ); +int64_t NextInSequence( uint64_t seqID ) { + return SequenceKeeper::Instance().NextInSequence(seqID); } -void DebugSequence( uint16_t id ) -{ - SequenceKeeper::Instance().PrintSequence( id ); +void DebugSequence( uint64_t seqID ) { + SequenceKeeper::Instance().PrintSequence(seqID); } diff --git a/plugins/debug.cpp b/plugins/debug.cpp index 30a5028f8..f4a22b8d1 100644 --- a/plugins/debug.cpp +++ b/plugins/debug.cpp @@ -104,85 +104,13 @@ JsonArchive& operator>>(JsonArchive& ar, const serialization::nvp& target) static constexpr auto defaultRegex = std::regex::optimize | std::regex::nosubs | std::regex::collate; -static const char* const commandHelp = - " Manage runtime debug print filters.\n" - "\n" - " debugfilter category [ []]\n" - " List categories matching regular expressions.\n" - " debugfilter filter []\n" - " List active filters or show detailed information for a filter.\n" - " debugfilter set [persistent] [ []]\n" - " Set a filter level to categories matching regular expressions.\n" - " debugfilter unset [ ...]\n" - " Unset filters matching space separated list of ids from 'filter'.\n" - " debugfilter disable [ ...]\n" - " Disable filters matching space separated list of ids from 'filter'.\n" - " debugfilter enable [ ...]\n" - " Enable filters matching space separated list of ids from 'filter'.\n" - " debugfilter header [enable] | [disable] [ ...]\n" - " Control which header metadata is shown along with each log message.\n" - " debugfilter help []\n" - " Show detailed help for a command or this help.\n"; -static const char* const commandCategory = - " category [ []]\n" - " List categories with optional filters. Parameters are passed to\n" - " std::regex to limit which once are shown. The first regular\n" - " expression is used to match the category and the second is used\n" - " to match the plugin name.\n"; -static const char* const commandSet = - " set [persistent] [ []]\n" - " Set filtering level for matching categories. 'level' must be one of\n" - " trace, debug, info, warning and error. The 'level' parameter sets\n" - " the lowest message level that will be shown. The command doesn't\n" - " allow filters to disable any error messages.\n" - " Default filter life time is until Dwarf Fortress process exists or\n" - " plugin is unloaded. Passing 'persistent' as second parameter tells\n" - " the plugin to store the filter to dfhack-config. Stored filters\n" - " will be active until always when the plugin is loaded. 'unset'\n" - " command can be used to remove persistent filters.\n" - " Filters are applied FIFO order. The latest filter will override any\n" - " older filter that also matches.\n"; -static const char* const commandFilters = - " filter []\n" - " Show the list of active filters. The first column is 'id' which can\n" - " be used to deactivate filters using 'unset' command.\n" - " Filters are printed in same order as applied - the oldest first.\n"; -static const char* const commandUnset = - " unset [ ...]\n" - " 'unset' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n"; -static const char* const commandDisable = - " disable [ ...]\n" - " 'disable' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n" - " 'disable' will print red filters that were already disabled.\n"; -static const char* const commandEnable = - " enable [ ...]\n" - " 'enable' takes space separated list of filter ids from 'filter'.\n" - " It will reset any matching category back to the default 'warning'\n" - " level or any other still active matching filter level.\n" - " 'enable' will print red filters that were already enabled.\n"; -static const char* const commandHeader = - " header [enable] | [disable] [ ...]\n" - " 'header' allows you to customize what metadata is displayed with\n" - " each log message. Run it without parameters to see the list of\n" - " configurable elements. Include an 'enable' or 'disable' keyword to\n" - " change specific elements.\n"; -static const char* const commandHelpDetails = - " help []\n" - " Show help for any of subcommands. Without any parameters it shows\n" - " short help for all subcommands.\n"; - //! Helper type to hold static dispatch table for subcommands struct CommandDispatch { //! Store handler function pointer and help message for commands struct Command { using handler_t = command_result(*)(color_ostream&,std::vector&); - Command(handler_t handler, const char* help) : - handler_(handler), - help_(help) + Command(handler_t handler) : + handler_(handler) {} command_result operator()(color_ostream& out, @@ -193,12 +121,8 @@ struct CommandDispatch { handler_t handler() const noexcept { return handler_; } - - const char* help() const noexcept - { return help_; } private: handler_t handler_; - const char* help_; }; using dispatch_t = const std::map; //! Name to handler function and help message mapping @@ -511,6 +435,8 @@ private: DebugManager::categorySignal_t::Connection connection_; }; +constexpr const char* FilterManager::configPath; + FilterManager::~FilterManager() { } @@ -1123,28 +1049,14 @@ static command_result configureHeader(color_ostream& out, using DFHack::debugPlugin::CommandDispatch; -static command_result printHelp(color_ostream& out, - std::vector& parameters) -{ - const char* help = commandHelp; - auto iter = CommandDispatch::dispatch.end(); - if (1u < parameters.size()) - iter = CommandDispatch::dispatch.find(parameters[1]); - if (iter != CommandDispatch::dispatch.end()) - help = iter->second.help(); - out << help << std::flush; - return CR_OK; -} - CommandDispatch::dispatch_t CommandDispatch::dispatch { - {"category", {listCategories,commandCategory}}, - {"filter", {listFilters,commandFilters}}, - {"set", {setFilter,commandSet}}, - {"unset", {unsetFilter,commandUnset}}, - {"enable", {enableFilter,commandEnable}}, - {"disable", {disableFilter,commandDisable}}, - {"header", {configureHeader,commandHeader}}, - {"help", {printHelp,commandHelpDetails}}, + {"category", {listCategories}}, + {"filter", {listFilters}}, + {"set", {setFilter}}, + {"unset", {unsetFilter}}, + {"enable", {enableFilter}}, + {"disable", {disableFilter}}, + {"header", {configureHeader}}, }; //! Dispatch command handling to the subcommand or help @@ -1154,13 +1066,14 @@ static command_result commandDebugFilter(color_ostream& out, DEBUG(command,out).print("debugfilter %s, parameter count %zu\n", parameters.size() > 0 ? parameters[0].c_str() : "", parameters.size()); - auto handler = printHelp; auto iter = CommandDispatch::dispatch.end(); if (0u < parameters.size()) iter = CommandDispatch::dispatch.find(parameters[0]); - if (iter != CommandDispatch::dispatch.end()) - handler = iter->second.handler(); - return (handler)(out, parameters); + if (iter != CommandDispatch::dispatch.end()) { + iter->second.handler()(out, parameters); + return CR_OK; + } + return CR_WRONG_USAGE; } } } /* namespace debug */ @@ -1171,9 +1084,7 @@ DFhackCExport DFHack::command_result plugin_init(DFHack::color_ostream& out, commands.emplace_back( "debugfilter", "Manage runtime debug print filters", - DFHack::debugPlugin::commandDebugFilter, - false, - DFHack::debugPlugin::commandHelp); + DFHack::debugPlugin::commandDebugFilter); auto& filMan = DFHack::debugPlugin::FilterManager::getInstance(); DFHack::command_result rv = DFHack::CR_OK; if ((rv = filMan.loadConfig(out)) != DFHack::CR_OK) diff --git a/plugins/deramp.cpp b/plugins/deramp.cpp index 62679cae0..1c397242b 100644 --- a/plugins/deramp.cpp +++ b/plugins/deramp.cpp @@ -109,13 +109,11 @@ command_result df_deramp (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "deramp", "Replace all ramps marked for removal with floors.", - df_deramp, false, - " If there are any ramps designated for removal, they will be instantly\n" - " removed. Any ramps that don't have their counterpart will also be removed\n" - " (fixes bugs with caveins)\n" - )); + commands.push_back( + PluginCommand( + "deramp", + "Removes all ramps designated for removal from the map.", + df_deramp)); return CR_OK; } diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 6d15f09dd..d1b84614f 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -13,7 +13,6 @@ dfhack_plugin(eventExample eventExample.cpp) dfhack_plugin(frozen frozen.cpp) dfhack_plugin(kittens kittens.cpp LINK_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) dfhack_plugin(memview memview.cpp memutils.cpp LINK_LIBRARIES lua) -dfhack_plugin(notes notes.cpp) dfhack_plugin(onceExample onceExample.cpp) dfhack_plugin(renderer-msg renderer-msg.cpp) dfhack_plugin(rprobe rprobe.cpp) diff --git a/plugins/devel/itemhacks.cpp b/plugins/devel/itemhacks.cpp index b2c1918c6..66ba8e45f 100644 --- a/plugins/devel/itemhacks.cpp +++ b/plugins/devel/itemhacks.cpp @@ -10,6 +10,7 @@ using std::vector; using std::string; +using std::endl; using namespace DFHack; ////////////////////// diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index fce924aff..89ece51b6 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -28,6 +28,7 @@ using std::vector; using std::string; +using std::endl; using namespace DFHack; DFHACK_PLUGIN("kittens"); @@ -135,13 +136,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) last_designation[2] = desig_z; out.print("Designation: %d %d %d\n",desig_x, desig_y, desig_z); } - int mouse_x, mouse_y; - Gui::getMousePos(mouse_x,mouse_y); - if(mouse_x != last_mouse[0] || mouse_y != last_mouse[1]) + df::coord mousePos = Gui::getMousePos(); + if(mousePos.x != last_mouse[0] || mousePos.y != last_mouse[1]) { - last_mouse[0] = mouse_x; - last_mouse[1] = mouse_y; - out.print("Mouse: %d %d\n",mouse_x, mouse_y); + last_mouse[0] = mousePos.x; + last_mouse[1] = mousePos.y; + out.print("Mouse: %d %d\n",mousePos.x, mousePos.y); } } return CR_OK; diff --git a/plugins/devel/notes.cpp b/plugins/devel/notes.cpp deleted file mode 100644 index 33b768af7..000000000 --- a/plugins/devel/notes.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include "Core.h" -#include -#include -#include -#include -#include -#include - -using std::vector; -using std::string; -using namespace DFHack; - -command_result df_notes (color_ostream &out, vector & parameters); - -DFHACK_PLUGIN("notes"); - -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand("dumpnotes", - "Dumps in-game notes", - df_notes)); - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - return CR_OK; -} - -command_result df_notes (color_ostream &con, vector & parameters) -{ - CoreSuspender suspend; - - DFHack::Notes * note_mod = Core::getInstance().getNotes(); - std::vector* note_list = note_mod->notes; - - if (note_list == NULL) - { - con.printerr("Notes are not supported under this version of DF.\n"); - return CR_OK; - } - - if (note_list->empty()) - { - con << "There are no notes." << std::endl; - return CR_OK; - } - - - for (size_t i = 0; i < note_list->size(); i++) - { - t_note* note = (*note_list)[i]; - - con.print("Note %p at: %d/%d/%d\n",note, note->x, note->y, note->z); - con.print("Note id: %d\n", note->id); - con.print("Note symbol: '%c'\n", note->symbol); - - if (note->name.length() > 0) - con << "Note name: " << (note->name) << std::endl; - if (note->text.length() > 0) - con << "Note text: " << (note->text) << std::endl; - - if (note->unk1 != 0) - con.print("unk1: %x\n", note->unk1); - if (note->unk2 != 0) - con.print("unk2: %x\n", note->unk2); - - con << std::endl; - } - - return CR_OK; -} diff --git a/plugins/devel/renderer-msg.cpp b/plugins/devel/renderer-msg.cpp index e6a33114f..1f26886c6 100644 --- a/plugins/devel/renderer-msg.cpp +++ b/plugins/devel/renderer-msg.cpp @@ -23,13 +23,33 @@ struct scdata { }; struct renderer_msg : public Renderer::renderer_wrap { - virtual void update_tile (int32_t x, int32_t y) { - static std::string str = std::string("DFHack: ") + plugin_name + " active"; - Screen::paintString(Screen::Pen(' ', 9, 0), 0, gps->dimy - 1, str); - for (int32_t i = 0; i < gps->dimx; ++i) - ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + bool message_dirty = true; // force redraw when renderer is installed + + virtual void update_tile(int32_t x, int32_t y) override { + draw_message(); renderer_wrap::update_tile(x, y); - }; + } + + virtual void update_all() override { + draw_message(); + renderer_wrap::update_all(); + } + + virtual void render() override { + message_dirty = true; + renderer_wrap::render(); + } + + void draw_message() { + if (message_dirty) { + static std::string str = std::string("DFHack: ") + plugin_name + " active"; + Screen::paintString(Screen::Pen(' ', COLOR_LIGHTCYAN, COLOR_GREEN), 0, gps->dimy - 1, str); + for (int32_t i = 0; i < gps->dimx; ++i) + ((scdata*)screen)[i * gps->dimy + gps->dimy - 1].bg = 2; + + message_dirty = false; + } + } }; DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) diff --git a/plugins/devel/tiles.cpp b/plugins/devel/tiles.cpp index 61640257a..f3b825299 100644 --- a/plugins/devel/tiles.cpp +++ b/plugins/devel/tiles.cpp @@ -6,6 +6,7 @@ #include using std::vector; using std::string; +using std::endl; #include "Core.h" #include diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp deleted file mode 100644 index eb98c181b..000000000 --- a/plugins/dfstream.cpp +++ /dev/null @@ -1,316 +0,0 @@ -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "DataDefs.h" -#include "df/graphic.h" -#include "df/enabler.h" -#include "df/renderer.h" - -#include -#include -#include "PassiveSocket.h" -#include "tinythread.h" - -using namespace DFHack; -using namespace df::enums; - -using std::string; -using std::vector; - -DFHACK_PLUGIN("dfstream"); -REQUIRE_GLOBAL(gps); -REQUIRE_GLOBAL(enabler); - -// Owns the thread that accepts TCP connections and forwards messages to clients; -// has a mutex -class client_pool { - typedef tthread::mutex mutex; - - mutex clients_lock; - std::vector clients; - - // TODO - delete this at some point - tthread::thread * accepter; - - static void accept_clients(void * client_pool_pointer) { - client_pool * p = reinterpret_cast(client_pool_pointer); - CPassiveSocket socket; - socket.Initialize(); - if (socket.Listen((const uint8_t *)"0.0.0.0", 8008)) { - std::cout << "Listening on a socket" << std::endl; - } else { - std::cout << "Not listening: " << socket.GetSocketError() << std::endl; - std::cout << socket.DescribeError() << std::endl; - } - while (true) { - CActiveSocket * client = socket.Accept(); - if (client != 0) { - lock l(*p); - p->clients.push_back(client); - } - } - } - -public: - class lock { - tthread::lock_guard l; - public: - lock(client_pool & p) - : l(p.clients_lock) - { - } - }; - friend class client_pool::lock; - - client_pool() { - accepter = new tthread::thread(accept_clients, this); - } - - // MUST have lock - bool has_clients() { - return !clients.empty(); - } - - // MUST have lock - void add_client(CActiveSocket * sock) { - clients.push_back(sock); - } - - // MUST have lock - void broadcast(const std::string & message) { - unsigned int sz = htonl(message.size()); - for (size_t i = 0; i < clients.size(); ++i) { - clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); - clients[i]->Send((const uint8_t *) message.c_str(), message.size()); - } - } -}; - -// A decorator (in the design pattern sense) of the DF renderer class. -// Sends the screen contents to a client_pool. -class renderer_decorator : public df::renderer { - // the renderer we're decorating - df::renderer * inner; - - // how many frames have passed since we last sent a frame - int framesNotPrinted; - - // set to false in the destructor - bool * alive; - - // clients to which we send the frame - client_pool clients; - - // The following three methods facilitate copying of state to the inner object - void set_to_null() { - screen = NULL; - screentexpos = NULL; - screentexpos_addcolor = NULL; - screentexpos_grayscale = NULL; - screentexpos_cf = NULL; - screentexpos_cbr = NULL; - screen_old = NULL; - screentexpos_old = NULL; - screentexpos_addcolor_old = NULL; - screentexpos_grayscale_old = NULL; - screentexpos_cf_old = NULL; - screentexpos_cbr_old = NULL; - } - - void copy_from_inner() { - screen = inner->screen; - screentexpos = inner->screentexpos; - screentexpos_addcolor = inner->screentexpos_addcolor; - screentexpos_grayscale = inner->screentexpos_grayscale; - screentexpos_cf = inner->screentexpos_cf; - screentexpos_cbr = inner->screentexpos_cbr; - screen_old = inner->screen_old; - screentexpos_old = inner->screentexpos_old; - screentexpos_addcolor_old = inner->screentexpos_addcolor_old; - screentexpos_grayscale_old = inner->screentexpos_grayscale_old; - screentexpos_cf_old = inner->screentexpos_cf_old; - screentexpos_cbr_old = inner->screentexpos_cbr_old; - } - - void copy_to_inner() { - inner->screen = screen; - inner->screentexpos = screentexpos; - inner->screentexpos_addcolor = screentexpos_addcolor; - inner->screentexpos_grayscale = screentexpos_grayscale; - inner->screentexpos_cf = screentexpos_cf; - inner->screentexpos_cbr = screentexpos_cbr; - inner->screen_old = screen_old; - inner->screentexpos_old = screentexpos_old; - inner->screentexpos_addcolor_old = screentexpos_addcolor_old; - inner->screentexpos_grayscale_old = screentexpos_grayscale_old; - inner->screentexpos_cf_old = screentexpos_cf_old; - inner->screentexpos_cbr_old = screentexpos_cbr_old; - } - -public: - renderer_decorator(df::renderer * inner, bool * alive) - : inner(inner) - , framesNotPrinted(0) - , alive(alive) - { - copy_from_inner(); - } - virtual void update_tile(int x, int y) { - copy_to_inner(); - inner->update_tile(x, y); - } - virtual void update_all() { - copy_to_inner(); - inner->update_all(); - } - virtual void render() { - copy_to_inner(); - inner->render(); - - ++framesNotPrinted; - int gfps = enabler->calculated_gfps; - if (gfps == 0) gfps = 1; - // send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds) - if ((framesNotPrinted * 1024) / gfps <= 128) return; - - client_pool::lock lock(clients); - if (!clients.has_clients()) return; - framesNotPrinted = 0; - std::stringstream frame; - frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n'; - unsigned char * sc_ = gps->screen; - for (int y = 0; y < gps->dimy; ++y) { - unsigned char * sc = sc_; - for (int x = 0; x < gps->dimx; ++x) { - unsigned char ch = sc[0]; - unsigned char bold = (sc[3] != 0) * 8; - unsigned char translate[] = - { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; - unsigned char fg = translate[(sc[1] + bold) % 16]; - unsigned char bg = translate[sc[2] % 16]*16; - frame.put(ch); - frame.put(fg+bg); - sc += 4*gps->dimy; - } - sc_ += 4; - } - clients.broadcast(frame.str()); - } - virtual void set_fullscreen() { inner->set_fullscreen(); } - virtual void zoom(df::zoom_commands cmd) { - copy_to_inner(); - inner->zoom(cmd); - } - virtual void resize(int w, int h) { - copy_to_inner(); - inner->resize(w, h); - copy_from_inner(); - } - virtual void grid_resize(int w, int h) { - copy_to_inner(); - inner->grid_resize(w, h); - copy_from_inner(); - } - virtual ~renderer_decorator() { - *alive = false; - if (inner) { - copy_to_inner(); - delete inner; - inner = 0; - } - set_to_null(); - } - virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } - virtual bool uses_opengl() { return inner->uses_opengl(); } - - static renderer_decorator * hook(df::renderer *& ptr, bool * alive) { - renderer_decorator * r = new renderer_decorator(ptr, alive); - ptr = r; - return r; - } - - static void unhook(df::renderer *& ptr, renderer_decorator * dec, color_ostream & out) { - dec->copy_to_inner(); - ptr = dec->inner; - dec->inner = 0; - delete dec; - } -}; - -inline df::renderer *& active_renderer() { - return enabler->renderer; -} - -// This class is a smart pointer around a renderer_decorator. -// It should only be assigned r_d pointers that use the alive-pointer of this -// instance. -// If the r_d has been deleted by an external force, this smart pointer doesn't -// redelete it. -class auto_renderer_decorator { - renderer_decorator * p; -public: - // pass this member to the ctor of renderer_decorator - bool alive; - - auto_renderer_decorator() - : p(0) - { - } - - ~auto_renderer_decorator() { - reset(); - } - - void reset() { - if (*this) { - delete p; - p = 0; - } - } - - operator bool() { - return (p != 0) && alive; - } - - auto_renderer_decorator & operator=(renderer_decorator *p) { - reset(); - this->p = p; - return *this; - } - - renderer_decorator * get() { - return p; - } - - renderer_decorator * operator->() { - return get(); - } -}; - -auto_renderer_decorator decorator; - -DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) -{ - if (!df::renderer::_identity.can_instantiate()) - { - out.printerr("Cannot allocate a renderer\n"); - return CR_OK; - } - if (!decorator) { - decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); - } - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - if (decorator && active_renderer() == decorator.get()) - { - renderer_decorator::unhook(active_renderer(), decorator.get(), out); - } - decorator.reset(); - return CR_OK; -} -// vim:set sw=4 sts=4 et: diff --git a/plugins/dig-now.cpp b/plugins/dig-now.cpp index 78b2b1522..365be3313 100644 --- a/plugins/dig-now.cpp +++ b/plugins/dig-now.cpp @@ -424,26 +424,30 @@ static bool is_smooth_wall(MapExtras::MapCache &map, const DFCoord &pos) { && tileShape(tt) == df::tiletype_shape::WALL; } -static bool is_smooth_wall_or_door(MapExtras::MapCache &map, - const DFCoord &pos) { - if (is_smooth_wall(map, pos)) - return true; - +static bool is_connector(MapExtras::MapCache &map, const DFCoord &pos) { df::building *bld = Buildings::findAtTile(pos); - return bld && bld->getType() == df::building_type::Door; + + return bld && + (bld->getType() == df::building_type::Door || + bld->getType() == df::building_type::Floodgate); +} + +static bool is_smooth_wall_or_connector(MapExtras::MapCache &map, + const DFCoord &pos) { + return is_smooth_wall(map, pos) || is_connector(map, pos); } // adds adjacent smooth walls and doors to the given tdir static TileDirection get_adjacent_smooth_walls(MapExtras::MapCache &map, const DFCoord &pos, TileDirection tdir) { - if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y-1, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y-1, pos.z))) tdir.north = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x, pos.y+1, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x, pos.y+1, pos.z))) tdir.south = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x-1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x-1, pos.y, pos.z))) tdir.west = 1; - if (is_smooth_wall_or_door(map, DFCoord(pos.x+1, pos.y, pos.z))) + if (is_smooth_wall_or_connector(map, DFCoord(pos.x+1, pos.y, pos.z))) tdir.east = 1; return tdir; } @@ -469,7 +473,7 @@ static bool adjust_smooth_wall_dir(MapExtras::MapCache &map, const DFCoord &pos, TileDirection tdir = BLANK_TILE_DIRECTION) { if (!is_smooth_wall(map, pos)) - return false; + return is_connector(map, pos); tdir = ensure_valid_tdir(get_adjacent_smooth_walls(map, pos, tdir)); @@ -838,17 +842,6 @@ static bool get_options(color_ostream &out, return true; } -static void print_help(color_ostream &out) { - auto L = Lua::Core::State; - Lua::StackUnwinder top(L); - - if (!lua_checkstack(L, 1) || - !Lua::PushModulePublic(out, L, "plugins.dig-now", "print_help") || - !Lua::SafeCall(out, L, 0, 0)) { - out.printerr("Failed to load dig-now Lua code\n"); - } -} - bool dig_now_impl(color_ostream &out, const dig_now_options &options) { if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -880,18 +873,18 @@ command_result dig_now(color_ostream &out, std::vector ¶ms) { dig_now_options options; if (!get_options(out, options, params) || options.help) - { - print_help(out); - return options.help ? CR_OK : CR_FAILURE; - } + return CR_WRONG_USAGE; return dig_now_impl(out, options) ? CR_OK : CR_FAILURE; } DFhackCExport command_result plugin_init(color_ostream &, std::vector &commands) { - commands.push_back(PluginCommand( - "dig-now", "Instantly complete dig designations", dig_now, false)); + commands.push_back( + PluginCommand( + "dig-now", + "Instantly complete dig designations.", + dig_now)); return CR_OK; } diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 6c33f206a..945bf3613 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -28,7 +28,6 @@ command_result digv (color_ostream &out, vector & parameters); command_result digvx (color_ostream &out, vector & parameters); command_result digl (color_ostream &out, vector & parameters); command_result diglx (color_ostream &out, vector & parameters); -command_result digauto (color_ostream &out, vector & parameters); command_result digexp (color_ostream &out, vector & parameters); command_result digcircle (color_ostream &out, vector & parameters); command_result digtype (color_ostream &out, vector & parameters); @@ -36,48 +35,42 @@ command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); +REQUIRE_GLOBAL(window_z); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "digv","Dig a whole vein.",digv,Gui::cursor_hotkey, - " Designates a whole vein under the cursor for digging.\n" - "Options:\n" - " x - follow veins through z-levels with stairs.\n" - )); + "digv", + "Dig a whole vein.", + digv, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "digvx","Dig a whole vein, following through z-levels.",digvx,Gui::cursor_hotkey, - " Designates a whole vein under the cursor for digging.\n" - " Also follows the vein between z-levels with stairs, like 'digv x' would.\n" - )); + "digvx", + "Dig a whole vein, following through z-levels.", + digvx, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "digl","Dig layerstone.",digl,Gui::cursor_hotkey, - " Designates layerstone under the cursor for digging.\n" - " Veins will not be touched.\n" - "Options:\n" - " x - follow layer through z-levels with stairs.\n" - " undo - clear designation (can be used together with 'x').\n" - )); + "digl", + "Dig layerstone.", + digl, + Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "diglx","Dig layerstone, following through z-levels.",diglx,Gui::cursor_hotkey, - " Designates layerstone under the cursor for digging.\n" - " Also follows the stone between z-levels with stairs, like 'digl x' would.\n" - )); - commands.push_back(PluginCommand("digexp","Select or designate an exploratory pattern.",digexp)); - commands.push_back(PluginCommand("digcircle","Dig designate a circle (filled or hollow)",digcircle)); - //commands.push_back(PluginCommand("digauto","Mark a tile for continuous digging.",autodig)); - commands.push_back(PluginCommand("digtype", "Dig all veins of a given type.", digtype,Gui::cursor_hotkey, - "For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated.\n" - "If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.\n" - "Options:\n" - " dig\n" - " channel\n" - " ramp\n" - " updown - up/down stairs\n" - " up - up stairs\n" - " down - down stairs\n" - " clear - clear designation\n" - )); + "diglx", + "Dig layerstone, following through z-levels.", + diglx, + Gui::cursor_hotkey)); + commands.push_back(PluginCommand( + "digexp", + "Select or designate an exploratory pattern.", + digexp)); + commands.push_back(PluginCommand( + "digcircle", + "Dig designate a circle (filled or hollow)", + digcircle)); + commands.push_back(PluginCommand( + "digtype", + "Dig all veins of a given type.", + digtype,Gui::cursor_hotkey)); return CR_OK; } @@ -1420,27 +1413,23 @@ command_result digl (color_ostream &out, vector & parameters) return CR_OK; } - -command_result digauto (color_ostream &out, vector & parameters) -{ - return CR_NOT_IMPLEMENTED; -} - command_result digtype (color_ostream &out, vector & parameters) { //mostly copy-pasted from digv int32_t priority = parse_priority(out, parameters); CoreSuspender suspend; - if ( parameters.size() > 1 ) + + if (!Maps::IsValid()) { - out.printerr("Too many parameters.\n"); + out.printerr("Map is not available!\n"); return CR_FAILURE; } - int32_t targetDigType; - if ( parameters.size() == 1 ) - { - string parameter = parameters[0]; + uint32_t xMax,yMax,zMax; + Maps::getSize(xMax,yMax,zMax); + + int32_t targetDigType = -1; + for (string parameter : parameters) { if ( parameter == "clear" ) targetDigType = tile_dig_designation::No; else if ( parameter == "dig" ) @@ -1455,26 +1444,16 @@ command_result digtype (color_ostream &out, vector & parameters) targetDigType = tile_dig_designation::DownStair; else if ( parameter == "up" ) targetDigType = tile_dig_designation::UpStair; + else if ( parameter == "-z" ) + zMax = *window_z + 1; else { - out.printerr("Invalid parameter.\n"); + out.printerr("Invalid parameter: '%s'.\n", parameter.c_str()); return CR_FAILURE; } } - else - { - targetDigType = -1; - } - - if (!Maps::IsValid()) - { - out.printerr("Map is not available!\n"); - return CR_FAILURE; - } int32_t cx, cy, cz; - uint32_t xMax,yMax,zMax; - Maps::getSize(xMax,yMax,zMax); uint32_t tileXMax = xMax * 16; uint32_t tileYMax = yMax * 16; Gui::getCursorCoords(cx,cy,cz); diff --git a/plugins/digFlood.cpp b/plugins/digFlood.cpp index 459a12c11..7370d3fe5 100644 --- a/plugins/digFlood.cpp +++ b/plugins/digFlood.cpp @@ -51,26 +51,9 @@ DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) { DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "digFlood", "Automatically dig out veins as you discover them.", - digFlood, false, - "Example:\n" - " digFlood 0\n" - " disable plugin\n" - " digFlood 1\n" - " enable plugin\n" - " digFlood 0 MICROCLINE COAL_BITUMINOUS 1\n" - " disable plugin and remove microcline and bituminous coal from being monitored, then re-enable plugin\n" - " digFlood 1 MICROCLINE 0 COAL_BITUMINOUS 1\n" - " do monitor microcline, don't monitor COAL_BITUMINOUS, then enable plugin\n" - " digFlood CLEAR\n" - " remove all inorganics from monitoring\n" - " digFlood digAll1\n" - " enable digAll mode: dig any vein, regardless of the monitor list\n" - " digFlood digAll0\n" - " disable digAll mode\n" - "\n" - "Note that while order matters, multiple commands can be sequenced in one line. It is recommended to alter your save-specific regionX/raw/onLoad.init or global onLoadWorld.init file so that you won't have to type in every mineral type you want to dig every time you start the game. Material names are case sensitive.\n" - )); + "digFlood", + "Automatically dig out veins as you discover them.", + digFlood)); return CR_OK; } diff --git a/plugins/diggingInvaders/diggingInvaders.cpp b/plugins/diggingInvaders/diggingInvaders.cpp index 573c85320..457a00e50 100644 --- a/plugins/diggingInvaders/diggingInvaders.cpp +++ b/plugins/diggingInvaders/diggingInvaders.cpp @@ -120,28 +120,9 @@ static int32_t jobDelayDefault[] = { DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "diggingInvaders", "Makes invaders dig to your dwarves.", - diggingInvadersCommand, false, /* true means that the command can't be used from non-interactive user interface */ - " diggingInvaders 0\n disables the plugin\n" - " diggingInvaders 1\n enables the plugin\n" - " diggingInvaders enable\n enables the plugin\n" - " diggingInvaders disable\n disables the plugin\n" - " diggingInvaders add GOBLIN\n registers the race GOBLIN as a digging invader. Case-sensitive.\n" - " diggingInvaders remove GOBLIN\n unregisters the race GOBLIN as a digging invader. Case-sensitive.\n" - " diggingInvaders setCost GOBLIN walk n\n sets the walk cost in the path algorithm for the race GOBLIN\n" - " diggingInvaders setCost GOBLIN destroyBuilding n\n" - " diggingInvaders setCost GOBLIN dig n\n" - " diggingInvaders setCost GOBLIN destroyRoughConstruction n\n rough constructions are made from boulders\n" - " diggingInvaders setCost GOBLIN destroySmoothConstruction n\n smooth constructions are made from blocks or bars instead of boulders\n" - " diggingInvaders setDelay GOBLIN destroyBuilding n\n adds to the job_completion_timer of destroy building jobs that are assigned to invaders\n" - " diggingInvaders setDelay GOBLIN dig n\n" - " diggingInvaders setDelay GOBLIN destroyRoughConstruction n\n" - " diggingInvaders setDelay GOBLIN destroySmoothConstruction n\n" - " diggingInvaders now\n makes invaders try to dig now, if plugin is enabled\n" - " diggingInvaders clear\n clears all digging invader races\n" - " diggingInvaders edgesPerTick n\n makes the pathfinding algorithm work on at most n edges per tick. Set to 0 or lower to make it unlimited." -// " diggingInvaders\n Makes invaders try to dig now.\n" - )); + "diggingInvaders", + "Makes invaders dig to your dwarves.", + diggingInvadersCommand)); //*df::global::debug_showambush = true; return CR_OK; diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 84e9b9abe..1a453e0d5 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -2075,20 +2075,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" - " Start monitoring \n" - " can be \"work\", \"misery\", \"weather\", or \"all\"\n" - "dwarfmonitor disable \n" - " as above\n\n" - "dwarfmonitor stats\n" - " Show statistics summary\n" - "dwarfmonitor prefs\n" - " Show dwarf preferences summary\n\n" - "dwarfmonitor reload\n" - " Reload configuration file (dfhack-config/dwarfmonitor.json)\n" - )); + "dwarfmonitor", + "Measure fort happiness and efficiency.", + dwarfmonitor_cmd)); dm_lua::state=Lua::Core::State; if (dm_lua::state == NULL) diff --git a/plugins/dwarfvet.cpp b/plugins/dwarfvet.cpp index 30308bc57..356fda238 100644 --- a/plugins/dwarfvet.cpp +++ b/plugins/dwarfvet.cpp @@ -370,14 +370,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "embark-assistant", "Embark site selection support.", - embark_assistant, false, /* false means that the command can be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command starts the embark-assist plugin that provides embark site\n" - " selection help. It has to be called while the pre-embark screen is\n" - " displayed and shows extended (and correct(?)) resource information for\n" - " the embark rectangle as well as normally undisplayed sites in the\n" - " current embark region. It also has a site selection tool with more\n" - " options than DF's vanilla search tool. For detailed help invoke the\n" - " in game info screen. Prefers 46 lines to display properly.\n" - )); + "embark-assistant", + "Embark site selection support.", + embark_assistant)); return CR_OK; } diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index 52348ea33..011a65fa3 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -475,7 +475,9 @@ namespace embark_assist { return; // We're at the world edge, so no incursions from the outside. } - if (!&survey_results->at(fetch_x).at(fetch_y).surveyed) { + if (!survey_results->at(fetch_x).at(fetch_y).surveyed) { + // If the data has been collected, incursion processing should be performed to evaluate whether a match actually is present. + // but if it hasn't we need to return with a failed_match *failed_match = true; return; } diff --git a/plugins/embark-tools.cpp b/plugins/embark-tools.cpp index b87295020..924def798 100644 --- a/plugins/embark-tools.cpp +++ b/plugins/embark-tools.cpp @@ -752,20 +752,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector second->getId() + ": " + iter->second->getDesc() + "\n"); - } commands.push_back(PluginCommand( "embark-tools", - "A collection of embark tools", - embark_tools_cmd, - false, - help.c_str() - )); + "Extend the embark screen functionality.", + embark_tools_cmd)); return CR_OK; } diff --git a/plugins/examples/persistent_per_save_example.cpp b/plugins/examples/persistent_per_save_example.cpp new file mode 100644 index 000000000..5a7bf5224 --- /dev/null +++ b/plugins/examples/persistent_per_save_example.cpp @@ -0,0 +1,166 @@ +// This template is appropriate for plugins that periodically check game state +// and make some sort of automated change. These types of plugins typically +// provide a command that can be used to configure the plugin behavior and +// require a world to be loaded before they can function. This kind of plugin +// should persist its state in the savegame and auto-re-enable itself when a +// savegame that had this plugin enabled is loaded. + +#include +#include + +#include "df/world.h" + +#include "Core.h" +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/Persistence.h" +#include "modules/World.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("persistent_per_save_example"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +// logging levels can be dynamically controlled with the `debugfilter` command. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(persistent_per_save_example, status, DebugCategory::LINFO); + // for logging during the periodic scan + DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; +enum ConfigValues { + CONFIG_IS_ENABLED = 0, + CONFIG_CYCLE_TICKS = 1, +}; +static int get_config_val(int index) { + if (!config.isValid()) + return -1; + return config.ival(index); +} +static bool get_config_bool(int index) { + return get_config_val(index) == 1; +} +static void set_config_val(int index, int value) { + if (config.isValid()) + config.ival(index) = value; +} +static void set_config_bool(int index, bool value) { + set_config_val(index, value ? 1 : 0); +} + +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static command_result do_command(color_ostream &out, vector ¶meters); +static void do_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(status,out).print("initializing %s\n", plugin_name); + + // provide a configuration interface for the plugin + commands.push_back(PluginCommand( + plugin_name, + "Short (~54 character) description of command.", + do_command)); + + return CR_OK; +} + +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 != is_enabled) { + is_enabled = enable; + DEBUG(status,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + } else { + DEBUG(status,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + is_enabled ? "enabled" : "disabled"); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(status,out).print("shutting down %s\n", plugin_name); + + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + config = World::GetPersistentData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(status,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentData(CONFIG_KEY); + set_config_bool(CONFIG_IS_ENABLED, is_enabled); + set_config_val(CONFIG_CYCLE_TICKS, 6000); + } + + // we have to copy our enabled flag into the global plugin variable, but + // all the other state we can directly read/modify from the persistent + // data structure. + is_enabled = get_config_bool(CONFIG_IS_ENABLED); + DEBUG(status,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + 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 (is_enabled && world->frame_counter - cycle_timestamp >= get_config_val(CONFIG_CYCLE_TICKS)) + do_cycle(out); + return CR_OK; +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + // be sure to suspend the core if any DF state is read or modified + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("Cannot run %s without a loaded world.\n", plugin_name); + return CR_FAILURE; + } + + // TODO: configuration logic + // simple commandline parsing can be done in C++, but there are lua libraries + // that can easily handle more complex commandlines. see the blueprint plugin + // for an example. + + return CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +static void do_cycle(color_ostream &out) { + // mark that we have recently run + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + // TODO: logic that runs every get_config_val(CONFIG_CYCLE_TICKS) ticks +} diff --git a/plugins/examples/simple_command_example.cpp b/plugins/examples/simple_command_example.cpp new file mode 100644 index 000000000..7b12a1271 --- /dev/null +++ b/plugins/examples/simple_command_example.cpp @@ -0,0 +1,41 @@ +// This template is appropriate for plugins that simply provide one or more +// commands, but don't need to be "enabled" to function. + +#include +#include + +#include "Debug.h" +#include "PluginManager.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("simple_command_example"); + +namespace DFHack { + DBG_DECLARE(simple_command_example, log); +} + +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); + + commands.push_back(PluginCommand( + plugin_name, + "Short (~54 character) description of command.", + do_command)); + + return CR_OK; +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + // be sure to suspend the core if any DF state is read or modified + CoreSuspender suspend; + + // TODO: command logic + + return CR_OK; +} diff --git a/plugins/examples/skeleton.cpp b/plugins/examples/skeleton.cpp new file mode 100644 index 000000000..539d84b1a --- /dev/null +++ b/plugins/examples/skeleton.cpp @@ -0,0 +1,193 @@ +// This is an example plugin that documents and implements all the plugin +// callbacks and features. You can include it in the regular build by setting +// the BUILD_SKELETON option in CMake to ON. Play with loading and unloading +// the plugin in various game states (e.g. with and without a world loaded), +// and see the debug messages get printed to the console. +// +// See the other example plugins in this directory for plugins that are +// configured for specific use cases (but don't come with as many comments as +// this one does). + +#include +#include + +#include "df/world.h" + +#include "Core.h" +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/Persistence.h" +#include "modules/World.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +// Expose the plugin name to the DFHack core, as well as metadata like the +// DFHack version that this plugin was compiled with. This macro provides a +// variable for the plugin name as const char * plugin_name. +// The name provided must correspond to the filename -- +// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case +DFHACK_PLUGIN("skeleton"); + +// The identifier declared with this macro (i.e. is_enabled) is used to track +// whether the plugin is in an "enabled" state. If you don't need enablement +// for your plugin, you don't need this line. This variable will also be read +// by the `plug` builtin command; when true the plugin will be shown as enabled. +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +// Any globals a plugin requires (e.g. world) should be listed here. +// For example, this line expands to "using df::global::world" and prevents the +// plugin from being loaded if df::global::world is null (i.e. missing from +// symbols.xml). +REQUIRE_GLOBAL(world); + +// logging levels can be dynamically controlled with the `debugfilter` command. +// Actual plugins will likely want to set the default level to LINFO or LWARNING +// instead of the LDEBUG used here. +namespace DFHack { + // for configuration-related logging + DBG_DECLARE(skeleton, status, DebugCategory::LDEBUG); + // for plugin_onupdate logging + DBG_DECLARE(skeleton, onupdate, DebugCategory::LDEBUG); + // for command-related logging + DBG_DECLARE(skeleton, command, DebugCategory::LDEBUG); +} + +static command_result command_callback1(color_ostream &out, vector ¶meters); + +// run when the plugin is loaded +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(status,out).print("initializing %s\n", plugin_name); + + // For in-tree plugins, don't use the "usage" parameter of PluginCommand. + // Instead, add an .rst file with the same name as the plugin to the + // docs/plugins/ directory. + commands.push_back(PluginCommand( + "skeleton", + "Short (~54 character) description of command.", // to use one line in the ``[DFHack]# ls`` output + command_callback1)); + return CR_OK; +} + +// run when the plugin is unloaded +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + DEBUG(status,out).print("shutting down %s\n", plugin_name); + + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; + +} + +// run when the `enable` or `disable` command is run with this plugin name as +// an argument +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + DEBUG(status,out).print("%s from the API\n", enable ? "enabled" : "disabled"); + + // you have to maintain the state of the is_enabled variable yourself. it + // doesn't happen automatically. + is_enabled = enable; + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + switch (event) { + case SC_UNKNOWN: + DEBUG(status,out).print("game state changed: SC_UNKNOWN\n"); + break; + case SC_WORLD_LOADED: + DEBUG(status,out).print("game state changed: SC_WORLD_LOADED\n"); + break; + case SC_WORLD_UNLOADED: + DEBUG(status,out).print("game state changed: SC_WORLD_UNLOADED\n"); + break; + case SC_MAP_LOADED: + DEBUG(status,out).print("game state changed: SC_MAP_LOADED\n"); + break; + case SC_MAP_UNLOADED: + DEBUG(status,out).print("game state changed: SC_MAP_UNLOADED\n"); + break; + case SC_VIEWSCREEN_CHANGED: + DEBUG(status,out).print("game state changed: SC_VIEWSCREEN_CHANGED\n"); + break; + case SC_CORE_INITIALIZED: + DEBUG(status,out).print("game state changed: SC_CORE_INITIALIZED\n"); + break; + case SC_BEGIN_UNLOAD: + DEBUG(status,out).print("game state changed: SC_BEGIN_UNLOAD\n"); + break; + case SC_PAUSED: + DEBUG(status,out).print("game state changed: SC_PAUSED\n"); + break; + case SC_UNPAUSED: + DEBUG(status,out).print("game state changed: SC_UNPAUSED\n"); + break; + } + + return CR_OK; +} + +// Whatever you put here will be done in each game frame refresh. Don't abuse it. +// Note that if the plugin implements the enabled API, this function is only called +// if the plugin is enabled. +DFhackCExport command_result plugin_onupdate (color_ostream &out) { + DEBUG(onupdate,out).print( + "onupdate called (run 'debugfilter set info skeleton onupdate' to stop" + " seeing these messages)\n"); + + return CR_OK; +} + +// If you need to save or load world-specific data, define these functions. +// plugin_save_data is called when the game might be about to save the world, +// and plugin_load_data is called whenever a new world is loaded. If the plugin +// is loaded or unloaded while a world is active, plugin_save_data or +// plugin_load_data will be called immediately. +DFhackCExport command_result plugin_save_data (color_ostream &out) { + DEBUG(status,out).print("save or unload is imminent; time to persist state\n"); + + // Call functions in the Persistence module here. If your PersistantDataItem + // objects are already up to date, then they will get persisted with the + // save automatically and there is nothing extra you need to do here. + return CR_OK; +} + +DFhackCExport command_result plugin_load_data (color_ostream &out) { + DEBUG(status,out).print("world is loading; time to load persisted state\n"); + + // Call functions in the Persistence module here. See + // persistent_per_save_example.cpp for an example. + return CR_OK; +} + +// This is the callback we registered in plugin_init. Note that while plugin +// callbacks are called with the core suspended, command callbacks are called +// from a different thread and need to explicity suspend the core if they +// interact with Lua or DF game state (most commands do at least one of these). +static command_result command_callback1(color_ostream &out, vector ¶meters) { + DEBUG(command,out).print("%s command called with %zu parameters\n", + plugin_name, parameters.size()); + + // I'll say it again: always suspend the core in command callbacks unless + // all your data is local. + CoreSuspender suspend; + + // Return CR_WRONG_USAGE to print out your help text. The help text is + // sourced from the associated rst file in docs/plugins/. The same help will + // also be returned by 'help your-command'. + + // simple commandline parsing can be done in C++, but there are lua libraries + // that can easily handle more complex commandlines. see the blueprint plugin + // for an example. + + // TODO: do something according to the flags set in the options struct + + return CR_OK; +} diff --git a/plugins/examples/ui_addition_example.cpp b/plugins/examples/ui_addition_example.cpp new file mode 100644 index 000000000..bbd3af3de --- /dev/null +++ b/plugins/examples/ui_addition_example.cpp @@ -0,0 +1,57 @@ +// This template is appropriate for plugins that can be enabled to make some +// specific persistent change to the game, but don't need a world to be loaded +// before they are enabled. These types of plugins typically register some sort +// of hook on enable and clear the hook on disable. They are generally enabled +// from dfhack.init and do not need to persist and reload their enabled state. + +#include +#include + +#include "df/viewscreen_titlest.h" + +#include "Debug.h" +#include "PluginManager.h" +#include "VTableInterpose.h" + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("ui_addition_example"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +namespace DFHack { + DBG_DECLARE(ui_addition_example, log); +} + +// example of hooking a screen so the plugin code will run whenever the screen +// is visible +struct title_version_hook : df::viewscreen_titlest { + typedef df::viewscreen_titlest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + + // TODO: injected render logic here + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(title_version_hook, render); + +DFhackCExport command_result plugin_shutdown (color_ostream &out) { + DEBUG(log,out).print("shutting down %s\n", plugin_name); + INTERPOSE_HOOK(title_version_hook, render).remove(); + return CR_OK; +} + +DFhackCExport command_result plugin_enable (color_ostream &out, bool enable) { + if (enable != is_enabled) { + DEBUG(log,out).print("%s %s\n", plugin_name, + is_enabled ? "enabled" : "disabled"); + if (!INTERPOSE_HOOK(title_version_hook, render).apply(enable)) + return CR_FAILURE; + + is_enabled = enable; + } + return CR_OK; +} diff --git a/plugins/external/.gitignore b/plugins/external/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/plugins/external/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 3948d8577..787f85c5d 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -224,20 +224,10 @@ DFhackCExport command_result plugin_enable ( color_ostream &out, bool enable ) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("fastdwarf", - "let dwarves teleport and/or finish jobs instantly", - fastdwarf, false, - "fastdwarf: make dwarves faster.\n" - "Usage:\n" - " fastdwarf (tele)\n" - "Valid values for speed:\n" - " * 0 - Make dwarves move and work at standard speed.\n" - " * 1 - Make dwarves move and work at maximum speed.\n" - " * 2 - Make ALL creatures move and work at maximum speed.\n" - "Valid values for (tele):\n" - " * 0 - Disable dwarf teleportation (default)\n" - " * 1 - Make dwarves teleport to their destinations instantly.\n" - )); + commands.push_back(PluginCommand( + "fastdwarf", + "Dwarves teleport and/or finish jobs instantly.", + fastdwarf)); return CR_OK; } diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index ddb5ef877..781fce0a8 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -44,41 +44,21 @@ DFHACK_PLUGIN("filltraffic"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "filltraffic","Flood-fill selected traffic designation from cursor", - filltraffic, Gui::cursor_hotkey, - " Flood-fill selected traffic type from the cursor.\n" - "Traffic Type Codes:\n" - " H: High Traffic\n" - " N: Normal Traffic\n" - " L: Low Traffic\n" - " R: Restricted Traffic\n" - "Other Options:\n" - " X: Fill across z-levels.\n" - " B: Include buildings and stockpiles.\n" - " P: Include empty space.\n" - "Example:\n" - " filltraffic H\n" - " When used in a room with doors,\n" - " it will set traffic to HIGH in just that room.\n" - )); + "filltraffic", + "Flood-fill selected traffic designation from cursor.", + filltraffic, Gui::cursor_hotkey)); commands.push_back(PluginCommand( - "alltraffic","Set traffic for the entire map", - alltraffic, false, - " Set traffic types for all tiles on the map.\n" - "Traffic Type Codes:\n" - " H: High Traffic\n" - " N: Normal Traffic\n" - " L: Low Traffic\n" - " R: Restricted Traffic\n" - )); + "alltraffic", + "Set traffic designation for the entire map.", + alltraffic)); commands.push_back(PluginCommand( - "restrictliquids","Restrict on every visible square with liquid", - restrictLiquid, false, "" - )); + "restrictliquids", + "Restrict traffic on every visible square with liquid.", + restrictLiquid)); commands.push_back(PluginCommand( - "restrictice","Restrict traffic on squares above visible ice", - restrictIce, false, "" - )); + "restrictice", + "Restrict traffic on squares above visible ice.", + restrictIce)); return CR_OK; } diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp deleted file mode 100644 index 0f63b4d93..000000000 --- a/plugins/fix-armory.cpp +++ /dev/null @@ -1,843 +0,0 @@ -// Fixes containers in barracks to actually work as intended. - -#include "Core.h" -#include "Console.h" -#include "Export.h" -#include "PluginManager.h" - -#include "modules/Gui.h" -#include "modules/Screen.h" -#include "modules/Units.h" -#include "modules/Items.h" -#include "modules/Job.h" -#include "modules/World.h" -#include "modules/Maps.h" - -#include "MiscUtils.h" - -#include "DataDefs.h" -#include -#include "df/ui.h" -#include "df/world.h" -#include "df/squad.h" -#include "df/unit.h" -#include "df/squad_position.h" -#include "df/squad_ammo_spec.h" -#include "df/items_other_id.h" -#include "df/item_weaponst.h" -#include "df/item_armorst.h" -#include "df/item_helmst.h" -#include "df/item_pantsst.h" -#include "df/item_shoesst.h" -#include "df/item_glovesst.h" -#include "df/item_shieldst.h" -#include "df/item_flaskst.h" -#include "df/item_backpackst.h" -#include "df/item_quiverst.h" -#include "df/item_ammost.h" -#include "df/building_weaponrackst.h" -#include "df/building_armorstandst.h" -#include "df/building_cabinetst.h" -#include "df/building_boxst.h" -#include "df/building_squad_use.h" -#include "df/job.h" -#include "df/general_ref_building_holderst.h" -#include "df/general_ref_building_destinationst.h" -#include "df/barrack_preference_category.h" - -#include - -using std::vector; -using std::string; -using std::endl; -using namespace DFHack; -using namespace df::enums; - -using df::global::ui; -using df::global::world; -using df::global::gamemode; -using df::global::ui_build_selector; - -using namespace DFHack::Gui; -using Screen::Pen; - -static command_result fix_armory(color_ostream &out, vector & parameters); - -DFHACK_PLUGIN("fix-armory"); - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); - -DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) -{ - commands.push_back(PluginCommand( - "fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false, - " fix-armory enable\n" - " Enables the tweaks.\n" - " fix-armory disable\n" - " Disables the tweaks. All equipment will be hauled off to stockpiles.\n" - )); - - if (Core::getInstance().isMapLoaded()) - plugin_onstatechange(out, SC_MAP_LOADED); - - return CR_OK; -} - -DFhackCExport command_result plugin_shutdown (color_ostream &out) -{ - return CR_OK; -} - -/* - * PART 1 - Stop restockpiling of items stored in the armory. - * - * For everything other than ammo this is quite straightforward, - * since the uniform switch code already tries to store items - * in barracks containers, and it is thus known what the intention - * is. Moreover these containers know which squad and member they - * belong to. - * - * For ammo there is no such code (in fact, ammo is never removed - * from a quiver, except when it is dropped itself), so I had to - * apply some improvisation. There is one place where BOX containers - * with Squad Equipment set are used as an anchor location for a - * pathfinding check when assigning ammo, so presumably that's - * the correct place. I however wanted to also differentiate - * training ammo, so came up with the following rules: - * - * 1. Combat ammo and ammo without any allowed use can be stored - * in BOXes marked for Squad Equipment, either directly or via - * containing room. No-allowed-use ammo is assumed to be reserved - * for emergency combat use, or something like that; however if - * it is already stored in a training chest, it won't be moved. - * 1a. If assigned to a squad position, that box can be used _only_ - * for ammo assigned to that specific _squad_. Otherwise, if - * multiple squads can use this room, they will store their - * ammo all mixed up. - * 2. Training ammo can be stored in BOXes within archery ranges - * (designated from archery target) that are enabled for Training. - * Train-only ammo in particular can _only_ be stored in such - * boxes. The inspiration for this comes from some broken code - * for weapon racks in Training rooms. - * - * As an additional feature (partially needed due to the constraints - * of working from an external hack), this plugin also blocks instant - * queueing of stockpiling jobs for items blocked on the ground, if - * these items are assigned to any squad. - * - * Since there apparently still are bugs that cause uniform items to be - * momentarily dropped on ground, this delay is set not to the minimally - * necessary 50 ticks, but to 0.5 - 1.0 in-game days, so as to provide a - * grace period during which the items can be instantly picked up again. - */ - -// Completely block the use of stockpiles -#define NO_STOCKPILES - -// Check if the item is assigned to any use controlled by the military tab -static bool is_assigned_item(df::item *item) -{ - if (!ui) - return false; - - auto type = item->getType(); - int idx = binsearch_index(ui->equipment.items_assigned[type], item->id); - if (idx < 0) - return false; - - return true; -} - -// Check if this ammo item is assigned to this squad with one of the specified uses -static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool train) -{ - for (size_t i = 0; i < squad->ammunition.size(); i++) - { - auto spec = squad->ammunition[i]; - bool cs = spec->flags.bits.use_combat; - bool ts = spec->flags.bits.use_training; - - // no-use ammo assumed to fit any category - if (((cs || !ts) && combat) || ((ts || !cs) && train)) - { - if (binsearch_index(spec->assigned, item->id) >= 0) - return true; - } - } - - return false; -} - -// Recursively check room parents to find out if this ammo item is allowed here -static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) -{ - auto squads = holder->getSquads(); - - if (squads) - { - for (size_t i = 0; i < squads->size(); i++) - { - auto use = (*squads)[i]; - - // For containers assigned to a squad, only consider that squad - if (squad_id >= 0 && use->squad_id != squad_id) - continue; - - // Squad Equipment -> combat - bool combat = use->mode.bits.squad_eq; - bool train = false; - - if (combat || train) - { - auto squad = df::squad::find(use->squad_id); - - if (squad && is_squad_ammo(item, squad, combat, train)) - return true; - } - } - } - // Ugh, archery targets don't actually have a squad use vector - else if (holder->getType() == building_type::ArcheryTarget) - { - auto &squads = world->squads.all; - - for (size_t si = 0; si < squads.size(); si++) - { - auto squad = squads[si]; - - // For containers assigned to a squad, only consider that squad - if (squad_id >= 0 && squad->id != squad_id) - continue; - - for (size_t j = 0; j < squad->rooms.size(); j++) - { - auto use = squad->rooms[j]; - - if (use->building_id != holder->id) - continue; - - // Squad Equipment -> combat - bool combat = use->mode.bits.squad_eq; - // Archery target with Train -> training - bool train = use->mode.bits.train; - - if (combat || train) - { - if (is_squad_ammo(item, squad, combat, train)) - return true; - } - - break; - } - } - } - - for (size_t i = 0; i < holder->parents.size(); i++) - if (can_store_ammo_rec(item, holder->parents[i], squad_id)) - return true; - - return false; -} - -// Check if the ammo item can be stored in this container -static bool can_store_ammo(df::item *item, df::building *holder) -{ - // Only chests - if (holder->getType() != building_type::Box) - return false; - - // with appropriate flags set - return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); -} - -// Check if the item is assigned to the squad member who owns this armory building -static bool belongs_to_position(df::item *item, df::building *holder) -{ - int sid = holder->getSpecificSquad(); - if (sid < 0) - return false; - - auto squad = df::squad::find(sid); - if (!squad) - return false; - - int position = holder->getSpecificPosition(); - - // Weapon racks belong to the whole squad, i.e. can be used by any position - if (position == -1 && holder->getType() == building_type::Weaponrack) - { - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) - return true; - } - } - else - { - auto cpos = vector_get(squad->positions, position); - if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) - return true; - } - - return false; -} - -// Check if the item is appropriately stored in an armory building -static bool is_in_armory(df::item *item) -{ - if (item->flags.bits.in_inventory || item->flags.bits.on_ground) - return false; - - auto holder = Items::getHolderBuilding(item); - if (!holder) - return false; - - // If indeed in a building, check if it is the right one - if (item->getType() == item_type::AMMO) - return can_store_ammo(item, holder); - else - return belongs_to_position(item, holder); -} - -/* - * Hooks used to affect stockpiling code as it runs, and prevent it - * from doing unwanted stuff. - * - * Toady can simply add these checks directly to the stockpiling code; - * we have to abuse some handy item vmethods. - */ - -template struct armory_hook : Item { - typedef Item interpose_base; - - /* - * This vmethod is called by the actual stockpiling code before it - * tries to queue a job, and is normally used to prevent stockpiling - * of uncollected webs. - */ - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { -#ifdef NO_STOCKPILES - /* - * Completely block any items assigned to a squad from being stored - * in stockpiles. The reason is that I still observe haulers running - * around with bins to pick them up for some reason. There could be - * some unaccounted race conditions involved. - */ - if (is_assigned_item(this)) - return false; -#else - // Block stockpiling of items in the armory. - if (is_in_armory(this)) - return false; - - /* - * When an item is removed from inventory due to Pickup Equipment - * process, the unit code directly invokes the stockpiling code - * and thus creates the job even before the item is actually dropped - * on the ground. We don't want this at all, especially due to the - * grace period idea. - * - * With access to source, that code can just be changed to simply - * drop the item on ground, without running stockpiling code. - */ - if (this->flags.bits.in_inventory) - { - auto holder = Items::getHolderUnit(this); - - // When that call happens, the item is still in inventory - if (holder && is_assigned_item(this)) - { - // And its ID is is this vector - if (::binsearch_index(holder->military.uniform_drop, this->id) >= 0) - return false; - } - } -#endif - - // Call the original vmethod - return INTERPOSE_NEXT(isCollected)(); - } - - /* - * This vmethod is used to actually put the item on the ground. - * When it does that, it also adds it to a vector of items to be - * instanly restockpiled by a loop in another part of the code. - * - * We don't want this either, even more than when removing from - * uniform, because this can happen in lots of various situations, - * including deconstructed containers etc, and we want our own - * armory code below to have a chance to look at the item. - * - * The logical place for this code is in the loop that processes - * that vector, but that part is not virtual. - */ - DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) - { - // First, let it do its work - bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); - - // Prevent instant restockpiling of dropped assigned items. - if (is_assigned_item(this)) - { - // The original vmethod adds the item to this vector to force instant check - auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED]; - - // If it is indeed there, remove it - if (erase_from_vector(ovec, &df::item::id, this->id)) - { - // and queue it to be checked normally in 0.5-1 in-game days - // (this is a grace period in case the uniform is dropped just - // for a moment due to a momentary glitch) - this->stockpile_countdown = 12 + random_int(12); - this->stockpile_delay = 0; - } - } - - return rv; - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); - -/* - * PART 2 - Actively queue jobs to haul assigned items to the armory. - * - * The logical place for this of course is in the same code that decides - * to put stuff in stockpiles, alongside the checks to prevent moving - * stuff away from the armory. We just run it independently every 50 - * simulation frames. - */ - -// Check if this item is loose and can be moved to armory -static bool can_store_item(df::item *item) -{ - // bad id, or cooldown timer still counting - if (!item || item->stockpile_countdown > 0) - return false; - - // bad flags? - if (item->flags.bits.in_job || - item->flags.bits.removed || - item->flags.bits.in_building || - item->flags.bits.encased || - item->flags.bits.owned || - item->flags.bits.forbid || - item->flags.bits.on_fire) - return false; - - // in unit inventory? - auto top = item; - - while (top->flags.bits.in_inventory) - { - auto parent = Items::getContainer(top); - if (!parent) break; - top = parent; - } - - if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) - return false; - - // already in armory? - if (is_in_armory(item)) - return false; - - return true; -} - -// Queue a job to store the item in the building, if possible -static bool try_store_item(df::building *target, df::item *item) -{ - // Check if the dwarves can path between the target and the item - df::coord tpos(target->centerx, target->centery, target->z); - df::coord ipos = Items::getPosition(item); - - if (!Maps::canWalkBetween(tpos, ipos)) - return false; - - // Check if the target has enough space left - if (!target->canStoreItem(item, true)) - return false; - - // Create the job - auto href = df::allocate(); - if (!href) - return false; - - auto job = new df::job(); - - job->pos = tpos; - - bool dest = false; - - // Choose the job type - correct matching is needed so that - // later canStoreItem calls would take the job into account. - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - // Without this flag dwarves will pick up the item, and - // then dismiss the job and put it back into the stockpile: - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreOwnedItem; - dest = true; - break; - default: - job->job_type = job_type::StoreItemInHospital; - dest = true; - break; - } - - // job <-> item link - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - return false; - } - - // job <-> building link - href->building_id = target->id; - target->jobs.push_back(job); - job->general_refs.push_back(href); - - // Two of the jobs need this link to find the job in canStoreItem(). - // They also don't actually need BUILDING_HOLDER, but it doesn't hurt. - if (dest) - { - auto rdest = df::allocate(); - - if (rdest) - { - rdest->building_id = target->id; - job->general_refs.push_back(rdest); - } - } - - // add to job list - Job::linkIntoWorld(job); - return true; -} - -// Store the item into the first building in the list that would accept it. -static void try_store_item(std::vector &vec, df::item *item) -{ - for (size_t i = 0; i < vec.size(); i++) - { - auto target = df::building::find(vec[i]); - if (!target) - continue; - - if (try_store_item(target, item)) - return; - } -} - -// Store the items into appropriate armory buildings -static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) -{ - for (size_t j = 0; j < items.size(); j++) - { - auto item = df::item::find(items[j]); - - // not loose - if (!can_store_item(item)) - continue; - - // queue jobs to put it in the appropriate container - if (item->isWeapon()) - try_store_item(squad->rack_combat, item); - else if (item->isClothing()) - try_store_item(pos->preferences[barrack_preference_category::Cabinet], item); - else if (item->isArmorNotClothing()) - try_store_item(pos->preferences[barrack_preference_category::Armorstand], item); - else - try_store_item(pos->preferences[barrack_preference_category::Box], item); - } -} - -// For storing ammo, use a data structure sorted by free space, to even out the load -typedef std::map > ammo_box_set; - -// Enumerate boxes in the room, adding them to the set -static void index_boxes(df::building *root, ammo_box_set &group, int squad_id) -{ - if (root->getType() == building_type::Box) - { - int id = root->getSpecificSquad(); - - if (id < 0 || id == squad_id) - { - //color_ostream_proxy out(Core::getInstance().getConsole()); - //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); - - group[root->getFreeCapacity(true)].insert(root); - } - } - - for (size_t i = 0; i < root->children.size(); i++) - index_boxes(root->children[i], group, squad_id); -} - -// Loop over the set from most empty to least empty -static bool try_store_ammo(df::item *item, ammo_box_set &group) -{ - int volume = item->getVolume(); - - for (auto it = group.rbegin(); it != group.rend(); ++it) - { - if (it->first < volume) - break; - - for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) - { - auto bld = *it2; - - if (try_store_item(bld, item)) - { - it->second.erase(bld); - group[bld->getFreeCapacity(true)].insert(bld); - return true; - } - } - } - - return false; -} - -// Collect chests for ammo storage -static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box_set &combat_set) -{ - for (size_t j = 0; j < squad->rooms.size(); j++) - { - auto room = squad->rooms[j]; - auto bld = df::building::find(room->building_id); - - // Chests in rooms marked for Squad Equipment used for combat ammo - if (room->mode.bits.squad_eq) - index_boxes(bld, combat_set, squad->id); - - // Chests in archery ranges used for training ammo - if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) - index_boxes(bld, train_set, squad->id); - } -} - -// Store ammo into appropriate chests -static void try_store_ammo(df::squad *squad) -{ - bool indexed = false; - ammo_box_set train_set, combat_set; - - for (size_t i = 0; i < squad->ammunition.size(); i++) - { - auto spec = squad->ammunition[i]; - bool cs = spec->flags.bits.use_combat; - bool ts = spec->flags.bits.use_training; - - for (size_t j = 0; j < spec->assigned.size(); j++) - { - auto item = df::item::find(spec->assigned[j]); - - // not loose - if (!can_store_item(item)) - continue; - - // compute the maps lazily - if (!indexed) - { - indexed = true; - index_ammo_boxes(squad, train_set, combat_set); - } - - // BUG: if the same container is in both sets, - // when a job is queued, the free space in the other - // set will not be updated, which could lead to uneven - // loading - but not to overflowing the container! - - // As said above, combat goes into Squad Equipment - if (cs && try_store_ammo(item, combat_set)) - continue; - // Training goes into Archery Range with Train - if (ts && try_store_ammo(item, train_set)) - continue; - // No use goes into combat - if (!(ts || cs) && try_store_ammo(item, combat_set)) - continue; - } - } -} - -DFHACK_PLUGIN_IS_ENABLED(is_enabled); - -DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) -{ - if (!is_enabled) - return CR_OK; - - // Process every 50th frame, sort of like regular stockpiling does - if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) - return CR_OK; - - // Loop over squads - auto &squads = world->squads.all; - - for (size_t si = 0; si < squads.size(); si++) - { - auto squad = squads[si]; - - for (size_t i = 0; i < squad->positions.size(); i++) - { - auto pos = squad->positions[i]; - - try_store_item_set(pos->assigned_items, squad, pos); - } - - try_store_ammo(squad); - } - - return CR_OK; -} - -static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable) -{ - if (!hook.apply(enable)) - out.printerr("Could not %s hook.\n", enable?"activate":"deactivate"); -} - -static void enable_hooks(color_ostream &out, bool enable) -{ - is_enabled = enable; - - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); - - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); - enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); -} - -static void enable_plugin(color_ostream &out) -{ - auto entry = World::GetPersistentData("fix-armory/enabled", NULL); - if (!entry.isValid()) - { - out.printerr("Could not save the status.\n"); - return; - } - - enable_hooks(out, true); -} - -static void disable_plugin(color_ostream &out) -{ - auto entry = World::GetPersistentData("fix-armory/enabled"); - World::DeletePersistentData(entry); - - enable_hooks(out, false); -} - -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_MAP_LOADED: - if (!gamemode || *gamemode == game_mode::DWARF) - { - bool enable = World::GetPersistentData("fix-armory/enabled").isValid(); - - if (enable) - { - out.print("Enabling the fix-armory plugin.\n"); - enable_hooks(out, true); - } - else - enable_hooks(out, false); - } - break; - case SC_MAP_UNLOADED: - enable_hooks(out, false); - default: - break; - } - - return CR_OK; -} - -DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) -{ - if (!Core::getInstance().isWorldLoaded()) { - out.printerr("World is not loaded: please load a game first.\n"); - return CR_FAILURE; - } - - if (enable) - enable_plugin(out); - else - disable_plugin(out); - - return CR_OK; -} - -static command_result fix_armory(color_ostream &out, vector ¶meters) -{ - CoreSuspender suspend; - - if (parameters.empty()) - return CR_WRONG_USAGE; - - string cmd = parameters[0]; - - if (cmd == "enable") - return plugin_enable(out, true); - else if (cmd == "disable") - return plugin_enable(out, false); - else - return CR_WRONG_USAGE; - - return CR_OK; -} diff --git a/plugins/fix-unit-occupancy.cpp b/plugins/fix-unit-occupancy.cpp index d602ae213..4b2db0f12 100644 --- a/plugins/fix-unit-occupancy.cpp +++ b/plugins/fix-unit-occupancy.cpp @@ -14,6 +14,7 @@ #include "df/unit.h" #include "df/world.h" +using std::endl; using namespace DFHack; DFHACK_PLUGIN("fix-unit-occupancy"); @@ -181,18 +182,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("fixveins", - "Remove invalid references to mineral inclusions and restore missing ones.", + commands.push_back(PluginCommand( + "fixveins", + "Restore missing mineral inclusions.", df_fixveins)); return CR_OK; } diff --git a/plugins/flows.cpp b/plugins/flows.cpp index 070776b10..a17554cb9 100644 --- a/plugins/flows.cpp +++ b/plugins/flows.cpp @@ -59,7 +59,8 @@ command_result df_flows (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("flows", + commands.push_back(PluginCommand( + "flows", "Counts map blocks with flowing liquids.", df_flows)); return CR_OK; diff --git a/plugins/follow.cpp b/plugins/follow.cpp index e9733d5da..53ff5c523 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -31,11 +31,10 @@ uint8_t prevMenuWidth; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "follow", "Make the screen follow the selected unit", - follow, Gui::view_unit_hotkey, - " Select a unit and run this plugin to make the camera follow it.\n" - " Moving the camera yourself deactivates the plugin.\n" - )); + "follow", + "Make the screen follow the selected unit.", + follow, + Gui::view_unit_hotkey)); followedUnit = 0; prevX=prevY=prevZ = -1; prevMenuWidth = 0; diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index ab39bb0d6..2ee4e47a7 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -55,171 +55,12 @@ const int const_GloveLeftHandedness = 2; command_result df_forceequip(color_ostream &out, vector & parameters); -const string forceequip_help = - "ForceEquip moves local items into a unit's inventory. It is typically\n" - "used to equip specific clothing/armor items onto a dwarf, but can also\n" - "be used to put armor onto a war animal or to add unusual items (such\n" - "as crowns) to any unit.\n" - "This plugin can process multiple items in a single call, but will only\n" - "work with a single unit (the first one it finds under the cursor).\n" - "In order to minimize confusion, it is recommended that you use\n" - "forceequip only when you have a unit standing alone atop a pile of\n" - "gear that you would like it to wear. Items which are stored in bins\n" - "or other containers (e.g. chests, armor racks) may also work, but\n" - "piling items on the floor (via a garbage dump activity zone, of the\n" - "DFHack autodump command) is the most reliable way to do it.\n" - "The plugin will ignore any items that are forbidden. Hence, you\n" - "can setup a large pile of surplus gear, walk a unit onto it (or\n" - "pasture an animal on it), unforbid a few items and run forceequip.\n" - "The (forbidden) majority of your gear will remain in-place, ready\n" - "for the next passerby." - "\n" - "As mentioned above, this plugin can be used to equip items onto\n" - "units (such as animals) which cannot normally equip gear. There's\n" - "an important caveat here - such creatures will automatically drop\n" - "inappropriate gear almost immediately (within 10 game ticks).\n" - "If you want them to retain their equipment, you must forbid it\n" - "AFTER using forceequip to get it into their inventory.\n" - "This technique can also be used to clothe dwarven infants, but\n" - "only if you're able to separate them from their mothers.\n" - "\n" - "By default, the forceequip plugin will attempt to avoid\n" - "conflicts and outright cheating. For instance, it will skip\n" - "any item which is flagged for use in a job, and will not\n" - "equip more than one piece of clothing/armor onto any given\n" - "body part. These restrictions can be overridden via command\n" - "switches (see examples below) but doing so puts you at greater\n" - "risk of unexpected consequences. For instance, a dwarf who\n" - "is wearing three breastplates will not be able to move very\n" - "quickly.\n" - "\n" - "Items equipped by this plugin DO NOT become owned by the\n" - "recipient. Adult dwarves are free to adjust their own\n" - "wardrobe, and may promptly decide to doff your gear in\n" - "favour of their owned items. Animals, as described above,\n" - "will tend to discard ALL clothing immediately unless it is\n" - "manually forbidden. Armor items seem to be an exception;\n" - "an animal will tend to retain an equipped suit of mail\n" - "even if you neglect to Forbid it.\n" - "\n" - "Please note that armored animals are quite vulnerable to ranged\n" - "attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n" - "arrows, and they are slowed by the weight of their armor.\n" - "\n" - "This plugin currently does not support weapons.\n" - "\n" - "Options:\n" - " here, h - process the unit and item(s) under the cursor.\n" - " - This option is enabled by default since the plugin\n" - " - does not currently support remote equpping.\n" - " ignore, i - bypasses the usual item eligibility checks (such as\n" - " - \"Never equip gear belonging to another dwarf\" and\n" - " - \"Nobody is allowed to equip a Hive\".)\n" - " multi, m - bypasses the 1-item-per-bodypart limit, allowing\n" - " - the unit to receive an unlimited amount of gear.\n" - " - Can be used legitimately (e.g. mitten + gauntlet)\n" - " - or for cheating (e.g. twelve breastplates).\n" - " m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n" - " - each part to receive 2, 3, or 4 pieces of gear.\n" - " selected, s - rather than processing all items piled at a unit's\n" - " - feet, process only the one item currently selected.\n" - " bodypart, bp - must be followed by a bodypart code (e.g. LH).\n" - " - Instructs the plugin to equip all available items\n" - " - onto this body part only. Typically used in\n" - " - conjunction with the f switch (to over-armor\n" - " - a particular bodypart) or the i switch (to equip\n" - " - an unusual item onto a specific slot).\n" - " verbose, v - provides detailed narration and error messages.\n" - " - Can be helpful in resolving failures; not needed\n" - " - for casual use.\n" - "\n" - "Examples:\n" - " forceequip\n" - " attempts to equip all of the items under the cursor onto the unit\n" - " under the cursor. Uses only clothing/armor items; ignores all\n" - " other types. Equips a maximum of 1 item onto each bodypart,\n" - " and equips only \"appropriate\" items in each slot (e.g. glove\n" - " --> hand). Bypasses any item which might cause a conflict,\n" - " such as a Boot belonging to a different dwarf.\n" - " forceequip bp LH\n" - " attempts to equip all local items onto the left hand of the local\n" - " unit. If the hand is already equipped then nothing will happen,\n" - " and if it is not equipped then only one appropriate item (e.g. \n" - " a single mitten or gauntlet) will be equipped. This command can\n" - " be useful if you don't want to selectively forbid individual items\n" - " and simply want the unit to equip, say, a Helmet while leaving\n" - " the rest of the pile alone.\n" - " forceequip m bp LH\n" - " as above, but will equip ALL appropriate items onto the unit's\n" - " left hand. After running this command, it might end up wearing\n" - " a dozen left-handed mittens. Use with caution, and remember\n" - " that dwarves will tend to drop supernumary items ASAP.\n" - " forceequip m\n" - " as above, but will equip ALL appropriate items onto any\n" - " appropriate bodypart. Tends to put several boots onto the right\n" - " foot while leaving the left foot bare.\n" - " forceequip m2\n" - " as above, but will equip up to two appropriate items onto each\n" - " bodypart. Helps to balance footwear, but doesn't ensure proper\n" - " placement (e.g. left foot gets two socks, right foot gets two\n" - " shoes). For best results, use \"selected bp LH\" and\n" - " \"selected bp RH\" instead.\n" - " forceequip i\n" - " performs the standard \"equip appropriate items onto appropriate\n" - " bodyparts\" logic, but also includes items that would normally\n" - " be considered ineligible (such as a sock which is owned by\n" - " a different dwarf).\n" - " forceequip bp NECK\n" - " attempts to equip any appropriate gear onto the Neck of the\n" - " local unit. Since the plugin believes that no items are actually\n" - " appropriate for the Neck slot, this command does nothing.\n" - " forceequip i bp NECK\n" - " attempts to equip items from the local pile onto the Neck\n" - " of the local unit. Ignores appropriateness restrictions.\n" - " If there's a millstone or an albatross carcass sitting on\n" - " the same square as the targeted unit, then there's a good\n" - " chance that it will end up around his neck. For precise\n" - " control, remember that you can selectively forbid some of\n" - " the items that are piled on the ground.\n" - " forceequip i m bp NECK\n" - " as above, but equips an unlimited number of items onto the\n" - " targeted bodypart. Effectively, all unforbidden items\n" - " (including helms, millstones, boulders, etc) will be\n" - " moved from the local pile and placed in the dwarf's\n" - " inventory (specifically, on his neck). When used with\n" - " a large pile of goods, this will leave the dwarf heavily\n" - " encumbered and very slow to move.\n" - " forceequip s\n" - " requires that a single item be selected using the k menu.\n" - " This item must occupy the same square as the target unit,\n" - " and must be unforbidden. Attempts to equip this single\n" - " item onto an appropriate slot in the unit's inventory.\n" - " Can serve as a quicker alternative to the selective-\n" - " unforbidding approach described above.\n" - " forceequip s m i bp HD\n" - " equips the selected item onto the unit's head. Ignores\n" - " all possible restrictions and conflicts. If you know\n" - " exactly what you want to equip, and exactly where you\n" - " want it to go, then this is the most straightforward\n" - " and reliable option.\n" - " forceequip v bp QQQ\n" - " guaranteed to fail (and accomplish nothing) because\n" - " there are no bodyparts called QQQ. However, since the\n" - " verbose switch is used, the resulting error messages\n" - " will list every bodypart that the unit DOES possess.\n" - " May be useful if you're unfamiliar with the BP codes\n" - " used by Dwarf Fortress, or if you're experimenting\n" - " with an exotic creature.\n" - "\n" - ; - DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "forceequip", "Move items from the ground into a unit's inventory", - df_forceequip, false, - forceequip_help.c_str() - )); + "forceequip", + "Move items into a unit's inventory.", + df_forceequip)); return CR_OK; } @@ -439,8 +280,7 @@ command_result df_forceequip(color_ostream &out, vector & parameters) if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man") { - out << forceequip_help << endl; - return CR_OK; + return CR_WRONG_USAGE; } else if (p == "here" || p == "h") { diff --git a/plugins/generated-creature-renamer.cpp b/plugins/generated-creature-renamer.cpp index 625c093cf..92c11cfaf 100644 --- a/plugins/generated-creature-renamer.cpp +++ b/plugins/generated-creature-renamer.cpp @@ -13,6 +13,7 @@ //#include "df/world.h" using namespace DFHack; +using std::endl; DFHACK_PLUGIN("generated-creature-renamer"); REQUIRE_GLOBAL(world); @@ -27,17 +28,12 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { commands.push_back(PluginCommand( - "getplants", "Cut down trees or gather shrubs by ID", - df_getplants, false, - " Specify the types of trees to cut down and/or shrubs to gather by their\n" - " plant IDs, separated by spaces.\n" - "Options:\n" - " -t - Tree: Select trees only (exclude shrubs)\n" - " -s - Shrub: Select shrubs only (exclude trees)\n" - " -f - Farming: Designate only shrubs that yield seeds for farming. Implies -s\n" - " -c - Clear: Clear designations instead of setting them\n" - " -x - eXcept: Apply selected action to all plants except those specified\n" - " -a - All: Select every type of plant (obeys -t/-s/-f)\n" - " -v - Verbose: List the number of (un)designations per plant\n" - " -n * - Number: Designate up to * (an integer number) plants of each species\n" - "Specifying both -t and -s or -f will have no effect.\n" - "If no plant IDs are specified, and the -a switch isn't given, all valid plant\n" - "IDs will be listed with -t, -s, and -f restricting the list to trees, shrubs,\n" - "and farmable shrubs, respectively.\n" - )); + "getplants", + "Designate trees for chopping and shrubs for gathering.", + df_getplants)); return CR_OK; } diff --git a/plugins/hotkeys.cpp b/plugins/hotkeys.cpp index e4cd13574..e92bc61fe 100644 --- a/plugins/hotkeys.cpp +++ b/plugins/hotkeys.cpp @@ -8,6 +8,7 @@ #include "modules/World.h" #include "modules/Gui.h" +#include "LuaTools.h" #include "PluginManager.h" DFHACK_PLUGIN("hotkeys"); @@ -44,6 +45,7 @@ static void find_active_keybindings(df::viewscreen *screen) sorted_keys.clear(); vector valid_keys; + for (char c = 'A'; c <= 'Z'; c++) { valid_keys.push_back(string(&c, 1)); @@ -54,6 +56,8 @@ static void find_active_keybindings(df::viewscreen *screen) valid_keys.push_back("F" + int_to_string(i)); } + valid_keys.push_back("`"); + auto current_focus = Gui::getFocusString(screen); for (int shifted = 0; shifted < 2; shifted++) { @@ -120,6 +124,29 @@ static void invoke_command(const size_t index) } } +static std::string get_help(const std::string &command, bool full_help) +{ + auto L = Lua::Core::State; + color_ostream_proxy out(Core::getInstance().getConsole()); + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, 2) || + !Lua::PushModulePublic(out, L, "helpdb", + full_help ? "get_entry_long_help" : "get_entry_short_help")) + return "Help text unavailable."; + + Lua::Push(L, command); + + if (!Lua::SafeCall(out, L, 1, 1)) + return "Help text unavailable."; + + const char *s = lua_tostring(L, -1); + if (!s) + return "Help text unavailable."; + + return s; +} + class ViewscreenHotkeys : public dfhack_viewscreen { public: @@ -219,31 +246,16 @@ public: if (first[0] == '#') return; - Plugin *plugin = Core::getInstance().getPluginManager()->getPluginByCommand(first); - if (plugin) + OutputString(COLOR_BROWN, x, y, "Help", true, help_start); + string help_text = get_help(first, show_usage); + vector lines; + split_string(&lines, help_text, "\n"); + for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++) { - for (size_t i = 0; i < plugin->size(); i++) + auto wrapped_lines = wrapString(*it, width); + for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++) { - auto pc = plugin->operator[](i); - if (pc.name == first) - { - OutputString(COLOR_BROWN, x, y, "Help", true, help_start); - vector lines; - string help_text = pc.description; - if (show_usage) - help_text += "\n\n" + pc.usage; - - split_string(&lines, help_text, "\n"); - for (auto it = lines.begin(); it != lines.end() && y < gps->dimy - 4; it++) - { - auto wrapped_lines = wrapString(*it, width); - for (auto wit = wrapped_lines.begin(); wit != wrapped_lines.end() && y < gps->dimy - 4; wit++) - { - OutputString(COLOR_WHITE, x, y, *wit, true, help_start); - } - } - break; - } + OutputString(COLOR_WHITE, x, y, *wit, true, help_start); } } } @@ -345,8 +357,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" - " Replace the exact material id in the job item.\n" - " job item-type \n" - " Replace the exact item type id in the job item.\n" - ) - ); + "job", + "Inspect and modify job item properties.", + job_cmd)); if (ui_workshop_job_cursor || ui_build_selector) { commands.push_back( PluginCommand( - "job-material", "Alter the material of the selected job.", - job_material, job_material_hotkey, - " job-material \n" - "Intended to be used as a keybinding:\n" - " - In 'q' mode, when a job is highlighted within a workshop\n" - " or furnace, changes the material of the job. Only inorganic\n" - " materials can be used in this mode.\n" - " - In 'b' mode, during selection of building components\n" - " positions the cursor over the first available choice\n" - " with the matching material.\n" - ) - ); + "job-material", + "Alter the material of the selected job or building.", + job_material, + job_material_hotkey)); } if (ui_workshop_job_cursor && job_next_id) { commands.push_back( PluginCommand( - "job-duplicate", "Duplicate the selected job in a workshop.", - job_duplicate, Gui::workshop_job_hotkey, - " - In 'q' mode, when a job is highlighted within a workshop\n" - " or furnace building, instantly duplicates the job.\n" - ) - ); + "job-duplicate", + "Duplicate the selected job in a workshop.", + job_duplicate, + Gui::workshop_job_hotkey)); } return CR_OK; diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 02569fd8b..f508c9797 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -845,38 +845,9 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector \n" - " Set max number of dwarves assigned to a labor.\n" - " labormanager max unmanaged\n" - " labormanager max disable\n" - " Don't attempt to manage this labor.\n" - " Any dwarves with unmanaged labors assigned will be less\n" - " likely to have managed labors assigned to them.\n" - " labormanager max none\n" - " Unrestrict the number of dwarves assigned to a labor.\n" - " labormanager priority \n" - " Change the assignment priority of a labor (default is 100)\n" - " labormanager reset \n" - " Return a labor to the default handling.\n" - " labormanager reset-all\n" - " Return all labors to the default handling.\n" - " labormanager list\n" - " List current status of all labors.\n" - " labormanager status\n" - " Show basic status information.\n" - "Function:\n" - " When enabled, labormanager periodically checks your dwarves and enables or\n" - " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" - " Warning: labormanager will override any manual changes you make to labors\n" - " while it is enabled, except where the labor is marked as unmanaged.\n" - " Do not try to run both autolabor and labormanager at the same time.\n" - )); + "labormanager", + "Automatically manage dwarf labors.", + labormanager)); generate_labor_to_skill_map(); diff --git a/plugins/lair.cpp b/plugins/lair.cpp index 6fb167988..7c3b1953f 100644 --- a/plugins/lair.cpp +++ b/plugins/lair.cpp @@ -61,7 +61,9 @@ command_result lair(color_ostream &out, std::vector & params) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("lair","Mark the map as a monster lair (avoids item scatter)",lair, false, - "Usage: 'lair' to mark entire map as monster lair, 'lair reset' to undo the operation.\n")); + commands.push_back(PluginCommand( + "lair", + "Mark the map as a monster lair to avoid item scatter.", + lair)); return CR_OK; } diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 5dd64812f..980deb747 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -55,6 +55,7 @@ using namespace df::enums; DFHACK_PLUGIN("liquids"); REQUIRE_GLOBAL(world); +static const char * HISTORY_FILE = "dfhack-config/liquids.history"; CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); @@ -62,25 +63,23 @@ command_result df_liquids_here (color_ostream &out, vector & parameters DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - liquids_hist.load("liquids.history"); + liquids_hist.load(HISTORY_FILE); commands.push_back(PluginCommand( - "liquids", "Place magma, water or obsidian.", - df_liquids, true, - "This tool allows placing magma, water and other similar things.\n" - "It is interactive and further help is available when you run it.\n" - "The settings will be remembered until dfhack is closed and you can call\n" - "'liquids-here' (mapped to a hotkey) to paint liquids at the cursor position\n" - "without the need to go back to the dfhack console.\n")); // interactive, needs console for prompt + "liquids", + "Place magma, water or obsidian.", + df_liquids, + true)); // interactive, needs console for prompt commands.push_back(PluginCommand( - "liquids-here", "Use settings from liquids at cursor position.", - df_liquids_here, Gui::cursor_hotkey, // non-interactive, needs ingame cursor - " This command is intended to be mapped to a hotkey and is identical to pressing Enter in liquids with the current parameters.\n")); + "liquids-here", + "Use settings from liquids at cursor position.", + df_liquids_here, + Gui::cursor_hotkey)); // non-interactive, needs ingame cursor return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - liquids_hist.save("liquids.history"); + liquids_hist.save(HISTORY_FILE); return CR_OK; } diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 08d48bd58..a9a7dc23c 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -1,3 +1,5 @@ +#pragma once + #include "uicommon.h" using df::global::enabler; diff --git a/plugins/lua/autobutcher.lua b/plugins/lua/autobutcher.lua new file mode 100644 index 000000000..f357f8fb1 --- /dev/null +++ b/plugins/lua/autobutcher.lua @@ -0,0 +1,80 @@ +local _ENV = mkmodule('plugins.autobutcher') + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function check_nonnegative_int(str) + local val = tonumber(str) + if is_positive_int(val) or val == 0 then return val end + qerror('expecting a non-negative integer, but got: '..tostring(str)) +end + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +local function process_races(opts, races, start_idx) + if #races < start_idx then + qerror('missing list of races (or "all" or "new" keywords)') + end + for i=start_idx,#races do + local race = races[i] + if race == 'all' then + opts.races_all = true + elseif race == 'new' then + opts.races_new = true + else + local str = df.new('string') + str.value = race + opts.races:insert('#', str) + end + end +end + +function parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + local command = positionals[1] + if command then opts.command = command end + + if opts.help or not command or command == 'now' or + command == 'autowatch' or command == 'noautowatch' or + command == 'list' or command == 'list_export' then + return + end + + if command == 'watch' or command == 'unwatch' or command == 'forget' then + process_races(opts, positionals, 2) + elseif command == 'target' then + opts.fk = check_nonnegative_int(positionals[2]) + opts.mk = check_nonnegative_int(positionals[3]) + opts.fa = check_nonnegative_int(positionals[4]) + opts.ma = check_nonnegative_int(positionals[5]) + process_races(opts, positionals, 6) + elseif command == 'ticks' then + local ticks = tonumber(positionals[2]) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. ticks) + else + opts.ticks = ticks + end + else + qerror(('unrecognized command: "%s"'):format(command)) + end +end + +return _ENV diff --git a/plugins/lua/autonestbox.lua b/plugins/lua/autonestbox.lua new file mode 100644 index 000000000..f7140b0b7 --- /dev/null +++ b/plugins/lua/autonestbox.lua @@ -0,0 +1,51 @@ +local _ENV = mkmodule('plugins.autonestbox') + +local argparse = require('argparse') + +local function is_int(val) + return val and val == math.floor(val) +end + +local function is_positive_int(val) + return is_int(val) and val > 0 +end + +local function process_args(opts, args) + if args[1] == 'help' then + opts.help = true + return + end + + return argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) +end + +function parse_commandline(opts, ...) + local positionals = process_args(opts, {...}) + + if opts.help then return end + + local in_ticks = false + for _,arg in ipairs(positionals) do + if in_ticks then + arg = tonumber(arg) + if not is_positive_int(arg) then + qerror('number of ticks must be a positive integer: ' .. arg) + else + opts.ticks = arg + end + in_ticks = false + elseif arg == 'ticks' then + in_ticks = true + elseif arg == 'now' then + opts.now = true + end + end + + if in_ticks then + qerror('missing number of ticks') + end +end + +return _ENV diff --git a/plugins/lua/blueprint.lua b/plugins/lua/blueprint.lua index 7b0076e14..718b211e8 100644 --- a/plugins/lua/blueprint.lua +++ b/plugins/lua/blueprint.lua @@ -3,47 +3,26 @@ local _ENV = mkmodule('plugins.blueprint') local argparse = require('argparse') local utils = require('utils') --- the info here is very basic and minimal, so hopefully we won't need to change --- it when features are added and the full blueprint docs in Plugins.rst are --- updated. -local help_text = [=[ - -blueprint -========= - -Records the structure of a portion of your fortress in quickfort blueprints. - -Usage: - - blueprint [] [ []] [] - blueprint gui [ []] [] - -Examples: - -blueprint gui - Runs gui/blueprint, the interactive blueprint frontend, where all - configuration can be set visually and interactively. - -blueprint 30 40 bedrooms - Generates blueprints for an area 30 tiles wide by 40 tiles tall, starting - from the active cursor on the current z-level. Output files are written to - the "blueprints" directory. - -See the online DFHack documentation for more examples and details. -]=] - -function print_help() print(help_text) end - local valid_phase_list = { 'dig', 'carve', + 'construct', 'build', 'place', 'zone', 'query', + 'rooms', } valid_phases = utils.invert(valid_phase_list) +local meta_phase_list = { + 'build', + 'place', + 'zone', + 'query', +} +meta_phases = utils.invert(meta_phase_list) + local valid_formats_list = { 'minimal', 'pretty', @@ -52,6 +31,7 @@ valid_formats = utils.invert(valid_formats_list) local valid_split_strategies_list = { 'none', + 'group', 'phase', } valid_split_strategies = utils.invert(valid_split_strategies_list) @@ -152,8 +132,10 @@ local function process_args(opts, args) {'f', 'format', hasArg=true, handler=function(optarg) parse_format(opts, optarg) end}, {'h', 'help', handler=function() opts.help = true end}, + {nil, 'nometa', handler=function() opts.nometa = true end}, {'s', 'playback-start', hasArg=true, handler=function(optarg) parse_start(opts, optarg) end}, + {nil, 'smooth', handler=function() opts.smooth = true end}, {'t', 'splitby', hasArg=true, handler=function(optarg) parse_split_strategy(opts, optarg) end}, }) @@ -162,6 +144,10 @@ local function process_args(opts, args) return end + if opts.split_strategy == 'phase' then + opts.nometa = true + end + return positionals end @@ -213,8 +199,13 @@ function parse_commandline(opts, ...) parse_positionals(opts, positionals, depth and 4 or 3) end +function is_meta_phase(opts, phase) + -- this is called directly by cpp so ensure we return a boolean, not nil + return not opts.nometa and meta_phases[phase] or false +end + -- returns the name of the output file for the given context -function get_filename(opts, phase) +function get_filename(opts, phase, ordinal) local fullname = 'blueprints/' .. opts.name local _,_,basename = fullname:find('/([^/]+)/?$') if not basename then @@ -224,11 +215,13 @@ function get_filename(opts, phase) if fullname:endswith('/') then fullname = fullname .. basename end - if opts.split_strategy == 'phase' then - return ('%s-%s.csv'):format(fullname, phase) + if opts.split_strategy == 'none' then + return ('%s.csv'):format(fullname) + end + if is_meta_phase(opts, phase) then + phase = 'meta' end - -- no splitting - return ('%s.csv'):format(fullname) + return ('%s-%d-%s.csv'):format(fullname, ordinal, phase) end -- compatibility with old exported API. diff --git a/plugins/lua/confirm.lua b/plugins/lua/confirm.lua index 13b28962a..e36a4fb86 100644 --- a/plugins/lua/confirm.lua +++ b/plugins/lua/confirm.lua @@ -219,6 +219,14 @@ function convict.get_message() "This action is irreversible." end +order_remove = defconf('order-remove') +function order_remove.intercept_key(key) + return key == keys.MANAGER_REMOVE and + not screen.in_max_workshops +end +order_remove.title = "Remove manager order" +order_remove.message = "Are you sure you want to remove this order?" + -- End of confirmation definitions function check() diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua index 78e363bef..542575b9c 100644 --- a/plugins/lua/cxxrandom.lua +++ b/plugins/lua/cxxrandom.lua @@ -151,8 +151,8 @@ bool_distribution = {} function bool_distribution:new(chance) local o = {} self.__index = self - if type(min) ~= 'number' or type(max) ~= 'number' then - error("Invalid arguments in bool_distribution construction. min and max must be numbers.") + if type(chance) ~= 'number' or chance < 0 or chance > 1 then + error("Invalid arguments in bool_distribution construction. chance must be a number between 0.0 and 1.0 (both included).") end o.p = chance setmetatable(o,self) @@ -208,7 +208,7 @@ function num_sequence:shuffle() if self.rngID == 'nil' then error("Add num_sequence object to crng as distribution, before attempting to shuffle.") end - ShuffleSequence(self.rngID, self.seqID) + ShuffleSequence(self.seqID, self.rngID) end return _ENV diff --git a/plugins/lua/dig-now.lua b/plugins/lua/dig-now.lua index 2d7ae40d7..b3ffeb0bc 100644 --- a/plugins/lua/dig-now.lua +++ b/plugins/lua/dig-now.lua @@ -4,37 +4,6 @@ local argparse = require('argparse') local guidm = require('gui.dwarfmode') local utils = require('utils') -local short_help_text = [=[ - -dig-now -======= - -Instantly completes dig designations, modifying map tiles and creating boulders, -ores, and gems as if a miner were doing the mining or engraving. By default, all -dig designations on the map are completed and boulder generation follows -standard game rules, but the behavior is configurable. - -Usage: - - dig-now [ []] [] - -Examples: - -dig-now - Dig all designated tiles according to standard game rules. - -dig-now --clean - Dig designated tiles, but don't generate any boulders, ores, or gems. - -dig-now --dump here - Dig tiles and dump all generated boulders, ores, and gems at the tile under - the game cursor. - -See the online DFHack documentation for details on all options. -]=] - -function print_help() print(short_help_text) end - local function parse_coords(opts, configname, arg) local cursor = argparse.coords(arg, configname) utils.assign(opts[configname], cursor) diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index 293874393..1e3170c45 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -93,7 +93,7 @@ end function registerReaction(reaction_name,callback) _registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {} _registeredStuff.reactionCallbacks[reaction_name]=callback - onReactionComplete._library=onReact + onReactionCompleting._library=onReact dfhack.onStateChange.eventful=unregall end @@ -154,7 +154,7 @@ eventType=invertTable{ "JOB_INITIATED", "JOB_STARTED", "JOB_COMPLETED", - "NEW_UNIT_ACTIVE", + "UNIT_NEW_ACTIVE", "UNIT_DEATH", "ITEM_CREATED", "BUILDING", diff --git a/plugins/lua/prospector.lua b/plugins/lua/prospector.lua new file mode 100644 index 000000000..403a1f43f --- /dev/null +++ b/plugins/lua/prospector.lua @@ -0,0 +1,45 @@ +local _ENV = mkmodule('plugins.prospector') + +local argparse = require('argparse') +local utils = require('utils') + +local VALID_SHOW_VALUES = utils.invert{ + 'summary', 'liquids', 'layers', 'features', 'ores', + 'gems', 'veins', 'shrubs', 'trees' +} + +function parse_commandline(opts, ...) + local show = {} + local positionals = argparse.processArgsGetopt({...}, { + {'h', 'help', handler=function() opts.help = true end}, + {'s', 'show', hasArg=true, handler=function(optarg) + show = argparse.stringList(optarg) end}, + {'v', 'values', handler=function() opts.value = true end}, + }) + + for _,p in ipairs(positionals) do + if p == 'all' then opts.hidden = true + elseif p == 'hell' then + opts.hidden = true + opts.tube = true + else + qerror(('unknown keyword: "%s"'):format(p)) + end + end + + if #show > 0 then + for s in pairs(VALID_SHOW_VALUES) do + opts[s] = false + end + end + + for _,s in ipairs(show) do + if VALID_SHOW_VALUES[s] then + opts[s] = true + else + qerror(('unknown report section: "%s"'):format(s)) + end + end +end + +return _ENV diff --git a/plugins/lua/zone.lua b/plugins/lua/zone.lua deleted file mode 100644 index 75c9feec8..000000000 --- a/plugins/lua/zone.lua +++ /dev/null @@ -1,12 +0,0 @@ -local _ENV = mkmodule('plugins.zone') - ---[[ - - Native functions: - - * autobutcher_isEnabled() - * autowatch_isEnabled() - ---]] - -return _ENV diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 5604b9760..2d3f3bc0a 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -53,8 +53,6 @@ REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(enabler); -#define CONFIG_PATH "manipulator" - struct SkillLevel { const char *name; @@ -654,10 +652,12 @@ namespace unit_ops { struct ProfessionTemplate { std::string name; + std::string displayName; + bool library; bool mask; std::vector labors; - bool load(string directory, string file) + bool load(string directory, string file, bool isLibrary) { cerr << "Attempt to load " << file << endl; std::ifstream infile(directory + "/" + file); @@ -665,14 +665,25 @@ struct ProfessionTemplate return false; } + library = isLibrary; + std::string line; name = file; // If no name is given we default to the filename + displayName = name; mask = false; while (std::getline(infile, line)) { if (strcmp(line.substr(0,5).c_str(),"NAME ")==0) { auto nextInd = line.find(' '); - name = line.substr(nextInd + 1); + displayName = line.substr(nextInd + 1); + name = displayName; + size_t slashpos = name.find_first_of("\\/"); + while (name.npos != slashpos) { + name = name.substr(slashpos + 1); + slashpos = name.find_first_of("\\/"); + } + if (name == "") + name = file; continue; } if (line == "MASK") @@ -747,7 +758,8 @@ struct ProfessionTemplate } }; -static std::string professions_folder = Filesystem::getcwd() + "/professions"; +static std::string professions_folder = "dfhack-config/professions"; +static std::string professions_library_folder = "dfhack-config/professions/library"; class ProfessionTemplateManager { public: @@ -762,27 +774,21 @@ public: } void load() { - vector files; - cerr << "Attempting to load professions: " << professions_folder.c_str() << endl; if (!Filesystem::isdir(professions_folder) && !Filesystem::mkdir(professions_folder)) { cerr << professions_folder << ": Does not exist and cannot be created" << endl; return; } - Filesystem::listdir(professions_folder, files); - std::sort(files.begin(), files.end()); - for(size_t i = 0; i < files.size(); i++) - { - if (files[i] == "." || files[i] == "..") - continue; + _load(professions_folder, false); + _load(professions_library_folder, true); - ProfessionTemplate t; - if (t.load(professions_folder, files[i])) - { - templates.push_back(t); - } - } + // sort alphabetically by display name, with user data above library data + std::sort(templates.begin(), templates.end(), + [](const ProfessionTemplate &a, const ProfessionTemplate &b) { + return (a.library == b.library && a.displayName < b.displayName) + || (b.library && !a.library); + }); } void save_from_unit(UnitInfo *unit) { @@ -794,6 +800,21 @@ public: t.save(professions_folder); reload(); } + +private: + void _load(const std::string &path, bool library) { + vector files; + Filesystem::listdir(path, files); + for (auto &fname : files) { + if (Filesystem::isdir(path + "/" + fname)) + continue; + + ProfessionTemplate t; + if (t.load(path, fname, library)) { + templates.push_back(t); + } + } + } }; static ProfessionTemplateManager manager; @@ -994,7 +1015,7 @@ public: manager.reload(); for (size_t i = 0; i < manager.templates.size(); i++) { - std::string name = manager.templates[i].name; + std::string name = manager.templates[i].displayName; if (manager.templates[i].mask) name += " (mask)"; ListEntry elem(name, i); @@ -2307,11 +2328,6 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!Filesystem::isdir(CONFIG_PATH) && !Filesystem::mkdir(CONFIG_PATH)) - { - out.printerr("manipulator: Could not create configuration folder: \"%s\"\n", CONFIG_PATH); - return CR_FAILURE; - } return CR_OK; } diff --git a/plugins/misery.cpp b/plugins/misery.cpp index b8e11f5c0..224060c6b 100644 --- a/plugins/misery.cpp +++ b/plugins/misery.cpp @@ -128,21 +128,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { } DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("misery", "increase the intensity of negative dwarven thoughts", - &misery, false, - "misery: When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).\n" - "Usage:\n" - " misery enable n\n" - " enable misery with optional magnitude n. If specified, n must be positive.\n" - " misery n\n" - " same as \"misery enable n\"\n" - " misery enable\n" - " same as \"misery enable 2\"\n" - " misery disable\n" - " stop adding new negative thoughts. This will not remove existing duplicated thoughts. Equivalent to \"misery 1\"\n" - " misery clear\n" - " remove fake thoughts added in this session of DF. Saving makes them permanent! Does not change factor.\n\n" - )); + commands.push_back(PluginCommand( + "misery", + "Increase the intensity of negative dwarven thoughts.", + misery)); return CR_OK; } diff --git a/plugins/mode.cpp b/plugins/mode.cpp index 5e58c50a0..aeda6380b 100644 --- a/plugins/mode.cpp +++ b/plugins/mode.cpp @@ -19,12 +19,10 @@ DFHACK_PLUGIN("mode"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "mode","View, change and track game mode.", - mode, true, - " Without any parameters, this command prints the current game mode\n" - " You can interactively set the game mode with 'mode set'.\n" - "!!Setting the game modes can be dangerous and break your game!!\n" - )); + "mode", + "View, change and track game mode.", + mode, + true)); return CR_OK; } diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index 618252ad3..124664fa2 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -20,6 +20,7 @@ #include "uicommon.h" #include "TileTypes.h" #include "DataFuncs.h" +#include "Debug.h" DFHACK_PLUGIN("mousequery"); REQUIRE_GLOBAL(enabler); @@ -32,6 +33,10 @@ using namespace df::enums::ui_sidebar_mode; #define PLUGIN_VERSION 0.18 +namespace DFHack { + DBG_DECLARE(mousequery,log,DebugCategory::LINFO); +} + static int32_t last_clicked_x, last_clicked_y, last_clicked_z; static int32_t last_pos_x, last_pos_y, last_pos_z; static df::coord last_move_pos; @@ -51,24 +56,16 @@ static uint32_t scroll_delay = 100; static bool awaiting_lbut_up, awaiting_rbut_up; static enum { None, Left, Right } drag_mode; -static df::coord get_mouse_pos(int32_t &mx, int32_t &my) +static df::coord get_mouse_pos(int32_t &mx, int32_t &my, int32_t &depth) { - df::coord pos; - pos.x = -30000; - - if (!enabler->tracking_on) - return pos; + df::coord pos = Gui::getMousePos(); - if (!Gui::getMousePos(mx, my)) - return pos; + depth = Gui::getDepthAt(pos.x, pos.y); + pos.z -= depth; - int32_t vx, vy, vz; - if (!Gui::getViewCoords(vx, vy, vz)) - return pos; - - pos.x = vx + mx - 1; - pos.y = vy + my - 1; - pos.z = vz - Gui::getDepthAt(mx, my); + df::coord vpos = Gui::getViewportPos(); + mx = pos.x - vpos.x + 1; + my = pos.y - vpos.y + 1; return pos; } @@ -296,10 +293,10 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return false; } - bool handleLeft(df::coord &mpos, int32_t mx, int32_t my) + bool handleLeft(df::coord &mpos, int32_t mx, int32_t my, int32_t depth) { if (!(Core::getInstance().getModstate() & DFH_MOD_SHIFT)) - mpos.z += Gui::getDepthAt(mx, my); + mpos.z += depth; bool cursor_still_here = (last_clicked_x == mpos.x && last_clicked_y == mpos.y && last_clicked_z == mpos.z); last_clicked_x = mpos.x; @@ -449,8 +446,8 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest bool handleMouse(const set *input) { - int32_t mx, my; - auto mpos = get_mouse_pos(mx, my); + int32_t mx, my, depth; + auto mpos = get_mouse_pos(mx, my, depth); if (mpos.x == -30000) return false; @@ -463,7 +460,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest last_move_pos = mpos; } else - return handleLeft(mpos, mx, my); + return handleLeft(mpos, mx, my, depth); } else if (enabler->mouse_rbut) { @@ -536,6 +533,8 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (mpos.x == x && mpos.y == y && mpos.z == z) return; + DEBUG(log).print("moving cursor to %d, %d, %d\n", + mpos.x, mpos.y, mpos.z); Gui::setCursorCoords(mpos.x, mpos.y, mpos.z); Gui::refreshSidebar(); } @@ -572,11 +571,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest auto dims = Gui::getDwarfmodeViewDims(); - int32_t mx, my; - auto mpos = get_mouse_pos(mx, my); + int32_t mx, my, depth; + auto mpos = get_mouse_pos(mx, my, depth); bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000; - if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2) - mpos_valid = false; // Check if in lever binding mode if (Gui::getFocusString(Core::getTopViewscreen()) == @@ -588,7 +585,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (awaiting_lbut_up && !enabler->mouse_lbut_down) { awaiting_lbut_up = false; - handleLeft(mpos, mx, my); + handleLeft(mpos, mx, my, depth); } if (awaiting_rbut_up && !enabler->mouse_rbut_down) @@ -723,7 +720,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest } } - OutputString(color, mx, my, "X", false, 0, 0, true); + Screen::paintTile(Screen::Pen('X', color), mx, my, true); return; } @@ -916,19 +913,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" - " Set delay when edge scrolling in tracking mode. Omit amount to display current setting.\n" - )); + "mousequery", + "Add mouse functionality to Dwarf Fortress.", + mousequery_cmd)); return CR_OK; } diff --git a/plugins/nestboxes.cpp b/plugins/nestboxes.cpp index 8b9a33289..403a8cb37 100644 --- a/plugins/nestboxes.cpp +++ b/plugins/nestboxes.cpp @@ -69,13 +69,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & parameters); @@ -48,21 +49,8 @@ DFhackCExport command_result plugin_init(color_ostream & out, std::vector files; + if (0 < Filesystem::listdir_recursive(ORDERS_LIBRARY_DIR, files, 0, false)) { + // if the library directory doesn't exist, just skip it + return; + } + + if (files.empty()) { + // if no files in the library directory, just skip it + return; + } + + for (auto it : files) + { + if (it.second) + continue; // skip directories + std::string name = it.first; + if (name.length() <= 5 || name.rfind(".json") != name.length() - 5) + continue; // skip non-.json files + name.resize(name.length() - 5); + out << "library/" << name << std::endl; + } +} + static command_result orders_list_command(color_ostream & out) { // use listdir_recursive instead of listdir even though orders doesn't @@ -150,6 +162,8 @@ static command_result orders_list_command(color_ostream & out) out << name << std::endl; } + list_library(out); + return CR_OK; } @@ -467,7 +481,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri condition["order"] = it2->order_id; condition["condition"] = enum_item_key(it2->condition); - // TODO: anon_1 + // TODO: unk_1 conditions.append(condition); } @@ -475,7 +489,7 @@ static command_result orders_export_command(color_ostream & out, const std::stri order["order_conditions"] = conditions; } - // TODO: anon_1 + // TODO: items orders.append(order); } @@ -540,10 +554,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete order; - out << COLOR_LIGHTRED << "Invalid item subtype for imported manager order: " << enum_item_key(order->item_type) << ":" << it["item_subtype"].asString() << std::endl; + delete order; + return CR_FAILURE; } } @@ -716,10 +730,10 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) } else { - delete condition; - out << COLOR_YELLOW << "Invalid item condition item subtype for imported manager order: " << enum_item_key(condition->item_type) << ":" << it2["item_subtype"].asString() << std::endl; + delete condition; + continue; } } @@ -873,13 +887,13 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) continue; } - // TODO: anon_1 + // TODO: unk_1 order->order_conditions.push_back(condition); } } - // TODO: anon_1 + // TODO: items world->manager_orders.push_back(order); } @@ -889,12 +903,20 @@ static command_result orders_import(color_ostream &out, Json::Value &orders) static command_result orders_import_command(color_ostream & out, const std::string & name) { - if (!is_safe_filename(out, name)) + std::string fname = name; + bool is_library = false; + if (0 == name.find("library/")) { + is_library = true; + fname = name.substr(8); + } + + if (!is_safe_filename(out, fname)) { return CR_WRONG_USAGE; } - const std::string filename(ORDERS_DIR + "/" + name + ".json"); + const std::string filename((is_library ? ORDERS_LIBRARY_DIR : ORDERS_DIR) + + "/" + fname + ".json"); Json::Value orders; { diff --git a/plugins/overlay.cpp b/plugins/overlay.cpp new file mode 100644 index 000000000..0c63e53e8 --- /dev/null +++ b/plugins/overlay.cpp @@ -0,0 +1,332 @@ +#include "df/viewscreen_adopt_regionst.h" +#include "df/viewscreen_adventure_logst.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_assign_display_itemst.h" +#include "df/viewscreen_barterst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_buildingst.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/viewscreen_civlistst.h" +#include "df/viewscreen_counterintelligencest.h" +#include "df/viewscreen_createquotast.h" +#include "df/viewscreen_customize_unitst.h" +#include "df/viewscreen_dungeonmodest.h" +#include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/viewscreen_dungeon_wrestlest.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_entityst.h" +#include "df/viewscreen_export_graphical_mapst.h" +#include "df/viewscreen_export_regionst.h" +#include "df/viewscreen_game_cleanerst.h" +#include "df/viewscreen_image_creator_mode.h" +#include "df/viewscreen_image_creatorst.h" +#include "df/viewscreen_itemst.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_jobmanagementst.h" +#include "df/viewscreen_jobst.h" +#include "df/viewscreen_justicest.h" +#include "df/viewscreen_kitchenpref_page.h" +#include "df/viewscreen_kitchenprefst.h" +#include "df/viewscreen_layer_arena_creaturest.h" +#include "df/viewscreen_layer_assigntradest.h" +#include "df/viewscreen_layer_choose_language_namest.h" +#include "df/viewscreen_layer_currencyst.h" +#include "df/viewscreen_layer_export_play_mapst.h" +#include "df/viewscreen_layer.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_musicsoundst.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.h" +#include "df/viewscreen_layer_reactionst.h" +#include "df/viewscreen_layer_squad_schedulest.h" +#include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_stone_restrictionst.h" +#include "df/viewscreen_layer_unit_actionst.h" +#include "df/viewscreen_layer_unit_healthst.h" +#include "df/viewscreen_layer_unit_relationshipst.h" +#include "df/viewscreen_layer_world_gen_param_presetst.h" +#include "df/viewscreen_layer_world_gen_paramst.h" +#include "df/viewscreen_legendsst.h" +#include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_locationsst.h" +#include "df/viewscreen_meetingst.h" +#include "df/viewscreen_movieplayerst.h" +#include "df/viewscreen_new_regionst.h" +#include "df/viewscreen_noblest.h" +#include "df/viewscreen_optionst.h" +#include "df/viewscreen_overallstatusst.h" +#include "df/viewscreen_petitionsst.h" +#include "df/viewscreen_petst.h" +#include "df/viewscreen_pricest.h" +#include "df/viewscreen_reportlistst.h" +#include "df/viewscreen_requestagreementst.h" +#include "df/viewscreen_savegamest.h" +#include "df/viewscreen_selectitemst.h" +#include "df/viewscreen_setupadventurest.h" +#include "df/viewscreen_setupdwarfgamest.h" +#include "df/viewscreen_storesst.h" +#include "df/viewscreen_textviewerst.h" +#include "df/viewscreen_titlest.h" +#include "df/viewscreen_topicmeeting_fill_land_holder_positionsst.h" +#include "df/viewscreen_topicmeetingst.h" +#include "df/viewscreen_topicmeeting_takerequestsst.h" +#include "df/viewscreen_tradeagreementst.h" +#include "df/viewscreen_tradelistst.h" +#include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_treasurelistst.h" +#include "df/viewscreen_unitlist_page.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_unitst.h" +#include "df/viewscreen_update_regionst.h" +#include "df/viewscreen_wagesst.h" +#include "df/viewscreen_workquota_conditionst.h" +#include "df/viewscreen_workquota_detailsst.h" +#include "df/viewscreen_workshop_profilest.h" + +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" +#include "VTableInterpose.h" + +using namespace DFHack; + +DFHACK_PLUGIN("overlay"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +namespace DFHack { + DBG_DECLARE(overlay, control, DebugCategory::LINFO); + DBG_DECLARE(overlay, event, DebugCategory::LINFO); +} + +template +struct viewscreen_overlay : T { + typedef T interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, logic, ()) { + INTERPOSE_NEXT(logic)(); + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *input)) { + INTERPOSE_NEXT(feed)(input); + } + DEFINE_VMETHOD_INTERPOSE(void, render, ()) { + INTERPOSE_NEXT(render)(); + } +}; + +#define IMPLEMENT_HOOKS(screen) \ + typedef viewscreen_overlay screen##_overlay; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, logic, 100); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, feed, 100); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(screen##_overlay, render, 100); + +IMPLEMENT_HOOKS(adopt_region) +IMPLEMENT_HOOKS(adventure_log) +IMPLEMENT_HOOKS(announcelist) +IMPLEMENT_HOOKS(assign_display_item) +IMPLEMENT_HOOKS(barter) +IMPLEMENT_HOOKS(buildinglist) +IMPLEMENT_HOOKS(building) +IMPLEMENT_HOOKS(choose_start_site) +IMPLEMENT_HOOKS(civlist) +IMPLEMENT_HOOKS(counterintelligence) +IMPLEMENT_HOOKS(createquota) +IMPLEMENT_HOOKS(customize_unit) +IMPLEMENT_HOOKS(dungeonmode) +IMPLEMENT_HOOKS(dungeon_monsterstatus) +IMPLEMENT_HOOKS(dungeon_wrestle) +IMPLEMENT_HOOKS(dwarfmode) +IMPLEMENT_HOOKS(entity) +IMPLEMENT_HOOKS(export_graphical_map) +IMPLEMENT_HOOKS(export_region) +IMPLEMENT_HOOKS(game_cleaner) +IMPLEMENT_HOOKS(image_creator) +IMPLEMENT_HOOKS(item) +IMPLEMENT_HOOKS(joblist) +IMPLEMENT_HOOKS(jobmanagement) +IMPLEMENT_HOOKS(job) +IMPLEMENT_HOOKS(justice) +IMPLEMENT_HOOKS(kitchenpref) +IMPLEMENT_HOOKS(layer_arena_creature) +IMPLEMENT_HOOKS(layer_assigntrade) +IMPLEMENT_HOOKS(layer_choose_language_name) +IMPLEMENT_HOOKS(layer_currency) +IMPLEMENT_HOOKS(layer_export_play_map) +IMPLEMENT_HOOKS(layer_military) +IMPLEMENT_HOOKS(layer_musicsound) +IMPLEMENT_HOOKS(layer_noblelist) +IMPLEMENT_HOOKS(layer_overall_health) +IMPLEMENT_HOOKS(layer_reaction) +IMPLEMENT_HOOKS(layer_squad_schedule) +IMPLEMENT_HOOKS(layer_stockpile) +IMPLEMENT_HOOKS(layer_stone_restriction) +IMPLEMENT_HOOKS(layer_unit_action) +IMPLEMENT_HOOKS(layer_unit_health) +IMPLEMENT_HOOKS(layer_unit_relationship) +IMPLEMENT_HOOKS(layer_world_gen_param_preset) +IMPLEMENT_HOOKS(layer_world_gen_param) +IMPLEMENT_HOOKS(legends) +IMPLEMENT_HOOKS(loadgame) +IMPLEMENT_HOOKS(locations) +IMPLEMENT_HOOKS(meeting) +IMPLEMENT_HOOKS(movieplayer) +IMPLEMENT_HOOKS(new_region) +IMPLEMENT_HOOKS(noble) +IMPLEMENT_HOOKS(option) +IMPLEMENT_HOOKS(overallstatus) +IMPLEMENT_HOOKS(petitions) +IMPLEMENT_HOOKS(pet) +IMPLEMENT_HOOKS(price) +IMPLEMENT_HOOKS(reportlist) +IMPLEMENT_HOOKS(requestagreement) +IMPLEMENT_HOOKS(savegame) +IMPLEMENT_HOOKS(selectitem) +IMPLEMENT_HOOKS(setupadventure) +IMPLEMENT_HOOKS(setupdwarfgame) +IMPLEMENT_HOOKS(stores) +IMPLEMENT_HOOKS(textviewer) +IMPLEMENT_HOOKS(title) +IMPLEMENT_HOOKS(topicmeeting_fill_land_holder_positions) +IMPLEMENT_HOOKS(topicmeeting) +IMPLEMENT_HOOKS(topicmeeting_takerequests) +IMPLEMENT_HOOKS(tradeagreement) +IMPLEMENT_HOOKS(tradegoods) +IMPLEMENT_HOOKS(tradelist) +IMPLEMENT_HOOKS(treasurelist) +IMPLEMENT_HOOKS(unitlist) +IMPLEMENT_HOOKS(unit) +IMPLEMENT_HOOKS(update_region) +IMPLEMENT_HOOKS(wages) +IMPLEMENT_HOOKS(workquota_condition) +IMPLEMENT_HOOKS(workquota_details) +IMPLEMENT_HOOKS(workshop_profile) + +#undef IMPLEMENT_HOOKS + +#define INTERPOSE_HOOKS_FAILED(screen) \ + !INTERPOSE_HOOK(screen##_overlay, logic).apply(enable) || \ + !INTERPOSE_HOOK(screen##_overlay, feed).apply(enable) || \ + !INTERPOSE_HOOK(screen##_overlay, render).apply(enable) + +DFhackCExport command_result plugin_enable(color_ostream &, bool enable) { + if (is_enabled == enable) + return CR_OK; + + DEBUG(control).print("%sing interpose hooks\n", enable ? "enabl" : "disabl"); + + if (INTERPOSE_HOOKS_FAILED(adopt_region) || + INTERPOSE_HOOKS_FAILED(adventure_log) || + INTERPOSE_HOOKS_FAILED(announcelist) || + INTERPOSE_HOOKS_FAILED(assign_display_item) || + INTERPOSE_HOOKS_FAILED(barter) || + INTERPOSE_HOOKS_FAILED(buildinglist) || + INTERPOSE_HOOKS_FAILED(building) || + INTERPOSE_HOOKS_FAILED(choose_start_site) || + INTERPOSE_HOOKS_FAILED(civlist) || + INTERPOSE_HOOKS_FAILED(counterintelligence) || + INTERPOSE_HOOKS_FAILED(createquota) || + INTERPOSE_HOOKS_FAILED(customize_unit) || + INTERPOSE_HOOKS_FAILED(dungeonmode) || + INTERPOSE_HOOKS_FAILED(dungeon_monsterstatus) || + INTERPOSE_HOOKS_FAILED(dungeon_wrestle) || + INTERPOSE_HOOKS_FAILED(dwarfmode) || + INTERPOSE_HOOKS_FAILED(entity) || + INTERPOSE_HOOKS_FAILED(export_graphical_map) || + INTERPOSE_HOOKS_FAILED(export_region) || + INTERPOSE_HOOKS_FAILED(game_cleaner) || + INTERPOSE_HOOKS_FAILED(image_creator) || + INTERPOSE_HOOKS_FAILED(item) || + INTERPOSE_HOOKS_FAILED(joblist) || + INTERPOSE_HOOKS_FAILED(jobmanagement) || + INTERPOSE_HOOKS_FAILED(job) || + INTERPOSE_HOOKS_FAILED(justice) || + INTERPOSE_HOOKS_FAILED(kitchenpref) || + INTERPOSE_HOOKS_FAILED(layer_arena_creature) || + INTERPOSE_HOOKS_FAILED(layer_assigntrade) || + INTERPOSE_HOOKS_FAILED(layer_choose_language_name) || + INTERPOSE_HOOKS_FAILED(layer_currency) || + INTERPOSE_HOOKS_FAILED(layer_export_play_map) || + INTERPOSE_HOOKS_FAILED(layer_military) || + INTERPOSE_HOOKS_FAILED(layer_musicsound) || + INTERPOSE_HOOKS_FAILED(layer_noblelist) || + INTERPOSE_HOOKS_FAILED(layer_overall_health) || + INTERPOSE_HOOKS_FAILED(layer_reaction) || + INTERPOSE_HOOKS_FAILED(layer_squad_schedule) || + INTERPOSE_HOOKS_FAILED(layer_stockpile) || + INTERPOSE_HOOKS_FAILED(layer_stone_restriction) || + INTERPOSE_HOOKS_FAILED(layer_unit_action) || + INTERPOSE_HOOKS_FAILED(layer_unit_health) || + INTERPOSE_HOOKS_FAILED(layer_unit_relationship) || + INTERPOSE_HOOKS_FAILED(layer_world_gen_param_preset) || + INTERPOSE_HOOKS_FAILED(layer_world_gen_param) || + INTERPOSE_HOOKS_FAILED(legends) || + INTERPOSE_HOOKS_FAILED(loadgame) || + INTERPOSE_HOOKS_FAILED(locations) || + INTERPOSE_HOOKS_FAILED(meeting) || + INTERPOSE_HOOKS_FAILED(movieplayer) || + INTERPOSE_HOOKS_FAILED(new_region) || + INTERPOSE_HOOKS_FAILED(noble) || + INTERPOSE_HOOKS_FAILED(option) || + INTERPOSE_HOOKS_FAILED(overallstatus) || + INTERPOSE_HOOKS_FAILED(petitions) || + INTERPOSE_HOOKS_FAILED(pet) || + INTERPOSE_HOOKS_FAILED(price) || + INTERPOSE_HOOKS_FAILED(reportlist) || + INTERPOSE_HOOKS_FAILED(requestagreement) || + INTERPOSE_HOOKS_FAILED(savegame) || + INTERPOSE_HOOKS_FAILED(selectitem) || + INTERPOSE_HOOKS_FAILED(setupadventure) || + INTERPOSE_HOOKS_FAILED(setupdwarfgame) || + INTERPOSE_HOOKS_FAILED(stores) || + INTERPOSE_HOOKS_FAILED(textviewer) || + INTERPOSE_HOOKS_FAILED(title) || + INTERPOSE_HOOKS_FAILED(topicmeeting_fill_land_holder_positions) || + INTERPOSE_HOOKS_FAILED(topicmeeting) || + INTERPOSE_HOOKS_FAILED(topicmeeting_takerequests) || + INTERPOSE_HOOKS_FAILED(tradeagreement) || + INTERPOSE_HOOKS_FAILED(tradegoods) || + INTERPOSE_HOOKS_FAILED(tradelist) || + INTERPOSE_HOOKS_FAILED(treasurelist) || + INTERPOSE_HOOKS_FAILED(unitlist) || + INTERPOSE_HOOKS_FAILED(unit) || + INTERPOSE_HOOKS_FAILED(update_region) || + INTERPOSE_HOOKS_FAILED(wages) || + INTERPOSE_HOOKS_FAILED(workquota_condition) || + INTERPOSE_HOOKS_FAILED(workquota_details) || + INTERPOSE_HOOKS_FAILED(workshop_profile)) + return CR_FAILURE; + + is_enabled = enable; + return CR_OK; +} + +#undef INTERPOSE_HOOKS_FAILED + +static command_result overlay_cmd(color_ostream &out, std::vector & parameters) { + if (DBG_NAME(control).isEnabled(DebugCategory::LDEBUG)) { + DEBUG(control).print("interpreting command with %zu parameters:\n", + parameters.size()); + for (auto ¶m : parameters) { + DEBUG(control).print(" %s\n", param.c_str()); + } + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back( + PluginCommand( + "overlay", + "Manage onscreen widgets.", + overlay_cmd)); + + return plugin_enable(out, true); +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + return plugin_enable(out, false); +} + +DFhackCExport command_result plugin_onupdate (color_ostream &out) { + return CR_OK; +} diff --git a/plugins/petcapRemover.cpp b/plugins/petcapRemover.cpp index 7d6588cbc..2c1de318d 100644 --- a/plugins/petcapRemover.cpp +++ b/plugins/petcapRemover.cpp @@ -36,18 +36,8 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector params; + return petcapRemover(out, params); } } diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 69e712ef8..2325c01e3 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -32,10 +32,7 @@ command_result df_grow (color_ostream &out, vector & parameters) { if(parameters[i] == "help" || parameters[i] == "?") { - out.print("Usage:\n" - "This command turns all living saplings on the map into full-grown trees.\n" - "With active cursor, work on the targetted one only.\n"); - return CR_OK; + return CR_WRONG_USAGE; } } @@ -91,11 +88,7 @@ command_result df_createplant (color_ostream &out, vector & parameters) { if ((parameters.size() != 1) || (parameters[0] == "help" || parameters[0] == "?")) { - out.print("Usage:\n" - "Create a new plant at the cursor.\n" - "Specify the type of plant to create by its raw ID (e.g. TOWER_CAP or MUSHROOM_HELMET_PLUMP).\n" - "Only shrubs and saplings can be placed, and they must be located on a dirt or grass floor.\n"); - return CR_OK; + return CR_WRONG_USAGE; } CoreSuspender suspend; @@ -201,10 +194,10 @@ command_result df_plant (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("plant", "Plant creation and removal.", df_plant, false, - "Command to create, grow or remove plants on the map. For more details, check the subcommand help :\n" - "plant grow help - Grows saplings into trees.\n" - "plant create help - Create a new plant.\n")); + commands.push_back(PluginCommand( + "plant", + "Grow shrubs or trees.", + df_plant)); return CR_OK; } diff --git a/plugins/probe.cpp b/plugins/probe.cpp index ba7c42ede..7781812b9 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -49,20 +49,14 @@ command_result df_bprobe (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("probe", - "A tile probe", - df_probe, - false, - "Hover the cursor over a tile to view its properties.\n")); + "Display information about the selected tile.", + df_probe)); commands.push_back(PluginCommand("cprobe", - "A creature probe", - df_cprobe, - false, - "Select a creature to view its properties.\n")); + "Display information about the selected creature.", + df_cprobe)); commands.push_back(PluginCommand("bprobe", - "A simple building probe", - df_bprobe, - false, - "Select a building to view its properties.\n")); + "Display information about the selected building.", + df_bprobe)); return CR_OK; } @@ -191,7 +185,7 @@ command_result df_probe (color_ostream &out, vector & parameters) } auto &block = *b->getRaw(); - out.print("block addr: 0x%p\n\n", &block); + out.print("block addr: %p\n\n", &block); /* if (showBlock) { @@ -340,7 +334,7 @@ command_result df_probe (color_ostream &out, vector & parameters) out.print("%-16s", ""); out.print(" %4d", block.local_feature); out.print(" (%2d)", local.type); - out.print(" addr 0x%p ", local.origin); + out.print(" addr %p ", local.origin); out.print(" %s\n", sa_feature(local.type)); } PRINT_FLAG( des, feature_global ); diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 7bc8f2388..e75c967dc 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -17,6 +17,7 @@ using namespace std; #include "Core.h" #include "Console.h" #include "Export.h" +#include "LuaTools.h" #include "PluginManager.h" #include "modules/Gui.h" #include "modules/MapCache.h" @@ -44,6 +45,50 @@ using df::coord2d; DFHACK_PLUGIN("prospector"); REQUIRE_GLOBAL(world); +struct prospect_options { + // whether to display help + bool help = false; + + // whether to scan the whole map or just the unhidden tiles + bool hidden = false; + + // whether to also show material values + bool value = false; + + // whether to show adamantine tube z-levels + bool tube = false; + + // which report sections to show + bool summary = true; + bool liquids = true; + bool layers = true; + bool features = true; + bool ores = true; + bool gems = true; + bool veins = true; + bool shrubs = true; + bool trees = true; + + static struct_identity _identity; +}; +static const struct_field_info prospect_options_fields[] = { + { struct_field_info::PRIMITIVE, "help", offsetof(prospect_options, help), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "hidden", offsetof(prospect_options, hidden), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "value", offsetof(prospect_options, value), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "tube", offsetof(prospect_options, tube), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "summary", offsetof(prospect_options, summary), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "liquids", offsetof(prospect_options, liquids), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "layers", offsetof(prospect_options, layers), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "features", offsetof(prospect_options, features), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "ores", offsetof(prospect_options, ores), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "gems", offsetof(prospect_options, gems), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "veins", offsetof(prospect_options, veins), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "shrubs", offsetof(prospect_options, shrubs), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::PRIMITIVE, "trees", offsetof(prospect_options, trees), &df::identity_traits::identity, 0, 0 }, + { struct_field_info::END } +}; +struct_identity prospect_options::_identity(sizeof(prospect_options), &df::allocator_fn, NULL, "prospect_options", NULL, prospect_options_fields); + struct matdata { const static int invalid_z = -30000; @@ -85,10 +130,10 @@ bool operator>(const matdata & q1, const matdata & q2) return q1.count > q2.count; } -template -struct shallower : public binary_function<_Tp, _Tp, bool> +template +struct shallower { - bool operator()(const _Tp& top, const _Tp& bottom) const + bool operator()(const Tp& top, const Tp& bottom) const { float topavg = (top.lower_z + top.upper_z)/2.0f; float btmavg = (bottom.lower_z + bottom.upper_z)/2.0f; @@ -123,9 +168,9 @@ 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 <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; else - con <<" Z:" << std::setw(4) << data.lower_z << std::endl; + con <<" Z:" << std::setw(4) << data.lower_z << std::endl; } static int getValue(const df::inorganic_raw &info) @@ -139,7 +184,7 @@ static int getValue(const df::plant_raw &info) } template class P> -void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool show_value) +void printMats(color_ostream &con, MatMap &mat, std::vector &materials, const prospect_options &options) { unsigned int total = 0; MatSorter sorting_vector; @@ -161,7 +206,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool T* mat = materials[it->first]; // Somewhat of a hack, but it works because df::inorganic_raw and df::plant_raw both have a field named "id" con << std::setw(25) << mat->id << " : "; - if (show_value) + if (options.value) con << std::setw(3) << getValue(*mat) << " : "; printMatdata(con, it->second); total += it->second.count; @@ -171,7 +216,7 @@ void printMats(color_ostream &con, MatMap &mat, std::vector &materials, bool } void printVeins(color_ostream &con, MatMap &mat_map, - DFHack::Materials* mats, bool show_value) + const prospect_options &options) { MatMap ores; MatMap gems; @@ -194,14 +239,20 @@ void printVeins(color_ostream &con, MatMap &mat_map, rest[kv.first] = kv.second; } - con << "Ores:" << std::endl; - printMats(con, ores, world->raws.inorganics, show_value); + if (options.ores) { + con << "Ores:" << std::endl; + printMats(con, ores, world->raws.inorganics, options); + } - con << "Gems:" << std::endl; - printMats(con, gems, world->raws.inorganics, show_value); + if (options.gems) { + con << "Gems:" << std::endl; + printMats(con, gems, world->raws.inorganics, options); + } - con << "Other vein stone:" << std::endl; - printMats(con, rest, world->raws.inorganics, show_value); + if (options.veins) { + con << "Other vein stone:" << std::endl; + printMats(con, rest, world->raws.inorganics, options); + } } command_result prospector (color_ostream &out, vector & parameters); @@ -209,21 +260,9 @@ command_result prospector (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "prospect", "Show stats of available raw resources.", - prospector, false, - " Prints a big list of all the present minerals.\n" - " By default, only the visible part of the map is scanned.\n" - "Options:\n" - " all - Scan the whole map, as if it was revealed.\n" - " value - Show material value in the output. Most useful for gems.\n" - " hell - Show the Z range of HFS tubes. Implies 'all'.\n" - "Pre-embark estimate:\n" - " If called during the embark selection screen, displays\n" - " an estimate of layer stone availability. If the 'all'\n" - " option is specified, also estimates veins.\n" - " The estimate is computed either for 1 embark tile of the\n" - " blinking biome, or for all tiles of the embark rectangle.\n" - )); + "prospect", + "Show raw resources available on the map.", + prospector)); return CR_OK; } @@ -522,8 +561,9 @@ bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &laye return true; } -static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, - bool showHidden, bool showValue) +static command_result embark_prospector(color_ostream &out, + df::viewscreen_choose_start_sitest *screen, + const prospect_options &options) { if (!world || !world->world_data) { @@ -549,12 +589,6 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos // Compute biomes std::map biomes; - /*if (screen->biome_highlighted) - { - out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); - biomes[screen->biome_rgn[screen->biome_idx]]++; - }*/ - for (int x = screen->location.embark_pos_min.x; x <= 15 && x <= screen->location.embark_pos_max.x; x++) { for (int y = screen->location.embark_pos_min.y; y <= 15 && y <= screen->location.embark_pos_max.y; y++) @@ -570,12 +604,14 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos } // Print the report - out << "Layer materials:" << std::endl; - printMats(out, layerMats, world->raws.inorganics, showValue); + if (options.layers) { + out << "Layer materials:" << std::endl; + printMats(out, layerMats, world->raws.inorganics, options); + } - if (showHidden) { + if (options.hidden) { DFHack::Materials *mats = Core::getInstance().getMaterials(); - printVeins(out, veinMats, mats, showValue); + printVeins(out, veinMats, options); mats->Finish(); } @@ -587,40 +623,8 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos return CR_OK; } -command_result prospector (color_ostream &con, vector & parameters) -{ - bool showHidden = false; - bool showPlants = true; - bool showSlade = true; - bool showTemple = true; - bool showValue = false; - bool showTube = false; - - for(size_t i = 0; i < parameters.size();i++) - { - if (parameters[i] == "all") - { - showHidden = true; - } - else if (parameters[i] == "value") - { - showValue = true; - } - else if (parameters[i] == "hell") - { - showHidden = showTube = true; - } - else - return CR_WRONG_USAGE; - } - - CoreSuspender suspend; - - // Embark screen active: estimate using world geology data - auto screen = Gui::getViewscreenByType(0); - if (screen) - return embark_prospector(con, screen, showHidden, showValue); - +static command_result map_prospector(color_ostream &con, + const prospect_options &options) { if (!Maps::IsValid()) { con.printerr("Map is not available!\n"); @@ -636,7 +640,6 @@ command_result prospector (color_ostream &con, vector & parameters) DFHack::t_feature blockFeatureGlobal; DFHack::t_feature blockFeatureLocal; - bool hasAquifer = false; bool hasDemonTemple = false; bool hasLair = false; MatMap baseMats; @@ -680,7 +683,7 @@ command_result prospector (color_ostream &con, vector & parameters) df::tile_occupancy occ = b->OccupancyAt(coord); // Skip hidden tiles - if (!showHidden && des.bits.hidden) + if (!options.hidden && des.bits.hidden) { continue; } @@ -688,7 +691,6 @@ command_result prospector (color_ostream &con, vector & parameters) // Check for aquifer if (des.bits.water_table) { - hasAquifer = true; aquiferTiles.add(global_z); } @@ -752,14 +754,13 @@ command_result prospector (color_ostream &con, vector & parameters) { veinMats[blockFeatureLocal.sub_material].add(global_z); } - else if (showTemple - && blockFeatureLocal.type == feature_type::deep_surface_portal) + else if (blockFeatureLocal.type == feature_type::deep_surface_portal) { hasDemonTemple = true; } } - if (showSlade && blockFeatureGlobal.type != -1 && des.bits.feature_global + if (blockFeatureGlobal.type != -1 && des.bits.feature_global && blockFeatureGlobal.type == feature_type::underworld_from_layer && blockFeatureGlobal.main_material == 0) // stone { @@ -777,7 +778,7 @@ command_result prospector (color_ostream &con, vector & parameters) // Check plants this way, as the other way wasn't getting them all // and we can check visibility more easily here - if (showPlants) + if (options.shrubs) { auto block = Maps::getBlockColumn(b_x,b_y); vector *plants = block ? &block->plants : NULL; @@ -790,7 +791,7 @@ command_result prospector (color_ostream &con, vector & parameters) continue; df::coord2d loc(plant.pos.x, plant.pos.y); loc = loc % 16; - if (showHidden || !b->DesignationAt(loc).bits.hidden) + if (options.hidden || !b->DesignationAt(loc).bits.hidden) { if(plant.flags.bits.is_shrub) plantMats[plant.material].add(global_z); @@ -810,15 +811,18 @@ command_result prospector (color_ostream &con, vector & parameters) MatMap::const_iterator it; - con << "Base materials:" << std::endl; - for (it = baseMats.begin(); it != baseMats.end(); ++it) - { - con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + if (options.summary) { + con << "Base materials:" << std::endl; + for (it = baseMats.begin(); it != baseMats.end(); ++it) + { + con << std::setw(25) << ENUM_KEY_STR(tiletype_material,(df::tiletype_material)it->first) << " : " << it->second.count << std::endl; + } + con << std::endl; } - if (liquidWater.count || liquidMagma.count) + if (options.liquids && (liquidWater.count || liquidMagma.count)) { - con << std::endl << "Liquids:" << std::endl; + con << "Liquids:" << std::endl; if (liquidWater.count) { con << std::setw(25) << "WATER" << " : "; @@ -829,51 +833,108 @@ command_result prospector (color_ostream &con, vector & parameters) con << std::setw(25) << "MAGMA" << " : "; printMatdata(con, liquidMagma); } + con << std::endl; } - con << std::endl << "Layer materials:" << std::endl; - printMats(con, layerMats, world->raws.inorganics, showValue); - - printVeins(con, veinMats, mats, showValue); - - if (showPlants) - { - con << "Shrubs:" << std::endl; - printMats(con, plantMats, world->raws.plants.all, showValue); - con << "Wood in trees:" << std::endl; - printMats(con, treeMats, world->raws.plants.all, showValue); + if (options.layers) { + con << "Layer materials:" << std::endl; + printMats(con, layerMats, world->raws.inorganics, options); } - if (hasAquifer) - { - con << "Has aquifer"; + if (options.features) { + con << "Features:" << std::endl; + + bool hasFeature = false; if (aquiferTiles.count) { - con << " : "; + con << std::setw(25) << "Has aquifer" << " : "; + if (options.value) + con << " "; printMatdata(con, aquiferTiles); + hasFeature = true; } - else - con << std::endl; - } - if (showTube && tubeTiles.count) - { - con << "Has HFS tubes : "; - printMatdata(con, tubeTiles); + if (options.tube && tubeTiles.count) + { + con << std::setw(25) << "Has HFS tubes" << " : "; + if (options.value) + con << " "; + printMatdata(con, tubeTiles, true); + hasFeature = true; + } + + if (hasDemonTemple) + { + con << std::setw(25) << "Has demon temple" << std::endl; + hasFeature = true; + } + + if (hasLair) + { + con << std::setw(25) << "Has lair" << std::endl; + hasFeature = true; + } + + if (!hasFeature) + con << std::setw(25) << "None" << std::endl; + + con << std::endl; } - if (hasDemonTemple) - { - con << "Has demon temple" << std::endl; + printVeins(con, veinMats, options); + + if (options.shrubs) { + con << "Shrubs:" << std::endl; + printMats(con, plantMats, world->raws.plants.all, options); } - if (hasLair) - { - con << "Has lair" << std::endl; + if (options.trees) { + con << "Wood in trees:" << std::endl; + printMats(con, treeMats, world->raws.plants.all, options); } // Cleanup mats->Finish(); - con << std::endl; + return CR_OK; } + +static bool get_options(color_ostream &out, + prospect_options &opts, + const vector ¶meters) +{ + auto L = Lua::Core::State; + Lua::StackUnwinder top(L); + + if (!lua_checkstack(L, parameters.size() + 2) || + !Lua::PushModulePublic( + out, L, "plugins.prospector", "parse_commandline")) { + out.printerr("Failed to load prospector Lua code\n"); + return false; + } + + Lua::Push(L, &opts); + + for (const string ¶m : parameters) + Lua::Push(L, param); + + if (!Lua::SafeCall(out, L, parameters.size() + 1, 0)) + return false; + + return true; +} + +command_result prospector(color_ostream &con, vector & parameters) +{ + CoreSuspender suspend; + + prospect_options options; + if (!get_options(con, options, parameters) || options.help) + return CR_WRONG_USAGE; + + // Embark screen active: estimate using world geology data + auto screen = Gui::getViewscreenByType(0); + return screen ? + embark_prospector(con, screen, options) : + map_prospector(con, options); +} diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index 0cac5bd77..5d2a6450c 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -29,8 +29,10 @@ command_result df_regrass (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("regrass", "Regrows surface grass.", df_regrass, false, - "Specify parameter 'max' to set all grass types to full density, otherwise only one type of grass will be restored per tile.\n")); + commands.push_back(PluginCommand( + "regrass", + "Regrows surface grass.", + df_regrass)); return CR_OK; } diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 9ab9a7502..643e92b21 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -221,12 +221,14 @@ DFHACK_PLUGIN_IS_ENABLED(enableUpdates); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); + commands.push_back(PluginCommand( + "RemoteFortressReader_version", + "List the loaded RemoteFortressReader version", + RemoteFortressReader_version)); commands.push_back(PluginCommand( "load-art-image-chunk", "Gets an art image chunk by index, loading from disk if necessary", - loadArtImageChunk, false, - "Usage: load_art_image_chunk N, where N is the id of the chunk to get.")); + loadArtImageChunk)); enableUpdates = true; return CR_OK; } diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 0ef3e02d5..7c46fc908 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -61,16 +61,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \"name\"\n" - " rename hotkey \"name\"\n" - " (identified by ordinal index)\n" - " rename unit \"nickname\"\n" - " rename unit-profession \"custom profession\"\n" - " (a unit must be highlighted in the ui)\n" - " rename building \"nickname\"\n" - " (a building must be highlighted via 'q')\n" - )); + "rename", + "Easily rename things.", + rename)); if (Core::getInstance().isWorldLoaded()) plugin_onstatechange(out, SC_WORLD_LOADED); diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index d391bdb36..b7c703e09 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -49,16 +49,9 @@ static command_result rendermax(color_ostream &out, vector & parameters DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "rendermax", "switch rendering engine.", rendermax, false, - " rendermax trippy\n" - " rendermax truecolor red|green|blue|white\n" - " rendermax lua\n" - " rendermax light - lighting engine\n" - " rendermax light reload - reload the settings file\n" - " rendermax light sun |cycle - set time to x (in hours) or cycle (same effect if x<0)\n" - " rendermax light occlusionON|occlusionOFF - debug the occlusion map\n" - " rendermax disable\n" - )); + "rendermax", + "Modify the map lighting.", + rendermax)); return CR_OK; } @@ -331,7 +324,7 @@ static command_result rendermax(color_ostream &out, vector & parameters return CR_WRONG_USAGE; if(!enabler->renderer->uses_opengl()) { - out.printerr("Sorry, this plugin needs open gl enabled printmode. Try STANDARD or other non-2D\n"); + out.printerr("Sorry, this plugin needs open GL-enabled printmode. Try STANDARD or other non-2D.\n"); return CR_FAILURE; } string cmd=parameters[0]; diff --git a/plugins/resume.cpp b/plugins/resume.cpp index 6b8ad5e7d..21a4b521e 100644 --- a/plugins/resume.cpp +++ b/plugins/resume.cpp @@ -302,18 +302,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector & params); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - commands.push_back(PluginCommand("reveal","Reveal the map.",reveal,false, - "Reveals the map, by default ignoring hell.\n" - "Options:\n" - "hell - also reveal hell, while forcing the game to pause.\n" - "demon - reveal hell, do not pause.\n")); - commands.push_back(PluginCommand("unreveal","Revert the map to its previous state.",unreveal,false, - "Reverts the previous reveal operation, hiding the map again.\n")); - commands.push_back(PluginCommand("revtoggle","Reveal/unreveal depending on state.",revtoggle,false, - "Toggles between reveal and unreveal.\n")); - commands.push_back(PluginCommand("revflood","Hide all, and reveal tiles reachable from the cursor.",revflood,false, - "This command hides the whole map. Then, starting from the cursor,\n" - "reveals all accessible tiles. Allows repairing perma-revealed maps.\n" - "Note that constructed walls are considered passable to work around DF bug 1871.\n")); - commands.push_back(PluginCommand("revforget", "Forget the current reveal data.",revforget,false, - "Forget the current reveal data, allowing to use reveal again.\n")); - commands.push_back(PluginCommand("nopause","Disable manual and automatic pausing.",nopause,false, - "Disable pausing (doesn't affect pause forced by reveal).\n" - "Activate with 'nopause 1', deactivate with 'nopause 0'.\n")); + commands.push_back(PluginCommand( + "reveal", + "Reveal the map.", + reveal)); + commands.push_back(PluginCommand( + "unreveal", + "Revert a revealed map to its unrevealed state.", + unreveal)); + commands.push_back(PluginCommand( + "revtoggle", + "Switch betwen reveal and unreveal.", + revtoggle)); + commands.push_back(PluginCommand( + "revflood", + "Hide all, then reveal tiles reachable from the cursor.", + revflood)); + commands.push_back(PluginCommand( + "revforget", + "Forget the current reveal data.", + revforget)); + commands.push_back(PluginCommand( + "nopause", + "Disable manual and automatic pausing.", + nopause)); return CR_OK; } diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 478a0899a..13ed8e66e 100644 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -49,47 +49,6 @@ bool ignoreSeeds(df::item_flags& f) // seeds with the following flags should not f.bits.in_job; }; -void printHelp(color_ostream &out) // prints help -{ - out.print( - "Watches the numbers of seeds available and enables/disables seed and plant cooking.\n" - "Each plant type can be assigned a limit. If their number falls below,\n" - "the plants and seeds of that type will be excluded from cookery.\n" - "If the number rises above the limit + %i, then cooking will be allowed.\n", buffer - ); - out.printerr( - "The plugin needs a fortress to be loaded and will deactivate automatically otherwise.\n" - "You have to reactivate with 'seedwatch start' after you load the game.\n" - ); - out.print( - "Options:\n" - "seedwatch all - Adds all plants from the abbreviation list to the watch list.\n" - "seedwatch start - Start watching.\n" - "seedwatch stop - Stop watching.\n" - "seedwatch info - Display whether seedwatch is watching, and the watch list.\n" - "seedwatch clear - Clears the watch list.\n\n" - ); - if(!abbreviations.empty()) - { - out.print("You can use these abbreviations for the plant tokens:\n"); - for(map::const_iterator i = abbreviations.begin(); i != abbreviations.end(); ++i) - { - out.print("%s -> %s\n", i->first.c_str(), i->second.c_str()); - } - } - out.print( - "Examples:\n" - "seedwatch MUSHROOM_HELMET_PLUMP 30\n" - " add MUSHROOM_HELMET_PLUMP to the watch list, limit = 30\n" - "seedwatch MUSHROOM_HELMET_PLUMP\n" - " removes MUSHROOM_HELMET_PLUMP from the watch list.\n" - "seedwatch ph 30\n" - " is the same as 'seedwatch MUSHROOM_HELMET_PLUMP 30'\n" - "seedwatch all 30\n" - " adds all plants from the abbreviation list to the watch list, the limit being 30.\n" - ); -}; - // searches abbreviations, returns expansion if so, returns original if not string searchAbbreviations(string in) { @@ -132,7 +91,9 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) map plantIDs; for(size_t i = 0; i < world->raws.plants.all.size(); ++i) { - plantIDs[world->raws.plants.all[i]->id] = i; + auto & plant = world->raws.plants.all[i]; + if (plant->material_defs.type[plant_material_def::seed] != -1) + plantIDs[plant->id] = i; } t_gamemodes gm; @@ -142,8 +103,7 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) if(gm.g_mode != game_mode::DWARF || !World::isFortressMode(gm.g_type)) { // just print the help - printHelp(out); - return CR_OK; + return CR_WRONG_USAGE; } string par; @@ -151,14 +111,12 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) switch(parameters.size()) { case 0: - printHelp(out); return CR_WRONG_USAGE; case 1: par = parameters[0]; if ((par == "help") || (par == "?")) { - printHelp(out); return CR_WRONG_USAGE; } else if(par == "start") @@ -180,11 +138,11 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) out.print("seedwatch Info:\n"); if(running) { - out.print("seedwatch is supervising. Use 'seedwatch stop' to stop supervision.\n"); + out.print("seedwatch is supervising. Use 'disable seedwatch' to stop supervision.\n"); } else { - out.print("seedwatch is not supervising. Use 'seedwatch start' to start supervision.\n"); + out.print("seedwatch is not supervising. Use 'enable seedwatch' to start supervision.\n"); } map watchMap; Kitchen::fillWatchMap(watchMap); @@ -226,10 +184,8 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) if(limit < 0) limit = 0; if(parameters[0] == "all") { - for(auto i = abbreviations.begin(); i != abbreviations.end(); ++i) - { - if(plantIDs.count(i->second) > 0) Kitchen::setLimit(plantIDs[i->second], limit); - } + for(auto & entry : plantIDs) + Kitchen::setLimit(entry.second, limit); } else { @@ -246,7 +202,6 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) } break; default: - printHelp(out); return CR_WRONG_USAGE; break; } @@ -256,7 +211,10 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) DFhackCExport command_result plugin_init(color_ostream &out, vector& commands) { - commands.push_back(PluginCommand("seedwatch", "Toggles seed cooking based on quantity available", df_seedwatch)); + commands.push_back(PluginCommand( + "seedwatch", + "Toggles seed cooking based on quantity available.", + df_seedwatch)); // fill in the abbreviations map, with abbreviations for the standard plants abbreviations["bs"] = "SLIVER_BARB"; abbreviations["bt"] = "TUBER_BLOATED"; diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index c7d620676..abeb2e98e 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -297,8 +297,10 @@ command_result df_showmood (color_ostream &out, vector & parameters) DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("showmood", "Shows items needed for current strange mood.", df_showmood, false, - "Run this command without any parameters to display information on the currently active Strange Mood.")); + commands.push_back(PluginCommand( + "showmood", + "Shows all items needed for active strange mood.", + df_showmood)); return CR_OK; } diff --git a/plugins/skeleton/CMakeLists.txt b/plugins/skeleton/CMakeLists.txt deleted file mode 100644 index cbe5f7ce6..000000000 --- a/plugins/skeleton/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -project(skeleton) -# A list of source files -set(PROJECT_SRCS - skeleton.cpp -) -# A list of headers -set(PROJECT_HDRS - skeleton.h -) -set_source_files_properties(${PROJECT_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}) - -# option to use a thread for no particular reason -option(SKELETON_THREAD "Use threads in the skeleton plugin." ON) -if(UNIX) - if(APPLE) - set(PROJECT_LIBS - # add any extra mac libraries here - ${PROJECT_LIBS} - ) - else() - set(PROJECT_LIBS - # add any extra linux libraries here - ${PROJECT_LIBS} - ) - endif() -else() - set(PROJECT_LIBS - # add any extra windows libraries here - ${PROJECT_LIBS} - ) -endif() -# this makes sure all the stuff is put in proper places and linked to dfhack -dfhack_plugin(skeleton ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS}) diff --git a/plugins/skeleton/skeleton.cpp b/plugins/skeleton/skeleton.cpp deleted file mode 100644 index 7d5936f6d..000000000 --- a/plugins/skeleton/skeleton.cpp +++ /dev/null @@ -1,171 +0,0 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D - -// some headers required for a plugin. Nothing special, just the basics. -#include "Core.h" -#include -#include -#include -#include -// If you need to save data per-world: -//#include "modules/Persistence.h" - -// DF data structure definition headers -#include "DataDefs.h" -//#include "df/world.h" - -// our own, empty header. -#include "skeleton.h" - -using namespace DFHack; -using namespace df::enums; - -// Expose the plugin name to the DFHack core, as well as metadata like the DFHack version. -// The name string provided must correspond to the filename - -// skeleton.plug.so, skeleton.plug.dylib, or skeleton.plug.dll in this case -DFHACK_PLUGIN("skeleton"); - -// The identifier declared with this macro (ie. enabled) can be specified by the user -// and subsequently used to manage the plugin's operations. -// This will also be tracked by `plug`; when true the plugin will be shown as enabled. -DFHACK_PLUGIN_IS_ENABLED(enabled); - -// Any globals a plugin requires (e.g. world) should be listed here. -// For example, this line expands to "using df::global::world" and prevents the -// plugin from being loaded if df::global::world is null (i.e. missing from symbols.xml): -// -REQUIRE_GLOBAL(world); - -// You may want some compile time debugging options -// one easy system just requires you to cache the color_ostream &out into a global debug variable -//#define P_DEBUG 1 -//uint16_t maxTickFreq = 1200; //maybe you want to use some events - -command_result command_callback1(color_ostream &out, std::vector ¶meters); - -DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("skeleton", - "~54 character description of plugin", //to use one line in the ``[DFHack]# ls`` output - command_callback1, - false, - "example usage" - " skeleton