Merge branch 'develop' into Autoclothing

develop
Myk 2023-01-20 14:05:26 -08:00 committed by GitHub
commit 310940e1a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 1606 additions and 505 deletions

@ -39,6 +39,6 @@ repos:
name: Check Authors.rst name: Check Authors.rst
language: python language: python
entry: python3 ci/authors-rst.py entry: python3 ci/authors-rst.py
files: docs/Authors\.rst files: docs/about/Authors\.rst
pass_filenames: false pass_filenames: false
exclude: '^(depends/|data/.*\.json$|.*\.diff$)' exclude: '^(depends/|data/.*\.json$|.*\.diff$)'

@ -132,14 +132,8 @@ endif()
find_package(Perl REQUIRED) find_package(Perl REQUIRED)
# set up folder structures for IDE solutions # set up folder structures for IDE solutions
# MSVC Express won't load solutions that use this. It also doesn't include MFC supported # checking for msvc express is meaningless now, all available editions of msvc support folder groupings
# Check for MFC!
find_package(MFC QUIET)
if(MFC_FOUND OR (NOT MSVC))
option(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." ON) option(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." ON)
else()
option(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." OFF)
endif()
if(CMAKE_USE_FOLDERS) if(CMAKE_USE_FOLDERS)
set_property(GLOBAL PROPERTY USE_FOLDERS ON) set_property(GLOBAL PROPERTY USE_FOLDERS ON)
@ -196,7 +190,7 @@ endif()
# set up versioning. # set up versioning.
set(DF_VERSION "50.05") set(DF_VERSION "50.05")
set(DFHACK_RELEASE "alpha0") set(DFHACK_RELEASE "alpha1")
set(DFHACK_PRERELEASE TRUE) set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -1,4 +1 @@
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 cmake --build VC2022 -t ALL_BUILD -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj
cd ..

@ -1,5 +1 @@
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 cmake --build VC2022 -t ALL_BUILD -- /m /p:Platform=x64 /p:Configuration=Release
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj
cd ..
pause

@ -1,6 +1,4 @@
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2022
cd VC2022
echo generating a build folder echo generating a build folder
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_STONESENSE=1 cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_STONESENSE=1

@ -1,7 +1,5 @@
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2022
cd VC2022
echo Pre-generating a build folder echo Pre-generating a build folder
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%"
cmake-gui . cmake-gui VC2022

@ -1,6 +1,4 @@
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2022
cd VC2022
echo generating a build folder echo generating a build folder
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0 cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0

@ -1,6 +1,4 @@
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2022
cd VC2022
echo generating a build folder echo generating a build folder
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DOCS=1 -DBUILD_STONESENSE=1 cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DOCS=1 -DBUILD_STONESENSE=1

@ -1,6 +1,4 @@
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
mkdir VC2022
cd VC2022
echo generating a build folder echo generating a build folder
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_SUPPORTED=0 -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0 -DBUILD_SIZECHECK=1 cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_SUPPORTED=0 -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0 -DBUILD_SIZECHECK=1

@ -1,4 +1 @@
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 cmake --build VC2022 -t INSTALL -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj
cd ..

@ -1,4 +1 @@
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 cmake --build VC2022 -t INSTALL -- /m /p:Platform=x64 /p:Configuration=Release
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=Release INSTALL.vcxproj
cd ..

@ -1,6 +1,2 @@
@echo off cmake --build VC2022 -t PACKAGE -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo PACKAGE.vcxproj
cd ..
exit %ERRORLEVEL% exit %ERRORLEVEL%

@ -1,5 +1 @@
@echo off cmake --build VC2022 -t PACKAGE -- /m /p:Platform=x64 /p:Configuration=Release
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
cd VC2022
msbuild /m /p:Platform=x64 /p:Configuration=Release PACKAGE.vcxproj
cd ..

@ -23,7 +23,7 @@ if ! test -f "$df_tardest"; then
fi fi
done <<URLS done <<URLS
https://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2 https://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2
https://files.dfhack.org/DF/0.${minor}.${patch}/df_${minor}_${patch}_linux.tar.bz2 https://files.dfhack.org/DF/${minor}.${patch}/df_${minor}_${patch}_linux.tar.bz2
URLS URLS
echo $df_tardest echo $df_tardest
if ! test -f "$df_tardest"; then if ! test -f "$df_tardest"; then
@ -42,11 +42,11 @@ URLS
fi fi
rm -rf df_linux rm -rf df_linux
mkdir -p df_linux/data/save mkdir -p df_linux/save
echo Extracting echo Extracting
tar xf "$df_tardest" --strip-components=1 -C df_linux tar xf "$df_tardest" --strip-components=1 -C df_linux
tar xf "$save_tardest" -C df_linux/data/save tar xf "$save_tardest" -C df_linux/save
echo Done echo Done
ls -l ls -l

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -27,8 +27,8 @@ keybinding add Ctrl-Shift-K gui/cp437-table
# dwarfmode bindings # # dwarfmode bindings #
###################### ######################
# quicksave, only in main dwarfmode screen and menu page # quicksave
#keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave keybinding add Ctrl-Alt-S@dwarfmode quicksave
# toggle the display of water level as 1-7 tiles # toggle the display of water level as 1-7 tiles
#keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl #keybinding add Ctrl-W@dwarfmode|dungeonmode twaterlvl
@ -47,7 +47,7 @@ keybinding add Ctrl-Shift-K gui/cp437-table
#keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here #keybinding add Ctrl-Shift-K@dwarfmode autodump-destroy-here
# apply blueprints to the map (Alt-F for compatibility with LNP Quickfort) # apply blueprints to the map (Alt-F for compatibility with LNP Quickfort)
#keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort keybinding add Ctrl-Shift-Q@dwarfmode gui/quickfort
#keybinding add Alt-F@dwarfmode gui/quickfort #keybinding add Alt-F@dwarfmode gui/quickfort
# show information collected by dwarfmonitor # show information collected by dwarfmonitor

@ -81,6 +81,9 @@
# Allow DFHack tools to overlay functionality and information on the DF screen # Allow DFHack tools to overlay functionality and information on the DF screen
enable overlay enable overlay
# Allow buildings to be placed now and built later when materials are available
enable buildingplan
# Dwarf Manipulator (simple in-game Dwarf Therapist replacement) # Dwarf Manipulator (simple in-game Dwarf Therapist replacement)
#enable manipulator #enable manipulator

@ -144,11 +144,10 @@ savegave portability, mod merging, and general organization of init files,
DFHack supports multiple init files both in the main DF directory and DFHack supports multiple init files both in the main DF directory and
save-specific init files in the save folders. save-specific init files in the save folders.
DFHack looks for init files in three places each time they could be run: DFHack looks for init files in two places each time they could be run:
#. The :file:`dfhack-config/init` subdirectory in the main DF directory #. The :file:`dfhack-config/init` subdirectory in the main DF directory and
#. :file:`data/save/{world}/raw`, where ``{world}`` is the current save, and #. :file:`save/{world}/init`, where ``{world}`` is the current save
#. :file:`data/save/{world}/raw/objects`
For each of those directories, all matching init files will be executed in For each of those directories, all matching init files will be executed in
alphabetical order. alphabetical order.
@ -156,7 +155,7 @@ alphabetical order.
Before running matched init scripts in any of those locations, the 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 :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 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 for this file, not any :file:`save` directories. If you want DFHack to load
without running any of its default configuration commands, edit the without running any of its default configuration commands, edit the
:file:`dfhack-config/init/default.*` files and comment out the commands you see :file:`dfhack-config/init/default.*` files and comment out the commands you see
there. there.
@ -217,10 +216,10 @@ after a modded save is unloaded.
.. _other_init_files: .. _other_init_files:
raw/init.d/\*.lua init.d/\*.lua
................. .............
Any lua script named ``raw/init.d/*.lua``, in the save or main DF directory, Any lua script named ``init.d/*.lua``, in the save or main DF directory,
will be run when any world or that save is loaded. will be run when any world or that save is loaded.
@ -234,12 +233,11 @@ run. By default, the following folders are searched, in order (relative to the
root DF folder): root DF folder):
#. :file:`dfhack-config/scripts` #. :file:`dfhack-config/scripts`
#. :file:`data/save/{<region folder>}/raw/scripts` (only if a save is loaded) #. :file:`save/{world}/scripts` (only if a save is loaded)
#. :file:`raw/scripts`
#. :file:`hack/scripts` #. :file:`hack/scripts`
For example, if ``teleport`` is run, these folders are searched in order for For example, if ``teleport`` is run, these folders are searched in order for
``teleport.lua`` or ``teleport.rb``, and the first matching file is run. ``teleport.lua``, and the first matching file is run.
Script paths can be added by modifying :file:`dfhack-config/script-paths.txt`. Script paths can be added by modifying :file:`dfhack-config/script-paths.txt`.
Each line should start with one of these characters: Each line should start with one of these characters:

@ -50,7 +50,7 @@ The GCC 4.8 build is built on Ubuntu 14.04 and targets an older glibc, so it
should work on older distributions. should work on older distributions.
In the event that none of the provided binaries work on your distribution, In the event that none of the provided binaries work on your distribution,
you may need to `compile DFHack from source <compile>`. you may need to `compile DFHack from source <building-dfhack-index>`.
macOS macOS
----- -----
@ -86,7 +86,7 @@ restrictions apply (e.g. a file named ``Windows64`` is for 64-bit Windows DF).
or by clicking "Download ZIP" on the repo homepage. This will give you an or by clicking "Download ZIP" on the repo homepage. This will give you an
incomplete copy of the DFHack source code, which will not work as-is. (If incomplete copy of the DFHack source code, which will not work as-is. (If
you want to compile DFHack instead of using a pre-built release, see you want to compile DFHack instead of using a pre-built release, see
`compile` for instructions.) `building-dfhack-index` for instructions.)
Installing DFHack Installing DFHack
================= =================

@ -0,0 +1,192 @@
.. _quickstart:
Quickstart guide
================
Welcome to DFHack! This guide will help get you oriented with the DFHack system
and teach you how to find and use the tools productively. If you're reading this
in `quickstart-guide`, hit the right arrow key or click on the hotkey hint in
the lower right corner of the window to go to the next page.
What is DFHack?
---------------
DFHack is a framework for Dwarf Fortress that provides a unified, cross-platform
environment that enables mods and tools to significantly extend the game. The
default DFHack distribution contains a wide variety of tools, including bugfixes,
interface improvements, automation agents, design blueprints, modding building
blocks, and more. Third-party tools (e.g. mods downloaded from Steam Workshop or
the forums) can also seamlessly integrate with the DFHack framework and extend
the game far beyond what can be done by just modding the raws.
DFHack's mission is to provide tools and interfaces for players and modders to:
- expand the bounds of what is possible in Dwarf Fortress
- reduce the impact of game bugs
- give the player more agency and control over the game
- provide alternatives to toilsome or frustrating aspects of gameplay
- **make the game more fun**
What can I do with DFHack tools?
--------------------------------
DFHack has been around for a long time -- almost as long as Dwarf Fortress
itself. Many of the game's rough edges have been smoothed with DFHack tools.
Here are some common tasks people use DFHack tools to accomplish:
- Automatically chop trees when log stocks are low
- Record blueprint files that allow copy and paste of fort designs
- Import and export lists of manager orders
- Clean contaminants from map squares that dwarves can't reach
- Automatically butcher excess livestock so you don't become overrun with
animals
- Promote time-sensitive job types (e.g. food hauling) so they are done
expediently
- Quickly scan the map for visible ores of specific types so you can focus
your mining efforts
Some tools are one-shot commands. For example, you can run `unforbid all <unforbid>`
to claim all items on the map after a messy siege.
Other tools must be `enabled <enable>` and then they will run in the background.
For example, `enable seedwatch <seedwatch>` will start monitoring your stocks of
seeds and prevent your chefs from cooking seeds that you need for planting.
Tools that are enabled in the context of a fort will save their state with that
fort, and the will remember that they are enabled the next time you load your save.
A third class of tools add information to the screen or provide new integrated
functionality via the DFHack `overlay` framework. For example, the `unsuspend`
tool, in addition to its basic function of unsuspending all building construction
jobs, can also overlay a marker on suspended buildings to indicate that they are
suspended (and will use different markers to tell you whether this is a problem).
These overlays can be enabled and configured with the `gui/overlay` interface.
How can I figure out which commands to run?
-------------------------------------------
There are several ways to scan DFHack tools and find the one you need right now.
The first place to check is the DFHack logo hover hotspot. It's in the upper
left corner of the screen by default, though you can move it anywhere you want
with the `gui/overlay` tool.
When you hover the mouse over the logo (or hit the Ctrl-Shift-C keyboard shortcut)
a list of DFHack tools relevant to the current context comes up. For example, when
you have a unit selected, the hotspot will show a list of tools that inspect
units, allow you to edit them, or maybe even teleport them. Next to each tool,
you'll see the global hotkey you can hit to invoke the command without even
opening the hover list.
You can run any DFHack tool from `gui/launcher`, which is always listed first in
the hover list. You can also bring up the launcher by tapping the backtick key
(\`) or hitting Ctrl-Shift-D. In the launcher, you can quickly autocomplete any
command name by selecting it in the list on the right side of the window.
Commands are ordered by how often you run them, so your favorite commands will
always be on top. You can also pull full commandlines out of your history with
Alt-S (or by clicking on the "history search" hotkey hint).
Once you have typed (or autocompleted, or searched for) a command, other commands
related to the one you have selected will appear in the autocomplete list.
Scanning through that list is a great way to learn about new tools that you might
find useful. You can also see how commands are grouped by running the `tags` command.
The bottom panel will show the full help text for the command you are running,
allowing you to refer to the usage documentation and examples when you are typing
your command.
How do DFHack in-game windows work?
-----------------------------------
Many DFHack tools have graphical interfaces that appear in-game. You can tell
which windows belong to DFHack tools because they will have the word "DFHack"
printed across their bottom frame edge. DFHack provides a custom windowing system
that gives the player a lot of control over where the windows appear and whether
they capture keyboard and mouse input.
The DFHack windowing system allows you to use DFHack tools without interrupting
the game. That is, if the game is unpaused, it will continue to run while a
DFHack window is open. You can also interact with the map, scrolling it with the
keyboard or mouse and selecting units, buildings, and items. Some tools will
capture all keyboard input, such as tools with editable text fields, and some will
force-pause the game if it makes sense to, like `gui/quickfort`, since you cannot
interact with the map normally while trying to apply a blueprint.
DFHack windows are draggable from the title bar or from anywhere on the window
that doesn't have a mouse-clickable widget on it. Many are resizable as well
(if the tool window has components that can reasonably be resized).
DFHack windows close with a right mouse click or keyboard Esc, but if you
want to keep a DFHack tool open while you interact with the game, you can click the
pin in the upper right corner of the DFHack window or hit Alt-L so
that the pin turns green. The DFHack window will then ignore right clicks and
Esc key presses that would otherwise close the window. This is especially
useful for the configuration tool windows for the automation tools. For example,
you can pin the `gui/autochop` window, set it to minimal mode, and let it sit
there monitoring your logging industry as you play, using it as a live status
window. Note that you can still right click *on* the DFHack tool window to close
it, even when it is pinned.
You can have multiple DFHack tool windows on the screen at the same time. The
one that is receiving keyboard input has a highlighted title bar and will appear
over other windows if dragged across them. Clicking on a DFHack window that is not
currently active will bring it to the foreground and make it the active window.
Where do I go next?
-------------------
To recap:
You can get to popular, relevant tools for the current context by hovering
the mouse over the DFHack logo or by hitting Ctrl-Shift-C.
You can get to the launcher and its integrated autocomplete, history search,
and help text by hitting backtick (\`) or Ctrl-Shift-D, or, of course, by
running it from the logo hover list.
You can list and start tools that run in the background with the `enable`
command.
You can configure screen overlays with the `gui/overlay` tool.
With those four tools, you have the complete DFHack tool suite at your
fingertips. So what to run first? Here are a few commands to get you started.
You can run them all from the launcher.
First, let's import some useful manager orders to keep your fort stocked with
basic necessities. Run ``orders import library/basic``. If you go to your
mangager orders screen, you can see all the orders that have been created for you.
Next, try setting up `autochop` by running the GUI configuration `gui/autochop`.
You can enable it from the GUI, so you don't need to run `enable autochop <enable>`
directly. You can set a target number of logs, and autochop will manage
your logging industry for you. You can control where your woodsdwarves go to
cut down trees by setting up burrows and configuring autochop to only cut in
those burrows. If you have the extra screen space, go ahead and click the pin so
it turns green and set the `gui/autochop` window to minimal mode (click on the
hint near the upper right corner of the window or hit Alt-M). As you play the game,
you can glance at it to check on your stocks of wood.
Finally, let's do some fort design copy-pasting. Go to some bedrooms that you have
set up in your fort. Run `gui/blueprint`, set a name for your blueprint by
clicking on the name field (or hitting the 'n' hotkey), typing "rooms" (or whatever)
and hitting Enter to set. Then draw a box around the target area by clicking with
the mouse. When you select the second corner, the blueprint will be saved to your
``blueprints`` subfolder.
Now open up `gui/quickfort`. You can search for the blueprint you just created by
typing its name, but it should be up near the top already. If you copied a dug-out
area with furniture in it, your blueprint will have two labels: "/dig" and "/build".
Click on the "/dig" blueprint or select it with the keyboard arrow keys and hit Enter.
You can rotate or flip the blueprint around if you need to with the transform hotkeys.
You'll see a preview of where the blueprint will be applied as you move the mouse
cursor around the map. Red outlines mean that the blueprint may fail to fully apply
at that location, so be sure to choose a spot where all the preview tiles are shown
with green diamonds. Click the mouse or hit Enter to apply the blueprint and
designate the tiles for digging. Your dwarves will come and dig it out as if you
had designated the tiles yourself.
Once the area is dug out, run `gui/quickfort` again and select the "/build" blueprint
this time. Apply the blueprint in the dug-out area, and your furniture will be
designated. It's just that easy!
There are many, many more tools to explore. Have fun!

@ -13,6 +13,7 @@ Name Github Other
Abel abstern Abel abstern
acwatkins acwatkins acwatkins acwatkins
Alexander Gavrilov angavrilov ag Alexander Gavrilov angavrilov ag
Amber Brown hawkowl
Amostubal Amostubal Amostubal Amostubal
Andrea Cattaneo acattaneo88 Andrea Cattaneo acattaneo88
AndreasPK AndreasPK AndreasPK AndreasPK
@ -46,10 +47,12 @@ David Corbett dscorbett
David Seguin dseguin David Seguin dseguin
David Timm dtimm David Timm dtimm
Deon Deon
dikbut Tjudge1
DoctorVanGogh DoctorVanGogh DoctorVanGogh DoctorVanGogh
Donald Ruegsegger hashaash Donald Ruegsegger hashaash
doomchild doomchild doomchild doomchild
DwarvenM DwarvenM DwarvenM DwarvenM
EarthPulseAcademy EarthPulseAcademy
ElMendukol ElMendukol ElMendukol ElMendukol
enjia2000 enjia2000
Eric Wald eswald Eric Wald eswald
@ -66,8 +69,10 @@ Guilherme Abraham GuilhermeAbraham
Harlan Playford playfordh Harlan Playford playfordh
Hayati Ayguen hayguen Hayati Ayguen hayguen
Herwig Hochleitner bendlas Herwig Hochleitner bendlas
Hevlikn Hevlikn
Ian S kremlin- Ian S kremlin-
IndigoFenix IndigoFenix
James 20k
James Gilles kazimuth James Gilles kazimuth
James Logsdon jlogsdon James Logsdon jlogsdon
Jared Adams Jared Adams
@ -80,14 +85,18 @@ Joel Meador janxious
John Beisley huin John Beisley huin
John Shade gsvslto John Shade gsvslto
Jonas Ask Jonas Ask
Jonathan Clark AridTag
Josh Cooper cppcooper coope Josh Cooper cppcooper coope
jowario jowario
kane-t kane-t kane-t kane-t
Kelly Kinkade ab9rf Kelly Kinkade ab9rf
Kib Arekatír arekatir
KlonZK KlonZK KlonZK KlonZK
Kris Parker kaypy Kris Parker kaypy
Kristjan Moore kristjanmoore Kristjan Moore kristjanmoore
Kromtec Kromtec Kromtec Kromtec
Kurik Amudnil Kurik Amudnil
Kévin Boissonneault KABoissonneault
Lethosor lethosor Lethosor lethosor
LordGolias LordGolias LordGolias LordGolias
Mark Nielson pseudodragon Mark Nielson pseudodragon
@ -111,6 +120,7 @@ Milo Christiansen milochristiansen
MithrilTuxedo MithrilTuxedo MithrilTuxedo MithrilTuxedo
mizipzor mizipzor mizipzor mizipzor
moversti moversti moversti moversti
Murad Beybalaev Erquint
Myk Taylor myk002 Myk Taylor myk002
napagokc napagokc napagokc napagokc
Neil Little nmlittle Neil Little nmlittle
@ -119,7 +129,10 @@ Nicolas Ayala nicolasayala
Nik Nyby nikolas Nik Nyby nikolas
Nikolay Amiantov abbradar Nikolay Amiantov abbradar
nocico nocico nocico nocico
NotRexButCaesar NotRexButCaesar
Nuno Fernandes UnknowableCoder
Omniclasm Omniclasm
oorzkws oorzkws
OwnageIsMagic OwnageIsMagic OwnageIsMagic OwnageIsMagic
palenerd dlmarquis palenerd dlmarquis
PassionateAngler PassionateAngler PassionateAngler PassionateAngler
@ -133,6 +146,7 @@ Pierre-David Bélanger pierredavidbelanger
potato potato
Priit Laes plaes Priit Laes plaes
Putnam Putnam3145 Putnam Putnam3145
quarque2 quarque2
Quietust quietust _Q Quietust quietust _Q
Rafał Karczmarczyk CarabusX Rafał Karczmarczyk CarabusX
Raidau Raidau Raidau Raidau
@ -145,6 +159,7 @@ reverb
Rich Rauenzahn rrauenza Rich Rauenzahn rrauenza
Rinin Rinin Rinin Rinin
rndmvar rndmvar rndmvar rndmvar
Rob Bailey actionninja
Robert Heinrich rh73 Robert Heinrich rh73
Robert Janetzko robertjanetzko Robert Janetzko robertjanetzko
Rocco Moretti roccomoretti Rocco Moretti roccomoretti
@ -165,8 +180,10 @@ scamtank scamtank
Sebastian Wolfertz Enkrod Sebastian Wolfertz Enkrod
seishuuu seishuuu seishuuu seishuuu
Seth Woodworth sethwoodworth Seth Woodworth sethwoodworth
Shim Panze Shim-Panze
simon simon
Simon Jackson sizeak Simon Jackson sizeak
Simon Lees simotek
stolencatkarma stolencatkarma
Stoyan Gaydarov sgayda2 Stoyan Gaydarov sgayda2
Su Moth-Tolias Su Moth-Tolias
@ -198,6 +215,7 @@ Vjek vjek
Warmist warmist Warmist warmist
Wes Malone wesQ3 Wes Malone wesQ3
Will Rogers wjrogers Will Rogers wjrogers
WoosterUK WoosterUK
ZechyW ZechyW ZechyW ZechyW
Zhentar Zhentar Zhentar Zhentar
zilpin zilpin zilpin zilpin

@ -16,6 +16,12 @@ command-prompt
============== ==============
Replaced by `gui/launcher --minimal <gui/launcher>`. Replaced by `gui/launcher --minimal <gui/launcher>`.
.. _create-items:
create-items
============
Replaced by `gui/create-item --multi <gui/create-item>`.
.. _deteriorateclothes: .. _deteriorateclothes:
deteriorateclothes deteriorateclothes
@ -124,6 +130,12 @@ 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 ``dfhack.init`` is autogenerated in ``dfhack-config/init``, this warning is no
longer necessary. longer necessary.
.. _masspit:
masspit
=======
Replaced with a GUI version: `gui/masspit`.
.. _resume: .. _resume:
resume resume

@ -36,15 +36,35 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## New Plugins ## New Plugins
## Fixes ## Fixes
- `autofarm`: don't duplicate status line entries for crops with no current supply
- `orders`: allow the orders library to be listed and imported properly (if you previously copied the orders library into your ``dfhack-config/orders`` directory to work around this bug, you can remove those files now)
## Misc Improvements
## Documentation
## API
## Lua
## Removed
# 50.05-alpha1
## Fixes
- ``widgets.WrappedLabel``: no longer resets scroll position when window is moved or resized
## Misc Improvements ## Misc Improvements
- Scrollable widgets now react to mouse wheel events when the mouse is over the widget - Scrollable widgets now react to mouse wheel events when the mouse is over the widget
- the ``dfhack-config/scripts/`` folder is now searched for scripts by default - the ``dfhack-config/scripts/`` folder is now searched for scripts by default
- `hotkeys`: overlay hotspot widget now shows the DFHack logo in graphics mode and "DFHack" in text mode - `hotkeys`: overlay hotspot widget now shows the DFHack logo in graphics mode and "DFHack" in text mode
- `script-paths`: removed "raw" directories from default script paths. now the default locations to search for scripts are ``dfhack-config/scripts``, ``save/*/scripts``, and ``hack/scripts``
- ``init.d``: directories have moved from the ``raw`` subfolder (which no longer exists) to the root of the main DF folder or a savegame folder
## Documentation ## Documentation
- `overlay-dev-guide`: added troubleshooting tips and common development workflows - `overlay-dev-guide`: added troubleshooting tips and common development workflows
- added DFHack architecture diagrams to the dev intro - added DFHack architecture diagrams to the dev intro
- added DFHack Quickstart guide
## API ## API
- ``Gui::getDwarfmodeDims``: now only returns map viewport dimensions; menu dimensions are obsolete - ``Gui::getDwarfmodeDims``: now only returns map viewport dimensions; menu dimensions are obsolete
@ -67,8 +87,6 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
- ``gui.CLEAR_PEN``: now clears the background and foreground and writes to the background (before it would always write to the foreground) - ``gui.CLEAR_PEN``: now clears the background and foreground and writes to the background (before it would always write to the foreground)
- ``gui.KEEP_LOWER_PEN``: a general use pen that writes associated tiles to the foreground while keeping the existing background - ``gui.KEEP_LOWER_PEN``: a general use pen that writes associated tiles to the foreground while keeping the existing background
## Internals
## Removed ## Removed
- ``fix-job-postings`` from the `workflow` plugin is now obsolete since affected savegames can no longer be loaded - ``fix-job-postings`` from the `workflow` plugin is now obsolete since affected savegames can no longer be loaded
- Ruby is no longer a supported DFHack scripting language - Ruby is no longer a supported DFHack scripting language

@ -1144,6 +1144,10 @@ Job module
Prints info about the job item. Prints info about the job item.
* ``dfhack.job.removeJob(job)``
Cancels a job, cleans up all references to it, and removes it from the world.
* ``dfhack.job.getGeneralRef(job, type)`` * ``dfhack.job.getGeneralRef(job, type)``
Searches for a general_ref with the given type. Searches for a general_ref with the given type.
@ -1994,6 +1998,11 @@ Low-level building creation functions:
Destroys the building, or queues a deconstruction job. Destroys the building, or queues a deconstruction job.
Returns *true* if the building was destroyed and deallocated immediately. Returns *true* if the building was destroyed and deallocated immediately.
* ``dfhack.buildings.notifyCivzoneModified(building)``
Rebuilds the civzone <-> overlapping building association mapping.
Call after changing extents or modifying size in some fashion
* ``dfhack.buildings.markedForRemoval(building)`` * ``dfhack.buildings.markedForRemoval(building)``
Returns *true* if the building is marked for removal (with :kbd:`x`), *false* Returns *true* if the building is marked for removal (with :kbd:`x`), *false*
@ -4416,6 +4425,13 @@ Subclass of Panel; automatically adjusts its own frame height and width to the
minimum required to show its subviews. Pairs nicely with a parent Panel that has minimum required to show its subviews. Pairs nicely with a parent Panel that has
``autoarrange_subviews`` enabled. ``autoarrange_subviews`` enabled.
It has the following attributes:
:auto_height: Sets self.frame.h from the positions and height of its subviews
(default is ``true``).
:auto_width: Sets self.frame.w from the positions and width of its subviews
(default is ``false``).
Pages class Pages class
----------- -----------
@ -4468,9 +4484,10 @@ calling ``setFocus(true)`` on the field object.
If an activation ``key`` is specified, the ``EditField`` will manage its own 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 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 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 and then call the ``on_submit`` callback. Pressing the Escape key (or r-clicking
release keyboard focus, but first it will restore the text that was displayed with the mouse) will also release keyboard focus, but first it will restore the
before the ``EditField`` gained focus and then call the ``on_change`` callback. 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. 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 You can click where you want the cursor to move or you can use any of the
@ -5800,9 +5817,13 @@ Enabling and disabling scripts
============================== ==============================
Scripts can choose to recognize the built-in ``enable`` and ``disable`` commands Scripts can choose to recognize the built-in ``enable`` and ``disable`` commands
by including the following line anywhere in their file:: by including the following line near the top of their file::
--@enable = true --@enable = true
--@module = true
Note that enableable scripts must also be `modules <reqscript>` so their
``isEnabled()`` functions can be called from outside the script.
When the ``enable`` and ``disable`` commands are invoked, the ``dfhack_flags`` When the ``enable`` and ``disable`` commands are invoked, the ``dfhack_flags``
table passed to the script will have the following fields set: table passed to the script will have the following fields set:
@ -5818,6 +5839,7 @@ command.
Example usage:: Example usage::
--@enable = true --@enable = true
--@module = true
enabled = enabled or false enabled = enabled or false
function isEnabled() function isEnabled()
@ -5871,9 +5893,9 @@ all script modules.
Save init script Save init script
================ ================
If a save directory contains a file called ``raw/init.lua``, it is If a save directory contains a file called ``init.lua``, it is
automatically loaded and executed every time the save is loaded. automatically loaded and executed every time the save is loaded.
The same applies to any files called ``raw/init.d/*.lua``. Every The same applies to any files called ``init.d/*.lua``. Every
such script can define the following functions to be called by dfhack: such script can define the following functions to be called by dfhack:
* ``function onStateChange(op) ... end`` * ``function onStateChange(op) ... end``

@ -61,9 +61,8 @@ be:
1. ``own-scripts/`` 1. ``own-scripts/``
2. ``dfhack-config/scripts/`` 2. ``dfhack-config/scripts/``
3. ``data/save/*/raw/scripts/`` 3. ``save/*/scripts/``
4. ``raw/scripts/`` 4. ``hack/scripts/``
5. ``hack/scripts/``
The structure of the game The structure of the game
------------------------- -------------------------
@ -359,7 +358,7 @@ names for the mod folders within it. In the example below, we'll use a mod ID of
``example-mod`` mod will be developed in the ``/path/to/mymods/example-mod/`` ``example-mod`` mod will be developed in the ``/path/to/mymods/example-mod/``
directory and has a basic structure that looks like this:: directory and has a basic structure that looks like this::
raw/init.d/example-mod.lua init.d/example-mod.lua
raw/objects/... raw/objects/...
raw/scripts/example-mod.lua raw/scripts/example-mod.lua
raw/scripts/example-mod/... raw/scripts/example-mod/...
@ -367,13 +366,13 @@ directory and has a basic structure that looks like this::
Let's go through that line by line. Let's go through that line by line.
* A short (one-line) script in ``raw/init.d/`` to initialise your * A short (one-line) script in ``init.d/`` to initialise your
mod when a save is loaded. mod when a save is loaded.
* Modifications to the game raws (potentially with custom raw tokens) go in * Modifications to the game raws (potentially with custom raw tokens) go in
``raw/objects/``. ``raw/objects/``.
* A control script in ``raw/scripts/`` that handles enabling and disabling your * A control script in ``scripts/`` that handles enabling and disabling your
mod. mod.
* A subfolder for your mod under ``raw/scripts/`` will contain all the internal * A subfolder for your mod under ``scripts/`` will contain all the internal
scripts and/or modules used by your mod. scripts and/or modules used by your mod.
It is a good idea to use a version control system to organize changes to your It is a good idea to use a version control system to organize changes to your
@ -385,10 +384,10 @@ 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 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``:: directory to your script paths in ``dfhack-config/script-paths.txt``::
+/path/to/mymods/example-mod/raw/scripts/ +/path/to/mymods/example-mod/scripts/
Ok, you're all set up! Now, let's take a look at an example Ok, you're all set up! Now, let's take a look at an example
``raw/scripts/example-mod.lua`` file:: ``scripts/example-mod.lua`` file::
-- main setup and teardown for example-mod -- main setup and teardown for example-mod
-- this next line indicates that the script supports the "enable" -- this next line indicates that the script supports the "enable"
@ -475,7 +474,7 @@ Ok, you're all set up! Now, let's take a look at an example
You can call ``enable example-mod`` and ``disable example-mod`` yourself while You can call ``enable example-mod`` and ``disable example-mod`` yourself while
developing, but for end users you can start your mod automatically from developing, but for end users you can start your mod automatically from
``raw/init.d/example-mod.lua``:: ``init.d/example-mod.lua``::
dfhack.run_command('enable example-mod') dfhack.run_command('enable example-mod')

@ -3,7 +3,7 @@ RemoteFortressReader
.. dfhack-tool:: .. dfhack-tool::
:summary: Backend for Armok Vision. :summary: Backend for Armok Vision.
:tags: untested dev graphics :tags: dev graphics
:no-command: :no-command:
.. dfhack-command:: RemoteFortressReader_version .. dfhack-command:: RemoteFortressReader_version

@ -2,48 +2,32 @@ autodump
======== ========
.. dfhack-tool:: .. dfhack-tool::
:summary: Automatically set items in a stockpile to be dumped. :summary: Instantly gather or destroy items marked for dumping.
:tags: untested fort armok fps productivity items stockpiles :tags: fort armok fps items
:no-command:
.. dfhack-command:: autodump
:summary: Teleports items marked for dumping to the cursor position.
.. dfhack-command:: autodump-destroy-here .. dfhack-command:: autodump-destroy-here
:summary: Destroy items marked for dumping under the cursor. :summary: Destroy items marked for dumping under the keyboard cursor.
.. dfhack-command:: autodump-destroy-item
:summary: Destroys the selected item.
When `enabled <enable>`, this plugin adds an option to the :kbd:`q` menu for This tool can instantly move all unforbidden items marked for dumping to the
stockpiles. When the ``autodump`` option is selected for the stockpile, any tile under the keyboard cursor. After moving the items, the dump flag is unset
items placed in the stockpile will automatically be designated to be dumped. 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.
When invoked as a command, it can instantly move all unforbidden items marked The keyboard cursor must be placed on a floor tile so the items can be dumped
for dumping to the tile under the cursor. After moving the items, the dump flag there.
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 Usage
----- -----
:: ::
enable autodump
autodump [<options>] autodump [<options>]
autodump-destroy-here autodump-destroy-here
autodump-destroy-item
``autodump-destroy-here`` is an alias for ``autodump destroy-here`` and is ``autodump-destroy-here`` is an alias for ``autodump destroy-here`` and is
intended for use as a keybinding. 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 Options
------- -------
@ -67,5 +51,5 @@ Examples
Teleports items marked for dumping to the cursor position. Teleports items marked for dumping to the cursor position.
``autodump destroy`` ``autodump destroy``
Destroys all unforbidden items marked for dumping Destroys all unforbidden items marked for dumping
``autodump-destroy-item`` ``autodump-destroy-here``
Destroys the selected item. Destroys items on the selected tile that are marked for dumping.

@ -3,7 +3,7 @@ blueprint
.. dfhack-tool:: .. dfhack-tool::
:summary: Record a live game map in a quickfort blueprint. :summary: Record a live game map in a quickfort blueprint.
:tags: untested fort design buildings map stockpiles :tags: fort design buildings map stockpiles
With ``blueprint``, you can export the structure of a portion of your fortress 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 in a blueprint file that you (or anyone else) can later play back with
@ -15,6 +15,13 @@ 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 used, starts at the active cursor location and extends right and down for the
requested width and height. requested width and height.
.. admonition:: Note
blueprint is still in the process of being updated for the new version of
DF. Stockpiles (the "place" phase), zones (the "zone" phase), building
configuration (the "query" phase), and game configuration (the "config"
phase) are not yet supported.
Usage Usage
----- -----

@ -3,7 +3,7 @@ cleanconst
.. dfhack-tool:: .. dfhack-tool::
:summary: Cleans up construction materials. :summary: Cleans up construction materials.
:tags: untested fort fps buildings :tags: fort fps buildings
This tool alters all constructions on the map so that they spawn their building 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 component when they are disassembled, allowing their actual build items to be

@ -6,7 +6,7 @@ cleaners
.. dfhack-tool:: .. dfhack-tool::
:summary: Provides commands for cleaning spatter from the map. :summary: Provides commands for cleaning spatter from the map.
:tags: untested adventure fort armok fps items map units :tags: adventure fort armok fps items map units
:no-command: :no-command:
.. dfhack-command:: clean .. dfhack-command:: clean

@ -3,7 +3,7 @@ cursecheck
.. dfhack-tool:: .. dfhack-tool::
:summary: Check for cursed creatures. :summary: Check for cursed creatures.
:tags: untested fort armok inspection units :tags: fort armok inspection units
This command checks a single map tile (or the whole map/world) for cursed This command checks a single map tile (or the whole map/world) for cursed
creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.). creatures (ghosts, vampires, necromancers, werebeasts, zombies, etc.).

@ -3,7 +3,7 @@ fastdwarf
.. dfhack-tool:: .. dfhack-tool::
:summary: Dwarves teleport and/or finish jobs instantly. :summary: Dwarves teleport and/or finish jobs instantly.
:tags: untested fort armok units :tags: fort armok units
Usage Usage
----- -----

@ -6,7 +6,7 @@ filltraffic
.. dfhack-tool:: .. dfhack-tool::
:summary: Set traffic designations using flood-fill starting at the cursor. :summary: Set traffic designations using flood-fill starting at the cursor.
:tags: untested fort design productivity map :tags: fort design productivity map
.. dfhack-command:: alltraffic .. dfhack-command:: alltraffic
:summary: Set traffic designations for every single tile of the map. :summary: Set traffic designations for every single tile of the map.

@ -3,7 +3,7 @@ misery
.. dfhack-tool:: .. dfhack-tool::
:summary: Increase the intensity of negative dwarven thoughts. :summary: Increase the intensity of negative dwarven thoughts.
:tags: untested fort armok auto units :tags: fort armok auto units
When enabled, negative thoughts that your dwarves have will multiply by the When enabled, negative thoughts that your dwarves have will multiply by the
specified factor. specified factor.

@ -3,7 +3,7 @@ nestboxes
.. dfhack-tool:: .. dfhack-tool::
:summary: Protect fertile eggs incubating in a nestbox. :summary: Protect fertile eggs incubating in a nestbox.
:tags: untested fort auto animals :tags: fort auto animals
:no-command: :no-command:
This plugin will automatically scan for and forbid fertile eggs incubating in a This plugin will automatically scan for and forbid fertile eggs incubating in a

@ -23,7 +23,7 @@ Usage
one-time orders first, then yearly, seasonally, monthly, and finally, daily. one-time orders first, then yearly, seasonally, monthly, and finally, daily.
You can keep your orders automatically sorted by adding the following command to You can keep your orders automatically sorted by adding the following command to
your ``onMapLoad.init`` file:: your ``dfhack-config/init/onMapLoad.init`` file::
repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ] repeat -name orders-sort -time 1 -timeUnits days -command [ orders sort ]

@ -3,7 +3,7 @@ tailor
.. dfhack-tool:: .. dfhack-tool::
:summary: Automatically keep your dwarves in fresh clothing. :summary: Automatically keep your dwarves in fresh clothing.
:tags: untested fort auto workorders :tags: fort auto workorders
Whenever the bookkeeper updates stockpile records, this plugin will scan the Whenever the bookkeeper updates stockpile records, this plugin will scan the
fort. If there are fresh cloths available, dwarves who are wearing tattered fort. If there are fresh cloths available, dwarves who are wearing tattered

@ -15,9 +15,10 @@ Quick Links
* `Downloads <https://www.github.com/DFHack/dfhack/releases>`_ * `Downloads <https://www.github.com/DFHack/dfhack/releases>`_
* `Installation guide <installing>` * `Installation guide <installing>`
* `quickstart`
* `Getting help <support>` * `Getting help <support>`
* :source:`Source code <>` * :source:`Source code <>`
(**important:** read `compile` before attempting to build from source.) (**important:** read `building-dfhack-index` before attempting to build from source.)
User Manual User Manual
=========== ===========
@ -26,6 +27,7 @@ User Manual
:maxdepth: 2 :maxdepth: 2
/docs/Introduction /docs/Introduction
/docs/Quickstart
/docs/Installing /docs/Installing
/docs/Core /docs/Core
/docs/Tools /docs/Tools

@ -472,9 +472,8 @@ void Core::getScriptPaths(std::vector<std::string> *dest)
if (df::global::world && isWorldLoaded()) { if (df::global::world && isWorldLoaded()) {
string save = World::ReadWorldFolder(); string save = World::ReadWorldFolder();
if (save.size()) if (save.size())
dest->push_back(df_path + "/data/save/" + save + "/raw/scripts"); dest->push_back(df_path + "/save/" + save + "/scripts");
} }
dest->push_back(df_path + "/raw/scripts");
dest->push_back(df_path + "/hack/scripts"); dest->push_back(df_path + "/hack/scripts");
for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it) for (auto it = script_paths[1].begin(); it != script_paths[1].end(); ++it)
dest->push_back(*it); dest->push_back(*it);
@ -2053,7 +2052,7 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve
if (!df::global::world) if (!df::global::world)
return; return;
std::string rawFolder = "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; std::string rawFolder = "save/" + (df::global::world->cur_savegame.save_dir) + "/init";
auto i = table.find(event); auto i = table.find(event);
if ( i != table.end() ) { if ( i != table.end() ) {
@ -2064,7 +2063,6 @@ void Core::handleLoadAndUnloadScripts(color_ostream& out, state_change_event eve
loadScriptFiles(this, out, set, CONFIG_PATH + "init"); loadScriptFiles(this, out, set, CONFIG_PATH + "init");
loadScriptFiles(this, out, set, rawFolder); loadScriptFiles(this, out, set, rawFolder);
loadScriptFiles(this, out, set, rawFolder + "objects/");
} }
for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it)
@ -2125,7 +2123,7 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
case SC_MAP_UNLOADED: case SC_MAP_UNLOADED:
if (world && world->cur_savegame.save_dir.size()) if (world && world->cur_savegame.save_dir.size())
{ {
std::string save_dir = "data/save/" + world->cur_savegame.save_dir; std::string save_dir = "save/" + world->cur_savegame.save_dir;
std::string evtlogpath = save_dir + "/events-dfhack.log"; std::string evtlogpath = save_dir + "/events-dfhack.log";
std::ofstream evtlog; std::ofstream evtlog;
evtlog.open(evtlogpath, std::ios_base::app); // append evtlog.open(evtlogpath, std::ios_base::app); // append

@ -1692,6 +1692,7 @@ static const LuaWrapper::FunctionReg dfhack_textures_module[] = {
WRAPM(Textures, getDfhackLogoTexposStart), WRAPM(Textures, getDfhackLogoTexposStart),
WRAPM(Textures, getGreenPinTexposStart), WRAPM(Textures, getGreenPinTexposStart),
WRAPM(Textures, getRedPinTexposStart), WRAPM(Textures, getRedPinTexposStart),
WRAPM(Textures, getIconsTexposStart),
{ NULL, NULL } { NULL, NULL }
}; };
@ -2238,6 +2239,7 @@ static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, constructWithItems), WRAPM(Buildings, constructWithItems),
WRAPM(Buildings, constructWithFilters), WRAPM(Buildings, constructWithFilters),
WRAPM(Buildings, deconstruct), WRAPM(Buildings, deconstruct),
WRAPM(Buildings, notifyCivzoneModified),
WRAPM(Buildings, markedForRemoval), WRAPM(Buildings, markedForRemoval),
WRAPM(Buildings, getRoomDescription), WRAPM(Buildings, getRoomDescription),
WRAPM(Buildings, isActivityZone), WRAPM(Buildings, isActivityZone),

@ -23,17 +23,20 @@ distribution.
*/ */
#pragma once #pragma once
#include "Export.h" #include "Export.h"
#include <algorithm> #include <algorithm>
#include <iostream>
#include <iomanip>
#include <cctype> #include <cctype>
#include <climits> #include <climits>
#include <stdint.h>
#include <vector>
#include <sstream>
#include <cstdio> #include <cstdio>
#include <iomanip>
#include <iostream>
#include <map>
#include <memory> #include <memory>
#include <sstream>
#include <stdint.h>
#include <vector>
#if defined(_MSC_VER) #if defined(_MSC_VER)
#define DFHACK_FUNCTION_SIG __FUNCSIG__ #define DFHACK_FUNCTION_SIG __FUNCSIG__
@ -338,6 +341,24 @@ inline typename T::mapped_type map_find(
return (it == map.end()) ? defval : it->second; return (it == map.end()) ? defval : it->second;
} }
template <class T, typename Fn>
static void for_each_(std::vector<T> &v, Fn func)
{
std::for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void for_each_(std::map<T, V> &v, Fn func)
{
std::for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(const std::vector<T> &src, std::vector<V> &dst, Fn func)
{
std::transform(src.begin(), src.end(), std::back_inserter(dst), func);
}
DFHACK_EXPORT bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail = NULL); DFHACK_EXPORT bool prefix_matches(const std::string &prefix, const std::string &key, std::string *tail = NULL);
template<typename T> template<typename T>

@ -3,7 +3,7 @@ coord(uint16_t _x, uint16_t _y, uint16_t _z) : x(_x), y(_y), z(_z) {}
operator coord2d() const { return coord2d(x,y); } operator coord2d() const { return coord2d(x,y); }
bool isValid() const { return x != -30000; } bool isValid() const { return x >= 0; }
void clear() { x = y = z = -30000; } void clear() { x = y = z = -30000; }
bool operator==(const coord &other) const bool operator==(const coord &other) const

@ -1,6 +1,6 @@
coord2d(uint16_t _x, uint16_t _y) : x(_x), y(_y) {} coord2d(uint16_t _x, uint16_t _y) : x(_x), y(_y) {}
bool isValid() const { return x != -30000; } bool isValid() const { return x >= 0; }
void clear() { x = y = -30000; } void clear() { x = y = -30000; }
bool operator==(const coord2d &other) const bool operator==(const coord2d &other) const

@ -202,6 +202,11 @@ DFHACK_EXPORT bool deconstruct(df::building *bld);
*/ */
DFHACK_EXPORT bool markedForRemoval(df::building *bld); DFHACK_EXPORT bool markedForRemoval(df::building *bld);
/**
* Rebuilds a civzones building associations after it has been modified
*/
DFHACK_EXPORT void notifyCivzoneModified(df::building* bld);
void updateBuildings(color_ostream& out, void* ptr); void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out); void clearBuildings(color_ostream& out);

