develop
Warmist 2012-11-29 12:13:28 +02:00
commit 57b69da1f0
98 changed files with 4031 additions and 1651 deletions

@ -121,7 +121,7 @@ ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS)
ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL)
if(APPLE) if(APPLE)
add_definitions(-D_DARWIN) add_definitions(-D_DARWIN)
elseif(UNIX) elseif(UNIX)
add_definitions(-D_LINUX) add_definitions(-D_LINUX)
elseif(WIN32) elseif(WIN32)
@ -172,6 +172,7 @@ IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins
@ -181,11 +182,11 @@ endif()
# Packaging with CPack! # Packaging with CPack!
IF(UNIX) IF(UNIX)
if(APPLE) if(APPLE)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
else() else()
SET(CPACK_GENERATOR "TGZ") SET(CPACK_GENERATOR "TGZ")
endif() endif()
ELSEIF(WIN32) ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
ENDIF() ENDIF()

@ -1109,6 +1109,12 @@ above operations accordingly. If enabled, pauses and zooms to position.</p>
<li><p class="first"><tt class="docutils literal">dfhack.job.printItemDetails(jobitem,idx)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.printItemDetails(jobitem,idx)</tt></p>
<p>Prints info about the job item.</p> <p>Prints info about the job item.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getGeneralRef(job, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getSpecificRef(job, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.job.getHolder(job)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.getHolder(job)</tt></p>
<p>Returns the building holding the job.</p> <p>Returns the building holding the job.</p>
</li> </li>
@ -1147,6 +1153,12 @@ the flags in the job item.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p>
<p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p> <p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getGeneralRef(unit, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getSpecificRef(unit, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getContainer(unit)</tt></p>
<p>Returns the container (cage) item or <em>nil</em>.</p> <p>Returns the container (cage) item or <em>nil</em>.</p>
</li> </li>
@ -1209,6 +1221,9 @@ is <em>true</em>, subtracts the rust penalty.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p> <p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getExperience(unit, skill[, total])</tt></p>
<p>Returns the experience value for the given skill. If <tt class="docutils literal">total</tt> is true, adds experience implied by the current rating.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p> <p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li> </li>
@ -1403,6 +1418,12 @@ burrows, or the presence of invaders.</p>
<div class="section" id="buildings-module"> <div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id25">Buildings module</a></h3> <h3><a class="toc-backref" href="#id25">Buildings module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getGeneralRef(building, type)</tt></p>
<p>Searches for a general_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getSpecificRef(building, type)</tt></p>
<p>Searches for a specific_ref with the given type.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.setOwner(item,unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.buildings.setOwner(item,unit)</tt></p>
<p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership. <p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership.
Returns <em>false</em> in case of error.</p> Returns <em>false</em> in case of error.</p>

@ -970,6 +970,10 @@ Units module
Computes the effective rating for the given skill, taking into account exhaustion, pain etc. Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
* ``dfhack.units.getExperience(unit, skill[, total])``
Returns the experience value for the given skill. If ``total`` is true, adds experience implied by the current rating.
* ``dfhack.units.computeMovementSpeed(unit)`` * ``dfhack.units.computeMovementSpeed(unit)``
Computes number of frames * 100 it takes the unit to move in its current state of mind and body. Computes number of frames * 100 it takes the unit to move in its current state of mind and body.

19
NEWS

@ -10,12 +10,18 @@ DFHack future
- fastdwarf: new mode using debug flags, and some internal consistency fixes. - fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches. - added a small stand-alone utility for applying and removing binary patches.
- removebadthoughts: add --dry-run option - removebadthoughts: add --dry-run option
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
New tweaks:
- tweak military-training: speed up melee squad training up to 10x (normally 3-5x).
New scripts: New scripts:
- binpatch: the same as the stand-alone binpatch.exe, but works at runtime. - binpatch: the same as the stand-alone binpatch.exe, but works at runtime.
- region-pops: displays animal populations of the region and allows tweaking them. - region-pops: displays animal populations of the region and allows tweaking them.
- lua: lua interpreter. - lua: lua interpreter front-end converted to a script from a native command.
- dfusion: misc scripts with a text based menu. - dfusion: misc scripts with a text based menu.
- embark: lets you embark anywhere. - embark: lets you embark anywhere.
- lever: list and pull fort levers from the dfhack console.
- stripcaged: mark items inside cages for dumping, eg caged goblin weapons.
New GUI scripts: New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders. - gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
@ -23,6 +29,14 @@ DFHack future
- gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/assign-rack: works together with a binary patch to fix weapon racks.
- gui/gm-editor: an universal editor for lots of dfhack things. - gui/gm-editor: an universal editor for lots of dfhack things.
- gui/companion-order: a adventure mode command interface for your companions. - gui/companion-order: a adventure mode command interface for your companions.
New binary patches:
- armorstand-capacity
- custom-reagent-size
- deconstruct-heapfall
- deconstruct-teleport
- hospital-overstocking
- training-ammo
- weaponrack-unassign
Workflow plugin: Workflow plugin:
- properly considers minecarts assigned to routes busy. - properly considers minecarts assigned to routes busy.
- code for deducing job outputs rewritten in lua for flexibility. - code for deducing job outputs rewritten in lua for flexibility.
@ -35,6 +49,9 @@ DFHack future
properly designated barracks be used again for storage of squad equipment. properly designated barracks be used again for storage of squad equipment.
New Search plugin by falconne: New Search plugin by falconne:
Adds an incremental search function to the Stocks, Trading and Unit List screens. Adds an incremental search function to the Stocks, Trading and Unit List screens.
New AutoMaterial plugin by falconne:
Makes building constructions (walls, floors, fortifications, etc) a little bit easier by
saving you from having to trawl through long lists of materials each time you place one.
Dfusion plugin: Dfusion plugin:
Reworked to make use of lua modules, now all the scripts can be used from other scripts. Reworked to make use of lua modules, now all the scripts can be used from other scripts.

File diff suppressed because it is too large Load Diff

@ -144,6 +144,16 @@ system console:
The patches are expected to be encoded in text format used by IDA. The patches are expected to be encoded in text format used by IDA.
Live patching
-------------
As an alternative, you can use the ``binpatch`` dfhack command to apply/remove
patches live in memory during a DF session.
In this case, updating symbols.xml is not necessary.
============================= =============================
Something doesn't work, help! Something doesn't work, help!
============================= =============================
@ -1077,6 +1087,9 @@ Subcommands that persist until disabled or DF quit:
:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian). Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
:readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu.
.. image:: images/tweak-plate.png
:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100% In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
@ -1095,9 +1108,16 @@ Subcommands that persist until disabled or DF quit:
:military-stable-assign: Preserve list order and cursor position when assigning to squad, :military-stable-assign: Preserve list order and cursor position when assigning to squad,
i.e. stop the rightmost list of the Positions page of the military i.e. stop the rightmost list of the Positions page of the military
screen from constantly resetting to the top. screen from constantly resetting to the top.
:military-color-assigned: Color squad candidates already assigned to other squads in brown/green :military-color-assigned: Color squad candidates already assigned to other squads in yellow/green
to make them stand out more in the list. to make them stand out more in the list.
.. image:: images/tweak-mil-color.png
:military-training: Speeds up melee squad training by removing an almost certainly
unintended inverse dependency of training speed on unit count
(i.e. the more units you have, the slower it becomes), and making
the units spar more.
fix-armory fix-armory
---------- ----------
@ -1946,6 +1966,41 @@ embark
====== ======
Allows to embark anywhere. Currently windows only. Allows to embark anywhere. Currently windows only.
lever
=====
Allow manipulation of in-game levers from the dfhack console.
Can list levers, including state and links, with::
lever list
To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``.
This is the same as 'q'uerying the building and queue a 'P'ull request.
To magically toggle the lever immediately, use::
lever pull 42 --now
stripcaged
==========
For dumping items inside cages. Will mark selected items for dumping, then
a dwarf may come and actually dump it. See also ``autodump``.
With the ``items`` argument, only dumps items laying in the cage, excluding
stuff worn by caged creatures. ``weapons`` will dump worn weapons, ``armor``
will dump everything worn by caged creatures (including armor and clothing),
and ``all`` will dump everything, on a creature or not.
``stripcaged list`` will display on the dfhack console the list of all cages
and their item content.
Without further arguments, all commands work on all cages and animal traps on
the map. With the ``here`` argument, considers only the in-game selected cage
(or the cage under the game cursor). To target only specific cages, you can
alternatively pass cage IDs as arguments::
stripcaged weapons 25321 34228
======================= =======================
In-game interface tools In-game interface tools
======================= =======================
@ -1958,6 +2013,9 @@ are mostly implemented by lua scripts.
In order to avoid user confusion, as a matter of policy all these tools In order to avoid user confusion, as a matter of policy all these tools
display the word "DFHack" on the screen somewhere while active. display the word "DFHack" on the screen somewhere while active.
When that is not appropriate because they merely add keybinding hints to
existing DF screens, they deliberately use red instead of green for the key.
As an exception, the tweak plugin described above does not follow this As an exception, the tweak plugin described above does not follow this
guideline because it arguably just fixes small usability bugs in the game UI. guideline because it arguably just fixes small usability bugs in the game UI.
@ -1968,12 +2026,18 @@ Dwarf Manipulator
Implemented by the manipulator plugin. To activate, open the unit screen and Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'. press 'l'.
.. image:: images/manipulator.png
This tool implements a Dwarf Therapist-like interface within the game UI. The This tool implements a Dwarf Therapist-like interface within the game UI. The
far left column displays the unit's Happiness (color-coded based on its far left column displays the unit's Happiness (color-coded based on its
value), and the right half of the screen displays each dwarf's labor settings value), and the right half of the screen displays each dwarf's labor settings
and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand
Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds Master, and U-Z for Legendary thru Legendary+5).
denote skills not controlled by labors.
Cells with teal backgrounds denote skills not controlled by labors, e.g.
military and social skills.
.. image:: images/manipulator2.png
Use the arrow keys or number pad to move the cursor around, holding Shift to Use the arrow keys or number pad to move the cursor around, holding Shift to
move 10 tiles at a time. move 10 tiles at a time.
@ -2012,6 +2076,8 @@ Search
The search plugin adds search to the Stocks, Trading and Unit List screens. The search plugin adds search to the Stocks, Trading and Unit List screens.
.. image:: images/search.png
Searching works the same way as the search option in "Move to Depot" does. Searching works the same way as the search option in "Move to Depot" does.
You will see the Search option displayed on screen with a hotkey (usually 's'). You will see the Search option displayed on screen with a hotkey (usually 's').
Pressing it lets you start typing a query and the relevant list will start Pressing it lets you start typing a query and the relevant list will start
@ -2032,10 +2098,48 @@ Value numbers displayed by the screen. Because of this, pressing the 't'
key while search is active clears the search instead of executing the trade. key while search is active clears the search instead of executing the trade.
AutoMaterial
============
The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.
Firstly, it moves the last used material for a given construction type to the top of
the list, if there are any left. So if you build a wall with chalk blocks, the next
time you place a wall the chalk blocks will be at the top of the list, regardless of
distance (it only does this in "grouped" mode, as individual item lists could be huge).
This should mean you can place most constructions without having to search for your
preferred material type.
.. image:: images/automaterial-mat.png
Pressing 'a' while highlighting any material will enable that material for "auto select"
for this construction type. You can enable multiple materials as autoselect. Now the next
time you place this type of construction, the plugin will automatically choose materials
for you from the kinds you enabled. If there is enough to satisfy the whole placement,
you won't be prompted with the material screen - the construction will be placed and you
will be back in the construction menu as if you did it manually.
When choosing the construction placement, you will see a couple of options:
.. image:: images/automaterial-pos.png
Use 'a' here to temporarily disable the material autoselection, e.g. if you need
to go to the material selection screen so you can toggle some materials on or off.
The other option (auto type selection, off by default) can be toggled on with 't'. If you
toggle this option on, instead of returning you to the main construction menu after selecting
materials, it returns you back to this screen. If you use this along with several autoselect
enabled materials, you should be able to place complex constructions more conveniently.
gui/liquids gui/liquids
=========== ===========
To use, bind to a key and activate in the 'k' mode. To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.
.. image:: images/liquids.png
While active, use the suggested keys to switch the usual liquids parameters, and Enter While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes. to select the target area and apply changes.
@ -2044,7 +2148,9 @@ to select the target area and apply changes.
gui/mechanisms gui/mechanisms
============== ==============
To use, bind to a key and activate in the 'q' mode. To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.
.. image:: images/mechanisms.png
Lists mechanisms connected to the building, and their links. Navigating the list centers Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings. the view on the relevant linked buildings.
@ -2062,21 +2168,35 @@ via a simple dialog in the game ui.
* ``gui/rename [building]`` in 'q' mode changes the name of a building. * ``gui/rename [building]`` in 'q' mode changes the name of a building.
.. image:: images/rename-bld.png
The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. The selected building must be one of stockpile, workshop, furnace, trap, or siege engine.
It is also possible to rename zones from the 'i' menu. It is also possible to rename zones from the 'i' menu.
* ``gui/rename [unit]`` with a unit selected changes the nickname. * ``gui/rename [unit]`` with a unit selected changes the nickname.
Unlike the built-in interface, this works even on enemies and animals.
* ``gui/rename unit-profession`` changes the selected unit's custom profession name. * ``gui/rename unit-profession`` changes the selected unit's custom profession name.
.. image:: images/rename-prof.png
Likewise, this can be applied to any unit, and when used on animals it overrides
their species string.
The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state.
The example config binds building/unit rename to Ctrl-Shift-N, and
unit profession change to Ctrl-Shift-T.
gui/room-list gui/room-list
============= =============
To use, bind to a key and activate in the 'q' mode, either immediately or after opening To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
the assign owner page. either immediately or after opening the assign owner page.
.. image:: images/room-list.png
The script lists other rooms owned by the same owner, or by the unit selected in the assign The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them. list, and allows unassigning them.
@ -2085,7 +2205,8 @@ list, and allows unassigning them.
gui/choose-weapons gui/choose-weapons
================== ==================
Bind to a key, and activate in the Equip->View/Customize page of the military screen. Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize
page of the military screen.
Depending on the cursor location, it rewrites all 'individual choice weapon' entries Depending on the cursor location, it rewrites all 'individual choice weapon' entries
in the selected squad or position to use a specific weapon type matching the assigned in the selected squad or position to use a specific weapon type matching the assigned
@ -2099,7 +2220,10 @@ and may lead to inappropriate weapons being selected.
gui/guide-path gui/guide-path
============== ==============
Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with
the cursor over a Guide order.
.. image:: images/guide-path.png
The script displays the cached path that will be used by the order; the game The script displays the cached path that will be used by the order; the game
computes it when the order is executed for the first time. computes it when the order is executed for the first time.
@ -2108,15 +2232,28 @@ computes it when the order is executed for the first time.
gui/workshop-job gui/workshop-job
================ ================
Bind to a key, and activate with a job selected in a workshop in the 'q' mode. Bind to a key (the example config uses Alt-A), and activate with a job selected in
a workshop in the 'q' mode.
.. image:: images/workshop-job.png
The script shows a list of the input reagents of the selected job, and allows changing The script shows a list of the input reagents of the selected job, and allows changing
them like the ``job item-type`` and ``job item-material`` commands. them like the ``job item-type`` and ``job item-material`` commands.
Specifically, pressing the 'i' key pops up a dialog that lets you select an item Specifically, pressing the 'i' key pops up a dialog that lets you select an item
type from a list. Pressing 'm', unless the item type does not allow a material, type from a list.
.. image:: images/workshop-job-item.png
Pressing 'm', unless the item type does not allow a material,
lets you choose a material. lets you choose a material.
.. image:: images/workshop-job-material.png
Since there are a lot more materials than item types, this dialog is more complex
and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked
with an arrow on the left.
.. warning:: .. warning::
Due to the way input reagent matching works in DF, you must select an item type Due to the way input reagent matching works in DF, you must select an item type
@ -2143,7 +2280,10 @@ you have to unset the material first.
gui/workflow gui/workflow
============ ============
Bind to a key, and activate with a job selected in a workshop in the 'q' mode. Bind to a key (the example config uses Alt-W), and activate with a job selected
in a workshop in the 'q' mode.
.. image:: images/workflow.png
This script provides a simple interface to constraints managed by the workflow This script provides a simple interface to constraints managed by the workflow
plugin. When active, it displays a list of all constraints applicable to the plugin. When active, it displays a list of all constraints applicable to the
@ -2161,17 +2301,31 @@ bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting
items and expanding the range each gives a 5x bonus). items and expanding the range each gives a 5x bonus).
Pressing 'n' produces a list of possible outputs of this job as guessed by Pressing 'n' produces a list of possible outputs of this job as guessed by
workflow, and lets you create a new constraint by just choosing one. If you workflow, and lets you create a new constraint by choosing one as template. If you
don't see the choice you want in the list, it likely means you have to adjust don't see the choice you want in the list, it likely means you have to adjust
the job material first using ``job item-material`` or ``gui/workshop-job``, the job material first using ``job item-material`` or ``gui/workshop-job``,
as described in ``workflow`` documentation above. In this manner, this feature as described in ``workflow`` documentation above. In this manner, this feature
can be used for troubleshooting jobs that don't match the right constraints. can be used for troubleshooting jobs that don't match the right constraints.
.. image:: images/workflow-new1.png
After selecting one of the presented outputs, the interface proceeds to the
next dialog, which allows you to edit the suggested constraint parameters to
suit your need, and set the item count range.
.. image:: images/workflow-new2.png
If you don't need advanced settings, you can just press 'y' to confirm creation.
gui/assign-rack gui/assign-rack
=============== ===============
Bind to a key, and activate when viewing a weapon rack in the 'q' mode. Bind to a key (the example config uses P), and activate when viewing a weapon
rack in the 'q' mode.
.. image:: images/assign-rack.png
This script is part of a group of related fixes to make the armory storage This script is part of a group of related fixes to make the armory storage
work again. The existing issues are: work again. The existing issues are:
@ -2191,7 +2345,8 @@ work again. The existing issues are:
The script interface simply lets you designate one of the squads that The script interface simply lets you designate one of the squads that
are assigned to the barracks/armory containing the selected stand as are assigned to the barracks/armory containing the selected stand as
the intended user. the intended user. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
gui/advfort gui/advfort
@ -2253,7 +2408,10 @@ Configuration UI
---------------- ----------------
The configuration front-end to the plugin is implemented by the gui/siege-engine The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key and activate after selecting a siege engine in 'q' mode. script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.
.. image:: images/siege-engine.png
The main mode displays the current target, selected ammo item type, linked stockpiles and The main mode displays the current target, selected ammo item type, linked stockpiles and
the allowed operator skill range. The map tile color is changed to signify if it can be the allowed operator skill range. The map tile color is changed to signify if it can be
@ -2283,7 +2441,10 @@ The power-meter plugin implements a modified pressure plate that detects power b
supplied to gear boxes built in the four adjacent N/S/W/E tiles. supplied to gear boxes built in the four adjacent N/S/W/E tiles.
The configuration front-end is implemented by the gui/power-meter script. Bind it to a The configuration front-end is implemented by the gui/power-meter script. Bind it to a
key and activate after selecting Pressure Plate in the build menu. key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate
in the build menu.
.. image:: images/power-meter.png
The script follows the general look and feel of the regular pressure plate build The script follows the general look and feel of the regular pressure plate build
configuration page, but configures parameters relevant to the modded power meter building. configuration page, but configures parameters relevant to the modded power meter building.

@ -133,6 +133,9 @@ tweak military-stable-assign
# in same list, color units already assigned to squads in brown & green # in same list, color units already assigned to squads in brown & green
tweak military-color-assigned tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
####################################################### #######################################################
# Apply binary patches at runtime # # Apply binary patches at runtime #
# # # #

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

@ -167,7 +167,7 @@ IF(UNIX)
IF(BUILD_EGGY) IF(BUILD_EGGY)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY})
ELSEIF(APPLE) ELSEIF(APPLE)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
ELSE() ELSE()
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX})
ENDIF() ENDIF()
@ -235,7 +235,7 @@ ENDIF()
IF(UNIX) IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE) IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF() ENDIF()
ELSE(WIN32) ELSE(WIN32)
#FIXME: do we really need psapi? #FIXME: do we really need psapi?
@ -273,14 +273,14 @@ ENDIF()
SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE) IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY})
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS})
@ -290,20 +290,20 @@ TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE) if(APPLE)
add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...")
endif() endif()
IF(UNIX) IF(UNIX)
if (APPLE) if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .) DESTINATION .)
else() else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD # On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .) DESTINATION .)
endif() endif()
ELSE() ELSE()

@ -375,7 +375,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
); );
con.print("\nDFHack version " DFHACK_VERSION ".\n"); con.print("\nDFHack version " DFHACK_VERSION ".\n");
} }
else if (parts.size() == 1) else if (parts.size() == 1)
{ {
@ -747,13 +747,13 @@ void fIOthread(void * iodata)
else if(ret) else if(ret)
{ {
// a proper, non-empty command was entered // a proper, non-empty command was entered
fprintf(stderr,"Adding command to history\n"); fprintf(stderr,"Adding command to history\n");
main_history.add(command); main_history.add(command);
fprintf(stderr,"Saving history\n"); fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history"); main_history.save("dfhack.history");
} }
fprintf(stderr,"Running command\n"); fprintf(stderr,"Running command\n");
auto rv = core->runCommand(con, command); auto rv = core->runCommand(con, command);

@ -271,39 +271,39 @@ DFhackCExport vPtr SDL_SetVideoMode(int width, int height, int bpp, uint32_t fla
static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0;
DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect)
{ {
if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 )
{ {
DFHack::Core & c = DFHack::Core::getInstance(); DFHack::Core & c = DFHack::Core::getInstance();
DFHack::Graphic* g = c.getGraphic(); DFHack::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);
if ( ov != NULL ) if ( ov != NULL )
{ {
if ( ov->paintOver ) if ( ov->paintOver )
{ {
_SDL_UpperBlit(src, srcrect, dst, dstrect); _SDL_UpperBlit(src, srcrect, dst, dstrect);
} }
DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect;
dstrect2->x = dstrect->x; dstrect2->x = dstrect->x;
dstrect2->y = dstrect->y; dstrect2->y = dstrect->y;
dstrect2->w = dstrect->w; dstrect2->w = dstrect->w;
dstrect2->h = dstrect->h; dstrect2->h = dstrect->h;
if ( ov->dstResize != NULL ) if ( ov->dstResize != NULL )
{ {
DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize;
dstrect2->x += r->x; dstrect2->x += r->x;
dstrect2->y += r->y; dstrect2->y += r->y;
dstrect2->w += r->w; dstrect2->w += r->w;
dstrect2->h += r->h; dstrect2->h += r->h;
} }
int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2);
delete dstrect2; delete dstrect2;
return result; return result;
} }
} }
return _SDL_UpperBlit(src, srcrect, dst, dstrect); return _SDL_UpperBlit(src, srcrect, dst, dstrect);
} }

@ -1180,6 +1180,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getNominalSkill), WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, computeMovementSpeed), WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),

@ -9,14 +9,14 @@
NSAutoreleasePool *thePool; NSAutoreleasePool *thePool;
int create_pool() { int create_pool() {
fprintf(stderr,"Creating autorelease pool\n"); fprintf(stderr,"Creating autorelease pool\n");
thePool = [[NSAutoreleasePool alloc] init]; thePool = [[NSAutoreleasePool alloc] init];
return 1; return 1;
} }
int destroy_pool() { int destroy_pool() {
fprintf(stderr,"Draining and releasing autorelease pool\n"); fprintf(stderr,"Draining and releasing autorelease pool\n");
[thePool drain]; [thePool drain];
[thePool release]; [thePool release];
return 0; return 0;
} }

@ -50,13 +50,13 @@ using namespace DFHack;
Process::Process(VersionInfoFactory * known_versions) Process::Process(VersionInfoFactory * known_versions)
{ {
int target_result; int target_result;
char path[1024]; char path[1024];
char *real_path; char *real_path;
uint32_t size = sizeof(path); uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
identified = false; identified = false;
my_descriptor = 0; my_descriptor = 0;
@ -166,29 +166,29 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
(vm_region_info_t)&info, &info_count, &object); (vm_region_info_t)&info, &info_count, &object);
if (kr == KERN_SUCCESS) { if (kr == KERN_SUCCESS) {
if (info.reserved==1) { if (info.reserved==1) {
address += vmsize; address += vmsize;
continue; continue;
} }
Dl_info dlinfo; Dl_info dlinfo;
int dlcheck; int dlcheck;
dlcheck = dladdr((const void*)address, &dlinfo); dlcheck = dladdr((const void*)address, &dlinfo);
if (dlcheck==0) { if (dlcheck==0) {
dlinfo.dli_fname = ""; dlinfo.dli_fname = "";
} }
t_memrange temp; t_memrange temp;
strncpy( temp.name, dlinfo.dli_fname, 1023 ); strncpy( temp.name, dlinfo.dli_fname, 1023 );
temp.name[1023] = 0; temp.name[1023] = 0;
temp.start = (void *) address; temp.start = (void *) address;
temp.end = (void *) (address+vmsize); temp.end = (void *) (address+vmsize);
temp.read = (info.protection & VM_PROT_READ); temp.read = (info.protection & VM_PROT_READ);
temp.write = (info.protection & VM_PROT_WRITE); temp.write = (info.protection & VM_PROT_WRITE);
temp.execute = (info.protection & VM_PROT_EXECUTE); temp.execute = (info.protection & VM_PROT_EXECUTE);
temp.shared = info.shared; temp.shared = info.shared;
temp.valid = true; temp.valid = true;
ranges.push_back(temp); ranges.push_back(temp);
fprintf(stderr, fprintf(stderr,
"%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n",
address, (address + vmsize), (vmsize >> 10), address, (address + vmsize), (vmsize >> 10),
(info.protection & VM_PROT_READ) ? 'r' : '-', (info.protection & VM_PROT_READ) ? 'r' : '-',
@ -203,7 +203,7 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
info.user_wired_count, info.user_wired_count,
info.reserved, info.reserved,
dlinfo.dli_fname); dlinfo.dli_fname);
address += vmsize; address += vmsize;
} else if (kr != KERN_INVALID_ADDRESS) { } else if (kr != KERN_INVALID_ADDRESS) {
@ -277,15 +277,15 @@ uint32_t Process::getTickCount()
string Process::getPath() string Process::getPath()
{ {
char path[1024]; char path[1024];
char *real_path; char *real_path;
uint32_t size = sizeof(path); uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) { if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL); real_path = realpath(path, NULL);
} }
std::string path_string(real_path); std::string path_string(real_path);
int last_slash = path_string.find_last_of("/"); int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash); std::string directory = path_string.substr(0,last_slash);
return directory; return directory;
} }
@ -304,4 +304,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
}
int Process::memDealloc(const void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(const void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -235,4 +235,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
}
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -473,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result; return result;
} }
void* Process::memAlloc(const int length)
{
void *ret;
// returns 0 on error
ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if (!ret)
ret = (void*)-1;
return ret;
}
int Process::memDealloc(void *ptr, const int length)
{
// can only free the whole region at once
// vfree returns 0 on error
return !VirtualFree(ptr, 0, MEM_RELEASE);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
DWORD old_prot = 0;
// only support a few constant combinations
if (prot == 0)
prot_native = PAGE_NOACCESS;
else if (prot == Process::MemProt::READ)
prot_native = PAGE_READONLY;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE))
prot_native = PAGE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READ;
else
return -1;
return !VirtualProtect(ptr, length, prot_native, &old_prot);
}

