Merge branch 'develop' into Autoclothing
commit
310940e1a2
@ -1,4 +1 @@
|
|||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
cmake --build VC2022 -t ALL_BUILD -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo ALL_BUILD.vcxproj
|
|
||||||
cd ..
|
|
||||||
|
@ -1,5 +1 @@
|
|||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
cmake --build VC2022 -t ALL_BUILD -- /m /p:Platform=x64 /p:Configuration=Release
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=Release ALL_BUILD.vcxproj
|
|
||||||
cd ..
|
|
||||||
pause
|
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
||||||
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
||||||
mkdir VC2022
|
|
||||||
cd VC2022
|
|
||||||
echo generating a build folder
|
echo generating a build folder
|
||||||
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_STONESENSE=1
|
cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=1 -DBUILD_DEV_PLUGINS=1 -DBUILD_STONESENSE=1
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
||||||
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
||||||
mkdir VC2022
|
|
||||||
cd VC2022
|
|
||||||
echo Pre-generating a build folder
|
echo Pre-generating a build folder
|
||||||
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%"
|
cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%"
|
||||||
cmake-gui .
|
cmake-gui VC2022
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
||||||
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
||||||
mkdir VC2022
|
|
||||||
cd VC2022
|
|
||||||
echo generating a build folder
|
echo generating a build folder
|
||||||
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0
|
cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
||||||
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
||||||
mkdir VC2022
|
|
||||||
cd VC2022
|
|
||||||
echo generating a build folder
|
echo generating a build folder
|
||||||
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DOCS=1 -DBUILD_STONESENSE=1
|
cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_DOCS=1 -DBUILD_STONESENSE=1
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
IF EXIST DF_PATH.txt SET /P _DF_PATH=<DF_PATH.txt
|
||||||
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
IF NOT EXIST DF_PATH.txt SET _DF_PATH=%CD%\DF
|
||||||
mkdir VC2022
|
|
||||||
cd VC2022
|
|
||||||
echo generating a build folder
|
echo generating a build folder
|
||||||
cmake ..\..\.. -G"Visual Studio 17 2022" -A x64 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_SUPPORTED=0 -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0 -DBUILD_SIZECHECK=1
|
cmake ..\.. -G"Visual Studio 17 2022" -A x64 -B VC2022 -DCMAKE_INSTALL_PREFIX="%_DF_PATH%" -DBUILD_SUPPORTED=0 -DBUILD_DEVEL=0 -DBUILD_DEV_PLUGINS=0 -DBUILD_STONESENSE=0 -DBUILD_SIZECHECK=1
|
||||||
|
@ -1,4 +1 @@
|
|||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
cmake --build VC2022 -t INSTALL -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo INSTALL.vcxproj
|
|
||||||
cd ..
|
|
||||||
|
@ -1,4 +1 @@
|
|||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
cmake --build VC2022 -t INSTALL -- /m /p:Platform=x64 /p:Configuration=Release
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=Release INSTALL.vcxproj
|
|
||||||
cd ..
|
|
||||||
|
@ -1,6 +1,2 @@
|
|||||||
@echo off
|
cmake --build VC2022 -t PACKAGE -- /m /p:Platform=x64 /p:Configuration=RelWithDebInfo
|
||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=RelWithDebInfo PACKAGE.vcxproj
|
|
||||||
cd ..
|
|
||||||
exit %ERRORLEVEL%
|
exit %ERRORLEVEL%
|
||||||
|
@ -1,5 +1 @@
|
|||||||
@echo off
|
cmake --build VC2022 -t PACKAGE -- /m /p:Platform=x64 /p:Configuration=Release
|
||||||
call "%ProgramFiles%\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
|
||||||
cd VC2022
|
|
||||||
msbuild /m /p:Platform=x64 /p:Configuration=Release PACKAGE.vcxproj
|
|
||||||
cd ..
|
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,192 @@
|
|||||||
|
.. _quickstart:
|
||||||
|
|
||||||
|
Quickstart guide
|
||||||
|
================
|
||||||
|
|
||||||
|
Welcome to DFHack! This guide will help get you oriented with the DFHack system
|
||||||
|
and teach you how to find and use the tools productively. If you're reading this
|
||||||
|
in `quickstart-guide`, hit the right arrow key or click on the hotkey hint in
|
||||||
|
the lower right corner of the window to go to the next page.
|
||||||
|
|
||||||
|
What is DFHack?
|
||||||
|
---------------
|
||||||
|
|
||||||
|
DFHack is a framework for Dwarf Fortress that provides a unified, cross-platform
|
||||||
|
environment that enables mods and tools to significantly extend the game. The
|
||||||
|
default DFHack distribution contains a wide variety of tools, including bugfixes,
|
||||||
|
interface improvements, automation agents, design blueprints, modding building
|
||||||
|
blocks, and more. Third-party tools (e.g. mods downloaded from Steam Workshop or
|
||||||
|
the forums) can also seamlessly integrate with the DFHack framework and extend
|
||||||
|
the game far beyond what can be done by just modding the raws.
|
||||||
|
|
||||||
|
DFHack's mission is to provide tools and interfaces for players and modders to:
|
||||||
|
|
||||||
|
- expand the bounds of what is possible in Dwarf Fortress
|
||||||
|
- reduce the impact of game bugs
|
||||||
|
- give the player more agency and control over the game
|
||||||
|
- provide alternatives to toilsome or frustrating aspects of gameplay
|
||||||
|
- **make the game more fun**
|
||||||
|
|
||||||
|
What can I do with DFHack tools?
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
DFHack has been around for a long time -- almost as long as Dwarf Fortress
|
||||||
|
itself. Many of the game's rough edges have been smoothed with DFHack tools.
|
||||||
|
Here are some common tasks people use DFHack tools to accomplish:
|
||||||
|
|
||||||
|
- Automatically chop trees when log stocks are low
|
||||||
|
- Record blueprint files that allow copy and paste of fort designs
|
||||||
|
- Import and export lists of manager orders
|
||||||
|
- Clean contaminants from map squares that dwarves can't reach
|
||||||
|
- Automatically butcher excess livestock so you don't become overrun with
|
||||||
|
animals
|
||||||
|
- Promote time-sensitive job types (e.g. food hauling) so they are done
|
||||||
|
expediently
|
||||||
|
- Quickly scan the map for visible ores of specific types so you can focus
|
||||||
|
your mining efforts
|
||||||
|
|
||||||
|
Some tools are one-shot commands. For example, you can run `unforbid all <unforbid>`
|
||||||
|
to claim all items on the map after a messy siege.
|
||||||
|
|
||||||
|
Other tools must be `enabled <enable>` and then they will run in the background.
|
||||||
|
For example, `enable seedwatch <seedwatch>` will start monitoring your stocks of
|
||||||
|
seeds and prevent your chefs from cooking seeds that you need for planting.
|
||||||
|
Tools that are enabled in the context of a fort will save their state with that
|
||||||
|
fort, and the will remember that they are enabled the next time you load your save.
|
||||||
|
|
||||||
|
A third class of tools add information to the screen or provide new integrated
|
||||||
|
functionality via the DFHack `overlay` framework. For example, the `unsuspend`
|
||||||
|
tool, in addition to its basic function of unsuspending all building construction
|
||||||
|
jobs, can also overlay a marker on suspended buildings to indicate that they are
|
||||||
|
suspended (and will use different markers to tell you whether this is a problem).
|
||||||
|
These overlays can be enabled and configured with the `gui/overlay` interface.
|
||||||
|
|
||||||
|
How can I figure out which commands to run?
|
||||||
|
-------------------------------------------
|
||||||
|
|
||||||
|
There are several ways to scan DFHack tools and find the one you need right now.
|
||||||
|
|
||||||
|
The first place to check is the DFHack logo hover hotspot. It's in the upper
|
||||||
|
left corner of the screen by default, though you can move it anywhere you want
|
||||||
|
with the `gui/overlay` tool.
|
||||||
|
|
||||||
|
When you hover the mouse over the logo (or hit the Ctrl-Shift-C keyboard shortcut)
|
||||||
|
a list of DFHack tools relevant to the current context comes up. For example, when
|
||||||
|
you have a unit selected, the hotspot will show a list of tools that inspect
|
||||||
|
units, allow you to edit them, or maybe even teleport them. Next to each tool,
|
||||||
|
you'll see the global hotkey you can hit to invoke the command without even
|
||||||
|
opening the hover list.
|
||||||
|
|
||||||
|
You can run any DFHack tool from `gui/launcher`, which is always listed first in
|
||||||
|
the hover list. You can also bring up the launcher by tapping the backtick key
|
||||||
|
(\`) or hitting Ctrl-Shift-D. In the launcher, you can quickly autocomplete any
|
||||||
|
command name by selecting it in the list on the right side of the window.
|
||||||
|
Commands are ordered by how often you run them, so your favorite commands will
|
||||||
|
always be on top. You can also pull full commandlines out of your history with
|
||||||
|
Alt-S (or by clicking on the "history search" hotkey hint).
|
||||||
|
|
||||||
|
Once you have typed (or autocompleted, or searched for) a command, other commands
|
||||||
|
related to the one you have selected will appear in the autocomplete list.
|
||||||
|
Scanning through that list is a great way to learn about new tools that you might
|
||||||
|
find useful. You can also see how commands are grouped by running the `tags` command.
|
||||||
|
|
||||||
|
The bottom panel will show the full help text for the command you are running,
|
||||||
|
allowing you to refer to the usage documentation and examples when you are typing
|
||||||
|
your command.
|
||||||
|
|
||||||
|
How do DFHack in-game windows work?
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
Many DFHack tools have graphical interfaces that appear in-game. You can tell
|
||||||
|
which windows belong to DFHack tools because they will have the word "DFHack"
|
||||||
|
printed across their bottom frame edge. DFHack provides a custom windowing system
|
||||||
|
that gives the player a lot of control over where the windows appear and whether
|
||||||
|
they capture keyboard and mouse input.
|
||||||
|
|
||||||
|
The DFHack windowing system allows you to use DFHack tools without interrupting
|
||||||
|
the game. That is, if the game is unpaused, it will continue to run while a
|
||||||
|
DFHack window is open. You can also interact with the map, scrolling it with the
|
||||||
|
keyboard or mouse and selecting units, buildings, and items. Some tools will
|
||||||
|
capture all keyboard input, such as tools with editable text fields, and some will
|
||||||
|
force-pause the game if it makes sense to, like `gui/quickfort`, since you cannot
|
||||||
|
interact with the map normally while trying to apply a blueprint.
|
||||||
|
|
||||||
|
DFHack windows are draggable from the title bar or from anywhere on the window
|
||||||
|
that doesn't have a mouse-clickable widget on it. Many are resizable as well
|
||||||
|
(if the tool window has components that can reasonably be resized).
|
||||||
|
|
||||||
|
DFHack windows close with a right mouse click or keyboard Esc, but if you
|
||||||
|
want to keep a DFHack tool open while you interact with the game, you can click the
|
||||||
|
pin in the upper right corner of the DFHack window or hit Alt-L so
|
||||||
|
that the pin turns green. The DFHack window will then ignore right clicks and
|
||||||
|
Esc key presses that would otherwise close the window. This is especially
|
||||||
|
useful for the configuration tool windows for the automation tools. For example,
|
||||||
|
you can pin the `gui/autochop` window, set it to minimal mode, and let it sit
|
||||||
|
there monitoring your logging industry as you play, using it as a live status
|
||||||
|
window. Note that you can still right click *on* the DFHack tool window to close
|
||||||
|
it, even when it is pinned.
|
||||||
|
|
||||||
|
You can have multiple DFHack tool windows on the screen at the same time. The
|
||||||
|
one that is receiving keyboard input has a highlighted title bar and will appear
|
||||||
|
over other windows if dragged across them. Clicking on a DFHack window that is not
|
||||||
|
currently active will bring it to the foreground and make it the active window.
|
||||||
|
|
||||||
|
Where do I go next?
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
To recap:
|
||||||
|
|
||||||
|
You can get to popular, relevant tools for the current context by hovering
|
||||||
|
the mouse over the DFHack logo or by hitting Ctrl-Shift-C.
|
||||||
|
|
||||||
|
You can get to the launcher and its integrated autocomplete, history search,
|
||||||
|
and help text by hitting backtick (\`) or Ctrl-Shift-D, or, of course, by
|
||||||
|
running it from the logo hover list.
|
||||||
|
|
||||||
|
You can list and start tools that run in the background with the `enable`
|
||||||
|
command.
|
||||||
|
|
||||||
|
You can configure screen overlays with the `gui/overlay` tool.
|
||||||
|
|
||||||
|
With those four tools, you have the complete DFHack tool suite at your
|
||||||
|
fingertips. So what to run first? Here are a few commands to get you started.
|
||||||
|
You can run them all from the launcher.
|
||||||
|
|
||||||
|
First, let's import some useful manager orders to keep your fort stocked with
|
||||||
|
basic necessities. Run ``orders import library/basic``. If you go to your
|
||||||
|
mangager orders screen, you can see all the orders that have been created for you.
|
||||||
|
|
||||||
|
Next, try setting up `autochop` by running the GUI configuration `gui/autochop`.
|
||||||
|
You can enable it from the GUI, so you don't need to run `enable autochop <enable>`
|
||||||
|
directly. You can set a target number of logs, and autochop will manage
|
||||||
|
your logging industry for you. You can control where your woodsdwarves go to
|
||||||
|
cut down trees by setting up burrows and configuring autochop to only cut in
|
||||||
|
those burrows. If you have the extra screen space, go ahead and click the pin so
|
||||||
|
it turns green and set the `gui/autochop` window to minimal mode (click on the
|
||||||
|
hint near the upper right corner of the window or hit Alt-M). As you play the game,
|
||||||
|
you can glance at it to check on your stocks of wood.
|
||||||
|
|
||||||
|
Finally, let's do some fort design copy-pasting. Go to some bedrooms that you have
|
||||||
|
set up in your fort. Run `gui/blueprint`, set a name for your blueprint by
|
||||||
|
clicking on the name field (or hitting the 'n' hotkey), typing "rooms" (or whatever)
|
||||||
|
and hitting Enter to set. Then draw a box around the target area by clicking with
|
||||||
|
the mouse. When you select the second corner, the blueprint will be saved to your
|
||||||
|
``blueprints`` subfolder.
|
||||||
|
|
||||||
|
Now open up `gui/quickfort`. You can search for the blueprint you just created by
|
||||||
|
typing its name, but it should be up near the top already. If you copied a dug-out
|
||||||
|
area with furniture in it, your blueprint will have two labels: "/dig" and "/build".
|
||||||
|
Click on the "/dig" blueprint or select it with the keyboard arrow keys and hit Enter.
|
||||||
|
You can rotate or flip the blueprint around if you need to with the transform hotkeys.
|
||||||
|
You'll see a preview of where the blueprint will be applied as you move the mouse
|
||||||
|
cursor around the map. Red outlines mean that the blueprint may fail to fully apply
|
||||||
|
at that location, so be sure to choose a spot where all the preview tiles are shown
|
||||||
|
with green diamonds. Click the mouse or hit Enter to apply the blueprint and
|
||||||
|
designate the tiles for digging. Your dwarves will come and dig it out as if you
|
||||||
|
had designated the tiles yourself.
|
||||||
|
|
||||||
|
Once the area is dug out, run `gui/quickfort` again and select the "/build" blueprint
|
||||||
|
this time. Apply the blueprint in the dug-out area, and your furniture will be
|
||||||
|
designated. It's just that easy!
|
||||||
|
|
||||||
|
There are many, many more tools to explore. Have fun!
|
@ -1 +1 @@
|
|||||||
Subproject commit 18446d51e4133a06271fd2672f5b816b8c56e937
|
Subproject commit 5d43fd9fd91007cf674bfde44b2c5a0ac70170db
|
@ -0,0 +1,688 @@
|
|||||||
|
#include "Core.h"
|
||||||
|
#include "Debug.h"
|
||||||
|
#include "LuaTools.h"
|
||||||
|
#include "PluginManager.h"
|
||||||
|
|
||||||
|
#include "modules/Items.h"
|
||||||
|
#include "modules/Job.h"
|
||||||
|
#include "modules/Materials.h"
|
||||||
|
#include "modules/Persistence.h"
|
||||||
|
#include "modules/World.h"
|
||||||
|
|
||||||
|
#include "df/building.h"
|
||||||
|
#include "df/building_design.h"
|
||||||
|
#include "df/item.h"
|
||||||
|
#include "df/job_item.h"
|
||||||
|
#include "df/world.h"
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
using std::map;
|
||||||
|
using std::pair;
|
||||||
|
using std::queue;
|
||||||
|
using std::string;
|
||||||
|
using std::unordered_map;
|
||||||
|
using std::vector;
|
||||||
|
|
||||||
|
using namespace DFHack;
|
||||||
|
|
||||||
|
DFHACK_PLUGIN("buildingplan");
|
||||||
|
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
|
||||||
|
|
||||||
|
REQUIRE_GLOBAL(world);
|
||||||
|
|
||||||
|
// logging levels can be dynamically controlled with the `debugfilter` command.
|
||||||
|
namespace DFHack {
|
||||||
|
// for configuration-related logging
|
||||||
|
DBG_DECLARE(buildingplan, status, DebugCategory::LINFO);
|
||||||
|
// for logging during the periodic scan
|
||||||
|
DBG_DECLARE(buildingplan, cycle, DebugCategory::LINFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const string CONFIG_KEY = string(plugin_name) + "/config";
|
||||||
|
static const string BLD_CONFIG_KEY = string(plugin_name) + "/building";
|
||||||
|
|
||||||
|
enum ConfigValues {
|
||||||
|
CONFIG_BLOCKS = 1,
|
||||||
|
CONFIG_BOULDERS = 2,
|
||||||
|
CONFIG_LOGS = 3,
|
||||||
|
CONFIG_BARS = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum BuildingConfigValues {
|
||||||
|
BLD_CONFIG_ID = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int get_config_val(PersistentDataItem &c, int index) {
|
||||||
|
if (!c.isValid())
|
||||||
|
return -1;
|
||||||
|
return c.ival(index);
|
||||||
|
}
|
||||||
|
static bool get_config_bool(PersistentDataItem &c, int index) {
|
||||||
|
return get_config_val(c, index) == 1;
|
||||||
|
}
|
||||||
|
static void set_config_val(PersistentDataItem &c, int index, int value) {
|
||||||
|
if (c.isValid())
|
||||||
|
c.ival(index) = value;
|
||||||
|
}
|
||||||
|
static void set_config_bool(PersistentDataItem &c, int index, bool value) {
|
||||||
|
set_config_val(c, index, value ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
class PlannedBuilding {
|
||||||
|
public:
|
||||||
|
const df::building::key_field_type id;
|
||||||
|
|
||||||
|
PlannedBuilding(color_ostream &out, df::building *building) : id(building->id) {
|
||||||
|
DEBUG(status,out).print("creating persistent data for building %d\n", id);
|
||||||
|
bld_config = DFHack::World::AddPersistentData(BLD_CONFIG_KEY);
|
||||||
|
set_config_val(bld_config, BLD_CONFIG_ID, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlannedBuilding(DFHack::PersistentDataItem &bld_config)
|
||||||
|
: id(get_config_val(bld_config, BLD_CONFIG_ID)), bld_config(bld_config) { }
|
||||||
|
|
||||||
|
void remove(color_ostream &out);
|
||||||
|
|
||||||
|
// Ensure the building still exists and is in a valid state. It can disappear
|
||||||
|
// for lots of reasons, such as running the game with the buildingplan plugin
|
||||||
|
// disabled, manually removing the building, modifying it via the API, etc.
|
||||||
|
df::building * getBuildingIfValidOrRemoveIfNot(color_ostream &out) {
|
||||||
|
auto bld = df::building::find(id);
|
||||||
|
bool valid = bld && bld->getBuildStage() == 0;
|
||||||
|
if (!valid) {
|
||||||
|
remove(out);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return bld;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
DFHack::PersistentDataItem bld_config;
|
||||||
|
};
|
||||||
|
|
||||||
|
static PersistentDataItem config;
|
||||||
|
// building id -> PlannedBuilding
|
||||||
|
unordered_map<int32_t, PlannedBuilding> planned_buildings;
|
||||||
|
// vector id -> filter bucket -> queue of (building id, job_item index)
|
||||||
|
map<df::job_item_vector_id, map<string, queue<pair<int32_t, int>>>> tasks;
|
||||||
|
|
||||||
|
// note that this just removes the PlannedBuilding. the tasks will get dropped
|
||||||
|
// as we discover them in the tasks queues and they fail to be found in planned_buildings.
|
||||||
|
// this "lazy" task cleaning algorithm works because there is no way to
|
||||||
|
// re-register a building once it has been removed -- if it has been booted out of
|
||||||
|
// planned_buildings, then it has either been built or desroyed. therefore there is
|
||||||
|
// no chance of duplicate tasks getting added to the tasks queues.
|
||||||
|
void PlannedBuilding::remove(color_ostream &out) {
|
||||||
|
DEBUG(status,out).print("removing persistent data for building %d\n", id);
|
||||||
|
DFHack::World::DeletePersistentData(config);
|
||||||
|
if (planned_buildings.count(id) > 0)
|
||||||
|
planned_buildings.erase(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const int32_t CYCLE_TICKS = 600; // twice per game day
|
||||||
|
static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle
|
||||||
|
|
||||||
|
static command_result do_command(color_ostream &out, vector<string> ¶meters);
|
||||||
|
static void do_cycle(color_ostream &out);
|
||||||
|
static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb);
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
|
||||||
|
DEBUG(status,out).print("initializing %s\n", plugin_name);
|
||||||
|
|
||||||
|
// provide a configuration interface for the plugin
|
||||||
|
commands.push_back(PluginCommand(
|
||||||
|
plugin_name,
|
||||||
|
"Plan building placement before you have materials.",
|
||||||
|
do_command));
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) {
|
||||||
|
if (enable != is_enabled) {
|
||||||
|
is_enabled = enable;
|
||||||
|
DEBUG(status,out).print("%s from the API; persisting\n",
|
||||||
|
is_enabled ? "enabled" : "disabled");
|
||||||
|
} else {
|
||||||
|
DEBUG(status,out).print("%s from the API, but already %s; no action\n",
|
||||||
|
is_enabled ? "enabled" : "disabled",
|
||||||
|
is_enabled ? "enabled" : "disabled");
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_shutdown (color_ostream &out) {
|
||||||
|
DEBUG(status,out).print("shutting down %s\n", plugin_name);
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_load_data (color_ostream &out) {
|
||||||
|
config = World::GetPersistentData(CONFIG_KEY);
|
||||||
|
|
||||||
|
if (!config.isValid()) {
|
||||||
|
DEBUG(status,out).print("no config found in this save; initializing\n");
|
||||||
|
config = World::AddPersistentData(CONFIG_KEY);
|
||||||
|
set_config_bool(config, CONFIG_BLOCKS, true);
|
||||||
|
set_config_bool(config, CONFIG_BOULDERS, true);
|
||||||
|
set_config_bool(config, CONFIG_LOGS, true);
|
||||||
|
set_config_bool(config, CONFIG_BARS, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG(status,out).print("loading persisted state\n");
|
||||||
|
planned_buildings.clear();
|
||||||
|
tasks.clear();
|
||||||
|
vector<PersistentDataItem> building_configs;
|
||||||
|
World::GetPersistentData(&building_configs, BLD_CONFIG_KEY);
|
||||||
|
const size_t num_building_configs = building_configs.size();
|
||||||
|
for (size_t idx = 0; idx < num_building_configs; ++idx) {
|
||||||
|
PlannedBuilding pb(building_configs[idx]);
|
||||||
|
registerPlannedBuilding(out, pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
|
||||||
|
if (event == DFHack::SC_WORLD_UNLOADED) {
|
||||||
|
DEBUG(status,out).print("world unloaded; clearing state for %s\n", plugin_name);
|
||||||
|
planned_buildings.clear();
|
||||||
|
tasks.clear();
|
||||||
|
}
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool cycle_requested = false;
|
||||||
|
|
||||||
|
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
|
||||||
|
if (!Core::getInstance().isWorldLoaded())
|
||||||
|
return CR_OK;
|
||||||
|
|
||||||
|
if (is_enabled &&
|
||||||
|
(cycle_requested || world->frame_counter - cycle_timestamp >= CYCLE_TICKS))
|
||||||
|
do_cycle(out);
|
||||||
|
return CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool call_buildingplan_lua(color_ostream *out, const char *fn_name,
|
||||||
|
int nargs = 0, int nres = 0,
|
||||||
|
Lua::LuaLambda && args_lambda = Lua::DEFAULT_LUA_LAMBDA,
|
||||||
|
Lua::LuaLambda && res_lambda = Lua::DEFAULT_LUA_LAMBDA) {
|
||||||
|
DEBUG(status).print("calling buildingplan lua function: '%s'\n", fn_name);
|
||||||
|
|
||||||
|
CoreSuspender guard;
|
||||||
|
|
||||||
|
auto L = Lua::Core::State;
|
||||||
|
Lua::StackUnwinder top(L);
|
||||||
|
|
||||||
|
if (!out)
|
||||||
|
out = &Core::getInstance().getConsole();
|
||||||
|
|
||||||
|
return Lua::CallLuaModuleFunction(*out, L, "plugins.buildingplan", fn_name,
|
||||||
|
nargs, nres,
|
||||||
|
std::forward<Lua::LuaLambda&&>(args_lambda),
|
||||||
|
std::forward<Lua::LuaLambda&&>(res_lambda));
|
||||||
|
}
|
||||||
|
|
||||||
|
static command_result do_command(color_ostream &out, vector<string> ¶meters) {
|
||||||
|
CoreSuspender suspend;
|
||||||
|
|
||||||
|
if (!Core::getInstance().isWorldLoaded()) {
|
||||||
|
out.printerr("Cannot configure %s without a loaded world.\n", plugin_name);
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool show_help = false;
|
||||||
|
if (!call_buildingplan_lua(&out, "parse_commandline", parameters.size(), 1,
|
||||||
|
[&](lua_State *L) {
|
||||||
|
for (const string ¶m : parameters)
|
||||||
|
Lua::Push(L, param);
|
||||||
|
},
|
||||||
|
[&](lua_State *L) {
|
||||||
|
show_help = !lua_toboolean(L, -1);
|
||||||
|
})) {
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return show_help ? CR_WRONG_USAGE : CR_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// cycle logic
|
||||||
|
//
|
||||||
|
|
||||||
|
struct BadFlags {
|
||||||
|
uint32_t whole;
|
||||||
|
|
||||||
|
BadFlags() {
|
||||||
|
df::item_flags flags;
|
||||||
|
#define F(x) flags.bits.x = true;
|
||||||
|
F(dump); F(forbid); F(garbage_collect);
|
||||||
|
F(hostile); F(on_fire); F(rotten); F(trader);
|
||||||
|
F(in_building); F(construction); F(in_job);
|
||||||
|
F(owned); F(in_chest); F(removed); F(encased);
|
||||||
|
F(spider_web);
|
||||||
|
#undef F
|
||||||
|
whole = flags.whole;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool itemPassesScreen(df::item * item) {
|
||||||
|
static const BadFlags bad_flags;
|
||||||
|
return !(item->flags.whole & bad_flags.whole)
|
||||||
|
&& !item->isAssignedToStockpile();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool matchesFilters(df::item * item, df::job_item * job_item) {
|
||||||
|
// check the properties that are not checked by Job::isSuitableItem()
|
||||||
|
if (job_item->item_type > -1 && job_item->item_type != item->getType())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->item_subtype > -1 &&
|
||||||
|
job_item->item_subtype != item->getSubtype())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->flags2.bits.building_material && !item->isBuildMat())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->metal_ore > -1 && !item->isMetalOre(job_item->metal_ore))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (job_item->has_tool_use > df::tool_uses::NONE
|
||||||
|
&& !item->hasToolUse(job_item->has_tool_use))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return DFHack::Job::isSuitableItem(
|
||||||
|
job_item, item->getType(), item->getSubtype())
|
||||||
|
&& DFHack::Job::isSuitableMaterial(
|
||||||
|
job_item, item->getMaterial(), item->getMaterialIndex(),
|
||||||
|
item->getType());
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isJobReady(color_ostream &out, df::job * job) {
|
||||||
|
int needed_items = 0;
|
||||||
|
for (auto job_item : job->job_items) { needed_items += job_item->quantity; }
|
||||||
|
if (needed_items) {
|
||||||
|
DEBUG(cycle,out).print("building needs %d more item(s)\n", needed_items);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool job_item_idx_lt(df::job_item_ref *a, df::job_item_ref *b) {
|
||||||
|
// we want the items in the opposite order of the filters
|
||||||
|
return a->job_item_idx > b->job_item_idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this function does not remove the job_items since their quantity fields are
|
||||||
|
// now all at 0, so there is no risk of having extra items attached. we don't
|
||||||
|
// remove them to keep the "finalize with buildingplan active" path as similar
|
||||||
|
// as possible to the "finalize with buildingplan disabled" path.
|
||||||
|
static void finalizeBuilding(color_ostream &out, df::building * bld) {
|
||||||
|
DEBUG(cycle,out).print("finalizing building %d\n", bld->id);
|
||||||
|
auto job = bld->jobs[0];
|
||||||
|
|
||||||
|
// sort the items so they get added to the structure in the correct order
|
||||||
|
std::sort(job->items.begin(), job->items.end(), job_item_idx_lt);
|
||||||
|
|
||||||
|
// derive the material properties of the building and job from the first
|
||||||
|
// applicable item. if any boulders are involved, it makes the whole
|
||||||
|
// structure "rough".
|
||||||
|
bool rough = false;
|
||||||
|
for (auto attached_item : job->items) {
|
||||||
|
df::item *item = attached_item->item;
|
||||||
|
rough = rough || item->getType() == df::item_type::BOULDER;
|
||||||
|
if (bld->mat_type == -1) {
|
||||||
|
bld->mat_type = item->getMaterial();
|
||||||
|
job->mat_type = bld->mat_type;
|
||||||
|
}
|
||||||
|
if (bld->mat_index == -1) {
|
||||||
|
bld->mat_index = item->getMaterialIndex();
|
||||||
|
job->mat_index = bld->mat_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bld->needsDesign()) {
|
||||||
|
auto act = (df::building_actual *)bld;
|
||||||
|
if (!act->design)
|
||||||
|
act->design = new df::building_design();
|
||||||
|
act->design->flags.bits.rough = rough;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we're good to go!
|
||||||
|
job->flags.bits.suspend = false;
|
||||||
|
Job::checkBuildingsNow();
|
||||||
|
}
|
||||||
|
|
||||||
|
static df::building * popInvalidTasks(color_ostream &out, queue<pair<int32_t, int>> & task_queue) {
|
||||||
|
while (!task_queue.empty()) {
|
||||||
|
auto & task = task_queue.front();
|
||||||
|
auto id = task.first;
|
||||||
|
if (planned_buildings.count(id) > 0) {
|
||||||
|
auto bld = planned_buildings.at(id).getBuildingIfValidOrRemoveIfNot(out);
|
||||||
|
if (bld && bld->jobs[0]->job_items[task.second]->quantity)
|
||||||
|
return bld;
|
||||||
|
}
|
||||||
|
DEBUG(cycle,out).print("discarding invalid task: bld=%d, job_item_idx=%d\n", id, task.second);
|
||||||
|
task_queue.pop();
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
|
||||||
|
map<string, queue<pair<int32_t, int>>> & buckets) {
|
||||||
|
auto other_id = ENUM_ATTR(job_item_vector_id, other, vector_id);
|
||||||
|
auto item_vector = df::global::world->items.other[other_id];
|
||||||
|
DEBUG(cycle,out).print("matching %zu item(s) in vector %s against %zu filter bucket(s)\n",
|
||||||
|
item_vector.size(),
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
buckets.size());
|
||||||
|
for (auto item_it = item_vector.rbegin();
|
||||||
|
item_it != item_vector.rend();
|
||||||
|
++item_it) {
|
||||||
|
auto item = *item_it;
|
||||||
|
if (!itemPassesScreen(item))
|
||||||
|
continue;
|
||||||
|
for (auto bucket_it = buckets.begin(); bucket_it != buckets.end(); ) {
|
||||||
|
auto & task_queue = bucket_it->second;
|
||||||
|
auto bld = popInvalidTasks(out, task_queue);
|
||||||
|
if (!bld) {
|
||||||
|
DEBUG(cycle,out).print("removing empty bucket: %s/%s; %zu bucket(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str(),
|
||||||
|
buckets.size() - 1);
|
||||||
|
bucket_it = buckets.erase(bucket_it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto & task = task_queue.front();
|
||||||
|
auto id = task.first;
|
||||||
|
auto job = bld->jobs[0];
|
||||||
|
auto filter_idx = task.second;
|
||||||
|
if (matchesFilters(item, job->job_items[filter_idx])
|
||||||
|
&& DFHack::Job::attachJobItem(job, item,
|
||||||
|
df::job_item_ref::Hauled, filter_idx))
|
||||||
|
{
|
||||||
|
MaterialInfo material;
|
||||||
|
material.decode(item);
|
||||||
|
ItemTypeInfo item_type;
|
||||||
|
item_type.decode(item);
|
||||||
|
DEBUG(cycle,out).print("attached %s %s to filter %d for %s(%d): %s/%s\n",
|
||||||
|
material.toString().c_str(),
|
||||||
|
item_type.toString().c_str(),
|
||||||
|
filter_idx,
|
||||||
|
ENUM_KEY_STR(building_type, bld->getType()).c_str(),
|
||||||
|
id,
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str());
|
||||||
|
// keep quantity aligned with the actual number of remaining
|
||||||
|
// items so if buildingplan is turned off, the building will
|
||||||
|
// be completed with the correct number of items.
|
||||||
|
--job->job_items[filter_idx]->quantity;
|
||||||
|
task_queue.pop();
|
||||||
|
if (isJobReady(out, job)) {
|
||||||
|
finalizeBuilding(out, bld);
|
||||||
|
planned_buildings.at(id).remove(out);
|
||||||
|
}
|
||||||
|
if (task_queue.empty()) {
|
||||||
|
DEBUG(cycle,out).print(
|
||||||
|
"removing empty item bucket: %s/%s; %zu left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket_it->first.c_str(),
|
||||||
|
buckets.size() - 1);
|
||||||
|
buckets.erase(bucket_it);
|
||||||
|
}
|
||||||
|
// we found a home for this item; no need to look further
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++bucket_it;
|
||||||
|
}
|
||||||
|
if (buckets.empty())
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VectorsToScanLast {
|
||||||
|
std::vector<df::job_item_vector_id> vectors;
|
||||||
|
VectorsToScanLast() {
|
||||||
|
// order is important here. we want to match boulders before wood and
|
||||||
|
// everything before bars. blocks are not listed here since we'll have
|
||||||
|
// already scanned them when we did the first pass through the buckets.
|
||||||
|
vectors.push_back(df::job_item_vector_id::BOULDER);
|
||||||
|
vectors.push_back(df::job_item_vector_id::WOOD);
|
||||||
|
vectors.push_back(df::job_item_vector_id::BAR);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static void do_cycle(color_ostream &out) {
|
||||||
|
static const VectorsToScanLast vectors_to_scan_last;
|
||||||
|
|
||||||
|
// mark that we have recently run
|
||||||
|
cycle_timestamp = world->frame_counter;
|
||||||
|
cycle_requested = false;
|
||||||
|
|
||||||
|
DEBUG(cycle,out).print("running %s cycle for %zu registered buildings\n",
|
||||||
|
plugin_name, planned_buildings.size());
|
||||||
|
|
||||||
|
for (auto it = tasks.begin(); it != tasks.end(); ) {
|
||||||
|
auto vector_id = it->first;
|
||||||
|
// we could make this a set, but it's only three elements
|
||||||
|
if (std::find(vectors_to_scan_last.vectors.begin(),
|
||||||
|
vectors_to_scan_last.vectors.end(),
|
||||||
|
vector_id) != vectors_to_scan_last.vectors.end()) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto & buckets = it->second;
|
||||||
|
doVector(out, vector_id, buckets);
|
||||||
|
if (buckets.empty()) {
|
||||||
|
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
tasks.size() - 1);
|
||||||
|
it = tasks.erase(it);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
for (auto vector_id : vectors_to_scan_last.vectors) {
|
||||||
|
if (tasks.count(vector_id) == 0)
|
||||||
|
continue;
|
||||||
|
auto & buckets = tasks[vector_id];
|
||||||
|
doVector(out, vector_id, buckets);
|
||||||
|
if (buckets.empty()) {
|
||||||
|
DEBUG(cycle,out).print("removing empty vector: %s; %zu vector(s) left\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
tasks.size() - 1);
|
||||||
|
tasks.erase(vector_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DEBUG(cycle,out).print("cycle done; %zu registered building(s) left\n",
|
||||||
|
planned_buildings.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////
|
||||||
|
// Lua API
|
||||||
|
// core will already be suspended when coming in through here
|
||||||
|
//
|
||||||
|
|
||||||
|
static string getBucket(const df::job_item & ji) {
|
||||||
|
std::ostringstream ser;
|
||||||
|
|
||||||
|
// pull out and serialize only known relevant fields. if we miss a few, then
|
||||||
|
// the filter bucket will be slighly less specific than it could be, but
|
||||||
|
// that's probably ok. we'll just end up bucketing slightly different items
|
||||||
|
// together. this is only a problem if the different filter at the front of
|
||||||
|
// the queue doesn't match any available items and blocks filters behind it
|
||||||
|
// that could be matched.
|
||||||
|
ser << ji.item_type << ':' << ji.item_subtype << ':' << ji.mat_type << ':'
|
||||||
|
<< ji.mat_index << ':' << ji.flags1.whole << ':' << ji.flags2.whole
|
||||||
|
<< ':' << ji.flags3.whole << ':' << ji.flags4 << ':' << ji.flags5 << ':'
|
||||||
|
<< ji.metal_ore << ':' << ji.has_tool_use;
|
||||||
|
|
||||||
|
return ser.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// get a list of item vectors that we should search for matches
|
||||||
|
static vector<df::job_item_vector_id> getVectorIds(color_ostream &out, df::job_item *job_item)
|
||||||
|
{
|
||||||
|
std::vector<df::job_item_vector_id> ret;
|
||||||
|
|
||||||
|
// if the filter already has the vector_id set to something specific, use it
|
||||||
|
if (job_item->vector_id > df::job_item_vector_id::IN_PLAY)
|
||||||
|
{
|
||||||
|
DEBUG(status,out).print("using vector_id from job_item: %s\n",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, job_item->vector_id).c_str());
|
||||||
|
ret.push_back(job_item->vector_id);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the filer is for building material, refer to our global settings for
|
||||||
|
// which vectors to search
|
||||||
|
if (job_item->flags2.bits.building_material)
|
||||||
|
{
|
||||||
|
if (get_config_bool(config, CONFIG_BLOCKS))
|
||||||
|
ret.push_back(df::job_item_vector_id::BLOCKS);
|
||||||
|
if (get_config_bool(config, CONFIG_BOULDERS))
|
||||||
|
ret.push_back(df::job_item_vector_id::BOULDER);
|
||||||
|
if (get_config_bool(config, CONFIG_LOGS))
|
||||||
|
ret.push_back(df::job_item_vector_id::WOOD);
|
||||||
|
if (get_config_bool(config, CONFIG_BARS))
|
||||||
|
ret.push_back(df::job_item_vector_id::BAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// fall back to IN_PLAY if no other vector was appropriate
|
||||||
|
if (ret.empty())
|
||||||
|
ret.push_back(df::job_item_vector_id::IN_PLAY);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
static bool registerPlannedBuilding(color_ostream &out, PlannedBuilding & pb) {
|
||||||
|
df::building * bld = pb.getBuildingIfValidOrRemoveIfNot(out);
|
||||||
|
if (!bld)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bld->jobs.size() != 1) {
|
||||||
|
DEBUG(status,out).print("unexpected number of jobs: want 1, got %zu\n", bld->jobs.size());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto job_items = bld->jobs[0]->job_items;
|
||||||
|
int num_job_items = job_items.size();
|
||||||
|
if (num_job_items < 1) {
|
||||||
|
DEBUG(status,out).print("unexpected number of job items: want >0, got %d\n", num_job_items);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
int32_t id = bld->id;
|
||||||
|
for (int job_item_idx = 0; job_item_idx < num_job_items; ++job_item_idx) {
|
||||||
|
auto job_item = job_items[job_item_idx];
|
||||||
|
auto bucket = getBucket(*job_item);
|
||||||
|
auto vector_ids = getVectorIds(out, job_item);
|
||||||
|
|
||||||
|
// if there are multiple vector_ids, schedule duplicate tasks. after
|
||||||
|
// the correct number of items are matched, the extras will get popped
|
||||||
|
// as invalid
|
||||||
|
for (auto vector_id : vector_ids) {
|
||||||
|
for (int item_num = 0; item_num < job_item->quantity; ++item_num) {
|
||||||
|
tasks[vector_id][bucket].push(std::make_pair(id, job_item_idx));
|
||||||
|
DEBUG(status,out).print("added task: %s/%s/%d,%d; "
|
||||||
|
"%zu vector(s), %zu filter bucket(s), %zu task(s) in bucket",
|
||||||
|
ENUM_KEY_STR(job_item_vector_id, vector_id).c_str(),
|
||||||
|
bucket.c_str(), id, job_item_idx, tasks.size(),
|
||||||
|
tasks[vector_id].size(), tasks[vector_id][bucket].size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// suspend jobs
|
||||||
|
for (auto job : bld->jobs)
|
||||||
|
job->flags.bits.suspend = true;
|
||||||
|
|
||||||
|
// add the planned buildings to our register
|
||||||
|
planned_buildings.emplace(bld->id, pb);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void printStatus(color_ostream &out) {
|
||||||
|
DEBUG(status,out).print("entering buildingplan_printStatus\n");
|
||||||
|
out.print("buildingplan is %s\n\n", is_enabled ? "enabled" : "disabled");
|
||||||
|
out.print(" finding materials for %zd buildings\n", planned_buildings.size());
|
||||||
|
out.print("Current settings:\n");
|
||||||
|
out.print(" use blocks: %s\n", get_config_bool(config, CONFIG_BLOCKS) ? "yes" : "no");
|
||||||
|
out.print(" use boulders: %s\n", get_config_bool(config, CONFIG_BOULDERS) ? "yes" : "no");
|
||||||
|
out.print(" use logs: %s\n", get_config_bool(config, CONFIG_LOGS) ? "yes" : "no");
|
||||||
|
out.print(" use bars: %s\n", get_config_bool(config, CONFIG_BARS) ? "yes" : "no");
|
||||||
|
out.print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool setSetting(color_ostream &out, string name, bool value) {
|
||||||
|
DEBUG(status,out).print("entering setSetting (%s -> %s)\n", name.c_str(), value ? "true" : "false");
|
||||||
|
if (name == "blocks")
|
||||||
|
set_config_bool(config, CONFIG_BLOCKS, value);
|
||||||
|
else if (name == "boulders")
|
||||||
|
set_config_bool(config, CONFIG_BOULDERS, value);
|
||||||
|
else if (name == "logs")
|
||||||
|
set_config_bool(config, CONFIG_LOGS, value);
|
||||||
|
else if (name == "bars")
|
||||||
|
set_config_bool(config, CONFIG_BARS, value);
|
||||||
|
else {
|
||||||
|
out.printerr("unrecognized setting: '%s'\n", name.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isPlannableBuilding(color_ostream &out, df::building_type type, int16_t subtype, int32_t custom) {
|
||||||
|
DEBUG(status,out).print("entering isPlannableBuilding\n");
|
||||||
|
int num_filters = 0;
|
||||||
|
if (!call_buildingplan_lua(&out, "get_num_filters", 3, 1,
|
||||||
|
[&](lua_State *L) {
|
||||||
|
Lua::Push(L, type);
|
||||||
|
Lua::Push(L, subtype);
|
||||||
|
Lua::Push(L, custom);
|
||||||
|
},
|
||||||
|
[&](lua_State *L) {
|
||||||
|
num_filters = lua_tonumber(L, -1);
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return num_filters >= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isPlannedBuilding(color_ostream &out, df::building *bld) {
|
||||||
|
TRACE(status,out).print("entering isPlannedBuilding\n");
|
||||||
|
return bld && planned_buildings.count(bld->id) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool addPlannedBuilding(color_ostream &out, df::building *bld) {
|
||||||
|
DEBUG(status,out).print("entering addPlannedBuilding\n");
|
||||||
|
if (!bld || planned_buildings.count(bld->id)
|
||||||
|
|| !isPlannableBuilding(out, bld->getType(), bld->getSubtype(),
|
||||||
|
bld->getCustomType()))
|
||||||
|
return false;
|
||||||
|
PlannedBuilding pb(out, bld);
|
||||||
|
return registerPlannedBuilding(out, pb);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void doCycle(color_ostream &out) {
|
||||||
|
DEBUG(status,out).print("entering doCycle\n");
|
||||||
|
do_cycle(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void scheduleCycle(color_ostream &out) {
|
||||||
|
DEBUG(status,out).print("entering scheduleCycle\n");
|
||||||
|
cycle_requested = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
DFHACK_PLUGIN_LUA_FUNCTIONS {
|
||||||
|
DFHACK_LUA_FUNCTION(printStatus),
|
||||||
|
DFHACK_LUA_FUNCTION(setSetting),
|
||||||
|
DFHACK_LUA_FUNCTION(isPlannableBuilding),
|
||||||
|
DFHACK_LUA_FUNCTION(isPlannedBuilding),
|
||||||
|
DFHACK_LUA_FUNCTION(addPlannedBuilding),
|
||||||
|
DFHACK_LUA_FUNCTION(doCycle),
|
||||||
|
DFHACK_LUA_FUNCTION(scheduleCycle),
|
||||||
|
DFHACK_LUA_END
|
||||||
|
};
|
@ -1 +1 @@
|
|||||||
Subproject commit 6376bbc630feaf8b4f68623cdd2c28c87ac11af4
|
Subproject commit a045369db6728979e709625908df3f4f36e868ca
|
@ -1 +1 @@
|
|||||||
Subproject commit a8c5ac9e94e26f0901917efac213ebedc7e0c276
|
Subproject commit 5bdfd379b2d13f56bab455f94af56ec0ce3e0cb1
|
Loading…
Reference in New Issue