@ -31,9 +31,15 @@ void cleanup();
DFHACK_EXPORT long getDfhackLogoTexposStart(); DFHACK_EXPORT long getDfhackLogoTexposStart();
/** /**
* Get the texpos for the UI pin tiles. Each are 2x2 grids. * Get the first texpos for the UI pin tiles. Each are 2x2 grids.
*/ */
DFHACK_EXPORT long getGreenPinTexposStart(); DFHACK_EXPORT long getGreenPinTexposStart();
DFHACK_EXPORT long getRedPinTexposStart(); DFHACK_EXPORT long getRedPinTexposStart();
/**
* Get the first texpos for the DFHack icons. It's a 5x2 grid.
*/
DFHACK_EXPORT long getIconsTexposStart();
} }
} }

@ -321,7 +321,7 @@ end
function pos2xyz(pos) function pos2xyz(pos)
if pos then if pos then
local x = pos.x local x = pos.x
if x and x ~= -30000 then if x and x >= 0 then
return x, pos.y, pos.z return x, pos.y, pos.z
end end
end end
@ -346,7 +346,7 @@ end
function pos2xy(pos) function pos2xy(pos)
if pos then if pos then
local x = pos.x local x = pos.x
if x and x ~= -30000 then if x and x >= 0 then
return x, pos.y return x, pos.y
end end
end end
@ -698,12 +698,15 @@ local valid_script_flags = {
local warned_scripts = {} local warned_scripts = {}
function dfhack.run_script(name,...) function dfhack.run_script(name,...)
if not warned_scripts[name] and require('helpdb').get_entry_tags(name).untested then if not warned_scripts[name] then
local helpdb = require('helpdb')
if helpdb.is_entry(name) and helpdb.get_entry_tags(name).untested then
warned_scripts[name] = true warned_scripts[name] = true
dfhack.printerr(('UNTESTED WARNING: the "%s" script has not been validated to work well with this version of DF.'):format(name)) dfhack.printerr(('UNTESTED WARNING: the "%s" script has not been validated to work well with this version of DF.'):format(name))
dfhack.printerr('It may not work as expected, or it may corrupt your game.') dfhack.printerr('It may not work as expected, or it may corrupt your game.')
qerror('Please run the command again to ignore this warning and proceed.') qerror('Please run the command again to ignore this warning and proceed.')
end end
end
return dfhack.run_script_with_env(nil, name, nil, ...) return dfhack.run_script_with_env(nil, name, nil, ...)
end end
@ -868,7 +871,7 @@ end
function dfhack.getSavePath() function dfhack.getSavePath()
if dfhack.isWorldLoaded() then if dfhack.isWorldLoaded() then
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir return dfhack.getDFPath() .. '/save/' .. df.global.world.cur_savegame.save_dir
end end
end end
@ -902,14 +905,14 @@ if dfhack.is_core_context then
local path = dfhack.getSavePath() local path = dfhack.getSavePath()
if path and op == SC_WORLD_LOADED then if path and op == SC_WORLD_LOADED then
loadInitFile(path, path..'/raw/init.lua') loadInitFile(path, path..'/init.lua')
local dirlist = dfhack.internal.getDir(path..'/raw/init.d/') local dirlist = dfhack.internal.getDir(path..'/init.d/')
if dirlist then if dirlist then
table.sort(dirlist) table.sort(dirlist)
for i,name in ipairs(dirlist) do for i,name in ipairs(dirlist) do
if string.match(name,'%.lua$') then if string.match(name,'%.lua$') then
loadInitFile(path, path..'/raw/init.d/'..name) loadInitFile(path, path..'/init.d/'..name)
end end
end end
end end

@ -481,7 +481,7 @@ end
function View:getMousePos(view_rect) function View:getMousePos(view_rect)
local rect = view_rect or self.frame_body local rect = view_rect or self.frame_body
local x,y = dscreen.getMousePos() local x,y = dscreen.getMousePos()
if rect and rect:inClipGlobalXY(x,y) then if rect and x and rect:inClipGlobalXY(x,y) then
return rect:localXY(x,y) return rect:localXY(x,y)
end end
end end
@ -725,6 +725,7 @@ function ZScreen:onInput(keys)
if ZScreen.super.onInput(self, keys) then if ZScreen.super.onInput(self, keys) then
-- ensure underlying DF screens don't also react to handled clicks -- ensure underlying DF screens don't also react to handled clicks
if keys._MOUSE_L_DOWN then if keys._MOUSE_L_DOWN then
-- note we can't clear mouse_lbut here. otherwise we break dragging,
df.global.enabler.mouse_lbut_down = 0 df.global.enabler.mouse_lbut_down = 0
end end
if keys._MOUSE_R_DOWN then if keys._MOUSE_R_DOWN then

@ -286,6 +286,19 @@ function get_hotkey_target(key)
end end
end end
function getMapKey(keys)
for code in pairs(keys) do
if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code]
or code == '_MOUSE_M_DOWN' or code == '_MOUSE_M'
or code == 'ZOOM_OUT' or code == 'ZOOM_IN' then
if not HOTKEY_KEYS[code] or get_hotkey_target(code) then
return true
end
return code
end
end
end
function Viewport:scrollByKey(key) function Viewport:scrollByKey(key)
local dx, dy, dz = get_movement_delta(key, 10, 20) local dx, dy, dz = get_movement_delta(key, 10, 20)
if dx then if dx then
@ -339,13 +352,9 @@ function DwarfOverlay:selectBuilding(building,cursor,viewport,gap)
end end
function DwarfOverlay:propagateMoveKeys(keys) function DwarfOverlay:propagateMoveKeys(keys)
for code,_ in pairs(keys) do local map_key = getMapKey(keys)
if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then if map_key then
if not HOTKEY_KEYS[code] or get_hotkey_target(code) then self:sendInputToParent(map_key)
self:sendInputToParent(code)
end
return code
end
end end
end end
@ -414,124 +423,45 @@ function DwarfOverlay:onAboutToShow(parent)
end end
end end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
MenuOverlay.ATTRS {
frame_inset = 0,
frame_background = gui.CLEAR_PEN,
-- if sidebar_mode is set, we will enter the specified sidebar mode on show
-- and restore the previous sidebar mode on dismiss. otherwise it is up to
-- the caller to ensure we are in a sidebar mode where the menu is visible.
sidebar_mode = DEFAULT_NIL,
}
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.')
end
if self.sidebar_mode then
self.saved_sidebar_mode = df.global.plotinfo.main.mode
-- what mode should we restore when this window is dismissed? ideally, we'd
-- restore the mode that the user has set, but we should fall back to
-- restoring the default mode if either of the following conditions are
-- true:
-- 1) enterSidebarMode doesn't support getting back into the current mode
-- 2) a dfhack viewscreen is currently visible. in this case, we can't trust
-- that the current sidebar mode was set by the user. it could just be a
-- MenuOverlay subclass that is currently being shown that has set the
-- sidebar mode for its own purposes.
if not SIDEBAR_MODE_KEYS[self.saved_sidebar_mode]
or dfhack.gui.getCurFocus(true):find('^dfhack/') then
self.saved_sidebar_mode = df.ui_sidebar_mode.Default
end
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")
end
end
function MenuOverlay:onDismiss()
if self.saved_sidebar_mode then
enterSidebarMode(self.saved_sidebar_mode)
end
end
function MenuOverlay:render(dc)
self:renderParent()
local menu = self.df_layout.menu
if menu then
-- Paint signature on the frame.
dscreen.paintString(
{fg=COLOR_BLACK,bg=COLOR_DARKGREY},
menu.x1+1, menu.y2+1, "DFHack"
)
if self.frame_background then
dc:fill(menu, self.frame_background)
end
MenuOverlay.super.render(self, dc)
end
end
-- Framework for managing rendering over the map area. This function is intended -- Framework for managing rendering over the map area. This function is intended
-- to be called from a subclass's onRenderBody() function. -- to be called from a window's onRenderFrame() function.
-- --
-- get_overlay_char_fn takes a coordinate position and an is_cursor boolean and -- get_overlay_pen_fn takes a coordinate position and an is_cursor boolean and
-- returns the char to render at that position and, optionally, the foreground -- returns the pen (and optional char and tile) to render at that position. If
-- and background colors to use to draw the char. If nothing should be rendered -- nothing should be rendered at that position, the function should return nil.
-- 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 -- 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 -- 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 -- get the range over which get_overlay_char_fn is called. If bounds_rect is not
-- specified, the entire viewport is scanned. -- specified, the entire viewport is scanned.
-- --
-- example call from a subclass: -- example call:
-- function MyMenuOverlaySubclass:onRenderBody() -- function MyMapOverlay:onRenderFrame(dc, rect)
-- local function get_overlay_char(pos) -- local function get_overlay_pen(pos)
-- return safe_index(self.overlay_chars, pos.z, pos.y, pos.x), COLOR_RED -- if safe_index(self.overlay_map, pos.z, pos.y, pos.x) then
-- return COLOR_GREEN, 'X', dfhack.screen.findGraphicsTile('CURSORS', 4, 3)
-- end -- end
-- self:renderMapOverlay(get_overlay_char, self.overlay_bounds)
-- end -- end
function MenuOverlay:renderMapOverlay(get_overlay_char_fn, bounds_rect) -- guidm.renderMapOverlay(get_overlay_pen, self.overlay_bounds)
local vp = self:getViewport() -- end
function renderMapOverlay(get_overlay_pen_fn, bounds_rect)
local vp = Viewport.get()
local rect = gui.ViewRect{rect=vp, local rect = gui.ViewRect{rect=vp,
clip_view=bounds_rect and gui.ViewRect{rect=bounds_rect} or nil} 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 -- nothing to do if the viewport is completely disjoint from the bounds_rect
if rect:isDefunct() then return end if rect:isDefunct() then return end
local dc = gui.Painter.new(self.df_layout.map)
local z = df.global.window_z local z = df.global.window_z
local cursor = getCursorPos() local cursor = getCursorPos()
for y=rect.clip_y1,rect.clip_y2 do for y=rect.clip_y1,rect.clip_y2 do
for x=rect.clip_x1,rect.clip_x2 do for x=rect.clip_x1,rect.clip_x2 do
local pos = xyz2pos(x, y, z) local pos = xyz2pos(x, y, z)
local overlay_char, fg_color, bg_color = get_overlay_char_fn( local overlay_pen, char, tile = get_overlay_pen_fn(pos, same_xy(cursor, pos))
pos, same_xy(cursor, pos)) if overlay_pen then
if not overlay_char then goto continue end
local stile = vp:tileToScreen(pos) local stile = vp:tileToScreen(pos)
dc:map(true):seek(stile.x, stile.y): dscreen.paintTile(overlay_pen, stile.x, stile.y, char, tile, true)
pen(fg_color or COLOR_GREEN, bg_color or COLOR_BLACK): end
char(overlay_char):map(false)
::continue::
end end
end end
end end

@ -456,6 +456,20 @@ end
function Panel:computeFrame(parent_rect) function Panel:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height local sw, sh = parent_rect.width, parent_rect.height
if self.frame then
if self.frame.t and self.frame.h and self.frame.t + self.frame.h > sh then
self.frame.t = math.max(0, sh - self.frame.h)
end
if self.frame.b and self.frame.h and self.frame.b + self.frame.h > sh then
self.frame.b = math.max(0, sh - self.frame.h)
end
if self.frame.l and self.frame.w and self.frame.l + self.frame.w > sw then
self.frame.l = math.max(0, sw - self.frame.w)
end
if self.frame.r and self.frame.w and self.frame.r + self.frame.w > sw then
self.frame.r = math.max(0, sw - self.frame.w)
end
end
return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset, return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset,
self.frame_style and 1 or 0) self.frame_style and 1 or 0)
end end
@ -530,6 +544,11 @@ Window.ATTRS {
ResizingPanel = defclass(ResizingPanel, Panel) ResizingPanel = defclass(ResizingPanel, Panel)
ResizingPanel.ATTRS{
auto_height = true,
auto_width = false,
}
-- adjust our frame dimensions according to positions and sizes of our subviews -- adjust our frame dimensions according to positions and sizes of our subviews
function ResizingPanel:postUpdateLayout(frame_body) function ResizingPanel:postUpdateLayout(frame_body)
local w, h = 0, 0 local w, h = 0, 0
@ -550,6 +569,8 @@ function ResizingPanel:postUpdateLayout(frame_body)
end end
if not self.frame then self.frame = {} end if not self.frame then self.frame = {} end
local oldw, oldh = self.frame.w, self.frame.h local oldw, oldh = self.frame.w, self.frame.h
if not self.auto_height then h = oldh end
if not self.auto_width then w = oldw end
self.frame.w, self.frame.h = w, h self.frame.w, self.frame.h = w, h
if not self._updateLayoutGuard and (oldw ~= w or oldh ~= h) then if not self._updateLayoutGuard and (oldw ~= w or oldh ~= h) then
self._updateLayoutGuard = true -- protect against infinite loops self._updateLayoutGuard = true -- protect against infinite loops
@ -708,7 +729,7 @@ function EditField:onInput(keys)
end end
end end
if self.key and keys.LEAVESCREEN then if self.key and (keys.LEAVESCREEN or keys._MOUSE_R_DOWN) then
local old = self.text local old = self.text
self:setText(self.saved_text) self:setText(self.saved_text)
if self.on_change and old ~= self.saved_text then if self.on_change and old ~= self.saved_text then
@ -1359,6 +1380,10 @@ function WrappedLabel:getWrappedText(width)
return text_to_wrap:wrap(width - self.indent) return text_to_wrap:wrap(width - self.indent)
end end
function WrappedLabel:preUpdateLayout()
self.saved_start_line_num = self.start_line_num
end
-- we can't set the text in init() since we may not yet have a frame that we -- we can't set the text in init() since we may not yet have a frame that we
-- can get wrapping bounds from. -- can get wrapping bounds from.
function WrappedLabel:postComputeFrame() function WrappedLabel:postComputeFrame()
@ -1371,6 +1396,7 @@ function WrappedLabel:postComputeFrame()
table.insert(text, NEWLINE) table.insert(text, NEWLINE)
end end
self:setText(text) self:setText(text)
self:scroll(self.saved_start_line_num - 1)
end end
------------------ ------------------
@ -1694,7 +1720,7 @@ function List:onRenderBody(dc)
local function paint_icon(icon, obj) local function paint_icon(icon, obj)
if type(icon) ~= 'string' then if type(icon) ~= 'string' then
dc:char(nil,icon) dc:tile(nil,icon)
else else
if current then if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)