@ -65,20 +65,20 @@ namespace DFHack
bool save (const char * filename) bool save (const char * filename)
{ {
std::ofstream outfile (filename); std::ofstream outfile (filename);
//fprintf(stderr,"Save: Initialized stream\n"); //fprintf(stderr,"Save: Initialized stream\n");
if(outfile.bad()) if(outfile.bad())
return false; return false;
//fprintf(stderr,"Save: Iterating...\n"); //fprintf(stderr,"Save: Iterating...\n");
for(auto iter = history.begin();iter < history.end(); iter++) for(auto iter = history.begin();iter < history.end(); iter++)
{ {
//fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str()); //fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str());
outfile << *iter << std::endl; outfile << *iter << std::endl;
//fprintf(stderr,"Save: Flushing\n"); //fprintf(stderr,"Save: Flushing\n");
outfile.flush(); outfile.flush();
} }
//fprintf(stderr,"Save: Closing\n"); //fprintf(stderr,"Save: Closing\n");
outfile.close(); outfile.close();
//fprintf(stderr,"Save: Done\n"); //fprintf(stderr,"Save: Done\n");
return true; return true;
} }
/// add a command to the history /// add a command to the history

@ -35,7 +35,7 @@ distribution.
// Stop some MS stupidity // Stop some MS stupidity
#ifdef interface #ifdef interface
#undef interface #undef interface
#endif #endif
typedef struct lua_State lua_State; typedef struct lua_State lua_State;

@ -291,6 +291,27 @@ namespace DFHack
/// write a possibly read-only memory area /// write a possibly read-only memory area
bool patchMemory(void *target, const void* src, size_t count); bool patchMemory(void *target, const void* src, size_t count);
/// allocate new memory pages for code or stuff
/// returns -1 on error (0 is a valid address)
void* memAlloc(const int length);
/// free memory pages from memAlloc
/// should have length = alloced length for portability
/// returns 0 on success
int memDealloc(void *ptr, const int length);
/// change memory page permissions
/// prot is a bitwise OR of the MemProt enum
/// returns 0 on success
int memProtect(void *ptr, const int length, const int prot);
enum MemProt {
READ = 1,
WRITE = 2,
EXEC = 4
};
private: private:
VersionInfo * my_descriptor; VersionInfo * my_descriptor;
PlatformSpecific *d; PlatformSpecific *d;

@ -41,7 +41,7 @@ namespace DFHack
struct struct
{ {
//Maybe should add 'up' and 'down' for Z-levels? //Maybe should add 'up' and 'down' for Z-levels?
unsigned char north,south,west,east; unsigned char north,south,west,east;
}; };
inline TileDirection() inline TileDirection()

@ -74,6 +74,28 @@ namespace DFHack
uint32_t xpNxtLvl; uint32_t xpNxtLvl;
}; };
typedef std::pair<df::coord2d, df::coord2d> rect2d;
inline rect2d intersect(rect2d a, rect2d b) {
df::coord2d g1 = a.first, g2 = a.second;
df::coord2d c1 = b.first, c2 = b.second;
df::coord2d rc1 = df::coord2d(std::max(g1.x, c1.x), std::max(g1.y, c1.y));
df::coord2d rc2 = df::coord2d(std::min(g2.x, c2.x), std::min(g2.y, c2.y));
return rect2d(rc1, rc2);
}
inline rect2d mkrect_xy(int x1, int y1, int x2, int y2) {
return rect2d(df::coord2d(x1, y1), df::coord2d(x2, y2));
}
inline rect2d mkrect_wh(int x, int y, int w, int h) {
return rect2d(df::coord2d(x, y), df::coord2d(x+w-1, y+h-1));
}
inline df::coord2d rect_size(const rect2d &rect) {
return rect.second - rect.first + df::coord2d(1,1);
}
DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files); DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files);
DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending);

@ -27,7 +27,7 @@ distribution.
#include "Pragma.h" #include "Pragma.h"
#include "Export.h" #include "Export.h"
#include "Types.h" /* #include "Types.h" */
#include <map> #include <map>
#include <sys/types.h> #include <sys/types.h>
#include <vector> #include <vector>

@ -36,55 +36,55 @@ distribution.
namespace DFHack namespace DFHack
{ {
// SDL stuff // SDL stuff
typedef signed short SINT16; typedef signed short SINT16;
typedef struct typedef struct
{ {
int16_t x, y; int16_t x, y;
uint16_t w, h; uint16_t w, h;
} DFSDL_Rect; } DFSDL_Rect;
typedef struct typedef struct
{ {
uint32_t flags; uint32_t flags;
void* format; // PixelFormat* void* format; // PixelFormat*
int w, h; int w, h;
int pitch; int pitch;
void* pixels; void* pixels;
void* userdata; // as far as i could see DF doesnt use this void* userdata; // as far as i could see DF doesnt use this
int locked; int locked;
void* lock_data; void* lock_data;
DFSDL_Rect clip_rect; DFSDL_Rect clip_rect;
void* map; void* map;
int refcount; int refcount;
} DFSDL_Surface; } DFSDL_Surface;
// ========= // =========
struct DFTileSurface struct DFTileSurface
{ {
bool paintOver; // draw over original tile? bool paintOver; // draw over original tile?
DFSDL_Surface* surface; // from where it should be drawn DFSDL_Surface* surface; // from where it should be drawn
DFSDL_Rect* rect; // from which coords (NULL to draw whole surface) DFSDL_Rect* rect; // from which coords (NULL to draw whole surface)
DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst)
}; };
class DFHACK_EXPORT Graphic : public Module class DFHACK_EXPORT Graphic : public Module
{ {
public: public:
Graphic(); Graphic();
~Graphic(); ~Graphic();
bool Finish() bool Finish()
{ {
return true; return true;
} }
bool Register(DFTileSurface* (*func)(int,int)); bool Register(DFTileSurface* (*func)(int,int));
bool Unregister(DFTileSurface* (*func)(int,int)); bool Unregister(DFTileSurface* (*func)(int,int));
DFTileSurface* Call(int x, int y); DFTileSurface* Call(int x, int y);
private: private:
struct Private; struct Private;
Private *d; Private *d;
}; };
} }

@ -29,6 +29,8 @@ distribution.
#include "ColorText.h" #include "ColorText.h"
#include <string> #include <string>
#include "Types.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/init.h" #include "df/init.h"
#include "df/ui.h" #include "df/ui.h"
@ -116,6 +118,9 @@ namespace DFHack
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2; int y1, y2;
bool menu_on, area_on, menu_forced; bool menu_on, area_on, menu_forced;
rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); }
rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); }
}; };
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();

