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)
if(APPLE)
add_definitions(-D_DARWIN)
add_definitions(-D_DARWIN)
elseif(UNIX)
add_definitions(-D_LINUX)
elseif(WIN32)
@ -172,6 +172,7 @@ IF(BUILD_LIBRARY)
add_subdirectory (library)
## install the default documentation files
install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif()
#build the plugins
@ -181,11 +182,11 @@ endif()
# Packaging with CPack!
IF(UNIX)
if(APPLE)
SET(CPACK_GENERATOR "ZIP")
else()
SET(CPACK_GENERATOR "TGZ")
endif()
if(APPLE)
SET(CPACK_GENERATOR "ZIP")
else()
SET(CPACK_GENERATOR "TGZ")
endif()
ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP")
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>
<p>Prints info about the job item.</p>
</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>
<p>Returns the building holding the job.</p>
</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>
<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><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>
<p>Returns the container (cage) item or <em>nil</em>.</p>
</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>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</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>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li>
@ -1403,6 +1418,12 @@ burrows, or the presence of invaders.</p>
<div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id25">Buildings module</a></h3>
<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>
<p>Replaces the owner of the building. If unit is <em>nil</em>, removes ownership.
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.
* ``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)``
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.
- added a small stand-alone utility for applying and removing binary patches.
- 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:
- 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.
- lua: lua interpreter.
- lua: lua interpreter front-end converted to a script from a native command.
- dfusion: misc scripts with a text based menu.
- 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:
- gui/guide-path: displays the cached path for minecart Guide orders.
- 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/gm-editor: an universal editor for lots of dfhack things.
- 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:
- properly considers minecarts assigned to routes busy.
- 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.
New Search plugin by falconne:
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:
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.
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!
=============================
@ -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.
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.
.. image:: images/tweak-plate.png
: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%
: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,
i.e. stop the rightmost list of the Positions page of the military
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.
.. 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
----------
@ -1946,6 +1966,41 @@ embark
======
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
=======================
@ -1958,6 +2013,9 @@ are mostly implemented by lua scripts.
In order to avoid user confusion, as a matter of policy all these tools
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
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
press 'l'.
.. image:: images/manipulator.png
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
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
Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds
denote skills not controlled by labors.
Master, and U-Z for Legendary thru Legendary+5).
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
move 10 tiles at a time.
@ -2012,6 +2076,8 @@ Search
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.
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
@ -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.
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
===========
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
to select the target area and apply changes.
@ -2044,7 +2148,9 @@ to select the target area and apply changes.
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
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.
.. image:: images/rename-bld.png
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.
* ``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.
.. 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 example config binds building/unit rename to Ctrl-Shift-N, and
unit profession change to Ctrl-Shift-T.
gui/room-list
=============
To use, bind to a key and activate in the 'q' mode, either immediately or after opening
the assign owner page.
To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
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
list, and allows unassigning them.
@ -2085,7 +2205,8 @@ list, and allows unassigning them.
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
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
==============
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
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
================
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
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
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.
.. 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::
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
============
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
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).
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
the job material first using ``job item-material`` or ``gui/workshop-job``,
as described in ``workflow`` documentation above. In this manner, this feature
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
===============
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
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
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
@ -2253,7 +2408,10 @@ Configuration UI
----------------
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 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.
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
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
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 #
# #

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)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY})
ELSEIF(APPLE)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
ELSE()
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX})
ENDIF()
@ -235,7 +235,7 @@ ENDIF()
IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF()
ELSE(WIN32)
#FIXME: do we really need psapi?
@ -273,14 +273,14 @@ ENDIF()
SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY})
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY})
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF()
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)
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()
IF(UNIX)
if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .)
if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .)
else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .)
endif()
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"
);
con.print("\nDFHack version " DFHACK_VERSION ".\n");
con.print("\nDFHack version " DFHACK_VERSION ".\n");
}
else if (parts.size() == 1)
{
@ -747,13 +747,13 @@ void fIOthread(void * iodata)
else if(ret)
{
// 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);
fprintf(stderr,"Saving history\n");
fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history");
}
fprintf(stderr,"Running command\n");
fprintf(stderr,"Running command\n");
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;
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 )
{
DFHack::Core & c = DFHack::Core::getInstance();
DFHack::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);
if ( ov != NULL )
{
if ( ov->paintOver )
{
_SDL_UpperBlit(src, srcrect, dst, dstrect);
}
DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect;
dstrect2->x = dstrect->x;
dstrect2->y = dstrect->y;
dstrect2->w = dstrect->w;
dstrect2->h = dstrect->h;
if ( ov->dstResize != NULL )
{
DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize;
dstrect2->x += r->x;
dstrect2->y += r->y;
dstrect2->w += r->w;
dstrect2->h += r->h;
}
int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2);
delete dstrect2;
return result;
}
}
if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 )
{
DFHack::Core & c = DFHack::Core::getInstance();
DFHack::Graphic* g = c.getGraphic();
DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h);
if ( ov != NULL )
{
if ( ov->paintOver )
{
_SDL_UpperBlit(src, srcrect, dst, dstrect);
}
DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect;
dstrect2->x = dstrect->x;
dstrect2->y = dstrect->y;
dstrect2->w = dstrect->w;
dstrect2->h = dstrect->h;
if ( ov->dstResize != NULL )
{
DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize;
dstrect2->x += r->x;
dstrect2->y += r->y;
dstrect2->w += r->w;
dstrect2->h += r->h;
}
int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2);
delete dstrect2;
return result;
}
}
return _SDL_UpperBlit(src, srcrect, dst, dstrect);
}

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

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

@ -53,10 +53,10 @@ Process::Process(VersionInfoFactory * known_versions)
char path[1024];
char *real_path;
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
identified = false;
my_descriptor = 0;
@ -166,29 +166,29 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
(vm_region_info_t)&info, &info_count, &object);
if (kr == KERN_SUCCESS) {
if (info.reserved==1) {
address += vmsize;
continue;
address += vmsize;
continue;
}
Dl_info dlinfo;
int dlcheck;
dlcheck = dladdr((const void*)address, &dlinfo);
if (dlcheck==0) {
dlinfo.dli_fname = "";
dlinfo.dli_fname = "";
}
t_memrange temp;
strncpy( temp.name, dlinfo.dli_fname, 1023 );
temp.name[1023] = 0;
temp.start = (void *) address;
temp.end = (void *) (address+vmsize);
temp.read = (info.protection & VM_PROT_READ);
temp.write = (info.protection & VM_PROT_WRITE);
temp.execute = (info.protection & VM_PROT_EXECUTE);
temp.shared = info.shared;
temp.valid = true;
ranges.push_back(temp);
fprintf(stderr,
strncpy( temp.name, dlinfo.dli_fname, 1023 );
temp.name[1023] = 0;
temp.start = (void *) address;
temp.end = (void *) (address+vmsize);
temp.read = (info.protection & VM_PROT_READ);
temp.write = (info.protection & VM_PROT_WRITE);
temp.execute = (info.protection & VM_PROT_EXECUTE);
temp.shared = info.shared;
temp.valid = true;
ranges.push_back(temp);
fprintf(stderr,
"%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n",
address, (address + vmsize), (vmsize >> 10),
(info.protection & VM_PROT_READ) ? 'r' : '-',
@ -277,15 +277,15 @@ uint32_t Process::getTickCount()
string Process::getPath()
{
char path[1024];
char path[1024];
char *real_path;
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
std::string path_string(real_path);
int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash);
uint32_t size = sizeof(path);
if (_NSGetExecutablePath(path, &size) == 0) {
real_path = realpath(path, NULL);
}
std::string path_string(real_path);
int last_slash = path_string.find_last_of("/");
std::string directory = path_string.substr(0,last_slash);
return directory;
}
@ -305,3 +305,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
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);
}

