Merge branch 'develop' into dig-now

develop
Josh Cooper 2023-03-03 12:38:22 -08:00
commit 9fb7e0d232
54 changed files with 4326 additions and 3720 deletions

@ -61,8 +61,10 @@ if(UNIX)
endif() endif()
if(WIN32) if(WIN32)
if((NOT MSVC) OR (MSVC_VERSION LESS 1930) OR (MSVC_VERSION GREATER 1934)) if(NOT MSVC)
message(SEND_ERROR "MSVC 2022 is required") message(SEND_ERROR "No MSVC found! MSVC 2022 version 1930 to 1935 is required.")
elseif((MSVC_VERSION LESS 1930) OR (MSVC_VERSION GREATER 1935))
message(SEND_ERROR "MSVC 2022 version 1930 to 1935 is required, Version Found: ${MSVC_VERSION}")
endif() endif()
endif() endif()
@ -190,7 +192,7 @@ endif()
# set up versioning. # set up versioning.
set(DF_VERSION "50.07") set(DF_VERSION "50.07")
set(DFHACK_RELEASE "alpha1") set(DFHACK_RELEASE "alpha2")
set(DFHACK_PRERELEASE TRUE) set(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -158,7 +158,7 @@ void MD5Final(unsigned char digest[16], MD5Context *ctx)
*/ */
void MD5Transform(uint32_t buf[4], uint32_t in[16]) void MD5Transform(uint32_t buf[4], uint32_t in[16])
{ {
register uint32_t a, b, c, d; uint32_t a, b, c, d;
a = buf[0]; a = buf[0];
b = buf[1]; b = buf[1];

@ -35,39 +35,64 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
## New Plugins ## New Plugins
## Fixes
-@ ``widgets.HotkeyLabel``: don't trigger on click if the widget is disabled
- ``dfhack.job.isSuitableMaterial``: now properly detects lack of fire and magma safety for vulnerable materials with high melting points
## Misc Improvements
## Documentation
## API
- Gui focus strings will no longer get the "dfhack/" prefix if the string "dfhack/" already exists in the focus string
## Lua
- ``dfhack.job.attachJobItem()``: allows you to attach specific items to a job
- ``dfhack.screen.paintTile()``: you can now explicitly clear the interface cursor from a map tile by passing ``0`` as the tile value
- ``widgets.Label``: token ``tile`` properties can now be functions that return a value
- ``widgets.CycleHotkeyLabel``: add ``label_below`` attribute for compact 2-line output
-@ ``widgets.FilteredList``: search key matching is now case insensitive by default
-@ ``gui.INTERIOR_FRAME``: a panel frame style for use in highlighting off interior areas of a UI
## Removed
-@ ``gui.THIN_FRAME``: replaced by ``gui.INTERIOR_FRAME``
# 50.07-alpha2
## Fixes ## Fixes
-@ `nestboxes`: fixed bug causing nestboxes themselves to be forbidden, which prevented citizens from using them to lay eggs. Now only eggs are forbidden. -@ `nestboxes`: fixed bug causing nestboxes themselves to be forbidden, which prevented citizens from using them to lay eggs. Now only eggs are forbidden.
- `autobutcher`: implemented work-around for Dwarf Fortress not setting nicknames properly, so that nicknames created in the in-game interface are detected & protect animals from being butchered properly. Note that nicknames for unnamed units are not currently saved by dwarf fortress - use ``enable fix/protect-nicks`` to fix any nicknames created/removed within dwarf fortress so they can be saved/reloaded when you reload the game. - `autobutcher`: implemented work-around for Dwarf Fortress not setting nicknames properly, so that nicknames created in the in-game interface are detected & protect animals from being butchered properly. Note that nicknames for unnamed units are not currently saved by dwarf fortress - use ``enable fix/protect-nicks`` to fix any nicknames created/removed within dwarf fortress so they can be saved/reloaded when you reload the game.
-@ `seedwatch`: fix saving and loading of seed stock targets -@ `seedwatch`: fix saving and loading of seed stock targets
- `autodump`: changed behaviour to only change ``dump`` and ``forbid`` flags if an item is successfully dumped. - `autodump`: changed behaviour to only change ``dump`` and ``forbid`` flags if an item is successfully dumped.
-@ `autochop`: generate default names for burrows with no assigned names -@ `autochop`: generate default names for burrows with no assigned names
- ``Buildings::StockpileIterator``: check for stockpile items on block boundary. - ``Buildings::StockpileIterator``: fix check for stockpile items on block boundary.
- `dig-now`: fixed multi-layer channel designations only channeling every second layer - `tailor`: block making clothing sized for toads; make replacement clothing orders use the size of the wearer, not the size of the garment
- `tailor`: block making clothing sized for toads; make replacement clothing orders use the size of the wearer, not the size of the garment; add support for adamantine cloth (off by default); improve logging
-@ `confirm`: fix fps drop when enabled -@ `confirm`: fix fps drop when enabled
- `channel-safely`: fix an out of bounds error regarding the REPORT event listener receiving (presumably) stale id's
## Misc Improvements ## Misc Improvements
- `autobutcher`: logs activity to the console terminal instead of making disruptive in-game announcements
- DFHack tool windows that capture mouse clicks (and therefore prevent you from clicking on the "pause" button) now unconditionally pause the game when they open (but you can still unpause with the keyboard if you want to). Examples of this behavior: `gui/quickfort`, `gui/blueprint`, `gui/liquids` - DFHack tool windows that capture mouse clicks (and therefore prevent you from clicking on the "pause" button) now unconditionally pause the game when they open (but you can still unpause with the keyboard if you want to). Examples of this behavior: `gui/quickfort`, `gui/blueprint`, `gui/liquids`
- `showmood`: now shows the number of items needed for cloth and bars in addition to the technically correct but always confusing "total dimension" (150 per bar or 10,000 per cloth) - `showmood`: now shows the number of items needed for cloth and bars in addition to the technically correct but always confusing "total dimension" (150 per bar or 10,000 per cloth)
-@ Stopped mouse clicks from affecting the map when a click on a DFHack screen dismisses the window -@ Stopped mouse clicks from affecting the map when a click on a DFHack screen dismisses the window
- `confirm`: configuration data is now persisted globally. - `confirm`: configuration data is now persisted globally.
- `dig-now`: added handling of dig designations that have been converted into active jobs - `tailor`: add support for adamantine cloth (off by default); improve logging
## Documentation
## API ## API
- ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system - ``Gui::any_civzone_hotkey``, ``Gui::getAnyCivZone``, ``Gui::getSelectedCivZone``: new functions to operate on the new zone system
- Units module: added new predicates for: - Units module: added new predicates for ``isGeldable()``, ``isMarkedForGelding()``, and ``isPet()``
- ``isGeldable()`` - ``Military``: New module for military functionality
- ``isMarkedForGelding()`` - ``Military``: new ``makeSquad`` to create a squad
- ``isPet()`` - ``Military``: changed ``getSquadName`` to take a squad identifier
- ``Military``: new ``updateRoomAssignments`` for assigning a squad to a barracks and archery range
- ``Maps::GetBiomeType`` renamed to ``Maps::getBiomeType`` for consistency
- ``Maps::GetBiomeTypeRef`` renamed to ``Maps::getBiomeTypeRef`` for consistency
## Lua ## Lua
- ``dfhack.gui.getSelectedCivZone``: returns the Zone that the user has selected currently - ``dfhack.gui.getSelectedCivZone``: returns the Zone that the user has selected currently
- ``widgets.FilteredList``: Added ``edit_on_change`` optional parameter to allow a custom callback on filter edit change. - ``widgets.FilteredList``: Added ``edit_on_change`` optional parameter to allow a custom callback on filter edit change.
- Added ``widgets.TabBar`` and ``widgets.Tab`` (migrated from control-panel.lua) - ``widgets.TabBar``: new library widget (migrated from control-panel.lua)
- ``maps.getBiomeType``: exposed preexisting function to Lua
## Removed
# 50.07-alpha1 # 50.07-alpha1

@ -1219,6 +1219,15 @@ Job module
if there are any jobs with ``first_id <= id < job_next_id``, if there are any jobs with ``first_id <= id < job_next_id``,
a lua list containing them. a lua list containing them.
* ``dfhack.job.attachJobItem(job, item, role, filter_idx, insert_idx)``
Attach a real item to this job. If the item is intended to satisfy a job_item
filter, the index of that filter should be passed in ``filter_idx``; otherwise,
pass ``-1``. Similarly, if you don't care where the item is inserted, pass
``-1`` for ``insert_idx``. The ``role`` param is a ``df.job_item_ref.T_role``.
If the item needs to be brought to the job site, then the value should be
``df.job_item_ref.T_role.Hauled``.
* ``dfhack.job.isSuitableItem(job_item, item_type, item_subtype)`` * ``dfhack.job.isSuitableItem(job_item, item_type, item_subtype)``
Does basic sanity checks to verify if the suggested item type matches Does basic sanity checks to verify if the suggested item type matches
@ -1580,6 +1589,24 @@ Units module
Returns a table of the cutoffs used by the above stress level functions. Returns a table of the cutoffs used by the above stress level functions.
Military module
~~~~~~~~~~~~~~~~~~~
* ``dfhack.military.makeSquad(assignment_id)``
Creates a new squad associated with the assignment (ie ``df::entity_position_assignment``, via ``id``) and returns it.
Fails if a squad already exists that is associated with that assignment, or if the assignment is not a fort mode player controlled squad.
Note: This function does not name the squad: consider setting a nickname (under ``squad.name.nickname``), and/or filling out the ``language_name`` object at ``squad.name``.
The returned squad is otherwise complete and requires no more setup to work correctly.
* ``dfhack.military.updateRoomAssignments(squad_id, assignment_id, squad_use_flags)``
Sets the sleep, train, indiv_eq, and squad_eq flags when training at a barracks.
* ``dfhack.military.getSquadName(squad_id)``
Returns the name of a squad as a string.
Action Timer API Action Timer API
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
@ -3757,6 +3784,12 @@ Misc
Wraps ``dfhack.screen.getKeyDisplay`` in order to allow using strings for the keycode argument. Wraps ``dfhack.screen.getKeyDisplay`` in order to allow using strings for the keycode argument.
* ``invert_color(color, bold)``
This inverts the brightness of ``color``. If this color is coming from a pen's
foreground color, include ``pen.bold`` in ``bold`` for this to work properly.
ViewRect class ViewRect class
-------------- --------------
@ -4319,9 +4352,11 @@ There are the following predefined frame style tables:
A frame suitable for overlay widget panels. A frame suitable for overlay widget panels.
* ``THIN_FRAME`` * ``INTERIOR_FRAME``
A frame suitable for light accent elements. A frame suitable for light interior accent elements. This frame does *not* have
a visible ``DFHack`` signature on it, so it must not be used as the most external
frame for a DFHack-owned UI.
gui.widgets gui.widgets
=========== ===========
@ -4662,7 +4697,9 @@ It has the following attributes:
:text_pen: Specifies the pen for active text. :text_pen: Specifies the pen for active text.
:text_dpen: Specifies the pen for disabled text. :text_dpen: Specifies the pen for disabled text.
:text_hpen: Specifies the pen for text hovered over by the mouse, if a click handler is registered. :text_hpen: Specifies the pen for text hovered over by the mouse, if a click
handler is registered. By default, this will invert the foreground
and background colors.
:disabled: Boolean or a callback; if true, the label is disabled. :disabled: Boolean or a callback; if true, the label is disabled.
:enabled: Boolean or a callback; if false, the label is disabled. :enabled: Boolean or a callback; if false, the label is disabled.
:auto_height: Sets self.frame.h from the text height. :auto_height: Sets self.frame.h from the text height.
@ -4696,8 +4733,8 @@ containing newlines, or a table with the following possible fields:
* ``token.tile = pen`` * ``token.tile = pen``
Specifies a pen or texture index to paint as one tile before the main part of Specifies a pen or texture index (or a function that returns a pen or texture
the token. index) to paint as one tile before the main part of the token.
* ``token.width = ...`` * ``token.width = ...``
@ -4769,6 +4806,18 @@ The Label widget implements the following methods:
``+halfpage``, ``-halfpage``, ``home``, or ``end``. It returns the number of ``+halfpage``, ``-halfpage``, ``home``, or ``end``. It returns the number of
lines that were actually scrolled (negative for scrolling up). lines that were actually scrolled (negative for scrolling up).
* ``label:shouldHover()``
This method returns whether or not this widget should show a hover effect,
generally you want to return ``true`` if there is some type of mouse handler
present. For example, for a ``HotKeyLabel``::
function HotkeyLabel:shouldHover()
-- When on_activate is set, text should also hover on mouseover
return HotkeyLabel.super.shouldHover(self) or self.on_activate
end
WrappedLabel class WrappedLabel class
------------------ ------------------
@ -4847,6 +4896,8 @@ It has the following attributes:
hotkey. hotkey.
:label_width: The number of spaces to allocate to the ``label`` (for use in :label_width: The number of spaces to allocate to the ``label`` (for use in
aligning a column of ``CycleHotkeyLabel`` labels). aligning a column of ``CycleHotkeyLabel`` labels).
:label_below: If ``true``, then the option value will apear below the label
instead of to the right of it. Defaults to ``false``.
:options: A list of strings or tables of :options: A list of strings or tables of
``{label=string, value=string[, pen=pen]}``. String options use the same ``{label=string, value=string[, pen=pen]}``. String options use the same
string for the label and value and the default pen. The optional ``pen`` string for the label and value and the default pen. The optional ``pen``
@ -4903,6 +4954,8 @@ item to call the ``on_submit`` callback for that item.
It has the following attributes: It has the following attributes:
:text_pen: Specifies the pen for deselected list entries. :text_pen: Specifies the pen for deselected list entries.
:text_hpen: Specifies the pen for entries that the mouse is hovered over.
Defaults to swapping the background/foreground colors.
:cursor_pen: Specifies the pen for the selected entry. :cursor_pen: Specifies the pen for the selected entry.
:inactive_pen: If specified, used for the cursor when the widget is not active. :inactive_pen: If specified, used for the cursor when the widget is not active.
:icon_pen: Default pen for icons. :icon_pen: Default pen for icons.
@ -4983,7 +5036,7 @@ construction that allows filtering the list by subwords of its items.
In addition to passing through all attributes supported by List, it In addition to passing through all attributes supported by List, it
supports: supports:
:case_sensitive: If true, matching is case sensitive. Defaults to true. :case_sensitive: If ``true``, matching is case sensitive. Defaults to ``false``.
:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field.
:edit_below: If true, the edit field is placed below the list instead of above. :edit_below: If true, the edit field is placed below the list instead of above.
:edit_key: If specified, the edit field is disabled until this key is pressed. :edit_key: If specified, the edit field is disabled until this key is pressed.

@ -1308,7 +1308,7 @@ legacy Python Quickfort. This setting has no effect on DFHack Quickfort, which
will use buildingplan to manage everything designated in a ``#build`` blueprint will use buildingplan to manage everything designated in a ``#build`` blueprint
regardless of the buildingplan UI settings. regardless of the buildingplan UI settings.
However, quickfort *does* use `buildingplan's filters <buildingplan-filters>` However, quickfort *does* use `buildingplan's filters <buildingplan>`
for each building type. For example, you can use the buildingplan UI to set the for each building type. For example, you can use the buildingplan UI to set the
type of stone you want your walls made out of. Or you can specify that all type of stone you want your walls made out of. Or you can specify that all
buildingplan-managed chairs and tables must be of Masterful quality. The current buildingplan-managed chairs and tables must be of Masterful quality. The current

@ -2,91 +2,93 @@ buildingplan
============ ============
.. dfhack-tool:: .. dfhack-tool::
:summary: Plan building construction before you have materials. :summary: Plan building layouts with or without materials.
:tags: untested fort design buildings :tags: fort design buildings
This plugin adds a planning mode for building placement. You can then place Buildingplan allows you to place furniture, constructions, and other buildings,
furniture, constructions, and other buildings before the required materials are regardless of whether the required materials are available. This allows you to
available, and they will be created in a suspended state. Buildingplan will focus purely on design elements when you are laying out your fort, and defers
periodically scan for appropriate items, and the jobs will be unsuspended when item production concerns to a more convenient time.
the items are available.
Buildingplan is as an alternative to the vanilla building placement UI. It
This is very useful when combined with manager work orders or `workflow` -- you appears after you have selected the type of building, furniture, or construction
can set a constraint to always have one or two doors/beds/tables/chairs/etc. that you want to place in the vanilla build menu. Buildingplan then takes over
available, and place as many as you like. Materials are used to build the for the actual placement step. If any building materials are not available yet
planned buildings as they are produced, with minimal space dedicated to for the placed building, it will be created in a suspended state. Buildingplan
stockpiles. will periodically scan for appropriate items and attach them. Once all items are
attached, the construction job will be unsuspended and a dwarf will come and
build the building. If you have the `unsuspend` overlay enabled (it is enabled
by default), then buildingplan-suspended buildings will appear with a ``P`` marker
on the main map, as opposed to the usual ``x`` marker for "regular" suspended
buildings.
If you want to impose restrictions on which items are chosen for the buildings,
buildingplan has full support for quality and material filters. Before you place
a building, you can select a component item in the list and hit ``f`` or click on
the ``filter`` button next to the item description. This will let you choose your
desired item quality range, whether the item must be decorated, and even which
specific materials the item must be made out of. This lets you create layouts
with a consistent color, if that is part of your design.
If you just care about the heat sensitivity of the building, you can set the
building to be fire- or magma-proof in the placement UI screen or in any item
filter screen, and the restriction will apply to all building items. This makes it
very easy to create magma-safe pump stacks, for example.
Buildingplan works very well in conjuction with other design tools like
`gui/quickfort`, which allow you to apply a building layout from a blueprint. You
can apply very large, complicated layouts, and the buildings will simply be built
when your dwarves get around to producing the needed materials. If you set filters
in the buildingplan UI before applying the blueprint, the filters will be applied
to the blueprint buildings, just as if you had planned them from the buildingplan
placement UI.
One way to integrate buildingplan into your gameplay is to create manager
workorders to ensure you always have a few blocks/doors/beds/etc. available. You
can then place as many of each building as you like. Produced items will be used
to build the planned buildings as they are produced, with minimal space dedicated
to stockpiles. The DFHack `orders` library can help with setting up these manager
workorders for you.
If you do not wish to use the ``buildingplan`` interface, you can turn off the
``buildingplan.planner`` overlay in `gui/overlay`. You should not disable the
``buildingplan`` service entirely in `gui/control-panel` since then existing
planned buildings in loaded forts will stop functioning.
Usage Usage
----- -----
:: ::
enable buildingplan buildingplan [status]
buildingplan set buildingplan set <setting> (true|false)
buildingplan set <setting> true|false
Examples
--------
Running ``buildingplan set`` without parameters displays the current settings. ``buildingplan``
Print a report of current settings, which kinds of buildings are planned,
and what kinds of materials the buildings are waiting for.
.. _buildingplan-settings: .. _buildingplan-settings:
Global settings Global settings
--------------- ---------------
The buildingplan plugin has global settings that can be set from the UI The buildingplan plugin has several global settings that affect what materials
(:kbd:`G` from any building placement screen, for example: can be chosen when attaching items to planned buildings:
:kbd:`b`:kbd:`a`:kbd:`G`). These settings can also be set via the
``buildingplan set`` command. The available settings are:
``all_enabled`` (default: false)
Enable planning mode for all building types.
``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false) ``blocks``, ``boulders``, ``logs``, ``bars`` (defaults: true, true, true, false)
Allow blocks, boulders, logs, or bars to be matched for generic "building Allow blocks, boulders, logs, or bars to be matched for generic "building
material" items. material" items.
``quickfort_mode`` (default: false)
Enable compatibility mode for the legacy Python Quickfort (this setting is
not required for DFHack `quickfort`)
The settings for ``blocks``, ``boulders``, ``logs``, and ``bars`` are saved with These settings are saved with your fort, so you only have to set them once and
your fort, so you only have to set them once and they will be persisted in your they will be persisted in your save.
save.
If you normally embark with some blocks on hand for early workshops, you might If you normally embark with some blocks on hand for early workshops, you might
want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to want to add this line to your ``dfhack-config/init/onMapLoad.init`` file to
always configure buildingplan to just use blocks for buildings and always configure `buildingplan` to just use blocks for buildings and
constructions:: constructions::
on-new-fortress buildingplan set boulders false; buildingplan set logs false on-new-fortress buildingplan set boulders false
on-new-fortress buildingplan set logs false
.. _buildingplan-filters:
Item filtering
--------------
While placing a building, you can set filters for what materials you want the
building made out of, what quality you want the component items to be, and
whether you want the items to be decorated.
If a building type takes more than one item to construct, use
:kbd:`Ctrl`:kbd:`Left` and :kbd:`Ctrl`:kbd:`Right` to select the item that you
want to set filters for. Any filters that you set will be used for all buildings
of the selected type placed from that point onward (until you set a new filter
or clear the current one). Buildings placed before the filters were changed will
keep the filter values that were set when the building was placed.
For example, you can be sure that all your constructed walls are the same color
by setting a filter to accept only certain types of stone.
Quickfort mode
--------------
If you use the external Python Quickfort to apply building blueprints instead of
the native DFHack `quickfort` script, you must enable Quickfort mode. This
temporarily enables buildingplan for all building types and adds an extra blank
screen after every building placement. This "dummy" screen is needed for Python
Quickfort to interact successfully with Dwarf Fortress.
Note that Quickfort mode is only for compatibility with the legacy Python
Quickfort. The DFHack `quickfort` script does not need this Quickfort mode to be
enabled. The `quickfort` script will successfully integrate with buildingplan as
long as the buildingplan plugin itself is enabled.

@ -3,7 +3,7 @@ channel-safely
.. dfhack-tool:: .. dfhack-tool::
:summary: Auto-manage channel designations to keep dwarves safe. :summary: Auto-manage channel designations to keep dwarves safe.
:tags: fort auto :tags: untested fort auto
Multi-level channel projects can be dangerous, and managing the safety of your Multi-level channel projects can be dangerous, and managing the safety of your
dwarves throughout the completion of such projects can be difficult and time dwarves throughout the completion of such projects can be difficult and time

@ -3,7 +3,7 @@ strangemood
.. dfhack-tool:: .. dfhack-tool::
:summary: Trigger a strange mood. :summary: Trigger a strange mood.
:tags: untested fort armok units :tags: fort armok units
Usage Usage
----- -----

@ -137,6 +137,7 @@ set(MODULE_HEADERS
include/modules/MapCache.h include/modules/MapCache.h
include/modules/Maps.h include/modules/Maps.h
include/modules/Materials.h include/modules/Materials.h
include/modules/Military.h
include/modules/Once.h include/modules/Once.h
include/modules/Persistence.h include/modules/Persistence.h
include/modules/Random.h include/modules/Random.h
@ -164,6 +165,7 @@ set(MODULE_SOURCES
modules/MapCache.cpp modules/MapCache.cpp
modules/Maps.cpp modules/Maps.cpp
modules/Materials.cpp modules/Materials.cpp
modules/Military.cpp
modules/Once.cpp modules/Once.cpp
modules/Persistence.cpp modules/Persistence.cpp
modules/Random.cpp modules/Random.cpp

@ -35,7 +35,6 @@ distribution.
#include <forward_list> #include <forward_list>
#include <type_traits> #include <type_traits>
#include <cstdarg> #include <cstdarg>
using namespace std;
#include "Error.h" #include "Error.h"
#include "MemAccess.h" #include "MemAccess.h"
@ -61,8 +60,6 @@ using namespace std;
#include "LuaTools.h" #include "LuaTools.h"
#include "DFHackVersion.h" #include "DFHackVersion.h"
#include "MiscUtils.h"
using namespace DFHack; using namespace DFHack;
#include "df/plotinfost.h" #include "df/plotinfost.h"
@ -99,7 +96,7 @@ using df::global::world;
// FIXME: A lot of code in one file, all doing different things... there's something fishy about it. // FIXME: A lot of code in one file, all doing different things... there's something fishy about it.
static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL); static bool parseKeySpec(std::string keyspec, int *psym, int *pmod, std::string *pfocus = NULL);
size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>& prefix, const std::string& folder); size_t loadScriptFiles(Core* core, color_ostream& out, const std::vector<std::string>& prefix, const std::string& folder);
namespace DFHack { namespace DFHack {
@ -160,9 +157,9 @@ struct CommandDepthCounter
}; };
thread_local int CommandDepthCounter::depth = 0; thread_local int CommandDepthCounter::depth = 0;
void Core::cheap_tokenise(string const& input, vector<string> &output) void Core::cheap_tokenise(std::string const& input, std::vector<std::string>& output)
{ {
string *cur = NULL; std::string *cur = NULL;
size_t i = 0; size_t i = 0;
// Check the first non-space character // Check the first non-space character
@ -234,7 +231,7 @@ void fHKthread(void * iodata)
PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr; PluginManager * plug_mgr = ((IODATA*) iodata)->plug_mgr;
if(plug_mgr == 0 || core == 0) if(plug_mgr == 0 || core == 0)
{ {
cerr << "Hotkey thread has croaked." << endl; std::cerr << "Hotkey thread has croaked." << std::endl;
return; return;
} }
bool keep_going = true; bool keep_going = true;
@ -256,10 +253,10 @@ void fHKthread(void * iodata)
struct sortable struct sortable
{ {
bool recolor; bool recolor;
string name; std::string name;
string description; std::string description;
//FIXME: Nuke when MSVC stops failing at being C++11 compliant //FIXME: Nuke when MSVC stops failing at being C++11 compliant
sortable(bool recolor_,const string& name_,const string & description_): recolor(recolor_), name(name_), description(description_){}; sortable(bool recolor_,const std::string& name_,const std::string & description_): recolor(recolor_), name(name_), description(description_){};
bool operator <(const sortable & rhs) const bool operator <(const sortable & rhs) const
{ {
if( name < rhs.name ) if( name < rhs.name )
@ -268,9 +265,9 @@ struct sortable
}; };
}; };
static string dfhack_version_desc() static std::string dfhack_version_desc()
{ {
stringstream s; std::stringstream s;
s << Version::dfhack_version() << " "; s << Version::dfhack_version() << " ";
if (Version::is_release()) if (Version::is_release())
s << "(release)"; s << "(release)";
@ -284,11 +281,11 @@ static string dfhack_version_desc()
namespace { namespace {
struct ScriptArgs { struct ScriptArgs {
const string *pcmd; const std::string *pcmd;
vector<string> *pargs; std::vector<std::string> *pargs;
}; };
struct ScriptEnableState { struct ScriptEnableState {
const string *pcmd; const std::string *pcmd;
bool pstate; bool pstate;
}; };
} }
@ -307,7 +304,7 @@ static bool init_run_script(color_ostream &out, lua_State *state, void *info)
return true; return true;
} }
static command_result runLuaScript(color_ostream &out, std::string name, vector<string> &args) static command_result runLuaScript(color_ostream &out, std::string name, std::vector<std::string> &args)
{ {
ScriptArgs data; ScriptArgs data;
data.pcmd = &name; data.pcmd = &name;
@ -346,25 +343,25 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
{ {
if (!command.empty()) if (!command.empty())
{ {
vector <string> parts; std::vector <std::string> parts;
Core::cheap_tokenise(command,parts); Core::cheap_tokenise(command,parts);
if(parts.size() == 0) if(parts.size() == 0)
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
string first = parts[0]; std::string first = parts[0];
parts.erase(parts.begin()); parts.erase(parts.begin());
if (first[0] == '#') if (first[0] == '#')
return CR_OK; return CR_OK;
cerr << "Invoking: " << command << endl; std::cerr << "Invoking: " << command << std::endl;
return runCommand(out, first, parts); return runCommand(out, first, parts);
} }
else else
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
} }
bool is_builtin(color_ostream &con, const string &command) { bool is_builtin(color_ostream &con, const std::string &command) {
CoreSuspender suspend; CoreSuspender suspend;
auto L = Lua::Core::State; auto L = Lua::Core::State;
Lua::StackUnwinder top(L); Lua::StackUnwinder top(L);
@ -385,7 +382,7 @@ bool is_builtin(color_ostream &con, const string &command) {
return lua_toboolean(L, -1); return lua_toboolean(L, -1);
} }
void get_commands(color_ostream &con, vector<string> &commands) { void get_commands(color_ostream &con, std::vector<std::string> &commands) {
CoreSuspender suspend; CoreSuspender suspend;
auto L = Lua::Core::State; auto L = Lua::Core::State;
Lua::StackUnwinder top(L); Lua::StackUnwinder top(L);
@ -431,10 +428,10 @@ static bool try_autocomplete(color_ostream &con, const std::string &first, std::
return false; return false;
} }
bool Core::addScriptPath(string path, bool search_before) bool Core::addScriptPath(std::string path, bool search_before)
{ {
lock_guard<mutex> lock(script_path_mutex); std::lock_guard<std::mutex> lock(script_path_mutex);
vector<string> &vec = script_paths[search_before ? 0 : 1]; std::vector<std::string> &vec = script_paths[search_before ? 0 : 1];
if (std::find(vec.begin(), vec.end(), path) != vec.end()) if (std::find(vec.begin(), vec.end(), path) != vec.end())
return false; return false;
if (!Filesystem::isdir(path)) if (!Filesystem::isdir(path))
@ -443,13 +440,13 @@ bool Core::addScriptPath(string path, bool search_before)
return true; return true;
} }
bool Core::removeScriptPath(string path) bool Core::removeScriptPath(std::string path)
{ {
lock_guard<mutex> lock(script_path_mutex); std::lock_guard<std::mutex> lock(script_path_mutex);
bool found = false; bool found = false;
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
vector<string> &vec = script_paths[i]; std::vector<std::string> &vec = script_paths[i];
while (1) while (1)
{ {
auto it = std::find(vec.begin(), vec.end(), path); auto it = std::find(vec.begin(), vec.end(), path);
@ -464,14 +461,14 @@ bool Core::removeScriptPath(string path)
void Core::getScriptPaths(std::vector<std::string> *dest) void Core::getScriptPaths(std::vector<std::string> *dest)
{ {
lock_guard<mutex> lock(script_path_mutex); std::lock_guard<std::mutex> lock(script_path_mutex);
dest->clear(); dest->clear();
string df_path = this->p->getPath() + "/"; std::string df_path = this->p->getPath() + "/";
for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it) for (auto it = script_paths[0].begin(); it != script_paths[0].end(); ++it)
dest->push_back(*it); dest->push_back(*it);
dest->push_back(df_path + CONFIG_PATH + "scripts"); dest->push_back(df_path + CONFIG_PATH + "scripts");
if (df::global::world && isWorldLoaded()) { if (df::global::world && isWorldLoaded()) {
string save = World::ReadWorldFolder(); std::string save = World::ReadWorldFolder();
if (save.size()) if (save.size())
dest->push_back(df_path + "/save/" + save + "/scripts"); dest->push_back(df_path + "/save/" + save + "/scripts");
} }
@ -481,13 +478,13 @@ void Core::getScriptPaths(std::vector<std::string> *dest)
} }
string Core::findScript(string name) std::string Core::findScript(std::string name)
{ {
vector<string> paths; std::vector<std::string> paths;
getScriptPaths(&paths); getScriptPaths(&paths);
for (auto it = paths.begin(); it != paths.end(); ++it) for (auto it = paths.begin(); it != paths.end(); ++it)
{ {
string path = *it + "/" + name; std::string path = *it + "/" + name;
if (Filesystem::isfile(path)) if (Filesystem::isfile(path))
return path; return path;
} }
@ -497,7 +494,7 @@ string Core::findScript(string name)
bool loadScriptPaths(color_ostream &out, bool silent = false) bool loadScriptPaths(color_ostream &out, bool silent = false)
{ {
using namespace std; using namespace std;
string filename(CONFIG_PATH + "script-paths.txt"); std::string filename(CONFIG_PATH + "script-paths.txt");
ifstream file(filename); ifstream file(filename);
if (!file) if (!file)
{ {
@ -505,7 +502,7 @@ bool loadScriptPaths(color_ostream &out, bool silent = false)
out.printerr("Could not load %s\n", filename.c_str()); out.printerr("Could not load %s\n", filename.c_str());
return false; return false;
} }
string raw; std::string raw;
int line = 0; int line = 0;
while (getline(file, raw)) while (getline(file, raw))
{ {
@ -516,7 +513,7 @@ bool loadScriptPaths(color_ostream &out, bool silent = false)
if (!(ss >> ch) || ch == '#') if (!(ss >> ch) || ch == '#')
continue; continue;
ss >> ws; // discard whitespace ss >> ws; // discard whitespace
string path; std::string path;
getline(ss, path); getline(ss, path);
if (ch == '+' || ch == '-') if (ch == '+' || ch == '-')
{ {
@ -565,7 +562,7 @@ static std::string sc_event_name (state_change_event id) {
return "SC_UNKNOWN"; return "SC_UNKNOWN";
} }
void help_helper(color_ostream &con, const string &entry_name) { void help_helper(color_ostream &con, const std::string &entry_name) {
CoreSuspender suspend; CoreSuspender suspend;
auto L = Lua::Core::State; auto L = Lua::Core::State;
Lua::StackUnwinder top(L); Lua::StackUnwinder top(L);
@ -583,7 +580,7 @@ void help_helper(color_ostream &con, const string &entry_name) {
} }
} }
void tags_helper(color_ostream &con, const string &tag) { void tags_helper(color_ostream &con, const std::string &tag) {
CoreSuspender suspend; CoreSuspender suspend;
auto L = Lua::Core::State; auto L = Lua::Core::State;
Lua::StackUnwinder top(L); Lua::StackUnwinder top(L);
@ -601,11 +598,11 @@ void tags_helper(color_ostream &con, const string &tag) {
} }
} }
void ls_helper(color_ostream &con, const vector<string> &params) { void ls_helper(color_ostream &con, const std::vector<std::string> &params) {
vector<string> filter; std::vector<std::string> filter;
bool skip_tags = false; bool skip_tags = false;
bool show_dev_commands = false; bool show_dev_commands = false;
string exclude_strs = ""; std::string exclude_strs = "";
bool in_exclude = false; bool in_exclude = false;
for (auto str : params) { for (auto str : params) {
@ -641,7 +638,7 @@ void ls_helper(color_ostream &con, const vector<string> &params) {
} }
} }
command_result Core::runCommand(color_ostream &con, const std::string &first_, vector<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first_, std::vector<std::string> &parts)
{ {
std::string first = first_; std::string first = first_;
CommandDepthCounter counter; CommandDepthCounter counter;
@ -717,7 +714,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
if (p->size() && (*p)[0] == '-') if (p->size() && (*p)[0] == '-')
{ {
if (p->find('a') != string::npos) if (p->find('a') != std::string::npos)
all = true; all = true;
} }
} }
@ -876,7 +873,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
con << parts[0]; con << parts[0];
bool builtin = is_builtin(con, parts[0]); bool builtin = is_builtin(con, parts[0]);
string lua_path = findScript(parts[0] + ".lua"); std::string lua_path = findScript(parts[0] + ".lua");
Plugin *plug = plug_mgr->getPluginByCommand(parts[0]); Plugin *plug = plug_mgr->getPluginByCommand(parts[0]);
if (builtin) if (builtin)
{ {
@ -933,31 +930,31 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
std::vector<std::string> list = ListKeyBindings(parts[1]); std::vector<std::string> list = ListKeyBindings(parts[1]);
if (list.empty()) if (list.empty())
con << "No bindings." << endl; con << "No bindings." << std::endl;
for (size_t i = 0; i < list.size(); i++) for (size_t i = 0; i < list.size(); i++)
con << " " << list[i] << endl; con << " " << list[i] << std::endl;
} }
else else
{ {
con << "Usage:" << endl con << "Usage:" << std::endl
<< " keybinding list <key>" << endl << " keybinding list <key>" << std::endl
<< " keybinding clear <key>[@context]..." << endl << " keybinding clear <key>[@context]..." << std::endl
<< " keybinding set <key>[@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding set <key>[@context] \"cmdline\" \"cmdline\"..." << std::endl
<< " keybinding add <key>[@context] \"cmdline\" \"cmdline\"..." << endl << " keybinding add <key>[@context] \"cmdline\" \"cmdline\"..." << std::endl
<< "Later adds, and earlier items within one command have priority." << endl << "Later adds, and earlier items within one command have priority." << std::endl
<< "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << endl << "Supported keys: [Ctrl-][Alt-][Shift-](A-Z, 0-9, F1-F12, `, or Enter)." << std::endl
<< "Context may be used to limit the scope of the binding, by" << endl << "Context may be used to limit the scope of the binding, by" << std::endl
<< "requiring the current context to have a certain prefix." << endl << "requiring the current context to have a certain prefix." << std::endl
<< "Current UI context is: " << endl << "Current UI context is: " << std::endl
<< join_strings("\n", Gui::getCurFocus(true)) << endl; << join_strings("\n", Gui::getCurFocus(true)) << std::endl;
} }
} }
else if (first == "alias") else if (first == "alias")
{ {
if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace")) if (parts.size() >= 3 && (parts[0] == "add" || parts[0] == "replace"))
{ {
const string &name = parts[1]; const std::string &name = parts[1];
vector<string> cmd(parts.begin() + 2, parts.end()); std::vector<std::string> cmd(parts.begin() + 2, parts.end());
if (!AddAlias(name, cmd, parts[0] == "replace")) if (!AddAlias(name, cmd, parts[0] == "replace"))
{ {
con.printerr("Could not add alias %s - already exists\n", name.c_str()); con.printerr("Could not add alias %s - already exists\n", name.c_str());
@ -977,15 +974,15 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
auto aliases = ListAliases(); auto aliases = ListAliases();
for (auto p : aliases) for (auto p : aliases)
{ {
con << p.first << ": " << join_strings(" ", p.second) << endl; con << p.first << ": " << join_strings(" ", p.second) << std::endl;
} }
} }
else else
{ {
con << "Usage: " << endl con << "Usage: " << std::endl
<< " alias add|replace <name> <command...>" << endl << " alias add|replace <name> <command...>" << std::endl
<< " alias delete|clear <name> <command...>" << endl << " alias delete|clear <name> <command...>" << std::endl
<< " alias list" << endl; << " alias list" << std::endl;
} }
} }
else if (first == "fpause") else if (first == "fpause")
@ -1038,8 +1035,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
else else
{ {
con << "Usage:" << endl con << "Usage:" << std::endl
<< " script <filename>" << endl; << " script <filename>" << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
@ -1065,13 +1062,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
if (parts.empty() || parts[0] == "help" || parts[0] == "?") if (parts.empty() || parts[0] == "help" || parts[0] == "?")
{ {
con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl;
con << "Valid event names (SC_ prefix is optional):" << endl; con << "Valid event names (SC_ prefix is optional):" << std::endl;
for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++) for (int i = SC_WORLD_LOADED; i <= SC_UNPAUSED; i++)
{ {
std::string name = sc_event_name((state_change_event)i); std::string name = sc_event_name((state_change_event)i);
if (name != "SC_UNKNOWN") if (name != "SC_UNKNOWN")
con << " " << name << endl; con << " " << name << std::endl;
} }
return CR_OK; return CR_OK;
} }
@ -1081,7 +1078,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
parts.push_back(""); parts.push_back("");
if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN) if (parts[1].size() && sc_event_id(parts[1]) == SC_UNKNOWN)
{ {
con << "Unrecognized event name: " << parts[1] << endl; con << "Unrecognized event name: " << parts[1] << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it) for (auto it = state_change_scripts.begin(); it != state_change_scripts.end(); ++it)
@ -1100,13 +1097,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save"))
{ {
con << "Usage: sc-script add EVENT path-to-script [-save]" << endl; con << "Usage: sc-script add EVENT path-to-script [-save]" << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
state_change_event evt = sc_event_id(parts[1]); state_change_event evt = sc_event_id(parts[1]);
if (evt == SC_UNKNOWN) if (evt == SC_UNKNOWN)
{ {
con << "Unrecognized event: " << parts[1] << endl; con << "Unrecognized event: " << parts[1] << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); bool save_specific = (parts.size() >= 4 && parts[3] == "-save");
@ -1115,7 +1112,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
if (script == *it) if (script == *it)
{ {
con << "Script already registered" << endl; con << "Script already registered" << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
} }
@ -1126,13 +1123,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
{ {
if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save")) if (parts.size() < 3 || (parts.size() >= 4 && parts[3] != "-save"))
{ {
con << "Usage: sc-script remove EVENT path-to-script [-save]" << endl; con << "Usage: sc-script remove EVENT path-to-script [-save]" << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
state_change_event evt = sc_event_id(parts[1]); state_change_event evt = sc_event_id(parts[1]);
if (evt == SC_UNKNOWN) if (evt == SC_UNKNOWN)
{ {
con << "Unrecognized event: " << parts[1] << endl; con << "Unrecognized event: " << parts[1] << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
bool save_specific = (parts.size() >= 4 && parts[3] == "-save"); bool save_specific = (parts.size() >= 4 && parts[3] == "-save");
@ -1145,13 +1142,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
else else
{ {
con << "Unrecognized script" << endl; con << "Unrecognized script" << std::endl;
return CR_FAILURE; return CR_FAILURE;
} }
} }
else else
{ {
con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << endl; con << "Usage: sc-script add|remove|list|help SC_EVENT [path-to-script] [...]" << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
@ -1173,13 +1170,13 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
if (!svc) if (!svc)
continue; continue;
file << "// Plugin: " << plug->getName() << endl; file << "// Plugin: " << plug->getName() << std::endl;
svc->dumpMethods(file); svc->dumpMethods(file);
} }
} }
else else
{ {
con << "Usage: devel/dump-rpc \"filename\"" << endl; con << "Usage: devel/dump-rpc \"filename\"" << std::endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
} }
@ -1196,8 +1193,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
} }
else if (res == CR_NOT_IMPLEMENTED) else if (res == CR_NOT_IMPLEMENTED)
{ {
string completed; std::string completed;
string filename = findScript(first + ".lua"); std::string filename = findScript(first + ".lua");
bool lua = filename != ""; bool lua = filename != "";
if ( !lua ) { if ( !lua ) {
filename = findScript(first + ".rb"); filename = findScript(first + ".rb");
@ -1234,22 +1231,22 @@ command_result Core::runCommand(color_ostream &con, const std::string &first_, v
return CR_OK; return CR_OK;
} }
bool Core::loadScriptFile(color_ostream &out, string fname, bool silent) bool Core::loadScriptFile(color_ostream &out, std::string fname, bool silent)
{ {
if(!silent) { if(!silent) {
INFO(script,out) << "Loading script: " << fname << std::endl; INFO(script,out) << "Loading script: " << fname << std::endl;
cerr << "Loading script: " << fname << std::endl; std::cerr << "Loading script: " << fname << std::endl;
} }
ifstream script(fname.c_str()); std::ifstream script(fname.c_str());
if ( !script.good() ) if ( !script.good() )
{ {
if(!silent) if(!silent)
out.printerr("Error loading script: %s\n", fname.c_str()); out.printerr("Error loading script: %s\n", fname.c_str());
return false; return false;
} }
string command; std::string command;
while(script.good()) { while(script.good()) {
string temp; std::string temp;
getline(script,temp); getline(script,temp);
bool doMore = false; bool doMore = false;
if ( temp.length() > 0 ) { if ( temp.length() > 0 ) {
@ -1333,18 +1330,18 @@ void fIOthread(void * iodata)
while (true) while (true)
{ {
string command = ""; std::string command = "";
int ret; int ret;
while ((ret = con.lineedit("[DFHack]# ",command, main_history)) while ((ret = con.lineedit("[DFHack]# ",command, main_history))
== Console::RETRY); == Console::RETRY);
if(ret == Console::SHUTDOWN) if(ret == Console::SHUTDOWN)
{ {
cerr << "Console is shutting down properly." << endl; std::cerr << "Console is shutting down properly." << std::endl;
return; return;
} }
else if(ret == Console::FAILURE) else if(ret == Console::FAILURE)
{ {
cerr << "Console caught an unspecified error." << endl; std::cerr << "Console caught an unspecified error." << std::endl;
continue; continue;
} }
else if(ret) else if(ret)
@ -1405,7 +1402,7 @@ Core::Core() :
void Core::fatal (std::string output) void Core::fatal (std::string output)
{ {
errorstate = true; errorstate = true;
stringstream out; std::stringstream out;
out << output ; out << output ;
if (output[output.size() - 1] != '\n') if (output[output.size() - 1] != '\n')
out << '\n'; out << '\n';
@ -1421,7 +1418,7 @@ void Core::fatal (std::string output)
out << "Check file stderr.log for details\n"; out << "Check file stderr.log for details\n";
MessageBox(0,out.str().c_str(),"DFHack error!", MB_OK | MB_ICONERROR); MessageBox(0,out.str().c_str(),"DFHack error!", MB_OK | MB_ICONERROR);
#else #else
cout << "DFHack fatal error: " << out.str() << std::endl; std::cout << "DFHack fatal error: " << out.str() << std::endl;
#endif #endif
bool is_headless = bool(getenv("DFHACK_HEADLESS")); bool is_headless = bool(getenv("DFHACK_HEADLESS"));
@ -1459,16 +1456,16 @@ bool Core::Init()
// this is handled as appropriate in Console-posix.cpp // this is handled as appropriate in Console-posix.cpp
fprintf(stdout, "dfhack: redirecting stdout to stdout.log (again)\n"); fprintf(stdout, "dfhack: redirecting stdout to stdout.log (again)\n");
if (!freopen("stdout.log", "w", stdout)) if (!freopen("stdout.log", "w", stdout))
cerr << "Could not redirect stdout to stdout.log" << endl; std::cerr << "Could not redirect stdout to stdout.log" << std::endl;
#endif #endif
fprintf(stderr, "dfhack: redirecting stderr to stderr.log\n"); fprintf(stderr, "dfhack: redirecting stderr to stderr.log\n");
if (!freopen("stderr.log", "w", stderr)) if (!freopen("stderr.log", "w", stderr))
cerr << "Could not redirect stderr to stderr.log" << endl; std::cerr << "Could not redirect stderr to stderr.log" << std::endl;
Filesystem::init(); Filesystem::init();
cerr << "DFHack build: " << Version::git_description() << "\n" std::cerr << "DFHack build: " << Version::git_description() << "\n"
<< "Starting with working directory: " << Filesystem::getcwd() << endl; << "Starting with working directory: " << Filesystem::getcwd() << std::endl;
// find out what we are... // find out what we are...
#ifdef LINUX_BUILD #ifdef LINUX_BUILD
@ -1477,7 +1474,7 @@ bool Core::Init()
const char * path = "hack\\symbols.xml"; const char * path = "hack\\symbols.xml";
#endif #endif
auto local_vif = dts::make_unique<DFHack::VersionInfoFactory>(); auto local_vif = dts::make_unique<DFHack::VersionInfoFactory>();
cerr << "Identifying DF version.\n"; std::cerr << "Identifying DF version.\n";
try try
{ {
local_vif->loadFile(path); local_vif->loadFile(path);
@ -1518,8 +1515,8 @@ bool Core::Init()
"recompile.\n" "recompile.\n"
"More details can be found in stderr.log in this folder.\n" "More details can be found in stderr.log in this folder.\n"
); );
cout << msg << endl; std::cout << msg << std::endl;
cerr << msg << endl; std::cerr << msg << std::endl;
fatal("Not a known DF version - XML version mismatch (see console or stderr.log)"); fatal("Not a known DF version - XML version mismatch (see console or stderr.log)");
} }
else else
@ -1529,13 +1526,13 @@ bool Core::Init()
errorstate = true; errorstate = true;
return false; return false;
} }
cerr << "Version: " << vinfo->getVersion() << endl; std::cerr << "Version: " << vinfo->getVersion() << std::endl;
p = std::move(local_p); p = std::move(local_p);
// Init global object pointers // Init global object pointers
df::global::InitGlobals(); df::global::InitGlobals();
cerr << "Initializing Console.\n"; std::cerr << "Initializing Console.\n";
// init the console. // init the console.
bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT)); bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT));
bool is_headless = bool(getenv("DFHACK_HEADLESS")); bool is_headless = bool(getenv("DFHACK_HEADLESS"));
@ -1551,29 +1548,29 @@ bool Core::Init()
} }
else else
{ {
cerr << "endwin(): bind failed" << endl; std::cerr << "endwin(): bind failed" << std::endl;
} }
} }
else else
{ {
cerr << "Headless mode requires PRINT_MODE:TEXT" << endl; std::cerr << "Headless mode requires PRINT_MODE:TEXT" << std::endl;
} }
#else #else
cerr << "Headless mode not supported on Windows" << endl; std::cerr << "Headless mode not supported on Windows" << std::endl;
#endif #endif
} }
if (is_text_mode && !is_headless) if (is_text_mode && !is_headless)
{ {
cerr << "Console is not available. Use dfhack-run to send commands.\n"; std::cerr << "Console is not available. Use dfhack-run to send commands.\n";
if (!is_text_mode) if (!is_text_mode)
{ {
cout << "Console disabled.\n"; std::cout << "Console disabled.\n";
} }
} }
else if(con.init(false)) else if(con.init(false))
cerr << "Console is running.\n"; std::cerr << "Console is running.\n";
else else
cerr << "Console has failed to initialize!\n"; std::cerr << "Console has failed to initialize!\n";
/* /*
// dump offsets to a file // dump offsets to a file
std::ofstream dump("offsets.log"); std::ofstream dump("offsets.log");
@ -1642,19 +1639,19 @@ bool Core::Init()
return false; return false;
} }
cerr << "Binding to SDL.\n"; std::cerr << "Binding to SDL.\n";
if (!DFSDL::init(con)) { if (!DFSDL::init(con)) {
fatal("cannot bind SDL libraries"); fatal("cannot bind SDL libraries");
return false; return false;
} }
cerr << "Initializing textures.\n"; std::cerr << "Initializing textures.\n";
Textures::init(con); Textures::init(con);
// create mutex for syncing with interactive tasks // create mutex for syncing with interactive tasks
cerr << "Initializing plugins.\n"; std::cerr << "Initializing plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(); plug_mgr->init();
cerr << "Starting the TCP listener.\n"; std::cerr << "Starting the TCP listener.\n";
auto listen = ServerMain::listen(RemoteClient::GetDefaultPort()); auto listen = ServerMain::listen(RemoteClient::GetDefaultPort());
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
@ -1662,7 +1659,7 @@ bool Core::Init()
if (!is_text_mode || is_headless) if (!is_text_mode || is_headless)
{ {
cerr << "Starting IO thread.\n"; std::cerr << "Starting IO thread.\n";
// create IO thread // create IO thread
d->iothread = std::thread{fIOthread, (void*)temp}; d->iothread = std::thread{fIOthread, (void*)temp};
} }
@ -1672,19 +1669,19 @@ bool Core::Init()
d->iothread = std::thread{fInitthread, (void*)temp}; d->iothread = std::thread{fInitthread, (void*)temp};
} }
cerr << "Starting DF input capture thread.\n"; std::cerr << "Starting DF input capture thread.\n";
// set up hotkey capture // set up hotkey capture
d->hotkeythread = std::thread(fHKthread, (void *) temp); d->hotkeythread = std::thread(fHKthread, (void *) temp);
started = true; started = true;
modstate = 0; modstate = 0;
if (!listen.get()) if (!listen.get())
cerr << "TCP listen failed.\n"; std::cerr << "TCP listen failed.\n";
if (df::global::game) if (df::global::game)
{ {
vector<string> args; std::vector<std::string> args;
const string & raw = df::global::game->command_line.original; const std::string & raw = df::global::game->command_line.original;
size_t offset = 0; size_t offset = 0;
while (offset < raw.size()) while (offset < raw.size())
{ {
@ -1698,7 +1695,7 @@ bool Core::Init()
else else
{ {
size_t next = raw.find(" ", offset); size_t next = raw.find(" ", offset);
if (next == string::npos) if (next == std::string::npos)
{ {
args.push_back(raw.substr(offset)); args.push_back(raw.substr(offset));
offset = raw.size(); offset = raw.size();
@ -1712,12 +1709,12 @@ bool Core::Init()
} }
for (auto it = args.begin(); it != args.end(); ) for (auto it = args.begin(); it != args.end(); )
{ {
const string & first = *it; const std::string & first = *it;
if (first.length() > 0 && first[0] == '+') if (first.length() > 0 && first[0] == '+')
{ {
vector<string> cmd; std::vector<std::string> cmd;
for (it++; it != args.end(); it++) { for (it++; it != args.end(); it++) {
const string & arg = *it; const std::string & arg = *it;
if (arg.length() > 0 && arg[0] == '+') if (arg.length() > 0 && arg[0] == '+')
{ {
break; break;
@ -1727,12 +1724,12 @@ bool Core::Init()
if (runCommand(con, first.substr(1), cmd) != CR_OK) if (runCommand(con, first.substr(1), cmd) != CR_OK)
{ {
cerr << "Error running command: " << first.substr(1); std::cerr << "Error running command: " << first.substr(1);
for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++) for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++)
{ {
cerr << " \"" << *it2 << "\""; std::cerr << " \"" << *it2 << "\"";
} }
cerr << "\n"; std::cerr << "\n";
} }
} }
else else
@ -1742,7 +1739,7 @@ bool Core::Init()
} }
} }
cerr << "DFHack is running.\n"; std::cerr << "DFHack is running.\n";
onStateChange(con, SC_CORE_INITIALIZED); onStateChange(con, SC_CORE_INITIALIZED);
@ -1761,7 +1758,7 @@ bool Core::setHotkeyCmd( std::string cmd )
/// removes the hotkey command and gives it to the caller thread /// removes the hotkey command and gives it to the caller thread
std::string Core::getHotkeyCmd( bool &keep_going ) std::string Core::getHotkeyCmd( bool &keep_going )
{ {
string returner; std::string returner;
std::unique_lock<std::mutex> lock(HotkeyMutex); std::unique_lock<std::mutex> lock(HotkeyMutex);
HotkeyCond.wait(lock, [this]() -> bool {return this->hotkey_set;}); HotkeyCond.wait(lock, [this]() -> bool {return this->hotkey_set;});
if (hotkey_set == SHUTDOWN) { if (hotkey_set == SHUTDOWN) {
@ -1986,22 +1983,22 @@ void getFilesWithPrefixAndSuffix(const std::string& folder, const std::string& p
return; return;
} }
size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>& prefix, const std::string& folder) { size_t loadScriptFiles(Core* core, color_ostream& out, const std::vector<std::string>& prefix, const std::string& folder) {
static const string suffix = ".init"; static const std::string suffix = ".init";
vector<string> scriptFiles; std::vector<std::string> scriptFiles;
for ( size_t a = 0; a < prefix.size(); a++ ) { for ( size_t a = 0; a < prefix.size(); a++ ) {
getFilesWithPrefixAndSuffix(folder, prefix[a], ".init", scriptFiles); getFilesWithPrefixAndSuffix(folder, prefix[a], ".init", scriptFiles);
} }
std::sort(scriptFiles.begin(), scriptFiles.end(), std::sort(scriptFiles.begin(), scriptFiles.end(),
[&](const string &a, const string &b) { [&](const std::string &a, const std::string &b) {
string a_base = a.substr(0, a.size() - suffix.size()); std::string a_base = a.substr(0, a.size() - suffix.size());
string b_base = b.substr(0, b.size() - suffix.size()); std::string b_base = b.substr(0, b.size() - suffix.size());
return a_base < b_base; return a_base < b_base;
}); });
size_t result = 0; size_t result = 0;
for ( size_t a = 0; a < scriptFiles.size(); a++ ) { for ( size_t a = 0; a < scriptFiles.size(); a++ ) {
result++; result++;
string path = ""; std::string path = "";
if (folder != ".") if (folder != ".")
path = folder + "/"; path = folder + "/";
core->loadScriptFile(out, path + scriptFiles[a], false); core->loadScriptFile(out, path + scriptFiles[a], false);
@ -2012,10 +2009,10 @@ size_t loadScriptFiles(Core* core, color_ostream& out, const vector<std::string>
namespace DFHack { namespace DFHack {
namespace X { namespace X {
typedef state_change_event Key; typedef state_change_event Key;
typedef vector<string> Val; typedef std::vector<std::string> Val;
typedef pair<Key,Val> Entry; typedef std::pair<Key,Val> Entry;
typedef vector<Entry> EntryVector; typedef std::vector<Entry> EntryVector;
typedef map<Key,Val> InitVariationTable; typedef std::map<Key,Val> InitVariationTable;
EntryVector computeInitVariationTable(void* none, ...) { EntryVector computeInitVariationTable(void* none, ...) {
va_list list; va_list list;
@ -2030,7 +2027,7 @@ namespace DFHack {
const char *v = va_arg(list, const char *); const char *v = va_arg(list, const char *);
if (!v || !v[0]) if (!v || !v[0])
break; break;
val.push_back(string(v)); val.emplace_back(v);
} }
result.push_back(Entry(key,val)); result.push_back(Entry(key,val));
} }
@ -2647,8 +2644,8 @@ bool Core::RunAlias(color_ostream &out, const std::string &name,
return false; return false;
} }
const string &first = aliases[name][0]; const std::string &first = aliases[name][0];
vector<string> parts(aliases[name].begin() + 1, aliases[name].end()); std::vector<std::string> parts(aliases[name].begin() + 1, aliases[name].end());
parts.insert(parts.end(), parameters.begin(), parameters.end()); parts.insert(parts.end(), parameters.begin(), parameters.end());
result = runCommand(out, first, parts); result = runCommand(out, first, parts);
return true; return true;

@ -55,6 +55,7 @@ distribution.
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/Military.h"
#include "modules/Random.h" #include "modules/Random.h"
#include "modules/Screen.h" #include "modules/Screen.h"
#include "modules/Textures.h" #include "modules/Textures.h"
@ -1659,6 +1660,7 @@ static bool jobItemEqual(const df::job_item *job1, const df::job_item *job2)
} }
static const LuaWrapper::FunctionReg dfhack_job_module[] = { static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,attachJobItem),
WRAPM(Job,cloneJobStruct), WRAPM(Job,cloneJobStruct),
WRAPM(Job,printItemDetails), WRAPM(Job,printItemDetails),
WRAPM(Job,printJobDetails), WRAPM(Job,printJobDetails),
@ -1811,7 +1813,6 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getGoalType), WRAPM(Units, getGoalType),
WRAPM(Units, getGoalName), WRAPM(Units, getGoalName),
WRAPM(Units, isGoalAchieved), WRAPM(Units, isGoalAchieved),
WRAPM(Units, getSquadName),
WRAPM(Units, getPhysicalDescription), WRAPM(Units, getPhysicalDescription),
WRAPM(Units, getRaceName), WRAPM(Units, getRaceName),
WRAPM(Units, getRaceNamePlural), WRAPM(Units, getRaceNamePlural),
@ -1936,6 +1937,15 @@ static const luaL_Reg dfhack_units_funcs[] = {
{ NULL, NULL } { NULL, NULL }
}; };
/***** Military Module *****/
static const LuaWrapper::FunctionReg dfhack_military_module[] = {
WRAPM(Military, makeSquad),
WRAPM(Military, updateRoomAssignments),
WRAPM(Military, getSquadName),
{ NULL, NULL }
};
/***** Items module *****/ /***** Items module *****/
static bool items_moveToGround(df::item *item, df::coord pos) static bool items_moveToGround(df::item *item, df::coord pos)
@ -3446,6 +3456,7 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs); OpenModule(state, "job", dfhack_job_module, dfhack_job_funcs);
OpenModule(state, "textures", dfhack_textures_module); OpenModule(state, "textures", dfhack_textures_module);
OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs);
OpenModule(state, "military", dfhack_military_module);
OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs);
OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs);
OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs); OpenModule(state, "world", dfhack_world_module, dfhack_world_funcs);

@ -404,6 +404,22 @@ DFHACK_EXPORT bool split_string(std::vector<std::string> *out,
bool squash_empty = false); bool squash_empty = false);
DFHACK_EXPORT std::string join_strings(const std::string &separator, const std::vector<std::string> &items); DFHACK_EXPORT std::string join_strings(const std::string &separator, const std::vector<std::string> &items);
template<typename T>
inline std::string join_strings(const std::string &separator, T &items) {
std::stringstream ss;
bool first = true;
for (auto &item : items) {
if (first)
first = false;
else
ss << separator;
ss << item;
}
return ss.str();
}
DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toUpper(const std::string &str);
DFHACK_EXPORT std::string toLower(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str);
DFHACK_EXPORT std::string to_search_normalized(const std::string &str); DFHACK_EXPORT std::string to_search_normalized(const std::string &str);

@ -8,6 +8,7 @@ namespace DFHack
// SDL stand-in type definitions // SDL stand-in type definitions
typedef signed short SINT16; typedef signed short SINT16;
typedef void DFSDL_sem; typedef void DFSDL_sem;
typedef void DFSDL_Event;
typedef struct typedef struct
{ {
@ -80,12 +81,15 @@ void cleanup();
DFHACK_EXPORT DFSDL_Surface * DFIMG_Load(const char *file); DFHACK_EXPORT DFSDL_Surface * DFIMG_Load(const char *file);
DFHACK_EXPORT int DFSDL_SetAlpha(DFSDL_Surface *surface, uint32_t flag, uint8_t alpha); DFHACK_EXPORT int DFSDL_SetAlpha(DFSDL_Surface *surface, uint32_t flag, uint8_t alpha);
DFHACK_EXPORT DFSDL_Surface * DFSDL_GetVideoSurface(void);
DFHACK_EXPORT DFSDL_Surface * DFSDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask); DFHACK_EXPORT DFSDL_Surface * DFSDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask);
DFHACK_EXPORT DFSDL_Surface * DFSDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask);
DFHACK_EXPORT int DFSDL_UpperBlit(DFSDL_Surface *src, const DFSDL_Rect *srcrect, DFSDL_Surface *dst, DFSDL_Rect *dstrect); DFHACK_EXPORT int DFSDL_UpperBlit(DFSDL_Surface *src, const DFSDL_Rect *srcrect, DFSDL_Surface *dst, DFSDL_Rect *dstrect);
DFHACK_EXPORT DFSDL_Surface * DFSDL_ConvertSurface(DFSDL_Surface *src, const DFSDL_PixelFormat *fmt, uint32_t flags); DFHACK_EXPORT DFSDL_Surface * DFSDL_ConvertSurface(DFSDL_Surface *src, const DFSDL_PixelFormat *fmt, uint32_t flags);
DFHACK_EXPORT void DFSDL_FreeSurface(DFSDL_Surface *surface); DFHACK_EXPORT void DFSDL_FreeSurface(DFSDL_Surface *surface);
DFHACK_EXPORT int DFSDL_SemWait(DFSDL_sem *sem); DFHACK_EXPORT int DFSDL_SemWait(DFSDL_sem *sem);
DFHACK_EXPORT int DFSDL_SemPost(DFSDL_sem *sem); DFHACK_EXPORT int DFSDL_SemPost(DFSDL_sem *sem);
DFHACK_EXPORT int DFSDL_PushEvent(DFSDL_Event *event);
} }

@ -0,0 +1,18 @@
#pragma once
#include "Export.h"
#include "DataDefs.h"
#include "df/squad.h"
namespace DFHack
{
namespace Military
{
DFHACK_EXPORT std::string getSquadName(int32_t squad_id);
DFHACK_EXPORT df::squad* makeSquad(int32_t assignment_id);
DFHACK_EXPORT void updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags);
}
}

@ -222,8 +222,6 @@ DFHACK_EXPORT df::goal_type getGoalType(df::unit *unit, size_t goalIndex = 0);
DFHACK_EXPORT std::string getGoalName(df::unit *unit, size_t goalIndex = 0); DFHACK_EXPORT std::string getGoalName(df::unit *unit, size_t goalIndex = 0);
DFHACK_EXPORT bool isGoalAchieved(df::unit *unit, size_t goalIndex = 0); DFHACK_EXPORT bool isGoalAchieved(df::unit *unit, size_t goalIndex = 0);
DFHACK_EXPORT std::string getSquadName(df::unit *unit);
DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit); DFHACK_EXPORT df::activity_entry *getMainSocialActivity(df::unit *unit);
DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit); DFHACK_EXPORT df::activity_event *getMainSocialEvent(df::unit *unit);

@ -916,7 +916,8 @@ end
WINDOW_FRAME = make_frame('Window', true) WINDOW_FRAME = make_frame('Window', true)
PANEL_FRAME = make_frame('Panel', false) PANEL_FRAME = make_frame('Panel', false)
MEDIUM_FRAME = make_frame('Medium', false) MEDIUM_FRAME = make_frame('Medium', false)
THIN_FRAME = make_frame('Thin', false) INTERIOR_FRAME = make_frame('Thin', false)
INTERIOR_FRAME.signature_pen = false
-- for compatibility with pre-steam code -- for compatibility with pre-steam code
GREY_LINE_FRAME = WINDOW_FRAME GREY_LINE_FRAME = WINDOW_FRAME
@ -987,4 +988,10 @@ function FramedScreen:onRenderFrame(dc, rect)
paint_frame(dc,rect,self.frame_style,self.frame_title) paint_frame(dc,rect,self.frame_style,self.frame_title)
end end
-- Inverts the brightness of the color, optionally taking a "bold" parameter,
-- which you should include if you're reading the fg color of a pen.
function invert_color(color, bold)
color = bold and (color + 8) or color
return (color + 8) % 16
end
return _ENV return _ENV

@ -1080,7 +1080,26 @@ local function is_disabled(token)
(token.enabled ~= nil and not getval(token.enabled)) (token.enabled ~= nil and not getval(token.enabled))
end end
function render_text(obj,dc,x0,y0,pen,dpen,disabled) -- Make the hover pen -- that is a pen that should render elements that has the
-- mouse hovering over it. if hpen is specified, it just checks the fields and
-- returns it (in parsed pen form)
local function make_hpen(pen, hpen)
if not hpen then
pen = dfhack.pen.parse(pen)
-- Swap the foreground and background
hpen = dfhack.pen.make(pen.bg, nil, pen.fg + (pen.bold and 8 or 0))
end
-- text_hpen needs a character in order to paint the background using
-- Painter:fill(), so let's make it paint a space to show the background
-- color
local hpen_parsed = dfhack.pen.parse(hpen)
hpen_parsed.ch = string.byte(' ')
return hpen_parsed
end
function render_text(obj,dc,x0,y0,pen,dpen,disabled,hpen,hovered)
local width = 0 local width = 0
for iline = dc and obj.start_line_num or 1, #obj.text_lines do for iline = dc and obj.start_line_num or 1, #obj.text_lines do
local x, line = 0, obj.text_lines[iline] local x, line = 0, obj.text_lines[iline]
@ -1105,8 +1124,8 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
if token.tile then if token.tile then
x = x + 1 x = x + 1
if dc then if dc then
local tile_pen = tonumber(token.tile) and local tile = getval(token.tile)
to_pen{tile=token.tile} or token.tile local tile_pen = tonumber(tile) and to_pen{tile=tile} or tile
dc:char(nil, tile_pen) dc:char(nil, tile_pen)
if token.width then if token.width then
dc:advance(token.width-1) dc:advance(token.width-1)
@ -1120,16 +1139,25 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
if dc then if dc then
local tpen = getval(token.pen) local tpen = getval(token.pen)
local dcpen = to_pen(tpen or pen)
-- If disabled, figure out which dpen to use
if disabled or is_disabled(token) then if disabled or is_disabled(token) then
dc:pen(getval(token.dpen) or tpen or dpen) dcpen = to_pen(getval(token.dpen) or tpen or dpen)
if keypen.fg ~= COLOR_BLACK then if keypen.fg ~= COLOR_BLACK then
keypen.bold = false keypen.bold = false
end end
else
dc:pen(tpen or pen) -- if hovered *and* disabled, combine both effects
if hovered then
dcpen = make_hpen(dcpen)
end
elseif hovered then
dcpen = make_hpen(dcpen, getval(token.hpen) or hpen)
end end
end
dc:pen(dcpen)
end
local width = getval(token.width) local width = getval(token.width)
local padstr local padstr
if width then if width then
@ -1204,7 +1232,7 @@ Label = defclass(Label, Widget)
Label.ATTRS{ Label.ATTRS{
text_pen = COLOR_WHITE, text_pen = COLOR_WHITE,
text_dpen = COLOR_DARKGREY, -- disabled text_dpen = COLOR_DARKGREY, -- disabled
text_hpen = DEFAULT_NIL, -- highlight - default is text_pen with reversed brightness text_hpen = DEFAULT_NIL, -- hover - default is to invert the fg/bg colors
disabled = DEFAULT_NIL, disabled = DEFAULT_NIL,
enabled = DEFAULT_NIL, enabled = DEFAULT_NIL,
auto_height = true, auto_height = true,
@ -1221,12 +1249,7 @@ function Label:init(args)
self:addviews{self.scrollbar} self:addviews{self.scrollbar}
-- use existing saved text if no explicit text was specified. this avoids
-- overwriting pre-formatted text that subclasses may have already set
self:setText(args.text or self.text) self:setText(args.text or self.text)
if not self.text_hpen then
self.text_hpen = ((tonumber(self.text_pen) or tonumber(self.text_pen.fg) or 0) + 8) % 16
end
end end
local function update_label_scrollbar(label) local function update_label_scrollbar(label)
@ -1274,12 +1297,16 @@ function Label:getTextWidth()
return self.text_width return self.text_width
end end
-- Overridden by subclasses that also want to add new mouse handlers, see
-- HotkeyLabel.
function Label:shouldHover()
return self.on_click or self.on_rclick
end
function Label:onRenderBody(dc) function Label:onRenderBody(dc)
local text_pen = self.text_pen local text_pen = self.text_pen
if self:getMousePos() and (self.on_click or self.on_rclick) then local hovered = self:getMousePos() and self:shouldHover()
text_pen = self.text_hpen render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self), self.text_hpen, hovered)
end
render_text(self,dc,0,0,text_pen,self.text_dpen,is_disabled(self))
end end
function Label:on_scrollbar(scroll_spec) function Label:on_scrollbar(scroll_spec)
@ -1432,6 +1459,11 @@ function HotkeyLabel:setLabel(label)
self:initializeLabel() self:initializeLabel()
end end
function HotkeyLabel:shouldHover()
-- When on_activate is set, text should also hover on mouseover
return self.on_activate or HotkeyLabel.super.shouldHover(self)
end
function HotkeyLabel:initializeLabel() function HotkeyLabel:initializeLabel()
self:setText{{key=self.key, key_sep=self.key_sep, text=self.label, self:setText{{key=self.key, key_sep=self.key_sep, text=self.label,
on_activate=self.on_activate}} on_activate=self.on_activate}}
@ -1440,7 +1472,8 @@ end
function HotkeyLabel:onInput(keys) function HotkeyLabel:onInput(keys)
if HotkeyLabel.super.onInput(self, keys) then if HotkeyLabel.super.onInput(self, keys) then
return true return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate then elseif keys._MOUSE_L_DOWN and self:getMousePos() and self.on_activate
and not is_disabled(self) then
self.on_activate() self.on_activate()
return true return true
end end
@ -1457,6 +1490,7 @@ CycleHotkeyLabel.ATTRS{
key_back=DEFAULT_NIL, key_back=DEFAULT_NIL,
label=DEFAULT_NIL, label=DEFAULT_NIL,
label_width=DEFAULT_NIL, label_width=DEFAULT_NIL,
label_below=false,
options=DEFAULT_NIL, options=DEFAULT_NIL,
initial_option=1, initial_option=1,
on_change=DEFAULT_NIL, on_change=DEFAULT_NIL,
@ -1465,16 +1499,27 @@ CycleHotkeyLabel.ATTRS{
function CycleHotkeyLabel:init() function CycleHotkeyLabel:init()
self:setOption(self.initial_option) self:setOption(self.initial_option)
local val_gap = 1
if self.label_below then
val_gap = 0 + (self.key_back and 1 or 0) + (self.key and 3 or 0)
end
self:setText{ self:setText{
self.key_back ~= nil and {key=self.key_back, key_sep='', width=0, on_activate=self:callback('cycle', true)} or {}, self.key_back ~= nil and {key=self.key_back, key_sep='', width=0, on_activate=self:callback('cycle', true)} or {},
{key=self.key, key_sep=': ', text=self.label, width=self.label_width, {key=self.key, key_sep=': ', text=self.label, width=self.label_width,
on_activate=self:callback('cycle')}, on_activate=self:callback('cycle')},
' ', self.label_below and NEWLINE or '',
{text=self:callback('getOptionLabel'), {gap=val_gap, text=self:callback('getOptionLabel'),
pen=self:callback('getOptionPen')}, pen=self:callback('getOptionPen')},
} }
end end
-- CycleHotkeyLabels are always clickable and therefore should always change
-- color when hovered.
function CycleHotkeyLabel:shouldHover()
return true
end
function CycleHotkeyLabel:cycle(backwards) function CycleHotkeyLabel:cycle(backwards)
local old_option_idx = self.option_idx local old_option_idx = self.option_idx
if self.option_idx == #self.options and not backwards then if self.option_idx == #self.options and not backwards then
@ -1541,7 +1586,7 @@ end
function CycleHotkeyLabel:onInput(keys) function CycleHotkeyLabel:onInput(keys)
if CycleHotkeyLabel.super.onInput(self, keys) then if CycleHotkeyLabel.super.onInput(self, keys) then
return true return true
elseif keys._MOUSE_L_DOWN and self:getMousePos() then elseif keys._MOUSE_L_DOWN and self:getMousePos() and not is_disabled(self) then
self:cycle() self:cycle()
return true return true
end end
@ -1565,6 +1610,7 @@ List = defclass(List, Widget)
List.ATTRS{ List.ATTRS{
text_pen = COLOR_CYAN, text_pen = COLOR_CYAN,
text_hpen = DEFAULT_NIL, -- hover color, defaults to inverting the FG/BG pens for each text object
cursor_pen = COLOR_LIGHTCYAN, cursor_pen = COLOR_LIGHTCYAN,
inactive_pen = DEFAULT_NIL, inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL, on_select = DEFAULT_NIL,
@ -1745,12 +1791,16 @@ function List:onRenderBody(dc)
end end
end end
local hoveridx = self:getIdxUnderMouse()
for i = top,iend do for i = top,iend do
local obj = choices[i] local obj = choices[i]
local current = (i == self.selected) local current = (i == self.selected)
local cur_pen = self.cursor_pen local hovered = (i == hoveridx)
local cur_dpen = self.text_pen -- cur_pen and cur_dpen can't be integers or background colors get
local active_pen = current and cur_pen or cur_dpen -- messed up in render_text for subsequent renders
local cur_pen = to_pen(self.cursor_pen)
local cur_dpen = to_pen(self.text_pen)
local active_pen = (current and cur_pen or cur_dpen)
if not getval(self.active) then if not getval(self.active) then
cur_pen = self.inactive_pen or self.cursor_pen cur_pen = self.inactive_pen or self.cursor_pen
@ -1764,7 +1814,7 @@ function List:onRenderBody(dc)
paint_icon(icon, obj) paint_icon(icon, obj)
end end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current, self.text_hpen, hovered)
local ip = dc.width local ip = dc.width
@ -1882,7 +1932,7 @@ end
FilteredList = defclass(FilteredList, Widget) FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS { FilteredList.ATTRS {
case_sensitive = true, case_sensitive = false,
edit_below = false, edit_below = false,
edit_key = DEFAULT_NIL, edit_key = DEFAULT_NIL,
edit_ignore_keys = DEFAULT_NIL, edit_ignore_keys = DEFAULT_NIL,

@ -28,12 +28,15 @@ static const std::vector<std::string> SDL_IMAGE_LIBS {
DFSDL_Surface * (*g_IMG_Load)(const char *) = nullptr; DFSDL_Surface * (*g_IMG_Load)(const char *) = nullptr;
int (*g_SDL_SetAlpha)(DFSDL_Surface *, uint32_t, uint8_t) = nullptr; int (*g_SDL_SetAlpha)(DFSDL_Surface *, uint32_t, uint8_t) = nullptr;
DFSDL_Surface * (*g_SDL_CreateRGBSurface)(uint32_t, int, int, int, uint32_t, uint32_t, uint32_t, uint32_t); DFSDL_Surface * (*g_SDL_GetVideoSurface)(void) = nullptr;
int (*g_SDL_UpperBlit)(DFSDL_Surface *, const DFSDL_Rect *, DFSDL_Surface *, DFSDL_Rect *); DFSDL_Surface * (*g_SDL_CreateRGBSurface)(uint32_t, int, int, int, uint32_t, uint32_t, uint32_t, uint32_t) = nullptr;
DFSDL_Surface * (*g_SDL_ConvertSurface)(DFSDL_Surface *, const DFSDL_PixelFormat *, uint32_t); DFSDL_Surface * (*g_SDL_CreateRGBSurfaceFrom)(void *pixels, int width, int height, int depth, int pitch, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) = nullptr;
void (*g_SDL_FreeSurface)(DFSDL_Surface *); int (*g_SDL_UpperBlit)(DFSDL_Surface *, const DFSDL_Rect *, DFSDL_Surface *, DFSDL_Rect *) = nullptr;
int (*g_SDL_SemWait)(DFSDL_sem *); DFSDL_Surface * (*g_SDL_ConvertSurface)(DFSDL_Surface *, const DFSDL_PixelFormat *, uint32_t) = nullptr;
int (*g_SDL_SemPost)(DFSDL_sem *); void (*g_SDL_FreeSurface)(DFSDL_Surface *) = nullptr;
int (*g_SDL_SemWait)(DFSDL_sem *) = nullptr;
int (*g_SDL_SemPost)(DFSDL_sem *) = nullptr;
int (*g_SDL_PushEvent)(DFSDL_Event *) = nullptr;
bool DFSDL::init(color_ostream &out) { bool DFSDL::init(color_ostream &out) {
for (auto &lib_str : SDL_LIBS) { for (auto &lib_str : SDL_LIBS) {
@ -63,12 +66,15 @@ bool DFSDL::init(color_ostream &out) {
bind(g_sdl_image_handle, IMG_Load); bind(g_sdl_image_handle, IMG_Load);
bind(g_sdl_handle, SDL_SetAlpha); bind(g_sdl_handle, SDL_SetAlpha);
bind(g_sdl_handle, SDL_GetVideoSurface);
bind(g_sdl_handle, SDL_CreateRGBSurface); bind(g_sdl_handle, SDL_CreateRGBSurface);
bind(g_sdl_handle, SDL_CreateRGBSurfaceFrom);
bind(g_sdl_handle, SDL_UpperBlit); bind(g_sdl_handle, SDL_UpperBlit);
bind(g_sdl_handle, SDL_ConvertSurface); bind(g_sdl_handle, SDL_ConvertSurface);
bind(g_sdl_handle, SDL_FreeSurface); bind(g_sdl_handle, SDL_FreeSurface);
bind(g_sdl_handle, SDL_SemWait); bind(g_sdl_handle, SDL_SemWait);
bind(g_sdl_handle, SDL_SemPost); bind(g_sdl_handle, SDL_SemPost);
bind(g_sdl_handle, SDL_PushEvent);
#undef bind #undef bind
DEBUG(dfsdl,out).print("sdl successfully loaded\n"); DEBUG(dfsdl,out).print("sdl successfully loaded\n");
@ -95,10 +101,18 @@ int DFSDL::DFSDL_SetAlpha(DFSDL_Surface *surface, uint32_t flag, uint8_t alpha)
return g_SDL_SetAlpha(surface, flag, alpha); return g_SDL_SetAlpha(surface, flag, alpha);
} }
DFSDL_Surface * DFSDL::DFSDL_GetVideoSurface(void) {
return g_SDL_GetVideoSurface();
}
DFSDL_Surface * DFSDL::DFSDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) { DFSDL_Surface * DFSDL::DFSDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) {
return g_SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask); return g_SDL_CreateRGBSurface(flags, width, height, depth, Rmask, Gmask, Bmask, Amask);
} }
DFSDL_Surface * DFSDL::DFSDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth, int pitch, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) {
return g_SDL_CreateRGBSurfaceFrom(pixels, width, height, depth, pitch, Rmask, Gmask, Bmask, Amask);
}
int DFSDL::DFSDL_UpperBlit(DFSDL_Surface *src, const DFSDL_Rect *srcrect, DFSDL_Surface *dst, DFSDL_Rect *dstrect) { int DFSDL::DFSDL_UpperBlit(DFSDL_Surface *src, const DFSDL_Rect *srcrect, DFSDL_Surface *dst, DFSDL_Rect *dstrect) {
return g_SDL_UpperBlit(src, srcrect, dst, dstrect); return g_SDL_UpperBlit(src, srcrect, dst, dstrect);
} }
@ -118,3 +132,7 @@ int DFSDL::DFSDL_SemWait(DFSDL_sem *sem) {
int DFSDL::DFSDL_SemPost(DFSDL_sem *sem) { int DFSDL::DFSDL_SemPost(DFSDL_sem *sem) {
return g_SDL_SemPost(sem); return g_SDL_SemPost(sem);
} }
int DFSDL::DFSDL_PushEvent(DFSDL_Event *event) {
return g_SDL_PushEvent(event);
}

@ -491,7 +491,12 @@ bool Gui::matchFocusString(std::string focus_string, df::viewscreen *top) {
static void push_dfhack_focus_string(dfhack_viewscreen *vs, std::vector<std::string> &focusStrings) static void push_dfhack_focus_string(dfhack_viewscreen *vs, std::vector<std::string> &focusStrings)
{ {
auto name = vs->getFocusString(); auto name = vs->getFocusString();
focusStrings.push_back(name.empty() ? "dfhack" : "dfhack/" + name); if (name.empty())
name = "dfhack";
else if (string::npos == name.find("dfhack/"))
name = "dfhack/" + name;
focusStrings.push_back(name);
} }
std::vector<std::string> Gui::getFocusStrings(df::viewscreen* top) std::vector<std::string> Gui::getFocusStrings(df::viewscreen* top)

@ -1647,5 +1647,5 @@ bool Items::isSquadEquipment(df::item *item)
return false; return false;
auto &vec = plotinfo->equipment.items_assigned[item->getType()]; auto &vec = plotinfo->equipment.items_assigned[item->getType()];
return binsearch_index(vec, &df::item::id, item->id) >= 0; return binsearch_index(vec, item->id) >= 0;
} }

@ -513,8 +513,14 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma
TEST(sewn_imageless, is_cloth); TEST(sewn_imageless, is_cloth);
TEST(glass_making, MAT_FLAG(CRYSTAL_GLASSABLE)); TEST(glass_making, MAT_FLAG(CRYSTAL_GLASSABLE));
TEST(fire_safe, material->heat.melting_point > 11000); TEST(fire_safe, material->heat.melting_point > 11000
TEST(magma_safe, material->heat.melting_point > 12000); && material->heat.boiling_point > 11000
&& material->heat.ignite_point > 11000
&& material->heat.heatdam_point > 11000);
TEST(magma_safe, material->heat.melting_point > 12000
&& material->heat.boiling_point > 12000
&& material->heat.ignite_point > 12000
&& material->heat.heatdam_point > 12000);
TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL));
TEST(non_economic, !inorganic || !(plotinfo && vector_get(plotinfo->economic_stone, index))); TEST(non_economic, !inorganic || !(plotinfo && vector_get(plotinfo->economic_stone, index)));

@ -0,0 +1,291 @@
#include <string>
#include <vector>
#include <array>
#include "MiscUtils.h"
#include "modules/Military.h"
#include "modules/Translation.h"
#include "df/building.h"
#include "df/building_civzonest.h"
#include "df/historical_figure.h"
#include "df/historical_entity.h"
#include "df/entity_position.h"
#include "df/entity_position_assignment.h"
#include "df/plotinfost.h"
#include "df/squad.h"
#include "df/squad_position.h"
#include "df/squad_schedule_order.h"
#include "df/squad_order.h"
#include "df/squad_order_trainst.h"
#include "df/world.h"
using namespace DFHack;
using namespace df::enums;
using df::global::world;
using df::global::plotinfo;
std::string Military::getSquadName(int32_t squad_id)
{
df::squad *squad = df::squad::find(squad_id);
if (!squad)
return "";
if (squad->alias.size() > 0)
return squad->alias;
return Translation::TranslateName(&squad->name, true);
}
//only works for making squads for fort mode player controlled dwarf squads
//could be extended straightforwardly by passing in entity
df::squad* Military::makeSquad(int32_t assignment_id)
{
if (df::global::squad_next_id == nullptr || df::global::plotinfo == nullptr)
return nullptr;
df::language_name name;
name.type = df::language_name_type::Squad;
for (int i=0; i < 7; i++)
{
name.words[i] = -1;
name.parts_of_speech[i] = df::part_of_speech::Noun;
}
df::historical_entity* fort = df::historical_entity::find(df::global::plotinfo->group_id);
if (fort == nullptr)
return nullptr;
df::entity_position_assignment* found_assignment = nullptr;
for (auto* assignment : fort->positions.assignments)
{
if (assignment->id == assignment_id)
{
found_assignment = assignment;
break;
}
}
if (found_assignment == nullptr)
return nullptr;
//this function does not attempt to delete or replace squads for assignments
if (found_assignment->squad_id != -1)
return nullptr;
df::entity_position* corresponding_position = nullptr;
for (auto* position : fort->positions.own)
{
if (position->id == found_assignment->position_id)
{
corresponding_position = position;
break;
}
}
if (corresponding_position == nullptr)
return nullptr;
df::squad* result = new df::squad();
result->id = *df::global::squad_next_id;
result->uniform_priority = result->id + 1; //no idea why, but seems to hold
result->carry_food = 2;
result->carry_water = 1;
result->entity_id = df::global::plotinfo->group_id;
result->leader_position = corresponding_position->id;
result->leader_assignment = found_assignment->id;
result->name = name;
int16_t squad_size = corresponding_position->squad_size;
for (int i=0; i < squad_size; i++)
{
//construct for squad_position seems to set all the attributes correctly
df::squad_position* pos = new df::squad_position();
result->positions.push_back(pos);
}
const auto& routines = df::global::plotinfo->alerts.routines;
for (const auto& routine : routines)
{
df::squad_schedule_entry* asched = (df::squad_schedule_entry*)malloc(sizeof(df::squad_schedule_entry) * 12);
for(int kk=0; kk < 12; kk++)
{
new (&asched[kk]) df::squad_schedule_entry;
for(int jj=0; jj < squad_size; jj++)
{
int32_t* order_assignments = new int32_t();
*order_assignments = -1;
asched[kk].order_assignments.push_back(order_assignments);
}
}
auto insert_training_order = [asched, squad_size](int month)
{
df::squad_schedule_order* order = new df::squad_schedule_order();
order->min_count = squad_size;
//assumed
order->positions.resize(squad_size);
df::squad_order* s_order = df::allocate<df::squad_order_trainst>();
s_order->year = *df::global::cur_year;
s_order->year_tick = *df::global::cur_year_tick;
order->order = s_order;
asched[month].orders.push_back(order);
//wear uniform while training
asched[month].uniform_mode = 0;
};
//Dwarf fortress does do this via a series of string comparisons
//Off duty: No orders, Sleep/room at will. Equip/orders only
if (routine->name == "Off duty")
{
for (int i=0; i < 12; i++)
{
asched[i].sleep_mode = 0;
asched[i].uniform_mode = 1;
}
}
//Staggered Training: Training orders at months 3 4 5 9 10 11, *or* 0 1 2 6 7 8, sleep/room at will. Equip/orders only, except train months which are equip/always
else if (routine->name == "Staggered training")
{
//this is semi randomised for different squads
//appears to be something like squad.id & 1, it isn't smart
//if you alternate squad creation, its 'correctly' staggered
//but it'll also happily not stagger them if you eg delete a squad and make another
std::array<int, 6> indices;
if ((*df::global::squad_next_id) & 1)
{
indices = {3, 4, 5, 9, 10, 11};
}
else
{
indices = {0, 1, 2, 6, 7, 8};
}
for (int index : indices)
{
insert_training_order(index);
//still sleep in room at will even when training
asched[index].sleep_mode = 0;
}
}
//see above, but with all indices
else if (routine->name == "Constant training")
{
for (int i=0; i < 12; i++)
{
insert_training_order(i);
//still sleep in room at will even when training
asched[i].sleep_mode = 0;
}
}
else if (routine->name == "Ready")
{
for (int i=0; i < 12; i++)
{
asched[i].sleep_mode = 2;
asched[i].uniform_mode = 0;
}
}
else
{
for (int i=0; i < 12; i++)
{
asched[i].sleep_mode = 0;
asched[i].uniform_mode = 0;
}
}
result->schedule.push_back(reinterpret_cast<df::squad::T_schedule*>(asched));
}
//Modify necessary world state
(*df::global::squad_next_id)++;
fort->squads.push_back(result->id);
df::global::world->squads.all.push_back(result);
found_assignment->squad_id = result->id;
return result;
}
void Military::updateRoomAssignments(int32_t squad_id, int32_t civzone_id, df::squad_use_flags flags)
{
df::squad* squad = df::squad::find(squad_id);
df::building* bzone = df::building::find(civzone_id);
df::building_civzonest* zone = strict_virtual_cast<df::building_civzonest>(bzone);
if (squad == nullptr || zone == nullptr)
return;
df::squad::T_rooms* room_from_squad = nullptr;
df::building_civzonest::T_squad_room_info* room_from_building = nullptr;
for (auto room : squad->rooms)
{
if (room->building_id == civzone_id)
{
room_from_squad = room;
break;
}
}
for (auto room : zone->squad_room_info)
{
if (room->squad_id == squad_id)
{
room_from_building = room;
break;
}
}
if (flags.whole == 0 && room_from_squad == nullptr && room_from_building == nullptr)
return;
//if we're setting 0 flags, and there's no room already, don't set a room
bool avoiding_squad_roundtrip = flags.whole == 0 && room_from_squad == nullptr;
if (!avoiding_squad_roundtrip && room_from_squad == nullptr)
{
room_from_squad = new df::squad::T_rooms();
room_from_squad->building_id = civzone_id;
insert_into_vector(squad->rooms, &df::squad::T_rooms::building_id, room_from_squad);
}
if (room_from_building == nullptr)
{
room_from_building = new df::building_civzonest::T_squad_room_info();
room_from_building->squad_id = squad_id;
insert_into_vector(zone->squad_room_info, &df::building_civzonest::T_squad_room_info::squad_id, room_from_building);
}
if (room_from_squad)
room_from_squad->mode = flags;
room_from_building->mode = flags;
if (flags.whole == 0 && !avoiding_squad_roundtrip)
{
for (int i=0; i < (int)squad->rooms.size(); i++)
{
if (squad->rooms[i]->building_id == civzone_id)
{
delete squad->rooms[i];
squad->rooms.erase(squad->rooms.begin() + i);
i--;
}
}
}
}

@ -130,8 +130,7 @@ static bool doSetTile_map(const Pen &pen, int x, int y) {
long texpos = pen.tile; long texpos = pen.tile;
if (!texpos && pen.ch) if (!texpos && pen.ch)
texpos = init->font.large_font_texpos[(uint8_t)pen.ch]; texpos = init->font.large_font_texpos[(uint8_t)pen.ch];
if (texpos) vp->screentexpos_interface[index] = texpos;
vp->screentexpos_interface[index] = texpos;
return true; return true;
} }
@ -877,7 +876,7 @@ void dfhack_lua_viewscreen::update_focus(lua_State *L, int idx)
if (focus.empty()) if (focus.empty())
focus = "lua"; focus = "lua";
else else if (string::npos == focus.find("lua/"))
focus = "lua/"+focus; focus = "lua/"+focus;
} }

@ -71,7 +71,6 @@ using namespace std;
#include "df/identity.h" #include "df/identity.h"
#include "df/job.h" #include "df/job.h"
#include "df/nemesis_record.h" #include "df/nemesis_record.h"
#include "df/squad.h"
#include "df/tile_occupancy.h" #include "df/tile_occupancy.h"
#include "df/plotinfost.h" #include "df/plotinfost.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
@ -589,19 +588,7 @@ bool Units::isMischievous(df::unit *unit)
bool Units::isAvailableForAdoption(df::unit* unit) bool Units::isAvailableForAdoption(df::unit* unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
auto refs = unit->specific_refs; return unit->flags3.bits.available_for_adoption;
for(size_t i=0; i<refs.size(); i++)
{
auto ref = refs[i];
auto reftype = ref->type;
if( reftype == df::specific_ref_type::PETINFO_PET )
{
//df::pet_info* pet = ref->pet;
return true;
}
}
return false;
} }
bool Units::isPet(df::unit* unit) bool Units::isPet(df::unit* unit)
@ -1959,19 +1946,6 @@ bool Units::isGoalAchieved(df::unit *unit, size_t goalIndex)
&& unit->status.current_soul->personality.dreams[goalIndex]->flags.whole != 0; && unit->status.current_soul->personality.dreams[goalIndex]->flags.whole != 0;
} }
std::string Units::getSquadName(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->military.squad_id == -1)
return "";
df::squad *squad = df::squad::find(unit->military.squad_id);
if (!squad)
return "";
if (squad->alias.size() > 0)
return squad->alias;
return Translation::TranslateName(&squad->name, true);
}
df::activity_entry *Units::getMainSocialActivity(df::unit *unit) df::activity_entry *Units::getMainSocialActivity(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);

@ -1 +1 @@
Subproject commit 7917f062c403a47d4d190bafc2470b247c8aa642 Subproject commit 8ae81f8d8f1f96d82b9074b205073bb8e8d29f96

@ -89,7 +89,7 @@ dfhack_plugin(autonestbox autonestbox.cpp LINK_LIBRARIES lua)
dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua) dfhack_plugin(blueprint blueprint.cpp LINK_LIBRARIES lua)
#dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua) #dfhack_plugin(burrows burrows.cpp LINK_LIBRARIES lua)
#dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua) #dfhack_plugin(building-hacks building-hacks.cpp LINK_LIBRARIES lua)
dfhack_plugin(buildingplan buildingplan.cpp LINK_LIBRARIES lua) add_subdirectory(buildingplan)
#dfhack_plugin(changeitem changeitem.cpp) #dfhack_plugin(changeitem changeitem.cpp)
dfhack_plugin(changelayer changelayer.cpp) dfhack_plugin(changelayer changelayer.cpp)
dfhack_plugin(changevein changevein.cpp) dfhack_plugin(changevein changevein.cpp)

@ -19,8 +19,6 @@
#include "LuaTools.h" #include "LuaTools.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Maps.h"
#include "modules/Persistence.h" #include "modules/Persistence.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
@ -805,8 +803,8 @@ static void autobutcher_cycle(color_ostream &out) {
w->UpdateConfig(out); w->UpdateConfig(out);
watched_races.emplace(unit->race, w); watched_races.emplace(unit->race, w);
string announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(unit->race); INFO(cycle,out).print("New race added to autobutcher watchlist: %s\n",
Gui::showAnnouncement(announce, 2, false); Units::getRaceNamePluralById(unit->race).c_str());
} }
if (w->isWatched) { if (w->isWatched) {
@ -828,9 +826,8 @@ static void autobutcher_cycle(color_ostream &out) {
if (slaughter_count) { if (slaughter_count) {
std::stringstream ss; std::stringstream ss;
ss << slaughter_count; ss << slaughter_count;
string announce = Units::getRaceNamePluralById(w.first) + " marked for slaughter: " + ss.str(); INFO(cycle,out).print("%s marked for slaughter: %s\n",
DEBUG(cycle,out).print("%s\n", announce.c_str()); Units::getRaceNamePluralById(w.first).c_str(), ss.str().c_str());
Gui::showAnnouncement(announce, 2, false);
} }
} }
} }
@ -954,10 +951,8 @@ static void autobutcher_setWatchListRace(color_ostream &out, unsigned id, unsign
WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma); WatchedRace * w = new WatchedRace(out, id, watched, fk, mk, fa, ma);
w->UpdateConfig(out); w->UpdateConfig(out);
watched_races.emplace(id, w); watched_races.emplace(id, w);
INFO(status,out).print("New race added to autobutcher watchlist: %s\n",
string announce; Units::getRaceNamePluralById(id).c_str());
announce = "New race added to autobutcher watchlist: " + Units::getRaceNamePluralById(id);
Gui::showAnnouncement(announce, 2, false);
} }
// remove entry from watchlist // remove entry from watchlist

@ -841,7 +841,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
if (p1 || p2) if (p1 || p2)
{ {
dwarf_info[dwarf].diplomacy = true; dwarf_info[dwarf].diplomacy = true;
INFO(cycle, out).print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", DEBUG(cycle, out).print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n",
dwarf, dwarfs[dwarf]->name.first_name.c_str()); dwarf, dwarfs[dwarf]->name.first_name.c_str());
break; break;
} }

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

@ -2,10 +2,15 @@ project(buildingplan)
set(COMMON_HDRS set(COMMON_HDRS
buildingplan.h buildingplan.h
buildingplan-planner.h buildingtypekey.h
buildingplan-rooms.h defaultitemfilters.h
itemfilter.h
plannedbuilding.h
) )
set_source_files_properties(${COMMON_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) set_source_files_properties(${COMMON_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
dfhack_plugin(buildingplan buildingplan.cpp buildingplan-planner.cpp dfhack_plugin(buildingplan
buildingplan-rooms.cpp ${COMMON_HDRS} LINK_LIBRARIES lua) buildingplan.cpp buildingplan_cycle.cpp buildingtypekey.cpp
defaultitemfilters.cpp itemfilter.cpp plannedbuilding.cpp
${COMMON_HDRS}
LINK_LIBRARIES lua)

File diff suppressed because it is too large Load Diff

@ -1,140 +0,0 @@
#pragma once
#include <queue>
#include <unordered_map>
#include "df/building.h"
#include "df/dfhack_material_category.h"
#include "df/item_quality.h"
#include "df/job_item.h"
#include "modules/Materials.h"
#include "modules/Persistence.h"
class ItemFilter
{
public:
ItemFilter();
void clear();
bool deserialize(std::string ser);
std::string serialize() const;
void addMaterialMask(uint32_t mask);
void clearMaterialMask();
void setMaterials(std::vector<DFHack::MaterialInfo> materials);
void incMinQuality();
void decMinQuality();
void incMaxQuality();
void decMaxQuality();
void toggleDecoratedOnly();
uint32_t getMaterialMask() const;
std::vector<std::string> getMaterials() const;
std::string getMinQuality() const;
std::string getMaxQuality() const;
bool getDecoratedOnly() const;
bool matches(df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item) const;
private:
// remove friend declaration when we no longer need v1 deserialization
friend void migrateV1ToV2();
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
df::item_quality min_quality;
df::item_quality max_quality;
bool decorated_only;
bool deserializeMaterialMask(std::string ser);
bool deserializeMaterials(std::string ser);
void setMinQuality(int quality);
void setMaxQuality(int quality);
bool matchesMask(DFHack::MaterialInfo &mat) const;
};
class PlannedBuilding
{
public:
PlannedBuilding(df::building *building, const std::vector<ItemFilter> &filters);
PlannedBuilding(DFHack::PersistentDataItem &config);
bool isValid() const;
void remove();
df::building * getBuilding();
const std::vector<ItemFilter> & getFilters() const;
private:
DFHack::PersistentDataItem config;
df::building *building;
const df::building::key_field_type building_id;
const std::vector<ItemFilter> filters;
};
// building type, subtype, custom
typedef std::tuple<df::building_type, int16_t, int32_t> BuildingTypeKey;
BuildingTypeKey toBuildingTypeKey(
df::building_type btype, int16_t subtype, int32_t custom);
BuildingTypeKey toBuildingTypeKey(df::building *bld);
BuildingTypeKey toBuildingTypeKey(df::ui_build_selector *uibs);
struct BuildingTypeKeyHash
{
std::size_t operator() (const BuildingTypeKey & key) const;
};
class Planner
{
public:
class ItemFiltersWrapper
{
public:
ItemFiltersWrapper(std::vector<ItemFilter> & item_filters)
: item_filters(item_filters) { }
std::vector<ItemFilter>::reverse_iterator rbegin() const { return item_filters.rbegin(); }
std::vector<ItemFilter>::reverse_iterator rend() const { return item_filters.rend(); }
const std::vector<ItemFilter> & get() const { return item_filters; }
private:
std::vector<ItemFilter> &item_filters;
};
const std::map<std::string, bool> & getGlobalSettings() const;
bool setGlobalSetting(std::string name, bool value);
void reset();
void addPlannedBuilding(df::building *bld);
PlannedBuilding *getPlannedBuilding(df::building *bld);
bool isPlannableBuilding(BuildingTypeKey key);
// returns an empty vector if the type is not supported
ItemFiltersWrapper getItemFilters(BuildingTypeKey key);
void doCycle();
private:
DFHack::PersistentDataItem config;
std::map<std::string, bool> global_settings;
std::unordered_map<BuildingTypeKey,
std::vector<ItemFilter>,
BuildingTypeKeyHash> default_item_filters;
// building id -> PlannedBuilding
std::unordered_map<int32_t, PlannedBuilding> planned_buildings;
// vector id -> filter bucket -> queue of (building id, job_item index)
std::map<df::job_item_vector_id, std::map<std::string, std::queue<std::pair<int32_t, int>>>> tasks;
bool registerTasks(PlannedBuilding &plannedBuilding);
void unregisterBuilding(int32_t id);
void popInvalidTasks(std::queue<std::pair<int32_t, int>> &task_queue);
void doVector(df::job_item_vector_id vector_id,
std::map<std::string, std::queue<std::pair<int32_t, int>>> & buckets);
};
extern Planner planner;

@ -1,226 +0,0 @@
#include "buildingplan.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;

@ -1,51 +0,0 @@
#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;

File diff suppressed because it is too large Load Diff

@ -1,8 +1,52 @@
#pragma once #pragma once
#include "buildingplan-planner.h" #include "itemfilter.h"
#include "buildingplan-rooms.h"
void debug(const char *fmt, ...) Wformat(printf,1,2); #include "modules/Persistence.h"
extern bool show_debugging; #include "df/building.h"
#include "df/job_item.h"
#include "df/job_item_vector_id.h"
#include <deque>
typedef std::deque<std::pair<int32_t, int>> Bucket;
typedef std::map<df::job_item_vector_id, std::map<std::string, Bucket>> Tasks;
extern const std::string FILTER_CONFIG_KEY;
extern const std::string BLD_CONFIG_KEY;
enum ConfigValues {
CONFIG_BLOCKS = 1,
CONFIG_BOULDERS = 2,
CONFIG_LOGS = 3,
CONFIG_BARS = 4,
};
enum FilterConfigValues {
FILTER_CONFIG_TYPE = 0,
FILTER_CONFIG_SUBTYPE = 1,
FILTER_CONFIG_CUSTOM = 2,
};
enum BuildingConfigValues {
BLD_CONFIG_ID = 0,
BLD_CONFIG_HEAT = 1,
};
enum HeatSafety {
HEAT_SAFETY_ANY = 0,
HEAT_SAFETY_FIRE = 1,
HEAT_SAFETY_MAGMA = 2,
};
int get_config_val(DFHack::PersistentDataItem &c, int index);
bool get_config_bool(DFHack::PersistentDataItem &c, int index);
void set_config_val(DFHack::PersistentDataItem &c, int index, int value);
void set_config_bool(DFHack::PersistentDataItem &c, int index, bool value);
std::vector<df::job_item_vector_id> getVectorIds(DFHack::color_ostream &out, const df::job_item *job_item);
bool itemPassesScreen(df::item * item);
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter);
bool isJobReady(DFHack::color_ostream &out, const std::vector<df::job_item *> &jitems);
void finalizeBuilding(DFHack::color_ostream &out, df::building *bld);

@ -0,0 +1,304 @@
#include "plannedbuilding.h"
#include "buildingplan.h"
#include "Debug.h"
#include "modules/Items.h"
#include "modules/Job.h"
#include "modules/Maps.h"
#include "modules/Materials.h"
#include "df/building_design.h"
#include "df/item.h"
#include "df/job.h"
#include "df/map_block.h"
#include "df/world.h"
#include <unordered_map>
using std::map;
using std::string;
using std::unordered_map;
namespace DFHack {
DBG_EXTERN(buildingplan, cycle);
}
using namespace DFHack;
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;
}
};
bool itemPassesScreen(df::item * item) {
static const BadFlags bad_flags;
return !(item->flags.whole & bad_flags.whole)
&& !item->isAssignedToStockpile();
}
bool matchesFilters(df::item * item, const df::job_item * job_item, HeatSafety heat, const ItemFilter &item_filter) {
// 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;
df::job_item jitem = *job_item;
if (heat == HEAT_SAFETY_MAGMA) {
jitem.flags2.bits.magma_safe = true;
jitem.flags2.bits.fire_safe = false;
} else if (heat == HEAT_SAFETY_FIRE && !jitem.flags2.bits.magma_safe)
jitem.flags2.bits.fire_safe = true;
return Job::isSuitableItem(
&jitem, item->getType(), item->getSubtype())
&& Job::isSuitableMaterial(
&jitem, item->getMaterial(), item->getMaterialIndex(),
item->getType())
&& item_filter.matches(item);
}
bool isJobReady(color_ostream &out, const std::vector<df::job_item *> &jitems) {
int needed_items = 0;
for (auto job_item : jitems) { 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.
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, Bucket &task_queue,
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
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_front();
}
return NULL;
}
// This is tricky. we want to choose an item that can be brought to the job site, but that's not
// necessarily the same as job->pos. it could be many tiles off in any direction (e.g. for bridges), or
// up or down (e.g. for stairs). For now, just return if the item is on a walkable tile.
static bool isAccessibleFrom(color_ostream &out, df::item *item, df::job *job) {
df::coord item_pos = Items::getPosition(item);
df::map_block *block = Maps::getTileBlock(item_pos);
bool is_walkable = false;
if (block) {
uint16_t walkability_group = index_tile(block->walkable, item_pos);
is_walkable = walkability_group != 0;
TRACE(cycle,out).print("item %d in walkability_group %u at (%d,%d,%d) is %saccessible from job site\n",
item->id, walkability_group, item_pos.x, item_pos.y, item_pos.z, is_walkable ? "" : "not ");
}
return is_walkable;
}
static void doVector(color_ostream &out, df::job_item_vector_id vector_id,
map<string, Bucket> &buckets,
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
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, planned_buildings);
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;
auto &pb = planned_buildings.at(id);
if (isAccessibleFrom(out, item, job)
&& matchesFilters(item, job->job_items[filter_idx], pb.heat_safety,
pb.item_filters[filter_idx])
&& 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_front();
if (isJobReady(out, job->job_items)) {
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);
vectors.push_back(df::job_item_vector_id::IN_PLAY);
}
};
void buildingplan_cycle(color_ostream &out, Tasks &tasks,
unordered_map<int32_t, PlannedBuilding> &planned_buildings) {
static const VectorsToScanLast vectors_to_scan_last;
DEBUG(cycle,out).print(
"running buildingplan cycle for %zu registered buildings\n",
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 a few 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, planned_buildings);
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, planned_buildings);
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());
}

@ -0,0 +1,59 @@
#include "buildingplan.h"
#include "buildingtypekey.h"
#include "Debug.h"
#include "MiscUtils.h"
using std::string;
using std::vector;
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using namespace DFHack;
// building type, subtype, custom
BuildingTypeKey::BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom)
: tuple(type, subtype, custom) { }
static BuildingTypeKey deserialize(color_ostream &out, const std::string &serialized) {
vector<string> key_parts;
split_string(&key_parts, serialized, ",");
if (key_parts.size() != 3) {
WARN(status,out).print("invalid key_str: '%s'\n", serialized.c_str());
return BuildingTypeKey(df::building_type::NONE, -1, -1);
}
return BuildingTypeKey((df::building_type)string_to_int(key_parts[0]),
string_to_int(key_parts[1]), string_to_int(key_parts[2]));
}
BuildingTypeKey::BuildingTypeKey(color_ostream &out, const std::string &serialized)
:tuple(deserialize(out, serialized)) { }
string BuildingTypeKey::serialize() const {
std::ostringstream ser;
ser << std::get<0>(*this) << ",";
ser << std::get<1>(*this) << ",";
ser << std::get<2>(*this);
return ser.str();
}
// rotates a size_t value left by count bits
// assumes count is not 0 or >= size_t_bits
// replace this with std::rotl when we move to C++20
static std::size_t rotl_size_t(size_t val, uint32_t count)
{
static const int size_t_bits = CHAR_BIT * sizeof(std::size_t);
return val << count | val >> (size_t_bits - count);
}
std::size_t BuildingTypeKeyHash::operator() (const BuildingTypeKey & key) const {
// cast first param to appease gcc-4.8, which is missing the enum
// specializations for std::hash
std::size_t h1 = std::hash<int32_t>()(static_cast<int32_t>(std::get<0>(key)));
std::size_t h2 = std::hash<int16_t>()(std::get<1>(key));
std::size_t h3 = std::hash<int32_t>()(std::get<2>(key));
return h1 ^ rotl_size_t(h2, 8) ^ rotl_size_t(h3, 16);
}

@ -0,0 +1,22 @@
#pragma once
#include "df/building_type.h"
#include <tuple>
#include <string>
namespace DFHack {
class color_ostream;
}
// building type, subtype, custom
struct BuildingTypeKey : public std::tuple<df::building_type, int16_t, int32_t> {
BuildingTypeKey(df::building_type type, int16_t subtype, int32_t custom);
BuildingTypeKey(DFHack::color_ostream &out, const std::string & serialized);
std::string serialize() const;
};
struct BuildingTypeKeyHash {
std::size_t operator() (const BuildingTypeKey & key) const;
};

@ -0,0 +1,60 @@
#include "defaultitemfilters.h"
#include "Debug.h"
#include "MiscUtils.h"
#include "modules/World.h"
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using std::string;
using std::vector;
using namespace DFHack;
BuildingTypeKey DefaultItemFilters::getKey(PersistentDataItem &filter_config) {
return BuildingTypeKey(
(df::building_type)get_config_val(filter_config, FILTER_CONFIG_TYPE),
get_config_val(filter_config, FILTER_CONFIG_SUBTYPE),
get_config_val(filter_config, FILTER_CONFIG_CUSTOM));
}
DefaultItemFilters::DefaultItemFilters(color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems)
: key(key) {
DEBUG(status,out).print("creating persistent data for filter key %d,%d,%d\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key));
filter_config = World::AddPersistentData(FILTER_CONFIG_KEY);
set_config_val(filter_config, FILTER_CONFIG_TYPE, std::get<0>(key));
set_config_val(filter_config, FILTER_CONFIG_SUBTYPE, std::get<1>(key));
set_config_val(filter_config, FILTER_CONFIG_CUSTOM, std::get<2>(key));
item_filters.resize(jitems.size());
filter_config.val() = serialize_item_filters(item_filters);
}
DefaultItemFilters::DefaultItemFilters(color_ostream &out, PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems)
: key(getKey(filter_config)), filter_config(filter_config) {
auto &serialized = filter_config.val();
DEBUG(status,out).print("deserializing item filters for key %d,%d,%d: %s\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
std::vector<ItemFilter> filters = deserialize_item_filters(out, serialized);
if (filters.size() != jitems.size()) {
WARN(status,out).print("ignoring invalid filters_str for key %d,%d,%d: '%s'\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), serialized.c_str());
item_filters.resize(jitems.size());
} else
item_filters = filters;
}
void DefaultItemFilters::setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index) {
if (index < 0 || item_filters.size() <= (size_t)index) {
WARN(status,out).print("invalid index for filter key %d,%d,%d: %d\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), index);
return;
}
item_filters[index] = filter;
filter_config.val() = serialize_item_filters(item_filters);
DEBUG(status,out).print("updated item filter and persisted for key %d,%d,%d: %s\n",
std::get<0>(key), std::get<1>(key), std::get<2>(key), filter_config.val().c_str());
}

@ -0,0 +1,24 @@
#pragma once
#include "buildingplan.h"
#include "buildingtypekey.h"
#include "modules/Persistence.h"
class DefaultItemFilters {
public:
static BuildingTypeKey getKey(DFHack::PersistentDataItem &filter_config);
const BuildingTypeKey key;
DefaultItemFilters(DFHack::color_ostream &out, BuildingTypeKey key, const std::vector<const df::job_item *> &jitems);
DefaultItemFilters(DFHack::color_ostream &out, DFHack::PersistentDataItem &filter_config, const std::vector<const df::job_item *> &jitems);
void setItemFilter(DFHack::color_ostream &out, const ItemFilter &filter, int index);
const std::vector<ItemFilter> & getItemFilters() const { return item_filters; }
private:
DFHack::PersistentDataItem filter_config;
std::vector<ItemFilter> item_filters;
};

@ -0,0 +1,181 @@
#include "itemfilter.h"
#include "Debug.h"
#include "df/item.h"
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using std::string;
using std::vector;
using namespace DFHack;
ItemFilter::ItemFilter() {
clear();
}
void ItemFilter::clear() {
min_quality = df::item_quality::Ordinary;
max_quality = df::item_quality::Masterful;
decorated_only = false;
mat_mask.whole = 0;
materials.clear();
}
bool ItemFilter::isEmpty() const {
return min_quality == df::item_quality::Ordinary
&& max_quality == df::item_quality::Masterful
&& !decorated_only
&& !mat_mask.whole
&& materials.empty();
}
static bool deserializeMaterialMask(string ser, df::dfhack_material_category mat_mask) {
if (ser.empty())
return true;
if (!parseJobMaterialCategory(&mat_mask, ser)) {
DEBUG(status).print("invalid job material category serialization: '%s'", ser.c_str());
return false;
}
return true;
}
static bool deserializeMaterials(string ser, vector<DFHack::MaterialInfo> &materials) {
if (ser.empty())
return true;
vector<string> mat_names;
split_string(&mat_names, ser, ",");
for (auto m = mat_names.begin(); m != mat_names.end(); m++) {
DFHack::MaterialInfo material;
if (!material.find(*m) || !material.isValid()) {
DEBUG(status).print("invalid material name serialization: '%s'", ser.c_str());
return false;
}
materials.push_back(material);
}
return true;
}
ItemFilter::ItemFilter(color_ostream &out, string serialized) {
clear();
vector<string> tokens;
split_string(&tokens, serialized, "/");
if (tokens.size() != 5) {
DEBUG(status,out).print("invalid ItemFilter serialization: '%s'", serialized.c_str());
return;
}
if (!deserializeMaterialMask(tokens[0], mat_mask) || !deserializeMaterials(tokens[1], materials))
return;
setMinQuality(atoi(tokens[2].c_str()));
setMaxQuality(atoi(tokens[3].c_str()));
decorated_only = static_cast<bool>(atoi(tokens[4].c_str()));
}
// format: mat,mask,elements/materials,list/minq/maxq/decorated
string ItemFilter::serialize() const {
std::ostringstream ser;
ser << bitfield_to_string(mat_mask, ",") << "/";
if (!materials.empty()) {
ser << materials[0].getToken();
for (size_t i = 1; i < materials.size(); ++i)
ser << "," << materials[i].getToken();
}
ser << "/" << static_cast<int>(min_quality);
ser << "/" << static_cast<int>(max_quality);
ser << "/" << static_cast<int>(decorated_only);
return ser.str();
}
static void clampItemQuality(df::item_quality *quality) {
if (*quality > df::item_quality::Artifact) {
DEBUG(status).print("clamping quality to Artifact");
*quality = df::item_quality::Artifact;
}
if (*quality < df::item_quality::Ordinary) {
DEBUG(status).print("clamping quality to Ordinary");
*quality = df::item_quality::Ordinary;
}
}
void ItemFilter::setMinQuality(int quality) {
min_quality = static_cast<df::item_quality>(quality);
clampItemQuality(&min_quality);
if (max_quality < min_quality)
max_quality = min_quality;
}
void ItemFilter::setMaxQuality(int quality) {
max_quality = static_cast<df::item_quality>(quality);
clampItemQuality(&max_quality);
if (max_quality < min_quality)
min_quality = max_quality;
}
void ItemFilter::setDecoratedOnly(bool decorated) {
decorated_only = decorated;
}
void ItemFilter::setMaterialMask(uint32_t mask) {
mat_mask.whole = mask;
}
void ItemFilter::setMaterials(const vector<DFHack::MaterialInfo> &materials) {
this->materials = materials;
}
static bool matchesMask(DFHack::MaterialInfo &mat, df::dfhack_material_category mat_mask) {
return mat_mask.whole ? mat.matches(mat_mask) : true;
}
bool ItemFilter::matches(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) const {
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, mat_mask) : matches(item_mat);
}
vector<ItemFilter> deserialize_item_filters(color_ostream &out, const string &serialized) {
std::vector<ItemFilter> filters;
vector<string> filter_strs;
split_string(&filter_strs, serialized, ";");
for (auto &str : filter_strs) {
filters.emplace_back(out, str);
}
return filters;
}
string serialize_item_filters(const vector<ItemFilter> &filters) {
vector<string> strs;
for (auto &filter : filters) {
strs.emplace_back(filter.serialize());
}
return join_strings(";", strs);
}

@ -0,0 +1,42 @@
#pragma once
#include "modules/Materials.h"
#include "df/dfhack_material_category.h"
#include "df/item_quality.h"
class ItemFilter {
public:
ItemFilter();
ItemFilter(DFHack::color_ostream &out, std::string serialized);
void clear();
bool isEmpty() const;
std::string serialize() const;
void setMinQuality(int quality);
void setMaxQuality(int quality);
void setDecoratedOnly(bool decorated);
void setMaterialMask(uint32_t mask);
void setMaterials(const std::vector<DFHack::MaterialInfo> &materials);
df::item_quality getMinQuality() const { return min_quality; }
df::item_quality getMaxQuality() const {return max_quality; }
bool getDecoratedOnly() const { return decorated_only; }
df::dfhack_material_category getMaterialMask() const { return mat_mask; }
std::vector<DFHack::MaterialInfo> getMaterials() const { return materials; }
bool matches(df::dfhack_material_category mask) const;
bool matches(DFHack::MaterialInfo &material) const;
bool matches(df::item *item) const;
private:
df::item_quality min_quality;
df::item_quality max_quality;
bool decorated_only;
df::dfhack_material_category mat_mask;
std::vector<DFHack::MaterialInfo> materials;
};
std::vector<ItemFilter> deserialize_item_filters(DFHack::color_ostream &out, const std::string &serialized);
std::string serialize_item_filters(const std::vector<ItemFilter> &filters);

@ -0,0 +1,110 @@
#include "plannedbuilding.h"
#include "buildingplan.h"
#include "Debug.h"
#include "MiscUtils.h"
#include "modules/World.h"
#include "df/job.h"
namespace DFHack {
DBG_EXTERN(buildingplan, status);
}
using std::string;
using std::vector;
using namespace DFHack;
static vector<vector<df::job_item_vector_id>> get_vector_ids(color_ostream &out, int bld_id) {
vector<vector<df::job_item_vector_id>> ret;
df::building *bld = df::building::find(bld_id);
if (!bld || bld->jobs.size() != 1)
return ret;
auto &job = bld->jobs[0];
for (auto &jitem : job->job_items) {
ret.emplace_back(getVectorIds(out, jitem));
}
return ret;
}
static vector<vector<df::job_item_vector_id>> deserialize_vector_ids(color_ostream &out, PersistentDataItem &bld_config) {
vector<vector<df::job_item_vector_id>> ret;
vector<string> rawstrs;
split_string(&rawstrs, bld_config.val(), "|");
const string &serialized = rawstrs[0];
DEBUG(status,out).print("deserializing vector ids for building %d: %s\n",
get_config_val(bld_config, BLD_CONFIG_ID), serialized.c_str());
vector<string> joined;
split_string(&joined, serialized, ";");
for (auto &str : joined) {
vector<string> lst;
split_string(&lst, str, ",");
vector<df::job_item_vector_id> ids;
for (auto &s : lst)
ids.emplace_back(df::job_item_vector_id(string_to_int(s)));
ret.emplace_back(ids);
}
if (!ret.size())
ret = get_vector_ids(out, get_config_val(bld_config, BLD_CONFIG_ID));
return ret;
}
static std::vector<ItemFilter> get_item_filters(color_ostream &out, PersistentDataItem &bld_config) {
std::vector<ItemFilter> ret;
vector<string> rawstrs;
split_string(&rawstrs, bld_config.val(), "|");
if (rawstrs.size() < 2)
return ret;
return deserialize_item_filters(out, rawstrs[1]);
}
static string serialize(const vector<vector<df::job_item_vector_id>> &vector_ids, const vector<ItemFilter> &item_filters) {
vector<string> joined;
for (auto &vec_list : vector_ids) {
joined.emplace_back(join_strings(",", vec_list));
}
std::ostringstream out;
out << join_strings(";", joined) << "|" << serialize_item_filters(item_filters);
return out.str();
}
PlannedBuilding::PlannedBuilding(color_ostream &out, df::building *bld, HeatSafety heat, const vector<ItemFilter> &item_filters)
: id(bld->id), vector_ids(get_vector_ids(out, id)), heat_safety(heat),
item_filters(item_filters) {
DEBUG(status,out).print("creating persistent data for building %d\n", id);
bld_config = World::AddPersistentData(BLD_CONFIG_KEY);
set_config_val(bld_config, BLD_CONFIG_ID, id);
set_config_val(bld_config, BLD_CONFIG_HEAT, heat_safety);
bld_config.val() = serialize(vector_ids, item_filters);
DEBUG(status,out).print("serialized state for building %d: %s\n", id, bld_config.val().c_str());
}
PlannedBuilding::PlannedBuilding(color_ostream &out, PersistentDataItem &bld_config)
: id(get_config_val(bld_config, BLD_CONFIG_ID)),
vector_ids(deserialize_vector_ids(out, bld_config)),
heat_safety((HeatSafety)get_config_val(bld_config, BLD_CONFIG_HEAT)),
item_filters(get_item_filters(out, bld_config)),
bld_config(bld_config) { }
// Ensure the building still exists and is in a valid state. It can disappear
// for lots of reasons, such as running the game with the buildingplan plugin
// disabled, manually removing the building, modifying it via the API, etc.
df::building * PlannedBuilding::getBuildingIfValidOrRemoveIfNot(color_ostream &out) {
auto bld = df::building::find(id);
bool valid = bld && bld->getBuildStage() == 0;
if (!valid) {
remove(out);
return NULL;
}
return bld;
}

@ -0,0 +1,36 @@
#pragma once
#include "buildingplan.h"
#include "itemfilter.h"
#include "Core.h"
#include "modules/Persistence.h"
#include "df/building.h"
#include "df/job_item_vector_id.h"
class PlannedBuilding {
public:
const df::building::key_field_type id;
// job_item idx -> list of vectors the task is linked to
const std::vector<std::vector<df::job_item_vector_id>> vector_ids;
const HeatSafety heat_safety;
const std::vector<ItemFilter> item_filters;
PlannedBuilding(DFHack::color_ostream &out, df::building *bld, HeatSafety heat, const std::vector<ItemFilter> &item_filters);
PlannedBuilding(DFHack::color_ostream &out, DFHack::PersistentDataItem &bld_config);
void remove(DFHack::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(DFHack::color_ostream &out);
private:
DFHack::PersistentDataItem bld_config;
};

@ -21,24 +21,27 @@ void ChannelJobs::load_channel_jobs() {
} }
bool ChannelJobs::has_cavein_conditions(const df::coord &map_pos) { bool ChannelJobs::has_cavein_conditions(const df::coord &map_pos) {
auto p = map_pos; if likely(Maps::isValidTilePos(map_pos)) {
auto ttype = *Maps::getTileType(p); auto p = map_pos;
if (!DFHack::isOpenTerrain(ttype)) { auto ttype = *Maps::getTileType(p);
// check shared neighbour for cave-in conditions if (!DFHack::isOpenTerrain(ttype)) {
df::coord neighbours[4]; // check shared neighbour for cave-in conditions
get_connected_neighbours(map_pos, neighbours); df::coord neighbours[4];
int connectedness = 4; get_connected_neighbours(map_pos, neighbours);
for (auto n: neighbours) { int connectedness = 4;
if (active.count(n) || DFHack::isOpenTerrain(*Maps::getTileType(n))) { for (auto n: neighbours) {
connectedness--; if (!Maps::isValidTilePos(n) || active.count(n) || DFHack::isOpenTerrain(*Maps::getTileType(n))) {
connectedness--;
}
} }
} if (!connectedness) {
if (!connectedness) { // do what?
// do what? p.z--;
p.z--; if (!Maps::isValidTilePos(p)) return false;
ttype = *Maps::getTileType(p); ttype = *Maps::getTileType(p);
if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) { if (DFHack::isOpenTerrain(ttype) || DFHack::isFloorTerrain(ttype)) {
return true; return true;
}
} }
} }
} }
@ -88,6 +91,7 @@ void ChannelGroups::add(const df::coord &map_pos) {
DEBUG(groups).print(" add(" COORD ")\n", COORDARGS(map_pos)); DEBUG(groups).print(" add(" COORD ")\n", COORDARGS(map_pos));
// and so we begin iterating the neighbours // and so we begin iterating the neighbours
for (auto &neighbour: neighbors) { for (auto &neighbour: neighbors) {
if unlikely(!Maps::isValidTilePos(neighbour)) continue;
// go to the next neighbour if this one doesn't have a group // go to the next neighbour if this one doesn't have a group
if (!groups_map.count(neighbour)) { if (!groups_map.count(neighbour)) {
TRACE(groups).print(" -> neighbour is not designated\n"); TRACE(groups).print(" -> neighbour is not designated\n");

@ -112,6 +112,10 @@ enum SettingConfigData {
// dig-now.cpp // dig-now.cpp
df::coord simulate_fall(const df::coord &pos) { df::coord simulate_fall(const df::coord &pos) {
if unlikely(!Maps::isValidTilePos(pos)) {
ERR(plugin).print("Error: simulate_fall(" COORD ") - invalid coordinate\n", COORDARGS(pos));
return {};
}
df::coord resting_pos(pos); df::coord resting_pos(pos);
while (Maps::ensureTileBlock(resting_pos)) { while (Maps::ensureTileBlock(resting_pos)) {
@ -130,6 +134,7 @@ df::coord simulate_area_fall(const df::coord &pos) {
get_neighbours(pos, neighbours); get_neighbours(pos, neighbours);
df::coord lowest = simulate_fall(pos); df::coord lowest = simulate_fall(pos);
for (auto p : neighbours) { for (auto p : neighbours) {
if unlikely(!Maps::isValidTilePos(p)) continue;
auto nlow = simulate_fall(p); auto nlow = simulate_fall(p);
if (nlow.z < lowest.z) { if (nlow.z < lowest.z) {
lowest = nlow; lowest = nlow;
@ -299,10 +304,11 @@ namespace CSP {
int32_t tick = df::global::world->frame_counter; int32_t tick = df::global::world->frame_counter;
auto report_id = (int32_t)(intptr_t(r)); auto report_id = (int32_t)(intptr_t(r));
if (df::global::world) { if (df::global::world) {
std::vector<df::report*> &reports = df::global::world->status.reports; df::report* report = df::report::find(report_id);
size_t idx = -1; if (!report) {
idx = df::report::binsearch_index(reports, report_id); WARN(plugin).print("Error: NewReportEvent() received an invalid report_id - a report* cannot be found\n");
df::report* report = reports.at(idx); return;
}
switch (report->type) { switch (report->type) {
case announcement_type::CANCEL_JOB: case announcement_type::CANCEL_JOB:
if (config.insta_dig) { if (config.insta_dig) {

@ -64,11 +64,13 @@ inline uint8_t count_accessibility(const df::coord &unit_pos, const df::coord &m
get_connected_neighbours(map_pos, connections); get_connected_neighbours(map_pos, connections);
uint8_t accessibility = Maps::canWalkBetween(unit_pos, map_pos) ? 1 : 0; uint8_t accessibility = Maps::canWalkBetween(unit_pos, map_pos) ? 1 : 0;
for (auto n: neighbours) { for (auto n: neighbours) {
if unlikely(!Maps::isValidTilePos(n)) continue;
if (Maps::canWalkBetween(unit_pos, n)) { if (Maps::canWalkBetween(unit_pos, n)) {
accessibility++; accessibility++;
} }
} }
for (auto n : connections) { for (auto n : connections) {
if unlikely(Maps::isValidTilePos(n)) continue;
if (Maps::canWalkBetween(unit_pos, n)) { if (Maps::canWalkBetween(unit_pos, n)) {
accessibility++; accessibility++;
} }
@ -77,22 +79,22 @@ inline uint8_t count_accessibility(const df::coord &unit_pos, const df::coord &m
} }
inline bool isEntombed(const df::coord &unit_pos, const df::coord &map_pos) { inline bool isEntombed(const df::coord &unit_pos, const df::coord &map_pos) {
if (Maps::canWalkBetween(unit_pos, map_pos)) { if likely(Maps::canWalkBetween(unit_pos, map_pos)) {
return false; return false;
} }
df::coord neighbours[8]; df::coord neighbours[8];
get_neighbours(map_pos, neighbours); get_neighbours(map_pos, neighbours);
return std::all_of(neighbours+0, neighbours+8, [&unit_pos](df::coord n) { return std::all_of(neighbours+0, neighbours+8, [&unit_pos](df::coord n) {
return !Maps::canWalkBetween(unit_pos, n); return !Maps::isValidTilePos(n) || !Maps::canWalkBetween(unit_pos, n);
}); });
} }
inline bool is_dig_job(const df::job* job) { inline bool is_dig_job(const df::job* job) {
return job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel; return job && (job->job_type == df::job_type::Dig || job->job_type == df::job_type::DigChannel);
} }
inline bool is_channel_job(const df::job* job) { inline bool is_channel_job(const df::job* job) {
return job->job_type == df::job_type::DigChannel; return job && (job->job_type == df::job_type::DigChannel);
} }
inline bool is_group_job(const ChannelGroups &groups, const df::job* job) { inline bool is_group_job(const ChannelGroups &groups, const df::job* job) {
@ -111,34 +113,48 @@ inline bool is_safe_fall(const df::coord &map_pos) {
df::coord below(map_pos); df::coord below(map_pos);
for (uint8_t zi = 0; zi < config.fall_threshold; ++zi) { for (uint8_t zi = 0; zi < config.fall_threshold; ++zi) {
below.z--; below.z--;
// falling out of bounds is probably considerably unsafe for a dwarf
if unlikely(!Maps::isValidTilePos(below)) {
return false;
}
// if we require vision, and we can't see below.. we'll need to assume it's safe to get anything done
if (config.require_vision && Maps::getTileDesignation(below)->bits.hidden) { if (config.require_vision && Maps::getTileDesignation(below)->bits.hidden) {
return true; //we require vision, and we can't see below.. so we gotta assume it's safe return true;
} }
// finally, if we're not looking at open space (air to fall through) it's safe to fall to
df::tiletype type = *Maps::getTileType(below); df::tiletype type = *Maps::getTileType(below);
if (!DFHack::isOpenTerrain(type)) { if (!DFHack::isOpenTerrain(type)) {
return true; return true;
} }
} }
// we exceeded the fall threshold, so it's not a safe fall
return false; return false;
} }
inline bool is_safe_to_dig_down(const df::coord &map_pos) { inline bool is_safe_to_dig_down(const df::coord &map_pos) {
df::coord pos(map_pos); df::coord pos(map_pos);
// todo: probably should rely on is_safe_fall, it looks like it could be simplified a great deal
for (uint8_t zi = 0; zi <= config.fall_threshold; ++zi) { for (uint8_t zi = 0; zi <= config.fall_threshold; ++zi) {
// assume safe if we can't see and need vision // if we're digging out of bounds, the game can handle that (hopefully)
if unlikely(!Maps::isValidTilePos(pos)) {
return true;
}
// if we require vision, and we can't see the tiles in question.. we'll need to assume it's safe to dig to get anything done
if (config.require_vision && Maps::getTileDesignation(pos)->bits.hidden) { if (config.require_vision && Maps::getTileDesignation(pos)->bits.hidden) {
return true; return true;
} }
df::tiletype type = *Maps::getTileType(pos); df::tiletype type = *Maps::getTileType(pos);
if (zi == 0 && DFHack::isOpenTerrain(type)) { if (zi == 0 && DFHack::isOpenTerrain(type)) {
// todo: remove? this is probably not useful.. and seems like the only considerable difference to is_safe_fall (aside from where each stops looking)
// the starting tile is open space, that's obviously not safe // the starting tile is open space, that's obviously not safe
return false; return false;
} else if (!DFHack::isOpenTerrain(type)) { } else if (!DFHack::isOpenTerrain(type)) {
// a tile after the first one is not open space // a tile after the first one is not open space
return true; return true;
} }
pos.z--; pos.z--; // todo: this can probably move to the beginning of the loop
} }
return false; return false;
} }

File diff suppressed because it is too large Load Diff

@ -493,6 +493,9 @@ function feed_viewscreen_widgets(vs_name, keys)
return false return false
end end
gui.markMouseClicksHandled(keys) gui.markMouseClicksHandled(keys)
if keys._MOUSE_L_DOWN then
df.global.enabler.mouse_lbut = 0
end
return true return true
end end

@ -8,6 +8,7 @@
#include <modules/Screen.h> #include <modules/Screen.h>
#include <modules/Translation.h> #include <modules/Translation.h>
#include <modules/Units.h> #include <modules/Units.h>
#include <modules/Military.h>
#include <modules/Filesystem.h> #include <modules/Filesystem.h>
#include <modules/Job.h> #include <modules/Job.h>
#include <vector> #include <vector>
@ -1305,7 +1306,7 @@ void viewscreen_unitlaborsst::refreshNames()
cur->job_mode = UnitInfo::JOB; cur->job_mode = UnitInfo::JOB;
} }
if (unit->military.squad_id > -1) { if (unit->military.squad_id > -1) {
cur->squad_effective_name = Units::getSquadName(unit); cur->squad_effective_name = Military::getSquadName(unit->military.squad_id);
cur->squad_info = stl_sprintf("%i", unit->military.squad_position + 1) + "." + cur->squad_effective_name; cur->squad_info = stl_sprintf("%i", unit->military.squad_position + 1) + "." + cur->squad_effective_name;
} else { } else {
cur->squad_effective_name = ""; cur->squad_effective_name = "";

@ -27,6 +27,7 @@
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "modules/DFSDL.h"
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/Units.h" #include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
@ -2897,7 +2898,7 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve
e.key.ksym.scancode = in->scancode(); e.key.ksym.scancode = in->scancode();
e.key.ksym.sym = (SDL::Key)in->sym(); e.key.ksym.sym = (SDL::Key)in->sym();
e.key.ksym.unicode = in->unicode(); e.key.ksym.unicode = in->unicode();
SDL_PushEvent(&e); DFHack::DFSDL::DFSDL_PushEvent(&e);
#endif #endif
return CR_OK; return CR_OK;
} }

@ -1 +1 @@
Subproject commit 6570fe01081f7e402495bc5339b4ff7a1aabf305 Subproject commit 3e494d9d968add443ebd63cc167933cc813f0eee

@ -1 +1 @@
Subproject commit f2c2f6aa7e7fe94871adf0a22d6966ddcac38afc Subproject commit 81183a380b11f4c3045a7888c35afe215d2185ad