Merge remote-tracking branch 'upstream/develop' into buildings_vectors

develop
Myk Taylor 2020-09-28 23:13:13 -07:00
commit 6cc1428199
27 changed files with 1802 additions and 1540 deletions

6
.gitmodules vendored

@ -18,13 +18,13 @@
url = ../../DFHack/jsoncpp.git
[submodule "depends/xlsxio"]
path = depends/xlsxio
url = ../../brechtsanders/xlsxio.git
url = ../../DFHack/xlsxio.git
shallow = true
[submodule "depends/libzip"]
path = depends/libzip
url = ../../nih-at/libzip.git
url = ../../DFHack/libzip.git
shallow = true
[submodule "depends/libexpat"]
path = depends/libexpat
url = ../../libexpat/libexpat.git
url = ../../DFHack/libexpat.git
shallow = true

@ -2,6 +2,7 @@
## some generic CMake magic
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
cmake_policy(SET CMP0048 NEW)
project(dfhack)
if("${CMAKE_GENERATOR}" STREQUAL Ninja)
@ -406,6 +407,7 @@ endif()
include_directories(depends/tthread)
include_directories(${ZLIB_INCLUDE_DIRS})
include_directories(depends/clsocket/src)
include_directories(depends/xlsxio/include)
add_subdirectory(depends)
find_package(Git REQUIRED)