@ -236,3 +236,28 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
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;
}
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)
{
std::ofstream outfile (filename);
//fprintf(stderr,"Save: Initialized stream\n");
//fprintf(stderr,"Save: Initialized stream\n");
if(outfile.bad())
return false;
//fprintf(stderr,"Save: Iterating...\n");
//fprintf(stderr,"Save: Iterating...\n");
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;
//fprintf(stderr,"Save: Flushing\n");
outfile.flush();
//fprintf(stderr,"Save: Flushing\n");
outfile.flush();
}
//fprintf(stderr,"Save: Closing\n");
//fprintf(stderr,"Save: Closing\n");
outfile.close();
//fprintf(stderr,"Save: Done\n");
//fprintf(stderr,"Save: Done\n");
return true;
}
/// add a command to the history

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

@ -291,6 +291,27 @@ namespace DFHack
/// write a possibly read-only memory area
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:
VersionInfo * my_descriptor;
PlatformSpecific *d;

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

@ -74,6 +74,28 @@ namespace DFHack
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 bool hasEnding (std::string const &fullString, std::string const &ending);

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

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

@ -29,6 +29,8 @@ distribution.
#include "ColorText.h"
#include <string>
#include "Types.h"
#include "DataDefs.h"
#include "df/init.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 y1, y2;
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();

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

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