@ -102,7 +102,6 @@ struct CoordHash {
}; };
static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding; static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding;
static unordered_map<df::coord, vector<int32_t>, CoordHash> locationToCivzones;
static df::building_extents_type *getExtentTile(df::building_extents &extent, df::coord2d tile) static df::building_extents_type *getExtentTile(df::building_extents &extent, df::coord2d tile)
{ {
@ -167,6 +166,163 @@ void buildings_onUpdate(color_ostream &out)
} }
} }
static void building_into_zone_unidir(df::building* bld, df::building_civzonest* zone)
{
for (size_t bid = 0; bid < zone->contained_buildings.size(); bid++)
{
if (zone->contained_buildings[bid] == bld)
return;
}
zone->contained_buildings.push_back(bld);
std::sort(zone->contained_buildings.begin(), zone->contained_buildings.end(), [](df::building* b1, df::building* b2)
{
return b1->id < b2->id;
});
}
static void zone_into_building_unidir(df::building* bld, df::building_civzonest* zone)
{
for (size_t bid = 0; bid < bld->relations.size(); bid++)
{
if (bld->relations[bid] == zone)
return;
}
bld->relations.push_back(zone);
std::sort(bld->relations.begin(), bld->relations.end(), [](df::building* b1, df::building* b2)
{
return b1->id < b2->id;
});
}
static bool is_suitable_building_for_zoning(df::building* bld)
{
return bld->canMakeRoom();
}
static void add_building_to_zone(df::building* bld, df::building_civzonest* zone)
{
if (!is_suitable_building_for_zoning(bld))
return;
building_into_zone_unidir(bld, zone);
zone_into_building_unidir(bld, zone);
}
static void add_building_to_all_zones(df::building* bld)
{
if (!is_suitable_building_for_zoning(bld))
return;
df::coord coord(bld->centerx, bld->centery, bld->z);
std::vector<df::building_civzonest*> cv;
Buildings::findCivzonesAt(&cv, coord);
for (size_t i=0; i < cv.size(); i++)
{
add_building_to_zone(bld, cv[i]);
}
}
static void add_zone_to_all_buildings(df::building* zone_as_building)
{
if (zone_as_building->getType() != building_type::Civzone)
return;
auto zone = strict_virtual_cast<df::building_civzonest>(zone_as_building);
if (zone == nullptr)
return;
auto& vec = world->buildings.other[buildings_other_id::IN_PLAY];
for (size_t i = 0; i < vec.size(); i++)
{
auto against = vec[i];
if (zone->z != against->z)
continue;
if (!is_suitable_building_for_zoning(against))
continue;
int32_t cx = against->centerx;
int32_t cy = against->centery;
df::coord2d coord(cx, cy);
//can a zone without extents exist?
if (zone->room.extents && zone->isExtentShaped())
{
auto etile = getExtentTile(zone->room, coord);
if (!etile || !*etile)
continue;
add_building_to_zone(against, zone);
}
}
}
static void remove_building_from_zone(df::building* bld, df::building_civzonest* zone)
{
for (int bid = 0; bid < (int)zone->contained_buildings.size(); bid++)
{
if (zone->contained_buildings[bid] == bld)
{
zone->contained_buildings.erase(zone->contained_buildings.begin() + bid);
bid--;
}
}
for (int bid = 0; bid < (int)bld->relations.size(); bid++)
{
if (bld->relations[bid] == zone)
{
bld->relations.erase(bld->relations.begin() + bid);
bid--;
}
}
}
static void remove_building_from_all_zones(df::building* bld)
{
df::coord coord(bld->centerx, bld->centery, bld->z);
std::vector<df::building_civzonest*> cv;
Buildings::findCivzonesAt(&cv, coord);
for (size_t i=0; i < cv.size(); i++)
{
remove_building_from_zone(bld, cv[i]);
}
}
static void remove_zone_from_all_buildings(df::building* zone_as_building)
{
if (zone_as_building->getType() != building_type::Civzone)
return;
auto zone = strict_virtual_cast<df::building_civzonest>(zone_as_building);
if (zone == nullptr)
return;
auto& vec = world->buildings.other[buildings_other_id::IN_PLAY];
//this is a paranoid check and slower than it could be. Zones contain a list of children
//good for fixing potentially bad game states when deleting a zone
for (size_t i = 0; i < vec.size(); i++)
{
df::building* bld = vec[i];
remove_building_from_zone(bld, zone);
}
}
uint32_t Buildings::getNumBuildings() uint32_t Buildings::getNumBuildings()
{ {
return world->buildings.all.size(); return world->buildings.all.size();
@ -325,78 +481,30 @@ static void cacheBuilding(df::building *building, bool is_civzone) {
for (int32_t y = p1.y; y <= p2.y; y++) { for (int32_t y = p1.y; y <= p2.y; y++) {
df::coord pt(x, y, building->z); df::coord pt(x, y, building->z);
if (Buildings::containsTile(building, pt, is_civzone)) { if (Buildings::containsTile(building, pt, is_civzone)) {
if (is_civzone) if (!is_civzone)
locationToCivzones[pt].push_back(id);
else
locationToBuilding[pt] = id; locationToBuilding[pt] = id;
} }
} }
} }
} }
static int32_t nextCivzone = 0;
static void cacheNewCivzones() {
if (!world || !building_next_id)
return;
int32_t nextBuildingId = *building_next_id;
for (int32_t id = nextCivzone; id < nextBuildingId; ++id) {
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], true);
}
nextCivzone = nextBuildingId;
}
bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec, bool Buildings::findCivzonesAt(std::vector<df::building_civzonest*> *pvec,
df::coord pos) { df::coord pos) {
pvec->clear(); pvec->clear();
// Tiles have an occupancy->bits.building flag to quickly determine if it is for (df::building_civzonest* zone : world->buildings.other.ACTIVITY_ZONE)
// covered by a buildling, but there is no such flag for civzones. {
// Therefore, we need to make sure that our cache is authoratative. if (pos.z != zone->z)
// Otherwise, we would have to fall back to linearly scanning the list of
// all civzones on a cache miss.
//
// Since we guarantee our cache contains *at least* all tiles that are
// currently covered by civzones, we can conclude that if a tile is not in
// the cache, there is no civzone there. Civzones *can* be dynamically
// shrunk, so we still need to verify that civzones that once covered this
// tile continue to cover this tile.
cacheNewCivzones();
auto civzones_it = locationToCivzones.find(pos);
if (civzones_it == locationToCivzones.end())
return false;
set<int32_t> ids_to_remove;
auto &civzones = civzones_it->second;
for (int32_t id : civzones) {
int32_t idx = df::building::binsearch_index(
world->buildings.other[buildings_other_id::ANY_ZONE], id);
df::building_civzonest *civzone = NULL;
if (idx > -1)
civzone = world->buildings.other.ANY_ZONE[idx];
if (!civzone || civzone->z != pos.z ||
!containsTile(civzone, pos, true)) {
ids_to_remove.insert(id);
continue; continue;
}
pvec->push_back(civzone);
}
// civzone no longer occupies this tile; update the cache if (zone->room.extents && zone->isExtentShaped())
if (!ids_to_remove.empty()) { {
for (auto it = civzones.begin(); it != civzones.end(); ) { auto etile = getExtentTile(zone->room, pos);
if (ids_to_remove.count(*it)) { if (!etile || !*etile)
it = civzones.erase(it);
continue; continue;
pvec->push_back(zone);
} }
++it;
}
if (civzones.empty())
locationToCivzones.erase(pos);
} }
return !pvec->empty(); return !pvec->empty();
@ -1053,7 +1161,6 @@ static int getMaxStockpileId()
return max_id; return max_id;
} }
/* TODO: understand how this changes for v50
static int getMaxCivzoneId() static int getMaxCivzoneId()
{ {
auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE];
@ -1068,7 +1175,6 @@ static int getMaxCivzoneId()
return max_id; return max_id;
} }
*/
bool Buildings::constructAbstract(df::building *bld) bool Buildings::constructAbstract(df::building *bld)
{ {
@ -1087,12 +1193,14 @@ bool Buildings::constructAbstract(df::building *bld)
stock->stockpile_number = getMaxStockpileId() + 1; stock->stockpile_number = getMaxStockpileId() + 1;
break; break;
/* TODO: understand how this changes for v50
case building_type::Civzone: case building_type::Civzone:
if (auto zone = strict_virtual_cast<df::building_civzonest>(bld)) if (auto zone = strict_virtual_cast<df::building_civzonest>(bld))
{
zone->zone_num = getMaxCivzoneId() + 1; zone->zone_num = getMaxCivzoneId() + 1;
add_zone_to_all_buildings(zone);
}
break; break;
*/
default: default:
break; break;
@ -1186,6 +1294,8 @@ bool Buildings::constructWithItems(df::building *bld, std::vector<df::item*> ite
bld->mat_index = items[i]->getMaterialIndex(); bld->mat_index = items[i]->getMaterialIndex();
} }
add_building_to_all_zones(bld);
createDesign(bld, rough); createDesign(bld, rough);
return true; return true;
} }
@ -1232,6 +1342,8 @@ bool Buildings::constructWithFilters(df::building *bld, std::vector<df::job_item
buildings_do_onupdate = true; buildings_do_onupdate = true;
add_building_to_all_zones(bld);
createDesign(bld, rough); createDesign(bld, rough);
return true; return true;
} }
@ -1272,6 +1384,10 @@ bool Buildings::deconstruct(df::building *bld)
// Don't clear arrows. // Don't clear arrows.
bld->uncategorize(); bld->uncategorize();
remove_building_from_all_zones(bld);
remove_zone_from_all_buildings(bld);
delete bld; delete bld;
if (world->selected_building == bld) if (world->selected_building == bld)
@ -1310,12 +1426,20 @@ bool Buildings::markedForRemoval(df::building *bld)
return false; return false;
} }
void Buildings::notifyCivzoneModified(df::building* bld)
{
if (bld->getType() != building_type::Civzone)
return;
//remove zone here needs to be the slow method
remove_zone_from_all_buildings(bld);
add_zone_to_all_buildings(bld);
}
void Buildings::clearBuildings(color_ostream& out) { void Buildings::clearBuildings(color_ostream& out) {
corner1.clear(); corner1.clear();
corner2.clear(); corner2.clear();
locationToBuilding.clear(); locationToBuilding.clear();
locationToCivzones.clear();
nextCivzone = 0;
} }
void Buildings::updateBuildings(color_ostream&, void* ptr) void Buildings::updateBuildings(color_ostream&, void* ptr)