@ -7,11 +7,11 @@
DFHack Quickfort User Manual
============================
DFHack Quickfort is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, such as MS Excel and [Google Sheets](https://sheets.new). You can also build your plan "for real" in Dwarf Fortress, and then export your map using DFHack's [blueprint](https://docs.dfhack.org/en/stable/docs/Plugins.html#blueprint) plugin. Most layout and building-oriented DF commands are supported through the use of multiple files or spreadsheets, each describing a different phase of DF construction: designation, building, placing stockpiles, and setting configuration.
DFHack Quickfort is a DFHack script that helps you build fortresses from "blueprint" .csv and .xlsx files. Many applications exist to edit these files, such as MS Excel and [Google Sheets](https://sheets.new). You can also build your plan "for real" in Dwarf Fortress, and then export your map using DFHack's [blueprint](https://docs.dfhack.org/en/stable/docs/Plugins.html#blueprint) plugin. Most layout and building-oriented DF commands are supported through the use of multiple files or spreadsheets, each describing a different phase of DF construction: designation, building, placing stockpiles/zones, and setting configuration.
The original idea and 1.0 codebase came from [Valdemar's](https://dwarffortresswiki.org/index.php/User:Valdemar) auto-designation macro. Joel Thornton (joelpt) reimplemented the core logic in Python and extended its functionality with [Quickfort 2.0](https://github.com/joelpt/quickfort). This DFHack-native implementation, called "DFHack Quickfort" or just "quickfort", builds upon Quickfort 2.0's formats and features. DFHack Quickfort is written in Lua and interacts with Dwarf Fortress memory structures directly, allowing for instantaneous blueprint application, error checking and recovery, and many other advanced features.
This document focuses on DFHack Quickfort's capabilities and teaches players how to understand and build blueprint files. Much of the text was originally written by Joel Thornton, reused here with his permission.
This document focuses on DFHack Quickfort's capabilities and teaches players how to understand and build blueprint files. Some of the text was originally written by Joel Thornton, reused here with his permission.
For those just looking to apply blueprints, check out the [quickfort command syntax](https://docs.dfhack.org/en/stable/docs/_auto/base.html#quickfort) in the DFHack Scripts documentation. There are also many ready-to-use blueprints available in the `blueprints/library` subfolder in your DFHack installation. Browse them on your computer or [online](https://github.com/DFHack/dfhack/tree/develop/data/blueprints/library), or run `quickfort list -l` at the `DFHack#` prompt to list them, and then `quickfort run` to apply them to your fort!
@ -25,15 +25,17 @@ Table of Contents
* [Editing Blueprints](#editing-blueprints)
* [Area expansion syntax](#area-expansion-syntax)
* [Automatic area expansion](#automatic-area-expansion)
* [Multilevel blueprints](#multilevel-blueprints)
* [Marker mode](#marker-mode)
* [Dig priorities](#dig-priorities)
* [Stockpiles and zones](#stockpiles-and-zones)
* [Minecart tracks](#minecart-tracks)
* [Multilevel blueprints](#multilevel-blueprints)
* [Modeline optional markers](modeline-optional-markers)
* [Modeline markers](modeline-markers)
* [Packaging a set of blueprints](#packaging-a-set-of-blueprints)
* [Meta blueprints](#meta-blueprints)
* [Troubleshooting](#troubleshooting)
* [Tips and tricks](#tips-and-tricks)
* [Buildingplan integration](#buildingplan-integration)
* [Generating manager orders](#generating-manager-orders)
* [Tips and tricks](#tips-and-tricks)
* [Caveats and limitations](#caveats-and-limitations)
* [Links](#links)
@ -42,46 +44,48 @@ Features
--------
* General
* Manages complete blueprints to handle the four main phases of DF construction
* Manages blueprints to handle all phases of DF construction
* Supports .csv and multi-worksheet .xlsx blueprint files
* Near-instant application, even for very large and complex blueprints
* Blueprints can span multiple z-levels
* Supports multiple blueprints per .csv file/spreadsheet sheet
* "meta" blueprints that automate the application of sequences of blueprints
* Blueprints can span multiple z-levels
* Undo functionality for dig, build, and place blueprints
* Undo functionality for dig, build, place, and zone blueprints
* Automatic cropping of blueprints so you don't get errors if the blueprint extends off the map
* Can generate manager orders for everything required to apply build blueprints
* Can generate manager orders for everything required by a build blueprint
* Library of ready-to-use blueprints included
* Verbose output mode for debugging
* Dig mode
* Supports all types of designations, including dumping/forbidding items and setting traffic areas
* Supports all types of designations, including dumping/forbidding items and setting traffic settings
* Supports setting dig priorities
* Supports applying dig blueprints in marker mode
* Handles carving arbitrarily complex minecart tracks, including tracks that cross other tracks
* Build and place modes
* Supports stockpiles of all shapes, not just rectangular blocks
* Configurable maximums for bins, barrels and wheelbarrows assigned to created stockpiles
* Build mode
* DFHack buildingplan integration
* Designate complete constructions at once, without having to wait for each tile to become supported before you can build it
* Automatic expansion of building footprints to their minimum dimensions, so only the center tile of a multi-tile building needs to be recorded in the blueprint
* Designates complete constructions at once, without having to wait for each tile to become supported before you can build it
* Automatic splitting of stockpiles and buildings that exceed maximum dimension limits
* Tile occupancy and validity checking so, for example, buildings that cannot be placed on a certain tile will simply be skipped instead of the blueprint failing to apply. Blueprints that are only partially applied for any reason (for example, you need to dig out some more tiles) can be safely reapplied to build the remaining buildings.
* Relaxed rules for farm plot and road placement, allowing tiles that are separated by invalid tiles (e.g. stone tiles for farm plots) to be part of the same structure
* Relaxed rules for farm plot and road placement: you can still place the building even if an invalid tile (e.g. stone tiles for farm plots) splits the designated area into two parts
* Intelligent boundary detection for adjacent buildings of the same type (e.g. a 6x6 block of `wj` cells will be correctly split into 4 jeweler's workshops)
* Place and zone modes
* Define stockpiles and zones in any continguous shape, not just rectangular blocks
* Configurable maximums for bins, barrels and wheelbarrows assigned to created stockpiles
* Automatic splitting of stockpiles and zones that exceed maximum dimension limits
* Query mode
* Support sending arbitrary keystroke sequences to the UI -- configure *anything*
* Send arbitrary keystroke sequences to the UI -- *anything* you can do through the UI is supported
* Supports aliases to automate frequent keystroke combos
* Includes a library of pre-made and tested aliases to automate most common tasks, such as configuring stockpiles for important item types or creating named hauling routes for quantum stockpiles.
* Supports including aliases in other aliases, and repeating key sequences a specified number of times
* Includes a library of pre-made and tested aliases to automate most common tasks, such as configuring stockpiles for important item types or creating hauling routes for quantum stockpiles.
* Skips sending key sequences when the cursor is over a tile that does not have a stockpile or building
* Instant halting of query blueprint application when keystroke errors are detected, such as when a key sequence leaves us stuck in a submenu
* Skips sending key sequences when the cursor is over a tile that does not have a stockpile or building, so missing buildings won't desynchronize your blueprint
* Instant halting of query blueprint application when keystroke errors are detected, such as when a key sequence leaves us stuck in a submenu, to make blueprint misconfigurations easier to debug
Editing Blueprints
------------------
The format of Quickfort-compatible blueprint files is straightforward.
It is recommended to use a spreadsheet editor such as Excel, [Google Sheets](https://sheets.new), or [LibreOffice](https://www.libreoffice.org) to edit these files, but any text editor will do.
We recommend using a spreadsheet editor such as Excel, [Google Sheets](https://sheets.new), or [LibreOffice](https://www.libreoffice.org) to edit blueprint files, but any text editor will do.
The first line (or upper-left cell) of the spreadsheet should look like this:
The format of Quickfort-compatible blueprint files is straightforward. The first line (or upper-left cell) of the spreadsheet should look like this:
#dig This is a decription.
@ -106,7 +110,7 @@ Below this line begin entering the keys you want sent in each cell. For example,
d d d d #
# # # # #
Note the # symbols at the right end of each row and below the last row. These are completely optional, but can be helpful when visualizing the layout.
Note the # symbols at the right end of each row and below the last row. These are completely optional, but can be helpful to make the row and column positions clear.
Once the dwarves have that dug out, let's build a walled-in bedroom within our dug-out area:
@ -117,7 +121,7 @@ Once the dwarves have that dug out, let's build a walled-in bedroom within our d
Cw Cw Cw #
# # # # #
Note my generosity - in addition to the bed (b) I've built a chest (h) here for the dwarf as well. Note that you must use the full series of keys needed to build something in each cell, e.g. 'Cw' enters DF's constructions submenu (C) and selects walls (w).
Note my generosity - in addition to the bed (b) I've built a chest (h) here for the dwarf as well. You must use the full series of keys needed to build something in each cell, e.g. 'Cw' enters DF's constructions submenu (C) and selects walls (w).
I'd also like to place a booze stockpile in the 2 unoccupied tiles in the room.
@ -176,7 +180,7 @@ In Quickfort, the following blueprints are equivalent:
The second example uses Quickfort's "area expansion syntax", which takes the form:
cmds(WxH)
keys(WxH)
In Quickfort the above two examples of specifying a contiguous 3x3 area produce identical output: a single 3x3 designation will be performed, rather than nine 1x1 designations as the first example might suggest.
@ -259,25 +263,93 @@ This style can be convenient for laying out multiple buildings of the same type.
Quickfort will intelligently break large areas of the same designation into appropriately-sized chunks.
Multilevel blueprints
---------------------
Multilevel blueprints are accommodated by separating Z-levels of the blueprint with `#>` (go down one z-level) or `#<` (go up one z-level) at the end of each floor.
#dig Stairs leading down to a small room below
j ` ` #
` ` ` #
` ` ` #
#> # # #
u d d #
d d d #
d d d #
# # # #
The marker must appear in the first column of the row to be recognized, just like a modeline.
Dig priorities
--------------
DF designation priorities are supported for `#dig` blueprints. The full syntax is `[letter][number][expansion]`, where if the `letter` is not specified, `d` is assumed, and if `number` is not specified, `4` is assumed (the default priority). So each of these blueprints is equivalent:
#dig dig the interior of the room at high priority
d d d d d #
d d1 d1 d1 d #
d d1 d1 d1 d #
d d1 d1 d1 d #
d d d d d #
# # # # # #
#dig dig the interior of the room at high priority
d d d d d #
d d1(3x3) d #
d ` ` ` d #
d ` ` ` d #
d d d d d #
# # # # # #
#dig dig the interior of the room at high priority
4 4 4 4 4 #
4 1 1 1 4 #
4 1 1 1 4 #
4 1 1 1 4 #
4 4 4 4 4 #
# # # # # #
Marker mode
-----------
Marker mode is useful for when you want to plan out your digging, but you don't want to dig everything just yet. In `#dig` mode, you can add a `m` before any other designation letter to indicate that the tile should be designated in marker mode. For example, to dig out the perimeter of a room, but leave the center of the room marked for digging later:
#dig
d d d d d #
d md md md d #
d md md md d #
d md md md d #
d d d d d #
# # # # # #
Then you can use "Toggle Standard/Marking" (`d-M`) to convert the center tiles to regular designations at your leisure.
To apply an entire dig blueprint in marker mode, regardless of what the blueprint itself says, you can set the global quickfort setting `force_marker_mode` to `true` before you apply the blueprint.
Note that the in-game UI setting "Standard/Marker Only" (`d-m`) does not have any effect on quickfort.
Stockpiles and zones
--------------------
It is very common to have stockpiles that accept multiple categories of items or zones that permit more than one activity. Although it is perfectly valid to declare a single-purpose stockpile or zone and then modify it with a `#query` blueprint, quickfort also supports directly declaring all the types on the `#place` and `#zone`blueprints. For example, to declare a 10x10 area that is a pasture, a fruit picking area, and a meeting area all at once, you could write:
It is very common to have stockpiles that accept multiple categories of items or zones that permit more than one activity. Although it is perfectly valid to declare a single-purpose stockpile or zone and then modify it with a `#query` blueprint, quickfort also supports directly declaring all the types on the `#place` and `#zone` blueprints. For example, to declare a 10x10 area that is a pasture, a fruit picking area, and a meeting area all at once, you could write:
#zone main pasture and picnic area
nmg(10x10) #
nmg(10x10)
And similarly, to declare a stockpile that accepts both corpses and refuse, you could write:
#place refuse heap
yr(20x10) #
yr(20x10)
The order of the individual letters doesn't matter.
To toggle the `active` flag for zones, add an `a` character to the string. For example, to create a *disabled* pit zone (that you later intend to turn into a pond and fill carefully to 3-depth water):
To toggle the `active` flag for zones, add an `a` character to the string. For example, to create a *disabled* pit zone (that you later intend to turn into a pond and carefully fill to 3-depth water):
#zone disabled future pond zone
pa(1x3) #
pa(1x3)
Note that while this notation covers most use cases, tweaking low-level zone parameters, like hospital supply levels or converting between pits and ponds, must still be done manually or with a `#query` blueprint.
@ -287,9 +359,9 @@ Minecart tracks
There are two ways to produce minecart tracks, and they are handled very differently by the game. You can carve them into hard natural floors or you can construct them out of building materials. Constructed tracks are conceptually simpler, so we'll start with them.
### Constructed Tracks ###
### Constructed tracks ###
Quickfort supports the designation of track stops and rollers through the normal mechanisms: a #build blueprint with `CS` and some number of 'd' and 'a' characters (for selecting dump direction and friction) in a cell designates a track stop and a #build blueprint with `Mr` and some number of 's' and 'q' characters (for direction and speed) designates a roller. This can get confusing very quickly and is very difficult to read in a blueprint. Constructed track segments don't even have keys associated with them at all!
Quickfort supports the designation of track stops and rollers through the normal mechanisms: a `#build` blueprint with `CS` and some number of `d` and `a` characters (for selecting dump direction and friction) in a cell designates a track stop and a `#build` blueprint with `Mr` and some number of `s` and `q` characters (for direction and speed) designates a roller. This can get confusing very quickly and is very difficult to read in a blueprint. Constructed track segments don't even have keys associated with them at all!
To solve this problem, Quickfort provides the following keywords for use in build blueprints:
@ -355,21 +427,23 @@ As an example, you can create an E-W track with stops at each end that dump to t
#build Example track
trackstopW trackEW trackEW trackEW trackstopE
Note that the **only** way to build track and track/ramp segments is with the keywords. The UI method of using "+" and "-" keys to select the track type from a list does not work since DFHack Quickfort doesn't actually send keys to the UI in order to build buildings. The text in your spreadsheet cells is mapped directly into DFHack API calls. Only query-mode blueprints still send actual keycodes to the UI.
Note that the **only** way to build track and track/ramp segments is with the keywords. The UI method of using "+" and "-" keys to select the track type from a list does not work since DFHack Quickfort doesn't actually send keys to the UI to build buildings. The text in your spreadsheet cells is mapped directly into DFHack API calls. Only `#query` blueprints still send actual keycodes to the UI.
### Carved Tracks ###
### Carved tracks ###
In the game, you carve a minecart track by specifying a beginning and ending tile and the game "adds" the designation to the tiles. You cannot designate single tiles. For example to carve two track segments that cross each other, you might use the cursor to designate a line of three vertical tiles like this:
` start here ` #
` ` ` #
` end here ` #
# # # #
Then to carve the cross, you'd do a horizonal segment:
` ` ` #
start here ` end here #
` ` ` #
# # # #
This will result in a carved track that would be equivalent to a constructed track of the form:
@ -379,7 +453,7 @@ This will result in a carved track that would be equivalent to a constructed tra
` trackN ` #
# # # #
To carve this same track with a dig blueprint, you'd use area expansion syntax with a height or width of 1 to indicate the segments to designate:
To carve this same track with a `#dig` blueprint, you'd use area expansion syntax with a height or width of 1 to indicate the segments to designate:
#dig
` T(1x3) ` #
@ -404,32 +478,14 @@ Which would result in a carved track simliar to a constructed track of the form:
# # # #
Multilevel blueprints
---------------------
Multilevel blueprints are accommodated by separating Z-levels of the blueprint with `#>` (go down one z-level) or `#<` (go up one z-level) at the end of each floor.
#dig Stairs leading down to a small room below
j ` ` #
` ` ` #
` ` ` #
#> # # #
u d d #
d d d #
d d d #
# # # #
The marker must appear in the first column of the row to be recognized, just like a modeline.
Modeline optional markers
-------------------------
Modeline markers
----------------
The modeline has some additional optional components that we haven't talked about yet. You can:
* give a blueprint a label by adding a `label()` marker
* set a cursor offset and/or start hint by adding a `start()` marker
* hide a blueprint with a `hidden()` marker
* hide a blueprint from being listed with a `hidden()` marker
* register a message to be displayed after the blueprint is successfully applied
The full modeline syntax, when everything is specified, is:
@ -440,7 +496,7 @@ Note that all elements are optional except for the initial `#mode`. Here are a f
#dig start(3; 3; Center tile of a 5-tile square) Regular blueprint comment
#build label(noblebedroom) start(10;15)
#query label(configstockpiles) No explicit start() means cursor is at upper left corner
#query label(configstockpiles) No explicit start() means cursor is at upper left corner
#meta label(digwholefort) start(center of stairs on surface)
#dig label(digdining) hidden() managed by the digwholefort meta blueprint
#zone label(pastures) message(remember to assign animals to the new pastures)
@ -461,7 +517,7 @@ Start positions specify a cursor offset for a particular blueprint, simplifying
will build the workshop *centered* on the cursor, not down and to the right of the cursor.
The two numbers specify the column and row (or X and Y offset) where the cursor is expected to be when you apply the blueprint. Position 1;1 is the top left cell. The optional comment will show up in the `quickfort list` output and should contain information about where to position the cursor. If the start position is 1;1, you can omit the numbers and just add a comment describing where to put the cursor. This is useful for meta blueprints that refer to other blueprints that have fully-specified start() markers. For example, a meta blueprint that refers to the `masonw` blueprint above could look like this:
The two numbers specify the column and row (or X and Y offset) where the cursor is expected to be when you apply the blueprint. Position 1;1 is the top left cell. The optional comment will show up in the `quickfort list` output and should contain information about where to position the cursor. If the start position is 1;1, you can omit the numbers and just add a comment describing where to put the cursor. This is also useful for meta blueprints that don't actually care where the cursor is, but that refer to other blueprints that have fully-specified `start()` markers. For example, a meta blueprint that refers to the `masonw` blueprint above could look like this:
#meta start(center of workshop) a mason workshop
/masonw
@ -472,7 +528,7 @@ A blueprint with a `hidden()` marker won't appear in `quickfort list` output unl
### Messages ###
A blueprint with a `message()` marker will display a message after the blueprint is applied with `quickfort run`. This is useful for reminding players to take manual steps that cannot be automated, like assigning animals to a pasture or assigning minecarts to a route, or listing the next step in a series of blueprints. For long or multi-part messages, it is fine to embed newlines:
A blueprint with a `message()` marker will display a message after the blueprint is applied with `quickfort run`. This is useful for reminding players to take manual steps that cannot be automated, like assigning animals to a pasture or assigning minecarts to a route, or listing the next step in a series of blueprints. For long or multi-part messages, you can embed newlines:
"#meta label(surface1) message(This would be a good time to start digging the industry level.
Once the area is clear, continue with /surface2.) clear the embark site and set up pastures"
@ -488,31 +544,31 @@ For both .csv files and .xlsx spreadsheets you can also add as many blueprints a
For example, you can store multiple blueprints together like this:
#dig label(digbed)
#dig label(bed1)
d d d d #
d d d d #
d d d d #
d d d d #
# # # # #
#build label(buildbed)
#build label(bed2)
b f h #
#
#
n #
# # # # #
#place label(placebed)
#place label(bed3)
#
f(2x2) #
#
#
# # # # #
#query label(boozebed)
#query label(bed4)
#
booze #
#
#
# # # # #
#query label(roombed)
#query label(bed5)
r{+ 3}& #
#
#
@ -526,6 +582,7 @@ Of course, you could still choose to keep your blueprints in single-sheet .csv f
bedroom.2.build.csv
bedroom.3.place.csv
bedroom.4.query.csv
bedroom.5.query2.csv
But the naming and organization is completely up to you.
@ -543,29 +600,29 @@ Meta blueprints are blueprints that script a series of other blueprints. Many bl
- Wait for buildings to get built
- Apply a different query blueprint to configure rooms
Those three "apply"s in the middle might as well get done in one command instead of three. A meta blueprint can encode that sequence. A meta blueprint refers to other blueprints by their label (see the [Modeline optional markers](modeline-optional-markers) section above) in the same format used by the `DFHack#` quickfort command: "<sheet_name>/<label>", or just "/<label>" for blueprints in .csv files or blueprints in the same spreadsheet sheet as the #meta blueprint that references them.
Those three "apply"s in the middle might as well get done in one command instead of three. A meta blueprint can encode that sequence. A meta blueprint refers to other blueprints by their label (see the [Modeline markers](modeline-markers) section above) in the same format used by the `DFHack#` quickfort command: "<sheet_name>/<label>", or just "/<label>" for blueprints in .csv files or blueprints in the same spreadsheet sheet as the #meta blueprint that references them.
A few examples might make this clearer. Say you have a .csv file with the following blueprints defined:
A few examples might make this clearer. Say you have a .csv file with the "bed" blueprints in the previous section:
#dig label(digbedroom)
#dig label(bed1)
...
#build label(buildbedroom)
#build label(bed2)
...
#place label(placebedroom)
#place label(bed3)
...
#query label(querystockpilesbedroom)
#query label(bed4)
...
#query label(makeroombedroom)
#query label(bed5)
...
Note how I've given them all labels so we can address them safely. If I hadn't given them labels, they would receive default labels of "1", "2", "3", etc, but those labels would change if I ever add more blueprints at the top. This is not a problem if we're just running the blueprints from the `quickfort list` command, but meta blueprints need a label that isn't going to change over time.
Note how I've given them all labels so we can address them safely. If I hadn't given them labels, they would receive default labels of "1", "2", "3", etc, but those labels would change if I ever add more blueprints at the top. This is not a problem if we're just running the blueprints individually from the `quickfort list` command, but meta blueprints need a label name that isn't going to change over time.
So let's add a meta blueprint to this file that will combine the middle three blueprints into one:
#meta plan bedroom: combines build, place, and stockpile config blueprints
/buildbedroom
/placebedroom
/querystockpilesbedroom
"#meta plan bedroom: combines build, place, and stockpile config blueprints"
/bed2
/bed3
/bed4
Now your sequence is shortened to:
@ -618,17 +675,29 @@ We can add a sheet named "dig_all" with the following contents (we're expecting
Note that for blueprints without an explicit label, we still need to address them by their auto-generated numerical label.
You can then hide the blueprints that you now manage with the `meta`-mode blueprint from `quickfort list` by adding a `hidden()` marker to their modelines. That way the output of `quickfort list` won't be cluttered by blueprints that you don't need to run directly. If you ever *do* need to access the managed blueprints individually, you can still see them with `quickfort list --hidden`.
You can then hide the blueprints that you now manage with the `#meta`-mode blueprint from `quickfort list` by adding a `hidden()` marker to their modelines. That way the output of `quickfort list` won't be cluttered by blueprints that you don't need to run directly. If you ever *do* need to access the managed blueprints individually, you can still see them with `quickfort list --hidden`.
Buildingplan integration
------------------------
Buildingplan is a DFHack plugin that keeps jobs in a suspended state until the materials required for the job are available. This prevents a building designation from being canceled when a dwarf picks up the job but can't find the materials.
For all types that buildingplan supports, quickfort using buildingplan to manage construction. Buildings are still constructed immediately if you have the materials, but you now have the freedom to apply build blueprints before you manufacture all required materials, and the jobs will be fulfilled as the materials become available.
If a `#build` blueprint only refers to supported types, the buildingplan integration pairs well with the [workflow](https://docs.dfhack.org/en/stable/docs/Plugins.html#workflow) plugin, which can build items a few at a time continuously as long as they are needed. For building types that are not yet supported by buildingplan, a good pattern to follow is to first run `quickfort orders` on the `#build` blueprint to manufacture all the required items, then apply the blueprint itself.
See [buildingplan documentation](https://docs.dfhack.org/en/stable/docs/Plugins.html#buildingplan) for a list of supported types.
Generating manager orders
-------------------------
Quickfort can generate manager orders to make sure you have the proper items in stock to apply a `build`-mode blueprint.
Quickfort can generate manager orders to make sure you have the proper items in stock to apply a `#build` blueprint.
Many items can be manufactured from different source materials. Orders will always choose rock when it can, then wood, then cloth, then iron. You can always remove orders that don't make sense for your fort and manually enqueue a similar order more to your liking. For example, if you want silk ropes instead of cloth ropes, make a new manager order for silk ropes and then remove the generated cloth rope order.
Many items can be manufactured from different source materials. Orders will always choose rock when it can, then wood, then cloth, then iron. You can always remove orders that don't make sense for your fort and manually enqueue a similar order more to your liking. For example, if you want silk ropes instead of cloth ropes, make a new manager order for an appropriate quantity of silk ropes, and then remove the generated cloth rope order.
Anything that requires generic building materials (workshops, constructions, etc.) will result in an order for a rock block. One "Make rock blocks" job produces four blocks per input boulder, so the number of jobs ordered will be the number of blocks you need divided by four (rounded up). You might end up with a few extra blocks, but not too many.
Anything that requires generic building materials (workshops, constructions, etc.) will result in an order for a rock block. One "Make rock blocks" job produces four blocks per boulder, so the number of jobs ordered will be the number of blocks you need divided by four (rounded up). You might end up with a few extra blocks, but not too many.
If you want your constructions to be in a consistent color, be sure to choose a rock type for all of your 'Make rock blocks' orders by selecting the order and hitting `d`. You might want to set the rock type for other non-block orders to something different if you fear running out of the type of rock that you want to use for blocks.
@ -636,7 +705,7 @@ There are a few building types that will generate extra manager orders for relat
- Track stops will generate an order for a minecart
- Traction benches will generate orders for a table, mechanism, and rope
- Levers will generate orders for extra two mechanisms for connecting the lever to a target
- Levers will generate an order for an extra two mechanisms for connecting the lever to a target
- Cage traps will generate an order for a cage
@ -647,7 +716,7 @@ Tips and tricks
* After digging out an area, you may wish to smooth and/or engrave the area before starting the build phase, as dwarves may be unable to access walls or floors that are behind/under built objects.
* If you are designating more than one level for digging at a time, you can make your miners more efficient by using marker mode (`dM`) on all levels but one. This prevents your miners from digging out a few tiles on one level, then running down/up the stairs to do a few tiles on an adjacent level. With only one level "live" and all other levels in marker mode, your miners can concentrate on one level at a time. You just have to rememer to "unmark" a new level when your miners are done with their current one.
* If you are designating more than one level for digging at a time, you can make your miners more efficient by using marker mode on all levels but one. This prevents your miners from digging out a few tiles on one level, then running down/up the stairs to do a few tiles on an adjacent level. With only one level "live" and all other levels in marker mode, your miners can concentrate on one level at a time. You just have to remember to "unmark" a new level when your miners are done with their current one.
* As of DF 0.34.x, it is no longer possible to build doors (d) at the same time that you build adjacent walls (Cw). Doors must now be built *after* walls are constructed for them to be next to. This does not affect the more common case where walls exist as a side-effect of having dug-out a room in a #dig blueprint.
@ -655,15 +724,15 @@ Tips and tricks
Caveats and limitations
-----------------------
* Buildings will be designated regardless of whether you have the required materials, but if materials are not available when the construction job is picked up by a dwarf, the buildings will be canceled and the designations will disappear. Until the buildingplan plugin can be extended to support all building types, you should use `quickfort orders` to pre-manufacture all the materials you need for a `#build`-mode blueprint before you apply it.
* Buildings will be designated regardless of whether you have the required materials, but if materials are not available when the construction job is picked up by a dwarf, the buildings will be canceled and the designations will disappear. Until the buildingplan plugin can be extended to support all building types, you should use `quickfort orders` to pre-manufacture all the materials you need for a `#build` blueprint before you apply it.
* If you use the `jugs` alias in your `#query`-mode blueprints, be aware that there is no way to differentiate jugs from other types of tools in the game. Therefore, `jugs` stockpiles will also take nest boxes and other tools. The only workaround is not to have other tools lying around in your fort.
* Likewise for bags. The game does not differentiate between empty and full bags, so you'll get gabs of gypsum power and sand in your bags stockpile unless you avoid collecting sand and are careful to assign all your gypsum to your hospital.
* Likewise for bags. The game does not differentiate between empty and full bags, so you'll get bags of gypsum power and sand in your bags stockpile unless you avoid collecting sand and are careful to assign all your gypsum to your hospital.
* Weapon traps and upright spear/spikes can currently only be built with a single weapon.
* Weapon traps and upright spear/spike traps can currently only be built with a single weapon.
* Pressure plates can be built, but they cannot be configured yet.
* Pressure plates can be built, but they cannot be usefully configured yet.
* Building instruments, bookcases, display furniture, and offering places are not yet supported by DFHack.

@ -1,27 +1,45 @@
"#meta label(help) message(Welcome to the Dreamfort Walkthrough!
#notes label(help) run me for the dreamfort walkthrough
Welcome to the Dreamfort Walkthrough!
It can be difficult applying a set of blueprints that you did not write yourself. This walkthrough will guide you through the high-level steps of building Dreamfort.
The final fort will have a walled-in area on the surface for livestock, trading, and military. One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall for use until you get the services level set up.
Beyond those two, the other layers can be built in any order, at any z-level, according to your preference and the layout peculiarities of your embark site:
- The industry level has a compact, but complete set of workshops and stockpiles
- The services level has dining, hospital, and justice services, including a well system. This level is 4 z-levels deep.
- The guildhall level has many rooms for buildling libraries, temples, and guildhalls
- The suites level has fancy rooms for your nobles
- The apartments level(s) has small but well-furnished bedrooms for your other dwarves
Dreamfort has a central staircase-based design. Choose a tile for your staircase on the surface in a nice, flat area. For all blueprints, the cursor will start on this tile on the z-level where you want to apply the blueprint.
Blueprints that require manual steps (like 'assign animals to pasture') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items.
Directly after embark, apply /surface1 on a flat area on the surface and /industry1 somewhere below, underground (but not immediately below the surface -- that will be for the farming level). Work your way through the steps for those levels: apply /surface2 when /surface1 is done, /industry2 when /industry1 is done, etc. Once you channel out parts of the surface with /surface3, you can start the farming sequence with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels.
This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, including full lists of their features, it will be easier to look at the .xlsx files than this giant .csv. You can view them online at:
https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP
""
"The final fort will have a walled-in area on the surface for livestock, trading, and military. Be sure to bring some blocks with you for the initial set of workshops! One z-level down is the farming level, with related workshops and vents up to the surface for miasma control. The farming level also has a miniature dining hall and dormitory for use until you get the services and apartment levels set up."
""
"Beyond those two, the other layers can be built in any order, at any z-level, according to your preference and the layout peculiarities of your embark site:"
"- The industry level has a compact, but complete set of workshops and stockpiles (minus what is already provided on the farming level)"
"- The services level has dining, hospital, and justice services, including a well system. This level is 4 z-levels deep."
"- The guildhall level has many rooms for building libraries, temples, and guildhalls"
- The suites level has fancy rooms for your nobles
- The apartments level(s) has small but well-furnished bedrooms for your other dwarves
""
"Dreamfort has a central staircase-based design. Choose a tile for your staircase on the surface in a nice, flat area. For all blueprints, the cursor will start on this tile on the z-level where you want to apply the blueprint."
""
"Blueprints that require manual steps (like 'assign animals to pasture') will leave a message telling you so after you apply them. Blueprints will also leave messages with hints when you might want to run ""quickfort orders"" on specific blueprints to start manufacturing needed items."
""
"Directly after embark, apply /surface1 on a flat area on the surface and /industry1 somewhere underground (but not immediately below the surface -- that will be for the farming level). Work your way through the steps for those levels: apply /surface2 when /surface1 is done, /industry2 when /industry1 is done, etc. Once you channel out parts of the surface with /surface3, you can start the farming sequence with /farming1. You can start the services, guildhall, suites, and apartments sequences as your fort needs those levels."
""
"This .csv file is generated from source .xlsx files. If you want to look at how these blueprints are put together, including full lists of their features, it will be easier to look at the .xlsx files than this giant .csv. You can view them online at: https://drive.google.com/drive/folders/1iS90EEVqUkxTeZiiukVj1pLloZqabKuP"
You are welcome to copy those files and make your own modifications!
) run me for help with dreamfort"
"#dreamfort.csv is generated with the following command:
for fname in dreamfort*.xlsx; do xlsx2csv -a -E Notes -p '' $fname; done | sed 's/,*$//'"
for fname in dreamfort*.xlsx; do xlsx2csv -a -p '' $fname; done | sed 's/,*$//'"
#notes label(surface_readme)
"Sets up a large, protected entrance to your fort in a flat area on the surface."
""
Features:
A starting set of workshops and stockpiles (which you can later remove once you establish your permanent workshops and storage)
Walls and lever-controlled gates for security
Trap-filled hallways for invaders
Livestock grazing area with nestbox zones and beehives
A grid of 1x1 farm plots (meant to be managed with DFHack autofarm)
An extra room near the entrance for a barracks (once you get your military going)
Miasma vents for the farming level that is intended to be built in the layer directly beneath the surface
""
Manual steps you have to take:
Assign grazing livestock to the large pasture and dogs to the pasture over the stairwell (DFHack's autonestbox will manage the nestbox zones)
Connect levers to the gates
""
"All blueprints are managed by the meta blueprints in the ""meta"" sheet. Each stage is meant to be applied after the previous stage is completely finished. For example, you can't build furniture until all the flooring has been constructed."
""
Be sure to choose an embark site that has an area flat enough to use these blueprints!
"#meta label(surface1) start(staircase center)
message(This would be a good time to start digging the industry level.
Once the area is clear, continue with /surface2.) clear an area and set up pastures"
@ -36,7 +54,7 @@ query_start/surface_query_start
clear/surface_clear
pick/surface_pick
""
"#meta label(surface3) start(staircase center) message(Once the channels are dug out and you have around 650 blocks, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface level"
"#meta label(surface3) start(staircase center) message(Once the channels are dug out and you have around 650 blocks, continue with /surface4. You can also start digging out the sub-surface farming level once the channels are done.) channel to prevent miasma in the sub-surface farming level"
dig/surface_channel
""
"#meta label(surface4) start(staircase center) message(Once floors and walls are built and you have completed the manager orders for /surface5, continue with /surface5.) cover up the holes with flooring and build walls"
@ -802,6 +820,31 @@ p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,p,
#notes label(farming_readme)
"Sets up farming, food storage, and related industries. Also provides post-embark necessities that can later be disassembed."
""
Features:
Pairs with the surface blueprints to provide miasma vents
Farm plots (intended to be managed by DFHack autofarm)
Plentiful food storage
Refuse stockpile and quantum dump for useless body parts
Small dormitory and dining room for post-embark needs
Small office for your manager
""
Workshops:
Kitchen
Brewery
Butcher
Fishery
Tannery
Farmer's Workshop
Quern
Screw Press
""
Manual steps you have to take:
Assign the office to your manager
Assign a minecart to your quantum garbage stockpile hauling route
"if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level"
"#dig label(farming1) start(23; 25; staircase center) message(This would be a good time to queue up manager orders for /farming2. Once the area is dug out, continue with /farming2.)"
@ -983,7 +1026,9 @@ query_stockpiles/farming_query_stockpiles
,,,,,,,,,,,,,,,,,,,,,,ry(1x1),,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
#query label(farming_query_stockpiles) start(23; 25) hidden() message(remember to assign a minecart to the garbage quantum stockpile) use the meta blueprints for normal application
"#query label(farming_query_stockpiles) start(23; 25) hidden() message(remember to:
- assign a minecart to the garbage quantum stockpile
- if the industry level is already built, configure the jugs, pots, and bags stockpiles to take from the ""Goods"" quantum stockpile on the industry level) use the meta blueprints for normal application"
,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,,`,,`,,forbidplants,`,`,`,`,`,`,`,`,`,`,`,`,`
@ -1017,7 +1062,7 @@ query_stockpiles/farming_query_stockpiles
,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,,,,`,,,,,`
,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,`,`,,give2up,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,`,`,`,,booze,`,`,`,`,`,`,,`,`,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,`,`,`,,booze,`,`,`,`,`,`,,`,`,`,,forbidcraftrefuse,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,`,`,`,,`,`,`,`,`,`,`,,`,,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,,,,,,,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,,,,,,,,,,,,,,,,bodyparts,linksonly,`,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
@ -1071,6 +1116,42 @@ query_stockpiles/farming_query_stockpiles
,,,,,,,,,,,,,,,,,,,,,,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`
#notes label(industry_readme)
Sets up workshops for all non-farming industries
""
Features:
Quantum stockpiles
Space-efficient layout for all workshops
with separate stockpiles for:
A reserve of uncut gems for strange moods that the jeweler's workshop cannot take from
Steel bars and coal so you can see at a glance if you're low on either
Liquids that cannot be quantum stockpiled (e.g. lye)
Meltable weapons and armor
""
Workshops:
3x Mason
4x Craftsdwarf
1x Jeweler
1x Mechanic
4x Smelter
1x Forge
1x Glassmaker
1x Kiln
4x Wood furnace
1x Ashery
1x Soap maker
1x Carpenter
1x Siege workshop
1x Bowyer
1x Dyer
1x Loom
1x Clothier
""
""
Manual steps you have to take:
Assign minecarts to your quantum stockpile hauling routes
"Give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level"
"If desired, set the stockpiles in the bottom right to auto-melt. This results in melting all weapons and armor that are not masterwork steel. This is great for your military, but it takes a *lot* of fuel. If you enable this, be sure you're in a heavily forested area, enable auto-chop, and set up manager orders to keep your coal stocks high."
"#dig label(industry1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /industry2. Once the area is dug out, continue with /industry2.)"
@ -1183,7 +1264,10 @@ query/industry_query
,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`
#query label(industry_query) start(18; 18) hidden() message(remember to assign minecarts to to your quantum stockpile hauling routes) use the meta blueprints for normal application
"#query label(industry_query) start(18; 18) hidden() message(remember to:
- assign minecarts to to your quantum stockpile hauling routes
- if the farming level is already built, give from the ""Goods"" quantum stockpile to the jugs, pots, and bags stockpiles on the farming level
- if you want to automatically melt goblinite and other low-quality weapons and armor, mark the south-east stockpiles for auto-melt) use the meta blueprints for normal application"
,,,,,,,,,,,roughgems,nocontainers,`,`,`,`,t{Down 6}&,`,`,`,`,`,`
@ -1219,7 +1303,24 @@ query/industry_query
,,,,,,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`
"#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Reapply this dig blueprint after digging to clear cistern ramps. Once the area is dug out, continue with /services2.)"
#notes label(services_readme)
"Sets up public services (dining, hospital, etc.)"
""
Features:
Spacious dining room (can be declared a tavern)
Food and drink stockpiles
Well system (bring your own water)
Public baths with soap stockpiles
Three well-appointed jail cells
Hospital with beds and storage
Garbage dump
""
Manual steps you have to take:
"If you want to declare the dining room as a tavern, the bedrooms at the top can be assigned to the tavern as rented rooms."
"Configure the soap stockpiles to take from the industry level ""Metalworker"" quantum stockpile (which holds all the bars)"
"Convert the bath pit zones to pond zones when you are ready to fill them with 3-depth water. This is the only really fiddly bit, since you have to carefully disable the pond zone again when the final bucket to bring the water to an even 3-depth is on the way."
"Fill the cisterns with water, either with a bucket brigade or by plumbing flowing water. Fill so that there are two z-levels of 7-depth water. If you want to fill with buckets, designate a pond zone on the level below the main floor. If you feel adventurous and are experience with water pressure, you can instead route (depressurized!) water to the second-to-bottom level (the one above the up staircases)."
"#dig label(services1) start(23; 22; staircase center) message(This would be a good time to queue manager orders for /services2. Once the area is dug out, continue with /services2.)"
,,d,d,d,,,d,d,d,,,d,d,d
@ -1294,13 +1395,13 @@ query/industry_query
,,,i,h,i,,i,h,i,,i,h,i,,,,,i,h,i,i,h,i,i,h,i
,,,i,h5,i,,i,h5,i,,i,h5,i,,,,,i,h5,i,i,h5,i,i,h5,i
,,,,,,,,,,,,,,,,,,,,d,d,d,d,d
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d,,,,,,,,,i,h,i
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d,,,,,,,,,i,h5,i
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d
,,,,,,,,,,,,,,,,,,,,d,d,d,d,d
#>
@ -1319,13 +1420,13 @@ query/industry_query
,,,u,z,u,,u,z,u,,u,z,u,,,,,u,z,u,u,z,u,u,z,u
,,,u,d,u,,u,d,u,,u,d,u,,,,,u,d,u,u,d,u,u,d,u
,,,,,,,,,,,,,,,,,,,,d,d,d,d,d
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d,,,,,,,,,u,z,u
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d,,,,,,,,,u,d,u
,,,,,,,,,,,,,,,,,,,,d,i,i,i,d
,,,,,,,,,,,,,,,,,,,,d,d,d,d,d
@ -1464,7 +1565,7 @@ query_stockpiles/services_query_stockpiles
,,`,`,`,`,`,`,,`,`,`,`,`,`
#query label(services_query_stockpiles) start(23; 22) hidden() use the meta blueprints for normal application
"#query label(services_query_stockpiles) start(23; 22) hidden() message(remember to configure the soap stockpiles to take from the ""Metalworker"" quantum stockpile on the industry level (where all bars are stored)) use the meta blueprints for normal application"
,,`,`,`,,,`,`,`,,,`,`,`
@ -1532,7 +1633,12 @@ query_stockpiles/services_query_stockpiles
,,`,`,`,`,`,`,,`,`,`,`,`,`
"#dig label(guildhall1) start(25; 25; staircase center) message(This would be a good time to queue manager orders for /guildhall3. Once the area is dug out, continue with /guildhall2.)"
#notes label(guildhall_readme)
"Multiple 7x7 rooms for guildhalls, temples, libraries, etc."
""
Features:
"Big empty rooms. Double-thick walls to ensure engravings are on the ""correct"" side. Fill with furniture and assign as needed."
"#dig label(guildhall1) start(25; 25; staircase center) message(This would be a good time to queue manager orders for /guildhall2. Once the area is dug out, continue with /guildhall2.)"
,,d,d,d,d,d,d,d,,,,,,d,d,d,d,d,d,d,,,,,,,,d,d,d,d,d,d,d,,,,,,d,d,d,d,d,d,d
@ -1582,69 +1688,19 @@ query_stockpiles/services_query_stockpiles
,,d,d,d,d,d,d,d,,,,,,d,d,d,d,d,d,d,,,,,,,,d,d,d,d,d,d,d,,,,,,d,d,d,d,d,d,d
"#dig label(guildhall2) start(25; 25; staircase center) message(Once tiles are smoothed, continue with /guildhall3.) smooth tiles that will be made inaccessible by furniture"
#build label(guildhall2) start(25; 25; staircase center) message(Smooth tiles and furnish/declare locations as required.) build basic furniture
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,s,s,s,,`,`,`,`,`,`,`,,s,,,,s,,`,`,`,`,`,`,`,,s,s,s,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`,,,s,`,s,,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,,,,s,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`,,s
,,,,,s,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s,s
,,,,,s,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,,,,,s
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,s,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,`,,,,`,,,,,,,,`,,,,`,,`,,,,`,,,,,,,,`,,,,`
,,,,,s,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`,,s
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s,,`,,`,,`,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,s,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`,,s
,,,,,,,`,,,,`,,,,,,,,`,,,,`,,`,,,,`,,,,,,,,`,,,,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,s,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,,,,s,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`,,,,,,s
,,,,,s,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s,s
,,,,,s,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`,,s
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,`,`,`,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`,,,s,`,s,,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,s,s,s,,`,`,`,`,`,`,`,,s,,,,s,,`,`,`,`,`,`,`,,s,s,s,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
#build label(guildhall3) start(25; 25; staircase center) message(Smooth the remaining tiles and furnish/declare locations as required.) build basic furniture
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`,,,s,t,s,,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,t,,,`,`,`,`,`,`,`,,,t,s,t,,,`,`,`,`,`,`,`,,,t,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,d,`,`,,,`,`,`,`,`,`,`,d,`,`,`,`,`,d,`,`,`,`,`,`,`,,,`,`,d,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,d,,,,`,,,,,,,,d,,,,`,`,`,,,,d,,,,,,,,`,,,,d
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,t,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,t
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
@ -1656,9 +1712,9 @@ query_stockpiles/services_query_stockpiles
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,s,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,d,,,,`,,,,,,,,d,,,,`,,`,,,,d,,,,,,,,`,,,,d
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,t,`,`,`,`,`,`,`,`,`,`,`,`,`,s,,`,,`,,`,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,t
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,t,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,t
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,s,,`,,`,,`,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,t,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,,,,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,t
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,,,,,,d,,,,`,,,,,,,,d,,,,`,,`,,,,d,,,,,,,,`,,,,d
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,s,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
@ -1670,18 +1726,25 @@ query_stockpiles/services_query_stockpiles
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,,,,,,,,,,`,,,,,,,,,,,,`,`,`,,,,,,,,,,,,`
,,,,,,s,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,s
,,,,,,t,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,`,t
,,,,,,,`,,,,`,,,,,,,,`,,,,`,`,`,,,,`,,,,,,,,`,,,,`
,,,,,,,d,,,,`,,,,,,,,d,,,,`,`,`,,,,d,,,,,,,,`,,,,d
,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`,,,`,`,`,,,`,`,`,`,`,`,`,,,`,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,d,`,`,,,`,`,`,`,`,`,`,d,`,`,`,`,`,d,`,`,`,`,`,`,`,,,`,`,d,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`,,,s,t,s,,,`,`,`,`,`,`,`,,,s,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,t,,,`,`,`,`,`,`,`,,,t,s,t,,,`,`,`,`,`,`,`,,,t,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`,,,,,,,,`,`,`,`,`,`,`,,,,,,`,`,`,`,`,`,`
#notes label(beds_readme)
Suites for nobles and apartments for the teeming masses
""
Features:
Well-appointed suites to satisfy most nobles
Apartments with beds and storage to keep dwarves happy
Meta blueprint included for designating 6 levels of apartments for a full 200+ dwarves
"#dig label(suites1) start(18; 18; staircase center) message(This would be a good time to queue manager orders for /suites2. Once the area is dug out, continue with /suites2) noble suites"
,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d,d

Can't render this file because it has a wrong number of fields in line 58.

@ -7,7 +7,7 @@
# The aliases in this file were tested in DF 0.47.04 on 2020 Jul 18.
#
# The aliases are generally split into three categories:
# 1) The aliases that name a type disables everything else for that stockpile
# 1) The aliases that name a type disable everything else for that stockpile
# category and enable only that type. For example, "preparedfood" enables
# prepared food and disables all other types of food.
# 2) The aliases that start with "forbid" only forbid (or toggle) the named type
@ -17,6 +17,10 @@
#
# Aliases that don't fit into those two categories have comments explaining
# their usage.
#
# There is also a non-alphanumeric alias built into the code for the common
# shorthand for "make room":
# r+ expands to r+&
########################################
@ -55,10 +59,12 @@ quantum: {linksonly}{nocontainers}{enableanimals}{enablefood}{furnitureprefix}{e
# Run one of the quantumstopfrom* aliases over a track stop that is set to dump
# into a quantum stockpile. The alias will set up the stop to accept all types
# (the actual types stored in the quantum stockpile is controlled by the feeder
# stockpile) and link the indicated adjacent feeder stockpile. All you need to
# do afterwards is assign a vehicle to the stop (and optionally give the route a
# name --see the namelastroute* aliases below). The track stop does not need to
# be constructed yet, but the feeder stockpile needs to exist so we can link it.
# stockpile) and link the indicated adjacent feeder stockpile (for example, the
# quantumstopfromsouth alias will link to a feeder stockpile to the South). All
# you need to do afterwards is assign a vehicle to the stop (and optionally
# give the route a name --see the namelastroute* aliases below). The track stop
# does not need to be constructed yet, but the feeder stockpile needs to exist
# so we can link it.
quantumstopprefix: ^hrs&xxx&{enablesequence 17}^
quantumstopfromeast: {quantumstopprefix}s{Right}p^{Left}^q
quantumstopfromsouth: {quantumstopprefix}s{Down}p^{Up}^q
@ -130,7 +136,7 @@ permitmiscliquid: {foodprefix}{Right}{Down 18}p^
noseeds: {disablefood}{enablefood}{forbidseeds}
# enables all food except for the types listed above
food: {noseeds}{forbidpreparedfood}{forbidunpreparedfish}{forbidplants}{forbidbooze}{forbiddye}{forbidtallow}{forbidmiscliquid}
food: {noseeds}{forbidpreparedfood}{forbidunpreparedfish}{forbidplants}{forbidbooze}{forbiddye}{forbidtallow}{forbidmiscliquid}
##################################

@ -18,67 +18,40 @@ option(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependenc
add_subdirectory(clsocket)
ide_folder(clsocket "Depends")
# assemble environment args to pass on to dependency projects
get_cmake_property(vars CACHE_VARIABLES)
foreach(var ${vars})
if(var MATCHES "^CMAKE_"
AND NOT var MATCHES "^CMAKE_CACHE"
AND NOT var MATCHES "^CMAKE_HOME"
AND NOT var MATCHES "^CMAKE_PROJECT")
list(APPEND CL_ARGS "-D${var}=${${var}}")
endif()
if(var MATCHES "^ZLIB")
list(APPEND CL_ARGS "-D${var}=${${var}}")
endif()
endforeach()
include(ExternalProject)
if(WIN32)
set(EXPAT_LIB_NAME_SUFFIX "MD")
set(XLSXIO_C_FLAGS "${CMAKE_C_FLAGS} /DXML_STATIC")
else()
set(EXPAT_LIB_NAME_SUFFIX "")
set(XLSXIO_C_FLAGS "${CMAKE_C_FLAGS} -DXML_STATIC")
endif()
set(LIBEXPAT_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/libexpat)
set(LIBEXPAT_LIB ${LIBEXPAT_INSTALL_DIR}/lib/libexpat${EXPAT_LIB_NAME_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX})
set(LIBEXPAT_LIB ${LIBEXPAT_LIB} PARENT_SCOPE)
ExternalProject_Add(libexpat_project
PREFIX libexpat
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libexpat/expat
INSTALL_DIR ${LIBEXPAT_INSTALL_DIR}
BUILD_BYPRODUCTS ${LIBEXPAT_LIB}
CMAKE_GENERATOR "${CMAKE_GENERATOR}"
CMAKE_GENERATOR_TOOLSET "${CMAKE_GENERATOR_TOOLSET}"
CMAKE_ARGS ${CL_ARGS} -DCMAKE_INSTALL_PREFIX=${LIBEXPAT_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release -DEXPAT_BUILD_EXAMPLES=OFF -DEXPAT_BUILD_TESTS=OFF -DEXPAT_BUILD_TOOLS=OFF -DEXPAT_SHARED_LIBS=OFF -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_POSITION_INDEPENDENT_CODE=ON
)
set(LIBZIP_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/libzip)
set(LIBZIP_LIB ${LIBZIP_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}zip${CMAKE_STATIC_LIBRARY_SUFFIX})
set(LIBZIP_LIB ${LIBZIP_LIB} PARENT_SCOPE)
ExternalProject_Add(libzip_project
PREFIX libzip
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/libzip
INSTALL_DIR ${LIBZIP_INSTALL_DIR}
BUILD_BYPRODUCTS ${LIBZIP_LIB}
CMAKE_GENERATOR "${CMAKE_GENERATOR}"
CMAKE_GENERATOR_TOOLSET "${CMAKE_GENERATOR_TOOLSET}"
CMAKE_ARGS ${CL_ARGS} -DCMAKE_INSTALL_PREFIX=${LIBZIP_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release -DBUILD_DOC=OFF -DBUILD_EXAMPLES=OFF -DBUILD_REGRESS=OFF -DBUILD_SHARED_LIBS=OFF -DBUILD_TOOLS=OFF -DENABLE_BZIP2=OFF -DENABLE_COMMONCRYPTO=OFF -DENABLE_GNUTLS=OFF -DENABLE_LZMA=OFF -DENABLE_MBEDTLS=OFF -DENABLE_OPENSSL=OFF -DENABLE_WINDOWS_CRYPTO=OFF -DCMAKE_INSTALL_LIBDIR=lib -DCMAKE_POSITION_INDEPENDENT_CODE=ON
)
set(XLSXIO_INSTALL_DIR ${CMAKE_CURRENT_BINARY_DIR}/xlsxio)
set(XLSXIO_INSTALL_DIR ${XLSXIO_INSTALL_DIR} PARENT_SCOPE)
set(XLSXIO_LIB ${XLSXIO_INSTALL_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}xlsxio_read${CMAKE_STATIC_LIBRARY_SUFFIX})
set(XLSXIO_LIB ${XLSXIO_LIB} PARENT_SCOPE)
ExternalProject_Add(xlsxio_project
PREFIX xlsxio
DEPENDS libexpat_project libzip_project
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/xlsxio
INSTALL_DIR ${XLSXIO_INSTALL_DIR}
BUILD_BYPRODUCTS ${XLSXIO_LIB}
CMAKE_GENERATOR "${CMAKE_GENERATOR}"
CMAKE_GENERATOR_TOOLSET "${CMAKE_GENERATOR_TOOLSET}"
CMAKE_ARGS ${CL_ARGS} -DCMAKE_INSTALL_PREFIX=${XLSXIO_INSTALL_DIR} -DCMAKE_BUILD_TYPE=Release -DBUILD_STATIC=ON -DBUILD_SHARED=OFF -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DBUILD_TOOLS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_DOCUMENTATION=OFF -DWITH_LIBZIP=ON -DZLIB_DIR=${ZLIB_DIR} -DLIBZIP_DIR=${LIBZIP_INSTALL_DIR} -DEXPAT_DIR=${LIBEXPAT_INSTALL_DIR} -DEXPAT_LIBRARIES=${LIBEXPAT_LIB} -DEXPAT_INCLUDE_DIRS=${LIBEXPAT_INSTALL_DIR}/include -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DCMAKE_C_FLAGS=${XLSXIO_C_FLAGS}
)
# set the default values of libexpat options - the descriptions are left empty
# because later option() calls *do* override those
set(EXPAT_BUILD_EXAMPLES OFF CACHE BOOL "")
set(EXPAT_BUILD_TESTS OFF CACHE BOOL "")
set(EXPAT_BUILD_TOOLS OFF CACHE BOOL "")
set(EXPAT_SHARED_LIBS OFF CACHE BOOL "")
set(EXPAT_BUILD_DOCS OFF CACHE BOOL "")
set(EXPAT_ENABLE_INSTALL OFF CACHE BOOL "")
add_subdirectory(libexpat/expat)
set(LIBZIP_BUILD_DOC OFF CACHE BOOL "")
set(LIBZIP_BUILD_EXAMPLES OFF CACHE BOOL "")
set(LIBZIP_BUILD_REGRESS OFF CACHE BOOL "")
set(LIBZIP_BUILD_SHARED_LIBS OFF CACHE BOOL "")
set(LIBZIP_BUILD_TOOLS OFF CACHE BOOL "")
set(LIBZIP_ENABLE_BZIP2 OFF CACHE BOOL "")
set(LIBZIP_ENABLE_COMMONCRYPTO OFF CACHE BOOL "")
set(LIBZIP_ENABLE_GNUTLS OFF CACHE BOOL "")
set(LIBZIP_ENABLE_LZMA OFF CACHE BOOL "")
set(LIBZIP_ENABLE_MBEDTLS OFF CACHE BOOL "")
set(LIBZIP_ENABLE_OPENSSL OFF CACHE BOOL "")
set(LIBZIP_ENABLE_WINDOWS_CRYPTO OFF CACHE BOOL "")
set(LIBZIP_DO_INSTALL OFF CACHE BOOL "")
add_subdirectory(libzip)
set(XLSXIO_USE_DFHACK_LIBS ON CACHE BOOL "")
set(XLSXIO_BUILD_STATIC ON CACHE BOOL "")
set(XLSXIO_BUILD_SHARED OFF CACHE BOOL "")
set(XLSXIO_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(XLSXIO_BUILD_EXAMPLES OFF CACHE BOOL "")
set(XLSXIO_BUILD_TOOLS OFF CACHE BOOL "")
set(XLSXIO_WITH_LIBZIP ON CACHE BOOL "")
set(XLSXIO_ZLIB_DIR "${ZLIB_DIR}" CACHE PATH "")
set(XLSXIO_LIBZIP_DIR "${CMAKE_CURRENT_BINARY_DIR}/libzip" CACHE PATH "")
set(XLSXIO_EXPAT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libexpat" CACHE PATH "")
set(XLSXIO_ENABLE_INSTALL OFF CACHE BOOL "")
add_subdirectory(xlsxio)

@ -1 +1 @@
Subproject commit a7bc26b69768f7fb24f0c7976fae24b157b85b13
Subproject commit 3c0f2e86ce4e7a3a3b30e765087d02a68bba7e6f

@ -1 +1 @@
Subproject commit 29b881d286f43189ff7392f609da78daa80c0606
Subproject commit be76fa5086bfe6b1a5e83c9855e39f98edc1f066

@ -1 +1 @@
Subproject commit 261d56815b29908fc960fecb9cb3143db4b485ad
Subproject commit 4056226fe0df6bff4593ee2353cca07c2b7f327e

@ -3,7 +3,7 @@
# This file defines custom keycode shortcuts for query mode blueprints.
# Definitions in this file take precedence over any definitions in the baseline
# aliases configuration file at hack/data/quickfort/aliases-common.txt. See that
# file for aliases that are already defined.
# file for the many useful aliases that are already defined.
#
# If possible, build on the baseline aliases when defining your own aliases. If
# the DF UI screens change, updated baseline aliases may allow your aliases to
@ -18,36 +18,42 @@
# matter of mimicking the keys used to navigate through the menus and select
# options. Use the aliases in your blueprint spreadsheets by writing an alias by
# itself in a cell, like "nocontainers", or reference an alias in a larger
# sequence by enclosing in in curly brackets, like "{nocontainers}{linksonly}"
# sequence by enclosing it in curly brackets, like "{nocontainers}{linksonly}"
#
# For example, say you have the following build and place blueprints:
#
# #build start(4;1;upper left corner of stockpile) mason stockpile
# ~, ~, ~, `, `, `
# ~, wm, ~, `, `, `
# ~, ~, ~, `, `, `
# #build start(4;1;upper left corner of stockpile) build masonry workshop
# ~, ~,~,`,`,`
# ~,wm,~,`,`,`
# ~, ~,~,`,`,`
#
# #place start(4;1;upper left corner of stockpile) build mason
# ~, ~, ~, s, s, s
# ~, ~, ~, s, s, s
# ~, ~, ~, s, s, s
# #place start(4;1;upper left corner of stockpile) place stockpile for mason
# ~,~,~,s,s,s
# ~,~,~,s,s,s
# ~,~,~,s,s,s
#
# and you want to configure the stockpile to hold only non-economic ("other")
# stone and to give to the adjacent mason workshop. You could write the
# keystrokes directly:
#
# #query start(4;1;upper left corner of stockpile) configure mason
# ~, ~, ~, s{Down 5}deb{Right}{Down 2}p^, `, `
# ~, ~, ~, g{Left 2}&, `, `
# ~, ~, ~, `, `, `
# ~,~,~,s{Down 5}deb{Right}{Down 2}p^,`,`
# ~,~,~,g{Left 2}&, `,`
# ~,~,~,`, `,`
#
# or you could use alias names:
#
# #query start(4;1;upper left corner of stockpile) configure mason
# ~, ~, ~, otherstone, `, `
# ~, ~, ~, give2left, `, `
# ~, ~, ~, `, `, `
# ~,~,~,otherstone,`,`
# ~,~,~,give2left, `,`
# ~,~,~,`, `,`
#
# you can combine the two aliases above into a single cell as well, if desired:
#
# #query start(4;1;upper left corner of stockpile) configure mason
# ~,~,~,{otherstone}{give2left},`,`
# ~,~,~,`, `,`
# ~,~,~,`, `,`
#
# The syntax for defining aliases is:
# aliasname: keystrokes
@ -63,10 +69,10 @@
#
# Anything enclosed within curly brackets can also have a number after it,
# indicating how many times that alias or keycode should be repeated. For
# example: "{buildblocks 9}" or "{Down 5}".
# example: "{togglesequence 9}" or "{Down 5}".
#
# Ctrl, Alt, and Shift modifiers can be specified for the next keycode by adding
# them as keycodes. For example, Alt-h is written as "{Alt}h".
# them into the key sequence. For example, Alt-h is written as "{Alt}h".
#
# Some frequently-used keystrokes are assigned shorthand characters. Think of
# them as single-character aliases that don't need to be surrounded in curly
@ -80,3 +86,5 @@
# If you need literal verisons of the shorthand characters, surround them in
# curly brackets, for example: "{~}"
#
#
# Add your custom aliases here:

@ -1831,9 +1831,27 @@ Screen API
The screen module implements support for drawing to the tiled screen of the game.
Note that drawing only has any effect when done from callbacks, so it can only
be feasibly used in the core context.
be feasibly used in the `core context <lua-core-context>`.
Basic painting functions:
.. contents::
:local:
Basic painting functions
~~~~~~~~~~~~~~~~~~~~~~~~
Common parameters to these functions include:
* ``x``, ``y``: screen coordinates in tiles; the upper left corner of the screen
is ``x = 0, y = 0``
* ``pen``: a `pen object <lua-screen-pen>`
* ``map``: a boolean indicating whether to draw to a separate map buffer
(defaults to false, which is suitable for off-map text or a screen that hides
the map entirely). Note that only third-party plugins like TWBT currently
implement a separate map buffer. If no such plugins are enabled, passing
``true`` has no effect. However, this parameter should still be used to ensure
that scripts work properly with such plugins.
Functions:
* ``dfhack.screen.getWindowSize()``
@ -1849,25 +1867,25 @@ Basic painting functions:
* ``dfhack.screen.paintTile(pen,x,y[,char,tile,map])``
Paints a tile using given parameters. See below for a description of pen.
Paints a tile using given parameters. `See below <lua-screen-pen>` for a description of ``pen``.
Returns *false* if coordinates out of bounds, or other error.
Returns *false* on error, e.g. if coordinates are out of bounds
* ``dfhack.screen.readTile(x,y[,map])``
Retrieves the contents of the specified tile from the screen buffers.
Returns a pen object, or *nil* if invalid or TrueType.
Returns a `pen object <lua-screen-pen>`, or *nil* if invalid or TrueType.
* ``dfhack.screen.paintString(pen,x,y,text[,map])``
Paints the string starting at *x,y*. Uses the string characters
in sequence to override the ``ch`` field of pen.
in sequence to override the ``ch`` field of `pen <lua-screen-pen>`.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.fillRect(pen,x1,y1,x2,y2[,map])``
Fills the rectangle specified by the coordinates with the given pen.
Fills the rectangle specified by the coordinates with the given `pen <lua-screen-pen>`.
Returns *true* if painting at least one character succeeded.
* ``dfhack.screen.findGraphicsTile(pagename,x,y)``
@ -1903,7 +1921,12 @@ Basic painting functions:
Returns the keybinding representing the given string input
character, or *nil* if impossible.
The "pen" argument used by functions above may be represented by
.. _lua-screen-pen:
Pen API
~~~~~~~
The ``pen`` argument used by ``dfhack.screen`` functions may be represented by
a table with the following possible fields:
``ch``
@ -1958,6 +1981,9 @@ Alternatively, it may be a pre-parsed native object with the following API:
assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and
``pen.tile_bg`` to *nil*.
Screen management
~~~~~~~~~~~~~~~~~
In order to actually be able to paint to the screen, it is necessary
to create and register a viewscreen (basically a modal dialog) with
the game.
@ -1986,7 +2012,11 @@ Apart from a native viewscreen object, these functions accept a table
as a screen. In this case, ``show`` creates a new native viewscreen
that delegates all processing to methods stored in that table.
.. note:: Lua-implemented screens are only supported in the core context.
.. note::
* The `gui.Screen class <lua-gui-screen>` provides stubs for all of the
functions listed below, and its use is recommended
* Lua-implemented screens are only supported in the `core context <lua-core-context>`.
Supported callbacks and fields are:
@ -2314,11 +2344,13 @@ and are only documented here for completeness:
Returns a numeric identifier of the current thread.
.. _lua-core-context:
Core interpreter context
========================
While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the
there is one special context managed by the DFHack core. It is the
only context that can receive events from DF and plugins.
Core context specific functions:
@ -2348,7 +2380,8 @@ Core context specific functions:
* ``dfhack.onStateChange.foo = function(code)``
Event. Receives the same codes as plugin_onstatechange in C++.
Creates a handler for state change events. Receives the same
`SC_ codes <lua-globals>` as ``plugin_onstatechange()`` in C++.
Event type
@ -2360,7 +2393,7 @@ through the table with next and calls all contained values.
This is intended as an extensible way to add listeners.
This type itself is available in any context, but only the
core context has the actual events defined by C++ code.
`core context <lua-core-context>` has the actual events defined by C++ code.
Features:
@ -2427,6 +2460,8 @@ The following module management functions are provided:
should be kept limited to the standard Lua library and API described
in this document.
.. _lua-globals:
Global environment
==================
@ -2443,9 +2478,9 @@ environment by the mandatory init file dfhack.lua:
COLOR_LIGHTBLUE, COLOR_LIGHTGREEN, COLOR_LIGHTCYAN, COLOR_LIGHTRED,
COLOR_LIGHTMAGENTA, COLOR_YELLOW, COLOR_WHITE
* ``dfhack.onStateChange`` event codes
* State change event codes, used by ``dfhack.onStateChange``
Available only in the core context, as is the event itself:
Available only in the `core context <lua-core-context>`, as is the event itself:
SC_WORLD_LOADED, SC_WORLD_UNLOADED, SC_MAP_LOADED,
SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED
@ -2964,7 +2999,9 @@ The painting natives in ``dfhack.screen`` apply to the whole screen, are
completely stateless and don't implement clipping.
The Painter class inherits from ViewRect to provide clipping and local
coordinates, and tracks current cursor position and current pen.
coordinates, and tracks current cursor position and current pen. It also
supports drawing to a separate map buffer if applicable (see ``map()`` below
for details).
* ``Painter{ ..., pen = ..., key_pen = ... }``
@ -2989,7 +3026,15 @@ coordinates, and tracks current cursor position and current pen.
* ``painter:cursor()``
Returns the current cursor *x,y* in local coordinates.
Returns the current cursor *x,y* in screen coordinates.
* ``painter:cursorX()``
Returns just the current *x* cursor coordinate
* ``painter:cursorY()``
Returns just the current *y* cursor coordinate
* ``painter:seek(x,y)``
@ -3009,10 +3054,22 @@ coordinates, and tracks current cursor position and current pen.
Sets the current pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*.
* ``painter:color(fg[,bold[,bg]])``
Sets the specified colors of the current pen and returns *self*.
* ``painter:key_pen(...)``
Sets the current keybinding pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*.
* ``painter:map(to_map)``
Enables or disables drawing to a separate map buffer. ``to_map`` is a boolean
that will be passed as the ``map`` parameter to any ``dfhack.screen`` functions
that accept it. Note that only third-party plugins like TWBT currently implement
a separate map buffer; if none are enabled, this function has no effect (but
should still be used to ensure proper support for such plugins). Returns *self*.
* ``painter:clear()``
Fills the whole clip rectangle with ``CLEAR_PEN``, and returns *self*.
@ -3029,7 +3086,7 @@ coordinates, and tracks current cursor position and current pen.
* ``painter:tile([char, tile[, ...]])``
Like above, but also allows overriding the ``tile`` property on ad-hoc basis.
Like ``char()`` above, but also allows overriding the ``tile`` property on ad-hoc basis.
* ``painter:string(text[, ...])``
@ -3039,7 +3096,13 @@ coordinates, and tracks current cursor position and current pen.
Paints the description of the keycode using ``dfhack.pen.parse(cur_key_pen,...)``; returns *self*.
As noted above, all painting methods return *self*, in order to allow chaining them like this::
* ``painter:key_string(keycode, text, ...)``
A convenience wrapper around both ``key()`` and ``string()`` that prints both
the specified keycode description and text, separated by ``:``. Any extra
arguments are passed directly to ``string()``. Returns *self*.
Unless specified otherwise above, all Painter methods return *self*, in order to allow chaining them like this::
painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...
@ -3160,6 +3223,8 @@ The class has the following methods:
Returns *true* if any of the subviews handled the event.
.. _lua-gui-screen:
Screen class
------------
@ -3591,12 +3656,22 @@ Plugins
.. contents::
:local:
DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by
``mkmodule('plugins.<name>')``; this means that a lua
module file is still necessary for ``require`` to read.
DFHack plugins may export native functions and events to Lua contexts. These are
exposed as ``plugins.<name>`` modules, which can be imported with
``require('plugins.<name>')``. The plugins listed in this section expose
functions and/or data to Lua in this way.
In addition to any native functions documented here, plugins that can be
enabled (that is, plugins that support the `enable/disable API <enable>`) will
have the following functions defined:
* ``isEnabled()`` returns whether the plugin is enabled.
* ``setEnabled(boolean)`` sets whether the plugin is enabled.
The following plugins have lua support.
For plugin developers, note that a Lua file in ``plugins/lua`` is required for
``require()`` to work, even if it contains no pure-Lua functions. This file must
contain ``mkmodule('plugins.<name>')`` to import any native functions defined in
the plugin. See existing files in ``plugins/lua`` for examples.
blueprint
=========
@ -4193,7 +4268,7 @@ Arguments are passed in to the scripts via the **...** built-in
quasi-variable; when the script is called by the DFHack core,
they are all guaranteed to be non-nil strings.
DFHack core invokes the scripts in the *core context* (see above);
DFHack core invokes the scripts in the `core context <lua-core-context>`;
however it is possible to call them from any lua code (including
from other scripts) in any context, via the same function the core uses:

@ -2635,25 +2635,34 @@ Usage:
createitem
==========
Allows creating new items of arbitrary types and made of arbitrary materials.
By default, items created are spawned at the feet of the selected unit.
Allows creating new items of arbitrary types and made of arbitrary materials. A
unit must be selected in-game to use this command. By default, items created are
spawned at the feet of the selected unit.
Specify the item and material information as you would indicate them in
custom reaction raws, with the following differences:
* Separate the item and material with a space rather than a colon
* If the item has no subtype, omit the :NONE
* If the item is REMAINS, FISH, FISH_RAW, VERMIN, PET, or EGG,
specify a CREATURE:CASTE pair instead of a material token.
* If the item has no subtype, the ``:NONE`` can be omitted
* If the item is ``REMAINS``, ``FISH``, ``FISH_RAW``, ``VERMIN``, ``PET``, or ``EGG``,
specify a ``CREATURE:CASTE`` pair instead of a material token.
Corpses, body parts, and prepared meals cannot be created using this tool.
Examples::
To obtain the item and material tokens of an existing item, run
``createitem inspect``. Its output can be passed directly as arguments to
``createitem`` to create new matching items, as long as the item type is
supported.
Examples:
* Create 2 pairs of steel gauntlets::
createitem GLOVES:ITEM_GLOVES_GAUNTLETS INORGANIC:STEEL 2
Create 2 pairs of steel gauntlets.
* Create tower-cap logs::
createitem WOOD PLANT_MAT:TOWER_CAP:WOOD
Create tower-cap logs.
For more examples, :wiki:`see this wiki page <Utility:DFHack/createitem>`.

@ -34,10 +34,12 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
# Future
## Fixes
- `getplants`: fixed a crash that could occur on some maps
- `search`: fixed an issue causing item counts on the trade screen to display inconsistently when searching
- `stockpiles`: fixed a crash when loading food stockpiles
## Misc Improvements
- `createitem`: added an ``inspect`` subcommand to print the item and material tokens of existing items, which can be used to create additional matching items
- `embark-assistant`: added support for searching for taller waterfalls (up to 50 z-levels tall)
# 0.47.04-r2

@ -8,6 +8,8 @@
#pragma once
#include <xlsxio_read.h>
#include "DataIdentity.h"
namespace DFHack {
@ -26,14 +28,12 @@ struct DFHACK_EXPORT xlsx_sheet_handle_identity : public compound_identity {
};
struct DFHACK_EXPORT xlsx_file_handle {
typedef struct xlsxio_read_struct* xlsxioreader;
const xlsxioreader handle;
xlsx_file_handle(xlsxioreader handle): handle(handle) {}
static xlsx_file_handle_identity _identity;
};
struct DFHACK_EXPORT xlsx_sheet_handle {
typedef struct xlsxio_read_sheet_struct* xlsxioreadersheet;
const xlsxioreadersheet handle;
xlsx_sheet_handle(xlsxioreadersheet handle): handle(handle) {}
static xlsx_sheet_handle_identity _identity;

@ -355,15 +355,15 @@ function Painter:string(text,pen,...)
return self:advance(#text, nil)
end
function Painter:key(code,pen,...)
function Painter:key(keycode,pen,...)
return self:string(
getKeyDisplay(code),
getKeyDisplay(keycode),
to_pen(self.cur_key_pen, pen, ...)
)
end
function Painter:key_string(code, text, ...)
return self:key(code):string(': '):string(text, ...)
function Painter:key_string(keycode, text, ...)
return self:key(keycode):string(': '):string(text, ...)
end
--------------------------

@ -74,23 +74,9 @@ add_custom_target(generate_proto DEPENDS ${PROJECT_PROTO_TMP_FILES})
set_source_files_properties( Brushes.h PROPERTIES HEADER_FILE_ONLY TRUE )
add_library(buildingplan-lib STATIC buildingplan-lib.cpp)
add_library(buildingplan-lib STATIC buildingplan-lib.cpp buildingplan-planner.cpp buildingplan-rooms.cpp)
target_link_libraries(buildingplan-lib dfhack)
# xlsxreader deps
add_library(xlsxio_read STATIC IMPORTED)
set_target_properties(xlsxio_read PROPERTIES IMPORTED_LOCATION ${XLSXIO_LIB})
add_library(zip STATIC IMPORTED)
set_target_properties(zip PROPERTIES IMPORTED_LOCATION ${LIBZIP_LIB})
add_library(expat STATIC IMPORTED)
set_target_properties(expat PROPERTIES IMPORTED_LOCATION ${LIBEXPAT_LIB})
if(WIN32)
set(LIB_Z_LIB "depends/zlib/lib/zlib")
else()
set(LIB_Z_LIB "z")
endif()
# Plugins
option(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON)
if(BUILD_SUPPORTED)
@ -189,11 +175,9 @@ if(BUILD_SUPPORTED)
add_subdirectory(tweak)
dfhack_plugin(workflow workflow.cpp LINK_LIBRARIES lua)
dfhack_plugin(workNow workNow.cpp)
dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read zip expat ${LIB_Z_LIB})
dfhack_plugin(xlsxreader xlsxreader.cpp LINK_LIBRARIES lua xlsxio_read_STATIC zip expat)
dfhack_plugin(zone zone.cpp LINK_LIBRARIES lua)
endif()
target_include_directories(xlsxreader PRIVATE ${XLSXIO_INSTALL_DIR}/include)
add_dependencies(xlsxreader xlsxio_project)
# this is the skeleton plugin. If you want to make your own, make a copy and then change it
option(BUILD_SKELETON "Build the skeleton plugin." OFF)

@ -1,690 +1,16 @@
#include "buildingplan-lib.h"
#define PLUGIN_VERSION 0.00
void debug(const string &msg)
{
if (!show_debugging)
return;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG (" << PLUGIN_VERSION << "): " << msg << endl;
}
void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
/*
* Material Choice Screen
*/
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
{
return mask.whole & mat_mask.whole;
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
bool ItemFilter::matches(df::item *item)
{
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
return false;
if (decorated_only && !item->hasImprovements())
return false;
auto imattype = item->getActualMaterial();
auto imatindex = item->getActualMaterialIndex();
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
}
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
{
std::vector<std::string> descriptions;
transform_(materials, descriptions, material_to_string_fn);
if (descriptions.size() == 0)
bitfield_to_string(&descriptions, mat_mask);
if (descriptions.size() == 0)
descriptions.push_back("any");
#include "Core.h"
return descriptions;
}
std::string ItemFilter::getMaterialFilterAsSerial()
{
std::string str;
str.append(bitfield_to_string(mat_mask, ","));
str.append("/");
if (materials.size() > 0)
{
for (size_t i = 0; i < materials.size(); i++)
str.append(materials[i].getToken() + ",");
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
}
return str;
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
{
valid = false;
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
using namespace DFHack;
if (tokens.size() > 0 && !tokens[0].empty())
{
if (!parseJobMaterialCategory(&mat_mask, tokens[0]))
return false;
}
if (tokens.size() > 1 && !tokens[1].empty())
{
std::vector<std::string> mat_names;
split_string(&mat_names, tokens[1], ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++)
{
DFHack::MaterialInfo material;
if (!material.find(*m) || !material.isValid())
return false;
materials.push_back(material);
}
}
valid = true;
return true;
}
std::string ItemFilter::getMinQuality()
{
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality()
{
return ENUM_KEY_STR(item_quality, max_quality);
}
bool ItemFilter::isValid()
{
return valid;
}
void ItemFilter::clear()
{
mat_mask.whole = 0;
materials.clear();
}
DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; }
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
{
selected_column = 0;
masks_column.setTitle("Type");
masks_column.multiselect = true;
masks_column.allow_search = false;
masks_column.left_margin = 2;
materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material");
materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0);
populateMasks();
populateMaterials();
masks_column.selectDefaultEntry();
materials_column.selectDefaultEntry();
materials_column.changeHighlight(0);
}
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
{
bool key_processed = false;
switch (selected_column)
{
case 0:
key_processed = masks_column.feed(input);
if (input->count(interface_key::SELECT))
populateMaterials(); // Redo materials lists based on category selection
break;
case 1:
key_processed = materials_column.feed(input);
break;
}
if (key_processed)
return;
if (input->count(interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
if (input->count(interface_key::CUSTOM_SHIFT_C))
{
filter->clear();
masks_column.clearSelection();
materials_column.clearSelection();
populateMaterials();
}
else if (input->count(interface_key::SEC_SELECT))
{
// Convert list selections to material filters
filter->mat_mask.whole = 0;
filter->materials.clear();
// Category masks
auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials, material_info_identity_fn);
Screen::dismiss(this);
}
else if (input->count(interface_key::CURSOR_LEFT))
{
--selected_column;
validateColumn();
}
else if (input->count(interface_key::CURSOR_RIGHT))
{
selected_column++;
validateColumn();
}
else if (enabler->tracking_on && enabler->mouse_lbut)
{
if (masks_column.setHighlightByMouse())
selected_column = 0;
else if (materials_column.setHighlightByMouse())
selected_column = 1;
enabler->mouse_lbut = enabler->mouse_rbut = 0;
}
}
void ViewscreenChooseMaterial::render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Building Material ");
masks_column.display(selected_column == 0);
materials_column.display(selected_column == 1);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Toggle", interface_key::SELECT);
x += 3;
OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT);
x += 3;
OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C);
x += 3;
OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN);
}
// START Room Reservation
ReservedRoom::ReservedRoom(df::building *building, std::string noble_code)
{
this->building = building;
config = DFHack::World::AddPersistentData("buildingplan/reservedroom");
config.val() = noble_code;
config.ival(1) = building->id;
pos = df::coord(building->centerx, building->centery, building->z);
}
ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &out)
{
this->config = config;
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
}
bool ReservedRoom::checkRoomAssignment()
{
if (!isValid())
return false;
auto np = getOwnersNobleCode();
bool correctOwner = false;
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
correctOwner = true;
break;
}
}
if (correctOwner)
return true;
for (auto iter = world->units.active.begin(); iter != world->units.active.end(); iter++)
{
df::unit* unit = *iter;
if (!Units::isCitizen(unit))
continue;
if (!Units::isActive(unit))
continue;
np = getUniqueNoblePositions(unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
Buildings::setOwner(building, unit);
break;
}
}
}
return true;
}
std::string RoomMonitor::getReservedNobleCode(int32_t buildingId)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId == iter->getId())
return iter->getCode();
}
return "";
}
void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code)
{
bool found = false;
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId != iter->getId())
{
continue;
}
else
{
if (noble_code == iter->getCode())
{
iter->remove();
reservedRooms.erase(iter);
}
else
{
iter->setCode(noble_code);
}
found = true;
break;
}
}
if (!found)
{
ReservedRoom room(df::building::find(buildingId), noble_code);
reservedRooms.push_back(room);
}
}
void RoomMonitor::doCycle()
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();)
{
if (iter->checkRoomAssignment())
{
++iter;
}
else
{
iter->remove();
iter = reservedRooms.erase(iter);
}
}
}
void RoomMonitor::reset(color_ostream &out)
{
reservedRooms.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom");
for (auto i = items.begin(); i != items.end(); i++)
{
ReservedRoom rr(*i, out);
if (rr.isValid())
addRoom(rr);
}
}
void delete_item_fn(df::job_item *x) { delete x; }
// START Planning
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
{
this->building = building;
this->filter = *filter;
pos = df::coord(building->centerx, building->centery, building->z);
config = DFHack::World::AddPersistentData("buildingplan/constraints");
config.val() = filter->getMaterialFilterAsSerial();
config.ival(1) = building->id;
config.ival(2) = filter->min_quality + 1;
config.ival(3) = static_cast<int>(filter->decorated_only) + 1;
config.ival(4) = filter->max_quality + 1;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
{
this->config = config;
if (!filter.parseSerializedMaterialTokens(config.val()))
{
out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str());
return;
}
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
filter.min_quality = static_cast<df::item_quality>(config.ival(2) - 1);
filter.max_quality = static_cast<df::item_quality>(config.ival(4) - 1);
filter.decorated_only = config.ival(3) - 1;
}
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
int32_t closest_distance = -1;
for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{
auto item = *item_iter;
if (!filter.matches(item))
continue;
auto pos = item->pos;
auto distance = abs(pos.x - building->centerx) +
abs(pos.y - building->centery) +
abs(pos.z - building->z) * 50;
if (closest_distance > -1 && distance >= closest_distance)
continue;
closest_distance = distance;
closest_item = item_iter;
}
if (closest_distance > -1 && assignItem(*closest_item))
{
debug("Item assigned");
items_vector->erase(closest_item);
remove();
return true;
}
return false;
}
bool PlannedBuilding::assignItem(df::item *item)
{
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
ref->building_id = building->id;
if (building->jobs.size() != 1)
return false;
auto job = building->jobs[0];
for_each_(job->job_items, delete_item_fn);
job->job_items.clear();
job->flags.bits.suspend = false;
bool rough = false;
Job::attachJobItem(job, item, df::job_item_ref::Hauled);
if (item->getType() == item_type::BOULDER)
rough = true;
building->mat_type = item->getMaterial();
building->mat_index = item->getMaterialIndex();
job->mat_type = building->mat_type;
job->mat_index = building->mat_index;
if (building->needsDesign())
{
auto act = (df::building_actual *) building;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
return true;
}
bool PlannedBuilding::isValid()
{
bool valid = filter.isValid() &&
building && Buildings::findAtTile(pos) == building &&
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
}
void Planner::reset(color_ostream &out)
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
}
void Planner::initialize()
{
#define add_building_type(btype, itype) \
item_for_building_type[df::building_type::btype] = df::item_type::itype; \
default_item_filters[df::building_type::btype] = ItemFilter(); \
available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \
is_relevant_item_type[df::item_type::itype] = true; \
if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \
planmode_enabled[df::building_type::btype] = false
FOR_ENUM_ITEMS(item_type, it)
is_relevant_item_type[it] = false;
add_building_type(Armorstand, ARMORSTAND);
add_building_type(Bed, BED);
add_building_type(Chair, CHAIR);
add_building_type(Coffin, COFFIN);
add_building_type(Door, DOOR);
add_building_type(Floodgate, FLOODGATE);
add_building_type(Hatch, HATCH_COVER);
add_building_type(GrateWall, GRATE);
add_building_type(GrateFloor, GRATE);
add_building_type(BarsVertical, BAR);
add_building_type(BarsFloor, BAR);
add_building_type(Cabinet, CABINET);
add_building_type(Box, BOX);
// skip kennels, farm plot
add_building_type(Weaponrack, WEAPONRACK);
add_building_type(Statue, STATUE);
add_building_type(Slab, SLAB);
add_building_type(Table, TABLE);
// skip roads ... furnaces
add_building_type(WindowGlass, WINDOW);
// skip gem window ... support
add_building_type(AnimalTrap, ANIMALTRAP);
add_building_type(Chain, CHAIN);
add_building_type(Cage, CAGE);
// skip archery target
add_building_type(TractionBench, TRACTION_BENCH);
// skip nest box, hive (tools)
#undef add_building_type
}
bool show_debugging = false;
void Planner::doCycle()
void debug(const std::string &msg)
{
debug("Running Cycle");
if (planned_buildings.size() == 0)
if (!show_debugging)
return;
debug("Planned count: " + int_to_string(planned_buildings.size()));
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
{
if (show_debugging)
debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType()));
auto required_item_type = item_for_building_type[building_iter->getType()];
auto items_vector = &available_item_vectors[required_item_type];
if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector))
{
debug("Unable to allocate an item");
++building_iter;
continue;
}
}
debug("Removing building plan");
building_iter = planned_buildings.erase(building_iter);
}
}
bool Planner::allocatePlannedBuilding(df::building_type type)
{
coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false;
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst)
return false;
df::job_item *filter = new df::job_item();
filter->item_type = item_type::NONE;
filter->mat_index = 0;
filter->flags2.bits.building_material = true;
std::vector<df::job_item*> filters;
filters.push_back(filter);
if (!Buildings::constructWithFilters(newinst, filters))
{
delete newinst;
return false;
}
if (type == building_type::Door)
{
auto door = virtual_cast<df::building_doorst>(newinst);
if (door)
door->door_flags.bits.pet_passable = true;
}
addPlannedBuilding(newinst);
return true;
}
PlannedBuilding *Planner::getSelectedPlannedBuilding()
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
return nullptr;
}
void Planner::adjustMinQuality(df::building_type type, int amount)
{
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
*min_quality = static_cast<df::item_quality>(*min_quality + amount);
boundsCheckItemQuality(min_quality);
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
if (*min_quality > *max_quality)
(*max_quality) = *min_quality;
}
void Planner::adjustMaxQuality(df::building_type type, int amount)
{
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
*max_quality = static_cast<df::item_quality>(*max_quality + amount);
boundsCheckItemQuality(max_quality);
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
if (*max_quality < *min_quality)
(*min_quality) = *max_quality;
}
void Planner::boundsCheckItemQuality(item_quality::item_quality *quality)
{
*quality = static_cast<df::item_quality>(*quality);
if (*quality > item_quality::Artifact)
(*quality) = item_quality::Artifact;
if (*quality < item_quality::Ordinary)
(*quality) = item_quality::Ordinary;
color_ostream_proxy out(Core::getInstance().getConsole());
out << "DEBUG: " << msg << endl;
}
map<df::building_type, bool> planmode_enabled, saved_planmodes;
bool show_debugging = false;
bool show_help = false;
Planner planner;
RoomMonitor roomMonitor;

@ -1,509 +1,8 @@
#ifndef BUILDINGPLAN_H
#define BUILDINGPLAN_H
#pragma once
#include "uicommon.h"
#include "listcolumn.h"
#include <functional>
// DF data structure definition headers
#include "DataDefs.h"
#include "Types.h"
#include "df/build_req_choice_genst.h"
#include "df/build_req_choice_specst.h"
#include "df/item.h"
#include "df/ui.h"
#include "df/ui_build_selector.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/items_other_id.h"
#include "df/job.h"
#include "df/world.h"
#include "df/building_constructionst.h"
#include "df/building_design.h"
#include "df/entity_position.h"
#include "modules/Buildings.h"
#include "modules/Maps.h"
#include "modules/Items.h"
#include "modules/Units.h"
#include "modules/Gui.h"
#include "TileTypes.h"
#include "df/job_item.h"
#include "df/dfhack_material_category.h"
#include "df/general_ref_building_holderst.h"
#include "modules/Job.h"
#include "df/building_design.h"
#include "df/buildings_other_id.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/building_doorst.h"
using df::global::ui;
using df::global::ui_build_selector;
using df::global::world;
struct MaterialDescriptor
{
df::item_type item_type;
int16_t item_subtype;
int16_t type;
int32_t index;
bool valid;
bool matches(const MaterialDescriptor &a) const
{
return a.valid && valid &&
a.type == type &&
a.index == index &&
a.item_type == item_type &&
a.item_subtype == item_subtype;
}
};
#define MAX_MASK 10
#define MAX_MATERIAL 21
#define SIDEBAR_WIDTH 30
static inline bool canReserveRoom(df::building *building)
{
if (!building)
return false;
if (building->jobs.size() > 0 && building->jobs[0]->job_type == job_type::DestroyBuilding)
return false;
return building->is_room;
}
static inline std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit)
{
std::vector<Units::NoblePosition> np;
Units::getNoblePositions(&np, unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == "MILITIA_CAPTAIN")
{
np.erase(iter);
break;
}
}
return np;
}
void delete_item_fn(df::job_item *x);
MaterialInfo &material_info_identity_fn(MaterialInfo &m);
extern map<df::building_type, bool> planmode_enabled, saved_planmodes;
void enable_quickfort_fn(pair<const df::building_type, bool>& pair);
#include "buildingplan-planner.h"
#include "buildingplan-rooms.h"
void debug(const std::string &msg);
std::string material_to_string_fn(MaterialInfo m);
extern bool show_debugging;
extern bool show_help;
struct ItemFilter
{
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
df::item_quality min_quality;
df::item_quality max_quality;
bool decorated_only;
ItemFilter() : min_quality(df::item_quality::Ordinary), max_quality(df::item_quality::Artifact), decorated_only(false), valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
bool matchesMask(DFHack::MaterialInfo &mat);
bool matches(const df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item);
std::vector<std::string> getMaterialFilterAsVector();
std::string getMaterialFilterAsSerial();
bool parseSerializedMaterialTokens(std::string str);
std::string getMinQuality();
std::string getMaxQuality();
bool isValid();
void clear();
private:
bool valid;
};
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
public:
ViewscreenChooseMaterial(ItemFilter *filter);
void feed(set<df::interface_key> *input);
void render();
std::string getFocusString() { return "buildingplan_choosemat"; }
private:
ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column;
int selected_column;
ItemFilter *filter;
df::building_type btype;
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
{
auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
if (filter->matches(mask))
entry.selected = true;
masks_column.add(entry);
}
void populateMasks()
{
masks_column.clear();
df::dfhack_material_category mask;
mask.whole = 0;
mask.bits.stone = true;
addMaskEntry(mask, "Stone");
mask.whole = 0;
mask.bits.wood = true;
addMaskEntry(mask, "Wood");
mask.whole = 0;
mask.bits.metal = true;
addMaskEntry(mask, "Metal");
mask.whole = 0;
mask.bits.soap = true;
addMaskEntry(mask, "Soap");
masks_column.filterDisplay();
}
void populateMaterials()
{
materials_column.clear();
df::dfhack_material_category selected_category;
std::vector<df::dfhack_material_category> selected_masks = masks_column.getSelectedElems();
if (selected_masks.size() == 1)
selected_category = selected_masks[0];
else if (selected_masks.size() > 1)
return;
df::world_raws &raws = world->raws;
for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++)
{
auto obj = raws.mat_table.builtin[i];
if (obj)
{
MaterialInfo material;
material.decode(i, -1);
addMaterialEntry(selected_category, material, material.toString());
}
}
for (size_t i = 0; i < raws.inorganics.size(); i++)
{
df::inorganic_raw *p = raws.inorganics[i];
MaterialInfo material;
material.decode(0, i);
addMaterialEntry(selected_category, material, material.toString());
}
decltype(selected_category) wood_flag;
wood_flag.bits.wood = true;
if (!selected_category.whole || selected_category.bits.wood)
{
for (size_t i = 0; i < raws.plants.all.size(); i++)
{
df::plant_raw *p = raws.plants.all[i];
for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++)
{
auto t = p->material[j];
if (p->material[j]->id != "WOOD")
continue;
MaterialInfo material;
material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i);
auto name = material.toString();
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
}
materials_column.sort();
}
void addMaterialEntry(df::dfhack_material_category &selected_category,
MaterialInfo &material, std::string name)
{
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
void validateColumn()
{
set_to_limit(selected_column, 1);
}
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
masks_column.resize();
materials_column.resize();
}
};
class ReservedRoom
{
public:
ReservedRoom(df::building *building, std::string noble_code);
ReservedRoom(PersistentDataItem &config, color_ostream &out);
bool checkRoomAssignment();
void remove() { DFHack::World::DeletePersistentData(config); }
bool isValid()
{
if (!building)
return false;
if (Buildings::findAtTile(pos) != building)
return false;
return canReserveRoom(building);
}
int32_t getId()
{
if (!isValid())
return 0;
return building->id;
}
std::string getCode() { return config.val(); }
void setCode(const std::string &noble_code) { config.val() = noble_code; }
private:
df::building *building;
PersistentDataItem config;
df::coord pos;
std::vector<Units::NoblePosition> getOwnersNobleCode()
{
if (!building->owner)
return std::vector<Units::NoblePosition> ();
return getUniqueNoblePositions(building->owner);
}
};
class RoomMonitor
{
public:
RoomMonitor() { }
std::string getReservedNobleCode(int32_t buildingId);
void toggleRoomForPosition(int32_t buildingId, std::string noble_code);
void doCycle();
void reset(color_ostream &out);
private:
std::vector<ReservedRoom> reservedRooms;
void addRoom(ReservedRoom &rr)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (iter->getId() == rr.getId())
return;
}
reservedRooms.push_back(rr);
}
};
// START Planning
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, ItemFilter *filter);
PlannedBuilding(PersistentDataItem &config, color_ostream &out);
bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item);
bool isValid();
df::building_type getType() { return building->getType(); }
bool isCurrentlySelectedBuilding() { return isValid() && (building == world->selected_building); }
ItemFilter *getFilter() { return &filter; }
void remove() { DFHack::World::DeletePersistentData(config); }
private:
df::building *building;
PersistentDataItem config;
df::coord pos;
ItemFilter filter;
};
class Planner
{
public:
bool in_dummmy_screen;
Planner() : in_dummmy_screen(false), quickfort_mode(false) { }
bool isPlanableBuilding(const df::building_type type) const
{
return item_for_building_type.find(type) != item_for_building_type.end();
}
void reset(color_ostream &out);
void initialize();
void addPlannedBuilding(df::building *bld)
{
for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++)
{
(*iter)->flags.bits.suspend = true;
}
PlannedBuilding pb(bld, &default_item_filters[bld->getType()]);
planned_buildings.push_back(pb);
}
void doCycle();
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
void removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); }
ItemFilter *getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; }
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
void enableQuickfortMode()
{
saved_planmodes = planmode_enabled;
for_each_(planmode_enabled, enable_quickfort_fn);
quickfort_mode = true;
}
void disableQuickfortMode()
{
planmode_enabled = saved_planmodes;
quickfort_mode = false;
}
bool inQuickFortMode() { return quickfort_mode; }
private:
map<df::building_type, df::item_type> item_for_building_type;
map<df::building_type, ItemFilter> default_item_filters;
map<df::item_type, std::vector<df::item *>> available_item_vectors;
map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
bool quickfort_mode;
std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(item_quality::item_quality *quality);
void gather_available_items()
{
debug("Gather available items");
for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++)
{
iter->second.clear();
}
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_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(artifact);
#undef F
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
df::item_type itype = item->getType();
if (!is_relevant_item_type[itype])
continue;
if (itype == item_type::BOX && item->isBag())
continue; //Skip bags
if (item->flags.bits.artifact)
continue;
if (item->flags.bits.in_job ||
item->isAssignedToStockpile() ||
item->flags.bits.owned ||
item->flags.bits.in_chest)
{
continue;
}
available_item_vectors[itype].push_back(item);
}
}
};
extern Planner planner;
extern RoomMonitor roomMonitor;
#endif