@ -27,7 +27,10 @@ distribution.
#include "Module.h"
#include "BitArray.h"
#include "ColorText.h"
#include "Types.h"
#include <string>
#include <set>
#include "DataDefs.h"
#include "df/graphic.h"
@ -50,6 +53,8 @@ namespace DFHack
{
class Core;
typedef std::set<df::interface_key> interface_key_set;
/**
* The Screen module
* \ingroup grp_modules
@ -94,11 +99,67 @@ namespace DFHack
: ch(ch), fg(fg), bg(bg), bold(bold),
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 getWindowSize();
inline rect2d getScreenRect() {
return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1));
}
/// Returns the state of [GRAPHICS:YES/NO]
DFHACK_EXPORT bool inGraphicsMode();
@ -133,6 +194,77 @@ namespace DFHack
/// Retrieve the string representation of the bound 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 {

@ -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 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);
struct NoblePosition {

@ -81,6 +81,55 @@ namespace DFHack
int &ival(int i) { 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(int id, const std::string &key, std::string *sv, int *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}
local FAKE_INPUT_KEYS = {
_MOUSE_L = true,
_MOUSE_R = true,
_STRING = true,
}
function simulateInput(screen,...)
local keys = {}
local function push_key(arg)
local kv = arg
if type(arg) == 'string' then
kv = df.interface_key[arg]
if kv == nil then
if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg)
end
end

@ -23,6 +23,7 @@ MaterialDialog.ATTRS{
frame_title = 'Select Material',
-- new attrs
none_caption = 'none',
hide_none = false,
use_inorganic = true,
use_creature = true,
use_plant = true,
@ -68,7 +69,7 @@ function MaterialDialog:init(info)
end
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
function MaterialDialog:onDestroy()
@ -78,9 +79,10 @@ function MaterialDialog:onDestroy()
end
function MaterialDialog:initBuiltinMode()
local choices = {
{ text = self.none_caption, mat_type = -1, mat_index = -1 },
}
local choices = {}
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
table.insert(choices, {
@ -281,9 +283,15 @@ function ItemTypeDialog(args)
args.with_filter = true
args.icon_width = 2
local choices = { {
icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1
} }
local choices = {}
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
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 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
local obj = choices[i]
local current = (i == self.selected)
@ -499,30 +511,28 @@ function List:onRenderBody(dc)
end
local y = (i - top)*self.row_height
local icon = getval(obj.icon)
if iw and obj.icon then
local icon = getval(obj.icon)
if icon then
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
if iw and icon then
dc:seek(0, y)
paint_icon(icon, obj)
end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
local ip = dc.width
if obj.key then
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(')')
end
if icon and not iw then
dc:seek(ip-1,y)
paint_icon(icon, obj)
end
end
end

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

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

@ -650,15 +650,15 @@ void MapExtras::Block::TileInfo::init_coninfo()
con_info = new ConInfo();
con_info->constructed.clear();
COPY(con_info->tiles, base_tiles);
memset(con_info->mattype, -1, sizeof(con_info->mattype));
memset(con_info->matindex, -1, sizeof(con_info->matindex));
memset(con_info->mat_type, -1, sizeof(con_info->mat_type));
memset(con_info->mat_index, -1, sizeof(con_info->mat_index));
}
MapExtras::Block::BasematInfo::BasematInfo()
{
dirty.clear();
memset(mattype,0,sizeof(mattype));
memset(matindex,-1,sizeof(matindex));
memset(mat_type,0,sizeof(mat_type));
memset(mat_index,-1,sizeof(mat_index));
memset(layermat,-1,sizeof(layermat));
}
@ -719,8 +719,8 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles)
is_con = true;
tiles->con_info->constructed.setassignment(x,y,true);
tiles->con_info->tiles[x][y] = tt;
tiles->con_info->mattype[x][y] = con->mat_type;
tiles->con_info->matindex[x][y] = con->mat_index;
tiles->con_info->mat_type[x][y] = con->mat_type;
tiles->con_info->mat_index[x][y] = con->mat_index;
tt = con->original_tile;
}
@ -753,14 +753,14 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats)
auto tt = tiles->base_tiles[x][y];
auto mat = info.getBaseMaterial(tt, df::coord2d(x,y));
bmats->mattype[x][y] = mat.mat_type;
bmats->matindex[x][y] = mat.mat_index;
bmats->mat_type[x][y] = mat.mat_type;
bmats->mat_index[x][y] = mat.mat_index;
// Copy base info back to construction layer
if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y))
{
tiles->con_info->mattype[x][y] = mat.mat_type;
tiles->con_info->matindex[x][y] = mat.mat_index;
tiles->con_info->mat_type[x][y] = mat.mat_type;
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)
{
MaterialInfo mi(mat.material, mat.index);
MaterialInfo mi(mat.mat_type, mat.mat_index);
if (mi.creature)
return mi.creature->creature_id + " " + mi.material->id;
else if (mi.plant)
@ -853,7 +853,7 @@ std::string Materials::getDescription(const t_material & mat)
// This is completely worthless now
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)
{
case MaterialInfo::Builtin:

@ -110,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
{
if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy;
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false;
auto dim = getWindowSize();
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;
}
@ -121,11 +121,11 @@ Pen Screen::readTile(int x, int y)
{
if (!gps) return Pen(0,0,0,-1);
int dimx = gps->dimx, dimy = gps->dimy;
if (x < 0 || x >= dimx || y < 0 || y >= dimy)
auto dim = getWindowSize();
if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1);
int index = x*dimy + y;
int index = x*dim.y + y;
auto screen = gps->screen + index*4;
if (screen[3] & 0x80)
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)
{
if (!gps || y < 0 || y >= gps->dimy) return false;
auto dim = getWindowSize();
if (!gps || y < 0 || y >= dim.y) return false;
Pen tmp(pen);
bool ok = false;
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;
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)
{
auto dim = getWindowSize();
if (!gps || !pen.valid()) return false;
if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0;
if (x2 >= gps->dimx) x2 = gps->dimx-1;
if (y2 >= gps->dimy) y2 = gps->dimy-1;
if (x2 >= dim.x) x2 = dim.x-1;
if (y2 >= dim.y) y2 = dim.y-1;
if (x1 > x2 || y1 > y2) return false;
for (int x = x1; x <= x2; x++)
{
int index = x*gps->dimy;
int index = x*dim.y;
for (int y = y1; y <= y2; y++)
doSetTile(pen, index+y);
@ -198,32 +200,33 @@ bool Screen::drawBorder(const std::string &title)
{
if (!gps) return false;
int dimx = gps->dimx, dimy = gps->dimy;
auto dim = getWindowSize();
Pen border('\xDB', 8);
Pen text(0, 0, 7);
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 * dimy + dimy - 1);
doSetTile(border, x * dim.y + 0);
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, (dimx - 1) * dimy + y);
doSetTile(border, 0 * dim.y + 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()
{
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()
@ -234,6 +237,21 @@ bool Screen::invalidate()
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)
{
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++)
{
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->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index);
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++)
{
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].index = p->readDWord(cmats[i] + off.job_material_index_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;
}
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)
{
/*

@ -197,11 +197,15 @@ PersistentDataItem World::AddPersistentData(const std::string &key)
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
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.first_name = key;
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);
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(manipulator manipulator.cpp)
DFHACK_PLUGIN(search search.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp)
# this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.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_chest
// || itm->flags.bits.in_inventory
|| itm->flags.bits.artifact1
|| itm->flags.bits.artifact
)
continue;
@ -271,7 +271,7 @@ command_result df_autodump_destroy_item(color_ostream &out, vector <string> & pa
if (item->flags.bits.construction ||
item->flags.bits.in_building ||
item->flags.bits.artifact1)
item->flags.bits.artifact)
{
out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n");
return CR_FAILURE;

@ -964,7 +964,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
else if (building_type::TradeDepot == type)
{
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 (trader_requested)
@ -1556,7 +1556,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1);
F(in_building); F(construction); F(artifact);
F(spider_web); F(owned); F(in_job);
#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)
{
df::item_type type = item->getType();
if(type == item_type::MEAT ||
if(type == item_type::MEAT ||
type == item_type::FISH ||
type == item_type::VERMIN ||
type == item_type::PET ||

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

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

@ -68,8 +68,8 @@ command_result rprobe (color_ostream &out, vector <string> & parameters)
// Embark screen active: estimate using world geology data
VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen());
if (!screen)
return CR_WRONG_USAGE;
if (!screen)
return CR_WRONG_USAGE;
if (!world || !world->world_data)
{

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

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

@ -323,7 +323,7 @@ public:
auto_renderer_decorator & operator=(renderer_decorator *p) {
reset();
this->p = p;
return *this;
return *this;
}
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
, 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 *);
@ -208,13 +208,13 @@ struct product_hook : item_product {
) {
if (auto product = products[this])
{
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
df::reaction* this_reaction=product->react;
CoreSuspendClaimer suspend;
color_ostream_proxy out(Core::getInstance().getConsole());
bool call_native=true;
onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native);
if(!call_native)
return;
if(!call_native)
return;
}
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(
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod
) {
info.react = react;
info.product = prod;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
}
static bool find_reactions(color_ostream &out)

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

@ -57,161 +57,161 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters);
const string forceequip_help =
"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"
"be used to put armor onto a war animal or to add unusual items (such\n"
"as crowns) to any unit.\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"
"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"
"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"
"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"
"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"
"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"
"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"
"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"
"as crowns) to any unit.\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"
"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"
"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"
"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"
"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"
"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"
"for the next passerby."
"\n"
"By default, the forceequip plugin will attempt to avoid\n"
"conflicts and outright cheating. For instance, it will skip\n"
"any item which is flagged for use in a job, and will not\n"
"equip more than one piece of clothing/armor onto any given\n"
"body part. These restrictions can be overridden via command\n"
"switches (see examples below) but doing so puts you at greater\n"
"risk of unexpected consequences. For instance, a dwarf who\n"
"is wearing three breastplates will not be able to move very\n"
"quickly.\n"
"\n"
"Items equipped by this plugin DO NOT become owned by the\n"
"recipient. Adult dwarves are free to adjust their own\n"
"wardrobe, and may promptly decide to doff your gear in\n"
"favour of their owned items. Animals, as described above,\n"
"will tend to discard ALL clothing immediately unless it is\n"
"manually forbidden. Armor items seem to be an exception;\n"
"an animal will tend to retain an equipped suit of mail\n"
"even if you neglect to Forbid it.\n"
"\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"
"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"
"By default, the forceequip plugin will attempt to avoid\n"
"conflicts and outright cheating. For instance, it will skip\n"
"any item which is flagged for use in a job, and will not\n"
"equip more than one piece of clothing/armor onto any given\n"
"body part. These restrictions can be overridden via command\n"
"switches (see examples below) but doing so puts you at greater\n"
"risk of unexpected consequences. For instance, a dwarf who\n"
"is wearing three breastplates will not be able to move very\n"
"quickly.\n"
"\n"
"Items equipped by this plugin DO NOT become owned by the\n"
"recipient. Adult dwarves are free to adjust their own\n"
"wardrobe, and may promptly decide to doff your gear in\n"
"favour of their owned items. Animals, as described above,\n"
"will tend to discard ALL clothing immediately unless it is\n"
"manually forbidden. Armor items seem to be an exception;\n"
"an animal will tend to retain an equipped suit of mail\n"
"even if you neglect to Forbid it.\n"
"\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"
" - \"Never equip gear belonging to another dwarf\" and\n"
" - \"Nobody is allowed to equip a Hive\".)\n"
" multi, m - bypasses the 1-item-per-bodypart limit, allowing\n"
" - the unit to receive an unlimited amount of gear.\n"
" - Can be used legitimately (e.g. mitten + gauntlet)\n"
" - or for cheating (e.g. twelve breastplates).\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"
" selected, s - rather than processing all items piled at a unit's\n"
" - feet, process only the one item currently selected.\n"
" bodypart, bp - must be followed by a bodypart code (e.g. LH).\n"
" - Instructs the plugin to equip all available items\n"
" - onto this body part only. Typically used in\n"
" - conjunction with the f switch (to over-armor\n"
" - a particular bodypart) or the i switch (to equip\n"
" - an unusual item onto a specific slot).\n"
" - \"Never equip gear belonging to another dwarf\" and\n"
" - \"Nobody is allowed to equip a Hive\".)\n"
" multi, m - bypasses the 1-item-per-bodypart limit, allowing\n"
" - the unit to receive an unlimited amount of gear.\n"
" - Can be used legitimately (e.g. mitten + gauntlet)\n"
" - or for cheating (e.g. twelve breastplates).\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"
" selected, s - rather than processing all items piled at a unit's\n"
" - feet, process only the one item currently selected.\n"
" bodypart, bp - must be followed by a bodypart code (e.g. LH).\n"
" - Instructs the plugin to equip all available items\n"
" - onto this body part only. Typically used in\n"
" - conjunction with the f switch (to over-armor\n"
" - a particular bodypart) or the i switch (to equip\n"
" - an unusual item onto a specific slot).\n"
" verbose, v - provides detailed narration and error messages.\n"
" - Can be helpful in resolving failures; not needed\n"
" - for casual use.\n"
"\n"
" - Can be helpful in resolving failures; not needed\n"
" - for casual use.\n"
"\n"
"Examples:\n"
" forceequip\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"
" other types. Equips a maximum of 1 item onto each bodypart,\n"
" and equips only \"appropriate\" items in each slot (e.g. glove\n"
" --> hand). Bypasses any item which might cause a conflict,\n"
" such as a Boot belonging to a different dwarf.\n"
" forceequip bp LH\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"
" 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"
" 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"
" the rest of the pile alone.\n"
" forceequip m bp LH\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"
" a dozen left-handed mittens. Use with caution, and remember\n"
" that dwarves will tend to drop supernumary items ASAP.\n"
" forceequip m\n"
" as above, but will equip ALL appropriate items onto any\n"
" appropriate bodypart. Tends to put several boots onto the right\n"
" foot while leaving the left foot bare.\n"
" forceequip m2\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"
" placement (e.g. left foot gets two socks, right foot gets two\n"
" shoes). For best results, use \"selected bp LH\" and\n"
" \"selected bp RH\" instead.\n"
" forceequip i\n"
" performs the standard \"equip appropriate items onto appropriate\n"
" bodyparts\" logic, but also includes items that would normally\n"
" be considered ineligible (such as a sock which is owned by\n"
" a different dwarf).\n"
" forceequip bp NECK\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"
" appropriate for the Neck slot, this command does nothing.\n"
" forceequip i bp NECK\n"
" attempts to equip items from the local pile onto the Neck\n"
" of the local unit. Ignores appropriateness restrictions.\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"
" chance that it will end up around his neck. For precise\n"
" control, remember that you can selectively forbid some of\n"
" the items that are piled on the ground.\n"
" forceequip i m bp NECK\n"
" as above, but equips an unlimited number of items onto the\n"
" targeted bodypart. Effectively, all unforbidden items\n"
" (including helms, millstones, boulders, etc) will be\n"
" moved from the local pile and placed in the dwarf's\n"
" inventory (specifically, on his neck). When used with\n"
" a large pile of goods, this will leave the dwarf heavily\n"
" encumbered and very slow to move.\n"
" forceequip s\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"
" and must be unforbidden. Attempts to equip this single\n"
" item onto an appropriate slot in the unit's inventory.\n"
" Can serve as a quicker alternative to the selective-\n"
" unforbidding approach described above.\n"
" forceequip s m i bp HD\n"
" equips the selected item onto the unit's head. Ignores\n"
" all possible restrictions and conflicts. If you know\n"
" exactly what you want to equip, and exactly where you\n"
" want it to go, then this is the most straightforward\n"
" and reliable option.\n"
" forceequip v bp QQQ\n"
" guaranteed to fail (and accomplish nothing) because\n"
" there are no bodyparts called QQQ. However, since the\n"
" verbose switch is used, the resulting error messages\n"
" will list every bodypart that the unit DOES possess.\n"
" May be useful if you're unfamiliar with the BP codes\n"
" used by Dwarf Fortress, or if you're experimenting\n"
" with an exotic creature.\n"
"\n"
;
" forceequip\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"
" other types. Equips a maximum of 1 item onto each bodypart,\n"
" and equips only \"appropriate\" items in each slot (e.g. glove\n"
" --> hand). Bypasses any item which might cause a conflict,\n"
" such as a Boot belonging to a different dwarf.\n"
" forceequip bp LH\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"
" 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"
" 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"
" the rest of the pile alone.\n"
" forceequip m bp LH\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"
" a dozen left-handed mittens. Use with caution, and remember\n"
" that dwarves will tend to drop supernumary items ASAP.\n"
" forceequip m\n"
" as above, but will equip ALL appropriate items onto any\n"
" appropriate bodypart. Tends to put several boots onto the right\n"
" foot while leaving the left foot bare.\n"
" forceequip m2\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"
" placement (e.g. left foot gets two socks, right foot gets two\n"
" shoes). For best results, use \"selected bp LH\" and\n"
" \"selected bp RH\" instead.\n"
" forceequip i\n"
" performs the standard \"equip appropriate items onto appropriate\n"
" bodyparts\" logic, but also includes items that would normally\n"
" be considered ineligible (such as a sock which is owned by\n"
" a different dwarf).\n"
" forceequip bp NECK\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"
" appropriate for the Neck slot, this command does nothing.\n"
" forceequip i bp NECK\n"
" attempts to equip items from the local pile onto the Neck\n"
" of the local unit. Ignores appropriateness restrictions.\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"
" chance that it will end up around his neck. For precise\n"
" control, remember that you can selectively forbid some of\n"
" the items that are piled on the ground.\n"
" forceequip i m bp NECK\n"
" as above, but equips an unlimited number of items onto the\n"
" targeted bodypart. Effectively, all unforbidden items\n"
" (including helms, millstones, boulders, etc) will be\n"
" moved from the local pile and placed in the dwarf's\n"
" inventory (specifically, on his neck). When used with\n"
" a large pile of goods, this will leave the dwarf heavily\n"
" encumbered and very slow to move.\n"
" forceequip s\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"
" and must be unforbidden. Attempts to equip this single\n"
" item onto an appropriate slot in the unit's inventory.\n"
" Can serve as a quicker alternative to the selective-\n"
" unforbidding approach described above.\n"
" forceequip s m i bp HD\n"
" equips the selected item onto the unit's head. Ignores\n"
" all possible restrictions and conflicts. If you know\n"
" exactly what you want to equip, and exactly where you\n"
" want it to go, then this is the most straightforward\n"
" and reliable option.\n"
" forceequip v bp QQQ\n"
" guaranteed to fail (and accomplish nothing) because\n"
" there are no bodyparts called QQQ. However, since the\n"
" verbose switch is used, the resulting error messages\n"
" will list every bodypart that the unit DOES possess.\n"
" May be useful if you're unfamiliar with the BP codes\n"
" used by Dwarf Fortress, or if you're experimenting\n"
" with an exotic creature.\n"
"\n"
;
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
@ -412,27 +412,27 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u
command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{
// 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)
// 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)
bool here = true;
// 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
// overridden with cmdline switches.
// 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
// overridden with cmdline switches.
int multiEquipLimit = 1;
// 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
bool ignore = false;
// 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
bool selected = false;
// 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.
bool verbose = false;
// 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.
// 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
bool ignore = false;
// 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
bool selected = false;
// 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.
bool verbose = false;
// 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.
std::string targetBodyPartCode;
// Parse the input
// Parse the input
for (size_t i = 0; i < parameters.size(); i++)
{
string & p = parameters[i];
@ -446,34 +446,34 @@ command_result df_forceequip(color_ostream &out, vector <string> & parameters)
{
here = true;
}
else if (p == "ignore" || p == "i")
{
ignore = true;
}
else if (p == "ignore" || p == "i")
{
ignore = true;
}
else if (p == "multi" || p == "m")
{
multiEquipLimit = INT_MAX;
}
else if (p == "m2")
{
multiEquipLimit = 2;
}
else if (p == "m3")
{
multiEquipLimit = 3;
}
else if (p == "m4")
{
multiEquipLimit = 4;
}
else if (p == "selected" || p == "s")
{
selected = true;
}
else if (p == "verbose" || p == "v")
{
verbose = true;
}
multiEquipLimit = INT_MAX;
}
else if (p == "m2")
{
multiEquipLimit = 2;
}
else if (p == "m3")
{
multiEquipLimit = 3;
}
else if (p == "m4")
{
multiEquipLimit = 4;
}
else if (p == "selected" || p == "s")
{
selected = true;
}
else if (p == "verbose" || p == "v")
{
verbose = true;
}
else if (p == "bodypart" || p == "bp" )
{
// 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())
{
out.printerr("Map is not available!\n");
return CR_FAILURE;
}
// Lookup the cursor position
int cx, cy, cz;
DFCoord pos_cursor;
// needs a cursor
if (!Gui::getCursorCoords(cx,cy,cz))
{
out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE;
}
pos_cursor = DFCoord(cx,cy,cz);
// Iterate over all units, process the first one whose pos == pos_cursor
df::unit * targetUnit;
size_t numUnits = world->units.all.size();
for(size_t i=0; i< numUnits; i++)
{
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);
if (pos_unit == pos_cursor)
break;
if (i + 1 == numUnits)
{
out.printerr("No unit found at cursor!\n");
return CR_FAILURE;
}
}
// Assert: unit found.
// If a specific bodypart was included in the command arguments, then search for it now
df::body_part_raw * targetBodyPart = NULL;
if (targetBodyPartCode.size() > 0) {
for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++)
{
// Tentatively assume that the part is a match
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
{
// 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()); }
break;
}
else
{
// 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()); }
targetBodyPart = NULL;
}
}
if (!targetBodyPart)
{
// 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());
return CR_FAILURE;
}
}
// Search for item(s)
MapCache mc;
// iterate over all items, process those where pos == pos_cursor
int itemsEquipped = 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.
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++)
{
df::item * currentItem;
// Search behaviour depends on whether the operation is driven by cursor location or UI selection
if (selected)
{
// The "search" is trivial - the selection must always cover either one or zero items
currentItem = Gui::getSelectedItem(out);
if (!currentItem) { return CR_FAILURE; }
}
else
{
// Lookup the current item in the world-space
currentItem = world->items.all[i];
// Test the item's position
DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z);
if (pos_item != pos_cursor)
{
// 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
continue;
}
// Bypass any forbidden items
else if (currentItem->flags.bits.forbid == 1)
{
// The item is forbidden; skip it
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
if (currentItem->flags.bits.in_inventory == 1)
{
// The item is in a unit's inventory; skip it
if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); }
}
else
{
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
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
// 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());
itemsEquipped++; // Track the number of items successfully processed (for feedback purposes)
}
}
}
if (itemsFound == 0) {
out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n");
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) { 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.
// Therefore, we must update the map.
mc.WriteAll();
// 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".
// The slight delay in recalculation is probably not worth worrying about.
// Work complete; report success
return CR_OK;
// Lookup the cursor position
int cx, cy, cz;
DFCoord pos_cursor;
// needs a cursor
if (!Gui::getCursorCoords(cx,cy,cz))
{
out.printerr("Cursor position not found. Please enable the cursor.\n");
return CR_FAILURE;
}
pos_cursor = DFCoord(cx,cy,cz);
// Iterate over all units, process the first one whose pos == pos_cursor
df::unit * targetUnit;
size_t numUnits = world->units.all.size();
for(size_t i=0; i< numUnits; i++)
{
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);
if (pos_unit == pos_cursor)
break;
if (i + 1 == numUnits)
{
out.printerr("No unit found at cursor!\n");
return CR_FAILURE;
}
}
// Assert: unit found.
// If a specific bodypart was included in the command arguments, then search for it now
df::body_part_raw * targetBodyPart = NULL;
if (targetBodyPartCode.size() > 0) {
for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++)
{
// Tentatively assume that the part is a match
targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex);
if (targetBodyPart->token.compare(targetBodyPartCode) == 0)
{
// 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()); }
break;
}
else
{
// 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()); }
targetBodyPart = NULL;
}
}
if (!targetBodyPart)
{
// 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());
return CR_FAILURE;
}
}
// Search for item(s)
MapCache mc;
// iterate over all items, process those where pos == pos_cursor
int itemsEquipped = 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.
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++)
{
df::item * currentItem;
// Search behaviour depends on whether the operation is driven by cursor location or UI selection
if (selected)
{
// The "search" is trivial - the selection must always cover either one or zero items
currentItem = Gui::getSelectedItem(out);
if (!currentItem) { return CR_FAILURE; }
}
else
{
// Lookup the current item in the world-space
currentItem = world->items.all[i];
// Test the item's position
DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z);
if (pos_item != pos_cursor)
{
// 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
continue;
}
// Bypass any forbidden items
else if (currentItem->flags.bits.forbid == 1)
{
// The item is forbidden; skip it
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
if (currentItem->flags.bits.in_inventory == 1)
{
// The item is in a unit's inventory; skip it
if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); }
}
else
{
itemsFound ++; // Track the number of items found under the cursor (for feedback purposes)
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
// 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());
itemsEquipped++; // Track the number of items successfully processed (for feedback purposes)
}
}
}
if (itemsFound == 0) {
out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n");
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) { 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.
// Therefore, we must update the map.
mc.WriteAll();
// 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".
// The slight delay in recalculation is probably not worth worrying about.
// Work complete; report success
return CR_OK;
}

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

@ -456,21 +456,23 @@ void viewscreen_unitlaborsst::refreshNames()
void viewscreen_unitlaborsst::calcSize()
{
num_rows = gps->dimy - 10;
auto dim = Screen::getWindowSize();
num_rows = dim.y - 10;
if (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
int col_minwidth[DISP_COLUMN_MAX];
int col_maxwidth[DISP_COLUMN_MAX];
col_minwidth[DISP_COLUMN_HAPPINESS] = 4;
col_maxwidth[DISP_COLUMN_HAPPINESS] = 4;
col_minwidth[DISP_COLUMN_NAME] = 0;
col_maxwidth[DISP_COLUMN_NAME] = 0;
col_minwidth[DISP_COLUMN_PROFESSION] = 0;
col_maxwidth[DISP_COLUMN_PROFESSION] = 0;
col_minwidth[DISP_COLUMN_NAME] = 16;
col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below
col_minwidth[DISP_COLUMN_PROFESSION] = 10;
col_maxwidth[DISP_COLUMN_PROFESSION] = 10; // adjusted in the loop below
col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60%
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;
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++)
{
if (columns[i].group != col.group)
@ -940,10 +942,11 @@ void viewscreen_unitlaborsst::render()
dfhack_viewscreen::render();
auto dim = Screen::getWindowSize();
Screen::clear();
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_NAME], 2, "Name");
Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession");
@ -1057,7 +1060,7 @@ void viewscreen_unitlaborsst::render()
}
}
else
bg = 4;
bg = 3;
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);
}
int x = 2;
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT));
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, ");
int x = 2, y = dim.y - 3;
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT));
OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL));
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, ");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL));
OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW));
OutputString(15, x, gps->dimy - 3, ": ViewCre, ");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW));
OutputString(15, x, y, ": ViewCre, ");
OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE));
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE));
OutputString(15, x, y, ": Zoom-Cre");
x = 2;
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN));
OutputString(15, x, gps->dimy - 2, ": Done, ");
x = 2; y = dim.y - 2;
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN));
OutputString(15, x, y, ": Done, ");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN));
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP));
OutputString(15, x, gps->dimy - 2, ": Sort by Skill, ");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN));
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP));
OutputString(15, x, y, ": Sort by Skill, ");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN));
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP));
OutputString(15, x, gps->dimy - 2, ": Sort by (");
OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::CHANGETAB));
OutputString(15, x, gps->dimy - 2, ") ");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN));
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP));
OutputString(15, x, y, ": Sort by (");
OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CHANGETAB));
OutputString(15, x, y, ") ");
switch (altsort)
{
case ALTSORT_NAME:
OutputString(15, x, gps->dimy - 2, "Name");
OutputString(15, x, y, "Name");
break;
case ALTSORT_PROFESSION:
OutputString(15, x, gps->dimy - 2, "Profession");
OutputString(15, x, y, "Profession");
break;
case ALTSORT_HAPPINESS:
OutputString(15, x, gps->dimy - 2, "Happiness");
OutputString(15, x, y, "Happiness");
break;
case ALTSORT_ARRIVAL:
OutputString(15, x, gps->dimy - 2, "Arrival");
OutputString(15, x, y, "Arrival");
break;
default:
OutputString(15, x, gps->dimy - 2, "Unknown");
OutputString(15, x, y, "Unknown");
break;
}
}
@ -1193,9 +1196,10 @@ struct unitlist_hook : df::viewscreen_unitlistst
if (units[page].size())
{
int x = 2;
OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF));
OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)");
auto dim = Screen::getWindowSize();
int x = 2, y = dim.y - 2;
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.
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:
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
ticks (advances only when the game is unpaused).
To stop being called, use:

@ -56,7 +56,7 @@ module DFHack
def item_isfree(i)
!i.flags.trader 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.in_building and
!i.flags.owned and

@ -159,8 +159,8 @@ module DFHack
v.inorganic_mat if v
end
# return the world_data.geo_biome for current tile
def geo_biome
# return the RegionMapEntry (from designation.biome)
def region_map_entry
b = designation.biome
wd = df.world.world_data
@ -174,7 +174,12 @@ module DFHack
ry -= 1 if b < 3 and ry > 0
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
# return the world_data.geo_biome.layer for current tile
@ -182,14 +187,53 @@ module DFHack
geo_biome.layers[designation.geolayer_index]
end
# current tile mat_index (vein if applicable, or base material)
def mat_index
mat_index_vein or stone_layer.mat_index
# MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...)
def mat_info
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
# MaterialInfo: inorganic token for current tile
def mat_info
MaterialInfo.new(0, mat_index)
def mat_type
mat_info.mat_type
end
def mat_index
mat_info.mat_index
end
def inspect

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

@ -438,6 +438,12 @@ static VALUE rb_cDFHack;
// 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()
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, "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, "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_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1);

@ -23,9 +23,12 @@ module Kernel
end
module DFHack
VERSION = version
class OnupdateCallback
attr_accessor :callback, :timelimit, :minyear, :minyeartick
def initialize(cb, tl, initdelay=0)
attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description
def initialize(descr, cb, tl, initdelay=0)
@description = descr
@callback = cb
@ticklimit = tl
@minyear = (tl ? df.cur_year : 0)
@ -34,22 +37,21 @@ module DFHack
# run callback if timedout
def check_run(year, yeartick, yearlen)
if !@ticklimit
@callback.call
else
if year > @minyear or (year == @minyear and yeartick >= @minyeartick)
@minyear = year
@minyeartick = yeartick + @ticklimit
if @minyeartick > yearlen
@minyear += 1
@minyeartick -= yearlen
end
@callback.call
if @ticklimit
return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick)
@minyear = year
@minyeartick = yeartick + @ticklimit
if @minyeartick > yearlen
@minyear += 1
@minyeartick -= yearlen
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
puts_err "onupdate cb #$!", $!.backtrace
puts_err "onupdate #@description unregistered: #$!", $!.backtrace
end
def <=>(o)
@ -61,10 +63,11 @@ module DFHack
attr_accessor :onupdate_list, :onstatechange_list
# register a callback to be called every gframe or more
# ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 }
def onupdate_register(ticklimit=nil, initialtickdelay=0, &b)
# ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 }
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 << OnupdateCallback.new(b, ticklimit, initialtickdelay)
@onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay)
DFHack.onupdate_active = true
if onext = @onupdate_list.sort.first
DFHack.onupdate_minyear = onext.minyear
@ -73,8 +76,9 @@ module DFHack
@onupdate_list.last
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)
b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String)
@onupdate_list.delete b
if @onupdate_list.empty?
DFHack.onupdate_active = false

@ -21,7 +21,7 @@ module DFHack
when :SelectTrainer
v.trainer_unit[v.trainer_cursor]
end
else
when :viewscreen_dwarfmodest
case ui.main.mode
when :ViewUnits
# nobody selected => idx == 0
@ -33,6 +33,15 @@ module DFHack
else
ui.follow_unit_tg if ui.follow_unit != -1
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
elsif what.kind_of?(Integer)
# search by id

@ -329,8 +329,9 @@ protected:
// Display hotkey message
void print_search_option(int x, int y = -1) const
{
auto dim = Screen::getWindowSize();
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) ? 10 : 15, x, y, ": Search");
@ -413,8 +414,9 @@ public:
print_search_option(2);
else
{
int x = 2;
OutputString(15, x, gps->dimy - 2, "Tab to enable Search");
auto dim = Screen::getWindowSize();
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)
{
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())
{

@ -51,6 +51,12 @@
#include "df/squad_position.h"
#include "df/job.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>
@ -128,6 +134,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak military-color-assigned [disable]\n"
" Color squad candidates already assigned to other squads in brown/green\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;
}
@ -204,15 +212,31 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
{
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))
{
bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default);
bool was_default = check_default();
df::coord view = Gui::getViewportPos();
df::coord cursor = Gui::getCursorPos();
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();
if (is_default && !was_default)
@ -233,7 +257,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
tmp.insert(interface_key::CURSOR_UP_Z);
INTERPOSE_NEXT(feed)(&tmp);
}
else if (cur_cursor.isValid())
else if (!is_default && cur_cursor.isValid())
{
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, 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)
{
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);
}
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
return CR_WRONG_USAGE;

@ -293,6 +293,7 @@ typedef std::map<std::pair<int,int>, bool> TMaterialCache;
struct ItemConstraint {
PersistentDataItem config;
PersistentDataItem history;
// Fixed key parsed into fields
bool is_craft;
@ -308,7 +309,7 @@ struct ItemConstraint {
int weight;
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 is_active, cant_resume_reported;
@ -318,7 +319,7 @@ struct ItemConstraint {
public:
ItemConstraint()
: 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)
{}
@ -326,9 +327,8 @@ public:
void setGoalCount(int v) { config.ival(0) = v; }
int goalGap() {
int cval = (config.ival(1) <= 0) ? 5 : config.ival(1);
int cmax = std::max(goalCount()-5, goalCount()/2);
return std::max(1, std::min(cmax, cval));
int cval = (config.ival(1) <= 0) ? std::min(5,goalCount()/2) : config.ival(1);
return std::max(1, std::min(goalCount()-1, cval));
}
void setGoalGap(int v) { config.ival(1) = v; }
@ -353,6 +353,44 @@ public:
request_resume = (size <= goalCount()-goalGap());
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 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)
{
@ -650,6 +688,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out)
update_job_data(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 *
******************************/
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;
split_string(&tokens, str, "/");
@ -683,7 +728,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
if (item.subtype >= 0)
weight += 10000;
df::dfhack_material_category mat_mask;
df::dfhack_material_category mat_mask(0);
std::string maskstr = vector_get(tokens,1);
if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) {
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;
}
if (!create)
return NULL;
ItemConstraint *nct = new ItemConstraint;
nct->is_craft = is_craft;
nct->item = item;
@ -774,6 +822,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
nct->init(str);
}
nct->history = World::GetPersistentData(history_key(nct->config), NULL);
constraints.push_back(nct);
return nct;
}
@ -785,6 +835,7 @@ static void delete_constraint(ItemConstraint *cv)
vector_erase_at(constraints, idx);
World::DeletePersistentData(cv->config);
World::DeletePersistentData(cv->history);
delete cv;
}
@ -1062,7 +1113,8 @@ static void map_job_items(color_ostream &out)
{
constraints[i]->item_amount = 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;
@ -1074,7 +1126,7 @@ static void map_job_items(color_ostream &out)
#define F(x) bad_flags.bits.x = true;
F(dump); F(forbid); F(garbage_collect);
F(hostile); F(on_fire); F(rotten); F(trader);
F(in_building); F(construction); F(artifact1);
F(in_building); F(construction); F(artifact);
#undef F
bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS);
@ -1095,9 +1147,9 @@ static void map_job_items(color_ostream &out)
bool is_invalid = false;
// don't count worn items
if (item->getWear() >= 1)
is_invalid = true;
// don't count worn items
if (item->getWear() >= 1)
is_invalid = true;
// Special handling
switch (itype) {
@ -1175,7 +1227,8 @@ static void map_job_items(color_ostream &out)
isAssignedSquad(item))
{
is_invalid = true;
cv->item_inuse++;
cv->item_inuse_count++;
cv->item_inuse_amount += item->getStackSize();
}
else
{
@ -1346,7 +1399,9 @@ static void push_constraint(lua_State *L, ItemConstraint *cv)
Lua::SetField(L, cv->is_craft, ctable, "is_craft");
lua_getglobal(L, "copyall");
Lua::PushDFObject(L, &cv->mat_mask);
lua_call(L, 1, 1);
lua_setfield(L, -2, "mat_mask");
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_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
@ -1417,6 +1473,22 @@ static int listConstraints(lua_State *L)
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)
{
auto token = luaL_checkstring(L, 1);
@ -1425,6 +1497,7 @@ static int setConstraint(lua_State *L)
int gap = luaL_optint(L, 4, -1);
color_ostream &out = *Lua::GetOutput(L);
update_data_structures(out);
ItemConstraint *icv = get_constraint(out, token);
if (!icv)
@ -1442,6 +1515,40 @@ static int setConstraint(lua_State *L)
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_LUA_FUNCTION(isEnabled),
DFHACK_LUA_FUNCTION(setEnabled),
@ -1451,7 +1558,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS {
DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(listConstraints),
DFHACK_LUA_COMMAND(findConstraint),
DFHACK_LUA_COMMAND(setConstraint),
DFHACK_LUA_COMMAND(getCountHistory),
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;
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 << "; "
<< cv->item_count << " stacks available, "
<< cv->item_inuse << " in use." << endl;
<< cv->item_inuse_count << " in use." << endl;
if (no_job) return;

@ -630,7 +630,7 @@ bool isTamable(df::unit* unit)
{
df::caste_raw *caste = raw->caste[j];
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 false;
@ -662,7 +662,7 @@ bool hasValidMapPos(df::unit* unit)
bool isNaked(df::unit* unit)
{
return (unit->inventory.empty());
return (unit->inventory.empty());
}
@ -1763,17 +1763,17 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
bool find_female = false;
bool find_not_female = false;
bool find_egglayer = false;
bool find_not_egglayer = false;
bool find_not_egglayer = false;
bool find_grazer = false;
bool find_not_grazer = false;
bool find_milkable = false;
bool find_not_milkable = false;
bool find_named = false;
bool find_not_named = false;
bool find_naked = false;
bool find_not_naked = false;
bool find_tamable = false;
bool find_not_tamable = false;
bool find_naked = false;
bool find_not_naked = false;
bool find_tamable = false;
bool find_not_tamable = false;
bool find_agemin = false;
bool find_agemax = false;
@ -2432,7 +2432,7 @@ command_result df_zone (color_ostream &out, vector <string> & parameters)
|| (find_grazer && !isGrazer(unit))
|| (find_not_grazer && isGrazer(unit))
|| (find_egglayer && !isEggLayer(unit))
|| (find_not_egglayer && isEggLayer(unit))
|| (find_not_egglayer && isEggLayer(unit))
|| (find_milkable && !isMilkable(unit))
|| (find_not_milkable && isMilkable(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_named && !unit->name.has_name)
|| (find_not_named && unit->name.has_name)
|| (find_naked && !isNaked(unit))
|| (find_not_naked && isNaked(unit))
|| (find_tamable && !isTamable(unit))
|| (find_not_tamable && isTamable(unit))
|| (find_naked && !isNaked(unit))
|| (find_not_naked && isNaked(unit))
|| (find_tamable && !isTamable(unit))
|| (find_not_tamable && isTamable(unit))
|| (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit)))
|| (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough?
|| (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit)))
@ -3425,13 +3425,13 @@ command_result start_autobutcher(color_ostream &out)
config_autobutcher.ival(0) = enable_autobutcher;
out << "Starting autobutcher." << endl;
init_autobutcher(out);
init_autobutcher(out);
return CR_OK;
}
command_result init_autobutcher(color_ostream &out)
{
cleanup_autobutcher(out);
cleanup_autobutcher(out);
config_autobutcher = World::GetPersistentData("autobutcher/config");
if(config_autobutcher.isValid())
@ -3466,9 +3466,9 @@ command_result init_autobutcher(color_ostream &out)
std::vector<PersistentDataItem> items;
World::GetPersistentData(&items, "autobutcher/watchlist/", true);
for (auto p = items.begin(); p != items.end(); p++)
{
string key = p->key();
for (auto p = items.begin(); p != items.end(); p++)
{
string key = p->key();
out << "Reading from save: " << key << endl;
//out << " raceid: " << p->ival(0) << endl;
//out << " watched: " << p->ival(1) << endl;
@ -3514,13 +3514,13 @@ command_result start_autonestbox(color_ostream &out)
config_autonestbox.ival(0) = enable_autonestbox;
out << "Starting autonestbox." << endl;
init_autonestbox(out);
init_autonestbox(out);
return CR_OK;
}
command_result init_autonestbox(color_ostream &out)
{
cleanup_autonestbox(out);
cleanup_autonestbox(out);
config_autonestbox = World::GetPersistentData("autonestbox/config");
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
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.focus_path = 'workflow-job'
JobConstraints.focus_path = 'workflow/job'
JobConstraints.ATTRS {
job = DEFAULT_NIL,
@ -53,8 +400,6 @@ JobConstraints.ATTRS {
frame_background = COLOR_BLACK,
}
local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false }
function JobConstraints:init(args)
self.building = dfhack.job.getHolder(self.job)
@ -71,40 +416,11 @@ function JobConstraints:init(args)
row_height = 4,
scroll_keys = widgets.SECONDSCROLL,
},
widgets.Label{
RangeEditor{
frame = { l = 0, b = 3 },
enabled = self:callback('isAnySelected'),
text = {
{ key = 'BUILDING_TRIGGER_ENABLE_CREATURE',
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 },
}
get_cb = self:callback('getCurConstraint'),
save_cb = self:callback('saveConstraint'),
},
widgets.Label{
frame = { l = 0, b = 0 },
@ -132,43 +448,6 @@ function JobConstraints:onGetSelectedJob()
return self.job
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)
clist = clist or workflow.listConstraints(self.job)
@ -247,45 +526,6 @@ function JobConstraints:saveConstraint(cons)
self:initListChoices(nil, out.token)
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()
local outputs = workflow.listJobOutputs(self.job)
if #outputs == 0 then
@ -318,7 +558,10 @@ function JobConstraints:onNewConstraint()
COLOR_WHITE,
choices,
function(idx,item)
self:saveConstraint(item.obj)
NewConstraint{
constraint = item.obj,
on_submit = self:callback('saveConstraint')
}:show()
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]
when 'here'
$magma_onupdate ||= df.onupdate_register(12) {
$magma_onupdate ||= df.onupdate_register('magmasource', 12) {
# called every 12 game ticks (100x a dwarf day)
if $magma_sources.empty?
df.onupdate_unregister($magma_onupdate)

@ -99,6 +99,16 @@ function boost_population(entry, factor, boost_count)
return boost_count
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 pops = enum_populations()
@ -123,6 +133,26 @@ elseif args[1] == 'boost' or args[1] == 'boost-all' then
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.')
else
print([[
@ -137,5 +167,9 @@ Usage:
population, otherwise decreases it.
region-pops boost-all <pattern> <factor>
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

@ -21,7 +21,7 @@ slayit = lambda { |u|
else
# it's getting hot around here
# !!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
df.onupdate_unregister(ouh)
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
$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?
df.onupdate_unregister($superdwarf_onupdate)
$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)."