@ -111,8 +111,8 @@ public:
{ {
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
return t_matpair( return t_matpair(
index_tile<int16_t>(basemats->mattype,p), index_tile<int16_t>(basemats->mat_type,p),
index_tile<int16_t>(basemats->matindex,p) index_tile<int16_t>(basemats->mat_index,p)
); );
} }
bool isVeinAt(df::coord2d p) bool isVeinAt(df::coord2d p)
@ -151,8 +151,8 @@ public:
if (!basemats) init_tiles(true); if (!basemats) init_tiles(true);
if (tiles->con_info) if (tiles->con_info)
return t_matpair( return t_matpair(
index_tile<int16_t>(tiles->con_info->mattype,p), index_tile<int16_t>(tiles->con_info->mat_type,p),
index_tile<int16_t>(tiles->con_info->matindex,p) index_tile<int16_t>(tiles->con_info->mat_index,p)
); );
return baseMaterialAt(p); return baseMaterialAt(p);
} }
@ -284,8 +284,8 @@ private:
struct ConInfo { struct ConInfo {
df::tile_bitmask constructed; df::tile_bitmask constructed;
df::tiletype tiles[16][16]; df::tiletype tiles[16][16];
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
}; };
struct TileInfo { struct TileInfo {
df::tile_bitmask frozen; df::tile_bitmask frozen;
@ -304,8 +304,8 @@ private:
}; };
struct BasematInfo { struct BasematInfo {
df::tile_bitmask dirty; df::tile_bitmask dirty;
t_blockmaterials mattype; t_blockmaterials mat_type;
t_blockmaterials matindex; t_blockmaterials mat_index;
t_blockmaterials layermat; t_blockmaterials layermat;
BasematInfo(); BasematInfo();

@ -338,10 +338,10 @@ namespace DFHack
*/ */
struct t_material struct t_material
{ {
t_itemType itemType; t_itemType item_type;
t_itemSubtype subType; t_itemSubtype item_subtype;
t_materialType material; t_materialType mat_type;
t_materialIndex index; t_materialIndex mat_index;
uint32_t flags; uint32_t flags;
}; };
/** /**

@ -27,7 +27,10 @@ distribution.
#include "Module.h" #include "Module.h"
#include "BitArray.h" #include "BitArray.h"
#include "ColorText.h" #include "ColorText.h"
#include "Types.h"
#include <string> #include <string>
#include <set>
#include "DataDefs.h" #include "DataDefs.h"
#include "df/graphic.h" #include "df/graphic.h"
@ -50,6 +53,8 @@ namespace DFHack
{ {
class Core; class Core;
typedef std::set<df::interface_key> interface_key_set;
/** /**
* The Screen module * The Screen module
* \ingroup grp_modules * \ingroup grp_modules
@ -94,11 +99,67 @@ namespace DFHack
: ch(ch), fg(fg), bg(bg), bold(bold), : ch(ch), fg(fg), bg(bg), bold(bold),
tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg)
{} {}
void adjust(int8_t nfg) { fg = nfg&7; bold = !!(nfg&8); }
void adjust(int8_t nfg, bool nbold) { fg = nfg; bold = nbold; }
void adjust(int8_t nfg, int8_t nbg) { adjust(nfg); bg = nbg; }
void adjust(int8_t nfg, bool nbold, int8_t nbg) { adjust(nfg, nbold); bg = nbg; }
Pen color(int8_t nfg) const { Pen cp(*this); cp.adjust(nfg); return cp; }
Pen color(int8_t nfg, bool nbold) const { Pen cp(*this); cp.adjust(nfg, nbold); return cp; }
Pen color(int8_t nfg, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbg); return cp; }
Pen color(int8_t nfg, bool nbold, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbold, nbg); return cp; }
Pen chtile(char ch) { Pen cp(*this); cp.ch = ch; return cp; }
Pen chtile(char ch, int tile) { Pen cp(*this); cp.ch = ch; cp.tile = tile; return cp; }
};
struct DFHACK_EXPORT ViewRect {
rect2d view, clip;
ViewRect(rect2d area) : view(area), clip(area) {}
ViewRect(rect2d area, rect2d clip) : view(area), clip(clip) {}
bool isDefunct() const {
return clip.first.x > clip.second.x || clip.first.y > clip.second.y;
}
int width() const { return view.second.x-view.first.x+1; }
int height() const { return view.second.y-view.first.y+1; }
df::coord2d local(df::coord2d pos) const {
return df::coord2d(pos.x - view.first.x, pos.y - view.first.y);
}
df::coord2d global(df::coord2d pos) const {
return df::coord2d(pos.x + view.first.x, pos.y + view.first.y);
}
df::coord2d global(int x, int y) const {
return df::coord2d(x + view.first.x, y + view.first.y);
}
bool inClipGlobal(int x, int y) const {
return x >= clip.first.x && x <= clip.second.x &&
y >= clip.first.y && y <= clip.second.y;
}
bool inClipGlobal(df::coord2d pos) const {
return inClipGlobal(pos.x, pos.y);
}
bool inClipLocal(int x, int y) const {
return inClipGlobal(x + view.first.x, y + view.first.y);
}
bool inClipLocal(df::coord2d pos) const {
return inClipLocal(pos.x, pos.y);
}
ViewRect viewport(rect2d area) const {
rect2d nview(global(area.first), global(area.second));
return ViewRect(nview, intersect(nview, clip));
}
}; };
DFHACK_EXPORT df::coord2d getMousePos(); DFHACK_EXPORT df::coord2d getMousePos();
DFHACK_EXPORT df::coord2d getWindowSize(); DFHACK_EXPORT df::coord2d getWindowSize();
inline rect2d getScreenRect() {
return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1));
}
/// Returns the state of [GRAPHICS:YES/NO] /// Returns the state of [GRAPHICS:YES/NO]
DFHACK_EXPORT bool inGraphicsMode(); DFHACK_EXPORT bool inGraphicsMode();
@ -133,6 +194,77 @@ namespace DFHack
/// Retrieve the string representation of the bound key. /// Retrieve the string representation of the bound key.
DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key);
/// A painter class that implements a clipping area and cursor/pen state
struct DFHACK_EXPORT Painter : ViewRect {
df::coord2d gcursor;
Pen cur_pen, cur_key_pen;
static const Pen default_pen;
static const Pen default_key_pen;
Painter(const ViewRect &area, const Pen &pen = default_pen, const Pen &kpen = default_key_pen)
: ViewRect(area), gcursor(area.view.first), cur_pen(pen), cur_key_pen(kpen)
{}
df::coord2d cursor() const { return local(gcursor); }
int cursorX() const { return gcursor.x - view.first.x; }
int cursorY() const { return gcursor.y - view.first.y; }
bool isValidPos() const { return inClipGlobal(gcursor); }
Painter viewport(rect2d area) const {
return Painter(ViewRect::viewport(area), cur_pen, cur_key_pen);
}
Painter &seek(df::coord2d pos) { gcursor = global(pos); return *this; }
Painter &seek(int x, int y) { gcursor = global(x,y); return *this; }
Painter &advance(int dx) { gcursor.x += dx; return *this; }
Painter &advance(int dx, int dy) { gcursor.x += dx; gcursor.y += dy; return *this; }
Painter &newline(int dx = 0) { gcursor.y++; gcursor.x = view.first.x + dx; return *this; }
const Pen &pen() const { return cur_pen; }
Painter &pen(const Pen &np) { cur_pen = np; return *this; }
Painter &pen(int8_t fg) { cur_pen.adjust(fg); return *this; }
const Pen &key_pen() const { return cur_key_pen; }
Painter &key_pen(const Pen &np) { cur_key_pen = np; return *this; }
Painter &key_pen(int8_t fg) { cur_key_pen.adjust(fg); return *this; }
Painter &clear() {
fillRect(Pen(' ',0,0,false), clip.first.x, clip.first.y, clip.second.x, clip.second.y);
return *this;
}
Painter &fill(const rect2d &area, const Pen &pen) {
rect2d irect = intersect(area, clip);
fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y);
return *this;
}
Painter &fill(const rect2d &area) { return fill(area, cur_pen); }
Painter &tile(const Pen &pen) {
if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y);
return advance(1);
}
Painter &tile() { return tile(cur_pen); }
Painter &tile(char ch) { return tile(cur_pen.chtile(ch)); }
Painter &tile(char ch, int tileid) { return tile(cur_pen.chtile(ch, tileid)); }
Painter &string(const std::string &str, const Pen &pen) {
do_paint_string(str, pen); return advance(str.size());
}
Painter &string(const std::string &str) { return string(str, cur_pen); }
Painter &string(const std::string &str, int8_t fg) { return string(str, cur_pen.color(fg)); }
Painter &key(df::interface_key kc, const Pen &pen) {
return string(getKeyDisplay(kc), pen);
}
Painter &key(df::interface_key kc) { return key(kc, cur_key_pen); }
private:
void do_paint_string(const std::string &str, const Pen &pen);
};
} }
class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen {

@ -238,6 +238,8 @@ DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {

@ -81,6 +81,55 @@ namespace DFHack
int &ival(int i) { return int_values[i]; } int &ival(int i) { return int_values[i]; }
int ival(int i) const { return int_values[i]; } int ival(int i) const { return int_values[i]; }
// Pack binary data into string field.
// Since DF serialization chokes on NUL bytes,
// use bit magic to ensure none of the bytes is 0.
// Choose the lowest bit for padding so that
// sign-extend can be used normally.
size_t data_size() const { return str_value->size(); }
bool check_data(size_t off, size_t sz = 1) {
return (str_value->size() >= off+sz);
}
void ensure_data(size_t off, size_t sz = 0) {
if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01');
}
uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; }
static const size_t int7_size = 1;
uint8_t get_uint7(size_t off) {
uint8_t *p = pdata(off);
return p[0]>>1;
}
int8_t get_int7(size_t off) {
uint8_t *p = pdata(off);
return int8_t(p[0])>>1;
}
void set_uint7(size_t off, uint8_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
}
void set_int7(size_t off, int8_t val) { set_uint7(off, val); }
static const size_t int28_size = 4;
uint32_t get_uint28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20);
}
int32_t get_int28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20);
}
void set_uint28(size_t off, uint32_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
p[1] = uint8_t((val>>6) | 1);
p[2] = uint8_t((val>>13) | 1);
p[3] = uint8_t((val>>20) | 1);
}
void set_int28(size_t off, int32_t val) { set_uint28(off, val); }
PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem() : id(0), str_value(0), int_values(0) {}
PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv)
: id(id), key_value(key), str_value(sv), int_values(iv) {} : id(id), key_value(key), str_value(sv), int_values(iv) {}

@ -10,13 +10,19 @@ local to_pen = dfhack.pen.parse
CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} CLEAR_PEN = to_pen{ch=32,fg=0,bg=0}
local FAKE_INPUT_KEYS = {
_MOUSE_L = true,
_MOUSE_R = true,
_STRING = true,
}
function simulateInput(screen,...) function simulateInput(screen,...)
local keys = {} local keys = {}
local function push_key(arg) local function push_key(arg)
local kv = arg local kv = arg
if type(arg) == 'string' then if type(arg) == 'string' then
kv = df.interface_key[arg] kv = df.interface_key[arg]
if kv == nil then if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg) error('Invalid keycode: '..arg)
end end
end end

@ -23,6 +23,7 @@ MaterialDialog.ATTRS{
frame_title = 'Select Material', frame_title = 'Select Material',
-- new attrs -- new attrs
none_caption = 'none', none_caption = 'none',
hide_none = false,
use_inorganic = true, use_inorganic = true,
use_creature = true, use_creature = true,
use_plant = true, use_plant = true,
@ -68,7 +69,7 @@ function MaterialDialog:init(info)
end end
function MaterialDialog:getWantedFrameSize(rect) function MaterialDialog:getWantedFrameSize(rect)
return math.max(40, #self.prompt), math.min(28, rect.height-8) return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end end
function MaterialDialog:onDestroy() function MaterialDialog:onDestroy()
@ -78,9 +79,10 @@ function MaterialDialog:onDestroy()
end end
function MaterialDialog:initBuiltinMode() function MaterialDialog:initBuiltinMode()
local choices = { local choices = {}
{ text = self.none_caption, mat_type = -1, mat_index = -1 }, if not self.hide_none then
} table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 })
end
if self.use_inorganic then if self.use_inorganic then
table.insert(choices, { table.insert(choices, {
@ -281,9 +283,15 @@ function ItemTypeDialog(args)
args.with_filter = true args.with_filter = true
args.icon_width = 2 args.icon_width = 2
local choices = { { local choices = {}
icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1
} } if not args.hide_none then
table.insert(choices, {
icon = '?', text = args.none_caption or 'none',
item_type = -1, item_subtype = -1
})
end
local filter = args.item_filter local filter = args.item_filter
for itype = 0,df.item_type._last_item do for itype = 0,df.item_type._last_item do

@ -488,6 +488,18 @@ function List:onRenderBody(dc)
local iend = math.min(#choices, top+self.page_size-1) local iend = math.min(#choices, top+self.page_size-1)
local iw = self.icon_width local iw = self.icon_width
local function paint_icon(icon, obj)
if type(icon) ~= 'string' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
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)
@ -499,30 +511,28 @@ function List:onRenderBody(dc)
end end
local y = (i - top)*self.row_height local y = (i - top)*self.row_height
local icon = getval(obj.icon)
if iw and obj.icon then if iw and icon then
local icon = getval(obj.icon) dc:seek(0, y)
if icon then paint_icon(icon, obj)
dc:seek(0, y)
if type(icon) ~= 'string' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
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)
local ip = dc.width
if obj.key then if obj.key then
local keystr = gui.getKeyDisplay(obj.key) local keystr = gui.getKeyDisplay(obj.key)
dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) ip = ip-2-#keystr
dc:seek(ip,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end end
if icon and not iw then
dc:seek(ip-1,y)
paint_icon(icon, obj)
end
end end
end end

@ -48,63 +48,63 @@ Module* DFHack::createGraphic()
struct Graphic::Private struct Graphic::Private
{ {
bool Started; bool Started;
vector<DFTileSurface* (*)(int,int)> funcs; vector<DFTileSurface* (*)(int,int)> funcs;
}; };
Graphic::Graphic() Graphic::Graphic()
{ {
d = new Private; d = new Private;
d->Started = true; d->Started = true;
} }
Graphic::~Graphic() Graphic::~Graphic()
{ {
delete d; delete d;
} }
bool Graphic::Register(DFTileSurface* (*func)(int,int)) bool Graphic::Register(DFTileSurface* (*func)(int,int))
{ {
d->funcs.push_back(func); d->funcs.push_back(func);
return true; return true;
} }
bool Graphic::Unregister(DFTileSurface* (*func)(int,int)) bool Graphic::Unregister(DFTileSurface* (*func)(int,int))
{ {
if ( d->funcs.empty() ) return false; if ( d->funcs.empty() ) return false;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin(); vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() ) while ( it != d->funcs.end() )
{ {
if ( *it == func ) if ( *it == func )
{ {
d->funcs.erase(it); d->funcs.erase(it);
return true; return true;
} }
it++; it++;
} }
return false; return false;
} }
// This will return first DFTileSurface it can get (or NULL if theres none) // This will return first DFTileSurface it can get (or NULL if theres none)
DFTileSurface* Graphic::Call(int x, int y) DFTileSurface* Graphic::Call(int x, int y)
{ {
if ( d->funcs.empty() ) return NULL; if ( d->funcs.empty() ) return NULL;
DFTileSurface* temp = NULL; DFTileSurface* temp = NULL;
vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin(); vector<DFTileSurface* (*)(int,int)>::iterator it = d->funcs.begin();
while ( it != d->funcs.end() ) while ( it != d->funcs.end() )
{ {
temp = (*it)(x,y); temp = (*it)(x,y);
if ( temp != NULL ) if ( temp != NULL )
{ {
return temp; return temp;
} }
it++; it++;
} }
return NULL; return NULL;
} }

@ -495,10 +495,10 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item)
item.id = itreal->id; item.id = itreal->id;
item.age = itreal->age; item.age = itreal->age;
item.flags = itreal->flags; item.flags = itreal->flags;
item.matdesc.itemType = itreal->getType(); item.matdesc.item_type = itreal->getType();
item.matdesc.subType = itreal->getSubtype(); item.matdesc.item_subtype = itreal->getSubtype();
item.matdesc.material = itreal->getMaterial(); item.matdesc.mat_type = itreal->getMaterial();
item.matdesc.index = itreal->getMaterialIndex(); item.matdesc.mat_index = itreal->getMaterialIndex();
item.wear_level = itreal->getWear(); item.wear_level = itreal->getWear();
item.quality = itreal->getQuality(); item.quality = itreal->getQuality();
item.quantity = itreal->getStackSize(); item.quantity = itreal->getStackSize();

@ -650,15 +650,15 @@ void MapExtras::Block::TileInfo::init_coninfo()
con_info = new ConInfo(); con_info = new ConInfo();
con_info->constructed.clear(); con_info->constructed.clear();
COPY(con_info->tiles, base_tiles); COPY(con_info->tiles, base_tiles);
memset(con_info->mattype, -1, sizeof(con_info->mattype)); memset(con_info->mat_type, -1, sizeof(con_info->mat_type));
memset(con_info->matindex, -1, sizeof(con_info->matindex)); memset(con_info->mat_index, -1, sizeof(con_info->mat_index));
} }
MapExtras::Block::BasematInfo::BasematInfo() MapExtras::Block::BasematInfo::BasematInfo()
{ {
dirty.clear(); dirty.clear();
memset(mattype,0,sizeof(mattype)); memset(mat_type,0,sizeof(mat_type));
memset(matindex,-1,sizeof(matindex)); memset(mat_index,-1,sizeof(mat_index));
memset(layermat,-1,sizeof(layermat)); memset(layermat,-1,sizeof(layermat));
} }
@ -719,8 +719,8 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles)
is_con = true; is_con = true;
tiles->con_info->constructed.setassignment(x,y,true); tiles->con_info->constructed.setassignment(x,y,true);
tiles->con_info->tiles[x][y] = tt; tiles->con_info->tiles[x][y] = tt;
tiles->con_info->mattype[x][y] = con->mat_type; tiles->con_info->mat_type[x][y] = con->mat_type;
tiles->con_info->matindex[x][y] = con->mat_index; tiles->con_info->mat_index[x][y] = con->mat_index;
tt = con->original_tile; tt = con->original_tile;
} }
@ -753,14 +753,14 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats)
auto tt = tiles->base_tiles[x][y]; auto tt = tiles->base_tiles[x][y];
auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); auto mat = info.getBaseMaterial(tt, df::coord2d(x,y));
bmats->mattype[x][y] = mat.mat_type; bmats->mat_type[x][y] = mat.mat_type;
bmats->matindex[x][y] = mat.mat_index; bmats->mat_index[x][y] = mat.mat_index;
// Copy base info back to construction layer // Copy base info back to construction layer
if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y))
{ {
tiles->con_info->mattype[x][y] = mat.mat_type; tiles->con_info->mat_type[x][y] = mat.mat_type;
tiles->con_info->matindex[x][y] = mat.mat_index; tiles->con_info->mat_index[x][y] = mat.mat_index;
} }
} }
} }

@ -840,7 +840,7 @@ bool Materials::ReadAllMaterials(void)
std::string Materials::getDescription(const t_material & mat) std::string Materials::getDescription(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
if (mi.creature) if (mi.creature)
return mi.creature->creature_id + " " + mi.material->id; return mi.creature->creature_id + " " + mi.material->id;
else if (mi.plant) else if (mi.plant)
@ -853,7 +853,7 @@ std::string Materials::getDescription(const t_material & mat)
// This is completely worthless now // This is completely worthless now
std::string Materials::getType(const t_material & mat) std::string Materials::getType(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
switch (mi.mode) switch (mi.mode)
{ {
case MaterialInfo::Builtin: case MaterialInfo::Builtin:

@ -110,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
{ {
if (!gps || !pen.valid()) return false; if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false;
doSetTile(pen, x*dimy + y); doSetTile(pen, x*dim.y + y);
return true; return true;
} }
@ -121,11 +121,11 @@ Pen Screen::readTile(int x, int y)
{ {
if (!gps) return Pen(0,0,0,-1); if (!gps) return Pen(0,0,0,-1);
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
int index = x*dimy + y; int index = x*dim.y + y;
auto screen = gps->screen + index*4; auto screen = gps->screen + index*4;
if (screen[3] & 0x80) if (screen[3] & 0x80)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
@ -154,14 +154,15 @@ Pen Screen::readTile(int x, int y)
bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
{ {
if (!gps || y < 0 || y >= gps->dimy) return false; auto dim = getWindowSize();
if (!gps || y < 0 || y >= dim.y) return false;
Pen tmp(pen); Pen tmp(pen);
bool ok = false; bool ok = false;
for (size_t i = -std::min(0,x); i < text.size(); i++) for (size_t i = -std::min(0,x); i < text.size(); i++)
{ {
if (x + i >= size_t(gps->dimx)) if (x + i >= size_t(dim.x))
break; break;
tmp.ch = text[i]; tmp.ch = text[i];
@ -175,17 +176,18 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
{ {
auto dim = getWindowSize();
if (!gps || !pen.valid()) return false; if (!gps || !pen.valid()) return false;
if (x1 < 0) x1 = 0; if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0; if (y1 < 0) y1 = 0;
if (x2 >= gps->dimx) x2 = gps->dimx-1; if (x2 >= dim.x) x2 = dim.x-1;
if (y2 >= gps->dimy) y2 = gps->dimy-1; if (y2 >= dim.y) y2 = dim.y-1;
if (x1 > x2 || y1 > y2) return false; if (x1 > x2 || y1 > y2) return false;
for (int x = x1; x <= x2; x++) for (int x = x1; x <= x2; x++)
{ {
int index = x*gps->dimy; int index = x*dim.y;
for (int y = y1; y <= y2; y++) for (int y = y1; y <= y2; y++)
doSetTile(pen, index+y); doSetTile(pen, index+y);
@ -198,32 +200,33 @@ bool Screen::drawBorder(const std::string &title)
{ {
if (!gps) return false; if (!gps) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
Pen border('\xDB', 8); Pen border('\xDB', 8);
Pen text(0, 0, 7); Pen text(0, 0, 7);
Pen signature(0, 0, 8); Pen signature(0, 0, 8);
for (int x = 0; x < dimx; x++) for (int x = 0; x < dim.x; x++)
{ {
doSetTile(border, x * dimy + 0); doSetTile(border, x * dim.y + 0);
doSetTile(border, x * dimy + dimy - 1); doSetTile(border, x * dim.y + dim.y - 1);
} }
for (int y = 0; y < dimy; y++) for (int y = 0; y < dim.y; y++)
{ {
doSetTile(border, 0 * dimy + y); doSetTile(border, 0 * dim.y + y);
doSetTile(border, (dimx - 1) * dimy + y); doSetTile(border, (dim.x - 1) * dim.y + y);
} }
paintString(signature, dimx-8, dimy-1, "DFHack"); paintString(signature, dim.x-8, dim.y-1, "DFHack");
return paintString(text, (dimx - title.length()) / 2, 0, title); return paintString(text, (dim.x - title.length()) / 2, 0, title);
} }
bool Screen::clear() bool Screen::clear()
{ {
if (!gps) return false; if (!gps) return false;
return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); auto dim = getWindowSize();
return fillRect(Pen(' ',0,0,false), 0, 0, dim.x-1, dim.y-1);
} }
bool Screen::invalidate() bool Screen::invalidate()
@ -234,6 +237,21 @@ bool Screen::invalidate()
return true; return true;
} }
const Pen Screen::Painter::default_pen(0,COLOR_GREY,0);
const Pen Screen::Painter::default_key_pen(0,COLOR_LIGHTGREEN,0);
void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen)
{
if (gcursor.y < clip.first.y || gcursor.y > clip.second.y)
return;
int dx = std::max(0, int(clip.first.x - gcursor.x));
int len = std::min((int)str.size(), int(clip.second.x - gcursor.x + 1));
if (len > dx)
paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx));
}
bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs)
{ {
if (!gps || !texture || x < 0 || y < 0) return false; if (!gps || !texture || x < 0 || y < 0) return false;

@ -405,7 +405,7 @@ bool Creatures::WriteJob(const t_creature * furball, std::vector<t_material> con
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType);
p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].subType); p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype);
p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex);
p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index);
p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags);
@ -475,7 +475,7 @@ bool Creatures::ReadJob(const t_creature * furball, vector<t_material> & mat)
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o); mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o);
mat[i].subType = p->readWord(cmats[i] + off.job_material_subtype_o); mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o);
mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o);
mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o);
mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o);
@ -909,6 +909,24 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust
return 0; return 0;
} }
int Units::getExperience(df::unit *unit, df::job_skill skill_id, bool total)
{
CHECK_NULL_POINTER(unit);
if (!unit->status.current_soul)
return 0;
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id);
if (!skill)
return 0;
int xp = skill->experience;
// exact formula used by the game:
if (total && skill->rating > 0)
xp += 500*skill->rating + 100*skill->rating*(skill->rating - 1)/2;
return xp;
}
int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
{ {
/* /*

@ -197,11 +197,15 @@ PersistentDataItem World::AddPersistentData(const std::string &key)
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector(); std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
df::historical_figure *hfig = new df::historical_figure(); df::historical_figure *hfig = new df::historical_figure();
hfig->id = next_persistent_id--; hfig->id = next_persistent_id;
hfig->name.has_name = true; hfig->name.has_name = true;
hfig->name.first_name = key; hfig->name.first_name = key;
memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); memset(hfig->name.words, 0xFF, sizeof(hfig->name.words));
if (!hfvec.empty())
hfig->id = std::min(hfig->id, hfvec[0]->id-1);
next_persistent_id = hfig->id-1;
hfvec.insert(hfvec.begin(), hfig); hfvec.insert(hfvec.begin(), hfig);
persistent_index.insert(T_persistent_item(key, -hfig->id)); persistent_index.insert(T_persistent_item(key, -hfig->id));

@ -1 +1 @@
Subproject commit 327a9662be81627ebcbb3aea11ffbca3e536b7ee Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc

@ -118,6 +118,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(search search.cpp) DFHACK_PLUGIN(search search.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp)
# this one exports functions to lua # this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)

@ -161,7 +161,7 @@ static command_result autodump_main(color_ostream &out, vector <string> & parame
|| itm->flags.bits.in_building || itm->flags.bits.in_building
|| itm->flags.bits.in_chest || itm->flags.bits.in_chest
// || itm->flags.bits.in_inventory // || itm->flags.bits.in_inventory
|| itm->flags.bits.artifact1 || itm->flags.bits.artifact
) )
continue; continue;
@ -271,7 +271,7 @@ command_result df_autodump_destroy_item(color_ostream &out, vector <string> & pa
if (item->flags.bits.construction || if (item->flags.bits.construction ||
item->flags.bits.in_building || item->flags.bits.in_building ||
item->flags.bits.artifact1) item->flags.bits.artifact)
{ {
out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n"); out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n");
return CR_FAILURE; return CR_FAILURE;

@ -964,7 +964,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
else if (building_type::TradeDepot == type) else if (building_type::TradeDepot == type)
{ {
df::building_tradedepotst* depot = (df::building_tradedepotst*) build; df::building_tradedepotst* depot = (df::building_tradedepotst*) build;
trader_requested = depot->trade_flags.bits.trader_requested; trader_requested = trader_requested || depot->trade_flags.bits.trader_requested;
if (print_debug) if (print_debug)
{ {
if (trader_requested) if (trader_requested)
@ -1556,7 +1556,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
#define F(x) bad_flags.bits.x = true; #define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect); F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader); F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1); F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job); F(spider_web); F(owned); F(in_job);
#undef F #undef F

@ -0,0 +1,378 @@
// Auto Material Select
#include <map>
#include <string>
#include <vector>
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <VTableInterpose.h>
// DF data structure definition headers
#include "DataDefs.h"
#include "MiscUtils.h"
#include "df/build_req_choice_genst.h"
#include "df/build_req_choice_specst.h"
#include "df/construction_type.h"
#include "df/item.h"
#include "df/ui.h"
#include "df/ui_build_selector.h"
#include "df/viewscreen_dwarfmodest.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
using std::map;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::ui;
using df::global::ui_build_selector;
DFHACK_PLUGIN("automaterial");
struct MaterialDescriptor
{
df::item_type item_type;
int16_t item_subtype;
int16_t type;
int32_t index;
bool valid;
bool matches(const MaterialDescriptor &a) const
{
return a.valid && valid &&
a.type == type &&
a.index == index &&
a.item_type == item_type &&
a.item_subtype == item_subtype;
}
};
static map<int16_t, MaterialDescriptor> last_used_material;
static map<int16_t, MaterialDescriptor> last_moved_material;
static map< int16_t, vector<MaterialDescriptor> > preferred_materials;
static map< int16_t, df::interface_key > hotkeys;
static bool last_used_moved = false;
static bool auto_choose_materials = true;
static bool auto_choose_attempted = true;
static bool revert_to_last_used_type = false;
static command_result automaterial_cmd(color_ostream &out, vector <string> & parameters)
{
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
static inline bool in_material_choice_stage()
{
return Gui::build_selector_hotkey(Core::getTopViewscreen()) &&
ui_build_selector->building_type == df::building_type::Construction &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector->stage == 2;
}
static inline bool in_placement_stage()
{
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->building_type == df::building_type::Construction &&
ui_build_selector->stage == 1;
}
static inline bool in_type_choice_stage()
{
return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) &&
ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector &&
ui_build_selector->building_type < 0;
}
static inline vector<MaterialDescriptor> &get_curr_constr_prefs()
{
if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end())
preferred_materials[ui_build_selector->building_subtype] = vector<MaterialDescriptor>();
return preferred_materials[ui_build_selector->building_subtype];
}
static inline MaterialDescriptor &get_last_used_material()
{
if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end())
last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor();
return last_used_material[ui_build_selector->building_subtype];
}
static void set_last_used_material(const MaterialDescriptor &matetial)
{
last_used_material[ui_build_selector->building_subtype] = matetial;
}
static MaterialDescriptor &get_last_moved_material()
{
if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end())
last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor();
return last_moved_material[ui_build_selector->building_subtype];
}
static void set_last_moved_material(const MaterialDescriptor &matetial)
{
last_moved_material[ui_build_selector->building_subtype] = matetial;
}
static MaterialDescriptor get_material_in_list(size_t i)
{
MaterialDescriptor result;
result.valid = false;
if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i]))
{
result.item_type = gen->item_type;
result.item_subtype = gen->item_subtype;
result.type = gen->mat_type;
result.index = gen->mat_index;
result.valid = true;
}
else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i]))
{
result.item_type = gen->item_type;
result.item_subtype = gen->item_subtype;
result.type = spec->candidate->getActualMaterial();
result.index = spec->candidate->getActualMaterialIndex();
result.valid = true;
}
return result;
}
static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material)
{
for (i = 0; i < get_curr_constr_prefs().size(); i++)
{
if (get_curr_constr_prefs()[i].matches(material))
return true;
}
return false;
}
static bool is_material_in_list(size_t &i, MaterialDescriptor &material)
{
const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big
for (i = 0; i < size; i++)
{
if (get_material_in_list(i).matches(material))
return true;
}
return false;
}
static bool move_material_to_top(MaterialDescriptor &material)
{
size_t i;
if (is_material_in_list(i, material))
{
auto sel_item = ui_build_selector->choices[i];
ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i);
ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item);
ui_build_selector->sel_index = 0;
set_last_moved_material(material);
return true;
}
set_last_moved_material(MaterialDescriptor());
return false;
}
static bool check_autoselect(MaterialDescriptor &material, bool toggle)
{
size_t idx;
if (is_material_in_autoselect(idx, material))
{
if (toggle)
vector_erase_at(get_curr_constr_prefs(), idx);
return true;
}
else
{
if (toggle)
get_curr_constr_prefs().push_back(material);
return false;
}
}
struct jobutils_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
bool choose_materials()
{
size_t size = ui_build_selector->choices.size();
for (size_t i = 0; i < size; i++)
{
MaterialDescriptor material = get_material_in_list(i);
size_t j;
if (is_material_in_autoselect(j, material))
{
ui_build_selector->sel_index = i;
std::set< df::interface_key > keys;
keys.insert(df::interface_key::SELECT_ALL);
this->feed(&keys);
if (!in_material_choice_stage())
return true;
}
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (in_material_choice_stage())
{
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
if (material.valid)
{
if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT))
{
if (get_last_moved_material().matches(material))
last_used_moved = false;
set_last_used_material(material);
}
else if (input->count(interface_key::CUSTOM_A))
{
check_autoselect(material, true);
input->clear();
}
}
}
else if (in_placement_stage())
{
if (input->count(interface_key::CUSTOM_A))
{
auto_choose_materials = !auto_choose_materials;
}
else if (input->count(interface_key::CUSTOM_T))
{
revert_to_last_used_type = !revert_to_last_used_type;
}
}
int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1;
INTERPOSE_NEXT(feed)(input);
if (revert_to_last_used_type &&
last_used_constr_subtype >= 0 &&
!in_material_choice_stage() &&
hotkeys.find(last_used_constr_subtype) != hotkeys.end())
{
interface_key_set keys;
keys.insert(hotkeys[last_used_constr_subtype]);
INTERPOSE_NEXT(feed)(&keys);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
if (in_material_choice_stage())
{
if (!last_used_moved)
{
if (auto_choose_materials && get_curr_constr_prefs().size() > 0)
{
last_used_moved = true;
if (choose_materials())
{
return;
}
}
else if (ui_build_selector->is_grouped)
{
last_used_moved = true;
move_material_to_top(get_last_used_material());
}
}
else if (!ui_build_selector->is_grouped)
{
last_used_moved = false;
}
}
else
{
last_used_moved = false;
}
INTERPOSE_NEXT(render)();
if (in_material_choice_stage())
{
MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index);
if (material.valid)
{
string title = "Disabled";
if (check_autoselect(material, false))
{
title = "Enabled";
}
auto dims = Gui::getDwarfmodeViewDims();
Screen::Painter dc(dims.menu());
dc.seek(1,24).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE);
dc.key(interface_key::CUSTOM_A).string(": Autoselect "+title);
}
}
else if (in_placement_stage() && ui_build_selector->building_subtype < construction_type::TrackN)
{
string autoselect_toggle = (auto_choose_materials) ? "Disable" : "Enable";
string revert_toggle = (revert_to_last_used_type) ? "Disable" : "Enable";
auto dims = Gui::getDwarfmodeViewDims();
Screen::Painter dc(dims.menu());
dc.seek(1,23).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE);
dc.key(interface_key::CUSTOM_A).string(": "+autoselect_toggle+" Auto Mat-Select").newline(1);
dc.key(interface_key::CUSTOM_T).string(": "+revert_toggle+" Auto Type-Select");
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (!gps || !ui_build_selector ||
!INTERPOSE_HOOK(jobutils_hook, feed).apply() ||
!INTERPOSE_HOOK(jobutils_hook, render).apply())
out.printerr("Could not insert jobutils hooks!\n");
hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL;
hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR;
hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP;
hotkeys[construction_type::UpStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP;
hotkeys[construction_type::DownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN;
hotkeys[construction_type::UpDownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN;
hotkeys[construction_type::Fortification] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION;
//Ignore tracks, DF already returns to track menu
return CR_OK;
}

@ -117,7 +117,7 @@ command_result df_cleanowned (color_ostream &out, vector <string> & parameters)
else if (item->flags.bits.on_ground) else if (item->flags.bits.on_ground)
{ {
df::item_type type = item->getType(); df::item_type type = item->getType();
if(type == item_type::MEAT || if(type == item_type::MEAT ||
type == item_type::FISH || type == item_type::FISH ||
type == item_type::VERMIN || type == item_type::VERMIN ||
type == item_type::PET || type == item_type::PET ||

@ -17,16 +17,16 @@ static tthread::mutex* mymutex=0;
struct memory_data struct memory_data
{ {
void * addr; void * addr;
size_t len; size_t len;
size_t refresh; size_t refresh;
int state; int state;
uint8_t *buf,*lbuf; uint8_t *buf,*lbuf;
vector<t_memrange> ranges; vector<t_memrange> ranges;
}memdata; }memdata;
enum HEXVIEW_STATES enum HEXVIEW_STATES
{ {
STATE_OFF,STATE_ON STATE_OFF,STATE_ON
}; };
command_result memview (color_ostream &out, vector <string> & parameters); command_result memview (color_ostream &out, vector <string> & parameters);
@ -34,151 +34,151 @@ DFHACK_PLUGIN("memview");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
commands.push_back(PluginCommand("memview","Shows memory in real time. Params: adrr length refresh_rate. If addr==0 then stop viewing",memview)); commands.push_back(PluginCommand("memview","Shows memory in real time. Params: adrr length refresh_rate. If addr==0 then stop viewing",memview));
memdata.state=STATE_OFF; memdata.state=STATE_OFF;
mymutex=new tthread::mutex; mymutex=new tthread::mutex;
return CR_OK; return CR_OK;
} }
size_t convert(const std::string& p,bool ishex=false) size_t convert(const std::string& p,bool ishex=false)
{ {
size_t ret; size_t ret;
std::stringstream conv; std::stringstream conv;
if(ishex) if(ishex)
conv<<std::hex; conv<<std::hex;
conv<<p; conv<<p;
conv>>ret; conv>>ret;
return ret; return ret;
} }
bool isAddr(uint32_t *trg,vector<t_memrange> & ranges) bool isAddr(uint32_t *trg,vector<t_memrange> & ranges)
{ {
if(trg[0]%4==0) if(trg[0]%4==0)
for(size_t i=0;i<ranges.size();i++) for(size_t i=0;i<ranges.size();i++)
if(ranges[i].isInRange((void *)trg[0])) if(ranges[i].isInRange((void *)trg[0]))
return true; return true;
return false; return false;
} }
void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream &con,vector<t_memrange> & ranges) void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream &con,vector<t_memrange> & ranges)
{ {
const size_t page_size=16; const size_t page_size=16;
for(size_t i=0;i<len;i+=page_size) for(size_t i=0;i<len;i+=page_size)
{ {
//con.gotoxy(1,i/page_size+1); //con.gotoxy(1,i/page_size+1);
con.print("0x%08X ",i+start); con.print("0x%08X ",i+start);
for(size_t j=0;(j<page_size) && (i+j<len);j++) for(size_t j=0;(j<page_size) && (i+j<len);j++)
{ {
if(j%4==0) if(j%4==0)
{ {
con.reset_color(); con.reset_color();
if(isAddr((uint32_t *)(buf+j+i),ranges)) if(isAddr((uint32_t *)(buf+j+i),ranges))
con.color(COLOR_LIGHTRED); //coloring in the middle does not work con.color(COLOR_LIGHTRED); //coloring in the middle does not work
//TODO make something better? //TODO make something better?
} }
if(lbuf[j+i]!=buf[j+i]) if(lbuf[j+i]!=buf[j+i])
con.print("*%02X",buf[j+i]); //if modfied show a star con.print("*%02X",buf[j+i]); //if modfied show a star
else else
con.print(" %02X",buf[j+i]); con.print(" %02X",buf[j+i]);
} }
con.reset_color(); con.reset_color();
con.print(" | "); con.print(" | ");
for(size_t j=0;(j<page_size) && (i+j<len);j++) for(size_t j=0;(j<page_size) && (i+j<len);j++)
if((buf[j+i]>31)&&(buf[j+i]<128)) //only printable ascii if((buf[j+i]>31)&&(buf[j+i]<128)) //only printable ascii
con.print("%c",buf[j+i]); con.print("%c",buf[j+i]);
else else
con.print("."); con.print(".");
//con.print("\n"); //con.print("\n");
} }
con.print("\n"); con.print("\n");
} }
void Deinit() void Deinit()
{ {
if(memdata.state==STATE_ON) if(memdata.state==STATE_ON)
{ {
memdata.state=STATE_OFF; memdata.state=STATE_OFF;
delete [] memdata.buf; delete [] memdata.buf;
delete [] memdata.lbuf; delete [] memdata.lbuf;
} }
} }
DFhackCExport command_result plugin_onupdate (color_ostream &out) DFhackCExport command_result plugin_onupdate (color_ostream &out)
{ {
mymutex->lock(); mymutex->lock();
if(memdata.state==STATE_OFF) if(memdata.state==STATE_OFF)
{ {
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
//Console &con=out; //Console &con=out;
uint64_t time2 = GetTimeMs64(); uint64_t time2 = GetTimeMs64();
uint64_t delta = time2-timeLast; uint64_t delta = time2-timeLast;
if(memdata.refresh!=0) if(memdata.refresh!=0)
if(delta<memdata.refresh) if(delta<memdata.refresh)
{ {
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
timeLast = time2; timeLast = time2;
Core::getInstance().p->read(memdata.addr,memdata.len,memdata.buf); Core::getInstance().p->read(memdata.addr,memdata.len,memdata.buf);
outputHex(memdata.buf,memdata.lbuf,memdata.len,(size_t)memdata.addr,out,memdata.ranges); outputHex(memdata.buf,memdata.lbuf,memdata.len,(size_t)memdata.addr,out,memdata.ranges);
memcpy(memdata.lbuf, memdata.buf, memdata.len); memcpy(memdata.lbuf, memdata.buf, memdata.len);
if(memdata.refresh==0) if(memdata.refresh==0)
Deinit(); Deinit();
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
command_result memview (color_ostream &out, vector <string> & parameters) command_result memview (color_ostream &out, vector <string> & parameters)
{ {
mymutex->lock(); mymutex->lock();
Core::getInstance().p->getMemRanges(memdata.ranges); Core::getInstance().p->getMemRanges(memdata.ranges);
memdata.addr=(void *)convert(parameters[0],true); memdata.addr=(void *)convert(parameters[0],true);
if(memdata.addr==0) if(memdata.addr==0)
{ {
Deinit(); Deinit();
memdata.state=STATE_OFF; memdata.state=STATE_OFF;
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
else else
{ {
Deinit(); Deinit();
bool isValid=false; bool isValid=false;
for(size_t i=0;i<memdata.ranges.size();i++) for(size_t i=0;i<memdata.ranges.size();i++)
if(memdata.ranges[i].isInRange(memdata.addr)) if(memdata.ranges[i].isInRange(memdata.addr))
isValid=true; isValid=true;
if(!isValid) if(!isValid)
{ {
out.printerr("Invalid address:%x\n",memdata.addr); out.printerr("Invalid address:%x\n",memdata.addr);
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
memdata.state=STATE_ON; memdata.state=STATE_ON;
} }
if(parameters.size()>1) if(parameters.size()>1)
memdata.len=convert(parameters[1]); memdata.len=convert(parameters[1]);
else else
memdata.len=20*16; memdata.len=20*16;
if(parameters.size()>2) if(parameters.size()>2)
memdata.refresh=convert(parameters[2]); memdata.refresh=convert(parameters[2]);
else else
memdata.refresh=0; memdata.refresh=0;
memdata.buf=new uint8_t[memdata.len]; memdata.buf=new uint8_t[memdata.len];
memdata.lbuf=new uint8_t[memdata.len]; memdata.lbuf=new uint8_t[memdata.len];
Core::getInstance().p->getMemRanges(memdata.ranges); Core::getInstance().p->getMemRanges(memdata.ranges);
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_shutdown (color_ostream &out) DFhackCExport command_result plugin_shutdown (color_ostream &out)
{ {
mymutex->lock(); mymutex->lock();
Deinit(); Deinit();
delete mymutex; delete mymutex;
mymutex->unlock(); mymutex->unlock();
return CR_OK; return CR_OK;
} }

@ -35,32 +35,32 @@ static bool enabled = false;
static void eggscan(color_ostream &out) static void eggscan(color_ostream &out)
{ {
CoreSuspender suspend; CoreSuspender suspend;
for (int i = 0; i < world->buildings.all.size(); ++i) for (int i = 0; i < world->buildings.all.size(); ++i)
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::NestBox == type) if (df::enums::building_type::NestBox == type)
{ {
bool fertile = false; bool fertile = false;
df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build); df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build);
if (nb->claimed_by != -1) if (nb->claimed_by != -1)
{ {
df::unit* u = df::unit::find(nb->claimed_by); df::unit* u = df::unit::find(nb->claimed_by);
if (u && u->relations.pregnancy_timer > 0) if (u && u->relations.pregnancy_timer > 0)
fertile = true; fertile = true;
} }
for (int j = 1; j < nb->contained_items.size(); j++) for (int j = 1; j < nb->contained_items.size(); j++)
{ {
df::item* item = nb->contained_items[j]->item; df::item* item = nb->contained_items[j]->item;
if (item->flags.bits.forbid != fertile) if (item->flags.bits.forbid != fertile)
{ {
item->flags.bits.forbid = fertile; item->flags.bits.forbid = fertile;
out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl;
} }
} }
} }
} }
} }
@ -68,12 +68,12 @@ static void eggscan(color_ostream &out)
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (world && ui) { if (world && ui) {
commands.push_back( commands.push_back(
PluginCommand("nestboxes", "Derp.", PluginCommand("nestboxes", "Derp.",
nestboxes, false, nestboxes, false,
"Derp.\n" "Derp.\n"
) )
); );
} }
return CR_OK; return CR_OK;
} }
@ -92,28 +92,28 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
if ((++cnt % 5) != 0) if ((++cnt % 5) != 0)
return CR_OK; return CR_OK;
eggscan(out); eggscan(out);
return CR_OK; return CR_OK;
} }
static command_result nestboxes(color_ostream &out, vector <string> & parameters) static command_result nestboxes(color_ostream &out, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool clean = false; bool clean = false;
int dump_count = 0; int dump_count = 0;
int good_egg = 0; int good_egg = 0;
if (parameters.size() == 1) { if (parameters.size() == 1) {
if (parameters[0] == "enable") if (parameters[0] == "enable")
enabled = true; enabled = true;
else if (parameters[0] == "disable") else if (parameters[0] == "disable")
enabled = false; enabled = false;
else else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} else { } else {
out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl;
} }
return CR_OK; return CR_OK;
} }

@ -67,10 +67,10 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
// Embark screen active: estimate using world geology data // Embark screen active: estimate using world geology data
VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen()); VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen());
if (!screen) if (!screen)
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
if (!world || !world->world_data) if (!world || !world->world_data)
{ {
out.printerr("World data is not available.\n"); out.printerr("World data is not available.\n");
@ -78,9 +78,9 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
} }
if (parameters.size() == 2) if (parameters.size() == 2)
{ {
if (parameters[0] == "rai") if (parameters[0] == "rai")
set_field = 0; set_field = 0;
else if (parameters[0] == "veg") else if (parameters[0] == "veg")
set_field = 1; set_field = 1;
@ -97,7 +97,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
else else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
if (screen->biome_highlighted) if (screen->biome_highlighted)
to_set = screen->biome_idx; to_set = screen->biome_idx;
else else
to_set = 0; to_set = 0;
@ -110,7 +110,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
coord2d cur_region = screen->region_pos; coord2d cur_region = screen->region_pos;
// Compute biomes // Compute biomes
for (int i = 0; i < screen->biome_rgn.size(); i++) for (int i = 0; i < screen->biome_rgn.size(); i++)
{ {
coord2d rg = screen->biome_rgn[i]; coord2d rg = screen->biome_rgn[i];
@ -140,7 +140,7 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
" geo_index: " << rd->geo_index << " geo_index: " << rd->geo_index <<
" landmass_id: " << rd->landmass_id << " landmass_id: " << rd->landmass_id <<
" flags: " << hex << rd->flags.as_int() << dec << endl; " flags: " << hex << rd->flags.as_int() << dec << endl;
out << out <<
"rai: " << rd->rainfall << " " << "rai: " << rd->rainfall << " " <<
"veg: " << rd->vegetation << " " << "veg: " << rd->vegetation << " " <<
"tem: " << rd->temperature << " " << "tem: " << rd->temperature << " " <<
@ -148,17 +148,17 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
"dra: " << rd->drainage << " " << "dra: " << rd->drainage << " " <<
"sav: " << rd->savagery << " " << "sav: " << rd->savagery << " " <<
"sal: " << rd->salinity; "sal: " << rd->salinity;
int32_t *p = (int32_t *)rd; int32_t *p = (int32_t *)rd;
int c = sizeof(*rd) / sizeof(int32_t); int c = sizeof(*rd) / sizeof(int32_t);
for (int j = 0; j < c; j++) { for (int j = 0; j < c; j++) {
if (j % 8 == 0) if (j % 8 == 0)
out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": "; out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": ";
out << " " << setfill('0') << setw(8) << hex << p[j]; out << " " << setfill('0') << setw(8) << hex << p[j];
} }
out << setfill(' ') << setw(0) << dec << endl; out << setfill(' ') << setw(0) << dec << endl;
} }
return CR_OK; return CR_OK;
} }

@ -38,12 +38,12 @@ DFHACK_PLUGIN("stockcheck");
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (world && ui) { if (world && ui) {
commands.push_back( commands.push_back(
PluginCommand("stockcheck", "Check for unprotected rottable items.", PluginCommand("stockcheck", "Check for unprotected rottable items.",
stockcheck, false, stockcheck, false,
"Scan world for items that are susceptible to rot. Currently just lists the items.\n" "Scan world for items that are susceptible to rot. Currently just lists the items.\n"
) )
); );
} }
return CR_OK; return CR_OK;
} }
@ -54,121 +54,121 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
} }
struct StockpileInfo { struct StockpileInfo {
building_stockpilest* sp; building_stockpilest* sp;
int size; int size;
int free; int free;
int x1, x2, y1, y2, z; int x1, x2, y1, y2, z;
public: public:
StockpileInfo(building_stockpilest *sp_) : sp(sp_) StockpileInfo(building_stockpilest *sp_) : sp(sp_)
{ {
MapExtras::MapCache mc; MapExtras::MapCache mc;
z = sp_->z; z = sp_->z;
x1 = sp_->room.x; x1 = sp_->room.x;
x2 = sp_->room.x + sp_->room.width; x2 = sp_->room.x + sp_->room.width;
y1 = sp_->room.y; y1 = sp_->room.y;
y2 = sp_->room.y + sp_->room.height; y2 = sp_->room.y + sp_->room.height;
int e = 0; int e = 0;
size = 0; size = 0;
free = 0; free = 0;
for (int y = y1; y < y2; y++) for (int y = y1; y < y2; y++)
for (int x = x1; x < x2; x++) for (int x = x1; x < x2; x++)
if (sp_->room.extents[e++] == 1) if (sp_->room.extents[e++] == 1)
{ {
size++; size++;
DFCoord cursor (x,y,z); DFCoord cursor (x,y,z);
uint32_t blockX = x / 16; uint32_t blockX = x / 16;
uint32_t tileX = x % 16; uint32_t tileX = x % 16;
uint32_t blockY = y / 16; uint32_t blockY = y / 16;
uint32_t tileY = y % 16; uint32_t tileY = y % 16;
MapExtras::Block * b = mc.BlockAt(cursor/16); MapExtras::Block * b = mc.BlockAt(cursor/16);
if(b && b->is_valid()) if(b && b->is_valid())
{ {
auto &block = *b->getRaw(); auto &block = *b->getRaw();
df::tile_occupancy &occ = block.occupancy[tileX][tileY]; df::tile_occupancy &occ = block.occupancy[tileX][tileY];
if (!occ.bits.item) if (!occ.bits.item)
free++; free++;
} }
} }
} }
bool isFull() { return free == 0; } bool isFull() { return free == 0; }
bool canHold(df::item *i) bool canHold(df::item *i)
{ {
return false; return false;
} }
bool inStockpile(df::item *i) bool inStockpile(df::item *i)
{ {
df::item *container = Items::getContainer(i); df::item *container = Items::getContainer(i);
if (container) if (container)
return inStockpile(container); return inStockpile(container);
if (i->pos.z != z) return false; if (i->pos.z != z) return false;
if (i->pos.x < x1 || i->pos.x >= x2 || if (i->pos.x < x1 || i->pos.x >= x2 ||
i->pos.y < y1 || i->pos.y >= y2) return false; i->pos.y < y1 || i->pos.y >= y2) return false;
int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width;
return sp->room.extents[e] == 1; return sp->room.extents[e] == 1;
} }
int getId() { return sp->id; } int getId() { return sp->id; }
}; };
static command_result stockcheck(color_ostream &out, vector <string> & parameters) static command_result stockcheck(color_ostream &out, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
std::vector<StockpileInfo*> stockpiles; std::vector<StockpileInfo*> stockpiles;
for (int i = 0; i < world->buildings.all.size(); ++i) for (int i = 0; i < world->buildings.all.size(); ++i)
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::Stockpile == type) if (df::enums::building_type::Stockpile == type)
{ {
building_stockpilest *sp = virtual_cast<building_stockpilest>(build); building_stockpilest *sp = virtual_cast<building_stockpilest>(build);
StockpileInfo *spi = new StockpileInfo(sp); StockpileInfo *spi = new StockpileInfo(sp);
stockpiles.push_back(spi); stockpiles.push_back(spi);
} }
} }
std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY]; std::vector<df::item*> &items = world->items.other[items_other_id::IN_PLAY];
// Precompute a bitmask with the bad flags // Precompute a bitmask with the bad flags
df::item_flags bad_flags; df::item_flags bad_flags;
bad_flags.whole = 0; bad_flags.whole = 0;
#define F(x) bad_flags.bits.x = true; #define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect); F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader); F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1); F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job); F(spider_web); F(owned); F(in_job);
#undef F #undef F
for (size_t i = 0; i < items.size(); i++) for (size_t i = 0; i < items.size(); i++)
{ {
df::item *item = items[i]; df::item *item = items[i];
if (item->flags.whole & bad_flags.whole) if (item->flags.whole & bad_flags.whole)
continue; continue;
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
df::item_type typ = item->getType(); df::item_type typ = item->getType();
if (typ != df::enums::item_type::MEAT && if (typ != df::enums::item_type::MEAT &&
typ != df::enums::item_type::FISH && typ != df::enums::item_type::FISH &&
typ != df::enums::item_type::FISH_RAW && typ != df::enums::item_type::FISH_RAW &&
typ != df::enums::item_type::PLANT && typ != df::enums::item_type::PLANT &&
typ != df::enums::item_type::CHEESE && typ != df::enums::item_type::CHEESE &&
typ != df::enums::item_type::FOOD && typ != df::enums::item_type::FOOD &&
typ != df::enums::item_type::EGG) typ != df::enums::item_type::EGG)
continue; continue;
df::item *container = 0; df::item *container = 0;
df::unit *holder = 0; df::unit *holder = 0;
df::building *building = 0; df::building *building = 0;
for (size_t i = 0; i < item->general_refs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
@ -177,106 +177,106 @@ static command_result stockcheck(color_ostream &out, vector <string> & parameter
switch (ref->getType()) switch (ref->getType())
{ {
case general_ref_type::CONTAINED_IN_ITEM: case general_ref_type::CONTAINED_IN_ITEM:
container = ref->getItem(); container = ref->getItem();
break; break;
case general_ref_type::UNIT_HOLDER: case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit(); holder = ref->getUnit();
break; break;
case general_ref_type::BUILDING_HOLDER: case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding(); building = ref->getBuilding();
break; break;
default: default:
break; break;
} }
} }
df::item *nextcontainer = container; df::item *nextcontainer = container;
df::item *lastcontainer = 0; df::item *lastcontainer = 0;
while(nextcontainer) { while(nextcontainer) {
df::item *thiscontainer = nextcontainer; df::item *thiscontainer = nextcontainer;
nextcontainer = 0; nextcontainer = 0;
for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) for (size_t i = 0; i < thiscontainer->general_refs.size(); i++)
{ {
df::general_ref *ref = thiscontainer->general_refs[i]; df::general_ref *ref = thiscontainer->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
case general_ref_type::CONTAINED_IN_ITEM: case general_ref_type::CONTAINED_IN_ITEM:
lastcontainer = nextcontainer = ref->getItem(); lastcontainer = nextcontainer = ref->getItem();
break; break;
case general_ref_type::UNIT_HOLDER: case general_ref_type::UNIT_HOLDER:
holder = ref->getUnit(); holder = ref->getUnit();
break; break;
case general_ref_type::BUILDING_HOLDER: case general_ref_type::BUILDING_HOLDER:
building = ref->getBuilding(); building = ref->getBuilding();
break; break;
default: default:
break; break;
} }
} }
} }
if (holder) if (holder)
continue; // carried items do not rot as far as i know continue; // carried items do not rot as far as i know
if (building) { if (building) {
df::building_type btype = building->getType(); df::building_type btype = building->getType();
if (btype == df::enums::building_type::TradeDepot || if (btype == df::enums::building_type::TradeDepot ||
btype == df::enums::building_type::Wagon) btype == df::enums::building_type::Wagon)
continue; // items in trade depot or the embark wagon do not rot continue; // items in trade depot or the embark wagon do not rot
if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox)
continue; // eggs in nest box do not rot continue; // eggs in nest box do not rot
} }
int canHoldCount = 0; int canHoldCount = 0;
StockpileInfo *current = 0; StockpileInfo *current = 0;
for (int idx = 0; idx < stockpiles.size(); idx++) for (int idx = 0; idx < stockpiles.size(); idx++)
{ {
StockpileInfo *spi = stockpiles[idx]; StockpileInfo *spi = stockpiles[idx];
if (spi->canHold(item)) canHoldCount++; if (spi->canHold(item)) canHoldCount++;
if (spi->inStockpile(item)) current=spi; if (spi->inStockpile(item)) current=spi;
} }
if (current) if (current)
continue; continue;
std::string description; std::string description;
item->getItemDescription(&description, 0); item->getItemDescription(&description, 0);
out << " * " << description; out << " * " << description;
if (container) { if (container) {
std::string containerDescription; std::string containerDescription;
container->getItemDescription(&containerDescription, 0); container->getItemDescription(&containerDescription, 0);
out << ", in container " << containerDescription; out << ", in container " << containerDescription;
if (lastcontainer) { if (lastcontainer) {
std::string lastcontainerDescription; std::string lastcontainerDescription;
lastcontainer->getItemDescription(&lastcontainerDescription, 0); lastcontainer->getItemDescription(&lastcontainerDescription, 0);
out << ", in container " << lastcontainerDescription; out << ", in container " << lastcontainerDescription;
} }
} }
if (holder) { if (holder) {
out << ", carried"; out << ", carried";
} }
if (building) { if (building) {
out << ", in building " << building->id << " (type=" << building->getType() << ")"; out << ", in building " << building->id << " (type=" << building->getType() << ")";
} }
out << ", flags=" << std::hex << item->flags.whole << std::dec; out << ", flags=" << std::hex << item->flags.whole << std::dec;
out << endl; out << endl;
} }
return CR_OK; return CR_OK;
} }

@ -61,11 +61,11 @@ bool isContainedInItem(df::unit* unit)
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{ {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"stripcaged", "strip caged units of all items", "stripcaged", "strip caged units of all items",
df_stripcaged, false, df_stripcaged, false,
"Clears forbid and sets dump for the inventories of all caged units." "Clears forbid and sets dump for the inventories of all caged units."
)); ));
return CR_OK; return CR_OK;
} }
@ -77,39 +77,39 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
command_result df_stripcaged(color_ostream &out, vector <string> & parameters) command_result df_stripcaged(color_ostream &out, vector <string> & parameters)
{ {
CoreSuspender suspend; CoreSuspender suspend;
bool keeparmor = true; bool keeparmor = true;
if (parameters.size() == 1 && parameters[0] == "dumparmor") if (parameters.size() == 1 && parameters[0] == "dumparmor")
{ {
out << "Dumping armor too" << endl; out << "Dumping armor too" << endl;
keeparmor = false; keeparmor = false;
} }
size_t count = 0; size_t count = 0;
for (size_t i=0; i < world->units.all.size(); i++) for (size_t i=0; i < world->units.all.size(); i++)
{ {
df::unit* unit = world->units.all[i]; df::unit* unit = world->units.all[i];
if (isContainedInItem(unit)) if (isContainedInItem(unit))
{ {
for (size_t j=0; j < unit->inventory.size(); j++) for (size_t j=0; j < unit->inventory.size(); j++)
{ {
df::unit_inventory_item* uii = unit->inventory[j]; df::unit_inventory_item* uii = unit->inventory[j];
if (uii->item) if (uii->item)
{ {
if (keeparmor && (uii->item->isArmorNotClothing() || uii->item->isClothing())) if (keeparmor && (uii->item->isArmorNotClothing() || uii->item->isClothing()))
continue; continue;
std::string desc; std::string desc;
uii->item->getItemDescription(&desc,0); uii->item->getItemDescription(&desc,0);
out << "Item " << desc << " dumped." << endl; out << "Item " << desc << " dumped." << endl;
uii->item->flags.bits.forbid = 0; uii->item->flags.bits.forbid = 0;
uii->item->flags.bits.dump = 1; uii->item->flags.bits.dump = 1;
count++; count++;
} }
} }
} }
} }
out << count << " items marked for dumping" << endl; out << count << " items marked for dumping" << endl;
return CR_OK; return CR_OK;
} }

@ -323,7 +323,7 @@ public:
auto_renderer_decorator & operator=(renderer_decorator *p) { auto_renderer_decorator & operator=(renderer_decorator *p) {
reset(); reset();
this->p = p; this->p = p;
return *this; return *this;
} }
renderer_decorator * get() { renderer_decorator * get() {

@ -185,7 +185,7 @@ df::item* find_item(
} }
static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector<df::item*> *in_items,std::vector<df::reaction_reagent*> *in_reag
, std::vector<df::item*> *out_items,bool *call_native){}; , std::vector<df::item*> *out_items,bool *call_native){};
DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *); DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector<df::item*> *,std::vector<df::reaction_reagent*> *,std::vector<df::item*> *,bool *);
@ -208,13 +208,13 @@ struct product_hook : item_product {
) { ) {
if (auto product = products[this]) if (auto product = products[this])
{ {
df::reaction* this_reaction=product->react; df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend; CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole()); color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true; bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native); onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native) if(!call_native)
return; return;
} }
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site); INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
@ -233,12 +233,12 @@ IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
static void parse_product( static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) { ) {
info.react = react; info.react = react;
info.product = prod; info.product = prod;
info.material.mat_type = prod->mat_type; info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index; info.material.mat_index = prod->mat_index;
} }
static bool find_reactions(color_ostream &out) static bool find_reactions(color_ostream &out)

@ -1,7 +1,7 @@
// Wide-area traffic designation utility. // Wide-area traffic designation utility.
// Flood-fill from cursor or fill entire map. // Flood-fill from cursor or fill entire map.
#include <ctype.h> //For toupper(). #include <ctype.h> //For toupper().
#include <algorithm> //for min(). #include <algorithm> //for min().
#include <map> #include <map>
#include <vector> #include <vector>

@ -57,161 +57,161 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters);
const string forceequip_help = const string forceequip_help =
"ForceEquip moves local items into a unit's inventory. It is typically\n" "ForceEquip moves local items into a unit's inventory. It is typically\n"
"used to equip specific clothing/armor items onto a dwarf, but can also\n" "used to equip specific clothing/armor items onto a dwarf, but can also\n"
"be used to put armor onto a war animal or to add unusual items (such\n" "be used to put armor onto a war animal or to add unusual items (such\n"
"as crowns) to any unit.\n" "as crowns) to any unit.\n"
"This plugin can process multiple items in a single call, but will only\n" "This plugin can process multiple items in a single call, but will only\n"
"work with a single unit (the first one it finds under the cursor).\n" "work with a single unit (the first one it finds under the cursor).\n"
"In order to minimize confusion, it is recommended that you use\n" "In order to minimize confusion, it is recommended that you use\n"
"forceequip only when you have a unit standing alone atop a pile of\n" "forceequip only when you have a unit standing alone atop a pile of\n"
"gear that you would like it to wear. Items which are stored in bins\n" "gear that you would like it to wear. Items which are stored in bins\n"
"or other containers (e.g. chests, armor racks) may also work, but\n" "or other containers (e.g. chests, armor racks) may also work, but\n"
"piling items on the floor (via a garbage dump activity zone, of the\n" "piling items on the floor (via a garbage dump activity zone, of the\n"
"DFHack autodump command) is the most reliable way to do it.\n" "DFHack autodump command) is the most reliable way to do it.\n"
"The plugin will ignore any items that are forbidden. Hence, you\n" "The plugin will ignore any items that are forbidden. Hence, you\n"
"can setup a large pile of surplus gear, walk a unit onto it (or\n" "can setup a large pile of surplus gear, walk a unit onto it (or\n"
"pasture an animal on it), unforbid a few items and run forceequip.\n" "pasture an animal on it), unforbid a few items and run forceequip.\n"
"The (forbidden) majority of your gear will remain in-place, ready\n" "The (forbidden) majority of your gear will remain in-place, ready\n"
"for the next passerby." "for the next passerby."
"\n"
"As mentioned above, this plugin can be used to equip items onto\n"
"units (such as animals) which cannot normally equip gear. There's\n"
"an important caveat here - such creatures will automatically drop\n"
"inappropriate gear almost immediately (within 10 game ticks).\n"
"If you want them to retain their equipment, you must forbid it\n"
"AFTER using forceequip to get it into their inventory.\n"
"This technique can also be used to clothe dwarven infants, but\n"
"only if you're able to separate them from their mothers.\n"
"\n" "\n"
"By default, the forceequip plugin will attempt to avoid\n" "As mentioned above, this plugin can be used to equip items onto\n"
"conflicts and outright cheating. For instance, it will skip\n" "units (such as animals) which cannot normally equip gear. There's\n"
"any item which is flagged for use in a job, and will not\n" "an important caveat here - such creatures will automatically drop\n"
"equip more than one piece of clothing/armor onto any given\n" "inappropriate gear almost immediately (within 10 game ticks).\n"
"body part. These restrictions can be overridden via command\n" "If you want them to retain their equipment, you must forbid it\n"
"switches (see examples below) but doing so puts you at greater\n" "AFTER using forceequip to get it into their inventory.\n"
"risk of unexpected consequences. For instance, a dwarf who\n" "This technique can also be used to clothe dwarven infants, but\n"
"is wearing three breastplates will not be able to move very\n" "only if you're able to separate them from their mothers.\n"
"quickly.\n" "\n"
"\n" "By default, the forceequip plugin will attempt to avoid\n"
"Items equipped by this plugin DO NOT become owned by the\n" "conflicts and outright cheating. For instance, it will skip\n"
"recipient. Adult dwarves are free to adjust their own\n" "any item which is flagged for use in a job, and will not\n"
"wardrobe, and may promptly decide to doff your gear in\n" "equip more than one piece of clothing/armor onto any given\n"
"favour of their owned items. Animals, as described above,\n" "body part. These restrictions can be overridden via command\n"
"will tend to discard ALL clothing immediately unless it is\n" "switches (see examples below) but doing so puts you at greater\n"
"manually forbidden. Armor items seem to be an exception;\n" "risk of unexpected consequences. For instance, a dwarf who\n"
"an animal will tend to retain an equipped suit of mail\n" "is wearing three breastplates will not be able to move very\n"
"even if you neglect to Forbid it.\n" "quickly.\n"
"\n" "\n"
"Please note that armored animals are quite vulnerable to ranged\n" "Items equipped by this plugin DO NOT become owned by the\n"
"attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n" "recipient. Adult dwarves are free to adjust their own\n"
"arrows, and they are slowed by the weight of their armor.\n" "wardrobe, and may promptly decide to doff your gear in\n"
"\n" "favour of their owned items. Animals, as described above,\n"
"This plugin currently does not support weapons.\n" "will tend to discard ALL clothing immediately unless it is\n"
"\n" "manually forbidden. Armor items seem to be an exception;\n"
"Options:\n" "an animal will tend to retain an equipped suit of mail\n"
" here, h - process the unit and item(s) under the cursor.\n" "even if you neglect to Forbid it.\n"
" - This option is enabled by default since the plugin\n" "\n"
" - does not currently support remote equpping.\n" "Please note that armored animals are quite vulnerable to ranged\n"
"attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n"
"arrows, and they are slowed by the weight of their armor.\n"
"\n"
"This plugin currently does not support weapons.\n"
"\n"
"Options:\n"
" here, h - process the unit and item(s) under the cursor.\n"
" - This option is enabled by default since the plugin\n"
" - does not currently support remote equpping.\n"
" ignore, i - bypasses the usual item eligibility checks (such as\n" " ignore, i - bypasses the usual item eligibility checks (such as\n"
" - \"Never equip gear belonging to another dwarf\" and\n" " - \"Never equip gear belonging to another dwarf\" and\n"
" - \"Nobody is allowed to equip a Hive\".)\n" " - \"Nobody is allowed to equip a Hive\".)\n"
" multi, m - bypasses the 1-item-per-bodypart limit, allowing\n" " multi, m - bypasses the 1-item-per-bodypart limit, allowing\n"
" - the unit to receive an unlimited amount of gear.\n" " - the unit to receive an unlimited amount of gear.\n"
" - Can be used legitimately (e.g. mitten + gauntlet)\n" " - Can be used legitimately (e.g. mitten + gauntlet)\n"
" - or for cheating (e.g. twelve breastplates).\n" " - or for cheating (e.g. twelve breastplates).\n"
" m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n" " m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n"
" - each part to receive 2, 3, or 4 pieces of gear.\n" " - each part to receive 2, 3, or 4 pieces of gear.\n"
" selected, s - rather than processing all items piled at a unit's\n" " selected, s - rather than processing all items piled at a unit's\n"
" - feet, process only the one item currently selected.\n" " - feet, process only the one item currently selected.\n"
" bodypart, bp - must be followed by a bodypart code (e.g. LH).\n" " bodypart, bp - must be followed by a bodypart code (e.g. LH).\n"
" - Instructs the plugin to equip all available items\n" " - Instructs the plugin to equip all available items\n"
" - onto this body part only. Typically used in\n" " - onto this body part only. Typically used in\n"
" - conjunction with the f switch (to over-armor\n" " - conjunction with the f switch (to over-armor\n"
" - a particular bodypart) or the i switch (to equip\n" " - a particular bodypart) or the i switch (to equip\n"
" - an unusual item onto a specific slot).\n" " - an unusual item onto a specific slot).\n"
" verbose, v - provides detailed narration and error messages.\n" " verbose, v - provides detailed narration and error messages.\n"
" - Can be helpful in resolving failures; not needed\n" " - Can be helpful in resolving failures; not needed\n"
" - for casual use.\n" " - for casual use.\n"
"\n" "\n"
"Examples:\n" "Examples:\n"
" forceequip\n" " forceequip\n"
" attempts to equip all of the items under the cursor onto the unit\n" " attempts to equip all of the items under the cursor onto the unit\n"
" under the cursor. Uses only clothing/armor items; ignores all\n" " under the cursor. Uses only clothing/armor items; ignores all\n"
" other types. Equips a maximum of 1 item onto each bodypart,\n" " other types. Equips a maximum of 1 item onto each bodypart,\n"
" and equips only \"appropriate\" items in each slot (e.g. glove\n" " and equips only \"appropriate\" items in each slot (e.g. glove\n"
" --> hand). Bypasses any item which might cause a conflict,\n" " --> hand). Bypasses any item which might cause a conflict,\n"
" such as a Boot belonging to a different dwarf.\n" " such as a Boot belonging to a different dwarf.\n"
" forceequip bp LH\n" " forceequip bp LH\n"
" attempts to equip all local items onto the left hand of the local\n" " attempts to equip all local items onto the left hand of the local\n"
" unit. If the hand is already equipped then nothing will happen,\n" " unit. If the hand is already equipped then nothing will happen,\n"
" and if it is not equipped then only one appropriate item (e.g. \n" " and if it is not equipped then only one appropriate item (e.g. \n"
" a single mitten or gauntlet) will be equipped. This command can\n" " a single mitten or gauntlet) will be equipped. This command can\n"
" be useful if you don't want to selectively forbid individual items\n" " be useful if you don't want to selectively forbid individual items\n"
" and simply want the unit to equip, say, a Helmet while leaving\n" " and simply want the unit to equip, say, a Helmet while leaving\n"
" the rest of the pile alone.\n" " the rest of the pile alone.\n"
" forceequip m bp LH\n" " forceequip m bp LH\n"
" as above, but will equip ALL appropriate items onto the unit's\n" " as above, but will equip ALL appropriate items onto the unit's\n"
" left hand. After running this command, it might end up wearing\n" " left hand. After running this command, it might end up wearing\n"
" a dozen left-handed mittens. Use with caution, and remember\n" " a dozen left-handed mittens. Use with caution, and remember\n"
" that dwarves will tend to drop supernumary items ASAP.\n" " that dwarves will tend to drop supernumary items ASAP.\n"
" forceequip m\n" " forceequip m\n"
" as above, but will equip ALL appropriate items onto any\n" " as above, but will equip ALL appropriate items onto any\n"
" appropriate bodypart. Tends to put several boots onto the right\n" " appropriate bodypart. Tends to put several boots onto the right\n"
" foot while leaving the left foot bare.\n" " foot while leaving the left foot bare.\n"
" forceequip m2\n" " forceequip m2\n"
" as above, but will equip up to two appropriate items onto each\n" " as above, but will equip up to two appropriate items onto each\n"
" bodypart. Helps to balance footwear, but doesn't ensure proper\n" " bodypart. Helps to balance footwear, but doesn't ensure proper\n"
" placement (e.g. left foot gets two socks, right foot gets two\n" " placement (e.g. left foot gets two socks, right foot gets two\n"
" shoes). For best results, use \"selected bp LH\" and\n" " shoes). For best results, use \"selected bp LH\" and\n"
" \"selected bp RH\" instead.\n" " \"selected bp RH\" instead.\n"
" forceequip i\n" " forceequip i\n"
" performs the standard \"equip appropriate items onto appropriate\n" " performs the standard \"equip appropriate items onto appropriate\n"
" bodyparts\" logic, but also includes items that would normally\n" " bodyparts\" logic, but also includes items that would normally\n"
" be considered ineligible (such as a sock which is owned by\n" " be considered ineligible (such as a sock which is owned by\n"
" a different dwarf).\n" " a different dwarf).\n"
" forceequip bp NECK\n" " forceequip bp NECK\n"
" attempts to equip any appropriate gear onto the Neck of the\n" " attempts to equip any appropriate gear onto the Neck of the\n"
" local unit. Since the plugin believes that no items are actually\n" " local unit. Since the plugin believes that no items are actually\n"
" appropriate for the Neck slot, this command does nothing.\n" " appropriate for the Neck slot, this command does nothing.\n"
" forceequip i bp NECK\n" " forceequip i bp NECK\n"
" attempts to equip items from the local pile onto the Neck\n" " attempts to equip items from the local pile onto the Neck\n"
" of the local unit. Ignores appropriateness restrictions.\n" " of the local unit. Ignores appropriateness restrictions.\n"
" If there's a millstone or an albatross carcass sitting on\n" " If there's a millstone or an albatross carcass sitting on\n"
" the same square as the targeted unit, then there's a good\n" " the same square as the targeted unit, then there's a good\n"
" chance that it will end up around his neck. For precise\n" " chance that it will end up around his neck. For precise\n"
" control, remember that you can selectively forbid some of\n" " control, remember that you can selectively forbid some of\n"
" the items that are piled on the ground.\n" " the items that are piled on the ground.\n"
" forceequip i m bp NECK\n" " forceequip i m bp NECK\n"
" as above, but equips an unlimited number of items onto the\n" " as above, but equips an unlimited number of items onto the\n"
" targeted bodypart. Effectively, all unforbidden items\n" " targeted bodypart. Effectively, all unforbidden items\n"
" (including helms, millstones, boulders, etc) will be\n" " (including helms, millstones, boulders, etc) will be\n"
" moved from the local pile and placed in the dwarf's\n" " moved from the local pile and placed in the dwarf's\n"
" inventory (specifically, on his neck). When used with\n" " inventory (specifically, on his neck). When used with\n"
" a large pile of goods, this will leave the dwarf heavily\n" " a large pile of goods, this will leave the dwarf heavily\n"
" encumbered and very slow to move.\n" " encumbered and very slow to move.\n"
" forceequip s\n" " forceequip s\n"
" requires that a single item be selected using the k menu.\n" " requires that a single item be selected using the k menu.\n"
" This item must occupy the same square as the target unit,\n" " This item must occupy the same square as the target unit,\n"
" and must be unforbidden. Attempts to equip this single\n" " and must be unforbidden. Attempts to equip this single\n"
" item onto an appropriate slot in the unit's inventory.\n" " item onto an appropriate slot in the unit's inventory.\n"
" Can serve as a quicker alternative to the selective-\n" " Can serve as a quicker alternative to the selective-\n"
" unforbidding approach described above.\n" " unforbidding approach described above.\n"
" forceequip s m i bp HD\n" " forceequip s m i bp HD\n"
" equips the selected item onto the unit's head. Ignores\n" " equips the selected item onto the unit's head. Ignores\n"
" all possible restrictions and conflicts. If you know\n" " all possible restrictions and conflicts. If you know\n"
" exactly what you want to equip, and exactly where you\n" " exactly what you want to equip, and exactly where you\n"
" want it to go, then this is the most straightforward\n" " want it to go, then this is the most straightforward\n"
" and reliable option.\n" " and reliable option.\n"
" forceequip v bp QQQ\n" " forceequip v bp QQQ\n"
" guaranteed to fail (and accomplish nothing) because\n" " guaranteed to fail (and accomplish nothing) because\n"
" there are no bodyparts called QQQ. However, since the\n" " there are no bodyparts called QQQ. However, since the\n"
" verbose switch is used, the resulting error messages\n" " verbose switch is used, the resulting error messages\n"
" will list every bodypart that the unit DOES possess.\n" " will list every bodypart that the unit DOES possess.\n"
" May be useful if you're unfamiliar with the BP codes\n" " May be useful if you're unfamiliar with the BP codes\n"
" used by Dwarf Fortress, or if you're experimenting\n" " used by Dwarf Fortress, or if you're experimenting\n"
" with an exotic creature.\n" " with an exotic creature.\n"
"\n" "\n"
; ;
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands) DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{ {
@ -233,7 +233,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
{ {
// Step 1: Check for anti-requisite conditions // Step 1: Check for anti-requisite conditions
df::unit * itemOwner = Items::getOwner(item); df::unit * itemOwner = Items::getOwner(item);
if (ignoreRestrictions) if (ignoreRestrictions)
{ {
// If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules // If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules
if (verbose) { Core::print("Skipping integrity checks...\n"); } if (verbose) { Core::print("Skipping integrity checks...\n"); }
@ -244,8 +244,8 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
return false; return false;
} }
else if (item->getType() != df::enums::item_type::GLOVES && else if (item->getType() != df::enums::item_type::GLOVES &&
item->getType() != df::enums::item_type::HELM && item->getType() != df::enums::item_type::HELM &&
item->getType() != df::enums::item_type::ARMOR && item->getType() != df::enums::item_type::ARMOR &&
item->getType() != df::enums::item_type::PANTS && item->getType() != df::enums::item_type::PANTS &&
item->getType() != df::enums::item_type::SHOES && item->getType() != df::enums::item_type::SHOES &&
!targetBodyPart) !targetBodyPart)
@ -295,7 +295,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); } if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); }
continue; continue;
} }
else else
{ {
// The specified body part has not been found, and we've reached the end of the list. Report failure. // The specified body part has not been found, and we've reached the end of the list. Report failure.
if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); } if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); }
@ -366,14 +366,14 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
// Collision detected; have we reached the limit? // Collision detected; have we reached the limit?
if (++collisions >= multiEquipLimit) if (++collisions >= multiEquipLimit)
{ {
if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); } if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); }
confirmedBodyPart = NULL; confirmedBodyPart = NULL;
break; break;
} }
} }
} }
if (confirmedBodyPart) if (confirmedBodyPart)
{ {
// Match found; no need to examine any other BPs // Match found; no need to examine any other BPs
if (verbose) { Core::print(" eligibility confirmed..."); } if (verbose) { Core::print(" eligibility confirmed..."); }
@ -412,31 +412,31 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
command_result df_forceequip(color_ostream &out, vector <string> & parameters) command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{ {
// The "here" option is hardcoded to true, because the plugin currently doesn't support // The "here" option is hardcoded to true, because the plugin currently doesn't support
// equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit) // equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit)
bool here = true; bool here = true;
// For balance (anti-cheating) reasons, the plugin applies a limit on the number of // For balance (anti-cheating) reasons, the plugin applies a limit on the number of
// item that can be equipped on any bodypart. This limit defaults to 1 but can be // item that can be equipped on any bodypart. This limit defaults to 1 but can be
// overridden with cmdline switches. // overridden with cmdline switches.
int multiEquipLimit = 1; int multiEquipLimit = 1;
// The plugin applies several pre-checks in order to reduce the risk of conflict // The plugin applies several pre-checks in order to reduce the risk of conflict
// and unintended side-effects. Most of these checks can be disabled via cmdline // and unintended side-effects. Most of these checks can be disabled via cmdline
bool ignore = false; bool ignore = false;
// By default, the plugin uses all gear piled on the selected square. Optionally, // By default, the plugin uses all gear piled on the selected square. Optionally,
// it can target only a single item (selected on the k menu) instead // it can target only a single item (selected on the k menu) instead
bool selected = false; bool selected = false;
// Most of the plugin's text output is suppressed by default. It can be enabled // Most of the plugin's text output is suppressed by default. It can be enabled
// to provide insight into errors, and/or for debugging purposes. // to provide insight into errors, and/or for debugging purposes.
bool verbose = false; bool verbose = false;
// By default, the plugin will mate each item to an appropriate bodypart. This // By default, the plugin will mate each item to an appropriate bodypart. This
// behaviour can be skipped if the user specifies a particular BP in the cmdline input. // behaviour can be skipped if the user specifies a particular BP in the cmdline input.
std::string targetBodyPartCode; std::string targetBodyPartCode;
// Parse the input // Parse the input
for (size_t i = 0; i < parameters.size(); i++) for (size_t i = 0; i < parameters.size(); i++)
{ {
string & p = parameters[i]; string & p = parameters[i];
if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man") if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man")
{ {
out << forceequip_help << endl; out << forceequip_help << endl;
@ -446,34 +446,34 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{ {
here = true; here = true;
} }
else if (p == "ignore" || p == "i") else if (p == "ignore" || p == "i")
{ {
ignore = true; ignore = true;
} }
else if (p == "multi" || p == "m") else if (p == "multi" || p == "m")
{ {
multiEquipLimit = INT_MAX; multiEquipLimit = INT_MAX;
} }
else if (p == "m2") else if (p == "m2")
{ {
multiEquipLimit = 2; multiEquipLimit = 2;
} }
else if (p == "m3") else if (p == "m3")
{ {
multiEquipLimit = 3; multiEquipLimit = 3;
} }
else if (p == "m4") else if (p == "m4")
{ {
multiEquipLimit = 4; multiEquipLimit = 4;
} }
else if (p == "selected" || p == "s") else if (p == "selected" || p == "s")
{ {
selected = true; selected = true;
} }
else if (p == "verbose" || p == "v") else if (p == "verbose" || p == "v")
{ {
verbose = true; verbose = true;
} }
else if (p == "bodypart" || p == "bp" ) else if (p == "bodypart" || p == "bp" )
{ {
// must be followed by bodypart code (e.g. NECK) // must be followed by bodypart code (e.g. NECK)
@ -492,150 +492,150 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters)
} }
} }
// Ensure that the map information is available (e.g. a game is actually in-progress) // Ensure that the map information is available (e.g. a game is actually in-progress)
if (!Maps::IsValid()) if (!Maps::IsValid())
{ {
out.printerr("Map is not available!\n"); out.printerr("Map is not available!\n");
return CR_FAILURE; return CR_FAILURE;
} }
// Lookup the cursor position // Lookup the cursor position
int cx, cy, cz; int cx, cy, cz;
DFCoord pos_cursor; DFCoord pos_cursor;
// needs a cursor // needs a cursor
if (!Gui::getCursorCoords(cx,cy,cz)) if (!Gui::getCursorCoords(cx,cy,cz))
{ {
out.printerr("Cursor position not found. Please enable the cursor.\n"); out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE; return CR_FAILURE;
} }
pos_cursor = DFCoord(cx,cy,cz); pos_cursor = DFCoord(cx,cy,cz);
// Iterate over all units, process the first one whose pos == pos_cursor // Iterate over all units, process the first one whose pos == pos_cursor
df::unit * targetUnit; df::unit * targetUnit;
size_t numUnits = world->units.all.size(); size_t numUnits = world->units.all.size();
for(size_t i=0; i< numUnits; i++) for(size_t i=0; i< numUnits; i++)
{ {
targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify
DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z); DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z);
if (pos_unit == pos_cursor) if (pos_unit == pos_cursor)
break; break;
if (i + 1 == numUnits) if (i + 1 == numUnits)
{ {
out.printerr("No unit found at cursor!\n"); out.printerr("No unit found at cursor!\n");
return CR_FAILURE; return CR_FAILURE;
} }
} }
// Assert: unit found. // Assert: unit found.
// If a specific bodypart was included in the command arguments, then search for it now // If a specific bodypart was included in the command arguments, then search for it now
df::body_part_raw * targetBodyPart = NULL; df::body_part_raw * targetBodyPart = NULL;
if (targetBodyPartCode.size() > 0) { if (targetBodyPartCode.size() > 0) {
for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++)
{ {
// Tentatively assume that the part is a match // Tentatively assume that the part is a match
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex); targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
if (targetBodyPart->token.compare(targetBodyPartCode) == 0) if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
{ {
// It is indeed a match; exit the loop (while leaving the variable populated) // It is indeed a match; exit the loop (while leaving the variable populated)
if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); } if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); }
break; break;
} }
else else
{ {
// Not a match; nullify the variable (it will get re-populated on the next pass through the loop) // Not a match; nullify the variable (it will get re-populated on the next pass through the loop)
if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); } if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); }
targetBodyPart = NULL; targetBodyPart = NULL;
} }
} }
if (!targetBodyPart) if (!targetBodyPart)
{ {
// Loop iteration is complete but no match was found. // Loop iteration is complete but no match was found.
out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str()); out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str());
return CR_FAILURE; return CR_FAILURE;
} }
} }
// Search for item(s) // Search for item(s)
MapCache mc; MapCache mc;
// iterate over all items, process those where pos == pos_cursor // iterate over all items, process those where pos == pos_cursor
int itemsEquipped = 0; int itemsEquipped = 0;
int itemsFound = 0; int itemsFound = 0;
int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary. int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary.
if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed). if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed).
for(int i=0; i< numItems; i++) for(int i=0; i< numItems; i++)
{ {
df::item * currentItem; df::item * currentItem;
// Search behaviour depends on whether the operation is driven by cursor location or UI selection // Search behaviour depends on whether the operation is driven by cursor location or UI selection
if (selected) if (selected)
{ {
// The "search" is trivial - the selection must always cover either one or zero items // The "search" is trivial - the selection must always cover either one or zero items
currentItem = Gui::getSelectedItem(out); currentItem = Gui::getSelectedItem(out);
if (!currentItem) { return CR_FAILURE; } if (!currentItem) { return CR_FAILURE; }
} }
else else
{ {
// Lookup the current item in the world-space // Lookup the current item in the world-space
currentItem = world->items.all[i]; currentItem = world->items.all[i];
// Test the item's position // Test the item's position
DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z); DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z);
if (pos_item != pos_cursor) if (pos_item != pos_cursor)
{ {
// The item is in the wrong place; skip it // The item is in the wrong place; skip it
// Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text // Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text
continue; continue;
} }
// Bypass any forbidden items // Bypass any forbidden items
else if (currentItem->flags.bits.forbid == 1) else if (currentItem->flags.bits.forbid == 1)
{ {
// The item is forbidden; skip it // The item is forbidden; skip it
if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); } if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); }
} }
} }
// Test the item; check whether we have any grounds to disqualify/reject it // Test the item; check whether we have any grounds to disqualify/reject it
if (currentItem->flags.bits.in_inventory == 1) if (currentItem->flags.bits.in_inventory == 1)
{ {
// The item is in a unit's inventory; skip it // The item is in a unit's inventory; skip it
if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); } if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); }
} }
else else
{ {
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes) itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose)) if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose))
{ {
// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer // // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer
// currentItem->getRace(); // currentItem->getRace();
// out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension()); // out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension());
itemsEquipped++; // Track the number of items successfully processed (for feedback purposes) itemsEquipped++; // Track the number of items successfully processed (for feedback purposes)
} }
} }
} }
if (itemsFound == 0) { if (itemsFound == 0) {
out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n"); out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n");
return CR_OK; return CR_OK;
} }
if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); } if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); }
if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); } if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); }
// At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded. // At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded.
// Therefore, we must update the map. // Therefore, we must update the map.
mc.WriteAll(); mc.WriteAll();
// Note: we might expect to recalculate the unit's weight at this point, in order to account for the // Note: we might expect to recalculate the unit's weight at this point, in order to account for the
// added items. In fact, this recalculation occurs automatically during each dwarf's "turn". // added items. In fact, this recalculation occurs automatically during each dwarf's "turn".
// The slight delay in recalculation is probably not worth worrying about. // The slight delay in recalculation is probably not worth worrying about.
// Work complete; report success // Work complete; report success
return CR_OK; return CR_OK;
} }

@ -9,6 +9,7 @@ local utils = require 'utils'
* isEnabled() * isEnabled()
* setEnabled(enable) * setEnabled(enable)
* listConstraints([job]) -> {...} * listConstraints([job]) -> {...}
* findConstraint(token) -> {...} or nil
* setConstraint(token[, by_count, goal, gap]) -> {...} * setConstraint(token[, by_count, goal, gap]) -> {...}
* deleteConstraint(token) -> true/false * deleteConstraint(token) -> true/false
@ -255,7 +256,7 @@ function constraintToToken(cspec)
end end
local mask_part local mask_part
if cspec.mat_mask then if cspec.mat_mask then
mask_part = table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',') mask_part = string.upper(table.concat(utils.list_bitfield_flags(cspec.mat_mask), ','))
end end
local mat_part local mat_part
if cspec.mat_type and cspec.mat_type >= 0 then if cspec.mat_type and cspec.mat_type >= 0 then
@ -270,8 +271,9 @@ function constraintToToken(cspec)
if cspec.is_local then if cspec.is_local then
table.insert(qlist, "LOCAL") table.insert(qlist, "LOCAL")
end end
if cspec.quality and cspec.quality > 0 then if cspec.min_quality and cspec.min_quality > 0 then
table.insert(qlist, df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality)) local qn = df.item_quality[cspec.min_quality] or error('invalid quality: '..cspec.min_quality)
table.insert(qlist, qn)
end end
local qpart local qpart
if #qlist > 0 then if #qlist > 0 then

@ -456,21 +456,23 @@ void viewscreen_unitlaborsst::refreshNames()
void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::calcSize()
{ {
num_rows = gps->dimy - 10; auto dim = Screen::getWindowSize();
num_rows = dim.y - 10;
if (num_rows > units.size()) if (num_rows > units.size())
num_rows = units.size(); num_rows = units.size();
int num_columns = gps->dimx - DISP_COLUMN_MAX - 1; int num_columns = dim.x - DISP_COLUMN_MAX - 1;
// min/max width of columns // min/max width of columns
int col_minwidth[DISP_COLUMN_MAX]; int col_minwidth[DISP_COLUMN_MAX];
int col_maxwidth[DISP_COLUMN_MAX]; int col_maxwidth[DISP_COLUMN_MAX];
col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_minwidth[DISP_COLUMN_HAPPINESS] = 4;
col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_HAPPINESS] = 4;
col_minwidth[DISP_COLUMN_NAME] = 0; col_minwidth[DISP_COLUMN_NAME] = 16;
col_maxwidth[DISP_COLUMN_NAME] = 0; col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below
col_minwidth[DISP_COLUMN_PROFESSION] = 0; col_minwidth[DISP_COLUMN_PROFESSION] = 10;
col_maxwidth[DISP_COLUMN_PROFESSION] = 0; col_maxwidth[DISP_COLUMN_PROFESSION] = 10; // adjusted in the loop below
col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60%
col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS;
@ -840,7 +842,7 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
const SkillColumn &col = columns[input_column]; const SkillColumn &col = columns[input_column];
bool newstatus = !unit->status.labors[col.labor]; bool newstatus = (col.labor == unit_labor::NONE) ? true : !unit->status.labors[col.labor];
for (int i = 0; i < NUM_COLUMNS; i++) for (int i = 0; i < NUM_COLUMNS; i++)
{ {
if (columns[i].group != col.group) if (columns[i].group != col.group)
@ -940,10 +942,11 @@ void viewscreen_unitlaborsst::render()
dfhack_viewscreen::render(); dfhack_viewscreen::render();
auto dim = Screen::getWindowSize();
Screen::clear(); Screen::clear();
Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); Screen::drawBorder(" Dwarf Manipulator - Manage Labors ");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap."); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap.");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession");
@ -1057,7 +1060,7 @@ void viewscreen_unitlaborsst::render()
} }
} }
else else
bg = 4; bg = 3;
Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row); Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row);
} }
} }
@ -1116,48 +1119,48 @@ void viewscreen_unitlaborsst::render()
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
} }
int x = 2; int x = 2, y = dim.y - 3;
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT));
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL));
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, "); OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW));
OutputString(15, x, gps->dimy - 3, ": ViewCre, "); OutputString(15, x, y, ": ViewCre, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE));
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); OutputString(15, x, y, ": Zoom-Cre");
x = 2; x = 2; y = dim.y - 2;
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN));
OutputString(15, x, gps->dimy - 2, ": Done, "); OutputString(15, x, y, ": Done, ");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN));
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP));
OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); OutputString(15, x, y, ": Sort by Skill, ");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN));
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP));
OutputString(15, x, gps->dimy - 2, ": Sort by ("); OutputString(15, x, y, ": Sort by (");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::CHANGETAB)); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CHANGETAB));
OutputString(15, x, gps->dimy - 2, ") "); OutputString(15, x, y, ") ");
switch (altsort) switch (altsort)
{ {
case ALTSORT_NAME: case ALTSORT_NAME:
OutputString(15, x, gps->dimy - 2, "Name"); OutputString(15, x, y, "Name");
break; break;
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
OutputString(15, x, gps->dimy - 2, "Profession"); OutputString(15, x, y, "Profession");
break; break;
case ALTSORT_HAPPINESS: case ALTSORT_HAPPINESS:
OutputString(15, x, gps->dimy - 2, "Happiness"); OutputString(15, x, y, "Happiness");
break; break;
case ALTSORT_ARRIVAL: case ALTSORT_ARRIVAL:
OutputString(15, x, gps->dimy - 2, "Arrival"); OutputString(15, x, y, "Arrival");
break; break;
default: default:
OutputString(15, x, gps->dimy - 2, "Unknown"); OutputString(15, x, y, "Unknown");
break; break;
} }
} }
@ -1193,9 +1196,10 @@ struct unitlist_hook : df::viewscreen_unitlistst
if (units[page].size()) if (units[page].size())
{ {
int x = 2; auto dim = Screen::getWindowSize();
OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); int x = 2, y = dim.y - 2;
OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); OutputString(12, x, y, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF));
OutputString(15, x, y, ": Manage labors (DFHack)");
} }
} }
}; };

@ -125,9 +125,9 @@ DFHack callbacks
The plugin interfaces with dfhack 'onupdate' hook. The plugin interfaces with dfhack 'onupdate' hook.
To register ruby code to be run every graphic frame, use: To register ruby code to be run every graphic frame, use:
handle = df.onupdate_register { puts 'i love flooding the console' } handle = df.onupdate_register('log') { puts 'i love flooding the console' }
You can also rate-limit when your callback is called to a number of game ticks: You can also rate-limit when your callback is called to a number of game ticks:
handle = df.onupdate_register(10) { puts '10 more in-game ticks elapsed' } handle = df.onupdate_register('myname', 10) { puts '10 more in-game ticks elapsed' }
In this case, the callback is called immediately, and then every X in-game In this case, the callback is called immediately, and then every X in-game
ticks (advances only when the game is unpaused). ticks (advances only when the game is unpaused).
To stop being called, use: To stop being called, use:

@ -56,7 +56,7 @@ module DFHack
def item_isfree(i) def item_isfree(i)
!i.flags.trader and !i.flags.trader and
!i.flags.in_job and !i.flags.in_job and
!i.flags.in_inventory and (!i.flags.in_inventory or i.general_refs.grep(GeneralRefContainedInItemst).first) and
!i.flags.removed and !i.flags.removed and
!i.flags.in_building and !i.flags.in_building and
!i.flags.owned and !i.flags.owned and

@ -159,8 +159,8 @@ module DFHack
v.inorganic_mat if v v.inorganic_mat if v
end end
# return the world_data.geo_biome for current tile # return the RegionMapEntry (from designation.biome)
def geo_biome def region_map_entry
b = designation.biome b = designation.biome
wd = df.world.world_data wd = df.world.world_data
@ -174,7 +174,12 @@ module DFHack
ry -= 1 if b < 3 and ry > 0 ry -= 1 if b < 3 and ry > 0
ry += 1 if b > 5 and ry < wd.world_height-1 ry += 1 if b > 5 and ry < wd.world_height-1
wd.geo_biomes[ wd.region_map[rx][ry].geo_index ] wd.region_map[rx][ry]
end
# return the world_data.geo_biome for current tile
def geo_biome
df.world.world_data.geo_biomes[ region_map_entry.geo_index ]
end end
# return the world_data.geo_biome.layer for current tile # return the world_data.geo_biome.layer for current tile
@ -182,14 +187,53 @@ module DFHack
geo_biome.layers[designation.geolayer_index] geo_biome.layers[designation.geolayer_index]
end end
# current tile mat_index (vein if applicable, or base material) # MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...)
def mat_index def mat_info
mat_index_vein or stone_layer.mat_index case tilemat
when :SOIL
base = stone_layer
if !df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
base = geo_biome.layers.find_all { |l| df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }.last
end
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
MaterialInfo.new(0, mat_index)
when :STONE
base = stone_layer
if df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY]
base = geo_biome.layers.find { |l| !df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }
end
mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length))
MaterialInfo.new(0, mat_index)
when :MINERAL
mat_index = (mat_index_vein || stone_layer.mat_index)
MaterialInfo.new(0, mat_index)
when :LAVA_STONE
# XXX this is wrong
# maybe should search world.region_details.pos == biome_region_pos ?
idx = mapblock.region_offset[designation.biome]
mat_index = df.world.world_data.region_details[idx].lava_stone
MaterialInfo.new(0, mat_index)
# TODO
#when :PLANT
#when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT
#when :FEATURE
#when :FROZEN_LIQUID
#when :CONSTRUCTION
else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER
MaterialInfo.new(-1, -1)
end
end end
# MaterialInfo: inorganic token for current tile def mat_type
def mat_info mat_info.mat_type
MaterialInfo.new(0, mat_index) end
def mat_index
mat_info.mat_index
end end
def inspect def inspect

@ -138,7 +138,6 @@ module DFHack
@@inspecting = {} # avoid infinite recursion on mutually-referenced objects @@inspecting = {} # avoid infinite recursion on mutually-referenced objects
def inspect def inspect
cn = self.class.name.sub(/^DFHack::/, '') cn = self.class.name.sub(/^DFHack::/, '')
cn << ' @' << ('0x%X' % _memaddr) if cn != ''
out = "#<#{cn}" out = "#<#{cn}"
return out << ' ...>' if @@inspecting[_memaddr] return out << ' ...>' if @@inspecting[_memaddr]
@@inspecting[_memaddr] = true @@inspecting[_memaddr] = true
@ -655,6 +654,13 @@ module DFHack
DFHack.memory_bitarray_set(@_memaddr, idx, v) DFHack.memory_bitarray_set(@_memaddr, idx, v)
end end
end end
def inspect
out = "#<DfFlagarray"
each_with_index { |e, idx|
out << " #{_indexenum.sym(idx)}" if e
}
out << '>'
end
include Enumerable include Enumerable
end end

@ -438,6 +438,12 @@ static VALUE rb_cDFHack;
// DFHack module ruby methods, binds specific dfhack methods // DFHack module ruby methods, binds specific dfhack methods
// df-dfhack version (eg "0.34.11-r2")
static VALUE rb_dfversion(VALUE self)
{
return rb_str_new(DFHACK_VERSION, strlen(DFHACK_VERSION));
}
// enable/disable calls to DFHack.onupdate() // enable/disable calls to DFHack.onupdate()
static VALUE rb_dfonupdate_active(VALUE self) static VALUE rb_dfonupdate_active(VALUE self)
{ {
@ -955,6 +961,7 @@ static void ruby_bind_dfhack(void) {
rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1);
rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1);
rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8);
rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0);
rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2); rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2);
rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1); rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);

@ -23,9 +23,12 @@ module Kernel
end end
module DFHack module DFHack
VERSION = version
class OnupdateCallback class OnupdateCallback
attr_accessor :callback, :timelimit, :minyear, :minyeartick attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description
def initialize(cb, tl, initdelay=0) def initialize(descr, cb, tl, initdelay=0)
@description = descr
@callback = cb @callback = cb
@ticklimit = tl @ticklimit = tl
@minyear = (tl ? df.cur_year : 0) @minyear = (tl ? df.cur_year : 0)
@ -34,22 +37,21 @@ module DFHack
# run callback if timedout # run callback if timedout
def check_run(year, yeartick, yearlen) def check_run(year, yeartick, yearlen)
if !@ticklimit if @ticklimit
@callback.call return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick)
else @minyear = year
if year > @minyear or (year == @minyear and yeartick >= @minyeartick) @minyeartick = yeartick + @ticklimit
@minyear = year if @minyeartick > yearlen
@minyeartick = yeartick + @ticklimit @minyear += 1
if @minyeartick > yearlen @minyeartick -= yearlen
@minyear += 1
@minyeartick -= yearlen
end
@callback.call
end end
end end
rescue # t0 = Time.now
@callback.call
# dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1
rescue Exception
df.onupdate_unregister self df.onupdate_unregister self
puts_err "onupdate cb #$!", $!.backtrace puts_err "onupdate #@description unregistered: #$!", $!.backtrace
end end
def <=>(o) def <=>(o)
@ -61,10 +63,11 @@ module DFHack
attr_accessor :onupdate_list, :onstatechange_list attr_accessor :onupdate_list, :onstatechange_list
# register a callback to be called every gframe or more # register a callback to be called every gframe or more
# ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 } # ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 }
def onupdate_register(ticklimit=nil, initialtickdelay=0, &b) def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b)
raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String)
@onupdate_list ||= [] @onupdate_list ||= []
@onupdate_list << OnupdateCallback.new(b, ticklimit, initialtickdelay) @onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay)
DFHack.onupdate_active = true DFHack.onupdate_active = true
if onext = @onupdate_list.sort.first if onext = @onupdate_list.sort.first
DFHack.onupdate_minyear = onext.minyear DFHack.onupdate_minyear = onext.minyear
@ -73,8 +76,9 @@ module DFHack
@onupdate_list.last @onupdate_list.last
end end
# delete the callback for onupdate ; use the value returned by onupdate_register # delete the callback for onupdate ; use the value returned by onupdate_register or the description
def onupdate_unregister(b) def onupdate_unregister(b)
b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String)
@onupdate_list.delete b @onupdate_list.delete b
if @onupdate_list.empty? if @onupdate_list.empty?
DFHack.onupdate_active = false DFHack.onupdate_active = false

@ -21,7 +21,7 @@ module DFHack
when :SelectTrainer when :SelectTrainer
v.trainer_unit[v.trainer_cursor] v.trainer_unit[v.trainer_cursor]
end end
else when :viewscreen_dwarfmodest
case ui.main.mode case ui.main.mode
when :ViewUnits when :ViewUnits
# nobody selected => idx == 0 # nobody selected => idx == 0
@ -33,6 +33,15 @@ module DFHack
else else
ui.follow_unit_tg if ui.follow_unit != -1 ui.follow_unit_tg if ui.follow_unit != -1
end end
when :viewscreen_dungeonmodest
case ui_advmode.menu
when :Default
world.units.active[0]
else
unit_find(cursor) # XXX
end
when :viewscreen_dungeon_monsterstatusst
curview.unit
end end
elsif what.kind_of?(Integer) elsif what.kind_of?(Integer)
# search by id # search by id

@ -329,8 +329,9 @@ protected:
// Display hotkey message // Display hotkey message
void print_search_option(int x, int y = -1) const void print_search_option(int x, int y = -1) const
{ {
auto dim = Screen::getWindowSize();
if (y == -1) if (y == -1)
y = gps->dimy - 2; y = dim.y - 2;
OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key));
OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); OutputString((entry_mode) ? 10 : 15, x, y, ": Search");
@ -413,8 +414,9 @@ public:
print_search_option(2); print_search_option(2);
else else
{ {
int x = 2; auto dim = Screen::getWindowSize();
OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); int x = 2, y = dim.y - 2;
OutputString(15, x, y, "Tab to enable Search");
} }
} }
@ -519,7 +521,8 @@ private:
virtual bool should_check_input(set<df::interface_key> *input) virtual bool should_check_input(set<df::interface_key> *input)
{ {
if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) ||
(!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF)))
{ {
if (!is_entry_mode()) if (!is_entry_mode())
{ {

@ -51,6 +51,12 @@
#include "df/squad_position.h" #include "df/squad_position.h"
#include "df/job.h" #include "df/job.h"
#include "df/general_ref_building_holderst.h" #include "df/general_ref_building_holderst.h"
#include "df/unit_health_info.h"
#include "df/activity_entry.h"
#include "df/activity_event_combat_trainingst.h"
#include "df/activity_event_individual_skill_drillst.h"
#include "df/activity_event_skill_demonstrationst.h"
#include "df/activity_event_sparringst.h"
#include <stdlib.h> #include <stdlib.h>
@ -128,6 +134,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak military-color-assigned [disable]\n" " tweak military-color-assigned [disable]\n"
" Color squad candidates already assigned to other squads in brown/green\n" " Color squad candidates already assigned to other squads in brown/green\n"
" to make them stand out more in the list.\n" " to make them stand out more in the list.\n"
" tweak military-training [disable]\n"
" Speed up melee squad training, removing inverse dependency on unit count.\n"
)); ));
return CR_OK; return CR_OK;
} }
@ -204,15 +212,31 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
{ {
typedef df::viewscreen_dwarfmodest interpose_base; typedef df::viewscreen_dwarfmodest interpose_base;
bool check_default()
{
switch (ui->main.mode) {
case ui_sidebar_mode::Default:
return true;
case ui_sidebar_mode::Build:
return ui_build_selector &&
(ui_build_selector->building_type < 0 ||
ui_build_selector->stage < 1);
default:
return false;
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input)) DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{ {
bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default); bool was_default = check_default();
df::coord view = Gui::getViewportPos(); df::coord view = Gui::getViewportPos();
df::coord cursor = Gui::getCursorPos(); df::coord cursor = Gui::getCursorPos();
INTERPOSE_NEXT(feed)(input); INTERPOSE_NEXT(feed)(input);
bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default); bool is_default = check_default();
df::coord cur_cursor = Gui::getCursorPos(); df::coord cur_cursor = Gui::getCursorPos();
if (is_default && !was_default) if (is_default && !was_default)
@ -233,7 +257,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
tmp.insert(interface_key::CURSOR_UP_Z); tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp); INTERPOSE_NEXT(feed)(&tmp);
} }
else if (cur_cursor.isValid()) else if (!is_default && cur_cursor.isValid())
{ {
last_cursor = df::coord(); last_cursor = df::coord();
} }
@ -662,6 +686,238 @@ struct military_assign_hook : df::viewscreen_layer_militaryst {
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render);
// Unit updates are executed based on an action divisor variable,
// which is computed from the alive unit count and has range 10-100.
static int adjust_unit_divisor(int value) {
return value*10/DF_GLOBAL_FIELD(ui, unit_action_divisor, 10);
}
static bool can_spar(df::unit *unit) {
return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap
(unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) &&
(!unit->health || (unit->health->flags.whole&0x7FF) == 0) &&
(!unit->job.current_job || unit->job.current_job != job_type::Rest);
}
static bool has_spar_inventory(df::unit *unit, df::job_skill skill)
{
using namespace df::enums::job_skill;
auto type = ENUM_ATTR(job_skill, type, skill);
if (type == job_skill_class::MilitaryWeapon)
{
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i];
if (item->mode == df::unit_inventory_item::Weapon &&
item->item->getMeleeSkill() == skill)
return true;
}
return false;
}
switch (skill) {
case THROW:
case RANGED_COMBAT:
return false;
case SHIELD:
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i];
if (item->mode == df::unit_inventory_item::Weapon &&
item->item->getType() == item_type::SHIELD)
return true;
}
return false;
case ARMOR:
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i];
if (item->mode == df::unit_inventory_item::Worn &&
item->item->isArmorNotClothing())
return true;
}
return false;
default:
return true;
}
}
struct military_training_ct_hook : df::activity_event_combat_trainingst {
typedef df::activity_event_combat_trainingst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit))
{
auto act = df::activity_entry::find(activity_id);
int cur_neid = act ? act->next_event_id : 0;
int cur_oc = organize_counter;
INTERPOSE_NEXT(process)(unit);
// Shorten the time it takes to organize stuff, so that in
// reality it remains the same instead of growing proportionally
// to the unit count.
if (organize_counter > cur_oc && organize_counter > 0)
organize_counter = adjust_unit_divisor(organize_counter);
if (act && act->next_event_id > cur_neid)
{
// New events were added. Check them.
for (size_t i = 0; i < act->events.size(); i++)
{
auto event = act->events[i];
if (event->flags.bits.dismissed || event->event_id < cur_neid)
continue;
if (auto sp = strict_virtual_cast<df::activity_event_sparringst>(event))
{
// Sparring has a problem in that all of its participants decrement
// the countdown variable. Fix this by multiplying it by the member count.
sp->countdown = sp->countdown * sp->participants.units.size();
}
else if (auto sd = strict_virtual_cast<df::activity_event_skill_demonstrationst>(event))
{
// Adjust initial counter values
sd->train_countdown = adjust_unit_divisor(sd->train_countdown);
sd->wait_countdown = adjust_unit_divisor(sd->wait_countdown);
// Check if the game selected the most skilled unit as the teacher
auto &units = sd->participants.units;
int maxv = -1, cur_xp = -1, minv = 0;
int best = -1;
size_t spar = 0;
for (size_t j = 0; j < units.size(); j++)
{
auto unit = df::unit::find(units[j]);
if (!unit) continue;
int xp = Units::getExperience(unit, sd->skill, true);
if (units[j] == sd->unit_id)
cur_xp = xp;
if (j == 0 || xp < minv)
minv = xp;
if (xp > maxv) {
maxv = xp;
best = j;
}
if (can_spar(unit) && has_spar_inventory(unit, sd->skill))
spar++;
}
color_ostream_proxy out(Core::getInstance().getConsole());
// If the xp gap is low, sometimes replace with sparring
if ((maxv - minv) < 64*15 && spar == units.size() &&
random_int(45) >= 30 + (maxv-minv)/64)
{
out.print("Replacing %s demonstration (xp %d-%d, gap %d) with sparring.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(), minv, maxv, maxv-minv);
if (auto spar = df::allocate<df::activity_event_sparringst>())
{
spar->event_id = sd->event_id;
spar->activity_id = sd->activity_id;
spar->parent_event_id = sd->parent_event_id;
spar->flags = sd->flags;
spar->participants = sd->participants;
spar->building_id = sd->building_id;
spar->countdown = 300*units.size();
delete sd;
act->events[i] = spar;
continue;
}
}
// If the teacher has less xp than somebody else, switch
if (best >= 0 && maxv > cur_xp)
{
out.print("Replacing %s teacher %d (%d xp) with %d (%d xp); xp gap %d.\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
sd->unit_id, cur_xp, units[best], maxv, maxv-minv);
sd->hist_figure_id = sd->participants.histfigs[best];
sd->unit_id = units[best];
}
else
{
out.print("Not changing %s demonstration (xp %d-%d, gap %d).\n",
ENUM_KEY_STR(job_skill, sd->skill).c_str(),
minv, maxv, maxv-minv);
}
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process);
struct military_training_sd_hook : df::activity_event_skill_demonstrationst {
typedef df::activity_event_skill_demonstrationst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit))
{
int cur_oc = organize_counter;
int cur_tc = train_countdown;
INTERPOSE_NEXT(process)(unit);
// Shorten the counters if they changed
if (organize_counter > cur_oc && organize_counter > 0)
organize_counter = adjust_unit_divisor(organize_counter);
if (train_countdown > cur_tc)
train_countdown = adjust_unit_divisor(train_countdown);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_training_sd_hook, process);
template<class T>
bool is_done(T *event, df::unit *unit)
{
return event->flags.bits.dismissed ||
binsearch_index(event->participants.units, unit->id) < 0;
}
struct military_training_sp_hook : df::activity_event_sparringst {
typedef df::activity_event_sparringst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit))
{
INTERPOSE_NEXT(process)(unit);
// Since there are no counters to fix, repeat the call
int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10;
for (int i = 1; i < cnt && !is_done(this, unit); i++)
INTERPOSE_NEXT(process)(unit);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_training_sp_hook, process);
struct military_training_id_hook : df::activity_event_individual_skill_drillst {
typedef df::activity_event_individual_skill_drillst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit))
{
INTERPOSE_NEXT(process)(unit);
// Since there are no counters to fix, repeat the call
int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10;
for (int i = 1; i < cnt && !is_done(this, unit); i++)
INTERPOSE_NEXT(process)(unit);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters) static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{ {
if (vector_get(parameters, 1) == "disable") if (vector_get(parameters, 1) == "disable")
@ -835,6 +1091,13 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
{ {
enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters);
} }
else if (cmd == "military-training")
{
enable_hook(out, INTERPOSE_HOOK(military_training_ct_hook, process), parameters);
enable_hook(out, INTERPOSE_HOOK(military_training_sd_hook, process), parameters);
enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters);
enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters);
}
else else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;

@ -293,6 +293,7 @@ typedef std::map<std::pair<int,int>, bool> TMaterialCache;
struct ItemConstraint { struct ItemConstraint {
PersistentDataItem config; PersistentDataItem config;
PersistentDataItem history;
// Fixed key parsed into fields // Fixed key parsed into fields
bool is_craft; bool is_craft;
@ -308,7 +309,7 @@ struct ItemConstraint {
int weight; int weight;
std::vector<ProtectedJob*> jobs; std::vector<ProtectedJob*> jobs;
int item_amount, item_count, item_inuse; int item_amount, item_count, item_inuse_amount, item_inuse_count;
bool request_suspend, request_resume; bool request_suspend, request_resume;
bool is_active, cant_resume_reported; bool is_active, cant_resume_reported;
@ -318,7 +319,7 @@ struct ItemConstraint {
public: public:
ItemConstraint() ItemConstraint()
: is_craft(false), min_quality(item_quality::Ordinary), is_local(false), : is_craft(false), min_quality(item_quality::Ordinary), is_local(false),
weight(0), item_amount(0), item_count(0), item_inuse(0), weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0),
is_active(false), cant_resume_reported(false) is_active(false), cant_resume_reported(false)
{} {}
@ -326,9 +327,8 @@ public:
void setGoalCount(int v) { config.ival(0) = v; } void setGoalCount(int v) { config.ival(0) = v; }
int goalGap() { int goalGap() {
int cval = (config.ival(1) <= 0) ? 5 : config.ival(1); int cval = (config.ival(1) <= 0) ? std::min(5,goalCount()/2) : config.ival(1);
int cmax = std::max(goalCount()-5, goalCount()/2); return std::max(1, std::min(goalCount()-1, cval));
return std::max(1, std::min(cmax, cval));
} }
void setGoalGap(int v) { config.ival(1) = v; } void setGoalGap(int v) { config.ival(1) = v; }
@ -353,6 +353,44 @@ public:
request_resume = (size <= goalCount()-goalGap()); request_resume = (size <= goalCount()-goalGap());
request_suspend = (size >= goalCount()); request_suspend = (size >= goalCount());
} }
static const size_t int28_size = PersistentDataItem::int28_size;
static const size_t hist_entry_size = PersistentDataItem::int28_size * 4;
size_t history_size() {
return history.data_size() / hist_entry_size;
}
size_t history_base(int idx) {
size_t hsize = history_size();
return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size;
}
int history_count(int idx) {
return history.get_int28(history_base(idx) + 0*int28_size);
}
int history_amount(int idx) {
return history.get_int28(history_base(idx) + 1*int28_size);
}
int history_inuse_count(int idx) {
return history.get_int28(history_base(idx) + 2*int28_size);
}
int history_inuse_amount(int idx) {
return history.get_int28(history_base(idx) + 3*int28_size);
}
void updateHistory()
{
size_t buffer_size = history_size();
if (buffer_size < 28)
history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size);
history.ival(0) = (history.ival(0)+1) % buffer_size;
size_t base = history.ival(0) * hist_entry_size;
history.set_int28(base + 0*int28_size, item_count);
history.set_int28(base + 1*int28_size, item_amount);
history.set_int28(base + 2*int28_size, item_inuse_count);
history.set_int28(base + 3*int28_size, item_inuse_amount);
}
}; };
/****************************** /******************************
@ -446,7 +484,7 @@ static void cleanup_state(color_ostream &out)
} }
static void check_lost_jobs(color_ostream &out, int ticks); static void check_lost_jobs(color_ostream &out, int ticks);
static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg = NULL); static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg = NULL, bool create = true);
static void start_protect(color_ostream &out) static void start_protect(color_ostream &out)
{ {
@ -650,6 +688,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
update_job_data(out); update_job_data(out);
process_constraints(out); process_constraints(out);
for (size_t i = 0; i < constraints.size(); i++)
constraints[i]->updateHistory();
} }
} }
@ -660,7 +701,11 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
* ITEM COUNT CONSTRAINT * * ITEM COUNT CONSTRAINT *
******************************/ ******************************/
static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg) static std::string history_key(PersistentDataItem &config) {
return stl_sprintf("workflow/history/%d", config.entry_id());
}
static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg, bool create)
{ {
std::vector<std::string> tokens; std::vector<std::string> tokens;
split_string(&tokens, str, "/"); split_string(&tokens, str, "/");
@ -683,7 +728,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
if (item.subtype >= 0) if (item.subtype >= 0)
weight += 10000; weight += 10000;
df::dfhack_material_category mat_mask; df::dfhack_material_category mat_mask(0);
std::string maskstr = vector_get(tokens,1); std::string maskstr = vector_get(tokens,1);
if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) {
out.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); out.printerr("Cannot decode material mask: %s\n", maskstr.c_str());
@ -757,6 +802,9 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
return ct; return ct;
} }
if (!create)
return NULL;
ItemConstraint *nct = new ItemConstraint; ItemConstraint *nct = new ItemConstraint;
nct->is_craft = is_craft; nct->is_craft = is_craft;
nct->item = item; nct->item = item;
@ -774,6 +822,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
nct->init(str); nct->init(str);
} }
nct->history = World::GetPersistentData(history_key(nct->config), NULL);
constraints.push_back(nct); constraints.push_back(nct);
return nct; return nct;
} }
@ -785,6 +835,7 @@ static void delete_constraint(ItemConstraint *cv)
vector_erase_at(constraints, idx); vector_erase_at(constraints, idx);
World::DeletePersistentData(cv->config); World::DeletePersistentData(cv->config);
World::DeletePersistentData(cv->history);
delete cv; delete cv;
} }
@ -1062,7 +1113,8 @@ static void map_job_items(color_ostream &out)
{ {
constraints[i]->item_amount = 0; constraints[i]->item_amount = 0;
constraints[i]->item_count = 0; constraints[i]->item_count = 0;
constraints[i]->item_inuse = 0; constraints[i]->item_inuse_amount = 0;
constraints[i]->item_inuse_count = 0;
} }
meltable_count = 0; meltable_count = 0;
@ -1074,7 +1126,7 @@ static void map_job_items(color_ostream &out)
#define F(x) bad_flags.bits.x = true; #define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect); F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader); F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1); F(in_building); F(construction); F(artifact);
#undef F #undef F
bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS);
@ -1095,9 +1147,9 @@ static void map_job_items(color_ostream &out)
bool is_invalid = false; bool is_invalid = false;
// don't count worn items // don't count worn items
if (item->getWear() >= 1) if (item->getWear() >= 1)
is_invalid = true; is_invalid = true;
// Special handling // Special handling
switch (itype) { switch (itype) {
@ -1175,7 +1227,8 @@ static void map_job_items(color_ostream &out)
isAssignedSquad(item)) isAssignedSquad(item))
{ {
is_invalid = true; is_invalid = true;
cv->item_inuse++; cv->item_inuse_count++;
cv->item_inuse_amount += item->getStackSize();
} }
else else
{ {
@ -1346,7 +1399,9 @@ static void push_constraint(lua_State *L, ItemConstraint *cv)
Lua::SetField(L, cv->is_craft, ctable, "is_craft"); Lua::SetField(L, cv->is_craft, ctable, "is_craft");
lua_getglobal(L, "copyall");
Lua::PushDFObject(L, &cv->mat_mask); Lua::PushDFObject(L, &cv->mat_mask);
lua_call(L, 1, 1);
lua_setfield(L, -2, "mat_mask"); lua_setfield(L, -2, "mat_mask");
Lua::SetField(L, cv->material.type, ctable, "mat_type"); Lua::SetField(L, cv->material.type, ctable, "mat_type");
@ -1363,7 +1418,8 @@ static void push_constraint(lua_State *L, ItemConstraint *cv)
Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); Lua::SetField(L, cv->item_amount, ctable, "cur_amount");
Lua::SetField(L, cv->item_count, ctable, "cur_count"); Lua::SetField(L, cv->item_count, ctable, "cur_count");
Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); Lua::SetField(L, cv->item_inuse_amount, ctable, "cur_in_use_amount");
Lua::SetField(L, cv->item_inuse_count, ctable, "cur_in_use_count");
// Current state value // Current state value
@ -1417,6 +1473,22 @@ static int listConstraints(lua_State *L)
return 1; return 1;
} }
static int findConstraint(lua_State *L)
{
auto token = luaL_checkstring(L, 1);
color_ostream &out = *Lua::GetOutput(L);
update_data_structures(out);
ItemConstraint *icv = get_constraint(out, token, NULL, false);
if (icv)
push_constraint(L, icv);
else
lua_pushnil(L);
return 1;
}
static int setConstraint(lua_State *L) static int setConstraint(lua_State *L)
{ {
auto token = luaL_checkstring(L, 1); auto token = luaL_checkstring(L, 1);
@ -1425,6 +1497,7 @@ static int setConstraint(lua_State *L)
int gap = luaL_optint(L, 4, -1); int gap = luaL_optint(L, 4, -1);
color_ostream &out = *Lua::GetOutput(L); color_ostream &out = *Lua::GetOutput(L);
update_data_structures(out);
ItemConstraint *icv = get_constraint(out, token); ItemConstraint *icv = get_constraint(out, token);
if (!icv) if (!icv)
@ -1442,6 +1515,40 @@ static int setConstraint(lua_State *L)
return 1; return 1;
} }
static int getCountHistory(lua_State *L)
{
auto token = luaL_checkstring(L, 1);
color_ostream &out = *Lua::GetOutput(L);
update_data_structures(out);
ItemConstraint *icv = get_constraint(out, token, NULL, false);
if (icv)
{
size_t hsize = icv->history_size();
lua_createtable(L, hsize, 0);
for (int i = hsize-1; i >= 0; i--)
{
lua_createtable(L, 0, 4);
Lua::SetField(L, icv->history_amount(i), -1, "cur_amount");
Lua::SetField(L, icv->history_count(i), -1, "cur_count");
Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount");
Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count");
lua_rawseti(L, -2, hsize-i); // reverse order
}
}
else
lua_pushnil(L);
return 1;
}
DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_LUA_FUNCTION(isEnabled), DFHACK_LUA_FUNCTION(isEnabled),
DFHACK_LUA_FUNCTION(setEnabled), DFHACK_LUA_FUNCTION(setEnabled),
@ -1451,7 +1558,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(listConstraints), DFHACK_LUA_COMMAND(listConstraints),
DFHACK_LUA_COMMAND(findConstraint),
DFHACK_LUA_COMMAND(setConstraint), DFHACK_LUA_COMMAND(setConstraint),
DFHACK_LUA_COMMAND(getCountHistory),
DFHACK_LUA_END DFHACK_LUA_END
}; };
@ -1499,10 +1608,10 @@ static void print_constraint(color_ostream &out, ItemConstraint *cv, bool no_job
<< cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl;
out.reset_color(); out.reset_color();
if (cv->item_count || cv->item_inuse) if (cv->item_count || cv->item_inuse_count)
out << prefix << " items: amount " << cv->item_amount << "; " out << prefix << " items: amount " << cv->item_amount << "; "
<< cv->item_count << " stacks available, " << cv->item_count << " stacks available, "
<< cv->item_inuse << " in use." << endl; << cv->item_inuse_count << " in use." << endl;
if (no_job) return; if (no_job) return;

@ -1,12 +1,12 @@
// Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...) // Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...)
// //
// the following things would be nice: // the following things would be nice:
// - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info
// - help finding caged dwarves? (maybe even allow to build their cages for fast release) // - help finding caged dwarves? (maybe even allow to build their cages for fast release)
// - dump info about caged goblins, animals, ... // - dump info about caged goblins, animals, ...
// - count grass tiles on pastures, move grazers to new pasture if old pasture is empty // - count grass tiles on pastures, move grazers to new pasture if old pasture is empty
// move hungry unpastured grazers to pasture with grass // move hungry unpastured grazers to pasture with grass
// //
// What is working so far: // What is working so far:
// - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff) // - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff)
// - mark a zone which is used for future assignment commands // - mark a zone which is used for future assignment commands
@ -287,7 +287,7 @@ static PersistentDataItem config_autonestbox;
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) switch (event)
{ {
case DFHack::SC_MAP_LOADED: case DFHack::SC_MAP_LOADED:
// initialize from the world just loaded // initialize from the world just loaded
@ -320,7 +320,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
autoNestbox(out, false); autoNestbox(out, false);
} }
} }
if(enable_autobutcher) if(enable_autobutcher)
{ {
if(++ticks_autobutcher >= sleep_autobutcher) if(++ticks_autobutcher >= sleep_autobutcher)
@ -630,7 +630,7 @@ bool isTamable(df::unit* unit)
{ {
df::caste_raw *caste = raw->caste[j]; df::caste_raw *caste = raw->caste[j];
if(caste->flags.is_set(caste_raw_flags::PET) || if(caste->flags.is_set(caste_raw_flags::PET) ||
caste->flags.is_set(caste_raw_flags::PET_EXOTIC)) caste->flags.is_set(caste_raw_flags::PET_EXOTIC))
return true; return true;
} }
return false; return false;
@ -652,8 +652,8 @@ bool isFemale(df::unit* unit)
bool hasValidMapPos(df::unit* unit) bool hasValidMapPos(df::unit* unit)
{ {
if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0
&& unit->pos.x < world->map.x_count && unit->pos.x < world->map.x_count
&& unit->pos.y < world->map.y_count && unit->pos.y < world->map.y_count
&& unit->pos.z < world->map.z_count) && unit->pos.z < world->map.z_count)
return true; return true;
else else
@ -662,7 +662,7 @@ bool hasValidMapPos(df::unit* unit)
bool isNaked(df::unit* unit) bool isNaked(df::unit* unit)
{ {
return (unit->inventory.empty()); return (unit->inventory.empty());
} }
@ -694,7 +694,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
out << " '" << unit->name.nickname << "'"; out << " '" << unit->name.nickname << "'";
out << ", "; out << ", ";
} }
if(isAdult(unit)) if(isAdult(unit))
out << "adult"; out << "adult";
else if(isBaby(unit)) else if(isBaby(unit))
@ -702,7 +702,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
else if(isChild(unit)) else if(isChild(unit))
out << "child"; out << "child";
out << " "; out << " ";
// sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca)
// all animals I looked at don't have babies anyways, their offspring starts as CHILD // all animals I looked at don't have babies anyways, their offspring starts as CHILD
//out << getRaceBabyName(unit); //out << getRaceBabyName(unit);
//out << getRaceChildName(unit); //out << getRaceChildName(unit);
@ -725,7 +725,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
out << ", age: " << getUnitAge(unit); out << ", age: " << getUnitAge(unit);
if(isTame(unit)) if(isTame(unit))
out << ", tame"; out << ", tame";
if(isOwnCiv(unit)) if(isOwnCiv(unit))
out << ", owned"; out << ", owned";
if(isWar(unit)) if(isWar(unit))
@ -742,7 +742,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
out << ", grazer"; out << ", grazer";
if(isMilkable(unit)) if(isMilkable(unit))
out << ", milkable"; out << ", milkable";
if(verbose) if(verbose)
{ {
out << ". Pos: ("<<unit->pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; out << ". Pos: ("<<unit->pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl;
@ -820,7 +820,7 @@ bool isPitPond(df::building * building)
if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0) if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0)
return true; return true;
else else
return false; return false;
} }
@ -879,7 +879,7 @@ df::unit* findUnitById(int32_t id)
int32_t findPenPitAtCursor() int32_t findPenPitAtCursor()
{ {
int32_t foundID = -1; int32_t foundID = -1;
if(cursor->x == -30000) if(cursor->x == -30000)
return -1; return -1;
@ -906,7 +906,7 @@ int32_t findPenPitAtCursor()
int32_t findCageAtCursor() int32_t findCageAtCursor()
{ {
int32_t foundID = -1; int32_t foundID = -1;
if(cursor->x == -30000) if(cursor->x == -30000)
return -1; return -1;
@ -935,7 +935,7 @@ int32_t findCageAtCursor()
int32_t findChainAtCursor() int32_t findChainAtCursor()
{ {
int32_t foundID = -1; int32_t foundID = -1;
if(cursor->x == -30000) if(cursor->x == -30000)
return -1; return -1;
@ -974,7 +974,7 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef()
// being called for the first time, need to initialize the vtable // being called for the first time, need to initialize the vtable
for(size_t i = 0; i < world->units.all.size(); i++) for(size_t i = 0; i < world->units.all.size(); i++)
{ {
df::unit * creature = world->units.all[i]; df::unit * creature = world->units.all[i];
for(size_t r = 0; r<creature->general_refs.size(); r++) for(size_t r = 0; r<creature->general_refs.size(); r++)
{ {
df::general_ref* ref; df::general_ref* ref;
@ -984,7 +984,7 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef()
if (strict_virtual_cast<df::general_ref_building_civzone_assignedst>(ref)) if (strict_virtual_cast<df::general_ref_building_civzone_assignedst>(ref))
{ {
// !! calling new() doesn't work, need _identity.instantiate() instead !! // !! calling new() doesn't work, need _identity.instantiate() instead !!
newref = (df::general_ref_building_civzone_assignedst*) newref = (df::general_ref_building_civzone_assignedst*)
df::general_ref_building_civzone_assignedst::_identity.instantiate(); df::general_ref_building_civzone_assignedst::_identity.instantiate();
vt_initialized = true; vt_initialized = true;
break; break;
@ -1090,13 +1090,13 @@ bool isInBuiltCageRoom(df::unit* unit)
for (size_t b=0; b < world->buildings.all.size(); b++) for (size_t b=0; b < world->buildings.all.size(); b++)
{ {
df::building* building = world->buildings.all[b]; df::building* building = world->buildings.all[b];
// !!! building->isRoom() returns true if the building can be made a room but currently isn't // !!! building->isRoom() returns true if the building can be made a room but currently isn't
// !!! except for coffins/tombs which always return false // !!! except for coffins/tombs which always return false
// !!! using the bool is_room however gives the correct state/value // !!! using the bool is_room however gives the correct state/value
if(!building->is_room) if(!building->is_room)
continue; continue;
if(building->getType() == building_type::Cage) if(building->getType() == building_type::Cage)
{ {
df::building_cagest* cage = (df::building_cagest*) building; df::building_cagest* cage = (df::building_cagest*) building;
@ -1118,7 +1118,7 @@ bool isInBuiltCageRoom(df::unit* unit)
// check a map position for a built cage // check a map position for a built cage
// animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage
// if they are on animal stockpiles they should count as unassigned to allow pasturing them // if they are on animal stockpiles they should count as unassigned to allow pasturing them
// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever // if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever
bool isBuiltCageAtPos(df::coord pos) bool isBuiltCageAtPos(df::coord pos)
{ {
bool cage = false; bool cage = false;
@ -1232,7 +1232,7 @@ bool isFreeEgglayer(df::unit * unit)
{ {
if( !isDead(unit) && !isUndead(unit) if( !isDead(unit) && !isUndead(unit)
&& isFemale(unit) && isFemale(unit)
&& isTame(unit) && isTame(unit)
&& isOwnCiv(unit) && isOwnCiv(unit)
&& isEggLayer(unit) && isEggLayer(unit)
&& !isAssigned(unit) && !isAssigned(unit)
@ -1306,7 +1306,7 @@ bool unassignUnitFromBuilding(df::unit* unit)
{ {
// game does not erase the ref until creature gets removed from cage // game does not erase the ref until creature gets removed from cage
//unit->general_refs.erase(unit->general_refs.begin() + idx); //unit->general_refs.erase(unit->general_refs.begin() + idx);
// walk through buildings, check cages for inhabitants, compare ids // walk through buildings, check cages for inhabitants, compare ids
for (size_t b=0; b < world->buildings.all.size(); b++) for (size_t b=0; b < world->buildings.all.size(); b++)
{ {
@ -1379,7 +1379,7 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building
<< "before using 'assign' for the first time." << endl; << "before using 'assign' for the first time." << endl;
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
} }
// check if unit is already pastured, remove that ref from unit and old pasture // check if unit is already pastured, remove that ref from unit and old pasture
// testing showed that removing the ref from the unit only seems to be necessary for pastured creatures // testing showed that removing the ref from the unit only seems to be necessary for pastured creatures
// if they are in cages on stockpiles the game unassigns them automatically // if they are in cages on stockpiles the game unassigns them automatically
@ -1401,8 +1401,8 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building
df::building_civzonest * civz = (df::building_civzonest *) building; df::building_civzonest * civz = (df::building_civzonest *) building;
civz->assigned_creature.push_back(unit->id); civz->assigned_creature.push_back(unit->id);
out << "Unit " << unit->id out << "Unit " << unit->id
<< "(" << getRaceName(unit) << ")" << "(" << getRaceName(unit) << ")"
<< " assigned to zone " << building->id; << " assigned to zone " << building->id;
if(isPitPond(building)) if(isPitPond(building))
out << " (pit/pond)."; out << " (pit/pond).";
@ -1425,7 +1425,7 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building
// don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug)
if(unit->relations.pet_owner_id != -1) if(unit->relations.pet_owner_id != -1)
return CR_OK; return CR_OK;
// check if unit is already pastured or caged, remove refs where necessary // check if unit is already pastured or caged, remove refs where necessary
bool cleared_old = unassignUnitFromBuilding(unit); bool cleared_old = unassignUnitFromBuilding(unit);
if(verbose) if(verbose)
@ -1442,8 +1442,8 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building
df::building_cagest* civz = (df::building_cagest*) building; df::building_cagest* civz = (df::building_cagest*) building;
civz->assigned_creature.push_back(unit->id); civz->assigned_creature.push_back(unit->id);
out << "Unit " << unit->id out << "Unit " << unit->id
<< "(" << getRaceName(unit) << ")" << "(" << getRaceName(unit) << ")"
<< " assigned to cage " << building->id; << " assigned to cage " << building->id;
out << endl; out << endl;
@ -1631,7 +1631,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose)
out << " (pit flags:" << civ->pit_flags.whole << ")"; out << " (pit flags:" << civ->pit_flags.whole << ")";
if(civ->pit_flags.bits.is_pond) if(civ->pit_flags.bits.is_pond)
out << ", pond"; out << ", pond";
else else
out << ", pit"; out << ", pit";
} }
out << endl; out << endl;
@ -1654,7 +1654,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose)
df::unit * creature = world->units.all[i]; df::unit * creature = world->units.all[i];
if(creature->id != cindex) if(creature->id != cindex)
continue; continue;
unitInfo(out, creature, verbose); unitInfo(out, creature, verbose);
} }
} }
@ -1694,7 +1694,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose)
df::unit * creature = world->units.all[i]; df::unit * creature = world->units.all[i];
if(creature->id != cindex) if(creature->id != cindex)
continue; continue;
unitInfo(out, creature, verbose); unitInfo(out, creature, verbose);
} }
} }
@ -1737,7 +1737,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
bool zone_info = false; bool zone_info = false;
//bool cage_info = false; //bool cage_info = false;
//bool chain_info = false; //bool chain_info = false;
bool invert_filter = false; bool invert_filter = false;
bool find_unassigned = false; bool find_unassigned = false;
bool find_caged = false; bool find_caged = false;
@ -1763,17 +1763,17 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
bool find_female = false; bool find_female = false;
bool find_not_female = false; bool find_not_female = false;
bool find_egglayer = false; bool find_egglayer = false;
bool find_not_egglayer = false; bool find_not_egglayer = false;
bool find_grazer = false; bool find_grazer = false;
bool find_not_grazer = false; bool find_not_grazer = false;
bool find_milkable = false; bool find_milkable = false;
bool find_not_milkable = false; bool find_not_milkable = false;
bool find_named = false; bool find_named = false;
bool find_not_named = false; bool find_not_named = false;
bool find_naked = false; bool find_naked = false;
bool find_not_naked = false; bool find_not_naked = false;
bool find_tamable = false; bool find_tamable = false;
bool find_not_tamable = false; bool find_not_tamable = false;
bool find_agemin = false; bool find_agemin = false;
bool find_agemax = false; bool find_agemax = false;
@ -1801,7 +1801,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < parameters.size(); i++) for (size_t i = 0; i < parameters.size(); i++)
{ {
string & p = parameters[i]; string & p = parameters[i];
if (p == "help" || p == "?") if (p == "help" || p == "?")
{ {
out << zone_help << endl; out << zone_help << endl;
@ -2388,7 +2388,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
df::unit *unit = world->units.all[c]; df::unit *unit = world->units.all[c];
// ignore dead and undead units // ignore dead and undead units
if (isDead(unit) || isUndead(unit)) if (isDead(unit) || isUndead(unit))
continue; continue;
// ignore merchant units by default // ignore merchant units by default
@ -2432,7 +2432,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
|| (find_grazer && !isGrazer(unit)) || (find_grazer && !isGrazer(unit))
|| (find_not_grazer && isGrazer(unit)) || (find_not_grazer && isGrazer(unit))
|| (find_egglayer && !isEggLayer(unit)) || (find_egglayer && !isEggLayer(unit))
|| (find_not_egglayer && isEggLayer(unit)) || (find_not_egglayer && isEggLayer(unit))
|| (find_milkable && !isMilkable(unit)) || (find_milkable && !isMilkable(unit))
|| (find_not_milkable && isMilkable(unit)) || (find_not_milkable && isMilkable(unit))
|| (find_male && !isMale(unit)) || (find_male && !isMale(unit))
@ -2441,10 +2441,10 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
|| (find_not_female && isFemale(unit)) || (find_not_female && isFemale(unit))
|| (find_named && !unit->name.has_name) || (find_named && !unit->name.has_name)
|| (find_not_named && unit->name.has_name) || (find_not_named && unit->name.has_name)
|| (find_naked && !isNaked(unit)) || (find_naked && !isNaked(unit))
|| (find_not_naked && isNaked(unit)) || (find_not_naked && isNaked(unit))
|| (find_tamable && !isTamable(unit)) || (find_tamable && !isTamable(unit))
|| (find_not_tamable && isTamable(unit)) || (find_not_tamable && isTamable(unit))
|| (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit))) || (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit)))
|| (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough? || (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough?
|| (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit))) || (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit)))
@ -2471,7 +2471,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
unitInfo(out, unit, verbose); unitInfo(out, unit, verbose);
continue; continue;
} }
if(nick_set) if(nick_set)
{ {
Units::setNickname(unit, target_nick); Units::setNickname(unit, target_nick);
@ -2540,8 +2540,8 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
} }
// de-assign from pen or pit // de-assign from pen or pit
// using the zone tool to free creatures from cages or chains // using the zone tool to free creatures from cages or chains
// is pointless imo since that is already quite easy using the ingame UI. // is pointless imo since that is already quite easy using the ingame UI.
// but it's easy to implement so I might as well add it later // but it's easy to implement so I might as well add it later
if(building_unassign) if(building_unassign)
{ {
@ -2555,7 +2555,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
out << "Unit unassigned." << endl; out << "Unit unassigned." << endl;
else else
out << "Unit is not assigned to an activity zone!" << endl; out << "Unit is not assigned to an activity zone!" << endl;
return CR_OK; return CR_OK;
} }
@ -2574,7 +2574,7 @@ command_result df_autonestbox(color_ostream &out, vector <string> & parameters)
for (size_t i = 0; i < parameters.size(); i++) for (size_t i = 0; i < parameters.size(); i++)
{ {
string & p = parameters[i]; string & p = parameters[i];
if (p == "help" || p == "?") if (p == "help" || p == "?")
{ {
out << autonestbox_help << endl; out << autonestbox_help << endl;
@ -2687,7 +2687,7 @@ command_result autoNestbox( color_ostream &out, bool verbose = false )
Gui::showAnnouncement(announce, 2, false); Gui::showAnnouncement(announce, 2, false);
out << announce << endl; out << announce << endl;
// can complain again // can complain again
// (might lead to spamming the same message twice, but catches the case // (might lead to spamming the same message twice, but catches the case
// where for example 2 new egglayers hatched right after 2 zones were created and assigned) // where for example 2 new egglayers hatched right after 2 zones were created and assigned)
autonestbox_did_complain = false; autonestbox_did_complain = false;
} }
@ -2699,7 +2699,7 @@ command_result autoNestbox( color_ostream &out, bool verbose = false )
// getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case // getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case
// (assuming that the value from there indicates in which tick of the current year the unit was born) // (assuming that the value from there indicates in which tick of the current year the unit was born)
bool compareUnitAgesYounger(df::unit* i, df::unit* j) bool compareUnitAgesYounger(df::unit* i, df::unit* j)
{ {
int32_t age_i = getUnitAge(i); int32_t age_i = getUnitAge(i);
int32_t age_j = getUnitAge(j); int32_t age_j = getUnitAge(j);
@ -2708,10 +2708,10 @@ bool compareUnitAgesYounger(df::unit* i, df::unit* j)
age_i = i->relations.birth_time; age_i = i->relations.birth_time;
age_j = j->relations.birth_time; age_j = j->relations.birth_time;
} }
return (age_i < age_j); return (age_i < age_j);
} }
bool compareUnitAgesOlder(df::unit* i, df::unit* j) bool compareUnitAgesOlder(df::unit* i, df::unit* j)
{ {
int32_t age_i = getUnitAge(i); int32_t age_i = getUnitAge(i);
int32_t age_j = getUnitAge(j); int32_t age_j = getUnitAge(j);
if(age_i == 0 && age_j == 0) if(age_i == 0 && age_j == 0)
@ -2719,7 +2719,7 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j)
age_i = i->relations.birth_time; age_i = i->relations.birth_time;
age_j = j->relations.birth_time; age_j = j->relations.birth_time;
} }
return (age_i > age_j); return (age_i > age_j);
} }
//enum WatchedRaceSubtypes //enum WatchedRaceSubtypes
@ -2748,7 +2748,7 @@ public:
vector <df::unit*> mk_ptr; vector <df::unit*> mk_ptr;
vector <df::unit*> fa_ptr; vector <df::unit*> fa_ptr;
vector <df::unit*> ma_ptr; vector <df::unit*> ma_ptr;
WatchedRace(bool watch, int id, int _fk, int _mk, int _fa, int _ma) WatchedRace(bool watch, int id, int _fk, int _mk, int _fa, int _ma)
{ {
isWatched = watch; isWatched = watch;
@ -2786,8 +2786,8 @@ public:
string keyname = "autobutcher/watchlist/" + getRaceName(raceId); string keyname = "autobutcher/watchlist/" + getRaceName(raceId);
out << "Something failed, could not find/create config key " << keyname << "!" << endl; out << "Something failed, could not find/create config key " << keyname << "!" << endl;
} }
} }
void RemoveConfig(color_ostream & out) void RemoveConfig(color_ostream & out)
{ {
if(!rconfig.isValid()) if(!rconfig.isValid())
@ -2802,7 +2802,7 @@ public:
sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger);
sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger);
} }
void PushUnit(df::unit * unit) void PushUnit(df::unit * unit)
{ {
if(isFemale(unit)) if(isFemale(unit))
@ -2894,7 +2894,7 @@ public:
} }
}; };
// vector of races handled by autobutcher // vector of races handled by autobutcher
// the name is a bit misleading since entries can be set to 'unwatched' // the name is a bit misleading since entries can be set to 'unwatched'
// to ignore them for a while but still keep the target count settings // to ignore them for a while but still keep the target count settings
std::vector<WatchedRace*> watched_races; std::vector<WatchedRace*> watched_races;
@ -2933,7 +2933,7 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
} }
// parse main command // parse main command
string & p = parameters[0]; string & p = parameters[0];
if (p == "help" || p == "?") if (p == "help" || p == "?")
{ {
out << autobutcher_help << endl; out << autobutcher_help << endl;
@ -3058,19 +3058,19 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
if(list_watched) if(list_watched)
{ {
out << "Autobutcher status: "; out << "Autobutcher status: ";
if(enable_autobutcher) if(enable_autobutcher)
out << "enabled,"; out << "enabled,";
else else
out << "not enabled,"; out << "not enabled,";
if (enable_autobutcher_autowatch) if (enable_autobutcher_autowatch)
out << " autowatch,"; out << " autowatch,";
else else
out << " noautowatch,"; out << " noautowatch,";
out << " sleep: " << sleep_autobutcher << endl; out << " sleep: " << sleep_autobutcher << endl;
out << "Default setting for new races:" out << "Default setting for new races:"
<< " fk=" << default_fk << " fk=" << default_fk
<< " mk=" << default_mk << " mk=" << default_mk
@ -3094,7 +3094,7 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
out << "watched: "; out << "watched: ";
else else
out << "not watched: "; out << "not watched: ";
out << name out << name
<< " fk=" << w->fk << " fk=" << w->fk
<< " mk=" << w->mk << " mk=" << w->mk
<< " fa=" << w->fa << " fa=" << w->fa
@ -3114,7 +3114,7 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
out << run << "start" << endl; out << run << "start" << endl;
if(!enable_autobutcher) if(!enable_autobutcher)
out << run << "stop" << endl; out << run << "stop" << endl;
if (enable_autobutcher_autowatch) if (enable_autobutcher_autowatch)
out << run << "autowatch" << endl; out << run << "autowatch" << endl;
@ -3147,8 +3147,8 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
} }
// parse rest of parameters for commands followed by a list of races // parse rest of parameters for commands followed by a list of races
if( watch_race if( watch_race
|| unwatch_race || unwatch_race
|| forget_race || forget_race
|| change_target ) || change_target )
{ {
@ -3226,7 +3226,7 @@ command_result df_autobutcher(color_ostream &out, vector <string> & parameters)
else else
{ {
// map race names from parameter list to ids // map race names from parameter list to ids
size_t num_races = df::global::world->raws.creatures.all.size(); size_t num_races = df::global::world->raws.creatures.all.size();
while(target_racenames.size()) while(target_racenames.size())
{ {
bool found_race = false; bool found_race = false;
@ -3345,7 +3345,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
|| isHunter(unit) // ignore hunting dogs etc || isHunter(unit) // ignore hunting dogs etc
// ignore creatures in built cages which are defined as rooms to leave zoos alone // ignore creatures in built cages which are defined as rooms to leave zoos alone
// (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher)
|| (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom()
|| unit->name.has_name || unit->name.has_name
) )
continue; continue;
@ -3372,7 +3372,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
string announce; string announce;
announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId);
Gui::showAnnouncement(announce, 2, false); Gui::showAnnouncement(announce, 2, false);
//out << announce << endl; //out << announce << endl;
} }
} }
@ -3389,7 +3389,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false )
string announce; string announce;
announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str(); announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str();
Gui::showAnnouncement(announce, 2, false); Gui::showAnnouncement(announce, 2, false);
//out << announce << endl; //out << announce << endl;
} }
} }
//out << slaughter_count << " units total marked for slaughter." << endl; //out << slaughter_count << " units total marked for slaughter." << endl;
@ -3425,13 +3425,13 @@ command_result start_autobutcher(color_ostream &out)
config_autobutcher.ival(0) = enable_autobutcher; config_autobutcher.ival(0) = enable_autobutcher;
out << "Starting autobutcher." << endl; out << "Starting autobutcher." << endl;
init_autobutcher(out); init_autobutcher(out);
return CR_OK; return CR_OK;
} }
command_result init_autobutcher(color_ostream &out) command_result init_autobutcher(color_ostream &out)
{ {
cleanup_autobutcher(out); cleanup_autobutcher(out);
config_autobutcher = World::GetPersistentData("autobutcher/config"); config_autobutcher = World::GetPersistentData("autobutcher/config");
if(config_autobutcher.isValid()) if(config_autobutcher.isValid())
@ -3466,9 +3466,9 @@ command_result init_autobutcher(color_ostream &out)
std::vector<PersistentDataItem> items; std::vector<PersistentDataItem> items;
World::GetPersistentData(&items, "autobutcher/watchlist/", true); World::GetPersistentData(&items, "autobutcher/watchlist/", true);
for (auto p = items.begin(); p != items.end(); p++) for (auto p = items.begin(); p != items.end(); p++)
{ {
string key = p->key(); string key = p->key();
out << "Reading from save: " << key << endl; out << "Reading from save: " << key << endl;
//out << " raceid: " << p->ival(0) << endl; //out << " raceid: " << p->ival(0) << endl;
//out << " watched: " << p->ival(1) << endl; //out << " watched: " << p->ival(1) << endl;
@ -3476,7 +3476,7 @@ command_result init_autobutcher(color_ostream &out)
//out << " mk: " << p->ival(3) << endl; //out << " mk: " << p->ival(3) << endl;
//out << " fa: " << p->ival(4) << endl; //out << " fa: " << p->ival(4) << endl;
//out << " ma: " << p->ival(5) << endl; //out << " ma: " << p->ival(5) << endl;
WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5));
w->rconfig = *p; w->rconfig = *p;
watched_races.push_back(w); watched_races.push_back(w);
@ -3514,13 +3514,13 @@ command_result start_autonestbox(color_ostream &out)
config_autonestbox.ival(0) = enable_autonestbox; config_autonestbox.ival(0) = enable_autonestbox;
out << "Starting autonestbox." << endl; out << "Starting autonestbox." << endl;
init_autonestbox(out); init_autonestbox(out);
return CR_OK; return CR_OK;
} }
command_result init_autonestbox(color_ostream &out) command_result init_autonestbox(color_ostream &out)
{ {
cleanup_autonestbox(out); cleanup_autonestbox(out);
config_autonestbox = World::GetPersistentData("autonestbox/config"); config_autonestbox = World::GetPersistentData("autonestbox/config");
if(config_autonestbox.isValid()) if(config_autonestbox.isValid())

@ -0,0 +1,152 @@
class AutoFarm
def initialize
@thresholds = Hash.new(50)
@lastcounts = Hash.new(0)
end
def setthreshold(id, v)
if df.world.raws.plants.all.find { |r| r.id == id }
@thresholds[id] = v.to_i
else
puts "No plant with id #{id}"
end
end
def setdefault(v)
@thresholds.default = v.to_i
end
def is_plantable(plant)
season = df.cur_season
harvest = df.cur_season_tick + plant.growdur * 10
will_finish = harvest < 10080
can_plant = plant.flags[season]
can_plant = can_plant && (will_finish || plant.flags[(season+1)%4])
can_plant
end
def find_plantable_plants
plantable = {}
for i in 0..df.ui.tasks.known_plants.length-1
if df.ui.tasks.known_plants[i]
plant = df.world.raws.plants.all[i]
if is_plantable(plant)
plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0)
plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0)
end
end
end
return plantable
end
def set_farms( plants, farms)
return if farms.length == 0
if plants.length == 0
plants = [-1]
end
season = df.cur_season
idx = 0
farms.each { |f|
f.plant_id[season] = plants[idx]
idx = (idx + 1) % plants.length
}
end
def process
return false unless @running
plantable = find_plantable_plants
counts = Hash.new(0)
df.world.items.other[:PLANT].each { |i|
if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect &&
!i.flags.hostile && !i.flags.on_fire && !i.flags.rotten &&
!i.flags.trader && !i.flags.in_building && !i.flags.construction &&
!i.flags.artifact && plantable.has_key?(i.mat_index))
counts[i.mat_index] = counts[i.mat_index] + i.stack_size
end
}
plants_s = []
plants_u = []
@lastcounts.clear
plantable.each_key { |k|
plant = df.world.raws.plants.all[k]
if (counts[k] < @thresholds[plant.id])
plants_s.push(k) if plantable[k] == :Surface
plants_u.push(k) if plantable[k] == :Underground
end
@lastcounts[plant.id] = counts[k]
}
farms_s = []
farms_u = []
df.world.buildings.other[:FARM_PLOT].each { |f|
if (f.flags.exists)
outside = df.map_designation_at(f.centerx,f.centery,f.z).outside
farms_s.push(f) if outside
farms_u.push(f) unless outside
end
}
set_farms(plants_s, farms_s)
set_farms(plants_u, farms_u)
end
def start
@onupdate = df.onupdate_register('autofarm', 100) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
stat = @running ? "Running." : "Stopped."
@thresholds.each { |k,v|
stat += "\n#{k} limit #{v} current #{@lastcounts[k]}"
}
stat += "\nDefault: #{@thresholds.default}"
stat
end
end
$AutoFarm = AutoFarm.new unless $AutoFarm
case $script_args[0]
when 'start'
$AutoFarm.start
when 'end', 'stop'
$AutoFarm.stop
when 'default'
$AutoFarm.setdefault($script_args[1])
when 'threshold'
t = $script_args[1]
$script_args[2..-1].each {|i|
$AutoFarm.setthreshold(i, t)
}
when 'delete'
$AutoFarm.stop
$AutoFarm = nil
else
if $AutoFarm
puts $AutoFarm.status
else
puts "AI not started"
end
end