@ -0,0 +1,552 @@
#include "df/general_ref_building_holderst.h"
#include "df/job_item.h"
#include "df/building_doorst.h"
#include "df/building_design.h"
#include "modules/Job.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "buildingplan-planner.h"
#include "buildingplan-lib.h"
#include "uicommon.h"
/*
* ItemFilter
*/
ItemFilter::ItemFilter() :
min_quality(df::item_quality::Ordinary),
max_quality(df::item_quality::Artifact),
decorated_only(false),
valid(true)
{
clear(); // mat_mask is not cleared by default (see issue #1047)
}
bool ItemFilter::matchesMask(DFHack::MaterialInfo &mat)
{
return (mat_mask.whole) ? mat.matches(mat_mask) : true;
}
bool ItemFilter::matches(const df::dfhack_material_category mask) const
{
return mask.whole & mat_mask.whole;
}
bool ItemFilter::matches(DFHack::MaterialInfo &material) const
{
for (auto it = materials.begin(); it != materials.end(); ++it)
if (material.matches(*it))
return true;
return false;
}
bool ItemFilter::matches(df::item *item)
{
if (item->getQuality() < min_quality || item->getQuality() > max_quality)
return false;
if (decorated_only && !item->hasImprovements())
return false;
auto imattype = item->getActualMaterial();
auto imatindex = item->getActualMaterialIndex();
auto item_mat = DFHack::MaterialInfo(imattype, imatindex);
return (materials.size() == 0) ? matchesMask(item_mat) : matches(item_mat);
}
std::string material_to_string_fn(DFHack::MaterialInfo m) { return m.toString(); }
std::vector<std::string> ItemFilter::getMaterialFilterAsVector()
{
std::vector<std::string> descriptions;
transform_(materials, descriptions, material_to_string_fn);
if (descriptions.size() == 0)
bitfield_to_string(&descriptions, mat_mask);
if (descriptions.size() == 0)
descriptions.push_back("any");
return descriptions;
}
std::string ItemFilter::getMaterialFilterAsSerial()
{
std::string str;
str.append(bitfield_to_string(mat_mask, ","));
str.append("/");
if (materials.size() > 0)
{
for (size_t i = 0; i < materials.size(); i++)
str.append(materials[i].getToken() + ",");
if (str[str.size()-1] == ',')
str.resize(str.size () - 1);
}
return str;
}
bool ItemFilter::parseSerializedMaterialTokens(std::string str)
{
valid = false;
std::vector<std::string> tokens;
split_string(&tokens, str, "/");
if (tokens.size() > 0 && !tokens[0].empty())
{
if (!parseJobMaterialCategory(&mat_mask, tokens[0]))
return false;
}
if (tokens.size() > 1 && !tokens[1].empty())
{
std::vector<std::string> mat_names;
split_string(&mat_names, tokens[1], ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++)
{
DFHack::MaterialInfo material;
if (!material.find(*m) || !material.isValid())
return false;
materials.push_back(material);
}
}
valid = true;
return true;
}
std::string ItemFilter::getMinQuality()
{
return ENUM_KEY_STR(item_quality, min_quality);
}
std::string ItemFilter::getMaxQuality()
{
return ENUM_KEY_STR(item_quality, max_quality);
}
bool ItemFilter::isValid()
{
return valid;
}
void ItemFilter::clear()
{
mat_mask.whole = 0;
materials.clear();
}
/*
* PlannedBuilding
*/
PlannedBuilding::PlannedBuilding(df::building *building, ItemFilter *filter)
{
this->building = building;
this->filter = *filter;
pos = df::coord(building->centerx, building->centery, building->z);
config = DFHack::World::AddPersistentData("buildingplan/constraints");
config.val() = filter->getMaterialFilterAsSerial();
config.ival(1) = building->id;
config.ival(2) = filter->min_quality + 1;
config.ival(3) = static_cast<int>(filter->decorated_only) + 1;
config.ival(4) = filter->max_quality + 1;
}
PlannedBuilding::PlannedBuilding(PersistentDataItem &config, color_ostream &out)
{
this->config = config;
if (!filter.parseSerializedMaterialTokens(config.val()))
{
out.printerr("Buildingplan: Cannot parse filter: %s\nDiscarding.", config.val().c_str());
return;
}
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
filter.min_quality = static_cast<df::item_quality>(config.ival(2) - 1);
filter.max_quality = static_cast<df::item_quality>(config.ival(4) - 1);
filter.decorated_only = config.ival(3) - 1;
}
bool PlannedBuilding::assignClosestItem(std::vector<df::item *> *items_vector)
{
decltype(items_vector->begin()) closest_item;
int32_t closest_distance = -1;
for (auto item_iter = items_vector->begin(); item_iter != items_vector->end(); item_iter++)
{
auto item = *item_iter;
if (!filter.matches(item))
continue;
auto pos = item->pos;
auto distance = abs(pos.x - building->centerx) +
abs(pos.y - building->centery) +
abs(pos.z - building->z) * 50;
if (closest_distance > -1 && distance >= closest_distance)
continue;
closest_distance = distance;
closest_item = item_iter;
}
if (closest_distance > -1 && assignItem(*closest_item))
{
debug("Item assigned");
items_vector->erase(closest_item);
remove();
return true;
}
return false;
}
void delete_item_fn(df::job_item *x) { delete x; }
bool PlannedBuilding::assignItem(df::item *item)
{
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref)
{
Core::printerr("Could not allocate general_ref_building_holderst\n");
return false;
}
ref->building_id = building->id;
if (building->jobs.size() != 1)
return false;
auto job = building->jobs[0];
for_each_(job->job_items, delete_item_fn);
job->job_items.clear();
job->flags.bits.suspend = false;
bool rough = false;
Job::attachJobItem(job, item, df::job_item_ref::Hauled);
if (item->getType() == item_type::BOULDER)
rough = true;
building->mat_type = item->getMaterial();
building->mat_index = item->getMaterialIndex();
job->mat_type = building->mat_type;
job->mat_index = building->mat_index;
if (building->needsDesign())
{
auto act = (df::building_actual *) building;
act->design = new df::building_design();
act->design->flags.bits.rough = rough;
}
return true;
}
bool PlannedBuilding::isValid()
{
bool valid = filter.isValid() &&
building && Buildings::findAtTile(pos) == building &&
building->getBuildStage() == 0;
if (!valid)
remove();
return valid;
}
df::building_type PlannedBuilding::getType()
{
return building->getType();
}
bool PlannedBuilding::isCurrentlySelectedBuilding()
{
return isValid() && (building == df::global::world->selected_building);
}
ItemFilter *PlannedBuilding::getFilter()
{
return &filter;
}
void PlannedBuilding::remove()
{
DFHack::World::DeletePersistentData(config);
}
/*
* Planner
*/
Planner::Planner() : in_dummmy_screen(false), quickfort_mode(false) { }
void enable_quickfort_fn(pair<const df::building_type, bool>& pair) { pair.second = true; }
bool Planner::isPlanableBuilding(const df::building_type type) const
{
return item_for_building_type.find(type) != item_for_building_type.end();
}
void Planner::reset(color_ostream &out)
{
planned_buildings.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/constraints");
for (auto i = items.begin(); i != items.end(); i++)
{
PlannedBuilding pb(*i, out);
if (pb.isValid())
planned_buildings.push_back(pb);
}
}
void Planner::initialize()
{
#define add_building_type(btype, itype) \
item_for_building_type[df::building_type::btype] = df::item_type::itype; \
default_item_filters[df::building_type::btype] = ItemFilter(); \
available_item_vectors[df::item_type::itype] = std::vector<df::item *>(); \
is_relevant_item_type[df::item_type::itype] = true; \
if (planmode_enabled.find(df::building_type::btype) == planmode_enabled.end()) \
planmode_enabled[df::building_type::btype] = false
FOR_ENUM_ITEMS(item_type, it)
is_relevant_item_type[it] = false;
add_building_type(Armorstand, ARMORSTAND);
add_building_type(Bed, BED);
add_building_type(Chair, CHAIR);
add_building_type(Coffin, COFFIN);
add_building_type(Door, DOOR);
add_building_type(Floodgate, FLOODGATE);
add_building_type(Hatch, HATCH_COVER);
add_building_type(GrateWall, GRATE);
add_building_type(GrateFloor, GRATE);
add_building_type(BarsVertical, BAR);
add_building_type(BarsFloor, BAR);
add_building_type(Cabinet, CABINET);
add_building_type(Box, BOX);
// skip kennels, farm plot
add_building_type(Weaponrack, WEAPONRACK);
add_building_type(Statue, STATUE);
add_building_type(Slab, SLAB);
add_building_type(Table, TABLE);
// skip roads ... furnaces
add_building_type(WindowGlass, WINDOW);
// skip gem window ... support
add_building_type(AnimalTrap, ANIMALTRAP);
add_building_type(Chain, CHAIN);
add_building_type(Cage, CAGE);
// skip archery target
add_building_type(TractionBench, TRACTION_BENCH);
// skip nest box, hive (tools)
#undef add_building_type
}
void Planner::addPlannedBuilding(df::building *bld)
{
for (auto iter = bld->jobs.begin(); iter != bld->jobs.end(); iter++)
{
(*iter)->flags.bits.suspend = true;
}
PlannedBuilding pb(bld, &default_item_filters[bld->getType()]);
planned_buildings.push_back(pb);
}
void Planner::doCycle()
{
debug("Running Cycle");
if (planned_buildings.size() == 0)
return;
debug("Planned count: " + int_to_string(planned_buildings.size()));
gather_available_items();
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end();)
{
if (building_iter->isValid())
{
if (show_debugging)
debug(std::string("Trying to allocate ") + enum_item_key_str(building_iter->getType()));
auto required_item_type = item_for_building_type[building_iter->getType()];
auto items_vector = &available_item_vectors[required_item_type];
if (items_vector->size() == 0 || !building_iter->assignClosestItem(items_vector))
{
debug("Unable to allocate an item");
++building_iter;
continue;
}
}
debug("Removing building plan");
building_iter = planned_buildings.erase(building_iter);
}
}
bool Planner::allocatePlannedBuilding(df::building_type type)
{
coord32_t cursor;
if (!DFHack::Gui::getCursorCoords(cursor.x, cursor.y, cursor.z))
return false;
auto newinst = Buildings::allocInstance(cursor.get_coord16(), type);
if (!newinst)
return false;
df::job_item *filter = new df::job_item();
filter->item_type = item_type::NONE;
filter->mat_index = 0;
filter->flags2.bits.building_material = true;
std::vector<df::job_item*> filters;
filters.push_back(filter);
if (!Buildings::constructWithFilters(newinst, filters))
{
delete newinst;
return false;
}
if (type == building_type::Door)
{
auto door = virtual_cast<df::building_doorst>(newinst);
if (door)
door->door_flags.bits.pet_passable = true;
}
addPlannedBuilding(newinst);
return true;
}
PlannedBuilding *Planner::getSelectedPlannedBuilding()
{
for (auto building_iter = planned_buildings.begin(); building_iter != planned_buildings.end(); building_iter++)
{
if (building_iter->isCurrentlySelectedBuilding())
{
return &(*building_iter);
}
}
return nullptr;
}
void Planner::removeSelectedPlannedBuilding() { getSelectedPlannedBuilding()->remove(); }
ItemFilter *Planner::getDefaultItemFilterForType(df::building_type type) { return &default_item_filters[type]; }
void Planner::adjustMinQuality(df::building_type type, int amount)
{
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
*min_quality = static_cast<df::item_quality>(*min_quality + amount);
boundsCheckItemQuality(min_quality);
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
if (*min_quality > *max_quality)
(*max_quality) = *min_quality;
}
void Planner::adjustMaxQuality(df::building_type type, int amount)
{
auto max_quality = &getDefaultItemFilterForType(type)->max_quality;
*max_quality = static_cast<df::item_quality>(*max_quality + amount);
boundsCheckItemQuality(max_quality);
auto min_quality = &getDefaultItemFilterForType(type)->min_quality;
if (*max_quality < *min_quality)
(*min_quality) = *max_quality;
}
void Planner::enableQuickfortMode()
{
saved_planmodes = planmode_enabled;
for_each_(planmode_enabled, enable_quickfort_fn);
quickfort_mode = true;
}
void Planner::disableQuickfortMode()
{
planmode_enabled = saved_planmodes;
quickfort_mode = false;
}
bool Planner::inQuickFortMode() { return quickfort_mode; }
void Planner::boundsCheckItemQuality(item_quality::item_quality *quality)
{
*quality = static_cast<df::item_quality>(*quality);
if (*quality > item_quality::Artifact)
(*quality) = item_quality::Artifact;
if (*quality < item_quality::Ordinary)
(*quality) = item_quality::Ordinary;
}
void Planner::gather_available_items()
{
debug("Gather available items");
for (auto iter = available_item_vectors.begin(); iter != available_item_vectors.end(); iter++)
{
iter->second.clear();
}
// Precompute a bitmask with the bad flags
df::item_flags bad_flags;
bad_flags.whole = 0;
#define F(x) bad_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(artifact);
#undef F
std::vector<df::item*> &items = df::global::world->items.other[df::items_other_id::IN_PLAY];
for (size_t i = 0; i < items.size(); i++)
{
df::item *item = items[i];
if (item->flags.whole & bad_flags.whole)
continue;
df::item_type itype = item->getType();
if (!is_relevant_item_type[itype])
continue;
if (itype == df::item_type::BOX && item->isBag())
continue; //Skip bags
if (item->flags.bits.artifact)
continue;
if (item->flags.bits.in_job ||
item->isAssignedToStockpile() ||
item->flags.bits.owned ||
item->flags.bits.in_chest)
{
continue;
}
available_item_vectors[itype].push_back(item);
}
}
std::map<df::building_type, bool> planmode_enabled, saved_planmodes;
Planner planner;