@ -46,6 +46,7 @@ using namespace DFHack;
#include "modules/Screen.h" #include "modules/Screen.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h"
#include "DataDefs.h" #include "DataDefs.h"
@ -625,10 +626,8 @@ bool Gui::anywhere_hotkey(df::viewscreen *) {
return true; return true;
} }
bool Gui::dwarfmode_hotkey(df::viewscreen *top) bool Gui::dwarfmode_hotkey(df::viewscreen *top) {
{ return World::isFortressMode();
// Require the main dwarf mode screen
return !!strict_virtual_cast<df::viewscreen_dwarfmodest>(top);
} }
bool Gui::unitjobs_hotkey(df::viewscreen *top) bool Gui::unitjobs_hotkey(df::viewscreen *top)
@ -650,7 +649,7 @@ bool Gui::item_details_hotkey(df::viewscreen *top)
static bool has_cursor() static bool has_cursor()
{ {
return df::global::cursor && df::global::cursor->x != -30000; return Gui::getCursorPos().isValid();
} }
bool Gui::cursor_hotkey(df::viewscreen *top) bool Gui::cursor_hotkey(df::viewscreen *top)
@ -1721,7 +1720,7 @@ bool Gui::autoDFAnnouncement(df::report_init r, string message)
// Check if the announcement will actually be announced // Check if the announcement will actually be announced
if (*gamemode == game_mode::ADVENTURE) if (*gamemode == game_mode::ADVENTURE)
{ {
if (r.pos.x != -30000 && if (r.pos.x >= 0 &&
r.type != announcement_type::CREATURE_SOUND && r.type != announcement_type::CREATURE_SOUND &&
r.type != announcement_type::REGULAR_CONVERSATION && r.type != announcement_type::REGULAR_CONVERSATION &&
r.type != announcement_type::CONFLICT_CONVERSATION && r.type != announcement_type::CONFLICT_CONVERSATION &&
@ -2152,7 +2151,7 @@ bool Gui::getDesignationCoords (int32_t &x, int32_t &y, int32_t &z)
x = selection_rect->start_x; x = selection_rect->start_x;
y = selection_rect->start_y; y = selection_rect->start_y;
z = selection_rect->start_z; z = selection_rect->start_z;
return (x == -30000) ? false : true; return (x >= 0) ? false : true;
} }
bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t z) bool Gui::setDesignationCoords (const int32_t x, const int32_t y, const int32_t z)

@ -21,6 +21,7 @@ static long g_num_dfhack_textures = 0;
static long g_dfhack_logo_texpos_start = -1; static long g_dfhack_logo_texpos_start = -1;
static long g_green_pin_texpos_start = -1; static long g_green_pin_texpos_start = -1;
static long g_red_pin_texpos_start = -1; static long g_red_pin_texpos_start = -1;
static long g_icons_texpos_start = -1;
// Converts an arbitrary Surface to something like the display format // Converts an arbitrary Surface to something like the display format
// (32-bit RGBA), and converts magenta to transparency if convert_magenta is set // (32-bit RGBA), and converts magenta to transparency if convert_magenta is set
@ -117,6 +118,8 @@ void Textures::init(color_ostream &out) {
&g_green_pin_texpos_start); &g_green_pin_texpos_start);
g_num_dfhack_textures += load_textures(out, "hack/data/art/red-pin.png", g_num_dfhack_textures += load_textures(out, "hack/data/art/red-pin.png",
&g_red_pin_texpos_start); &g_red_pin_texpos_start);
g_num_dfhack_textures += load_textures(out, "hack/data/art/icons.png",
&g_icons_texpos_start);
DEBUG(textures,out).print("loaded %ld textures\n", g_num_dfhack_textures); DEBUG(textures,out).print("loaded %ld textures\n", g_num_dfhack_textures);
@ -160,3 +163,7 @@ long Textures::getGreenPinTexposStart() {
long Textures::getRedPinTexposStart() { long Textures::getRedPinTexposStart() {
return g_red_pin_texpos_start; return g_red_pin_texpos_start;
} }
long Textures::getIconsTexposStart() {
return g_icons_texpos_start;
}

@ -1455,7 +1455,7 @@ int Units::computeMovementSpeed(df::unit *unit)
if (isBaby(unit)) if (isBaby(unit))
speed += 3000; speed += 3000;
if (unit->flags3.bits.unk15) if (unit->flags3.bits.diving)
speed /= 20; speed /= 20;
if (unit->counters2.exhaustion >= 2000) if (unit->counters2.exhaustion >= 2000)
@ -1965,7 +1965,7 @@ int Units::getStressCategory(df::unit *unit)
if (!unit->status.current_soul) if (!unit->status.current_soul)
return int(stress_cutoffs.size()) / 2; return int(stress_cutoffs.size()) / 2;
return getStressCategoryRaw(unit->status.current_soul->personality.stress_level); return getStressCategoryRaw(unit->status.current_soul->personality.stress);
} }
int Units::getStressCategoryRaw(int32_t stress_level) int Units::getStressCategoryRaw(int32_t stress_level)