@ -0,0 +1,58 @@
class AutoUnsuspend
def initialize
end
def process
return false unless @running
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend)
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)." unless count == 0
end
def start
@onupdate = df.onupdate_register('autounsuspend', 5) { process }
@running = true
end
def stop
df.onupdate_unregister(@onupdate)
@running = false
end
def status
@running ? 'Running.' : 'Stopped.'
end
end
case $script_args[0]
when 'start'
$AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend
$AutoUnsuspend.start
when 'end', 'stop'
$AutoUnsuspend.stop
else
if $AutoUnsuspend
puts $AutoUnsuspend.status
else
puts 'Not loaded.'
end
end

@ -43,9 +43,356 @@ function check_repeat(job, cb)
end end
end end
function describe_item_type(iobj)
local itemline = 'any item'
if iobj.is_craft then
itemline = 'any craft'
elseif iobj.item_type >= 0 then
itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type
local subtype = iobj.item_subtype or -1
local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
if def then
itemline = def.name
elseif count >= 0 then
itemline = 'any '..itemline
end
end
return itemline
end
function is_caste_mat(iobj)
return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end
function describe_material(iobj)
local matline = 'any material'
if is_caste_mat(iobj) then
matline = 'no material'
elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
if info then
matline = info:toString()
else
matline = iobj.mat_type..':'..iobj.mat_index
end
end
return matline
end
local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }
RangeEditor = defclass(RangeEditor, widgets.Label)
RangeEditor.ATTRS {
get_cb = DEFAULT_NIL,
save_cb = DEFAULT_NIL
}
function RangeEditor:init(args)
self:setText{
{ key = 'BUILDING_TRIGGER_ENABLE_CREATURE',
text = function()
local cons = self.get_cb() or null_cons
if cons.goal_by_count then
return ': Count stacks '
else
return ': Count items '
end
end,
on_activate = self:callback('onChangeUnit') },
{ key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify',
on_activate = self:callback('onEditRange') },
NEWLINE, ' ',
{ key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_gap', 5) },
{ key = 'BUILDING_TRIGGER_MIN_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_gap', -1) },
{ text = function()
local cons = self.get_cb() or null_cons
return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap)
end },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_value', -1) },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_value', 5) },
{ text = function()
local cons = self.get_cb() or null_cons
return string.format(': Max %-4d', cons.goal_value)
end },
}
end
function RangeEditor:onChangeUnit()
local cons = self.get_cb()
cons.goal_by_count = not cons.goal_by_count
self.save_cb(cons)
end
function RangeEditor:onEditRange()
local cons = self.get_cb()
dlg.showInputPrompt(
'Input Range',
'Enter the new constraint range:',
COLOR_WHITE,
(cons.goal_value-cons.goal_gap)..'-'..cons.goal_value,
function(text)
local maxv = string.match(text, '^%s*(%d+)%s*$')
if maxv then
cons.goal_value = maxv
return self.save_cb(cons)
end
local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$')
if minv and maxv and minv ~= maxv then
cons.goal_value = math.max(minv,maxv)
cons.goal_gap = math.abs(maxv-minv)
return self.save_cb(cons)
end
dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED)
end
)
end
function RangeEditor:onIncRange(field, delta)
local cons = self.get_cb()
if not cons.goal_by_count then
delta = delta * 5
end
cons[field] = math.max(1, cons[field] + delta)
self.save_cb(cons)
end
NewConstraint = defclass(NewConstraint, gui.FramedScreen)
NewConstraint.focus_path = 'workflow/new'
NewConstraint.ATTRS {
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'New workflow constraint',
frame_width = 39,
frame_height = 20,
frame_inset = 1,
constraint = DEFAULT_NIL,
on_submit = DEFAULT_NIL,
}
function NewConstraint:init(args)
self.constraint = args.constraint or {}
rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false })
local matlist = {}
local matsel = 1
local matmask = self.constraint.mat_mask
for i,v in ipairs(df.dfhack_material_category) do
if v and v ~= 'wood2' then
table.insert(matlist, { icon = self:callback('isMatSelected', v), text = v })
if matmask and matmask[v] and matsel == 1 then
matsel = #matlist
end
end
end
self:addviews{
widgets.Label{
frame = { l = 0, t = 0 },
text = 'Items matching:'
},
widgets.Label{
frame = { l = 1, t = 2, w = 26 },
text = {
'Type: ',
{ pen = COLOR_LIGHTCYAN,
text = function() return describe_item_type(self.constraint) end },
NEWLINE, ' ',
{ key = 'CUSTOM_T', text = ': Select, ',
on_activate = self:callback('chooseType') },
{ key = 'CUSTOM_SHIFT_C', text = ': Crafts',
on_activate = self:callback('chooseCrafts') },
NEWLINE, NEWLINE,
'Material: ',
{ pen = COLOR_LIGHTCYAN,
text = function() return describe_material(self.constraint) end },
NEWLINE, ' ',
{ key = 'CUSTOM_P', text = ': Specific',
on_activate = self:callback('chooseMaterial') },
NEWLINE, NEWLINE,
'Other:',
NEWLINE, ' ',
{ key = 'D_MILITARY_SUPPLIES_WATER_DOWN',
on_activate = self:callback('incQuality', -1) },
{ key = 'D_MILITARY_SUPPLIES_WATER_UP', key_sep = ': ',
text = function()
return df.item_quality[self.constraint.min_quality or 0]..' quality'
end,
on_activate = self:callback('incQuality', 1) },
NEWLINE, ' ',
{ key = 'CUSTOM_L', key_sep = ': ',
text = function()
if self.constraint.is_local then
return 'Locally made only'
else
return 'Include foreign'
end
end,
on_activate = self:callback('toggleLocal') },
}
},
widgets.Label{
frame = { l = 0, t = 13 },
text = {
'Desired range: ',
{ pen = COLOR_LIGHTCYAN,
text = function()
local cons = self.constraint
local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value
if cons.goal_by_count then
return goal .. ' stacks'
else
return goal .. ' items'
end
end },
}
},
RangeEditor{
frame = { l = 1, t = 15 },
get_cb = self:cb_getfield('constraint'),
save_cb = self:callback('onRangeChange'),
},
widgets.Label{
frame = { l = 30, t = 0 },
text = 'Mat class'
},
widgets.List{
view_id = 'matlist',
frame = { l = 30, t = 2, w = 9, h = 18 },
scroll_keys = widgets.STANDARDSCROLL,
choices = matlist,
selected = matsel,
on_submit = self:callback('onToggleMatclass')
},
widgets.Label{
frame = { l = 0, b = 0, w = 29 },
text = {
{ key = 'LEAVESCREEN', text = ': Cancel, ',
on_activate = self:callback('dismiss') },
{ key = 'MENU_CONFIRM', key_sep = ': ',
text = function()
if self.is_existing then return 'Update' else return 'Create new' end
end,
on_activate = function()
self:dismiss()
if self.on_submit then
self.on_submit(self.constraint)
end
end },
}
},
}
end
function NewConstraint:postinit()
self:onChange()
end
function NewConstraint:onChange()
local token = workflow.constraintToToken(self.constraint)
local out = workflow.findConstraint(token)
if out then
self.constraint = out
self.is_existing = true
else
self.constraint.token = token
self.is_existing = false
end
end
function NewConstraint:chooseType()
guimat.ItemTypeDialog{
prompt = 'Please select a new item type',
hide_none = true,
on_select = function(itype,isub)
local cons = self.constraint
cons.item_type = itype
cons.item_subtype = isub
cons.is_craft = nil
self:onChange()
end
}:show()
end
function NewConstraint:chooseCrafts()
local cons = self.constraint
cons.item_type = -1
cons.item_subtype = -1
cons.is_craft = true
self:onChange()
end
function NewConstraint:chooseMaterial()
local cons = self.constraint
guimat.MaterialDialog{
prompt = 'Please select a new material',
none_caption = 'any material',
frame_width = 37,
on_select = function(mat_type, mat_index)
local cons = self.constraint
cons.mat_type = mat_type
cons.mat_index = mat_index
cons.mat_mask = nil
self:onChange()
end
}:show()
end
function NewConstraint:incQuality(diff)
local cons = self.constraint
local nq = (cons.min_quality or 0) + diff
if nq < 0 then
nq = df.item_quality.Masterful
elseif nq > df.item_quality.Masterful then
nq = 0
end
cons.min_quality = nq
self:onChange()
end
function NewConstraint:toggleLocal()
local cons = self.constraint
cons.is_local = not cons.is_local
self:onChange()
end
function NewConstraint:isMatSelected(token)
if self.constraint.mat_mask and self.constraint.mat_mask[token] then
return { ch = '\xfb', fg = COLOR_LIGHTGREEN }
else
return nil
end
end
function NewConstraint:onToggleMatclass(idx,obj)
local cons = self.constraint
if cons.mat_mask and cons.mat_mask[obj.text] then
cons.mat_mask[obj.text] = false
else
cons.mat_mask = cons.mat_mask or {}
cons.mat_mask[obj.text] = true
cons.mat_type = -1
cons.mat_index = -1
end
self:onChange()
end
function NewConstraint:onRangeChange()
local cons = self.constraint
cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1))
end
JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) JobConstraints = defclass(JobConstraints, guidm.MenuOverlay)
JobConstraints.focus_path = 'workflow-job' JobConstraints.focus_path = 'workflow/job'
JobConstraints.ATTRS { JobConstraints.ATTRS {
job = DEFAULT_NIL, job = DEFAULT_NIL,
@ -53,8 +400,6 @@ JobConstraints.ATTRS {
frame_background = COLOR_BLACK, frame_background = COLOR_BLACK,
} }
local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }
function JobConstraints:init(args) function JobConstraints:init(args)
self.building = dfhack.job.getHolder(self.job) self.building = dfhack.job.getHolder(self.job)
@ -71,40 +416,11 @@ function JobConstraints:init(args)
row_height = 4, row_height = 4,
scroll_keys = widgets.SECONDSCROLL, scroll_keys = widgets.SECONDSCROLL,
}, },
widgets.Label{ RangeEditor{
frame = { l = 0, b = 3 }, frame = { l = 0, b = 3 },
enabled = self:callback('isAnySelected'), enabled = self:callback('isAnySelected'),
text = { get_cb = self:callback('getCurConstraint'),
{ key = 'BUILDING_TRIGGER_ENABLE_CREATURE', save_cb = self:callback('saveConstraint'),
text = function()
local cons = self:getCurConstraint() or null_cons
if cons.goal_by_count then
return ': Count stacks '
else
return ': Count items '
end
end,
on_activate = self:callback('onChangeUnit') },
{ key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify',
on_activate = self:callback('onEditRange') },
NEWLINE, ' ',
{ key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_gap', 5) },
{ key = 'BUILDING_TRIGGER_MIN_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_gap', -1) },
{ text = function()
local cons = self:getCurConstraint() or null_cons
return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap)
end },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN',
on_activate = self:callback('onIncRange', 'goal_value', -1) },
{ key = 'BUILDING_TRIGGER_MAX_SIZE_UP',
on_activate = self:callback('onIncRange', 'goal_value', 5) },
{ text = function()
local cons = self:getCurConstraint() or null_cons
return string.format(': Max %-4d', cons.goal_value)
end },
}
}, },
widgets.Label{ widgets.Label{
frame = { l = 0, b = 0 }, frame = { l = 0, b = 0 },
@ -132,43 +448,6 @@ function JobConstraints:onGetSelectedJob()
return self.job return self.job
end end
function describe_item_type(iobj)
local itemline = 'any item'
if iobj.is_craft then
itemline = 'any craft'
elseif iobj.item_type >= 0 then
itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type
local subtype = iobj.item_subtype or -1
local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype)
local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype)
if def then
itemline = def.name
elseif count >= 0 then
itemline = 'any '..itemline
end
end
return itemline
end
function is_caste_mat(iobj)
return dfhack.items.isCasteMaterial(iobj.item_type or -1)
end
function describe_material(iobj)
local matline = 'any material'
if is_caste_mat(iobj) then
matline = 'no material'
elseif (iobj.mat_type or -1) >= 0 then
local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index)
if info then
matline = info:toString()
else
matline = iobj.mat_type..':'..iobj.mat_index
end
end
return matline
end
function JobConstraints:initListChoices(clist, sel_token) function JobConstraints:initListChoices(clist, sel_token)
clist = clist or workflow.listConstraints(self.job) clist = clist or workflow.listConstraints(self.job)
@ -247,45 +526,6 @@ function JobConstraints:saveConstraint(cons)
self:initListChoices(nil, out.token) self:initListChoices(nil, out.token)
end end
function JobConstraints:onChangeUnit()
local cons = self:getCurConstraint()
cons.goal_by_count = not cons.goal_by_count
self:saveConstraint(cons)
end
function JobConstraints:onEditRange()
local cons = self:getCurConstraint()
dlg.showInputPrompt(
'Input Range',
'Enter the new constraint range:',
COLOR_WHITE,
(cons.goal_value-cons.goal_gap)..'-'..cons.goal_value,
function(text)
local maxv = string.match(text, '^%s*(%d+)%s*$')
if maxv then
cons.goal_value = maxv
return self:saveConstraint(cons)
end
local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$')
if minv and maxv and minv ~= maxv then
cons.goal_value = math.max(minv,maxv)
cons.goal_gap = math.abs(maxv-minv)
return self:saveConstraint(cons)
end
dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED)
end
)
end
function JobConstraints:onIncRange(field, delta)
local cons = self:getCurConstraint()
if not cons.goal_by_count then
delta = delta * 5
end
cons[field] = math.max(1, cons[field] + delta)
self:saveConstraint(cons)
end
function JobConstraints:onNewConstraint() function JobConstraints:onNewConstraint()
local outputs = workflow.listJobOutputs(self.job) local outputs = workflow.listJobOutputs(self.job)
if #outputs == 0 then if #outputs == 0 then
@ -318,7 +558,10 @@ function JobConstraints:onNewConstraint()
COLOR_WHITE, COLOR_WHITE,
choices, choices,
function(idx,item) function(idx,item)
self:saveConstraint(item.obj) NewConstraint{
constraint = item.obj,
on_submit = self:callback('saveConstraint')
}:show()
end end
) )
end end