@ -0,0 +1,108 @@
#pragma once
#include "df/item_quality.h"
#include "df/dfhack_material_category.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
struct ItemFilter
{
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
df::item_quality min_quality;
df::item_quality max_quality;
bool decorated_only;
ItemFilter();
bool matchesMask(DFHack::MaterialInfo &mat);
bool matches(const df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item);
std::vector<std::string> getMaterialFilterAsVector();
std::string getMaterialFilterAsSerial();
bool parseSerializedMaterialTokens(std::string str);
std::string getMinQuality();
std::string getMaxQuality();
bool isValid();
void clear();
private:
bool valid;
};
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, ItemFilter *filter);
PlannedBuilding(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
bool assignClosestItem(std::vector<df::item *> *items_vector);
bool assignItem(df::item *item);
bool isValid();
void remove();
df::building_type getType();
bool isCurrentlySelectedBuilding();
ItemFilter *getFilter();
private:
df::building *building;
DFHack::PersistentDataItem config;
df::coord pos;
ItemFilter filter;
};
class Planner
{
public:
bool in_dummmy_screen;
Planner();
bool isPlanableBuilding(const df::building_type type) const;
void reset(DFHack::color_ostream &out);
void initialize();
void addPlannedBuilding(df::building *bld);
void doCycle();
bool allocatePlannedBuilding(df::building_type type);
PlannedBuilding *getSelectedPlannedBuilding();
void removeSelectedPlannedBuilding();
ItemFilter *getDefaultItemFilterForType(df::building_type type);
void adjustMinQuality(df::building_type type, int amount);
void adjustMaxQuality(df::building_type type, int amount);
void enableQuickfortMode();
void disableQuickfortMode();
bool inQuickFortMode();
private:
std::map<df::building_type, df::item_type> item_for_building_type;
std::map<df::building_type, ItemFilter> default_item_filters;
std::map<df::item_type, std::vector<df::item *>> available_item_vectors;
std::map<df::item_type, bool> is_relevant_item_type; //Needed for fast check when looping over all items
bool quickfort_mode;
std::vector<PlannedBuilding> planned_buildings;
void boundsCheckItemQuality(df::enums::item_quality::item_quality *quality);
void gather_available_items();
};
extern std::map<df::building_type, bool> planmode_enabled, saved_planmodes;
extern Planner planner;