@ -1 +1 @@
Subproject commit 18446d51e4133a06271fd2672f5b816b8c56e937 Subproject commit 5d43fd9fd91007cf674bfde44b2c5a0ac70170db

@ -1,6 +1,6 @@
#!/bin/sh #!/bin/sh
DF_DIR=$(dirname "$0") DF_DIR=$(dirname "$(readlink -f "$0")")
cd "${DF_DIR}" cd "${DF_DIR}"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack" export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./hack/libs":"./hack"

@ -78,7 +78,7 @@ set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua) dfhack_plugin(autobutcher autobutcher.cpp LINK_LIBRARIES lua)
dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua) dfhack_plugin(autochop autochop.cpp LINK_LIBRARIES lua)
dfhack_plugin(autoclothing autoclothing.cpp) dfhack_plugin(autoclothing autoclothing.cpp)
#dfhack_plugin(autodump autodump.cpp) dfhack_plugin(autodump autodump.cpp)
dfhack_plugin(autofarm autofarm.cpp) dfhack_plugin(autofarm autofarm.cpp)
#dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static) #dfhack_plugin(autogems autogems.cpp LINK_LIBRARIES jsoncpp_static)
#add_subdirectory(autolabor) #add_subdirectory(autolabor)
@ -86,20 +86,20 @@ dfhack_plugin(autofarm autofarm.cpp)
#dfhack_plugin(automelt automelt.cpp) #dfhack_plugin(automelt automelt.cpp)
#dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua) #dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua)
#dfhack_plugin(autotrade autotrade.cpp) #dfhack_plugin(autotrade autotrade.cpp)
#dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua)
#dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) #dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua)
#dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua) #dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua)
#add_subdirectory(buildingplan) dfhack_plugin(buildingplan buildingplan.cpp LINK_LIBRARIES lua)
#dfhack_plugin(changeitem changeitem.cpp) #dfhack_plugin(changeitem changeitem.cpp)
dfhack_plugin(changelayer changelayer.cpp) dfhack_plugin(changelayer changelayer.cpp)
dfhack_plugin(changevein changevein.cpp) dfhack_plugin(changevein changevein.cpp)
#add_subdirectory(channel-safely) #add_subdirectory(channel-safely)
#dfhack_plugin(cleanconst cleanconst.cpp) dfhack_plugin(cleanconst cleanconst.cpp)
#dfhack_plugin(cleaners cleaners.cpp) dfhack_plugin(cleaners cleaners.cpp)
dfhack_plugin(cleanowned cleanowned.cpp) dfhack_plugin(cleanowned cleanowned.cpp)
#dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua) #dfhack_plugin(confirm confirm.cpp LINK_LIBRARIES lua)
#dfhack_plugin(createitem createitem.cpp) #dfhack_plugin(createitem createitem.cpp)
#dfhack_plugin(cursecheck cursecheck.cpp) dfhack_plugin(cursecheck cursecheck.cpp)
#dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua) #dfhack_plugin(cxxrandom cxxrandom.cpp LINK_LIBRARIES lua)
#dfhack_plugin(deramp deramp.cpp) #dfhack_plugin(deramp deramp.cpp)
dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(debug debug.cpp LINK_LIBRARIES jsoncpp_static)
@ -113,7 +113,7 @@ dfhack_plugin(dig-now dig-now.cpp LINK_LIBRARIES lua)
#dfhack_plugin(embark-tools embark-tools.cpp) #dfhack_plugin(embark-tools embark-tools.cpp)
dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua)
dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(fastdwarf fastdwarf.cpp)
#dfhack_plugin(filltraffic filltraffic.cpp) dfhack_plugin(filltraffic filltraffic.cpp)
#dfhack_plugin(fix-unit-occupancy fix-unit-occupancy.cpp) #dfhack_plugin(fix-unit-occupancy fix-unit-occupancy.cpp)
#dfhack_plugin(fixveins fixveins.cpp) #dfhack_plugin(fixveins fixveins.cpp)
#dfhack_plugin(flows flows.cpp) #dfhack_plugin(flows flows.cpp)
@ -130,10 +130,10 @@ dfhack_plugin(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) #dfhack_plugin(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
#dfhack_plugin(manipulator manipulator.cpp) #dfhack_plugin(manipulator manipulator.cpp)
#dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua) #dfhack_plugin(map-render map-render.cpp LINK_LIBRARIES lua)
#dfhack_plugin(misery misery.cpp) dfhack_plugin(misery misery.cpp)
#dfhack_plugin(mode mode.cpp) #dfhack_plugin(mode mode.cpp)
#dfhack_plugin(mousequery mousequery.cpp) #dfhack_plugin(mousequery mousequery.cpp)
#dfhack_plugin(nestboxes nestboxes.cpp) dfhack_plugin(nestboxes nestboxes.cpp)
dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static) dfhack_plugin(orders orders.cpp LINK_LIBRARIES jsoncpp_static)
dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua) dfhack_plugin(overlay overlay.cpp LINK_LIBRARIES lua)
dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua)
@ -149,7 +149,7 @@ add_subdirectory(remotefortressreader)
dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua) dfhack_plugin(reveal reveal.cpp LINK_LIBRARIES lua)
#dfhack_plugin(search search.cpp) #dfhack_plugin(search search.cpp)
dfhack_plugin(seedwatch seedwatch.cpp) dfhack_plugin(seedwatch seedwatch.cpp)
#dfhack_plugin(showmood showmood.cpp) dfhack_plugin(showmood showmood.cpp)
#dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua) #dfhack_plugin(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
#dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua) #dfhack_plugin(sort sort.cpp LINK_LIBRARIES lua)
#dfhack_plugin(steam-engine steam-engine.cpp) #dfhack_plugin(steam-engine steam-engine.cpp)
@ -158,7 +158,7 @@ dfhack_plugin(seedwatch seedwatch.cpp)
#add_subdirectory(stockpiles) #add_subdirectory(stockpiles)
#dfhack_plugin(stocks stocks.cpp) #dfhack_plugin(stocks stocks.cpp)
#dfhack_plugin(strangemood strangemood.cpp) #dfhack_plugin(strangemood strangemood.cpp)
#dfhack_plugin(tailor tailor.cpp) dfhack_plugin(tailor tailor.cpp)
dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua) dfhack_plugin(tiletypes tiletypes.cpp Brushes.h LINK_LIBRARIES lua)
#dfhack_plugin(title-folder title-folder.cpp) #dfhack_plugin(title-folder title-folder.cpp)
#dfhack_plugin(title-version title-version.cpp) #dfhack_plugin(title-version title-version.cpp)