@ -0,0 +1,117 @@
# control your levers from the dfhack console
def lever_pull_job(bld)
ref = DFHack::GeneralRefBuildingHolderst.cpp_new
ref.building_id = bld.id
job = DFHack::Job.cpp_new
job.job_type = :PullLever
job.pos = [bld.centerx, bld.centery, bld.z]
job.general_refs << ref
bld.jobs << job
df.job_link job
puts lever_descr(bld)
end
def lever_pull_cheat(bld)
bld.linked_mechanisms.each { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r|
r.building_tg.setTriggerState(bld.state)
}
}
bld.state = (bld.state == 0 ? 1 : 0)
puts lever_descr(bld)
end
def lever_descr(bld, idx=nil)
ret = []
# lever description
descr = ''
descr << "#{idx}: " if idx
descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}"
bld.jobs.each { |j|
if j.job_type == :PullLever
flags = ''
flags << ', repeat' if j.flags.repeat
flags << ', suspended' if j.flags.suspend
descr << " (pull order#{flags})"
end
}
bld.linked_mechanisms.map { |i|
i.general_refs.grep(DFHack::GeneralRefBuildingHolderst)
}.flatten.each { |r|
# linked building description
tg = r.building_tg
state = tg.gate_flags.closed ? 'closed' : 'opened'
state << ', closing' if tg.gate_flags.closing
state << ', opening' if tg.gate_flags.opening
ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}")
# indent other links
descr = descr.gsub(/./, ' ')
}
ret << descr if ret.empty?
ret
end
def lever_list
@lever_list = []
df.world.buildings.other[:TRAP].find_all { |bld|
bld.trap_type == :Lever
}.sort_by { |bld| bld.id }.each { |bld|
puts lever_descr(bld, @lever_list.length)
@lever_list << bld.id
}
end
case $script_args[0]
when 'pull'
cheat = $script_args.delete('--cheat') || $script_args.delete('--now')
id = $script_args[1].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
if cheat
lever_pull_cheat(bld)
else
lever_pull_job(bld)
end
when 'list'
lever_list
when /^\d+$/
id = $script_args[0].to_i
id = @lever_list[id] || id
bld = df.building_find(id)
raise 'invalid lever id' if not bld
puts lever_descr(bld)
else
puts <<EOS
Lever control from the dfhack console
Usage:
lever list
shows the list of levers in the fortress, with their id and links
lever pull 42
order the dwarves to pull lever 42
lever pull 42 --cheat
magically pull lever 42 immediately
EOS
end