@ -0,0 +1,227 @@
#include "buildingplan-rooms.h"
#include "buildingplan-lib.h"
#include <df/entity_position.h>
#include <df/job_type.h>
#include <df/world.h>
#include <modules/World.h>
#include <modules/Units.h>
#include <modules/Buildings.h>
using namespace DFHack;
bool canReserveRoom(df::building *building)
{
if (!building)
return false;
if (building->jobs.size() > 0 && building->jobs[0]->job_type == df::job_type::DestroyBuilding)
return false;
return building->is_room;
}
std::vector<Units::NoblePosition> getUniqueNoblePositions(df::unit *unit)
{
std::vector<Units::NoblePosition> np;
Units::getNoblePositions(&np, unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == "MILITIA_CAPTAIN")
{
np.erase(iter);
break;
}
}
return np;
}
/*
* ReservedRoom
*/
ReservedRoom::ReservedRoom(df::building *building, std::string noble_code)
{
this->building = building;
config = DFHack::World::AddPersistentData("buildingplan/reservedroom");
config.val() = noble_code;
config.ival(1) = building->id;
pos = df::coord(building->centerx, building->centery, building->z);
}
ReservedRoom::ReservedRoom(PersistentDataItem &config, color_ostream &)
{
this->config = config;
building = df::building::find(config.ival(1));
if (!building)
return;
pos = df::coord(building->centerx, building->centery, building->z);
}
bool ReservedRoom::checkRoomAssignment()
{
if (!isValid())
return false;
auto np = getOwnersNobleCode();
bool correctOwner = false;
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
correctOwner = true;
break;
}
}
if (correctOwner)
return true;
for (auto iter = df::global::world->units.active.begin(); iter != df::global::world->units.active.end(); iter++)
{
df::unit* unit = *iter;
if (!Units::isCitizen(unit))
continue;
if (!Units::isActive(unit))
continue;
np = getUniqueNoblePositions(unit);
for (auto iter = np.begin(); iter != np.end(); iter++)
{
if (iter->position->code == getCode())
{
Buildings::setOwner(building, unit);
break;
}
}
}
return true;
}
void ReservedRoom::remove() { DFHack::World::DeletePersistentData(config); }
bool ReservedRoom::isValid()
{
if (!building)
return false;
if (Buildings::findAtTile(pos) != building)
return false;
return canReserveRoom(building);
}
int32_t ReservedRoom::getId()
{
if (!isValid())
return 0;
return building->id;
}
std::string ReservedRoom::getCode() { return config.val(); }
void ReservedRoom::setCode(const std::string &noble_code) { config.val() = noble_code; }
std::vector<Units::NoblePosition> ReservedRoom::getOwnersNobleCode()
{
if (!building->owner)
return std::vector<Units::NoblePosition> ();
return getUniqueNoblePositions(building->owner);
}
/*
* RoomMonitor
*/
std::string RoomMonitor::getReservedNobleCode(int32_t buildingId)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId == iter->getId())
return iter->getCode();
}
return "";
}
void RoomMonitor::toggleRoomForPosition(int32_t buildingId, std::string noble_code)
{
bool found = false;
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (buildingId != iter->getId())
{
continue;
}
else
{
if (noble_code == iter->getCode())
{
iter->remove();
reservedRooms.erase(iter);
}
else
{
iter->setCode(noble_code);
}
found = true;
break;
}
}
if (!found)
{
ReservedRoom room(df::building::find(buildingId), noble_code);
reservedRooms.push_back(room);
}
}
void RoomMonitor::doCycle()
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end();)
{
if (iter->checkRoomAssignment())
{
++iter;
}
else
{
iter->remove();
iter = reservedRooms.erase(iter);
}
}
}
void RoomMonitor::reset(color_ostream &out)
{
reservedRooms.clear();
std::vector<PersistentDataItem> items;
DFHack::World::GetPersistentData(&items, "buildingplan/reservedroom");
for (auto i = items.begin(); i != items.end(); i++)
{
ReservedRoom rr(*i, out);
if (rr.isValid())
addRoom(rr);
}
}
void RoomMonitor::addRoom(ReservedRoom &rr)
{
for (auto iter = reservedRooms.begin(); iter != reservedRooms.end(); iter++)
{
if (iter->getId() == rr.getId())
return;
}
reservedRooms.push_back(rr);
}
RoomMonitor roomMonitor;