@ -522,10 +522,8 @@ static void scan_logs(int32_t *usable_logs, const vector<df::unit *> &citizens,
if (item->getType() != item_type::WOOD) if (item->getType() != item_type::WOOD)
continue; continue;
if (!is_valid_item(item)) { if (!is_valid_item(item))
INFO(status).print("autochop is_valid_item actually caught something useful!! Please tell the DFHack team.\n");
continue; continue;
}
if (!is_accessible_item(item->pos, citizens)) { if (!is_accessible_item(item->pos, citizens)) {
if (inaccessible_logs) if (inaccessible_logs)

@ -1,33 +1,34 @@
// Quick Dumper : Moves items marked as "dump" to cursor // Quick Dumper : Moves items marked as "dump" to cursor
// FIXME: local item cache in map blocks needs to be fixed after teleporting items // FIXME: local item cache in map blocks needs to be fixed after teleporting items
#include <iostream>
#include <iomanip>
#include <sstream>
#include <climits>
#include <vector>
#include <string>
#include <algorithm>
#include <set>
using namespace std;
#include "Core.h" #include "Core.h"
#include "Console.h" #include "Console.h"
#include "DataDefs.h"
#include "Export.h" #include "Export.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "DataDefs.h"
#include "df/item.h" #include "df/item.h"
#include "df/world.h" #include "df/world.h"
#include "df/general_ref.h" #include "df/general_ref.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/building_stockpilest.h" #include "df/building_stockpilest.h"
#include "uicommon.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <climits>
#include <vector>
#include <string>
#include <algorithm>
#include <set>
using namespace std;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -40,6 +41,7 @@ DFHACK_PLUGIN("autodump");
REQUIRE_GLOBAL(gps); REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
/* TODO: merge with stockpiles plugin
// Stockpile interface START // Stockpile interface START
static const string PERSISTENCE_KEY = "autodump/stockpiles"; static const string PERSISTENCE_KEY = "autodump/stockpiles";
@ -267,6 +269,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
} }
// Stockpile interface END // Stockpile interface END
*/
command_result df_autodump(color_ostream &out, vector <string> & parameters); command_result df_autodump(color_ostream &out, vector <string> & parameters);
command_result df_autodump_destroy_here(color_ostream &out, vector <string> & parameters); command_result df_autodump_destroy_here(color_ostream &out, vector <string> & parameters);
@ -276,18 +279,21 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
{ {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autodump", "autodump",
"Teleport items marked for dumping to the cursor.", "Teleport items marked for dumping to the keyboard cursor.",
df_autodump)); df_autodump,
Gui::cursor_hotkey));
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autodump-destroy-here", "autodump-destroy-here",
"Destroy items marked for dumping under cursor.", "Destroy items marked for dumping under the keyboard cursor.",
df_autodump_destroy_here, df_autodump_destroy_here,
Gui::cursor_hotkey)); Gui::cursor_hotkey));
/* you can no longer select items
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"autodump-destroy-item", "autodump-destroy-item",
"Destroy the selected item.", "Destroy the selected item.",
df_autodump_destroy_item, df_autodump_destroy_item,
Gui::any_item_hotkey)); Gui::any_item_hotkey));
*/
return CR_OK; return CR_OK;
} }
@ -340,16 +346,14 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
MapCache MC; MapCache MC;
int dumped_total = 0; int dumped_total = 0;
int cx, cy, cz; df::coord pos_cursor;
DFCoord pos_cursor;
if(!destroy || here) if(!destroy || here)
{ {
if (!Gui::getCursorCoords(cx,cy,cz)) pos_cursor = Gui::getCursorPos();
{ if (!pos_cursor.isValid()) {
out.printerr("Cursor position not found. Please enable the cursor.\n"); out.printerr("Keyboard cursor must be over a suitable map tile.\n");
return CR_FAILURE; return CR_FAILURE;
} }
pos_cursor = DFCoord(cx,cy,cz);
} }
if (!destroy) if (!destroy)
{ {
@ -441,13 +445,14 @@ command_result df_autodump(color_ostream &out, vector <string> & parameters)
command_result df_autodump_destroy_here(color_ostream &out, vector <string> & parameters) command_result df_autodump_destroy_here(color_ostream &out, vector <string> & parameters)
{ {
// HOTKEY COMMAND; CORE ALREADY SUSPENDED
if (!parameters.empty()) if (!parameters.empty())
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
vector<string> args; vector<string> args;
args.push_back("destroy-here"); args.push_back("destroy-here");
CoreSuspender suspend;
return autodump_main(out, args); return autodump_main(out, args);
} }

@ -332,6 +332,7 @@ public:
void status(color_ostream& out) void status(color_ostream& out)
{ {
out << "Autofarm is " << (enabled ? "Active." : "Stopped.") << '\n'; out << "Autofarm is " << (enabled ? "Active." : "Stopped.") << '\n';
for (auto& lc : lastCounts) for (auto& lc : lastCounts)
{ {
auto plant = world->raws.plants.all[lc.first]; auto plant = world->raws.plants.all[lc.first];
@ -340,7 +341,7 @@ public:
for (auto& th : thresholds) for (auto& th : thresholds)
{ {
if (lastCounts[th.first] > 0) if (lastCounts.count(th.first) > 0)
continue; continue;
auto plant = world->raws.plants.all[th.first]; auto plant = world->raws.plants.all[th.first];
out << plant->id << " limit " << getThreshold(th.first) << " current 0" << '\n'; out << plant->id << " limit " << getThreshold(th.first) << " current 0" << '\n';

@ -299,7 +299,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render);
bool read_config(color_ostream &out) { bool read_config(color_ostream &out) {
std::string path = "data/save/" + World::ReadWorldFolder() + "/autogems.json"; std::string path = "save/" + World::ReadWorldFolder() + "/autogems.json";
if (!Filesystem::isfile(path)) { if (!Filesystem::isfile(path)) {
// no need to require the config file to exist // no need to require the config file to exist
return true; return true;

@ -850,6 +850,7 @@ static const char * get_tile_build(const df::coord &pos,
return add_expansion_syntax(ctx, keys); return add_expansion_syntax(ctx, keys);
} }
/* TODO: understand how this changes for v50
static const char * get_place_keys(const tile_context &ctx) { static const char * get_place_keys(const tile_context &ctx) {
df::building_stockpilest* sp = df::building_stockpilest* sp =
virtual_cast<df::building_stockpilest>(ctx.b); virtual_cast<df::building_stockpilest>(ctx.b);
@ -1086,6 +1087,7 @@ static const char * get_tile_rooms(const df::coord &, const tile_context &ctx) {
str << "r{+ " << (max_dim - 3) << "}&"; str << "r{+ " << (max_dim - 3) << "}&";
return cache(str); return cache(str);
} }
*/
static bool create_output_dir(color_ostream &out, static bool create_output_dir(color_ostream &out,
const blueprint_options &opts) { const blueprint_options &opts) {
@ -1326,6 +1328,7 @@ static bool do_transform(color_ostream &out,
get_tile_construct, ensure_building); get_tile_construct, ensure_building);
add_processor(processors, opts, "build", "build", opts.build, add_processor(processors, opts, "build", "build", opts.build,
get_tile_build, ensure_building); get_tile_build, ensure_building);
/* TODO: understand how this changes for v50
add_processor(processors, opts, "place", "place", opts.place, add_processor(processors, opts, "place", "place", opts.place,
get_tile_place, ensure_building); get_tile_place, ensure_building);
add_processor(processors, opts, "zone", "zone", opts.zone, get_tile_zone); add_processor(processors, opts, "zone", "zone", opts.zone, get_tile_zone);
@ -1333,6 +1336,14 @@ static bool do_transform(color_ostream &out,
get_tile_query, ensure_building); get_tile_query, ensure_building);
add_processor(processors, opts, "query", "rooms", opts.rooms, add_processor(processors, opts, "query", "rooms", opts.rooms,
get_tile_rooms, ensure_building); get_tile_rooms, ensure_building);
*/ if (opts.place)
out.printerr("'place' blueprints are not yet supported for the current version of DF\n");
if (opts.zone)
out.printerr("'zone' blueprints are not yet supported for the current version of DF\n");
if (opts.query)
out.printerr("'query' blueprints are not yet supported for the current version of DF\n");
if (opts.rooms)
out.printerr("'rooms' blueprints are not yet supported for the current version of DF\n");
if (processors.empty()) { if (processors.empty()) {
out.printerr("no phases requested! nothing to do!\n"); out.printerr("no phases requested! nothing to do!\n");

@ -0,0 +1,688 @@
#include "Core.h"
#include "Debug.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/building_design.h"
#include "df/item.h"
#include "df/job_item.h"
#include "df/world.h"
#include <queue>
#include <string>
#include <vector>
#include <unordered_map>
using std::map;
using std::pair;
using std::queue;
using std::string;
using std::unordered_map;
using std::vector;
using namespace DFHack;
DFHACK_PLUGIN("buildingplan");
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(buildingplan, status, DebugCategory::LINFO);
// for logging during the periodic scan
DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO);
}
static const string CONFIG_KEY = string(plugin_name) + "/config";
static const string BLD_CONFIG_KEY = string(plugin_name) + "/building";
enum ConfigValues {
CONFIG_BLOCKS = 1,
CONFIG_BOULDERS = 2,
CONFIG_LOGS = 3,
CONFIG_BARS = 4,
};
enum BuildingConfigValues {
BLD_CONFIG_ID = 0,
};
static int get_config_val(PersistentDataItem &c, int index) {
if (!c.isValid())
return -1;
return c.ival(index);
}
static bool get_config_bool(PersistentDataItem &c, int index) {
return get_config_val(c, index) == 1;
}
static void set_config_val(PersistentDataItem &c, int index, int value) {
if (c.isValid())
c.ival(index) = value;
}
static void set_config_bool(PersistentDataItem &c, int index, bool value) {
set_config_val(c, index, value ? 1 : 0);
}
class PlannedBuilding {
public:
const df::building::key_field_type id;
PlannedBuilding(color_ostream &out, df::building *building) : id(building->id) {
DEBUG(status,out).print("creating persistent data for building %d\n", id);
bld_config = DFHack::World::AddPersistentData(BLD_CONFIG_KEY);
set_config_val(bld_config, BLD_CONFIG_ID, id);
}
PlannedBuilding(DFHack::PersistentDataItem &bld_config)
: id(get_config_val(bld_config, BLD_CONFIG_ID)), bld_config(bld_config) { }
void remove(color_ostream &out);
// Ensure the building still exists and is in a valid state. It can disappear
// for lots of reasons, such as running the game with the buildingplan plugin
// disabled, manually removing the building, modifying it via the API, etc.
df::building * getBuildingIfValidOrRemoveIfNot(color_ostream &out) {
auto bld = df::building::find(id);
bool valid = bld && bld->getBuildStage() == 0;
if (!valid) {
remove(out);
return NULL;
}
return bld;
}
private:
DFHack::PersistentDataItem bld_config;
};
static PersistentDataItem config;
// building id -> PlannedBuilding
unordered_map<int32_t, PlannedBuilding> planned_buildings;
// vector id -> filter bucket -> queue of (building id, job_item index)
map<df::job_item_vector_id, map<string, queue<pair<int32_t, int>>>> tasks;
// note that this just removes the PlannedBuilding. the tasks will get dropped
// as we discover them in the tasks queues and they fail to be found in planned_buildings.
// this "lazy" task cleaning algorithm works because there is no way to
// re-register a building once it has been removed -- if it has been booted out of
// planned_buildings, then it has either been built or desroyed. therefore there is
// no chance of duplicate tasks getting added to the tasks queues.
void PlannedBuilding::remove(color_ostream &out) {
DEBUG(status,out).print("removing persistent data for building %d\n", id);
DFHack::World::DeletePersistentData(config);
if (planned_buildings.count(id) > 0)
planned_buildings.erase(id);
}
static const int32_t CYCLE_TICKS = 600; // twice per game day
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
static command_result do_command(color_ostream &out, vector<string> &parameters);
static void do_cycle(color_ostream &out);
static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
DEBUG(status,out).print("initializing %s\n", plugin_name);
// provide a configuration interface for the plugin
commands.push_back(PluginCommand(
plugin_name,
"Plan building placement before you have materials.",
do_command));
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
if (enable != is_enabled) {
is_enabled = enable;
DEBUG(status,out).print("%s from the API; persisting\n",
is_enabled ? "enabled" : "disabled");
} 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, CONFIG_BLOCKS, true);
set_config_bool(config, CONFIG_BOULDERS, true);
set_config_bool(config, CONFIG_LOGS, true);
set_config_bool(config, CONFIG_BARS, false);
}
DEBUG(status,out).print("loading persisted state\n");
planned_buildings.clear();
tasks.clear();
vector<PersistentDataItem> building_configs;
World::GetPersistentData(&building_configs, BLD_CONFIG_KEY);
const size_t num_building_configs = building_configs.size();
for (size_t idx = 0; idx < num_building_configs; ++idx) {
PlannedBuilding pb(building_configs[idx]);
registerPlannedBuilding(out, pb);
}
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_WORLD_UNLOADED) {
DEBUG(status,out).print("world unloaded; clearing state for %s\n", plugin_name);
planned_buildings.clear();
tasks.clear();
}
return CR_OK;
}
static bool cycle_requested = false;
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (!Core::getInstance().isWorldLoaded())
return CR_OK;
if (is_enabled &&
(cycle_requested || world->frame_counter - cycle_timestamp >= CYCLE_TICKS))
do_cycle(out);
return CR_OK;
}
static bool call_buildingplan_lua(color_ostream *out, const char *fn_name,
int nargs = 0, int nres = 0,
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
DEBUG(status).print("calling buildingplan lua function: '%s'\n", fn_name);
CoreSuspender guard;
auto L = Lua::Core::State;
Lua::StackUnwinder top(L);
if (!out)
out = &Core::getInstance().getConsole();
return Lua::CallLuaModuleFunction(*out, L, "plugins.buildingplan", fn_name,
nargs, nres,
std::forward<Lua::LuaLambda&&>(args_lambda),
std::forward<Lua::LuaLambda&&>(res_lambda));
}
static command_result do_command(color_ostream &out, vector<string> &parameters) {
CoreSuspender suspend;
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot configure %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
bool show_help = false;
if (!call_buildingplan_lua(&out, "parse_commandline", parameters.size(), 1,
[&](lua_State *L) {
for (const string &param : parameters)
Lua::Push(L, param);
},
[&](lua_State *L) {
show_help = !lua_toboolean(L, -1);
})) {
return CR_FAILURE;
}
return show_help ? CR_WRONG_USAGE : CR_OK;
}
/////////////////////////////////////////////////////
// cycle logic
//
struct BadFlags {
uint32_t whole;
BadFlags() {
df::item_flags flags;
#define F(x) flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(in_job);
F(owned); F(in_chest); F(removed); F(encased);
F(spider_web);
#undef F
whole = flags.whole;
}
};
static bool itemPassesScreen(df::item * item) {
static const BadFlags bad_flags;
return !(item->flags.whole & bad_flags.whole)
&& !item->isAssignedToStockpile();
}
static bool matchesFilters(df::item * item, df::job_item * job_item) {
// check the properties that are not checked by Job::isSuitableItem()
if (job_item->item_type > -1 && job_item->item_type != item->getType())
return false;
if (job_item->item_subtype > -1 &&
job_item->item_subtype != item->getSubtype())
return false;
if (job_item->flags2.bits.building_material && !item->isBuildMat())
return false;
if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore))
return false;
if (job_item->has_tool_use > df::tool_uses::NONE
&& !item->hasToolUse(job_item->has_tool_use))
return false;
return DFHack::Job::isSuitableItem(
job_item, item->getType(), item->getSubtype())
&& DFHack::Job::isSuitableMaterial(
job_item, item->getMaterial(), item->getMaterialIndex(),
item->getType());
}
static bool isJobReady(color_ostream &out, df::job * job) {
int needed_items = 0;
for (auto job_item : job->job_items) { needed_items += job_item->quantity; }
if (needed_items) {
DEBUG(cycle,out).print("building needs %d more item(s)\n", needed_items);
return false;
}
return true;
}
static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) {
// we want the items in the opposite order of the filters
return a->job_item_idx > b->job_item_idx;
}
// this function does not remove the job_items since their quantity fields are
// now all at 0, so there is no risk of having extra items attached. we don't
// remove them to keep the "finalize with buildingplan active" path as similar
// as possible to the "finalize with buildingplan disabled" path.
static void finalizeBuilding(color_ostream &out, df::building * bld) {
DEBUG(cycle,out).print("finalizing building %d\n", bld->id);
auto job = bld->jobs[0];
// sort the items so they get added to the structure in the correct order
std::sort(job->items.begin(), job->items.end(), job_item_idx_lt);
// derive the material properties of the building and job from the first
// applicable item. if any boulders are involved, it makes the whole
// structure "rough".
bool rough = false;
for (auto attached_item : job->items) {
df::item *item = attached_item->item;
rough = rough || item->getType() == df::item_type::BOULDER;
if (bld->mat_type == -1) {
bld->mat_type = item->getMaterial();
job->mat_type = bld->mat_type;
}
if (bld->mat_index == -1) {
bld->mat_index = item->getMaterialIndex();
job->mat_index = bld->mat_index;
}
}
if (bld->needsDesign()) {
auto act = (df::building_actual *)bld;
if (!act->design)
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
// we're good to go!
job->flags.bits.suspend = false;
Job::checkBuildingsNow();
}
static df::building * popInvalidTasks(color_ostream &out, queue<pair<int32_t, int>> & task_queue) {
while (!task_queue.empty()) {
auto & task = task_queue.front();
auto id = task.first;
if (planned_buildings.count(id) > 0) {
auto bld = planned_buildings.at(id).getBuildingIfValidOrRemoveIfNot(out);
if (bld && bld->jobs[0]->job_items[task.second]->quantity)
return bld;
}
DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second);
task_queue.pop();
}
return NULL;
}
static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
map<string, queue<pair<int32_t, int>>> & buckets) {
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
auto item_vector = df::global::world->items.other[other_id];
DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n",
item_vector.size(),
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
buckets.size());
for (auto item_it = item_vector.rbegin();
item_it != item_vector.rend();
++item_it) {
auto item = *item_it;
if (!itemPassesScreen(item))
continue;
for (auto bucket_it = buckets.begin(); bucket_it != buckets.end(); ) {
auto & task_queue = bucket_it->second;
auto bld = popInvalidTasks(out, task_queue);
if (!bld) {
DEBUG(cycle,out).print("removing empty bucket: %s/%s; %zu bucket(s) left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
bucket_it->first.c_str(),
buckets.size() - 1);
bucket_it = buckets.erase(bucket_it);
continue;
}
auto & task = task_queue.front();
auto id = task.first;
auto job = bld->jobs[0];
auto filter_idx = task.second;
if (matchesFilters(item, job->job_items[filter_idx])
&& DFHack::Job::attachJobItem(job, item,
df::job_item_ref::Hauled, filter_idx))
{
MaterialInfo material;
material.decode(item);
ItemTypeInfo item_type;
item_type.decode(item);
DEBUG(cycle,out).print("attached %s %s to filter %d for %s(%d): %s/%s\n",
material.toString().c_str(),
item_type.toString().c_str(),
filter_idx,
ENUM_KEY_STR(building_type, bld->getType()).c_str(),
id,
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
bucket_it->first.c_str());
// keep quantity aligned with the actual number of remaining
// items so if buildingplan is turned off, the building will
// be completed with the correct number of items.
--job->job_items[filter_idx]->quantity;
task_queue.pop();
if (isJobReady(out, job)) {
finalizeBuilding(out, bld);
planned_buildings.at(id).remove(out);
}
if (task_queue.empty()) {
DEBUG(cycle,out).print(
"removing empty item bucket: %s/%s; %zu left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
bucket_it->first.c_str(),
buckets.size() - 1);
buckets.erase(bucket_it);
}
// we found a home for this item; no need to look further
break;
}
++bucket_it;
}
if (buckets.empty())
break;
}
}
struct VectorsToScanLast {
std::vector<df::job_item_vector_id> vectors;
VectorsToScanLast() {
// order is important here. we want to match boulders before wood and
// everything before bars. blocks are not listed here since we'll have
// already scanned them when we did the first pass through the buckets.
vectors.push_back(df::job_item_vector_id::BOULDER);
vectors.push_back(df::job_item_vector_id::WOOD);
vectors.push_back(df::job_item_vector_id::BAR);
}
};
static void do_cycle(color_ostream &out) {
static const VectorsToScanLast vectors_to_scan_last;
// mark that we have recently run
cycle_timestamp = world->frame_counter;
cycle_requested = false;
DEBUG(cycle,out).print("running %s cycle for %zu registered buildings\n",
plugin_name, planned_buildings.size());
for (auto it = tasks.begin(); it != tasks.end(); ) {
auto vector_id = it->first;
// we could make this a set, but it's only three elements
if (std::find(vectors_to_scan_last.vectors.begin(),
vectors_to_scan_last.vectors.end(),
vector_id) != vectors_to_scan_last.vectors.end()) {
++it;
continue;
}
auto & buckets = it->second;
doVector(out, vector_id, buckets);
if (buckets.empty()) {
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
tasks.size() - 1);
it = tasks.erase(it);
}
else
++it;
}
for (auto vector_id : vectors_to_scan_last.vectors) {
if (tasks.count(vector_id) == 0)
continue;
auto & buckets = tasks[vector_id];
doVector(out, vector_id, buckets);
if (buckets.empty()) {
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
tasks.size() - 1);
tasks.erase(vector_id);
}
}
DEBUG(cycle,out).print("cycle done; %zu registered building(s) left\n",
planned_buildings.size());
}
/////////////////////////////////////////////////////
// Lua API
// core will already be suspended when coming in through here
//
static string getBucket(const df::job_item & ji) {
std::ostringstream ser;
// pull out and serialize only known relevant fields. if we miss a few, then
// the filter bucket will be slighly less specific than it could be, but
// that's probably ok. we'll just end up bucketing slightly different items
// together. this is only a problem if the different filter at the front of
// the queue doesn't match any available items and blocks filters behind it
// that could be matched.
ser << ji.item_type << ':' << ji.item_subtype << ':' << ji.mat_type << ':'
<< ji.mat_index << ':' << ji.flags1.whole << ':' << ji.flags2.whole
<< ':' << ji.flags3.whole << ':' << ji.flags4 << ':' << ji.flags5 << ':'
<< ji.metal_ore << ':' << ji.has_tool_use;
return ser.str();
}
// get a list of item vectors that we should search for matches
static vector<df::job_item_vector_id> getVectorIds(color_ostream &out, df::job_item *job_item)
{
std::vector<df::job_item_vector_id> ret;
// if the filter already has the vector_id set to something specific, use it
if (job_item->vector_id > df::job_item_vector_id::IN_PLAY)
{
DEBUG(status,out).print("using vector_id from job_item: %s\n",
ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str());
ret.push_back(job_item->vector_id);
return ret;
}
// if the filer is for building material, refer to our global settings for
// which vectors to search
if (job_item->flags2.bits.building_material)
{
if (get_config_bool(config, CONFIG_BLOCKS))
ret.push_back(df::job_item_vector_id::BLOCKS);
if (get_config_bool(config, CONFIG_BOULDERS))
ret.push_back(df::job_item_vector_id::BOULDER);
if (get_config_bool(config, CONFIG_LOGS))
ret.push_back(df::job_item_vector_id::WOOD);
if (get_config_bool(config, CONFIG_BARS))
ret.push_back(df::job_item_vector_id::BAR);
}
// fall back to IN_PLAY if no other vector was appropriate
if (ret.empty())
ret.push_back(df::job_item_vector_id::IN_PLAY);
return ret;
}
static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) {
df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out);
if (!bld)
return false;
if (bld->jobs.size() != 1) {
DEBUG(status,out).print("unexpected number of jobs: want 1, got %zu\n", bld->jobs.size());
return false;
}
auto job_items = bld->jobs[0]->job_items;
int num_job_items = job_items.size();
if (num_job_items < 1) {
DEBUG(status,out).print("unexpected number of job items: want >0, got %d\n", num_job_items);
return false;
}
int32_t id = bld->id;
for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) {
auto job_item = job_items[job_item_idx];
auto bucket = getBucket(*job_item);
auto vector_ids = getVectorIds(out, job_item);
// if there are multiple vector_ids, schedule duplicate tasks. after
// the correct number of items are matched, the extras will get popped
// as invalid
for (auto vector_id : vector_ids) {
for (int item_num = 0; item_num < job_item->quantity; ++item_num) {
tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx));
DEBUG(status,out).print("added task: %s/%s/%d,%d; "
"%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket",
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
bucket.c_str(), id, job_item_idx, tasks.size(),
tasks[vector_id].size(), tasks[vector_id][bucket].size());
}
}
}
// suspend jobs
for (auto job : bld->jobs)
job->flags.bits.suspend = true;
// add the planned buildings to our register
planned_buildings.emplace(bld->id, pb);
return true;
}
static void printStatus(color_ostream &out) {
DEBUG(status,out).print("entering buildingplan_printStatus\n");
out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled");
out.print(" finding materials for %zd buildings\n", planned_buildings.size());
out.print("Current settings:\n");
out.print(" use blocks: %s\n", get_config_bool(config, CONFIG_BLOCKS) ? "yes" : "no");
out.print(" use boulders: %s\n", get_config_bool(config, CONFIG_BOULDERS) ? "yes" : "no");
out.print(" use logs: %s\n", get_config_bool(config, CONFIG_LOGS) ? "yes" : "no");
out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no");
out.print("\n");
}
static bool setSetting(color_ostream &out, string name, bool value) {
DEBUG(status,out).print("entering setSetting (%s -> %s)\n", name.c_str(), value ? "true" : "false");
if (name == "blocks")
set_config_bool(config, CONFIG_BLOCKS, value);
else if (name == "boulders")
set_config_bool(config, CONFIG_BOULDERS, value);
else if (name == "logs")
set_config_bool(config, CONFIG_LOGS, value);
else if (name == "bars")
set_config_bool(config, CONFIG_BARS, value);
else {
out.printerr("unrecognized setting: '%s'\n", name.c_str());
return false;
}
return true;
}
static bool isPlannableBuilding(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom) {
DEBUG(status,out).print("entering isPlannableBuilding\n");
int num_filters = 0;
if (!call_buildingplan_lua(&out, "get_num_filters", 3, 1,
[&](lua_State *L) {
Lua::Push(L, type);
Lua::Push(L, subtype);
Lua::Push(L, custom);
},
[&](lua_State *L) {
num_filters = lua_tonumber(L, -1);
})) {
return false;
}
return num_filters >= 1;
}
static bool isPlannedBuilding(color_ostream &out, df::building *bld) {
TRACE(status,out).print("entering isPlannedBuilding\n");
return bld && planned_buildings.count(bld->id) > 0;
}
static bool addPlannedBuilding(color_ostream &out, df::building *bld) {
DEBUG(status,out).print("entering addPlannedBuilding\n");
if (!bld || planned_buildings.count(bld->id)
|| !isPlannableBuilding(out, bld->getType(), bld->getSubtype(),
bld->getCustomType()))
return false;
PlannedBuilding pb(out, bld);
return registerPlannedBuilding(out, pb);
}
static void doCycle(color_ostream &out) {
DEBUG(status,out).print("entering doCycle\n");
do_cycle(out);
}
static void scheduleCycle(color_ostream &out) {
DEBUG(status,out).print("entering scheduleCycle\n");
cycle_requested = true;
}
DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(printStatus),
DFHACK_LUA_FUNCTION(setSetting),
DFHACK_LUA_FUNCTION(isPlannableBuilding),
DFHACK_LUA_FUNCTION(isPlannedBuilding),
DFHACK_LUA_FUNCTION(addPlannedBuilding),
DFHACK_LUA_FUNCTION(doCycle),
DFHACK_LUA_FUNCTION(scheduleCycle),
DFHACK_LUA_END
};