@ -4,7 +4,7 @@ $magma_sources ||= []
case $script_args[0] case $script_args[0]
when 'here' when 'here'
$magma_onupdate ||= df.onupdate_register(12) { $magma_onupdate ||= df.onupdate_register('magmasource', 12) {
# called every 12 game ticks (100x a dwarf day) # called every 12 game ticks (100x a dwarf day)
if $magma_sources.empty? if $magma_sources.empty?
df.onupdate_unregister($magma_onupdate) df.onupdate_unregister($magma_onupdate)

@ -99,6 +99,16 @@ function boost_population(entry, factor, boost_count)
return boost_count return boost_count
end end
function incr_population(entry, factor, boost_count)
for _,v in ipairs(entry.records) do
if v.quantity < 10000001 then
boost_count = boost_count + 1
v.quantity = math.max(0, v.quantity + factor)
end
end
return boost_count
end
local args = {...} local args = {...}
local pops = enum_populations() local pops = enum_populations()
@ -123,6 +133,26 @@ elseif args[1] == 'boost' or args[1] == 'boost-all' then
end end
end end
print('Updated '..count..' populations.')
elseif args[1] == 'incr' or args[1] == 'incr-all' then
local factor = tonumber(args[3])
if not factor then
qerror('Invalid increment factor.')
end
local count = 0
if args[1] == 'incr' then
local entry = pops.any[args[2]] or qerror('Unknown population token.')
count = incr_population(entry, factor, count)
else
for k,entry in pairs(pops.any) do
if string.match(k, args[2]) then
count = incr_population(entry, factor, count)
end
end
end
print('Updated '..count..' populations.') print('Updated '..count..' populations.')
else else
print([[ print([[
@ -137,5 +167,9 @@ Usage:
population, otherwise decreases it. population, otherwise decreases it.
region-pops boost-all <pattern> <factor> region-pops boost-all <pattern> <factor>
Same as above, but match using a pattern acceptable to list. Same as above, but match using a pattern acceptable to list.
region-pops incr <TOKEN> <factor>
Augment (or diminish) all populations of TOKEN by factor (additive).
region-pops incr-all <pattern> <factor>
Same as above, but match using a pattern acceptable to list.
]]) ]])
end end

@ -21,7 +21,7 @@ slayit = lambda { |u|
else else
# it's getting hot around here # it's getting hot around here
# !!WARNING!! do not call on a magma-safe creature # !!WARNING!! do not call on a magma-safe creature
ouh = df.onupdate_register(1) { ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) {
if u.flags1.dead if u.flags1.dead
df.onupdate_unregister(ouh) df.onupdate_unregister(ouh)
else else

@ -0,0 +1,200 @@
# mark stuff inside of cages for dumping.
def plural(nr, name)
# '1 cage' / '4 cages'
"#{nr} #{name}#{'s' if nr > 1}"
end
def cage_dump_items(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsItemst)
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_armor(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Worn
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_weapons(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst)
ref.unit_tg.inventory.each { |it|
next if it.mode != :Weapon
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_all(list)
count = 0
count_cage = 0
list.each { |cage|
pre_count = count
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
next if ref.item_tg.flags.dump
count += 1
ref.item_tg.flags.dump = true
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
next if it.item.flags.dump
count += 1
it.item.flags.dump = true
}
end
}
count_cage += 1 if pre_count != count
}
puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}"
end
def cage_dump_list(list)
count_total = Hash.new(0)
empty_cages = 0
list.each { |cage|
count = Hash.new(0)
cage.general_refs.each { |ref|
case ref
when DFHack::GeneralRefContainsItemst
count[ref.item_tg._rtti_classname] += 1
when DFHack::GeneralRefContainsUnitst
ref.unit_tg.inventory.each { |it|
count[it.item._rtti_classname] += 1
}
# TODO vermin ?
else
puts "unhandled ref #{ref.inspect}" if $DEBUG
end
}
type = case cage
when DFHack::ItemCagest; 'Cage'
when DFHack::ItemAnimaltrapst; 'Animal trap'
else cage._rtti_classname
end
if count.empty?
empty_cages += 1
else
puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
end
count.each { |k, v| count_total[k] += v }
}
if list.length > 2
puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" }
puts "with #{plural(empty_cages, 'empty cage')}"
end
end
# handle magic script arguments
here_only = $script_args.delete 'here'
if here_only
it = df.item_find
list = [it]
if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) }
end
puts 'Please select a cage' if list.empty?
elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first
list = []
ids.each { |id|
$script_args.delete id
if not it = df.item_find(id.to_i)
puts "Invalid item id #{id}"
elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst)
puts "Item ##{id} is not a cage"
list << it
else
list << it
end
}
puts 'Please use a valid cage id' if list.empty?
else
list = df.world.items.other[:ANY_CAGE_OR_TRAP]
end
# act
case $script_args[0]
when 'items'
cage_dump_items(list) if not list.empty?
when 'armor'
cage_dump_armor(list) if not list.empty?
when 'weapons'
cage_dump_weapons(list) if not list.empty?
when 'all'
cage_dump_all(list) if not list.empty?
when 'list'
cage_dump_list(list) if not list.empty?
else
puts <<EOS
Marks items inside all cages for dumping.
Add 'here' to dump stuff only for selected cage.
Add a cage id to dump stuff for this cage only.
See 'autodump' to actually dump stuff.
Usage:
stripcaged items
dump items directly in cages (eg seeds after training)
stripcaged [armor|weapons] here
dump armor or weapons of caged creatures in selected cage
stripcaged all 28 29
dump every item in cage id 28 and 29, along with every item worn by creatures in there too
stripcaged list
show content of the cages
EOS
end

@ -8,7 +8,12 @@ when 'add'
if u = df.unit_find if u = df.unit_find
$superdwarf_ids |= [u.id] $superdwarf_ids |= [u.id]
$superdwarf_onupdate ||= df.onupdate_register(1) { if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode)
onupdate_delay = nil
else
onupdate_delay = 1
end
$superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) {
if $superdwarf_ids.empty? if $superdwarf_ids.empty?
df.onupdate_unregister($superdwarf_onupdate) df.onupdate_unregister($superdwarf_onupdate)
$superdwarf_onupdate = nil $superdwarf_onupdate = nil

@ -0,0 +1,17 @@
joblist = df.world.job_list.next
count = 0
while joblist
job = joblist.item
joblist = joblist.next
if job.job_type == :ConstructBuilding
if (job.flags.suspend && job.items && job.items[0])
item = job.items[0].item
job.flags.suspend = false
count += 1
end
end
end
puts "Unsuspended #{count} job(s)."