@ -0,0 +1,51 @@
#pragma once
#include "modules/Persistence.h"
#include "modules/Units.h"
class ReservedRoom
{
public:
ReservedRoom(df::building *building, std::string noble_code);
ReservedRoom(DFHack::PersistentDataItem &config, DFHack::color_ostream &out);
bool checkRoomAssignment();
void remove();
bool isValid();
int32_t getId();
std::string getCode();
void setCode(const std::string &noble_code);
private:
df::building *building;
DFHack::PersistentDataItem config;
df::coord pos;
std::vector<DFHack::Units::NoblePosition> getOwnersNobleCode();
};
class RoomMonitor
{
public:
RoomMonitor() { }
std::string getReservedNobleCode(int32_t buildingId);
void toggleRoomForPosition(int32_t buildingId, std::string noble_code);
void doCycle();
void reset(DFHack::color_ostream &out);
private:
std::vector<ReservedRoom> reservedRooms;
void addRoom(ReservedRoom &rr);
};
bool canReserveRoom(df::building *building);
std::vector<DFHack::Units::NoblePosition> getUniqueNoblePositions(df::unit *unit);
extern RoomMonitor roomMonitor;

@ -1,41 +1,297 @@
#include "LuaTools.h"
#include "buildingplan-lib.h"
#include "df/entity_position.h"
#include "df/interface_key.h"
#include "df/ui_build_selector.h"
#include "df/viewscreen_dwarfmodest.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "LuaTools.h"
#include "PluginManager.h"
#include "uicommon.h"
#include "listcolumn.h"
DFHACK_PLUGIN("buildingplan");
#define PLUGIN_VERSION 0.14
#define PLUGIN_VERSION 0.15
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
#define MAX_MASK 10
#define MAX_MATERIAL 21
using namespace DFHack;
using namespace df::enums;
static bool is_planmode_enabled(df::building_type type)
bool show_help = false;
class ViewscreenChooseMaterial : public dfhack_viewscreen
{
if (planmode_enabled.find(type) == planmode_enabled.end())
public:
ViewscreenChooseMaterial(ItemFilter *filter);
void feed(set<df::interface_key> *input);
void render();
std::string getFocusString() { return "buildingplan_choosemat"; }
private:
ListColumn<df::dfhack_material_category> masks_column;
ListColumn<MaterialInfo> materials_column;
int selected_column;
ItemFilter *filter;
df::building_type btype;
void addMaskEntry(df::dfhack_material_category &mask, const std::string &text)
{
return false;
auto entry = ListEntry<df::dfhack_material_category>(pad_string(text, MAX_MASK, false), mask);
if (filter->matches(mask))
entry.selected = true;
masks_column.add(entry);
}
return planmode_enabled[type];
void populateMasks()
{
masks_column.clear();
df::dfhack_material_category mask;
mask.whole = 0;
mask.bits.stone = true;
addMaskEntry(mask, "Stone");
mask.whole = 0;
mask.bits.wood = true;
addMaskEntry(mask, "Wood");
mask.whole = 0;
mask.bits.metal = true;
addMaskEntry(mask, "Metal");
mask.whole = 0;
mask.bits.soap = true;
addMaskEntry(mask, "Soap");
masks_column.filterDisplay();
}
void populateMaterials()
{
materials_column.clear();
df::dfhack_material_category selected_category;
std::vector<df::dfhack_material_category> selected_masks = masks_column.getSelectedElems();
if (selected_masks.size() == 1)
selected_category = selected_masks[0];
else if (selected_masks.size() > 1)
return;
df::world_raws &raws = world->raws;
for (int i = 1; i < DFHack::MaterialInfo::NUM_BUILTIN; i++)
{
auto obj = raws.mat_table.builtin[i];
if (obj)
{
MaterialInfo material;
material.decode(i, -1);
addMaterialEntry(selected_category, material, material.toString());
}
}
for (size_t i = 0; i < raws.inorganics.size(); i++)
{
df::inorganic_raw *p = raws.inorganics[i];
MaterialInfo material;
material.decode(0, i);
addMaterialEntry(selected_category, material, material.toString());
}
decltype(selected_category) wood_flag;
wood_flag.bits.wood = true;
if (!selected_category.whole || selected_category.bits.wood)
{
for (size_t i = 0; i < raws.plants.all.size(); i++)
{
df::plant_raw *p = raws.plants.all[i];
for (size_t j = 0; p->material.size() > 1 && j < p->material.size(); j++)
{
auto t = p->material[j];
if (p->material[j]->id != "WOOD")
continue;
MaterialInfo material;
material.decode(DFHack::MaterialInfo::PLANT_BASE+j, i);
auto name = material.toString();
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
}
materials_column.sort();
}
void addMaterialEntry(df::dfhack_material_category &selected_category,
MaterialInfo &material, std::string name)
{
if (!selected_category.whole || material.matches(selected_category))
{
ListEntry<MaterialInfo> entry(pad_string(name, MAX_MATERIAL, false), material);
if (filter->matches(material))
entry.selected = true;
materials_column.add(entry);
}
}
void validateColumn()
{
set_to_limit(selected_column, 1);
}
void resize(int32_t x, int32_t y)
{
dfhack_viewscreen::resize(x, y);
masks_column.resize();
materials_column.resize();
}
};
DFHack::MaterialInfo &material_info_identity_fn(DFHack::MaterialInfo &m) { return m; }
ViewscreenChooseMaterial::ViewscreenChooseMaterial(ItemFilter *filter)
{
selected_column = 0;
masks_column.setTitle("Type");
masks_column.multiselect = true;
masks_column.allow_search = false;
masks_column.left_margin = 2;
materials_column.left_margin = MAX_MASK + 3;
materials_column.setTitle("Material");
materials_column.multiselect = true;
this->filter = filter;
masks_column.changeHighlight(0);
populateMasks();
populateMaterials();
masks_column.selectDefaultEntry();
materials_column.selectDefaultEntry();
materials_column.changeHighlight(0);
}
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &out)
void ViewscreenChooseMaterial::feed(set<df::interface_key> *input)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
bool key_processed = false;
switch (selected_column)
{
planner.doCycle();
roomMonitor.doCycle();
case 0:
key_processed = masks_column.feed(input);
if (input->count(interface_key::SELECT))
populateMaterials(); // Redo materials lists based on category selection
break;
case 1:
key_processed = materials_column.feed(input);
break;
}
return CR_OK;
if (key_processed)
return;
if (input->count(interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
if (input->count(interface_key::CUSTOM_SHIFT_C))
{
filter->clear();
masks_column.clearSelection();
materials_column.clearSelection();
populateMaterials();
}
else if (input->count(interface_key::SEC_SELECT))
{
// Convert list selections to material filters
filter->mat_mask.whole = 0;
filter->materials.clear();
// Category masks
auto masks = masks_column.getSelectedElems();
for (auto it = masks.begin(); it != masks.end(); ++it)
filter->mat_mask.whole |= it->whole;
// Specific materials
auto materials = materials_column.getSelectedElems();
transform_(materials, filter->materials, material_info_identity_fn);
Screen::dismiss(this);
}
else if (input->count(interface_key::CURSOR_LEFT))
{
--selected_column;
validateColumn();
}
else if (input->count(interface_key::CURSOR_RIGHT))
{
selected_column++;
validateColumn();
}
else if (enabler->tracking_on && enabler->mouse_lbut)
{
if (masks_column.setHighlightByMouse())
selected_column = 0;
else if (materials_column.setHighlightByMouse())
selected_column = 1;
enabler->mouse_lbut = enabler->mouse_rbut = 0;
}
}
void ViewscreenChooseMaterial::render()
{
if (Screen::isDismissed(this))
return;
dfhack_viewscreen::render();
Screen::clear();
Screen::drawBorder(" Building Material ");
masks_column.display(selected_column == 0);
materials_column.display(selected_column == 1);
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Toggle", interface_key::SELECT);
x += 3;
OutputHotkeyString(x, y, "Save", interface_key::SEC_SELECT);
x += 3;
OutputHotkeyString(x, y, "Clear", interface_key::CUSTOM_SHIFT_C);
x += 3;
OutputHotkeyString(x, y, "Cancel", interface_key::LEAVESCREEN);
}
//START Viewscreen Hook
static bool is_planmode_enabled(df::building_type type)
{
if (planmode_enabled.find(type) == planmode_enabled.end())
{
return false;
}
return planmode_enabled[type];
}
struct buildingplan_hook : public df::viewscreen_dwarfmodest
{
//START UI Methods
@ -58,8 +314,8 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
bool isInPlannedBuildingPlacementMode()
{
return ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->stage < 2 &&
df::global::ui_build_selector &&
df::global::ui_build_selector->stage < 2 &&
planner.isPlanableBuilding(ui_build_selector->building_type);
}
@ -350,7 +606,6 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(buildingplan_hook, render);
static command_result buildingplan_cmd(color_ostream &out, vector <string> & parameters)
{
if (!parameters.empty())
@ -401,7 +656,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
return CR_OK;
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
@ -416,6 +670,23 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
return CR_OK;
}
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
{
planner.doCycle();
roomMonitor.doCycle();
}
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &)
{
return CR_OK;
}
// Lua API section
static bool isPlannableBuilding(df::building_type type) {

@ -53,9 +53,12 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector<Plugin
" PET, and EGG, replace this with a creature ID and caste.\n"
" [count] - How many of the item you wish to create.\n"
"\n"
"To obtain the item and material of an existing item, run \n"
"'createitem inspect' with that item selected in-game.\n"
"\n"
"To use this command, you must select which unit will create the items.\n"
"By default, items created will be placed at that unit's feet.\n"
"To change this, type 'createitem <destination>'.\n"
"To change this, run 'createitem <destination>'.\n"
"Valid destinations:\n"
"* floor - Place items on floor beneath maker's feet.\n"
"* item - Place items inside selected container.\n"
@ -142,7 +145,21 @@ command_result df_createitem (color_ostream &out, vector <string> & parameters)
if (parameters.size() == 1)
{
if (parameters[0] == "floor")
if (parameters[0] == "inspect")
{
CoreSuspender suspend;
df::item *item = Gui::getSelectedItem(out);
if (!item)
{
return CR_FAILURE;
}
ItemTypeInfo iinfo(item->getType(), item->getSubtype());
MaterialInfo minfo(item->getMaterial(), item->getMaterialIndex());
out.print("%s %s\n", iinfo.getToken().c_str(), minfo.getToken().c_str());
return CR_OK;
}
else if (parameters[0] == "floor")
{
dest_container = -1;
dest_building = -1;

@ -1,11 +1,28 @@
#include "buildingplan-lib.h"
#include <fstream>
#include <vector>
#include "df/world.h"
#include "df/trap_type.h"
#include "modules/Filesystem.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "PluginManager.h"
#include "buildingplan-lib.h"
#include "uicommon.h"
DFHACK_PLUGIN("fortplan");
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(world);
#define PLUGIN_VERSION 0.15
using namespace std;
using namespace DFHack;
command_result fortplan(color_ostream &out, vector<string> & params);
struct BuildingInfo {
@ -88,7 +105,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCom
#define DAY_TICKS 1200
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
if (Maps::IsValid() && !World::ReadPauseState() && world->frame_counter % (DAY_TICKS/2) == 0)
if (Maps::IsValid() && !World::ReadPauseState() && df::global::world->frame_counter % (DAY_TICKS/2) == 0)
{
planner.doCycle();
}
@ -100,7 +117,7 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
{
if (!gps)
if (!df::global::gps )
return CR_FAILURE;
if (enable != is_enabled)

@ -247,6 +247,9 @@ bool picked(const df::plant *plant, int32_t growth_subtype) {
int32_t pos_y = site->global_min_y + plant->pos.y / 48;
size_t id = pos_x + pos_y * 16 * world_data->world_width;
df::world_object_data *object_data = df::world_object_data::find(id);
if (!object_data) {
return false;
}
df::map_block_column *column = world->map.map_block_columns[(plant->pos.x / 16) * world->map.x_count_block + (plant->pos.y / 16)];
for (size_t i = 0; i < object_data->picked_growths.x.size(); i++) {

@ -1 +1 @@
Subproject commit 070e8d221a1de3c0c5a0fa4ae80a9c86e7e510fc
Subproject commit 436b98f6b2dd8a8a845e5eccaeaecd5024dc1b9f