@ -63,7 +63,7 @@ function parse_commandline(...)
elseif command == 'undesignate' then elseif command == 'undesignate' then
autochop_undesignate() autochop_undesignate()
elseif command == 'target' then elseif command == 'target' then
setTarget(args[2], args[3]) setTargets(args[2], args[3])
elseif command == 'chop' then elseif command == 'chop' then
do_set_burrow_config('chop', true, args[2]) do_set_burrow_config('chop', true, args[2])
elseif command == 'nochop' then elseif command == 'nochop' then

@ -14,11 +14,31 @@ local _ENV = mkmodule('plugins.buildingplan')
--]] --]]
local dialogs = require('gui.dialogs') local argparse = require('argparse')
local guidm = require('gui.dwarfmode')
require('dfhack.buildings') require('dfhack.buildings')
-- does not need the core suspended 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(...)
local args, opts = {...}, {}
local positionals = process_args(opts, args)
if opts.help then
return false
end
return true
end
function get_num_filters(btype, subtype, custom) function get_num_filters(btype, subtype, custom)
local filters = dfhack.buildings.getFiltersByType( local filters = dfhack.buildings.getFiltersByType(
{}, btype, subtype, custom) {}, btype, subtype, custom)
@ -26,6 +46,9 @@ function get_num_filters(btype, subtype, custom)
return 0 return 0
end end
local dialogs = require('gui.dialogs')
local guidm = require('gui.dwarfmode')
local function to_title_case(str) local function to_title_case(str)
str = str:gsub('(%a)([%w_]*)', str = str:gsub('(%a)([%w_]*)',
function (first, rest) return first:upper()..rest:lower() end) function (first, rest) return first:upper()..rest:lower() end)

@ -32,12 +32,7 @@ function HotspotMenuWidget:overlay_onupdate()
end end
function HotspotMenuWidget:overlay_trigger() function HotspotMenuWidget:overlay_trigger()
local hotkeys, bindings = getHotkeys() return MenuScreen{hotspot_frame=self.frame}:show()
return MenuScreen{
hotspot_frame=self.frame,
hotkeys=hotkeys,
bindings=bindings,
mouseover=self.mouseover}:show()
end end
local dscreen = dfhack.screen local dscreen = dfhack.screen
@ -71,21 +66,17 @@ end
-- register the menu hotspot with the overlay -- register the menu hotspot with the overlay
OVERLAY_WIDGETS = {menu=HotspotMenuWidget} OVERLAY_WIDGETS = {menu=HotspotMenuWidget}
-- ---------- -- -- ---- --
-- MenuScreen -- -- Menu --
-- ---------- -- -- ---- --
local ARROW = string.char(26) local ARROW = string.char(26)
local MAX_LIST_WIDTH = 45 local MAX_LIST_WIDTH = 45
local MAX_LIST_HEIGHT = 15 local MAX_LIST_HEIGHT = 15
MenuScreen = defclass(MenuScreen, gui.Screen) Menu = defclass(MenuScreen, widgets.Panel)
MenuScreen.ATTRS{ Menu.ATTRS{
focus_path='hotkeys/menu',
hotspot_frame=DEFAULT_NIL, hotspot_frame=DEFAULT_NIL,
hotkeys=DEFAULT_NIL,
bindings=DEFAULT_NIL,
mouseover=false,
} }
-- get a map from the binding string to a list of hotkey strings that all -- get a map from the binding string to a list of hotkey strings that all
@ -142,23 +133,30 @@ local function get_choices(hotkeys, bindings, is_inverted)
return choices, max_width return choices, max_width
end end
function MenuScreen:init() function Menu:init()
local hotkeys, bindings = getHotkeys()
local is_inverted = not not self.hotspot_frame.b local is_inverted = not not self.hotspot_frame.b
local choices,list_width = get_choices(self.hotkeys, self.bindings, local choices,list_width = get_choices(hotkeys, bindings, is_inverted)
is_inverted)
local list_frame = copyall(self.hotspot_frame) local list_frame = copyall(self.hotspot_frame)
local list_widget_frame = {h=math.min(#choices, MAX_LIST_HEIGHT)}
local quickstart_frame = {}
list_frame.w = list_width + 2 list_frame.w = list_width + 2
list_frame.h = math.min(#choices, MAX_LIST_HEIGHT) + 2 list_frame.h = list_widget_frame.h + 4
if list_frame.t then if list_frame.t then
list_frame.t = math.max(0, list_frame.t - 1) list_frame.t = math.max(0, list_frame.t - 1)
list_widget_frame.t = 0
quickstart_frame.b = 0
else else
list_frame.b = math.max(0, list_frame.b - 1) list_frame.b = math.max(0, list_frame.b - 1)
list_widget_frame.b = 0
quickstart_frame.t = 0
end end
if list_frame.l then if list_frame.l then
list_frame.l = math.max(0, list_frame.l - 1) list_frame.l = math.max(0, list_frame.l + 5)
else else
list_frame.r = math.max(0, list_frame.r - 1) list_frame.r = math.max(0, list_frame.r + 5)
end end
local help_frame = {w=list_frame.w, l=list_frame.l, r=list_frame.r} local help_frame = {w=list_frame.w, l=list_frame.l, r=list_frame.r}
@ -169,21 +167,30 @@ function MenuScreen:init()
end end
self:addviews{ self:addviews{
widgets.ResizingPanel{ widgets.Panel{
view_id='list_panel', view_id='list_panel',
autoarrange_subviews=true,
frame=list_frame, frame=list_frame,
frame_style=gui.GREY_LINE_FRAME, frame_style=gui.GREY_LINE_FRAME,
frame_background=gui.CLEAR_PEN, frame_background=gui.CLEAR_PEN,
subviews={ subviews={
widgets.List{ widgets.List{
view_id='list', view_id='list',
frame=list_widget_frame,
choices=choices, choices=choices,
icon_width=2, icon_width=2,
on_select=self:callback('onSelect'), on_select=self:callback('onSelect'),
on_submit=self:callback('onSubmit'), on_submit=self:callback('onSubmit'),
on_submit2=self:callback('onSubmit2'), on_submit2=self:callback('onSubmit2'),
}, },
widgets.Panel{frame={h=1}},
widgets.HotkeyLabel{
frame=quickstart_frame,
label='Quickstart guide',
key='STRING_A063',
on_activate=function()
self:onSubmit(nil, {command='quickstart-guide'})
end,
},
}, },
}, },
widgets.ResizingPanel{ widgets.ResizingPanel{
@ -207,11 +214,7 @@ function MenuScreen:init()
end end
end end
function MenuScreen:onDismiss() function Menu:onSelect(_, choice)
cleanupHotkeys()
end
function MenuScreen:onSelect(_, choice)
if not choice or #self.subviews == 0 then return end if not choice or #self.subviews == 0 then return end
local first_word = choice.command:trim():split(' +')[1] local first_word = choice.command:trim():split(' +')[1]
if first_word:startswith(':') then first_word = first_word:sub(2) end if first_word:startswith(':') then first_word = first_word:sub(2) end
@ -220,22 +223,21 @@ function MenuScreen:onSelect(_, choice)
self.subviews.help_panel:updateLayout() self.subviews.help_panel:updateLayout()
end end
function MenuScreen:onSubmit(_, choice) function Menu:onSubmit(_, choice)
if not choice then return end if not choice then return end
dfhack.screen.hideGuard(self, dfhack.run_command, choice.command) dfhack.screen.hideGuard(self.parent_view, dfhack.run_command, choice.command)
self:dismiss() self.parent_view:dismiss()
end end
function MenuScreen:onSubmit2(_, choice) function Menu:onSubmit2(_, choice)
if not choice then return end if not choice then return end
self:dismiss() self.parent_view:dismiss()
dfhack.run_script('gui/launcher', choice.command) dfhack.run_script('gui/launcher', choice.command)
end end
function MenuScreen:onInput(keys) function Menu:onInput(keys)
if keys.LEAVESCREEN then if keys.LEAVESCREEN or keys._MOUSE_R_DOWN then
self:dismiss() return false
return true
elseif keys.STANDARDSCROLL_RIGHT then elseif keys.STANDARDSCROLL_RIGHT then
self:onSubmit2(self.subviews.list:getSelected()) self:onSubmit2(self.subviews.list:getSelected())
return true return true
@ -246,19 +248,29 @@ function MenuScreen:onInput(keys)
self:onSubmit2(list:getSelected()) self:onSubmit2(list:getSelected())
return true return true
end end
if not self:getMouseFramePos() then
self.parent_view:dismiss()
return true
end
end end
return self:inputToSubviews(keys) self:inputToSubviews(keys)
return true -- we're modal
end end
function MenuScreen:onRenderFrame(dc, rect) function Menu:onRenderFrame(dc, rect)
if self.initialize then if self.initialize then
self.initialize() self.initialize()
self.initialize = nil self.initialize = nil
end end
self:renderParent() Menu.super.onRenderFrame(dc, rect)
end
function Menu:getMouseFramePos()
return self.subviews.list_panel:getMouseFramePos() or
self.subviews.help_panel:getMouseFramePos()
end end
function MenuScreen:onRenderBody(dc) function Menu:onRenderBody(dc)
local panel = self.subviews.list_panel local panel = self.subviews.list_panel
local list = self.subviews.list local list = self.subviews.list
local idx = list:getIdxUnderMouse() local idx = list:getIdxUnderMouse()
@ -267,14 +279,35 @@ function MenuScreen:onRenderBody(dc)
-- selection, don't override the selection until the mouse moves to -- selection, don't override the selection until the mouse moves to
-- another item -- another item
list:setSelected(idx) list:setSelected(idx)
self.mouseover = true
self.last_mouse_idx = idx self.last_mouse_idx = idx
elseif not panel:getMousePos(gui.ViewRect{rect=panel.frame_rect}) end
and self.mouseover then if self:getMouseFramePos() then
self.mouseover = true
elseif self.mouseover then
-- once the mouse has entered the list area, leaving the frame should -- once the mouse has entered the list area, leaving the frame should
-- close the menu screen -- close the menu screen
self:dismiss() self.parent_view:dismiss()
end end
end end
-- ---------- --
-- MenuScreen --
-- ---------- --
MenuScreen = defclass(MenuScreen, gui.ZScreen)
MenuScreen.ATTRS {
focus_path='hotkeys/menu',
hotspot_frame=DEFAULT_NIL,
}
function MenuScreen:init()
self:addviews{
Menu{hotspot_frame=self.hotspot_frame},
}
end
function MenuScreen:onDismiss()
cleanupHotkeys()
end
return _ENV return _ENV

@ -43,7 +43,12 @@ end
local function triggered_screen_has_lock() local function triggered_screen_has_lock()
if not trigger_lock_holder_screen then return false end if not trigger_lock_holder_screen then return false end
if trigger_lock_holder_screen:isActive() then return true end if trigger_lock_holder_screen:isActive() then
if trigger_lock_holder_screen.raise then
trigger_lock_holder_screen:raise()
end
return true
end
return register_trigger_lock_screen(nil, nil) return register_trigger_lock_screen(nil, nil)
end end
@ -343,7 +348,7 @@ end
local function do_trigger(args, quiet) local function do_trigger(args, quiet)
if triggered_screen_has_lock() then if triggered_screen_has_lock() then
dfhack.printerr(('cannot trigger widget; widget "%s" is already active') dfhack.printerr(('cannot trigger widget; widget "%s" is already active')
:format(active_triggered_widget)) :format(trigger_lock_holder_description))
return return
end end
do_by_names_or_numbers(args[1], function(name, db_entry) do_by_names_or_numbers(args[1], function(name, db_entry)
@ -429,9 +434,8 @@ local function _update_viewscreen_widgets(vs_name, vs, now_ms)
return now_ms return now_ms
end end
-- not subject to trigger lock since these widgets are already filtered by
-- viewscreen
function update_viewscreen_widgets(vs_name, vs) function update_viewscreen_widgets(vs_name, vs)
if triggered_screen_has_lock() then return end
local now_ms = _update_viewscreen_widgets(vs_name, vs, nil) local now_ms = _update_viewscreen_widgets(vs_name, vs, nil)
_update_viewscreen_widgets('all', vs, now_ms) _update_viewscreen_widgets('all', vs, now_ms)
end end

@ -41,7 +41,7 @@ DFHACK_PLUGIN("orders");
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
static const std::string ORDERS_DIR = "dfhack-config/orders"; static const std::string ORDERS_DIR = "dfhack-config/orders";
static const std::string ORDERS_LIBRARY_DIR = "dfhack-config/orders/library"; static const std::string ORDERS_LIBRARY_DIR = "hack/data/orders";
static command_result orders_command(color_ostream & out, std::vector<std::string> & parameters); static command_result orders_command(color_ostream & out, std::vector<std::string> & parameters);
@ -86,6 +86,11 @@ static command_result orders_command(color_ostream & out, std::vector<std::strin
return orders_list_command(out); return orders_list_command(out);
} }
if (!Core::getInstance().isWorldLoaded()) {
out.printerr("Cannot run %s without a loaded world.\n", plugin_name);
return CR_FAILURE;
}
if (parameters[0] == "export" && parameters.size() == 2) if (parameters[0] == "export" && parameters.size() == 2)
{ {
return orders_export_command(out, parameters[1]); return orders_export_command(out, parameters[1]);
@ -139,19 +144,9 @@ static command_result orders_list_command(color_ostream & out)
// support subdirs so we can identify and ignore subdirs with ".json" names. // support subdirs so we can identify and ignore subdirs with ".json" names.
// also listdir_recursive will alphabetize the list for us. // also listdir_recursive will alphabetize the list for us.
std::map<std::string, bool> files; std::map<std::string, bool> files;
if (0 < Filesystem::listdir_recursive(ORDERS_DIR, files, 0, false)) Filesystem::listdir_recursive(ORDERS_DIR, files, 0, false);
{
out << COLOR_LIGHTRED << "Unable to list files in directory: " << ORDERS_DIR << std::endl;
return CR_FAILURE;
}
if (files.empty()) for (auto it : files) {
{
out << COLOR_YELLOW << "No exported orders yet. Create manager orders and export them with 'orders export <name>', or copy pre-made orders .json files into " << ORDERS_DIR << "." << std::endl << std::endl;
}
for (auto it : files)
{
if (it.second) if (it.second)
continue; // skip directories continue; // skip directories
std::string name = it.first; std::string name = it.first;

@ -619,13 +619,13 @@ void CopyBuilding(int buildingIndex, RemoteFortressReader::BuildingInstance * re
continue; continue;
if (zone->type != df::civzone_type::ArcheryRange) if (zone->type != df::civzone_type::ArcheryRange)
continue; continue;
if(zone->dir_x < 0) if(zone->zone_settings.archery.dir_x < 0)
remote_build->set_direction(EAST); remote_build->set_direction(EAST);
else if(zone->dir_x > 0) else if(zone->zone_settings.archery.dir_x > 0)
remote_build->set_direction(WEST); remote_build->set_direction(WEST);
else if (zone->dir_y < 0) else if (zone->zone_settings.archery.dir_y < 0)
remote_build->set_direction(SOUTH); remote_build->set_direction(SOUTH);
else if (zone->dir_y > 0) else if (zone->zone_settings.archery.dir_y > 0)
remote_build->set_direction(NORTH); remote_build->set_direction(NORTH);
break; break;
} }

@ -1171,11 +1171,11 @@ void lightingEngineViewscreen::loadSettings()
std::string rawFolder; std::string rawFolder;
if(df::global::world->cur_savegame.save_dir!="") if(df::global::world->cur_savegame.save_dir!="")
{ {
rawFolder= "data/save/" + (df::global::world->cur_savegame.save_dir) + "/raw/"; rawFolder= "save/" + (df::global::world->cur_savegame.save_dir) + "/";
} }
else else
{ {
rawFolder= "raw/"; rawFolder= "dfhack-config/";
} }
const std::string settingsfile=rawFolder+"rendermax.lua"; const std::string settingsfile=rawFolder+"rendermax.lua";

@ -1 +1 @@
Subproject commit 6376bbc630feaf8b4f68623cdd2c28c87ac11af4 Subproject commit a045369db6728979e709625908df3f4f36e868ca

@ -6,6 +6,7 @@
#include "Core.h" #include "Core.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "Debug.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
@ -29,18 +30,23 @@
#include "modules/World.h" #include "modules/World.h"
using namespace DFHack; using namespace DFHack;
using namespace std;
using df::global::world; using df::global::world;
using df::global::plotinfo; using df::global::plotinfo;
DFHACK_PLUGIN("tailor"); DFHACK_PLUGIN("tailor");
#define AUTOENABLE false #define AUTOENABLE false
DFHACK_PLUGIN_IS_ENABLED(enabled); DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(plotinfo); REQUIRE_GLOBAL(plotinfo);
namespace DFHack {
DBG_DECLARE(tailor, cycle, DebugCategory::LINFO);
DBG_DECLARE(tailor, config, DebugCategory::LINFO);
}
class Tailor { class Tailor {
// ARMOR, SHOES, HELM, GLOVES, PANTS // ARMOR, SHOES, HELM, GLOVES, PANTS
@ -48,7 +54,7 @@ class Tailor {
private: private:
const map<df::job_type, df::item_type> jobTypeMap = { const std::map<df::job_type, df::item_type> jobTypeMap = {
{ df::job_type::MakeArmor, df::item_type::ARMOR }, { df::job_type::MakeArmor, df::item_type::ARMOR },
{ df::job_type::MakePants, df::item_type::PANTS }, { df::job_type::MakePants, df::item_type::PANTS },
{ df::job_type::MakeHelm, df::item_type::HELM }, { df::job_type::MakeHelm, df::item_type::HELM },
@ -56,7 +62,7 @@ private:
{ df::job_type::MakeShoes, df::item_type::SHOES } { df::job_type::MakeShoes, df::item_type::SHOES }
}; };
const map<df::item_type, df::job_type> itemTypeMap = { const std::map<df::item_type, df::job_type> itemTypeMap = {
{ df::item_type::ARMOR, df::job_type::MakeArmor }, { df::item_type::ARMOR, df::job_type::MakeArmor },
{ df::item_type::PANTS, df::job_type::MakePants }, { df::item_type::PANTS, df::job_type::MakePants },
{ df::item_type::HELM, df::job_type::MakeHelm }, { df::item_type::HELM, df::job_type::MakeHelm },
@ -107,13 +113,13 @@ private:
std::list<MatType> all_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER }; std::list<MatType> all_materials = { M_SILK, M_CLOTH, M_YARN, M_LEATHER };
map<pair<df::item_type, int>, int> available; // key is item type & size std::map<std::pair<df::item_type, int>, int> available; // key is item type & size
map<pair<df::item_type, int>, int> needed; // same std::map<std::pair<df::item_type, int>, int> needed; // same
map<pair<df::item_type, int>, int> queued; // same std::map<std::pair<df::item_type, int>, int> queued; // same
map<int, int> sizes; // this maps body size to races std::map<int, int> sizes; // this maps body size to races
map<tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size std::map<std::tuple<df::job_type, int, int>, int> orders; // key is item type, item subtype, size
std::map<MatType, int> supply; std::map<MatType, int> supply;
@ -147,7 +153,7 @@ private:
df::item_type t = i->getType(); df::item_type t = i->getType();
int size = world->raws.creatures.all[i->getMakerRace()]->adultsize; int size = world->raws.creatures.all[i->getMakerRace()]->adultsize;
available[make_pair(t, size)] += 1; available[std::make_pair(t, size)] += 1;
} }
} }
@ -180,7 +186,7 @@ private:
supply[M_LEATHER] += i->getStackSize(); supply[M_LEATHER] += i->getStackSize();
} }
out->print("tailor: available silk %d yarn %d cloth %d leather %d\n", supply[M_SILK], supply[M_YARN], supply[M_CLOTH], supply[M_LEATHER]); DEBUG(cycle).print("tailor: available silk %d yarn %d cloth %d leather %d\n", supply[M_SILK], supply[M_YARN], supply[M_CLOTH], supply[M_LEATHER]);
} }
void scan_replacements() void scan_replacements()
@ -193,10 +199,10 @@ private:
Units::isBaby(u)) Units::isBaby(u))
continue; // skip units we don't control continue; // skip units we don't control
set <df::item_type> wearing; std::set <df::item_type> wearing;
wearing.clear(); wearing.clear();
deque<df::item*> worn; std::deque<df::item*> worn;
worn.clear(); worn.clear();
for (auto inv : u->inventory) for (auto inv : u->inventory)
@ -212,10 +218,16 @@ private:
int size = world->raws.creatures.all[u->race]->adultsize; int size = world->raws.creatures.all[u->race]->adultsize;
sizes[size] = u->race; sizes[size] = u->race;
for (auto ty : set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES }) for (auto ty : std::set<df::item_type>{ df::item_type::ARMOR, df::item_type::PANTS, df::item_type::SHOES })
{ {
if (wearing.count(ty) == 0) if (wearing.count(ty) == 0)
needed[make_pair(ty, size)] += 1; {
TRACE(cycle).print("tailor: one %s of size %d needed to cover %s\n",
ENUM_KEY_STR(item_type, ty).c_str(),
size,
Translation::TranslateName(&u->name, false).c_str());
needed[std::make_pair(ty, size)] += 1;
}
} }
for (auto w : worn) for (auto w : worn)
@ -227,13 +239,13 @@ private:
std::string description; std::string description;
w->getItemDescription(&description, 0); w->getItemDescription(&description, 0);
if (available[make_pair(ty, size)] > 0) if (available[std::make_pair(ty, size)] > 0)
{ {
if (w->flags.bits.owned) if (w->flags.bits.owned)
{ {
bool confiscated = Items::setOwner(w, NULL); bool confiscated = Items::setOwner(w, NULL);
out->print( INFO(cycle).print(
"tailor: %s %s from %s.\n", "tailor: %s %s from %s.\n",
(confiscated ? "confiscated" : "could not confiscate"), (confiscated ? "confiscated" : "could not confiscate"),
description.c_str(), description.c_str(),
@ -242,18 +254,22 @@ private:
} }
if (wearing.count(ty) == 0) if (wearing.count(ty) == 0)
available[make_pair(ty, size)] -= 1; {
DEBUG(cycle).print("tailor: allocating a %s to %s\n",
ENUM_KEY_STR(item_type, ty).c_str(),
Translation::TranslateName(&u->name, false).c_str());
available[std::make_pair(ty, size)] -= 1;
}
if (w->getWear() > 1) if (w->getWear() > 1)
w->flags.bits.dump = true; w->flags.bits.dump = true;
} }
else else
{ {
// out->print("%s worn by %s needs replacement\n", DEBUG(cycle).print ("%s worn by %s needs replacement, but none available\n",
// description.c_str(), description.c_str(),
// Translation::TranslateName(&u->name, false).c_str() Translation::TranslateName(&u->name, false).c_str());
// ); orders[std::make_tuple(o, w->getSubtype(), size)] += 1;
orders[make_tuple(o, w->getSubtype(), size)] += 1;
} }
} }
} }
@ -270,7 +286,7 @@ private:
int count = a.second; int count = a.second;
int sub = 0; int sub = 0;
vector<int16_t> v; std::vector<int16_t> v;
switch (ty) { switch (ty) {
case df::item_type::ARMOR: v = entity->resources.armor_type; break; case df::item_type::ARMOR: v = entity->resources.armor_type; break;
@ -299,7 +315,8 @@ private:
} }
const df::job_type j = itemTypeMap.at(ty); const df::job_type j = itemTypeMap.at(ty);
orders[make_tuple(j, sub, size)] += count; orders[std::make_tuple(j, sub, size)] += count;
DEBUG(cycle).print("tailor: %s times %d of size %d ordered\n", ENUM_KEY_STR(job_type, j).c_str(), count, size);
} }
} }
@ -318,7 +335,11 @@ private:
int size = world->raws.creatures.all[race]->adultsize; int size = world->raws.creatures.all[race]->adultsize;
orders[make_tuple(o->job_type, sub, size)] -= o->amount_left; orders[std::make_tuple(o->job_type, sub, size)] -= o->amount_left;
TRACE(cycle).print("tailor: existing order for %d %s of size %d detected\n",
o->amount_left,
ENUM_KEY_STR(job_type, o->job_type).c_str(),
size);
} }
} }
@ -333,14 +354,14 @@ private:
int sub; int sub;
int size; int size;
tie(ty, sub, size) = o.first; std::tie(ty, sub, size) = o.first;
int count = o.second; int count = o.second;
if (count > 0) if (count > 0)
{ {
vector<int16_t> v; std::vector<int16_t> v;
BitArray<df::armor_general_flags>* fl; BitArray<df::armor_general_flags>* fl;
string name_s, name_p; std::string name_s, name_p;
switch (ty) { switch (ty) {
@ -382,7 +403,7 @@ private:
if (!can_make) if (!can_make)
{ {
out->print("tailor: civilization cannot make %s, skipped\n", name_p.c_str()); INFO(cycle).print("tailor: civilization cannot make %s, skipped\n", name_p.c_str());
continue; continue;
} }
@ -416,7 +437,7 @@ private:
world->manager_orders.push_back(order); world->manager_orders.push_back(order);
out->print("tailor: added order #%d for %d %s %s, sized for %s\n", INFO(cycle).print("tailor: added order #%d for %d %s %s, sized for %s\n",
order->id, order->id,
c, c,
bitfield_to_string(order->material_category).c_str(), bitfield_to_string(order->material_category).c_str(),
@ -464,9 +485,9 @@ public:
} }
public: public:
command_result set_materials(color_ostream& out, vector<string>& parameters) command_result set_materials(color_ostream& out, std::vector<std::string>& parameters)
{ {
list<MatType> newmat; std::list<MatType> newmat;
newmat.clear(); newmat.clear();
for (auto m = parameters.begin() + 1; m != parameters.end(); m++) for (auto m = parameters.begin() + 1; m != parameters.end(); m++)
@ -475,7 +496,7 @@ public:
auto mm = std::find_if(all_materials.begin(), all_materials.end(), nameMatch); auto mm = std::find_if(all_materials.begin(), all_materials.end(), nameMatch);
if (mm == all_materials.end()) if (mm == all_materials.end())
{ {
out.print("tailor: material %s not recognized\n", m->c_str()); WARN(config,out).print("tailor: material %s not recognized\n", m->c_str());
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
else { else {
@ -484,7 +505,7 @@ public:
} }
material_order = newmat; material_order = newmat;
out.print("tailor: material list set to %s\n", get_material_list().c_str()); INFO(config,out).print("tailor: material list set to %s\n", get_material_list().c_str());
return CR_OK; return CR_OK;
} }
@ -549,7 +570,7 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out)
return CR_OK; return CR_OK;
} }
static command_result tailor_cmd(color_ostream& out, vector <string>& parameters) { static command_result tailor_cmd(color_ostream& out, std::vector <std::string>& parameters) {
bool desired = enabled; bool desired = enabled;
if (parameters.size() == 1 && (parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1")) if (parameters.size() == 1 && (parameters[0] == "enable" || parameters[0] == "on" || parameters[0] == "1"))
{ {

@ -17,7 +17,7 @@ struct title_start_rename_hook : df::viewscreen_titlest {
inline std::string full_save_dir(const std::string &region_name) inline std::string full_save_dir(const std::string &region_name)
{ {
return std::string("data/save/") + region_name; return std::string("save/") + region_name;
} }
bool do_rename() bool do_rename()

@ -65,24 +65,6 @@ struct coord32_t
} }
}; };
template <class T, typename Fn>
static void for_each_(vector<T> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void for_each_(map<T, V> &v, Fn func)
{
for_each(v.begin(), v.end(), func);
}
template <class T, class V, typename Fn>
static void transform_(const vector<T> &src, vector<V> &dst, Fn func)
{
transform(src.begin(), src.end(), back_inserter(dst), func);
}
typedef int8_t UIColor; typedef int8_t UIColor;
static inline void OutputString(UIColor color, int &x, int &y, const std::string &text, static inline void OutputString(UIColor color, int &x, int &y, const std::string &text,

@ -1 +1 @@
Subproject commit a8c5ac9e94e26f0901917efac213ebedc7e0c276 Subproject commit 5bdfd379b2d13f56bab455f94af56ec0ce3e0cb1