develop
Timothy Collett 2013-02-25 08:55:32 -05:00
commit 52834ec454
268 changed files with 23366 additions and 7402 deletions

3
.gitignore vendored

@ -57,3 +57,6 @@ dfhack/python/dist
build/CPack*Config.cmake build/CPack*Config.cmake
/cmakeall.bat /cmakeall.bat
# vim swap files
*.swp

@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif() endif()
# set up versioning. # set up versioning.
set(DF_VERSION_MAJOR "0") set(DF_VERSION "0.34.11")
set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "11")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.") SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
## where to install things (after the build is done, classic 'make install' or package structure) ## where to install things (after the build is done, classic 'make install' or package structure)
@ -121,7 +117,7 @@ ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS)
ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL)
if(APPLE) if(APPLE)
add_definitions(-D_DARWIN) add_definitions(-D_DARWIN)
elseif(UNIX) elseif(UNIX)
add_definitions(-D_LINUX) add_definitions(-D_LINUX)
elseif(WIN32) elseif(WIN32)
@ -145,7 +141,7 @@ include_directories(depends/clsocket/src)
add_subdirectory(depends) add_subdirectory(depends)
find_package(Docutils) #find_package(Docutils)
#set (RST_FILES #set (RST_FILES
#"Readme" #"Readme"
@ -172,6 +168,7 @@ IF(BUILD_LIBRARY)
add_subdirectory (library) add_subdirectory (library)
## install the default documentation files ## install the default documentation files
install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION})
install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION})
endif() endif()
#build the plugins #build the plugins
@ -181,11 +178,11 @@ endif()
# Packaging with CPack! # Packaging with CPack!
IF(UNIX) IF(UNIX)
if(APPLE) if(APPLE)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
else() else()
SET(CPACK_GENERATOR "TGZ") SET(CPACK_GENERATOR "TGZ")
endif() endif()
ELSEIF(WIN32) ELSEIF(WIN32)
SET(CPACK_GENERATOR "ZIP") SET(CPACK_GENERATOR "ZIP")
ENDIF() ENDIF()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

60
NEWS

@ -3,6 +3,8 @@ DFHack future
Internals: Internals:
- support for displaying active keybindings properly. - support for displaying active keybindings properly.
- support for reusable widgets in lua screen library. - support for reusable widgets in lua screen library.
- Maps::canStepBetween: returns whether you can walk between two tiles in one step.
- EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly.
Notable bugfixes: Notable bugfixes:
- autobutcher can be re-enabled again after being stopped. - autobutcher can be re-enabled again after being stopped.
- stopped Dwarf Manipulator from unmasking vampires. - stopped Dwarf Manipulator from unmasking vampires.
@ -10,10 +12,66 @@ DFHack future
- fastdwarf: new mode using debug flags, and some internal consistency fixes. - fastdwarf: new mode using debug flags, and some internal consistency fixes.
- added a small stand-alone utility for applying and removing binary patches. - added a small stand-alone utility for applying and removing binary patches.
- removebadthoughts: add --dry-run option - removebadthoughts: add --dry-run option
- superdwarf: work in adventure mode too
- tweak stable-cursor: carries cursor location from/to Build menu.
- deathcause: allow selection from the unitlist screen
- slayrace: allow targetting undeads
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 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.
- soundsense-season: writes the correct season to gamelog.txt on world load.
- create-items: spawn items
New GUI scripts: New GUI scripts:
- gui/guide-path: displays the cached path for minecart Guide orders. - gui/guide-path: displays the cached path for minecart Guide orders.
- gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them.
- gui/workflow: a front-end for the workflow plugin. - gui/workflow: a front-end for the workflow plugin (part inspired by falconne).
- 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.
- gui/advfort: a way to do jobs with your adventurer (e.g. build fort).
New binary patches (for use with binpatch):
- armorstand-capacity: doubles the capacity of armor stands.
- custom-reagent-size: lets custom reactions use small amounts of inputs.
- deconstruct-heapfall: stops some items still falling on head when deconstructing.
- deconstruct-teleport: stops items from 16x16 block teleporting when deconstructing.
- hospital-overstocking: stops hospital overstocking with supplies.
- training-ammo: lets dwarves with quiver full of combat-only ammo train.
- weaponrack-unassign: fixes bug that negates work done by gui/assign-rack.
Workflow plugin:
- properly considers minecarts assigned to routes busy.
- code for deducing job outputs rewritten in lua for flexibility.
- logic fix: collecting webs produces silk, and ungathered webs are not thread.
- items assigned to squads are considered busy, even if not in inventory.
- shearing and milking jobs are supported, but only with generic MILK or YARN outputs.
- workflow announces when the stock level gets very low once a season.
New Fix Armory plugin:
Together with a couple of binary patches and the gui/assign-rack script,
this plugin makes weapon racks, armor stands, chests and cabinets in
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, Stockpile 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.
New Eventful plugin:
A collection of lua events, that will allow new ways to interact with df world.
Auto syndrome plugin:
A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws.
Infinite sky plugin:
Create new z-levels automatically or on request.
True transformation plugin:
A better way of doing permanent transformations that allows later transformations.
Work now plugin:
Makes the game assign jobs every time you pause.
DFHack v0.34.11-r2 DFHack v0.34.11-r2

File diff suppressed because it is too large Load Diff

@ -58,9 +58,35 @@ The stonesense plugin might require some additional libraries on Linux.
If any of the plugins or dfhack itself refuses to load, check the stderr.log If any of the plugins or dfhack itself refuses to load, check the stderr.log
file created in your DF folder. file created in your DF folder.
Getting started
===============
If DFHack is installed correctly, it will automatically pop up a console
window once DF is started as usual on windows. Linux and Mac OS X require
running the dfhack script from the terminal, and will use that terminal for
the console.
**NOTE**: The dfhack-run executable is there for calling DFHack commands in
an already running DF+DFHack instance from external OS scripts and programs,
and is *not* the way how you use DFHack normally.
DFHack has a lot of features, which can be accessed by typing commands in the
console, or by mapping them to keyboard shortcuts. Most of the newer and more
user-friendly tools are designed to be at least partially used via the latter
way.
In order to set keybindings, you have to create a text configuration file
called ``dfhack.init``; the installation comes with an example version called
``dfhack.init-example``, which is fully functional, covers all of the recent
features and can be simply renamed to ``dfhack.init``. You are encouraged to look
through it to learn which features it makes available under which key combinations.
For more information, refer to the rest of this document.
============ ============
Using DFHack Using DFHack
============ ============
DFHack basically extends what DF can do with something similar to the drop-down DFHack basically extends what DF can do with something similar to the drop-down
console found in Quake engine games. On Windows, this is a separate command line console found in Quake engine games. On Windows, this is a separate command line
window. On linux, the terminal used to launch the dfhack script is taken over window. On linux, the terminal used to launch the dfhack script is taken over
@ -118,6 +144,16 @@ system console:
The patches are expected to be encoded in text format used by IDA. The patches are expected to be encoded in text format used by IDA.
Live patching
-------------
As an alternative, you can use the ``binpatch`` dfhack command to apply/remove
patches live in memory during a DF session.
In this case, updating symbols.xml is not necessary.
============================= =============================
Something doesn't work, help! Something doesn't work, help!
============================= =============================
@ -231,6 +267,8 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p
* 'fastdwarf 1 1' enables both * 'fastdwarf 1 1' enables both
* 'fastdwarf 0' disables both * 'fastdwarf 0' disables both
* 'fastdwarf 1' enables speedydwarf and disables teledwarf * 'fastdwarf 1' enables speedydwarf and disables teledwarf
* 'fastdwarf 2 ...' sets a native debug flag in the game memory
that implements an even more aggressive version of speedydwarf.
Game interface Game interface
============== ==============
@ -1049,6 +1087,9 @@ Subcommands that persist until disabled or DF quit:
:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
Does NOT fix the problem when soldiers go off-duty (i.e. civilian). Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
:readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu.
.. image:: images/tweak-plate.png
:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
In very item-heavy forts with big stockpiles this can improve FPS by 50-100% In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
:fast-heat: Further improves temperature update performance by ensuring that 1 degree :fast-heat: Further improves temperature update performance by ensuring that 1 degree
@ -1067,9 +1108,78 @@ Subcommands that persist until disabled or DF quit:
:military-stable-assign: Preserve list order and cursor position when assigning to squad, :military-stable-assign: Preserve list order and cursor position when assigning to squad,
i.e. stop the rightmost list of the Positions page of the military i.e. stop the rightmost list of the Positions page of the military
screen from constantly resetting to the top. screen from constantly resetting to the top.
:military-color-assigned: Color squad candidates already assigned to other squads in brown/green :military-color-assigned: Color squad candidates already assigned to other squads in yellow/green
to make them stand out more in the list. to make them stand out more in the list.
.. image:: images/tweak-mil-color.png
:military-training: Speeds up melee squad training by removing an almost certainly
unintended inverse dependency of training speed on unit count
(i.e. the more units you have, the slower it becomes), and making
the units spar more.
fix-armory
----------
Enables a fix for storage of squad equipment in barracks.
Specifically, it prevents your haulers from moving squad equipment
to stockpiles, and instead queues jobs to store it on weapon racks,
armor stands, and in containers.
.. note::
In order to actually be used, weapon racks have to be patched and
manually assigned to a squad. See documentation for ``gui/assign-rack``
below.
Also, the default capacity of armor stands is way too low, so you
may want to also apply the ``armorstand-capacity`` patch. Check out
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
for more information about the bugs.
Note that the buildings in the armory are used as follows:
* Weapon racks (when patched) are used to store any assigned weapons.
Each rack belongs to a specific squad, and can store up to 5 weapons.
* Armor stands belong to specific squad members and are used for
armor and shields. By default one stand can store one item of each
type (hence one boot or gauntlet); if patched, the limit is raised to 2,
which should be sufficient.
* Cabinets are used to store assigned clothing for a specific squad member.
They are **never** used to store owned clothing.
* Chests (boxes, etc) are used for a flask, backpack or quiver assigned
to the squad member. Due to a probable bug, food is dropped out of the
backpack when it is stored.
.. warning::
Although armor stands, cabinets and chests properly belong only to one
squad member, the owner of the building used to create the barracks will
randomly use any containers inside the room. Thus, it is recommended to
always create the armory from a weapon rack.
Contrary to the common misconception, all these uses are controlled by the
*Individual Equipment* usage flag. The *Squad Equipment* flag is actually
intended for ammo, but the game does even less in that area than for armor
and weapons. This plugin implements the following rules almost from scratch:
* Combat ammo is stored in chests inside rooms with Squad Equipment enabled.
* If a chest is assigned to a squad member due to Individual Equipment also
being set, it is only used for that squad's ammo; otherwise, any squads
with Squad Equipment on the room will use all of the chests at random.
* Training ammo is stored in chests inside archery ranges designated from
archery targets, and controlled by the same Train flag as archery training
itself. This is inspired by some defunct code for weapon racks.
There are some minor traces in the game code to suggest that the first of
these rules is intended by Toady; the rest are invented by this plugin.
Mode switch and reclaim Mode switch and reclaim
======================= =======================
@ -1195,10 +1305,18 @@ Usage:
List workflow-controlled jobs (if in a workshop, filtered by it). List workflow-controlled jobs (if in a workshop, filtered by it).
``workflow list`` ``workflow list``
List active constraints, and their job counts. List active constraints, and their job counts.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]`` ``workflow list-commands``
Set a constraint. The first form counts each stack as only 1 item. List active constraints as workflow commands that re-create them;
this list can be copied to a file, and then reloaded using the
``script`` built-in command.
``workflow count <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting every stack as 1 item.
``workflow amount <constraint-spec> <cnt-limit> [cnt-gap]``
Set a constraint, counting all items within stacks.
``workflow unlimit <constraint-spec>`` ``workflow unlimit <constraint-spec>``
Delete a constraint. Delete a constraint.
``workflow unlimit-all``
Delete all constraints.
Function Function
........ ........
@ -1213,6 +1331,37 @@ amount goes above or below the limit. The gap specifies how much below the limit
the amount has to drop before jobs are resumed; this is intended to reduce the amount has to drop before jobs are resumed; this is intended to reduce
the frequency of jobs being toggled. the frequency of jobs being toggled.
Check out the ``gui/workflow`` script below for a simple front-end integrated
in the game UI.
Constraint format
.................
The contstraint spec consists of 4 parts, separated with '/' characters::
ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
The first part is mandatory and specifies the item type and subtype,
using the raw tokens for items, in the same syntax you would e.g. use
for a custom reaction input. See this list for more info: http://dwarffortresswiki.org/index.php/Item_token
The subsequent parts are optional:
- A generic material spec constrains the item material to one of
the hard-coded generic classes, which currently include::
PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN
METAL STONE SAND GLASS CLAY MILK
- A specific material spec chooses the material exactly, using the
raw syntax for reaction input materials, e.g. INORGANIC:IRON,
although for convenience it also allows just IRON, or ACACIA:WOOD etc.
See this page for more details on the unabbreviated raw syntax:
http://dwarffortresswiki.org/index.php/Material_token
- A comma-separated list of miscellaneous flags, which currently can
be used to ignore imported items or items below a certain quality.
Constraint examples Constraint examples
................... ...................
@ -1238,10 +1387,15 @@ Make sure there are always 25-30 empty bins/barrels/bags.
Make sure there are always 15-20 coal and 25-30 copper bars. Make sure there are always 15-20 coal and 25-30 copper bars.
:: ::
workflow count BAR//COAL 20 workflow count BAR//COAL 20
workflow count BAR//COPPER 30 workflow count BAR//COPPER 30
Produce 15-20 gold crafts.
::
workflow count CRAFTS//GOLD 20
Collect 15-20 sand bags and clay boulders. Collect 15-20 sand bags and clay boulders.
:: ::
@ -1253,9 +1407,16 @@ Make sure there are always 80-100 units of dimple dye.
workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
.. note::
In order for this to work, you have to set the material of the PLANT input In order for this to work, you have to set the material of the PLANT input
on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material'
command. command. Otherwise the plugin won't be able to deduce the output material.
Maintain 10-100 locally-made crafts of exceptional quality.
::
workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
Fortress activity management Fortress activity management
@ -1545,19 +1706,17 @@ twice.
dfusion dfusion
------- -------
This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
:Friendship: a binary plugin that allows multi race forts (to use make a script that imports plugins.dfusion.friendship and use Friendship:install{table} table should contain list of race names.)
See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 :Embark: a binary plugin that allows multi race embark (to use make a script that imports plugins.dfusion.embark and use Embark:install{table} table should contain list of race names or list of pairs (race-name, caste_id)).
Confirmed working DFusion plugins: See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=93317.0
:simple_embark: allows changing the number of dwarves available on embark.
.. note:: .. note::
* Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
* This is currently working only on Windows. * The game will be suspended while you're using dfusion. Don't panic when it doesn't respond.
* The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
misery misery
------ ------
@ -1621,6 +1780,17 @@ gui/*
Scripts that implement dialogs inserted into the main game window are put in this Scripts that implement dialogs inserted into the main game window are put in this
directory. directory.
binpatch
========
Checks, applies or removes binary patches directly in memory at runtime::
binpatch check/apply/remove <patchname>
If the name of the patch has no extension or directory separators, the
script uses ``hack/patches/<df-version>/<name>.dif``, thus auto-selecting
the version appropriate for the currently loaded executable.
quicksave quicksave
========= =========
@ -1680,13 +1850,16 @@ slayrace
======== ========
Kills any unit of a given race. Kills any unit of a given race.
With no argument, lists the available races. With no argument, lists the available races and count eligible targets.
With the special argument ``him``, targets only the selected creature. With the special argument ``him``, targets only the selected creature.
With the special argument ``undead``, targets all undeads on the map,
regardless of their race.
Any non-dead non-caged unit of the specified race gets its ``blood_count`` Any non-dead non-caged unit of the specified race gets its ``blood_count``
set to 0, which means immediate death at the next game tick. For creatures set to 0, which means immediate death at the next game tick. For creatures
such as vampires, also set animal.vanish_countdown to 2. such as vampires, it also sets animal.vanish_countdown to 2.
An alternate mode is selected by adding a 2nd argument to the command, An alternate mode is selected by adding a 2nd argument to the command,
``magma``. In this case, a column of 7/7 magma is generated on top of the ``magma``. In this case, a column of 7/7 magma is generated on top of the
@ -1769,7 +1942,95 @@ deathcause
========== ==========
Focus a body part ingame, and this script will display the cause of death of Focus a body part ingame, and this script will display the cause of death of
the creature. the creature.
Also works when selecting units from the 'u'nitlist viewscreen.
lua
===
There are the following ways to invoke this command:
1. ``lua`` (without any parameters)
This starts an interactive lua interpreter.
2. ``lua -f "filename"`` or ``lua --file "filename"``
This loads and runs the file indicated by filename.
3. ``lua -s ["filename"]`` or ``lua --save ["filename"]``
This loads and runs the file indicated by filename from the save
directory. If the filename is not supplied, it loads "dfhack.lua".
4. ``:lua`` *lua statement...*
Parses and executes the lua statement like the interactive interpreter would.
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
create-items
============
Spawn arbitrary items under the cursor.
The first argument gives the item category, the second gives the material,
and the optionnal third gives the number of items to create (defaults to 20).
Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``,
``web``.
Instead of material, using ``list`` makes the script list eligible materials.
The ``web`` item category will create an uncollected cobweb on the floor.
Note that the script does not enforce anything, and will let you create
boulders of toad blood and stuff like that.
However the ``list`` mode will only show 'normal' materials.
Exemples::
create-items boulders COAL_BITUMINOUS 12
create-items plant tail_pig
create-items log list
create-items web CREATURE:SPIDER_CAVE_GIANT:SILK
create-items bar CREATURE:CAT:SOAP
create-items bar adamantine
======================= =======================
In-game interface tools In-game interface tools
@ -1783,6 +2044,9 @@ are mostly implemented by lua scripts.
In order to avoid user confusion, as a matter of policy all these tools In order to avoid user confusion, as a matter of policy all these tools
display the word "DFHack" on the screen somewhere while active. display the word "DFHack" on the screen somewhere while active.
When that is not appropriate because they merely add keybinding hints to
existing DF screens, they deliberately use red instead of green for the key.
As an exception, the tweak plugin described above does not follow this As an exception, the tweak plugin described above does not follow this
guideline because it arguably just fixes small usability bugs in the game UI. guideline because it arguably just fixes small usability bugs in the game UI.
@ -1793,12 +2057,18 @@ Dwarf Manipulator
Implemented by the manipulator plugin. To activate, open the unit screen and Implemented by the manipulator plugin. To activate, open the unit screen and
press 'l'. press 'l'.
.. image:: images/manipulator.png
This tool implements a Dwarf Therapist-like interface within the game UI. The This tool implements a Dwarf Therapist-like interface within the game UI. The
far left column displays the unit's Happiness (color-coded based on its far left column displays the unit's Happiness (color-coded based on its
value), and the right half of the screen displays each dwarf's labor settings value), and the right half of the screen displays each dwarf's labor settings
and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand
Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds Master, and U-Z for Legendary thru Legendary+5).
denote skills not controlled by labors.
Cells with teal backgrounds denote skills not controlled by labors, e.g.
military and social skills.
.. image:: images/manipulator2.png
Use the arrow keys or number pad to move the cursor around, holding Shift to Use the arrow keys or number pad to move the cursor around, holding Shift to
move 10 tiles at a time. move 10 tiles at a time.
@ -1832,10 +2102,88 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit
directly to the main dwarf mode screen. directly to the main dwarf mode screen.
Search
======
The search plugin adds search to the Stocks, Animals, Trading, Stockpile,
Noble (assignment candidates), Military (position candidates), Burrows
(unit list), Rooms, Announcements, Job List 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
filtering automatically.
Pressing ENTER, ESC or the arrow keys will return you to browsing the now
filtered list, which still functions as normal. You can clear the filter
by either going back into search mode and backspacing to delete it, or
pressing the "shifted" version of the search hotkey while browsing the
list (e.g. if the hotkey is 's', then hitting 'shift-s' will clear any
filter).
Leaving any screen automatically clears the filter.
In the Trade screen, the actual trade will always only act on items that
are actually visible in the list; the same effect applies to the Trade
Value numbers displayed by the screen. Because of this, the 't' key is
blocked while search is active, so you have to reset the filters first.
Pressing Alt-C will clear both search strings.
In the stockpile screen the option only appears if the cursor is in the
rightmost list:
.. image:: images/search-stockpile.png
Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only
on items actually shown in the rightmost list, so it is possible to select
only fat or tallow by forbidding fats, then searching for fat/tallow, and
using Permit Fats again while the list is filtered.
AutoMaterial
============
The automaterial plugin makes building constructions (walls, floors, fortifications,
etc) a little bit easier by saving you from having to trawl through long lists of
materials each time you place one.
Firstly, it moves the last used material for a given construction type to the top of
the list, if there are any left. So if you build a wall with chalk blocks, the next
time you place a wall the chalk blocks will be at the top of the list, regardless of
distance (it only does this in "grouped" mode, as individual item lists could be huge).
This should mean you can place most constructions without having to search for your
preferred material type.
.. image:: images/automaterial-mat.png
Pressing 'a' while highlighting any material will enable that material for "auto select"
for this construction type. You can enable multiple materials as autoselect. Now the next
time you place this type of construction, the plugin will automatically choose materials
for you from the kinds you enabled. If there is enough to satisfy the whole placement,
you won't be prompted with the material screen - the construction will be placed and you
will be back in the construction menu as if you did it manually.
When choosing the construction placement, you will see a couple of options:
.. image:: images/automaterial-pos.png
Use 'a' here to temporarily disable the material autoselection, e.g. if you need
to go to the material selection screen so you can toggle some materials on or off.
The other option (auto type selection, off by default) can be toggled on with 't'. If you
toggle this option on, instead of returning you to the main construction menu after selecting
materials, it returns you back to this screen. If you use this along with several autoselect
enabled materials, you should be able to place complex constructions more conveniently.
gui/liquids gui/liquids
=========== ===========
To use, bind to a key and activate in the 'k' mode. To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.
.. image:: images/liquids.png
While active, use the suggested keys to switch the usual liquids parameters, and Enter While active, use the suggested keys to switch the usual liquids parameters, and Enter
to select the target area and apply changes. to select the target area and apply changes.
@ -1844,7 +2192,9 @@ to select the target area and apply changes.
gui/mechanisms gui/mechanisms
============== ==============
To use, bind to a key and activate in the 'q' mode. To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.
.. image:: images/mechanisms.png
Lists mechanisms connected to the building, and their links. Navigating the list centers Lists mechanisms connected to the building, and their links. Navigating the list centers
the view on the relevant linked buildings. the view on the relevant linked buildings.
@ -1862,21 +2212,35 @@ via a simple dialog in the game ui.
* ``gui/rename [building]`` in 'q' mode changes the name of a building. * ``gui/rename [building]`` in 'q' mode changes the name of a building.
.. image:: images/rename-bld.png
The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. The selected building must be one of stockpile, workshop, furnace, trap, or siege engine.
It is also possible to rename zones from the 'i' menu. It is also possible to rename zones from the 'i' menu.
* ``gui/rename [unit]`` with a unit selected changes the nickname. * ``gui/rename [unit]`` with a unit selected changes the nickname.
Unlike the built-in interface, this works even on enemies and animals.
* ``gui/rename unit-profession`` changes the selected unit's custom profession name. * ``gui/rename unit-profession`` changes the selected unit's custom profession name.
.. image:: images/rename-prof.png
Likewise, this can be applied to any unit, and when used on animals it overrides
their species string.
The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state.
The example config binds building/unit rename to Ctrl-Shift-N, and
unit profession change to Ctrl-Shift-T.
gui/room-list gui/room-list
============= =============
To use, bind to a key and activate in the 'q' mode, either immediately or after opening To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode,
the assign owner page. either immediately or after opening the assign owner page.
.. image:: images/room-list.png
The script lists other rooms owned by the same owner, or by the unit selected in the assign The script lists other rooms owned by the same owner, or by the unit selected in the assign
list, and allows unassigning them. list, and allows unassigning them.
@ -1885,7 +2249,8 @@ list, and allows unassigning them.
gui/choose-weapons gui/choose-weapons
================== ==================
Bind to a key, and activate in the Equip->View/Customize page of the military screen. Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize
page of the military screen.
Depending on the cursor location, it rewrites all 'individual choice weapon' entries Depending on the cursor location, it rewrites all 'individual choice weapon' entries
in the selected squad or position to use a specific weapon type matching the assigned in the selected squad or position to use a specific weapon type matching the assigned
@ -1896,6 +2261,193 @@ Rationale: individual choice seems to be unreliable when there is a weapon short
and may lead to inappropriate weapons being selected. and may lead to inappropriate weapons being selected.
gui/guide-path
==============
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.
gui/workshop-job
================
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.
.. 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
if you select a material, or the material will be matched incorrectly in some cases.
If you press 'm' without choosing an item type, the script will auto-choose it
if there is only one valid choice, or pop up an error message box instead of the
material selection dialog.
Note that both materials and item types presented in the dialogs are filtered
by the job input flags, and even the selected item type for material selection,
or material for item type selection. Many jobs would let you select only one
input item type.
For example, if you choose a *plant* input item type for your prepare meal job,
it will only let you select cookable materials.
If you choose a *barrel* item instead (meaning things stored in barrels, like
drink or milk), it will let you select any material, since in this case the
material is matched against the barrel itself. Then, if you select, say, iron,
and then try to change the input item type, now it won't let you select *plant*;
you have to unset the material first.
gui/workflow
============
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
current job, and their current status.
A constraint specifies a certain range to be compared against either individual
*item* or whole *stack* count, an item type and optionally a material. When the
current count is below the lower bound of the range, the job is resumed; if it
is above or equal to the top bound, it will be suspended. Within the range, the
specific constraint has no effect on the job; others may still affect it.
Pressing 'I' switches the current constraint between counting stacks or items.
Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the
bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting
items and expanding the range each gives a 2x bonus).
Pressing 'A' produces a list of possible outputs of this job as guessed by
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
If you select one of the outputs with Enter, the matching constraint is simply
added to the list. If you use Shift-Enter, 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
Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen)
opens the overall status screen, which was copied from the C++ implementation
by falconne for better integration with the rest of the lua script:
.. image:: images/workflow-status.png
This screen shows all currently existing workflow constraints, and allows
monitoring and/or changing them from one screen. The constraint list can
be filtered by typing text in the field below.
The color of the stock level number indicates how "healthy" the stock level
is, based on current count and trend. Bright green is very good, green is good,
red is bad, bright red is very bad.
The limit number is also color-coded. Red means that there are currently no
workshops producing that item (i.e. no jobs). If it's yellow, that means the
production has been delayed, possibly due to lack of input materials.
The chart on the right is a plot of the last 14 days (28 half day plots) worth
of stock history for the selected item, with the rightmost point representing
the current stock value. The bright green dashed line is the target
limit (maximum) and the dark green line is that minus the gap (minimum).
gui/assign-rack
===============
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:
* Weapon racks have to each be assigned to a specific squad, like with
beds/boxes/armor stands and individual squad members, but nothing in
the game does this. This issue is what this script addresses.
* Even if assigned by the script, **the game will unassign the racks again without a binary patch**.
This patch is called ``weaponrack-unassign``, and can be applied via
the binpatch program, or the matching script. See this for more info
about the bug:
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
* Haulers still take equpment stored in the armory away to the stockpiles,
unless the ``fix-armory`` plugin above is used.
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. In order to aid in the choice, it shows the number
of currently assigned racks for every valid squad.
gui/advfort
=============
This script allows to perform jobs in adventure mode. For more complete help
press '?' while script is running. It's most confortable to use this as a
keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:
* -a or --nodfassign - uses different method to assign items.
* -i or --inventory - checks inventory for possible items to use in the job.
* -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones).
implies -a
* job - selects that job (e.g. Dig or FellTree)
gui/gm-editor
=============
There are three ways to open this editor:
* using gui/gm-editor command/keybinding - opens editor on what is selected
or viewed (e.g. unit/item description screen)
* using gui/gm-editor <lua command> - executes lua command and opens editor on
it's results (e.g. gui/gm-editor "df.global.world.items.all" shows all items)
* using gui/gm-edito dialog - shows an in game dialog to input lua command. Works
the same as version above.
This editor allows to change and modify almost anything in df. Press '?' for an
in-game help.
============= =============
Behavior Mods Behavior Mods
============= =============
@ -1931,7 +2483,10 @@ Configuration UI
---------------- ----------------
The configuration front-end to the plugin is implemented by the gui/siege-engine The configuration front-end to the plugin is implemented by the gui/siege-engine
script. Bind it to a key and activate after selecting a siege engine in 'q' mode. script. Bind it to a key (the example config uses Alt-A) and activate after selecting
a siege engine in 'q' mode.
.. image:: images/siege-engine.png
The main mode displays the current target, selected ammo item type, linked stockpiles and The main mode displays the current target, selected ammo item type, linked stockpiles and
the allowed operator skill range. The map tile color is changed to signify if it can be the allowed operator skill range. The map tile color is changed to signify if it can be
@ -1961,7 +2516,10 @@ The power-meter plugin implements a modified pressure plate that detects power b
supplied to gear boxes built in the four adjacent N/S/W/E tiles. supplied to gear boxes built in the four adjacent N/S/W/E tiles.
The configuration front-end is implemented by the gui/power-meter script. Bind it to a The configuration front-end is implemented by the gui/power-meter script. Bind it to a
key and activate after selecting Pressure Plate in the build menu. key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate
in the build menu.
.. image:: images/power-meter.png
The script follows the general look and feel of the regular pressure plate build The script follows the general look and feel of the regular pressure plate build
configuration page, but configures parameters relevant to the modded power meter building. configuration page, but configures parameters relevant to the modded power meter building.
@ -2083,3 +2641,4 @@ be bought from caravans. :)
To be really useful this needs patches from bug 808, ``tweak fix-dimensions`` To be really useful this needs patches from bug 808, ``tweak fix-dimensions``
and ``tweak advmode-contained``. and ``tweak advmode-contained``.

@ -2,23 +2,34 @@
# Generic dwarfmode bindings # # Generic dwarfmode bindings #
############################## ##############################
# toggle the display of water level as 1-7 tiles
keybinding add Ctrl-W twaterlvl keybinding add Ctrl-W twaterlvl
# with cursor: # with cursor:
# designate the whole vein for digging
keybinding add Ctrl-V digv keybinding add Ctrl-V digv
keybinding add Ctrl-Shift-V "digv x" keybinding add Ctrl-Shift-V "digv x"
# clean the selected tile of blood etc
keybinding add Ctrl-C spotclean keybinding add Ctrl-C spotclean
# destroy items designated for dump in the selected tile
keybinding add Ctrl-Shift-K autodump-destroy-here keybinding add Ctrl-Shift-K autodump-destroy-here
# any item: # with an item selected:
# destroy the selected item
keybinding add Ctrl-K autodump-destroy-item keybinding add Ctrl-K autodump-destroy-item
# scripts:
# quicksave, only in main dwarfmode screen and menu page # quicksave, only in main dwarfmode screen and menu page
keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave
# gui/rename script # gui/rename script - rename units and buildings
keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-N gui/rename
keybinding add Alt-Shift-P "gui/rename unit-profession" keybinding add Ctrl-Shift-T "gui/rename unit-profession"
############################## ##############################
# Generic adv mode bindings # # Generic adv mode bindings #
@ -31,10 +42,10 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force"
# Context-specific bindings # # Context-specific bindings #
############################# #############################
# q->stockpile; p # q->stockpile; p - copy & paste stockpiles
keybinding add Alt-P copystock keybinding add Alt-P copystock
# q->workshop # q->workshop - duplicate the selected job
keybinding add Ctrl-D job-duplicate keybinding add Ctrl-D job-duplicate
# materials: q->workshop; b->select items # materials: q->workshop; b->select items
@ -48,7 +59,7 @@ keybinding add Shift-O "job-material OBSIDIAN"
keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-T "job-material ORTHOCLASE"
keybinding add Shift-G "job-material GLASS_GREEN" keybinding add Shift-G "job-material GLASS_GREEN"
# sort units and items # sort units and items in the on-screen list
keybinding add Alt-Shift-N "sort-units name" "sort-items description" keybinding add Alt-Shift-N "sort-units name" "sort-items description"
keybinding add Alt-Shift-R "sort-units arrival" keybinding add Alt-Shift-R "sort-units arrival"
keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" keybinding add Alt-Shift-T "sort-units profession" "sort-items type material"
@ -60,7 +71,7 @@ keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms
# browse rooms of same owner # browse rooms of same owner
keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# interface for the liquids plugin # interface for the liquids plugin - spawn water/magma/obsidian
keybinding add Alt-L@dwarfmode/LookAround gui/liquids keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction # machine power sensitive pressure plate construction
@ -79,7 +90,11 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path
keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job
# workflow front-end # workflow front-end
keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow
keybinding add Alt-W@overallstatus "gui/workflow status"
# assign weapon racks to squads so that they can be used
keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack
############################ ############################
# UI and game logic tweaks # # UI and game logic tweaks #
@ -118,3 +133,39 @@ tweak fast-trade
tweak military-stable-assign tweak military-stable-assign
# in same list, color units already assigned to squads in brown & green # in same list, color units already assigned to squads in brown & green
tweak military-color-assigned tweak military-color-assigned
# remove inverse dependency of squad training speed on unit list size and use more sparring
tweak military-training
###########
# Scripts #
###########
# write the correct season to gamelog on world load
soundsense-season
# patch the material objects in memory to fix cloth stockpiles
fix/cloth-stockpile enable
#######################################################
# Apply binary patches at runtime #
# #
# Commented out by default; enable the ones you want. #
#######################################################
# Bug 5994 - items teleported when removing a construction
#binpatch apply deconstruct-teleport
#binpatch apply deconstruct-heapfall
# Bug 4406 - hospital overstocking on all items
#binpatch apply hospital-overstocking
# Bug 808 - custom reactions completely using up all of their reagents
#binpatch apply custom-reagent-size
# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo
#binpatch apply training-ammo
# Bug 1445 - weapon racks broken, armor stand capacity too low
#binpatch apply weaponrack-unassign
#binpatch apply armorstand-capacity

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: 5.8 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: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 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

@ -111,6 +111,7 @@ include/modules/Burrows.h
include/modules/Constructions.h include/modules/Constructions.h
include/modules/Units.h include/modules/Units.h
include/modules/Engravings.h include/modules/Engravings.h
include/modules/EventManager.h
include/modules/Gui.h include/modules/Gui.h
include/modules/Items.h include/modules/Items.h
include/modules/Job.h include/modules/Job.h
@ -121,7 +122,6 @@ include/modules/Materials.h
include/modules/Notes.h include/modules/Notes.h
include/modules/Screen.h include/modules/Screen.h
include/modules/Translation.h include/modules/Translation.h
include/modules/Vegetation.h
include/modules/Vermin.h include/modules/Vermin.h
include/modules/World.h include/modules/World.h
include/modules/Graphic.h include/modules/Graphic.h
@ -133,6 +133,7 @@ modules/Burrows.cpp
modules/Constructions.cpp modules/Constructions.cpp
modules/Units.cpp modules/Units.cpp
modules/Engravings.cpp modules/Engravings.cpp
modules/EventManager.cpp
modules/Gui.cpp modules/Gui.cpp
modules/Items.cpp modules/Items.cpp
modules/Job.cpp modules/Job.cpp
@ -142,7 +143,6 @@ modules/Materials.cpp
modules/Notes.cpp modules/Notes.cpp
modules/Screen.cpp modules/Screen.cpp
modules/Translation.cpp modules/Translation.cpp
modules/Vegetation.cpp
modules/Vermin.cpp modules/Vermin.cpp
modules/World.cpp modules/World.cpp
modules/Graphic.cpp modules/Graphic.cpp
@ -167,7 +167,7 @@ IF(UNIX)
IF(BUILD_EGGY) IF(BUILD_EGGY)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY})
ELSEIF(APPLE) ELSEIF(APPLE)
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN})
ELSE() ELSE()
LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX})
ENDIF() ENDIF()
@ -235,7 +235,7 @@ ENDIF()
IF(UNIX) IF(UNIX)
SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
IF(APPLE) IF(APPLE)
SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread)
ENDIF() ENDIF()
ELSE(WIN32) ELSE(WIN32)
#FIXME: do we really need psapi? #FIXME: do we really need psapi?
@ -273,14 +273,14 @@ ENDIF()
SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
IF(APPLE) IF(APPLE)
SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework)
SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib)
SET(ZIP_LIBRARY /usr/lib/libz.dylib) SET(ZIP_LIBRARY /usr/lib/libz.dylib)
TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY})
TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY})
SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0)
SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0)
ENDIF() ENDIF()
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS})
@ -290,20 +290,20 @@ TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE) if(APPLE)
add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...")
endif() endif()
IF(UNIX) IF(UNIX)
if (APPLE) if (APPLE)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run
DESTINATION .) DESTINATION .)
else() else()
# On linux, copy our version of the df launch script which sets LD_PRELOAD # On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .) DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .) DESTINATION .)
endif() endif()
ELSE() ELSE()
@ -346,6 +346,10 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts
PATTERN "*.rb" PATTERN "*.rb"
) )
install(DIRECTORY ${dfhack_SOURCE_DIR}/patches
DESTINATION ${DFHACK_DATA_DESTINATION}
FILES_MATCHING PATTERN "*.dif")
# Unused for so long that it's not even relevant now... # Unused for so long that it's not even relevant now...
if(BUILD_DEVEL) if(BUILD_DEVEL)
if(WIN32) if(WIN32)

@ -44,6 +44,7 @@ using namespace std;
#include "VersionInfo.h" #include "VersionInfo.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "ModuleFactory.h" #include "ModuleFactory.h"
#include "modules/EventManager.h"
#include "modules/Gui.h" #include "modules/Gui.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
@ -316,7 +317,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr,
rbcmd += "'" + args[i] + "', "; rbcmd += "'" + args[i] + "', ";
rbcmd += "]\n"; rbcmd += "]\n";
rbcmd += "load './hack/scripts/" + name + ".rb'"; rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }";
return plug_mgr->eval_ruby(out, rbcmd.c_str()); return plug_mgr->eval_ruby(out, rbcmd.c_str());
} }
@ -343,6 +344,50 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
return CR_NOT_IMPLEMENTED; return CR_NOT_IMPLEMENTED;
} }
static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed)
{
std::vector<std::string> possible;
auto plug_mgr = Core::getInstance().getPluginManager();
for(size_t i = 0; i < plug_mgr->size(); i++)
{
const Plugin * plug = (plug_mgr->operator[](i));
for (size_t j = 0; j < plug->size(); j++)
{
const PluginCommand &pcmd = plug->operator[](j);
if (pcmd.isHotkeyCommand())
continue;
if (pcmd.name.substr(0, first.size()) == first)
possible.push_back(pcmd.name);
}
}
bool all = (first.find('/') != std::string::npos);
std::map<string, string> scripts;
listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all);
for (auto iter = scripts.begin(); iter != scripts.end(); ++iter)
if (iter->first.substr(0, first.size()) == first)
possible.push_back(iter->first);
if (possible.size() == 1)
{
completed = possible[0];
fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str());
return true;
}
if (possible.size() > 1 && possible.size() < 8)
{
std::string out;
for (size_t i = 0; i < possible.size(); i++)
out += " " + possible[i];
con.print("Possible completions:%s\n", out.c_str());
}
return false;
}
command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts) command_result Core::runCommand(color_ostream &con, const std::string &first, vector<string> &parts)
{ {
if (!first.empty()) if (!first.empty())
@ -375,7 +420,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
" reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n"
); );
con.print("\nDFHack version " DFHACK_VERSION ".\n"); con.print("\nDFHack version " DFHACK_VERSION ".\n");
} }
else if (parts.size() == 1) else if (parts.size() == 1)
{ {
@ -665,10 +710,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve
if(res == CR_NOT_IMPLEMENTED) if(res == CR_NOT_IMPLEMENTED)
{ {
auto filename = getHackPath() + "scripts/" + first; auto filename = getHackPath() + "scripts/" + first;
std::string completed;
if (fileExists(filename + ".lua")) if (fileExists(filename + ".lua"))
res = runLuaScript(con, first, parts); res = runLuaScript(con, first, parts);
else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) else if (plug_mgr->eval_ruby && fileExists(filename + ".rb"))
res = runRubyScript(con, plug_mgr, first, parts); res = runRubyScript(con, plug_mgr, first, parts);
else if (try_autocomplete(con, first, completed))
return runCommand(con, completed, parts);
else else
con.printerr("%s is not a recognized command.\n", first.c_str()); con.printerr("%s is not a recognized command.\n", first.c_str());
} }
@ -733,7 +782,6 @@ void fIOthread(void * iodata)
{ {
string command = ""; string command = "";
int ret = con.lineedit("[DFHack]# ",command, main_history); int ret = con.lineedit("[DFHack]# ",command, main_history);
fprintf(stderr,"Command: [%s]\n",command.c_str());
if(ret == -2) if(ret == -2)
{ {
cerr << "Console is shutting down properly." << endl; cerr << "Console is shutting down properly." << endl;
@ -747,14 +795,10 @@ void fIOthread(void * iodata)
else if(ret) else if(ret)
{ {
// a proper, non-empty command was entered // a proper, non-empty command was entered
fprintf(stderr,"Adding command to history\n");
main_history.add(command); main_history.add(command);
fprintf(stderr,"Saving history\n");
main_history.save("dfhack.history"); main_history.save("dfhack.history");
} }
fprintf(stderr,"Running command\n");
auto rv = core->runCommand(con, command); auto rv = core->runCommand(con, command);
if (rv == CR_NOT_IMPLEMENTED) if (rv == CR_NOT_IMPLEMENTED)
@ -869,7 +913,6 @@ bool Core::Init()
// Init global object pointers // Init global object pointers
df::global::InitGlobals(); df::global::InitGlobals();
init_screen_module(this);
cerr << "Initializing Console.\n"; cerr << "Initializing Console.\n";
// init the console. // init the console.
@ -895,6 +938,7 @@ bool Core::Init()
*/ */
// initialize data defs // initialize data defs
virtual_identity::Init(this); virtual_identity::Init(this);
init_screen_module(this);
// initialize common lua context // initialize common lua context
Lua::Core::Init(con); Lua::Core::Init(con);
@ -904,6 +948,7 @@ bool Core::Init()
cerr << "Initializing Plugins.\n"; cerr << "Initializing Plugins.\n";
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(this);
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
@ -1238,6 +1283,8 @@ static int buildings_timer = 0;
void Core::onUpdate(color_ostream &out) void Core::onUpdate(color_ostream &out)
{ {
EventManager::manageEvents(out);
// convert building reagents // convert building reagents
if (buildings_do_onupdate && (++buildings_timer & 1)) if (buildings_do_onupdate && (++buildings_timer & 1))
buildings_onUpdate(out); buildings_onUpdate(out);
@ -1251,6 +1298,8 @@ void Core::onUpdate(color_ostream &out)
void Core::onStateChange(color_ostream &out, state_change_event event) void Core::onStateChange(color_ostream &out, state_change_event event)
{ {
EventManager::onStateChange(out, event);
buildings_onStateChange(out, event); buildings_onStateChange(out, event);
plug_mgr->OnStateChange(out, event); plug_mgr->OnStateChange(out, event);
@ -1615,15 +1664,27 @@ void ClassNameCheck::getKnownClassNames(std::vector<std::string> &names)
names.push_back(*it); names.push_back(*it);
} }
bool Process::patchMemory(void *target, const void* src, size_t count) MemoryPatcher::MemoryPatcher(Process *p_) : p(p_)
{
if (!p)
p = Core::getInstance().p;
}
MemoryPatcher::~MemoryPatcher()
{
close();
}
bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write)
{ {
uint8_t *sptr = (uint8_t*)target; uint8_t *sptr = (uint8_t*)target;
uint8_t *eptr = sptr + count; uint8_t *eptr = sptr + count;
// Find the valid memory ranges // Find the valid memory ranges
std::vector<t_memrange> ranges; if (ranges.empty())
getMemRanges(ranges); p->getMemRanges(ranges);
// Find the ranges that this area spans
unsigned start = 0; unsigned start = 0;
while (start < ranges.size() && ranges[start].end <= sptr) while (start < ranges.size() && ranges[start].end <= sptr)
start++; start++;
@ -1646,23 +1707,45 @@ bool Process::patchMemory(void *target, const void* src, size_t count)
return false; return false;
// Apply writable permissions & update // Apply writable permissions & update
bool ok = true; for (unsigned i = start; i < end; i++)
for (unsigned i = start; i < end && ok; i++)
{ {
t_memrange perms = ranges[i]; auto &perms = ranges[i];
if ((perms.write || !write) && perms.read)
continue;
save.push_back(perms);
perms.write = perms.read = true; perms.write = perms.read = true;
if (!setPermisions(perms, perms)) if (!p->setPermisions(perms, perms))
ok = false; return false;
} }
if (ok) return true;
memmove(target, src, count); }
bool MemoryPatcher::write(void *target, const void *src, size_t size)
{
if (!makeWritable(target, size))
return false;
for (unsigned i = start; i < end && ok; i++) memmove(target, src, size);
setPermisions(ranges[i], ranges[i]); return true;
}
void MemoryPatcher::close()
{
for (size_t i = 0; i < save.size(); i++)
p->setPermisions(save[i], save[i]);
save.clear();
ranges.clear();
};
bool Process::patchMemory(void *target, const void* src, size_t count)
{
MemoryPatcher patcher(this);
return ok; return patcher.write(target, src, count);
} }
/******************************************************************************* /*******************************************************************************

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

@ -725,6 +725,316 @@ static void OpenMatinfo(lua_State *state)
lua_pop(state, 1); lua_pop(state, 1);
} }
/**************
* Pen object *
**************/
static int DFHACK_PEN_TOKEN = 0;
void Lua::Push(lua_State *L, const Screen::Pen &info)
{
if (!info.valid())
{
lua_pushnil(L);
return;
}
void *pdata = lua_newuserdata(L, sizeof(Pen));
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
lua_setmetatable(L, -2);
new (pdata) Pen(info);
}
static Pen *check_pen_native(lua_State *L, int index)
{
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2))
luaL_argerror(L, index, "not a pen object");
lua_pop(L, 2);
return (Pen*)lua_touserdata(L, index);
}
void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color)
{
index = lua_absindex(L, index);
luaL_checkany(L, index);
if (lua_isnil(L, index))
{
if (!allow_nil)
luaL_argerror(L, index, "nil pen not allowed");
*pen = Pen(0,0,0,-1);
}
else if (lua_isuserdata(L, index))
{
*pen = *check_pen_native(L, index);
}
else if (allow_color && lua_isnumber(L, index))
{
*pen = Pen(0, lua_tointeger(L, index)&15, 0);
}
else
{
luaL_checktype(L, index, LUA_TTABLE);
decode_pen(L, *pen, index);
}
}
static int adjust_pen(lua_State *L, bool no_copy)
{
lua_settop(L, 4);
Pen pen;
int iidx = 1;
Lua::CheckPen(L, &pen, 1, true, true);
if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4))
{
if (lua_isnumber(L, 2) || lua_isnil(L, 2))
{
if (!pen.valid())
pen = Pen();
iidx = -1;
pen.fg = luaL_optint(L, 2, pen.fg) & 15;
pen.bg = luaL_optint(L, 3, pen.bg);
if (!lua_isnil(L, 4))
pen.bold = lua_toboolean(L, 4);
else if (!lua_isnil(L, 2))
{
pen.bold = !!(pen.fg & 8);
pen.fg &= 7;
}
}
else
{
iidx = 2;
Lua::CheckPen(L, &pen, 2, false, false);
}
}
if (no_copy && iidx > 0 && lua_isuserdata(L, iidx))
lua_pushvalue(L, iidx);
else
Lua::Push(L, pen);
return 1;
}
static int dfhack_pen_parse(lua_State *L)
{
return adjust_pen(L, true);
}
static int dfhack_pen_make(lua_State *L)
{
return adjust_pen(L, false);
}
static void make_pen_table(lua_State *L, Pen &pen)
{
if (!pen.valid())
luaL_error(L, "invalid pen state");
else
{
lua_newtable(L);
lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
}
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color");
break;
}
}
}
static void get_pen_mirror(lua_State *L, int idx)
{
lua_getuservalue(L, idx);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
Pen pen;
Lua::CheckPen(L, &pen, idx, false, false);
make_pen_table(L, pen);
lua_dup(L);
lua_setuservalue(L, idx);
}
}
static int dfhack_pen_index(lua_State *L)
{
lua_settop(L, 2);
luaL_checktype(L, 1, LUA_TUSERDATA);
// check metatable
if (!lua_getmetatable(L, 1))
luaL_argerror(L, 1, "must be a pen");
lua_pushvalue(L, 2);
lua_rawget(L, -2);
if (!lua_isnil(L, -1))
return 1;
// otherwise read from the mirror table, creating it if necessary
lua_settop(L, 2);
get_pen_mirror(L, 1);
lua_pushvalue(L, 2);
lua_rawget(L, -2);
return 1;
}
static int pen_pnext(lua_State *L)
{
lua_settop(L, 2); /* create a 2nd argument if there isn't one */
if (lua_next(L, lua_upvalueindex(1)))
return 2;
lua_pushnil(L);
return 1;
}
static int dfhack_pen_pairs(lua_State *L)
{
luaL_checktype(L, 1, LUA_TUSERDATA);
get_pen_mirror(L, 1);
lua_pushcclosure(L, pen_pnext, 1);
lua_pushnil(L);
lua_pushnil(L);
return 3;
}
const char *const pen_fields[] = {
"ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL
};
static int dfhack_pen_newindex(lua_State *L)
{
lua_settop(L, 3);
luaL_checktype(L, 1, LUA_TUSERDATA);
int id = luaL_checkoption(L, 2, NULL, pen_fields);
int arg = 0;
Pen &pen = *check_pen_native(L, 1);
bool wipe_tile = false, wipe_tc = false;
switch (id) {
case 0:
if (lua_type(L, 3) != LUA_TNUMBER)
arg = (unsigned char)*luaL_checkstring(L, 3);
else
arg = luaL_checkint(L, 3);
pen.ch = arg;
lua_pushinteger(L, (unsigned char)pen.ch);
break;
case 1:
pen.fg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.fg);
break;
case 2:
pen.bold = lua_toboolean(L, 3);
lua_pushboolean(L, pen.bold);
break;
case 3:
pen.bg = luaL_checkint(L, 3) & 15;
lua_pushinteger(L, pen.bg);
break;
case 4:
arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3);
if (arg < 0)
luaL_argerror(L, 3, "invalid tile index");
pen.tile = arg;
if (pen.tile)
lua_pushinteger(L, pen.tile);
else
lua_pushnil(L);
break;
case 5:
wipe_tile = (pen.tile_mode == Pen::TileColor);
pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs;
lua_pushboolean(L, pen.tile_mode == Pen::CharColor);
break;
case 6:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; }
pen.tile_fg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_fg);
break;
case 7:
if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; }
pen.tile_bg = luaL_checkint(L, 3) & 15;
pen.tile_mode = Pen::TileColor;
lua_pushinteger(L, pen.tile_bg);
break;
}
lua_getuservalue(L, 1);
if (!lua_isnil(L, -1))
{
lua_remove(L, 3);
lua_insert(L, 2);
lua_rawset(L, 2);
if (wipe_tc) {
lua_pushnil(L); lua_setfield(L, 2, "tile_color");
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg");
}
if (wipe_tile) {
lua_pushnil(L); lua_setfield(L, 2, "tile_fg");
lua_pushnil(L); lua_setfield(L, 2, "tile_bg");
}
}
return 0;
}
static const luaL_Reg dfhack_pen_funcs[] = {
{ "parse", dfhack_pen_parse },
{ "make", dfhack_pen_make },
{ "__index", dfhack_pen_index },
{ "__pairs", dfhack_pen_pairs },
{ "__newindex", dfhack_pen_newindex },
{ NULL, NULL }
};
static void OpenPen(lua_State *state)
{
luaL_getsubtable(state, lua_gettop(state), "pen");
lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN);
luaL_setfuncs(state, dfhack_pen_funcs, 0);
lua_pop(state, 1);
}
/************************ /************************
* Wrappers for C++ API * * Wrappers for C++ API *
************************/ ************************/
@ -811,6 +1121,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = {
WRAPM(Job,cloneJobStruct), WRAPM(Job,cloneJobStruct),
WRAPM(Job,printItemDetails), WRAPM(Job,printItemDetails),
WRAPM(Job,printJobDetails), WRAPM(Job,printJobDetails),
WRAPM(Job,getGeneralRef),
WRAPM(Job,getSpecificRef),
WRAPM(Job,getHolder), WRAPM(Job,getHolder),
WRAPM(Job,getWorker), WRAPM(Job,getWorker),
WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkBuildingsNow),
@ -847,6 +1159,8 @@ static const luaL_Reg dfhack_job_funcs[] = {
/***** Units module *****/ /***** Units module *****/
static const LuaWrapper::FunctionReg dfhack_units_module[] = { static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getGeneralRef),
WRAPM(Units, getSpecificRef),
WRAPM(Units, getContainer), WRAPM(Units, getContainer),
WRAPM(Units, setNickname), WRAPM(Units, setNickname),
WRAPM(Units, getVisibleName), WRAPM(Units, getVisibleName),
@ -866,6 +1180,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getNominalSkill), WRAPM(Units, getNominalSkill),
WRAPM(Units, getEffectiveSkill), WRAPM(Units, getEffectiveSkill),
WRAPM(Units, getExperience),
WRAPM(Units, computeMovementSpeed), WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
@ -942,6 +1257,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getOwner), WRAPM(Items, getOwner),
WRAPM(Items, setOwner), WRAPM(Items, setOwner),
WRAPM(Items, getContainer), WRAPM(Items, getContainer),
WRAPM(Items, getHolderBuilding),
WRAPM(Items, getHolderUnit),
WRAPM(Items, getDescription), WRAPM(Items, getDescription),
WRAPM(Items, isCasteMaterial), WRAPM(Items, isCasteMaterial),
WRAPM(Items, getSubtypeCount), WRAPM(Items, getSubtypeCount),
@ -1115,6 +1432,8 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) {
} }
static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { static const LuaWrapper::FunctionReg dfhack_buildings_module[] = {
WRAPM(Buildings, getGeneralRef),
WRAPM(Buildings, getSpecificRef),
WRAPM(Buildings, setOwner), WRAPM(Buildings, setOwner),
WRAPM(Buildings, allocInstance), WRAPM(Buildings, allocInstance),
WRAPM(Buildings, checkFreeTiles), WRAPM(Buildings, checkFreeTiles),
@ -1249,7 +1568,7 @@ static int screen_getWindowSize(lua_State *L)
static int screen_paintTile(lua_State *L) static int screen_paintTile(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2); int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3); int y = luaL_checkint(L, 3);
if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) if (lua_gettop(L) >= 4 && !lua_isnil(L, 4))
@ -1270,44 +1589,14 @@ static int screen_readTile(lua_State *L)
int x = luaL_checkint(L, 1); int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2); int y = luaL_checkint(L, 2);
Pen pen = Screen::readTile(x, y); Pen pen = Screen::readTile(x, y);
Lua::Push(L, pen);
if (!pen.valid())
{
lua_pushnil(L);
}
else
{
lua_newtable(L);
lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch");
lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg");
lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg");
lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold");
if (pen.tile)
{
lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile");
switch (pen.tile_mode) {
case Pen::CharColor:
lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color");
break;
case Pen::TileColor:
lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg");
lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg");
break;
default:
break;
}
}
}
return 1; return 1;
} }
static int screen_paintString(lua_State *L) static int screen_paintString(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x = luaL_checkint(L, 2); int x = luaL_checkint(L, 2);
int y = luaL_checkint(L, 3); int y = luaL_checkint(L, 3);
const char *text = luaL_checkstring(L, 4); const char *text = luaL_checkstring(L, 4);
@ -1318,7 +1607,7 @@ static int screen_paintString(lua_State *L)
static int screen_fillRect(lua_State *L) static int screen_fillRect(lua_State *L)
{ {
Pen pen; Pen pen;
decode_pen(L, pen, 1); Lua::CheckPen(L, &pen, 1);
int x1 = luaL_checkint(L, 2); int x1 = luaL_checkint(L, 2);
int y1 = luaL_checkint(L, 3); int y1 = luaL_checkint(L, 3);
int x2 = luaL_checkint(L, 4); int x2 = luaL_checkint(L, 4);
@ -1436,9 +1725,11 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
return rv; return rv;
} }
static uint32_t getImageBase() { return Core::getInstance().p->getBase(); }
static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); }
static const LuaWrapper::FunctionReg dfhack_internal_module[] = { static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
WRAP(getImageBase),
WRAP(getRebaseDelta), WRAP(getRebaseDelta),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1492,6 +1783,18 @@ static int internal_getVTable(lua_State *L)
return 1; return 1;
} }
static int internal_adjustOffset(lua_State *L)
{
lua_settop(L, 2);
int off = luaL_checkint(L, 1);
int rv = Core::getInstance().p->adjustOffset(off, lua_toboolean(L, 2));
if (rv >= 0)
lua_pushinteger(L, rv);
else
lua_pushnil(L);
return 1;
}
static int internal_getMemRanges(lua_State *L) static int internal_getMemRanges(lua_State *L)
{ {
std::vector<DFHack::t_memrange> ranges; std::vector<DFHack::t_memrange> ranges;
@ -1535,6 +1838,81 @@ static int internal_patchMemory(lua_State *L)
return 1; return 1;
} }
static int internal_patchBytes(lua_State *L)
{
luaL_checktype(L, 1, LUA_TTABLE);
lua_settop(L, 2);
MemoryPatcher patcher;
if (!lua_isnil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
lua_pushnil(L);
while (lua_next(L, 2))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in verify table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, false))
{
lua_pushnil(L);
lua_pushstring(L, "invalid verify address");
lua_pushvalue(L, -3);
return 3;
}
if (*addr != value)
{
lua_pushnil(L);
lua_pushstring(L, "wrong verify value");
lua_pushvalue(L, -3);
return 3;
}
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
int isnum;
uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum);
if (!isnum)
luaL_error(L, "invalid value in write table");
lua_pop(L, 1);
if (!patcher.verifyAccess(addr, 1, true))
{
lua_pushnil(L);
lua_pushstring(L, "invalid write address");
lua_pushvalue(L, -3);
return 3;
}
}
lua_pushnil(L);
while (lua_next(L, 1))
{
uint8_t *addr = (uint8_t*)checkaddr(L, -2, true);
uint8_t value = (uint8_t)lua_tounsigned(L, -1);
lua_pop(L, 1);
*addr = value;
}
lua_pushboolean(L, true);
return 1;
}
static int internal_memmove(lua_State *L) static int internal_memmove(lua_State *L)
{ {
void *dest = checkaddr(L, 1); void *dest = checkaddr(L, 1);
@ -1624,8 +2002,10 @@ static const luaL_Reg dfhack_internal_funcs[] = {
{ "getAddress", internal_getAddress }, { "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress }, { "setAddress", internal_setAddress },
{ "getVTable", internal_getVTable }, { "getVTable", internal_getVTable },
{ "adjustOffset", internal_adjustOffset },
{ "getMemRanges", internal_getMemRanges }, { "getMemRanges", internal_getMemRanges },
{ "patchMemory", internal_patchMemory }, { "patchMemory", internal_patchMemory },
{ "patchBytes", internal_patchBytes },
{ "memmove", internal_memmove }, { "memmove", internal_memmove },
{ "memcmp", internal_memcmp }, { "memcmp", internal_memcmp },
{ "memscan", internal_memscan }, { "memscan", internal_memscan },
@ -1642,6 +2022,7 @@ void OpenDFHackApi(lua_State *state)
{ {
OpenPersistent(state); OpenPersistent(state);
OpenMatinfo(state); OpenMatinfo(state);
OpenPen(state);
LuaWrapper::SetFunctionWrappers(state, dfhack_module); LuaWrapper::SetFunctionWrappers(state, dfhack_module);
OpenModule(state, "gui", dfhack_gui_module); OpenModule(state, "gui", dfhack_gui_module);

@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state,
type_identity *type, const char *msg, type_identity *type, const char *msg,
int val_index, bool perr, bool signal) int val_index, bool perr, bool signal)
{ {
std::string error = stl_sprintf(msg, type->getFullName().c_str()); std::string typestr = type ? type->getFullName() : "any pointer";
std::string error = stl_sprintf(msg, typestr.c_str());
if (signal) if (signal)
{ {
@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_
if (lua_isnil(state, val_index)) if (lua_isnil(state, val_index))
return NULL; return NULL;
if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index))
return NULL;
void *rv = get_object_internal(state, type, val_index, exact_type, false); void *rv = get_object_internal(state, type, val_index, exact_type, false);
@ -1548,6 +1551,10 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
void OpenDFHackApi(lua_State *state); void OpenDFHackApi(lua_State *state);
namespace DFHack { namespace Lua { namespace Core {
static void InitCoreContext();
}}}
lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
{ {
if (!state) if (!state)
@ -1651,6 +1658,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_dup(state); lua_dup(state);
lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
// Init core-context specific stuff before loading dfhack.lua
if (IsCoreContext(state))
Lua::Core::InitCoreContext();
// load dfhack.lua // load dfhack.lua
Require(out, state, "dfhack"); Require(out, state, "dfhack");
@ -1826,8 +1837,12 @@ void DFHack::Lua::Core::Init(color_ostream &out)
State = luaL_newstate(); State = luaL_newstate();
// Calls InitCoreContext after checking IsCoreContext
Lua::Open(out, State); Lua::Open(out, State);
}
static void Lua::Core::InitCoreContext()
{
lua_newtable(State); lua_newtable(State);
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);

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

@ -148,6 +148,11 @@ bool prefix_matches(const std::string &prefix, const std::string &key, std::stri
return false; return false;
} }
int random_int(int max)
{
return int(int64_t(rand())*max/(int64_t(RAND_MAX)+1));
}
#ifdef LINUX_BUILD // Linux #ifdef LINUX_BUILD // Linux
uint64_t GetTimeMs64() uint64_t GetTimeMs64()
{ {

@ -22,6 +22,7 @@ must not be misrepresented as being the original software.
distribution. distribution.
*/ */
#include "modules/EventManager.h"
#include "Internal.h" #include "Internal.h"
#include "Core.h" #include "Core.h"
#include "MemAccess.h" #include "MemAccess.h"
@ -211,9 +212,10 @@ bool Plugin::load(color_ostream &con)
} }
const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_name =(const char ** ) LookupPlugin(plug, "name");
const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "version");
if(!plug_name || !plug_version) Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
if(!plug_name || !plug_version || !plug_self)
{ {
con.printerr("Plugin %s has no name or version.\n", filename.c_str()); con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str());
ClosePlugin(plug); ClosePlugin(plug);
RefAutolock lock(access); RefAutolock lock(access);
state = PS_BROKEN; state = PS_BROKEN;
@ -228,6 +230,7 @@ bool Plugin::load(color_ostream &con)
state = PS_BROKEN; state = PS_BROKEN;
return false; return false;
} }
*plug_self = this;
RefAutolock lock(access); RefAutolock lock(access);
plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init"); plugin_init = (command_result (*)(color_ostream &, std::vector <PluginCommand> &)) LookupPlugin(plug, "plugin_init");
if(!plugin_init) if(!plugin_init)
@ -270,6 +273,7 @@ bool Plugin::unload(color_ostream &con)
// if we are actually loaded // if we are actually loaded
if(state == PS_LOADED) if(state == PS_LOADED)
{ {
EventManager::unregisterAll(this);
// notify the plugin about an attempt to shutdown // notify the plugin about an attempt to shutdown
if (plugin_onstatechange && if (plugin_onstatechange &&
plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND)
@ -598,6 +602,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn)
} }
PluginManager::PluginManager(Core * core) PluginManager::PluginManager(Core * core)
{
cmdlist_mutex = new mutex();
eval_ruby = NULL;
}
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
void PluginManager::init(Core * core)
{ {
#ifdef LINUX_BUILD #ifdef LINUX_BUILD
string path = core->getHackPath() + "plugins/"; string path = core->getHackPath() + "plugins/";
@ -606,8 +626,6 @@ PluginManager::PluginManager(Core * core)
string path = core->getHackPath() + "plugins\\"; string path = core->getHackPath() + "plugins\\";
const string searchstr = ".plug.dll"; const string searchstr = ".plug.dll";
#endif #endif
cmdlist_mutex = new mutex();
eval_ruby = NULL;
vector <string> filez; vector <string> filez;
getdir(path, filez); getdir(path, filez);
for(size_t i = 0; i < filez.size();i++) for(size_t i = 0; i < filez.size();i++)
@ -622,16 +640,6 @@ PluginManager::PluginManager(Core * core)
} }
} }
PluginManager::~PluginManager()
{
for(size_t i = 0; i < all_plugins.size();i++)
{
delete all_plugins[i];
}
all_plugins.clear();
delete cmdlist_mutex;
}
Plugin *PluginManager::getPluginByName (const std::string & name) Plugin *PluginManager::getPluginByName (const std::string & name)
{ {
for(size_t i = 0; i < all_plugins.size(); i++) for(size_t i = 0; i < all_plugins.size(); i++)

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

@ -155,9 +155,14 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
fclose(mapFile); fclose(mapFile);
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
return 0; return 0x8048000;
}
int Process::adjustOffset(int offset, bool /*to_file*/)
{
return offset;
} }
static int getdir (string dir, vector<string> &files) static int getdir (string dir, vector<string> &files)
@ -230,4 +235,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect);
return result==0; return result==0;
} }
// returns -1 on error
void* Process::memAlloc(const int length)
{
return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
}
int Process::memDealloc(void *ptr, const int length)
{
return munmap(ptr, length);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
if (prot & Process::MemProt::READ)
prot_native |= PROT_READ;
if (prot & Process::MemProt::WRITE)
prot_native |= PROT_WRITE;
if (prot & Process::MemProt::EXEC)
prot_native |= PROT_EXEC;
return mprotect(ptr, length, prot_native);
}

@ -160,7 +160,7 @@ Process::Process(VersionInfoFactory * factory)
identified = true; identified = true;
// give the process a data model and memory layout fixed for the base of first module // give the process a data model and memory layout fixed for the base of first module
my_descriptor = new VersionInfo(*vinfo); my_descriptor = new VersionInfo(*vinfo);
my_descriptor->rebaseTo((uint32_t)d->base); my_descriptor->rebaseTo(getBase());
for(size_t i = 0; i < threads_ids.size();i++) for(size_t i = 0; i < threads_ids.size();i++)
{ {
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]); HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]);
@ -394,13 +394,46 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
} }
} }
uint32_t Process::getBase() uintptr_t Process::getBase()
{ {
if(d) if(d)
return (uint32_t) d->base; return (uintptr_t) d->base;
return 0x400000; return 0x400000;
} }
int Process::adjustOffset(int offset, bool to_file)
{
if (!d)
return -1;
for(int i = 0; i < d->pe_header.FileHeader.NumberOfSections; i++)
{
auto &section = d->sections[i];
if (to_file)
{
unsigned delta = offset - section.VirtualAddress;
if (delta >= section.Misc.VirtualSize)
continue;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
return -1;
return (int)(section.PointerToRawData + delta);
}
else
{
unsigned delta = offset - section.PointerToRawData;
if (!section.PointerToRawData || delta >= section.SizeOfRawData)
continue;
if (delta >= section.Misc.VirtualSize)
return -1;
return (int)(section.VirtualAddress + delta);
}
}
return -1;
}
string Process::doReadClassName (void * vptr) string Process::doReadClassName (void * vptr)
{ {
char * rtti = readPtr((char *)vptr - 0x4); char * rtti = readPtr((char *)vptr - 0x4);
@ -440,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange)
return result; return result;
} }
void* Process::memAlloc(const int length)
{
void *ret;
// returns 0 on error
ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if (!ret)
ret = (void*)-1;
return ret;
}
int Process::memDealloc(void *ptr, const int length)
{
// can only free the whole region at once
// vfree returns 0 on error
return !VirtualFree(ptr, 0, MEM_RELEASE);
}
int Process::memProtect(void *ptr, const int length, const int prot)
{
int prot_native = 0;
DWORD old_prot = 0;
// only support a few constant combinations
if (prot == 0)
prot_native = PAGE_NOACCESS;
else if (prot == Process::MemProt::READ)
prot_native = PAGE_READONLY;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE))
prot_native = PAGE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READWRITE;
else if (prot == (Process::MemProt::READ | Process::MemProt::EXEC))
prot_native = PAGE_EXECUTE_READ;
else
return -1;
return !VirtualProtect(ptr, length, prot_native, &old_prot);
}

@ -100,6 +100,24 @@ bool DFHack::removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type
return false; return false;
} }
df::item *DFHack::findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getItem() : NULL;
}
df::building *DFHack::findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getBuilding() : NULL;
}
df::unit *DFHack::findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type)
{
auto ref = findRef(vec, type);
return ref ? ref->getUnit() : NULL;
}
df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type) df::specific_ref *DFHack::findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type)
{ {
for (int i = vec.size()-1; i >= 0; i--) for (int i = vec.size()-1; i >= 0; i--)

@ -39,15 +39,54 @@ using namespace DFHack;
/* /*
* Code for accessing method pointers directly. Very compiler-specific. * Code for accessing method pointers directly. Very compiler-specific.
*
* Pointers to methods in C++ are conceptually similar to pointers to
* functions, but with some complications. Specifically, the target of
* such pointer can be either:
*
* - An ordinary non-virtual method, in which case the pointer behaves
* not much differently from a simple function pointer.
* - A virtual method, in which case calling the pointer must emulate
* an ordinary call to that method, i.e. fetch the real code address
* from the vtable at the appropriate index.
*
* This means that pointers to virtual methods actually have to encode
* the relevant vtable index value in some way. Also, since these two
* types of pointers cannot be distinguished by data type and differ
* only in value, any sane compiler would ensure that any non-virtual
* method that can potentially be called via a pointer uses the same
* parameter passing rules as an equivalent virtual method, so that
* the same parameter passing code would work with both types of pointer.
*
* This means that with a few small low-level compiler-specific wrappers
* to access the data inside such pointers it is possible to:
*
* - Convert a non-virtual method pointer into a code address that
* can be directly put into a vtable.
* - Convert a pointer taken out of a vtable into a fake non-virtual
* method pointer that can be used to easily call the original
* vmethod body.
* - Extract a vtable index out of a virtual method pointer.
*
* Taken together, these features allow delegating all the difficult
* and fragile tasks like passing parameters and calculating the
* vtable index to the C++ compiler.
*/ */
#if defined(_MSC_VER) #if defined(_MSC_VER)
// MSVC may use up to 3 different representations
// based on context, but adding the /vmg /vmm options
// forces it to stick to this one. It can accomodate
// multiple, but not virtual inheritance.
struct MSVC_MPTR { struct MSVC_MPTR {
void *method; void *method;
intptr_t this_shift; intptr_t this_shift;
}; };
// Debug builds sometimes use additional thunks that
// just jump to the real one, presumably to attach some
// additional debug info.
static uint32_t *follow_jmp(void *ptr) static uint32_t *follow_jmp(void *ptr)
{ {
uint8_t *p = (uint8_t*)ptr; uint8_t *p = (uint8_t*)ptr;
@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr)
{ {
switch (*p) switch (*p)
{ {
case 0xE9: case 0xE9: // jmp near rel32
p += 5 + *(int32_t*)(p+1); p += 5 + *(int32_t*)(p+1);
break; break;
case 0xEB: case 0xEB: // jmp short rel8
p += 2 + *(int8_t*)(p+1); p += 2 + *(int8_t*)(p+1);
break; break;
default: default:
@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr)
#elif defined(__GXX_ABI_VERSION) #elif defined(__GXX_ABI_VERSION)
// GCC seems to always use this structure - possibly unless
// virtual inheritance is involved, but that's irrelevant.
struct GCC_MPTR { struct GCC_MPTR {
intptr_t method; intptr_t method; // Code pointer or tagged vtable offset
intptr_t this_shift; intptr_t this_shift;
}; };
@ -166,12 +207,12 @@ void *virtual_identity::get_vmethod_ptr(int idx)
return vtable[idx]; return vtable[idx];
} }
bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr)
{ {
assert(idx >= 0); assert(idx >= 0);
void **vtable = (void**)vtable_ptr; void **vtable = (void**)vtable_ptr;
if (!vtable) return NULL; if (!vtable) return NULL;
return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); return patcher.write(&vtable[idx], &ptr, sizeof(void*));
} }
/* /*
@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v
{ {
if (vmethod_idx < 0 || interpose_method == NULL) if (vmethod_idx < 0 || interpose_method == NULL)
{ {
/*
* A failure here almost certainly means a problem in one
* of the pointer-to-method access wrappers above:
*
* - vmethod_idx comes from vmethod_pointer_to_idx_
* - interpose_method comes from method_pointer_to_addr_
*/
fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n", fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n",
vmethod_idx, unsigned(interpose_method)); vmethod_idx, unsigned(interpose_method));
fflush(stderr); fflush(stderr);
@ -344,7 +393,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
auto last = this; auto last = this;
while (last->prev) last = last->prev; while (last->prev) last = last->prev;
from->set_vmethod_ptr(vmethod_idx, last->saved_chain); MemoryPatcher patcher;
from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain);
// Unlink the chains // Unlink the chains
child_hosts.erase(from); child_hosts.erase(from);
@ -379,13 +430,15 @@ bool VMethodInterposeLinkBase::apply(bool enable)
assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr));
// Apply the new method ptr // Apply the new method ptr
MemoryPatcher patcher;
set_chain(old_ptr); set_chain(old_ptr);
if (next_link) if (next_link)
{ {
next_link->set_chain(interpose_method); next_link->set_chain(interpose_method);
} }
else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method))
{ {
set_chain(NULL); set_chain(NULL);
return false; return false;
@ -459,7 +512,7 @@ bool VMethodInterposeLinkBase::apply(bool enable)
{ {
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == old_link); assert(nhost->interpose_list[vmethod_idx] == old_link);
nhost->set_vmethod_ptr(vmethod_idx, interpose_method); nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method);
nhost->interpose_list[vmethod_idx] = this; nhost->interpose_list[vmethod_idx] = this;
} }
@ -496,9 +549,11 @@ void VMethodInterposeLinkBase::remove()
} }
else else
{ {
MemoryPatcher patcher;
// Remove from the list in the identity and vtable // Remove from the list in the identity and vtable
host->interpose_list[vmethod_idx] = prev; host->interpose_list[vmethod_idx] = prev;
host->set_vmethod_ptr(vmethod_idx, saved_chain); host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
for (auto it = child_next.begin(); it != child_next.end(); ++it) for (auto it = child_next.begin(); it != child_next.end(); ++it)
{ {
@ -515,7 +570,7 @@ void VMethodInterposeLinkBase::remove()
auto nhost = *it; auto nhost = *it;
assert(nhost->interpose_list[vmethod_idx] == this); assert(nhost->interpose_list[vmethod_idx] == this);
nhost->interpose_list[vmethod_idx] = prev; nhost->interpose_list[vmethod_idx] = prev;
nhost->set_vmethod_ptr(vmethod_idx, saved_chain); nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain);
if (prev) if (prev)
prev->child_hosts.insert(nhost); prev->child_hosts.insert(nhost);
} }

@ -103,13 +103,13 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
{ {
mem->setOS(OS_LINUX); mem->setOS(OS_LINUX);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x8048000);
} }
else if(os == "darwin") else if(os == "darwin")
{ {
mem->setOS(OS_APPLE); mem->setOS(OS_APPLE);
// this is wrong... I'm not going to do base image relocation on linux though. // this is wrong... I'm not going to do base image relocation on linux though.
mem->setBase(0x0); mem->setBase(0x1000000);
} }
else else
{ {

@ -171,8 +171,9 @@ BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len)
state |= Applied; state |= Applied;
else else
{ {
cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val cerr << std::hex << bv.offset << ": "
<< ", but currently " << cv << std::dec << endl; << unsigned(bv.old_val) << " " << unsigned(bv.new_val)
<< ", but currently " << unsigned(cv) << std::dec << endl;
return Conflict; return Conflict;
} }
} }

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

@ -61,7 +61,6 @@ distribution.
#include "modules/Translation.h" #include "modules/Translation.h"
#include "modules/World.h" #include "modules/World.h"
#include "modules/Items.h" #include "modules/Items.h"
#include "modules/Vegetation.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/Gui.h" #include "modules/Gui.h"

@ -35,7 +35,7 @@ distribution.
// Stop some MS stupidity // Stop some MS stupidity
#ifdef interface #ifdef interface
#undef interface #undef interface
#endif #endif
typedef struct lua_State lua_State; typedef struct lua_State lua_State;
@ -294,6 +294,7 @@ namespace DFHack
#endif #endif
class DFHACK_EXPORT VMethodInterposeLinkBase; class DFHACK_EXPORT VMethodInterposeLinkBase;
class MemoryPatcher;
class DFHACK_EXPORT virtual_identity : public struct_identity { class DFHACK_EXPORT virtual_identity : public struct_identity {
static std::map<void*, virtual_identity*> known; static std::map<void*, virtual_identity*> known;
@ -313,7 +314,7 @@ namespace DFHack
bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); }
void *get_vmethod_ptr(int index); void *get_vmethod_ptr(int index);
bool set_vmethod_ptr(int index, void *ptr); bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr);
public: public:
virtual_identity(size_t size, TAllocateFn alloc, virtual_identity(size_t size, TAllocateFn alloc,

@ -41,6 +41,9 @@ namespace DFHack {
namespace Units { namespace Units {
struct NoblePosition; struct NoblePosition;
} }
namespace Screen {
struct Pen;
};
} }
namespace DFHack {namespace Lua { namespace DFHack {namespace Lua {
@ -285,6 +288,7 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj);
void Push(lua_State *state, const Units::NoblePosition &pos); void Push(lua_State *state, const Units::NoblePosition &pos);
DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info);
DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info);
template<class T> inline void Push(lua_State *state, T *ptr) { template<class T> inline void Push(lua_State *state, T *ptr) {
PushDFObject(state, ptr); PushDFObject(state, ptr);
} }
@ -315,6 +319,8 @@ namespace DFHack {namespace Lua {
DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos);
DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos);
DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true);
DFHACK_EXPORT bool IsCoreContext(lua_State *state); DFHACK_EXPORT bool IsCoreContext(lua_State *state);
namespace Event { namespace Event {
@ -474,3 +480,18 @@ namespace DFHack {namespace Lua {
name##_event.invoke(out, 5); \ name##_event.invoke(out, 5); \
} \ } \
} }
#define DEFINE_LUA_EVENT_6(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5,arg_type6) \
static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \
void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4,arg_type5 arg5, arg_type6 arg6) { \
handler(out, arg1, arg2, arg3, arg4, arg5, arg6); \
if (auto state = name##_event.state_if_count()) { \
DFHack::Lua::Push(state, arg1); \
DFHack::Lua::Push(state, arg2); \
DFHack::Lua::Push(state, arg3); \
DFHack::Lua::Push(state, arg4); \
DFHack::Lua::Push(state, arg5); \
DFHack::Lua::Push(state, arg6); \
name##_event.invoke(out, 6); \
} \
}

@ -275,11 +275,13 @@ namespace DFHack
{ {
return my_descriptor; return my_descriptor;
}; };
uint32_t getBase(); uintptr_t getBase();
/// get the DF Process ID /// get the DF Process ID
int getPID(); int getPID();
/// get the DF Process FilePath /// get the DF Process FilePath
std::string getPath(); std::string getPath();
/// Adjust between in-memory and in-file image offset
int adjustOffset(int offset, bool to_file = false);
/// millisecond tick count, exactly as DF uses /// millisecond tick count, exactly as DF uses
uint32_t getTickCount(); uint32_t getTickCount();
@ -289,6 +291,27 @@ namespace DFHack
/// write a possibly read-only memory area /// write a possibly read-only memory area
bool patchMemory(void *target, const void* src, size_t count); bool patchMemory(void *target, const void* src, size_t count);
/// allocate new memory pages for code or stuff
/// returns -1 on error (0 is a valid address)
void* memAlloc(const int length);
/// free memory pages from memAlloc
/// should have length = alloced length for portability
/// returns 0 on success
int memDealloc(void *ptr, const int length);
/// change memory page permissions
/// prot is a bitwise OR of the MemProt enum
/// returns 0 on success
int memProtect(void *ptr, const int length, const int prot);
enum MemProt {
READ = 1,
WRITE = 2,
EXEC = 4
};
private: private:
VersionInfo * my_descriptor; VersionInfo * my_descriptor;
PlatformSpecific *d; PlatformSpecific *d;
@ -315,5 +338,22 @@ namespace DFHack
// Get list of names given to ClassNameCheck constructors. // Get list of names given to ClassNameCheck constructors.
static void getKnownClassNames(std::vector<std::string> &names); static void getKnownClassNames(std::vector<std::string> &names);
}; };
class DFHACK_EXPORT MemoryPatcher
{
Process *p;
std::vector<t_memrange> ranges, save;
public:
MemoryPatcher(Process *p = NULL);
~MemoryPatcher();
bool verifyAccess(void *target, size_t size, bool write = false);
bool makeWritable(void *target, size_t size) {
return verifyAccess(target, size, true);
}
bool write(void *target, const void *src, size_t size);
void close();
};
} }
#endif #endif

@ -331,6 +331,8 @@ inline T clip_range(T a, T1 minv, T2 maxv) {
return a; return a;
} }
DFHACK_EXPORT int random_int(int max);
/** /**
* Returns the amount of milliseconds elapsed since the UNIX epoch. * Returns the amount of milliseconds elapsed since the UNIX epoch.
* Works on both windows and linux. * Works on both windows and linux.

@ -33,7 +33,6 @@ namespace DFHack
Module* createGui(); Module* createGui();
Module* createWorld(); Module* createWorld();
Module* createMaterials(); Module* createMaterials();
Module* createVegetation();
Module* createNotes(); Module* createNotes();
Module* createGraphic(); Module* createGraphic();
} }

@ -205,6 +205,7 @@ namespace DFHack
friend class Plugin; friend class Plugin;
PluginManager(Core * core); PluginManager(Core * core);
~PluginManager(); ~PluginManager();
void init(Core* core);
void OnUpdate(color_ostream &out); void OnUpdate(color_ostream &out);
void OnStateChange(color_ostream &out, state_change_event event); void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
@ -246,7 +247,8 @@ namespace DFHack
/// You have to have this in every plugin you write - just once. Ideally on top of the main file. /// You have to have this in every plugin you write - just once. Ideally on top of the main file.
#define DFHACK_PLUGIN(plugin_name) \ #define DFHACK_PLUGIN(plugin_name) \
DFhackDataExport const char * version = DFHACK_VERSION;\ DFhackDataExport const char * version = DFHACK_VERSION;\
DFhackDataExport const char * name = plugin_name; DFhackDataExport const char * name = plugin_name;\
DFhackDataExport Plugin *plugin_self = NULL;
#define DFHACK_PLUGIN_LUA_COMMANDS \ #define DFHACK_PLUGIN_LUA_COMMANDS \
DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = DFhackCExport const DFHack::CommandReg plugin_lua_commands[] =

@ -41,7 +41,7 @@ namespace DFHack
struct struct
{ {
//Maybe should add 'up' and 'down' for Z-levels? //Maybe should add 'up' and 'down' for Z-levels?
unsigned char north,south,west,east; unsigned char north,south,west,east;
}; };
inline TileDirection() inline TileDirection()
@ -203,6 +203,12 @@ namespace DFHack
return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype));
} }
inline
bool FlowPassableDown(df::tiletype tiletype)
{
return ENUM_ATTR(tiletype_shape, passable_flow_down, tileShape(tiletype));
}
inline inline
bool isWalkable(df::tiletype tiletype) bool isWalkable(df::tiletype tiletype)
{ {

@ -74,12 +74,38 @@ namespace DFHack
uint32_t xpNxtLvl; uint32_t xpNxtLvl;
}; };
typedef std::pair<df::coord2d, df::coord2d> rect2d;
inline rect2d intersect(rect2d a, rect2d b) {
df::coord2d g1 = a.first, g2 = a.second;
df::coord2d c1 = b.first, c2 = b.second;
df::coord2d rc1 = df::coord2d(std::max(g1.x, c1.x), std::max(g1.y, c1.y));
df::coord2d rc2 = df::coord2d(std::min(g2.x, c2.x), std::min(g2.y, c2.y));
return rect2d(rc1, rc2);
}
inline rect2d mkrect_xy(int x1, int y1, int x2, int y2) {
return rect2d(df::coord2d(x1, y1), df::coord2d(x2, y2));
}
inline rect2d mkrect_wh(int x, int y, int w, int h) {
return rect2d(df::coord2d(x, y), df::coord2d(x+w-1, y+h-1));
}
inline df::coord2d rect_size(const rect2d &rect) {
return rect.second - rect.first + df::coord2d(1,1);
}
DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files); DFHACK_EXPORT int getdir(std::string dir, std::vector<std::string> &files);
DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending);
DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type); DFHACK_EXPORT df::general_ref *findRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id); DFHACK_EXPORT bool removeRef(std::vector<df::general_ref*> &vec, df::general_ref_type type, int id);
DFHACK_EXPORT df::item *findItemRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::building *findBuildingRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::unit *findUnitRef(std::vector<df::general_ref*> &vec, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type); DFHACK_EXPORT df::specific_ref *findRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type);
DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr); DFHACK_EXPORT bool removeRef(std::vector<df::specific_ref*> &vec, df::specific_ref_type type, void *ptr);
}// namespace DFHack }// namespace DFHack

@ -28,6 +28,58 @@ distribution.
namespace DFHack namespace DFHack
{ {
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
Important caveat:
This will NOT intercept calls to the superclass vmethod
from overriding vmethod bodies in subclasses, i.e. whenever
DF code contains something like this, the call to "superclass::foo()"
doesn't actually use vtables, and thus will never trigger any hooks:
class superclass { virtual foo() { ... } };
class subclass : superclass { virtual foo() { ... superclass::foo(); ... } };
The only workaround is to implement and apply a second hook for subclass::foo,
and repeat that for any other subclasses and sub-subclasses that override this
vmethod.
*/
template<bool> struct StaticAssert; template<bool> struct StaticAssert;
template<> struct StaticAssert<true> {}; template<> struct StaticAssert<true> {};
@ -81,43 +133,6 @@ namespace DFHack
return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx)); return addr_to_method_pointer<P>(identity.get_vmethod_ptr(idx));
} }
/* VMethod interpose API.
This API allows replacing an entry in the original vtable
with code defined by DFHack, while retaining ability to
call the original code. The API can be safely used from
plugins, and multiple hooks for the same vmethod are
automatically chained (subclass before superclass; at same
level highest priority called first; undefined order otherwise).
Usage:
struct my_hack : df::someclass {
typedef df::someclass interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) {
// If needed by the code, claim the suspend lock.
// DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK!
// CoreSuspendClaimer suspend;
...
INTERPOSE_NEXT(foo)(arg) // call the original
...
}
};
IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo);
or
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority);
void init() {
if (!INTERPOSE_HOOK(my_hack, foo).apply())
error();
}
void shutdown() {
INTERPOSE_HOOK(my_hack, foo).remove();
}
*/
#define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \ #define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \
typedef rtype (interpose_base::*interpose_ptr_##name)args; \ typedef rtype (interpose_base::*interpose_ptr_##name)args; \
@ -142,18 +157,21 @@ namespace DFHack
friend class virtual_identity; friend class virtual_identity;
virtual_identity *host; // Class with the vtable virtual_identity *host; // Class with the vtable
int vmethod_idx; int vmethod_idx; // Index of the interposed method in the vtable
void *interpose_method; // Pointer to the code of the interposing method void *interpose_method; // Pointer to the code of the interposing method
void *chain_mptr; // Pointer to the chain field below void *chain_mptr; // Pointer to the chain field in the subclass below
int priority; int priority; // Higher priority hooks are called earlier
bool applied; bool applied; // True if this hook is currently applied
void *saved_chain; // Previous pointer to the code void *saved_chain; // Pointer to the code of the original vmethod or next hook
VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method
// inherited vtable members // Chain of hooks within the same host
VMethodInterposeLinkBase *next, *prev;
// Subclasses that inherit this topmost hook directly
std::set<virtual_identity*> child_hosts; std::set<virtual_identity*> child_hosts;
// Hooks within subclasses that branch off this topmost hook
std::set<VMethodInterposeLinkBase*> child_next; std::set<VMethodInterposeLinkBase*> child_next;
// (See the cpp file for a more detailed description of these links)
void set_chain(void *chain); void set_chain(void *chain);
void on_host_delete(virtual_identity *host); void on_host_delete(virtual_identity *host);
@ -172,6 +190,9 @@ namespace DFHack
template<class Base, class Ptr> template<class Base, class Ptr>
class VMethodInterposeLink : public VMethodInterposeLinkBase { class VMethodInterposeLink : public VMethodInterposeLinkBase {
public: public:
// Exactly the same as the saved_chain field of superclass,
// but converted to the appropriate pointer-to-method type.
// Kept up to date via the chain_mptr pointer.
Ptr chain; Ptr chain;
operator Ptr () { return chain; } operator Ptr () { return chain; }

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

@ -25,7 +25,9 @@ distribution.
#pragma once #pragma once
#include "Export.h" #include "Export.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "Types.h"
#include "df/building.h" #include "df/building.h"
#include "df/building_type.h"
#include "df/civzone_type.h" #include "df/civzone_type.h"
#include "df/furnace_type.h" #include "df/furnace_type.h"
#include "df/workshop_type.h" #include "df/workshop_type.h"
@ -92,6 +94,9 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building);
*/ */
DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes); DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map <uint32_t, std::string> & btypes);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::building *building, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::building *building, df::specific_ref_type type);
/** /**
* Sets the owner unit for the building. * Sets the owner unit for the building.
*/ */
@ -178,5 +183,8 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector<df::job_i
*/ */
DFHACK_EXPORT bool deconstruct(df::building *bld); DFHACK_EXPORT bool deconstruct(df::building *bld);
void updateBuildings(color_ostream& out, void* ptr);
void clearBuildings(color_ostream& out);
} }
} }

@ -0,0 +1,60 @@
#pragma once
#ifndef EVENT_MANAGER_H_INCLUDED
#define EVENT_MANAGER_H_INCLUDED
#include "Core.h"
#include "Export.h"
#include "ColorText.h"
#include "PluginManager.h"
#include "Console.h"
namespace DFHack {
namespace EventManager {
namespace EventType {
enum EventType {
TICK,
JOB_INITIATED,
JOB_COMPLETED,
UNIT_DEATH,
ITEM_CREATED,
BUILDING,
CONSTRUCTION,
SYNDROME,
INVASION,
EVENT_MAX
};
}
struct EventHandler {
void (*eventHandler)(color_ostream&, void*); //called when the event happens
int32_t freq;
EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) {
}
bool operator==(EventHandler& handle) const {
return eventHandler == handle.eventHandler && freq == handle.freq;
}
bool operator!=(EventHandler& handle) const {
return !( *this == handle);
}
};
struct SyndromeData {
int32_t unitId;
int32_t syndromeIndex;
SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) {
}
};
DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false);
DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin);
DFHACK_EXPORT void unregisterAll(Plugin* plugin);
void manageEvents(color_ostream& out);
void onStateChange(color_ostream& out, state_change_event event);
}
}
#endif

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

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

@ -151,6 +151,11 @@ DFHACK_EXPORT df::item *getContainer(df::item *item);
/// which items does it contain? /// which items does it contain?
DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items); DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector<df::item*> *items);
/// which building holds it?
DFHACK_EXPORT df::building *getHolderBuilding(df::item *item);
/// which unit holds it?
DFHACK_EXPORT df::unit *getHolderUnit(df::item *item);
/// Returns the true position of the item. /// Returns the true position of the item.
DFHACK_EXPORT df::coord getPosition(df::item *item); DFHACK_EXPORT df::coord getPosition(df::item *item);

@ -28,6 +28,8 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "Types.h"
#include <ostream> #include <ostream>
#include "DataDefs.h" #include "DataDefs.h"
@ -47,7 +49,7 @@ namespace DFHack
{ {
namespace Job { namespace Job {
// Duplicate the job structure. It is not linked into any DF lists. // Duplicate the job structure. It is not linked into any DF lists.
DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false);
// Delete a cloned structure. // Delete a cloned structure.
DFHACK_EXPORT void deleteJobStruct(df::job *job); DFHACK_EXPORT void deleteJobStruct(df::job *job);
@ -55,6 +57,9 @@ namespace DFHack
DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx); DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx);
DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::job *job, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::job *job, df::specific_ref_type type);
DFHACK_EXPORT df::building *getHolder(df::job *job); DFHACK_EXPORT df::building *getHolder(df::job *job);
DFHACK_EXPORT df::unit *getWorker(df::job *job); DFHACK_EXPORT df::unit *getWorker(df::job *job);

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

@ -32,7 +32,6 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "modules/Vegetation.h"
#include <vector> #include <vector>
#include "BitArray.h" #include "BitArray.h"
#include "modules/Materials.h" #include "modules/Materials.h"
@ -309,6 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which );
DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2);
} }
} }
#endif #endif

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

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

@ -205,6 +205,9 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target);
/// Returns the true position of the unit (non-trivial in case of caged). /// Returns the true position of the unit (non-trivial in case of caged).
DFHACK_EXPORT df::coord getPosition(df::unit *unit); DFHACK_EXPORT df::coord getPosition(df::unit *unit);
DFHACK_EXPORT df::general_ref *getGeneralRef(df::unit *unit, df::general_ref_type type);
DFHACK_EXPORT df::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type);
DFHACK_EXPORT df::item *getContainer(df::unit *unit); DFHACK_EXPORT df::item *getContainer(df::unit *unit);
DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick);
@ -235,6 +238,8 @@ DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {

@ -1,70 +0,0 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#ifndef CL_MOD_VEGETATION
#define CL_MOD_VEGETATION
/**
* \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves
* @ingroup grp_modules
*/
#include "Export.h"
#include "DataDefs.h"
#include "df/plant.h"
namespace DFHack
{
namespace Vegetation
{
const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years
// "Simplified" copy of plant
struct t_plant {
df::language_name name;
df::plant_flags flags;
int16_t material;
df::coord pos;
int32_t grow_counter;
uint16_t temperature_1;
uint16_t temperature_2;
int32_t is_burning;
int32_t hitpoints;
int16_t update_order;
//std::vector<void *> unk1;
//int32_t unk2;
//uint16_t temperature_3;
//uint16_t temperature_4;
//uint16_t temperature_5;
// Pointer to original object, in case you want to modify it
df::plant *origin;
};
DFHACK_EXPORT bool isValid();
DFHACK_EXPORT uint32_t getCount();
DFHACK_EXPORT df::plant * getPlant(const int32_t index);
DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out);
}
}
#endif

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

@ -0,0 +1,121 @@
-- Simple binary patch with IDA dif file support.
local function load_patch(name)
local filename = name
if not string.match(filename, '[./\\]') then
filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif'
end
local file, err = io.open(filename, 'r')
if not file then
if string.match(err, ': No such file or directory') then
return nil, 'patch not found'
end
end
local old_bytes = {}
local new_bytes = {}
for line in file:lines() do
if string.match(line, '^%x+:') then
local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$')
if not offset then
file:close()
return nil, 'could not parse: '..line
end
offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16)
if oldv > 255 or newv > 255 then
file:close()
return nil, 'invalid byte values: '..line
end
old_bytes[offset] = oldv
new_bytes[offset] = newv
end
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end
local function rebase_table(input)
local output = {}
local base = dfhack.internal.getImageBase()
for k,v in pairs(input) do
local offset = dfhack.internal.adjustOffset(k)
if not offset then
return nil, string.format('invalid offset: %x', k)
end
output[base + offset] = v
end
return output
end
local function rebase_patch(patch)
local nold, err = rebase_table(patch.old_bytes)
if not nold then return nil, err end
local nnew, err = rebase_table(patch.new_bytes)
if not nnew then return nil, err end
return { name = patch.name, old_bytes = nold, new_bytes = nnew }
end
BinaryPatch = defclass(BinaryPatch)
BinaryPatch.ATTRS {
name = DEFAULT_NIL,
old_bytes = DEFAULT_NIL,
new_bytes = DEFAULT_NIL,
}
function load_dif_file(name)
local patch, err = load_patch(name)
if not patch then return nil, err end
local rpatch, err = rebase_patch(patch)
if not rpatch then return nil, err end
return BinaryPatch(rpatch)
end
function BinaryPatch:status()
local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes)
if old_ok then
return 'removed'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return 'applied'
else
return 'conflict', addr
end
end
function BinaryPatch:isApplied()
return dfhack.internal.patchBytes({}, self.new_bytes)
end
function BinaryPatch:apply()
local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes)
if ok then
return true, 'applied the patch'
elseif dfhack.internal.patchBytes({}, self.new_bytes) then
return true, 'patch is already applied'
else
return false, string.format('conflict at address %x', addr)
end
end
function BinaryPatch:isRemoved()
return dfhack.internal.patchBytes({}, self.old_bytes)
end
function BinaryPatch:remove()
local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes)
if ok then
return true, 'removed the patch'
elseif dfhack.internal.patchBytes({}, self.old_bytes) then
return true, 'patch is already removed'
else
return false, string.format('conflict at address %x', addr)
end
end
return _ENV

@ -65,10 +65,14 @@ end
local function apply_attrs(obj, attrs, init_table) local function apply_attrs(obj, attrs, init_table)
for k,v in pairs(attrs) do for k,v in pairs(attrs) do
if v == DEFAULT_NIL then local init_v = init_table[k]
v = nil if init_v ~= nil then
obj[k] = init_v
elseif v == DEFAULT_NIL then
obj[k] = nil
else
obj[k] = v
end end
obj[k] = init_table[k] or v
end end
end end

@ -328,9 +328,11 @@ end
-- Command scripts -- Command scripts
dfhack.internal.scripts = dfhack.internal.scripts or {} local internal = dfhack.internal
local scripts = dfhack.internal.scripts internal.scripts = internal.scripts or {}
local scripts = internal.scripts
local hack_path = dfhack.getHackPath() local hack_path = dfhack.getHackPath()
function dfhack.run_script(name,...) function dfhack.run_script(name,...)
@ -349,5 +351,42 @@ function dfhack.run_script(name,...)
return f(...) return f(...)
end end
-- Per-save init file
function dfhack.getSavePath()
if dfhack.isWorldLoaded() then
return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir
end
end
if dfhack.is_core_context then
dfhack.onStateChange.DFHACK_PER_SAVE = function(op)
if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then
if internal.save_init then
if internal.save_init.onUnload then
safecall(internal.save_init.onUnload)
end
internal.save_init = nil
end
local path = dfhack.getSavePath()
if path and op == SC_WORLD_LOADED then
local env = setmetatable({ SAVE_PATH = path }, { __index = base_env })
local f,perr = loadfile(path..'/raw/init.lua', 't', env)
if f == nil then
if not string.match(perr, 'No such file or directory') then
dfhack.printerr(perr)
end
elseif safecall(f) then
internal.save_init = env
end
end
elseif internal.save_init and internal.save_init.onStateChange then
safecall(internal.save_init.onStateChange, op)
end
end
end
-- Feed the table back to the require() mechanism. -- Feed the table back to the require() mechanism.
return dfhack return dfhack

@ -334,7 +334,22 @@ local trap_inputs = {
}, },
[df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } } [df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } }
} }
local siegeengine_input = {
[df.siegeengine_type.Catapult] = {
{
item_type=df.item_type.CATAPULTPARTS,
vector_id=df.job_item_vector_id.CATAPULTPARTS,
quantity=3
}
},
[df.siegeengine_type.Ballista] = {
{
item_type=df.item_type.BALLISTAPARTS,
vector_id=df.job_item_vector_id.BALLISTAPARTS,
quantity=3
}
},
}
--[[ Functions for lookup in tables. ]] --[[ Functions for lookup in tables. ]]
local function get_custom_inputs(custom) local function get_custom_inputs(custom)
@ -359,6 +374,8 @@ local function get_inputs_by_type(type,subtype,custom)
end end
elseif type == df.building_type.Trap then elseif type == df.building_type.Trap then
return trap_inputs[subtype] return trap_inputs[subtype]
elseif type == df.building_type.SiegeEngine then
return siegeengine_input[subtype]
else else
return building_inputs[type] return building_inputs[type]
end end

@ -0,0 +1,591 @@
local _ENV = mkmodule('dfhack.workshops')
local utils = require 'utils'
input_filter_defaults = {
item_type = -1,
item_subtype = -1,
mat_type = -1,
mat_index = -1,
flags1 = {},
-- Instead of noting those that allow artifacts, mark those that forbid them.
-- Leaves actually enabling artifacts to the discretion of the API user,
-- which is the right thing because unlike the game UI these filters are
-- used in a way that does not give the user a chance to choose manually.
flags2 = { allow_artifact = true },
flags3 = {},
flags4 = 0,
flags5 = 0,
reaction_class = '',
has_material_reaction_product = '',
metal_ore = -1,
min_dimension = -1,
has_tool_use = -1,
quantity = 1
}
local fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL}
jobs_furnace={
[df.furnace_type.Smelter]={
{
name="Melt metal object",
items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
[df.furnace_type.MagmaSmelter]={
{
name="Melt metal object",
items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated
job_fields={job_type=df.job_type.MeltMetalObject}
}
},
--[[ [df.furnace_type.MetalsmithsForge]={
unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask))
},
]]
--MetalsmithsForge,
--MagmaForge
--[[
forges:
weapons and ammo-> from raws...
armor -> raws
furniture -> builtins?
siege eq-> builtin (only balista head)
trap eq -> from raws+ mechanisms
other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron
metal clothing-> raws???
]]
[df.furnace_type.GlassFurnace]={
{
name="collect sand",
items={},
job_fields={job_type=df.job_type.CollectSand}
},
--glass crafts x3
},
[df.furnace_type.WoodFurnace]={
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make charcoal",
items={{}},
job_fields={job_type=df.job_type.MakeCharcoal}
},
{
name="make ash",
items={{}},
job_fields={job_type=df.job_type.MakeAsh}
}
},
[df.furnace_type.Kiln]={
{
name="collect clay",
items={},
job_fields={job_type=df.job_type.CollectClay}
}
},
}
jobs_workshop={
[df.workshop_type.Jewelers]={
{
name="cut gems",
items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.CutGems}
},
{
name="encrust finished goods with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust ammo with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
{
name="encrust furniture with gems",
items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}},
job_fields={job_type=df.job_type.EncrustWithGems}
},
},
[df.workshop_type.Fishery]={
{
name="prepare raw fish",
items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}},
job_fields={job_type=df.job_type.PrepareRawFish}
},
{
name="extract from raw fish",
items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromRawFish}
},
{
name="catch live fish",
items={},
job_fields={job_type=df.job_type.CatchLiveFish}
}, -- no items?
},
[df.workshop_type.Still]={
{
name="brew drink",
items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}},
job_fields={job_type=df.job_type.BrewDrink}
},
{
name="extract from plants",
items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}},
job_fields={job_type=df.job_type.ExtractFromPlants}
},
--mead from raws?
},
[df.workshop_type.Masons]={
defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct slab",
items={{}},
job_fields={job_type=df.job_type.ConstructSlab}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct quern",
items={{}},
job_fields={job_type=df.job_type.ConstructQuern}
},
{
name="construct millstone",
items={{}},
job_fields={job_type=df.job_type.ConstructMillstone}
},
},
[df.workshop_type.Carpenters]={
--training weapons, wooden shields
defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD},
{
name="make barrel",
items={{}},
job_fields={job_type=df.job_type.MakeBarrel}
},
{
name="make bucket",
items={{}},
job_fields={job_type=df.job_type.MakeBucket}
},
{
name="make animal trap",
items={{}},
job_fields={job_type=df.job_type.MakeAnimalTrap}
},
{
name="make cage",
items={{}},
job_fields={job_type=df.job_type.MakeCage}
},
{
name="construct bed",
items={{}},
job_fields={job_type=df.job_type.ConstructBed}
},
{
name="construct bin",
items={{}},
job_fields={job_type=df.job_type.ConstructBin}
},
{
name="construct armor stand",
items={{}},
job_fields={job_type=df.job_type.ConstructArmorStand}
},
{
name="construct blocks",
items={{}},
job_fields={job_type=df.job_type.ConstructBlocks}
},
{
name="construct throne",
items={{}},
job_fields={job_type=df.job_type.ConstructThrone}
},
{
name="construct coffin",
items={{}},
job_fields={job_type=df.job_type.ConstructCoffin}
},
{
name="construct door",
items={{}},
job_fields={job_type=df.job_type.ConstructDoor}
},
{
name="construct floodgate",
items={{}},
job_fields={job_type=df.job_type.ConstructFloodgate}
},
{
name="construct hatch cover",
items={{}},
job_fields={job_type=df.job_type.ConstructHatchCover}
},
{
name="construct grate",
items={{}},
job_fields={job_type=df.job_type.ConstructGrate}
},
{
name="construct cabinet",
items={{}},
job_fields={job_type=df.job_type.ConstructCabinet}
},
{
name="construct chest",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct statue",
items={{}},
job_fields={job_type=df.job_type.ConstructStatue}
},
{
name="construct table",
items={{}},
job_fields={job_type=df.job_type.ConstructTable}
},
{
name="construct weapon rack",
items={{}},
job_fields={job_type=df.job_type.ConstructWeaponRack}
},
{
name="construct splint",
items={{}},
job_fields={job_type=df.job_type.ConstructSplint}
},
{
name="construct crutch",
items={{}},
job_fields={job_type=df.job_type.ConstructCrutch}
},
},
[df.workshop_type.Kitchen]={
--mat_type=2,3,4
defaults={flags1={unrotten=true,cookable=true}},
{
name="prepare easy meal",
items={{flags1={solid=true}},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=2}
},
{
name="prepare fine meal",
items={{flags1={solid=true}},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=3}
},
{
name="prepare lavish meal",
items={{flags1={solid=true}},{},{},{}},
job_fields={job_type=df.job_type.PrepareMeal,mat_type=4}
},
},
[df.workshop_type.Butchers]={
{
name="butcher an animal",
items={{flags1={butcherable=true,unrotten=true,nearby=true}}},
job_fields={job_type=df.job_type.ButcherAnimal}
},
{
name="extract from land animal",
items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}},
job_fields={job_type=df.job_type.ExtractFromLandAnimal}
},
{
name="catch live land animal",
items={},
job_fields={job_type=df.job_type.CatchLiveLandAnimal}
},
},
[df.workshop_type.Mechanics]={
{
name="construct mechanisms",
items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,
flags3={hard=true}}},
job_fields={job_type=df.job_type.ConstructMechanisms}
},
{
name="construct traction bench",
items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}},
job_fields={job_type=df.job_type.ConstructTractionBench}
},
},
[df.workshop_type.Loom]={
{
name="weave plant thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave silk thread cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave yarn cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="weave inorganic cloth",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}},
job_fields={job_type=df.job_type.WeaveCloth}
},
{
name="collect webs",
items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}},
job_fields={job_type=df.job_type.CollectWebs}
},
},
[df.workshop_type.Leatherworks]={
defaults={item_type=SKIN_TANNED},
{
name="construct leather bag",
items={{}},
job_fields={job_type=df.job_type.ConstructChest}
},
{
name="construct waterskin",
items={{}},
job_fields={job_type=df.job_type.MakeFlask}
},
{
name="construct backpack",
items={{}},
job_fields={job_type=df.job_type.MakeBackpack}
},
{
name="construct quiver",
items={{}},
job_fields={job_type=df.job_type.MakeQuiver}
},
{
name="sew leather image",
items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}},
job_fields={job_type=df.job_type.SewImage}
},
},
[df.workshop_type.Dyers]={
{
name="dye thread",
items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
{
name="dye cloth",
items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}},
{flags1={unrotten=true},flags2={dye=true}}},
job_fields={job_type=df.job_type.DyeThread}
},
},
[df.workshop_type.Siege]={
{
name="construct balista parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructBallistaParts}
},
{
name="construct catapult parts",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.ConstructCatapultParts}
},
{
name="assemble balista arrow",
items={{item_type=df.item_type.WOOD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
{
name="assemble tipped balista arrow",
items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}},
job_fields={job_type=df.job_type.AssembleSiegeAmmo}
},
},
}
local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2)
if bid1~=-1 and bid2~=-1 and bid1~=bid2 then
return false
end
if wid1~=-1 and wid2~=-1 and wid1~=wid2 then
return false
end
if cid1~=-1 and cid2~=-1 and cid1~=cid2 then
return false
end
return true
end
local function scanRawsReaction(buildingId,workshopId,customId)
local ret={}
for idx,reaction in ipairs(df.global.world.raws.reactions) do
for k,v in pairs(reaction.building.type) do
if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then
table.insert(ret,reaction)
end
end
end
return ret
end
local function reagentToJobItem(reagent,react_id,reagentId)
local ret_item
ret_item=utils.clone_with_default(reagent, input_filter_defaults)
ret_item.reaction_id=react_id
ret_item.reagent_index=reagentId
return ret_item
end
local function addReactionJobs(ret,bid,wid,cid)
local reactions=scanRawsReaction(bid,wid or -1,cid or -1)
for idx,react in pairs(reactions) do
local job={name=react.name,
items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code}
}
for reagentId,reagent in pairs(react.reagents) do
table.insert(job.items,reagentToJobItem(reagent,idx,reagentId))
end
if react.flags.FUEL then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
end
local function scanRawsOres()
local ret={}
for idx,ore in ipairs(df.global.world.raws.inorganics) do
if #ore.metal_ore.mat_index~=0 then
ret[idx]=ore
end
end
return ret
end
local function addSmeltJobs(ret,use_fuel)
local ores=scanRawsOres()
for idx,ore in pairs(ores) do
print("adding:",ore.material.state_name.Solid)
printall(ore)
local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={
{item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}}
if use_fuel then
table.insert(job.items,fuel)
end
table.insert(ret,job)
end
return ret
end
function getJobs(buildingId,workshopId,customId)
local ret={}
local c_jobs
if buildingId==df.building_type.Workshop then
c_jobs=jobs_workshop[workshopId]
elseif buildingId==df.building_type.Furnace then
c_jobs=jobs_furnace[workshopId]
if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then
c_jobs=utils.clone(c_jobs,true)
addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter)
end
else
return nil
end
if c_jobs==nil then
c_jobs={}
else
c_jobs=utils.clone(c_jobs,true)
end
addReactionJobs(c_jobs,buildingId,workshopId,customId)
for jobId,contents in pairs(c_jobs) do
if jobId~="defaults" then
local entry={}
entry.name=contents.name
local lclDefaults=utils.clone(input_filter_defaults,true)
if c_jobs.defaults ~=nil then
utils.assign(lclDefaults,c_jobs.defaults)
end
entry.items={}
for k,item in pairs(contents.items) do
entry.items[k]=utils.clone(lclDefaults,true)
utils.assign(entry.items[k],item)
end
if contents.job_fields~=nil then
entry.job_fields={}
utils.assign(entry.job_fields,contents.job_fields)
end
ret[jobId]=entry
end
end
--get jobs, add in from raws
return ret
end
return _ENV

@ -6,7 +6,17 @@ local dscreen = dfhack.screen
USE_GRAPHICS = dscreen.inGraphicsMode() USE_GRAPHICS = dscreen.inGraphicsMode()
CLEAR_PEN = {ch=32,fg=0,bg=0} 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,
_MOUSE_L_DOWN = true,
_MOUSE_R_DOWN = true,
_STRING = true,
}
function simulateInput(screen,...) function simulateInput(screen,...)
local keys = {} local keys = {}
@ -14,7 +24,7 @@ function simulateInput(screen,...)
local kv = arg local kv = arg
if type(arg) == 'string' then if type(arg) == 'string' then
kv = df.interface_key[arg] kv = df.interface_key[arg]
if kv == nil then if kv == nil and not FAKE_INPUT_KEYS[arg] then
error('Invalid keycode: '..arg) error('Invalid keycode: '..arg)
end end
end end
@ -104,10 +114,14 @@ function inset_frame(rect, inset, gap)
return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
end end
function compute_frame_body(wavail, havail, spec, inset, gap) function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame)
gap = gap or 0 gap = gap or 0
local l,t,r,b = parse_inset(inset) local l,t,r,b = parse_inset(inset)
local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) local xgap,ygap = 0,0
if inner_frame then
xgap,ygap = gap*2+l+r, gap*2+t+b
end
local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap)
local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap)
return rect, body return rect, body
end end
@ -116,16 +130,6 @@ function blink_visible(delay)
return math.floor(dfhack.getTickCount()/delay) % 2 == 0 return math.floor(dfhack.getTickCount()/delay) % 2 == 0
end end
function to_pen(default, pen, bg, bold)
if pen == nil then
return default or {}
elseif type(pen) ~= 'table' then
return {fg=pen,bg=bg,bold=bold}
else
return pen
end
end
function getKeyDisplay(code) function getKeyDisplay(code)
if type(code) == 'string' then if type(code) == 'string' then
code = df.interface_key[code] code = df.interface_key[code]
@ -215,7 +219,8 @@ Painter = defclass(Painter, ViewRect)
function Painter:init(args) function Painter:init(args)
self.x = self.x1 self.x = self.x1
self.y = self.y1 self.y = self.y1
self.cur_pen = to_pen(nil, args.pen or COLOR_GREY) self.cur_pen = to_pen(args.pen or COLOR_GREY)
self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN)
end end
function Painter.new(rect, pen) function Painter.new(rect, pen)
@ -241,6 +246,7 @@ end
function Painter:viewport(x,y,w,h) function Painter:viewport(x,y,w,h)
local vp = ViewRect.viewport(x,y,w,h) local vp = ViewRect.viewport(x,y,w,h)
vp.cur_pen = self.cur_pen vp.cur_pen = self.cur_pen
vp.cur_key_pen = self.cur_key_pen
return mkinstance(Painter, vp):seek(0,0) return mkinstance(Painter, vp):seek(0,0)
end end
@ -280,10 +286,12 @@ function Painter:pen(pen,...)
end end
function Painter:color(fg,bold,bg) function Painter:color(fg,bold,bg)
self.cur_pen = copyall(self.cur_pen) self.cur_pen = to_pen(self.cur_pen, fg, bg, bold)
self.cur_pen.fg = fg return self
self.cur_pen.bold = bold end
if bg then self.cur_pen.bg = bg end
function Painter:key_pen(pen,...)
self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...)
return self return self
end end
@ -339,10 +347,10 @@ function Painter:string(text,pen,...)
return self:advance(#text, nil) return self:advance(#text, nil)
end end
function Painter:key(code,pen,bg,...) function Painter:key(code,pen,...)
return self:string( return self:string(
getKeyDisplay(code), getKeyDisplay(code),
pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... to_pen(self.cur_key_pen, pen, ...)
) )
end end
@ -513,17 +521,18 @@ function Screen:sendInputToParent(...)
end end
end end
function Screen:show(below) function Screen:show(parent)
if self._native then if self._native then
error("This screen is already on display") error("This screen is already on display")
end end
self:onAboutToShow(below) parent = parent or dfhack.gui.getCurViewscreen(true)
if not dscreen.show(self, below) then self:onAboutToShow(parent)
if not dscreen.show(self, parent.child) then
error('Could not show screen') error('Could not show screen')
end end
end end
function Screen:onAboutToShow() function Screen:onAboutToShow(parent)
end end
function Screen:onShow() function Screen:onShow()
@ -556,28 +565,28 @@ end
-- Plain grey-colored frame. -- Plain grey-colored frame.
GREY_FRAME = { GREY_FRAME = {
frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY },
title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE },
signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
} }
-- The usual boundary used by the DF screens. Often has fancy pattern in tilesets. -- The usual boundary used by the DF screens. Often has fancy pattern in tilesets.
BOUNDARY_FRAME = { BOUNDARY_FRAME = {
frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK },
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY }, signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY },
} }
GREY_LINE_FRAME = { GREY_LINE_FRAME = {
frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK },
h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK },
v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK },
lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK },
lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK },
rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK },
rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK },
title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY },
signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK }, signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK },
} }
function paint_frame(x1,y1,x2,y2,style,title) function paint_frame(x1,y1,x2,y2,style,title)
@ -620,7 +629,7 @@ end
function FramedScreen:computeFrame(parent_rect) function FramedScreen:computeFrame(parent_rect)
local sw, sh = parent_rect.width, parent_rect.height local sw, sh = parent_rect.width, parent_rect.height
local fw, fh = self:getWantedFrameSize(parent_rect) local fw, fh = self:getWantedFrameSize(parent_rect)
return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true)
end end
function FramedScreen:onRenderFrame(dc, rect) function FramedScreen:onRenderFrame(dc, rect)

@ -0,0 +1,285 @@
-- Stock dialog for selecting buildings
local _ENV = mkmodule('gui.buildings')
local gui = require('gui')
local widgets = require('gui.widgets')
local dlg = require('gui.dialogs')
local utils = require('utils')
ARROW = string.char(26)
WORKSHOP_ABSTRACT={
[df.building_type.Civzone]=true,[df.building_type.Stockpile]=true,
}
WORKSHOP_SPECIAL={
[df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true,
[df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true
}
BuildingDialog = defclass(BuildingDialog, gui.FramedScreen)
BuildingDialog.focus_path = 'BuildingDialog'
BuildingDialog.ATTRS{
prompt = 'Type or select a building from this list',
frame_style = gui.GREY_LINE_FRAME,
frame_inset = 1,
frame_title = 'Select Building',
-- new attrs
none_caption = 'none',
hide_none = false,
use_abstract = true,
use_workshops = true,
use_tool_workshop=true,
use_furnace = true,
use_construction = true,
use_siege = true,
use_trap = true,
use_custom = true,
building_filter = DEFAULT_NIL,
on_select = DEFAULT_NIL,
on_cancel = DEFAULT_NIL,
on_close = DEFAULT_NIL,
}
function BuildingDialog:init(info)
self:addviews{
widgets.Label{
text = {
self.prompt, '\n\n',
'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN }
},
text_pen = COLOR_WHITE,
frame = { l = 0, t = 0 },
},
widgets.Label{
view_id = 'back',
visible = false,
text = { { key = 'LEAVESCREEN', text = ': Back' } },
frame = { r = 0, b = 0 },
auto_width = true,
},
widgets.FilteredList{
view_id = 'list',
not_found_label = 'No matching buildings',
frame = { l = 0, r = 0, t = 4, b = 2 },
icon_width = 2,
on_submit = self:callback('onSubmitItem'),
},
widgets.Label{
text = { {
key = 'SELECT', text = ': Select',
disabled = function() return not self.subviews.list:canSubmit() end
} },
frame = { l = 0, b = 0 },
}
}
self:initBuiltinMode()
end
function BuildingDialog:getWantedFrameSize(rect)
return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8)
end
function BuildingDialog:onDestroy()
if self.on_close then
self.on_close()
end
end
function BuildingDialog:initBuiltinMode()
local choices = {}
if not self.hide_none then
table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1})
end
if self.use_workshops then
table.insert(choices, {
icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W',
cb = self:callback('initWorkshopMode')
})
end
if self.use_furnace then
table.insert(choices, {
icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F',
cb = self:callback('initFurnaceMode')
})
end
if self.use_trap then
table.insert(choices, {
icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T',
cb = self:callback('initTrapMode')
})
end
if self.use_construction then
table.insert(choices, {
icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C',
cb = self:callback('initConstructionMode')
})
end
if self.use_siege then
table.insert(choices, {
icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S',
cb = self:callback('initSiegeMode')
})
end
if self.use_custom then
table.insert(choices, {
icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U',
cb = self:callback('initCustomMode')
})
end
for i=0,df.building_type._last_item do
if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then
self:addBuilding(choices, df.building_type[i], i, -1,-1,nil)
end
end
self:pushContext('Any building', choices)
end
function BuildingDialog:initWorkshopMode()
local choices = {}
for i=0,df.workshop_type._last_item do
if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then
self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil)
end
end
self:pushContext('Workshops', choices)
end
function BuildingDialog:initTrapMode()
local choices = {}
for i=0,df.trap_type._last_item do
self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil)
end
self:pushContext('Traps', choices)
end
function BuildingDialog:initConstructionMode()
local choices = {}
for i=0,df.construction_type._last_item do
self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil)
end
self:pushContext('Constructions', choices)
end
function BuildingDialog:initFurnaceMode()
local choices = {}
for i=0,df.furnace_type._last_item do
self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil)
end
self:pushContext('Furnaces', choices)
end
function BuildingDialog:initSiegeMode()
local choices = {}
for i=0,df.siegeengine_type._last_item do
self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil)
end
self:pushContext('Siege weapons', choices)
end
function BuildingDialog:initCustomMode()
local choices = {}
local raws=df.global.world.raws.buildings.all
for k,v in pairs(raws) do
self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v)
end
self:pushContext('Custom workshops', choices)
end
function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent)
-- Check the filter
if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then
return
end
table.insert(choices, {
text = name:lower(),
customshop = parent,
type_id = type_id, subtype_id = subtype_id, custom_id=custom_id
})
end
function BuildingDialog:pushContext(name, choices)
if not self.back_stack then
self.back_stack = {}
self.subviews.back.visible = false
else
table.insert(self.back_stack, {
context_str = self.context_str,
all_choices = self.subviews.list:getChoices(),
edit_text = self.subviews.list:getFilter(),
selected = self.subviews.list:getSelected(),
})
self.subviews.back.visible = true
end
self.context_str = name
self.subviews.list:setChoices(choices, 1)
end
function BuildingDialog:onGoBack()
local save = table.remove(self.back_stack)
self.subviews.back.visible = (#self.back_stack > 0)
self.context_str = save.context_str
self.subviews.list:setChoices(save.all_choices)
self.subviews.list:setFilter(save.edit_text, save.selected)
end
function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index)
self:dismiss()
if self.on_select then
self.on_select(type_id,subtype_id,custom_id,choice,index)
end
end
function BuildingDialog:onSubmitItem(idx, item)
if item.cb then
item:cb(idx)
else
self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx)
end
end
function BuildingDialog:onInput(keys)
if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then
if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then
self:onGoBack()
else
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
end
else
self:inputToSubviews(keys)
end
end
function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter)
BuildingDialog{
frame_title = title,
prompt = prompt,
building_filter = build_filter,
on_select = on_select,
on_cancel = on_cancel,
}:show()
end
return _ENV

@ -152,7 +152,9 @@ ListBox.ATTRS{
with_filter = false, with_filter = false,
cursor_pen = DEFAULT_NIL, cursor_pen = DEFAULT_NIL,
select_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL on_select = DEFAULT_NIL,
on_select2 = DEFAULT_NIL,
select2_hint = DEFAULT_NIL,
} }
function ListBox:preinit(info) function ListBox:preinit(info)
@ -160,14 +162,24 @@ function ListBox:preinit(info)
end end
function ListBox:init(info) function ListBox:init(info)
local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) local spen = dfhack.pen.parse(COLOR_CYAN, self.select_pen, nil, false)
local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) local cpen = dfhack.pen.parse(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true)
local list_widget = widgets.List local list_widget = widgets.List
if self.with_filter then if self.with_filter then
list_widget = widgets.FilteredList list_widget = widgets.FilteredList
end end
local on_submit2
if self.select2_hint or self.on_select2 then
on_submit2 = function(sel, obj)
self:dismiss()
if self.on_select2 then self.on_select2(sel, obj) end
local cb = obj.on_select2
if cb then cb(obj, sel) end
end
end
self:addviews{ self:addviews{
list_widget{ list_widget{
view_id = 'list', view_id = 'list',
@ -182,11 +194,19 @@ function ListBox:init(info)
local cb = obj.on_select or obj[2] local cb = obj.on_select or obj[2]
if cb then cb(obj, sel) end if cb then cb(obj, sel) end
end, end,
on_submit2 = on_submit2,
frame = { l = 0, r = 0 }, frame = { l = 0, r = 0 },
} }
} }
end end
function ListBox:onRenderFrame(dc,rect)
ListBox.super.onRenderFrame(self,dc,rect)
if self.select2_hint then
dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY)
end
end
function ListBox:getWantedFrameSize() function ListBox:getWantedFrameSize()
local mw, mh = InputBox.super.getWantedFrameSize(self) local mw, mh = InputBox.super.getWantedFrameSize(self)
local list = self.subviews.list local list = self.subviews.list

@ -373,10 +373,8 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor)
end end
end end
function DwarfOverlay:onAboutToShow(below) function DwarfOverlay:onAboutToShow(parent)
local screen = dfhack.gui.getCurViewscreen() if not df.viewscreen_dwarfmodest:is_instance(parent) then
if below then screen = below.parent end
if not df.viewscreen_dwarfmodest:is_instance(screen) then
error("This screen requires the main dwarfmode view") error("This screen requires the main dwarfmode view")
end end
end end

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

@ -60,6 +60,7 @@ Panel = defclass(Panel, Widget)
Panel.ATTRS { Panel.ATTRS {
on_render = DEFAULT_NIL, on_render = DEFAULT_NIL,
on_layout = DEFAULT_NIL,
} }
function Panel:init(args) function Panel:init(args)
@ -70,6 +71,10 @@ function Panel:onRenderBody(dc)
if self.on_render then self.on_render(dc) end if self.on_render then self.on_render(dc) end
end end
function Panel:postComputeFrame(body)
if self.on_layout then self.on_layout(body) end
end
----------- -----------
-- Pages -- -- Pages --
----------- -----------
@ -242,20 +247,37 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
end end
if token.text or token.key then if token.text or token.key then
local text = getval(token.text) or '' local text = ''..(getval(token.text) or '')
local keypen local keypen
if dc then if dc then
local tpen = getval(token.pen)
if disabled or is_disabled(token) then if disabled or is_disabled(token) then
dc:pen(getval(token.dpen) or dpen) dc:pen(getval(token.dpen) or tpen or dpen)
keypen = COLOR_GREEN keypen = COLOR_GREEN
else else
dc:pen(getval(token.pen) or pen) dc:pen(tpen or pen)
keypen = COLOR_LIGHTGREEN keypen = COLOR_LIGHTGREEN
end end
end end
x = x + #text local width = getval(token.width)
local padstr
if width then
x = x + width
if #text > width then
text = string.sub(text,1,width)
else
if token.pad_char then
padstr = string.rep(token.pad_char,width-#text)
end
if dc and token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end
else
x = x + #text
end
if token.key then if token.key then
local keystr = gui.getKeyDisplay(token.key) local keystr = gui.getKeyDisplay(token.key)
@ -280,6 +302,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled)
dc:string(text) dc:string(text)
end end
end end
if width and dc and not token.rjustify then
if padstr then dc:string(padstr) else dc:advance(width-#text) end
end
end end
token.x2 = x token.x2 = x
@ -383,6 +409,7 @@ List.ATTRS{
inactive_pen = DEFAULT_NIL, inactive_pen = DEFAULT_NIL,
on_select = DEFAULT_NIL, on_select = DEFAULT_NIL,
on_submit = DEFAULT_NIL, on_submit = DEFAULT_NIL,
on_submit2 = DEFAULT_NIL,
row_height = 1, row_height = 1,
scroll_keys = STANDARDSCROLL, scroll_keys = STANDARDSCROLL,
icon_width = DEFAULT_NIL, icon_width = DEFAULT_NIL,
@ -391,7 +418,13 @@ List.ATTRS{
function List:init(info) function List:init(info)
self.page_top = 1 self.page_top = 1
self.page_size = 1 self.page_size = 1
self:setChoices(info.choices, info.selected)
if info.choices then
self:setChoices(info.choices, info.selected)
else
self.choices = {}
self.selected = 1
end
end end
function List:setChoices(choices, selected) function List:setChoices(choices, selected)
@ -454,6 +487,9 @@ function List:moveCursor(delta, force_cb)
if cnt < 1 then if cnt < 1 then
self.page_top = 1 self.page_top = 1
self.selected = 1 self.selected = 1
if force_cb and self.on_select then
self.on_select(nil,nil)
end
return return
end end
@ -487,6 +523,18 @@ function List:onRenderBody(dc)
local iend = math.min(#choices, top+self.page_size-1) local iend = math.min(#choices, top+self.page_size-1)
local iw = self.icon_width local iw = self.icon_width
local function paint_icon(icon, obj)
if type(icon) ~= 'string' then
dc:char(nil,icon)
else
if current then
dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen)
else
dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen)
end
end
end
for i = top,iend do for i = top,iend do
local obj = choices[i] local obj = choices[i]
local current = (i == self.selected) local current = (i == self.selected)
@ -498,27 +546,28 @@ function List:onRenderBody(dc)
end end
local y = (i - top)*self.row_height local y = (i - top)*self.row_height
local icon = getval(obj.icon)
if iw and obj.icon then if iw and icon then
dc:seek(0, y) dc:seek(0, y)
if type(obj.icon) == 'table' then paint_icon(icon, obj)
dc:char(nil,obj.icon)
else
if current then
dc:string(obj.icon, obj.icon_pen or cur_pen)
else
dc:string(obj.icon, obj.icon_pen or cur_dpen)
end
end
end end
render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current)
local ip = dc.width
if obj.key then if obj.key then
local keystr = gui.getKeyDisplay(obj.key) local keystr = gui.getKeyDisplay(obj.key)
dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) ip = ip-2-#keystr
dc:seek(ip,y):pen(self.text_pen)
dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')')
end end
if icon and not iw then
dc:seek(ip-1,y)
paint_icon(icon, obj)
end
end end
end end
@ -528,10 +577,19 @@ function List:submit()
end end
end end
function List:submit2()
if self.on_submit2 and #self.choices > 0 then
self.on_submit2(self:getSelected())
end
end
function List:onInput(keys) function List:onInput(keys)
if self.on_submit and keys.SELECT then if self.on_submit and keys.SELECT then
self:submit() self:submit()
return true return true
elseif self.on_submit2 and keys.SEC_SELECT then
self:submit2()
return true
else else
for k,v in pairs(self.scroll_keys) do for k,v in pairs(self.scroll_keys) do
if keys[k] then if keys[k] then
@ -567,10 +625,14 @@ end
FilteredList = defclass(FilteredList, Widget) FilteredList = defclass(FilteredList, Widget)
FilteredList.ATTRS {
edit_below = false,
}
function FilteredList:init(info) function FilteredList:init(info)
self.edit = EditField{ self.edit = EditField{
text_pen = info.cursor_pen, text_pen = info.edit_pen or info.cursor_pen,
frame = { l = info.icon_width, t = 0 }, frame = { l = info.icon_width, t = 0, h = 1 },
on_change = self:callback('onFilterChange'), on_change = self:callback('onFilterChange'),
on_char = self:callback('onFilterChar'), on_char = self:callback('onFilterChar'),
} }
@ -579,10 +641,15 @@ function FilteredList:init(info)
text_pen = info.text_pen, text_pen = info.text_pen,
cursor_pen = info.cursor_pen, cursor_pen = info.cursor_pen,
inactive_pen = info.inactive_pen, inactive_pen = info.inactive_pen,
icon_pen = info.icon_pen,
row_height = info.row_height, row_height = info.row_height,
scroll_keys = info.scroll_keys, scroll_keys = info.scroll_keys,
icon_width = info.icon_width, icon_width = info.icon_width,
} }
if self.edit_below then
self.edit.frame = { l = info.icon_width, b = 0, h = 1 }
self.list.frame = { t = 0, b = 2 }
end
if info.on_select then if info.on_select then
self.list.on_select = function() self.list.on_select = function()
return info.on_select(self:getSelected()) return info.on_select(self:getSelected())
@ -593,14 +660,23 @@ function FilteredList:init(info)
return info.on_submit(self:getSelected()) return info.on_submit(self:getSelected())
end end
end end
if info.on_submit2 then
self.list.on_submit2 = function()
return info.on_submit2(self:getSelected())
end
end
self.not_found = Label{ self.not_found = Label{
visible = false, visible = true,
text = info.not_found_label or 'No matches', text = info.not_found_label or 'No matches',
text_pen = COLOR_LIGHTRED, text_pen = COLOR_LIGHTRED,
frame = { l = info.icon_width, t = 2 }, frame = { l = info.icon_width, t = self.list.frame.t },
} }
self:addviews{ self.edit, self.list, self.not_found } self:addviews{ self.edit, self.list, self.not_found }
self:setChoices(info.choices, info.selected) if info.choices then
self:setChoices(info.choices, info.selected)
else
self.choices = {}
end
end end
function FilteredList:getChoices() function FilteredList:getChoices()
@ -619,6 +695,10 @@ function FilteredList:submit()
return self.list:submit() return self.list:submit()
end end
function FilteredList:submit2()
return self.list:submit2()
end
function FilteredList:canSubmit() function FilteredList:canSubmit()
return not self.not_found.visible return not self.not_found.visible
end end

@ -24,7 +24,7 @@ function CheckedArray:__len()
return self.count return self.count
end end
function CheckedArray:__index(idx) function CheckedArray:__index(idx)
if type(idx) == number then if type(idx) == "number" then
if idx >= self.count then if idx >= self.count then
error('Index out of bounds: '..tostring(idx)) error('Index out of bounds: '..tostring(idx))
end end
@ -195,6 +195,26 @@ function MemoryArea:delete()
for k,v in pairs(self) do self[k] = nil end for k,v in pairs(self) do self[k] = nil end
end end
-- Static code segment search
function get_code_segment()
local cstart, cend
for i,mem in ipairs(dfhack.internal.getMemRanges()) do
if mem.read and mem.execute
and (string.match(mem.name,'/dwarfort%.exe$')
or string.match(mem.name,'/Dwarf_Fortress$')
or string.match(mem.name,'Dwarf Fortress%.exe'))
then
cstart = mem.start_addr
cend = mem.end_addr
end
end
if cstart and cend then
return MemoryArea.new(cstart, cend)
end
end
-- Static data segment search -- Static data segment search
local function find_data_segment() local function find_data_segment()

@ -283,7 +283,11 @@ function clone_with_default(obj,default,force)
return rv return rv
end end
-- Parse an integer value into a bitfield table
function parse_bitfield_int(value, type_ref) function parse_bitfield_int(value, type_ref)
if value == 0 then
return nil
end
local res = {} local res = {}
for i,v in ipairs(type_ref) do for i,v in ipairs(type_ref) do
if bit32.extract(value, i) ~= 0 then if bit32.extract(value, i) ~= 0 then
@ -293,6 +297,19 @@ function parse_bitfield_int(value, type_ref)
return res return res
end end
-- List the enabled flag names in the bitfield table
function list_bitfield_flags(bitfield, list)
list = list or {}
if bitfield then
for name,val in pairs(bitfield) do
if val then
table.insert(list, name)
end
end
end
return list
end
-- Sort a vector or lua table -- Sort a vector or lua table
function sort_vector(vector,field,cmp) function sort_vector(vector,field,cmp)
local fcmp = compare_field(field,cmp) local fcmp = compare_field(field,cmp)
@ -440,6 +457,7 @@ function getBuildingCenter(building)
return xyz2pos(building.centerx, building.centery, building.z) return xyz2pos(building.centerx, building.centery, building.z)
end end
-- Split the string by the given delimiter
function split_string(self, delimiter) function split_string(self, delimiter)
local result = { } local result = { }
local from = 1 local from = 1

@ -25,11 +25,16 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include <map>
#include <string> #include <string>
#include <unordered_map>
#include <vector> #include <vector>
#include <map>
using namespace std; using namespace std;
#include "ColorText.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "MemAccess.h" #include "MemAccess.h"
#include "Types.h" #include "Types.h"
@ -77,6 +82,14 @@ using df::global::building_next_id;
using df::global::process_jobs; using df::global::process_jobs;
using df::building_def; using df::building_def;
struct CoordHash {
size_t operator()(const df::coord pos) const {
return pos.x*65537 + pos.y*17 + pos.z;
}
};
static unordered_map<df::coord, int32_t, CoordHash> locationToBuilding;
static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile)
{ {
if (!extent.extents) if (!extent.extents)
@ -178,6 +191,20 @@ bool Buildings::ReadCustomWorkshopTypes(map <uint32_t, string> & btypes)
return true; return true;
} }
df::general_ref *Buildings::getGeneralRef(df::building *building, df::general_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->general_refs, type);
}
df::specific_ref *Buildings::getSpecificRef(df::building *building, df::specific_ref_type type)
{
CHECK_NULL_POINTER(building);
return findRef(building->specific_refs, type);
}
bool Buildings::setOwner(df::building *bld, df::unit *unit) bool Buildings::setOwner(df::building *bld, df::unit *unit)
{ {
CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(bld);
@ -222,6 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos)
if (!occ || !occ->bits.building) if (!occ || !occ->bits.building)
return NULL; return NULL;
// Try cache lookup in case it works:
auto cached = locationToBuilding.find(pos);
if (cached != locationToBuilding.end())
{
auto building = df::building::find(cached->second);
if (building && building->z == pos.z &&
building->isSettingOccupancy() &&
containsTile(building, pos, false))
{
return building;
}
}
// The authentic method, i.e. how the game generally does this:
auto &vec = df::building::get_vector(); auto &vec = df::building::get_vector();
for (size_t i = 0; i < vec.size(); i++) for (size_t i = 0; i < vec.size(); i++)
{ {
@ -895,7 +937,7 @@ static bool linkForConstruct(df::job* &job, df::building *bld)
job = new df::job(); job = new df::job();
job->job_type = df::job_type::ConstructBuilding; job->job_type = df::job_type::ConstructBuilding;
job->pos = df::coord(bld->centerx, bld->centery, bld->z); job->pos = df::coord(bld->centerx, bld->centery, bld->z);
job->references.push_back(ref); job->general_refs.push_back(ref);
bld->jobs.push_back(job); bld->jobs.push_back(job);
@ -1063,3 +1105,61 @@ bool Buildings::deconstruct(df::building *bld)
return true; return true;
} }
static unordered_map<int32_t, df::coord> corner1;
static unordered_map<int32_t, df::coord> corner2;
void Buildings::clearBuildings(color_ostream& out) {
corner1.clear();
corner2.clear();
locationToBuilding.clear();
}
void Buildings::updateBuildings(color_ostream& out, void* ptr)
{
int32_t id = (int32_t)ptr;
auto building = df::building::find(id);
if (building)
{
// Already cached -> weird, so bail out
if (corner1.count(id))
return;
// Civzones cannot be cached because they can
// overlap each other and normal buildings.
if (!building->isSettingOccupancy())
return;
df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z);
df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z);
corner1[id] = p1;
corner2[id] = p2;
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,building->z);
if (containsTile(building, pt, false))
locationToBuilding[pt] = id;
}
}
}
else if (corner1.count(id))
{
//existing building: destroy it
df::coord p1 = corner1[id];
df::coord p2 = corner2[id];
for ( int32_t x = p1.x; x <= p2.x; x++ ) {
for ( int32_t y = p1.y; y <= p2.y; y++ ) {
df::coord pt(x,y,p1.z);
auto cur = locationToBuilding.find(pt);
if (cur != locationToBuilding.end() && cur->second == id)
locationToBuilding.erase(cur);
}
}
corner1.erase(id);
corner2.erase(id);
}
}

@ -0,0 +1,503 @@
#include "Core.h"
#include "Console.h"
#include "modules/Buildings.h"
#include "modules/Constructions.h"
#include "modules/EventManager.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/construction.h"
#include "df/global_objects.h"
#include "df/item.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/ui.h"
#include "df/unit.h"
#include "df/unit_syndrome.h"
#include "df/world.h"
#include <map>
#include <unordered_map>
#include <unordered_set>
using namespace std;
using namespace DFHack;
using namespace EventManager;
/*
* TODO:
* error checking
* consider a typedef instead of a struct for EventHandler
**/
//map<uint32_t, vector<DFHack::EventManager::EventHandler> > tickQueue;
multimap<uint32_t, EventHandler> tickQueue;
//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever
multimap<Plugin*, EventHandler> handlers[EventType::EVENT_MAX];
uint32_t eventLastTick[EventType::EVENT_MAX];
const uint32_t ticksPerYear = 403200;
void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) {
handlers[e].insert(pair<Plugin*, EventHandler>(plugin, handler));
}
void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( !Core::getInstance().isWorldLoaded() ) {
tick = 0;
if ( absolute ) {
Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n");
}
}
if ( absolute ) {
tick = 0;
}
tickQueue.insert(pair<uint32_t, EventHandler>(tick+(uint32_t)when, handler));
handlers[EventType::TICK].insert(pair<Plugin*,EventHandler>(plugin,handler));
return;
}
void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) {
for ( multimap<Plugin*, EventHandler>::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) {
if ( (*i).first != plugin )
break;
EventHandler handle = (*i).second;
if ( handle == handler ) {
handlers[e].erase(i);
break;
}
}
return;
}
void DFHack::EventManager::unregisterAll(Plugin* plugin) {
for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) {
if ( (*i).first != plugin )
break;
//shenanigans to avoid concurrent modification
EventHandler getRidOf = (*i).second;
bool didSomething;
do {
didSomething = false;
for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) {
EventHandler candidate = (*j).second;
if ( getRidOf != candidate )
continue;
tickQueue.erase(j);
didSomething = true;
break;
}
} while(didSomething);
}
for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) {
handlers[a].erase(plugin);
}
return;
}
static void manageTickEvent(color_ostream& out);
static void manageJobInitiatedEvent(color_ostream& out);
static void manageJobCompletedEvent(color_ostream& out);
static void manageUnitDeathEvent(color_ostream& out);
static void manageItemCreationEvent(color_ostream& out);
static void manageBuildingEvent(color_ostream& out);
static void manageConstructionEvent(color_ostream& out);
static void manageSyndromeEvent(color_ostream& out);
static void manageInvasionEvent(color_ostream& out);
//tick event
static uint32_t lastTick = 0;
//job initiated
static int32_t lastJobId = -1;
//job completed
static unordered_map<int32_t, df::job*> prevJobs;
//unit death
static unordered_set<int32_t> livingUnits;
//item creation
static int32_t nextItem;
//building
static int32_t nextBuilding;
static unordered_set<int32_t> buildings;
//construction
static unordered_set<df::construction*> constructions;
static bool gameLoaded;
//invasion
static int32_t nextInvasion;
void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) {
static bool doOnce = false;
if ( !doOnce ) {
//TODO: put this somewhere else
doOnce = true;
EventHandler buildingHandler(Buildings::updateBuildings, 100);
DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL);
//out.print("Registered listeners.\n %d", __LINE__);
}
if ( event == DFHack::SC_WORLD_UNLOADED ) {
lastTick = 0;
lastJobId = -1;
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
tickQueue.clear();
livingUnits.clear();
nextItem = -1;
nextBuilding = -1;
buildings.clear();
constructions.clear();
Buildings::clearBuildings(out);
gameLoaded = false;
nextInvasion = -1;
} else if ( event == DFHack::SC_WORLD_LOADED ) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
multimap<uint32_t,EventHandler> newTickQueue;
for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) {
newTickQueue.insert(pair<uint32_t,EventHandler>(tick + (*i).first, (*i).second));
}
tickQueue.clear();
tickQueue.insert(newTickQueue.begin(), newTickQueue.end());
nextItem = 0;
nextBuilding = 0;
lastTick = 0;
nextInvasion = df::global::ui->invasions.next_id;
gameLoaded = true;
}
}
void DFHack::EventManager::manageEvents(color_ostream& out) {
if ( !gameLoaded ) {
return;
}
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
if ( tick <= lastTick )
return;
lastTick = tick;
int32_t eventFrequency[EventType::EVENT_MAX];
for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) {
int32_t min = 1000000000;
for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) {
EventHandler bob = (*b).second;
if ( bob.freq < min )
min = bob.freq;
}
eventFrequency[a] = min;
}
manageTickEvent(out);
if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) {
manageJobInitiatedEvent(out);
eventLastTick[EventType::JOB_INITIATED] = tick;
}
if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) {
manageJobCompletedEvent(out);
eventLastTick[EventType::JOB_COMPLETED] = tick;
}
if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) {
manageUnitDeathEvent(out);
eventLastTick[EventType::UNIT_DEATH] = tick;
}
if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) {
manageItemCreationEvent(out);
eventLastTick[EventType::ITEM_CREATED] = tick;
}
if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) {
manageBuildingEvent(out);
eventLastTick[EventType::BUILDING] = tick;
}
if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) {
manageConstructionEvent(out);
eventLastTick[EventType::CONSTRUCTION] = tick;
}
if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) {
manageSyndromeEvent(out);
eventLastTick[EventType::SYNDROME] = tick;
}
if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) {
manageInvasionEvent(out);
eventLastTick[EventType::INVASION] = tick;
}
return;
}
static void manageTickEvent(color_ostream& out) {
uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear
+ DFHack::World::ReadCurrentTick();
while ( !tickQueue.empty() ) {
if ( tick < (*tickQueue.begin()).first )
break;
EventHandler handle = (*tickQueue.begin()).second;
tickQueue.erase(tickQueue.begin());
handle.eventHandler(out, (void*)tick);
}
}
static void manageJobInitiatedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_INITIATED].empty() )
return;
if ( lastJobId == -1 ) {
lastJobId = *df::global::job_next_id - 1;
return;
}
if ( lastJobId+1 == *df::global::job_next_id ) {
return; //no new jobs
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end());
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
if ( link->item->id <= lastJobId )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)link->item);
}
}
lastJobId = *df::global::job_next_id - 1;
}
static void manageJobCompletedEvent(color_ostream& out) {
if ( handlers[EventType::JOB_COMPLETED].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end());
map<int32_t, df::job*> nowJobs;
for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) {
if ( link->item == NULL )
continue;
nowJobs[link->item->id] = link->item;
}
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
if ( nowJobs.find((*i).first) != nowJobs.end() )
continue;
//recently finished or cancelled job!
for ( auto j = copy.begin(); j != copy.end(); j++ ) {
(*j).second.eventHandler(out, (void*)(*i).second);
}
}
//erase old jobs, copy over possibly altered jobs
for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) {
Job::deleteJobStruct((*i).second);
}
prevJobs.clear();
//create new jobs
for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) {
/*map<int32_t, df::job*>::iterator i = prevJobs.find((*j).first);
if ( i != prevJobs.end() ) {
continue;
}*/
df::job* newJob = Job::cloneJobStruct((*j).second, true);
prevJobs[newJob->id] = newJob;
}
/*//get rid of old pointers to deallocated jobs
for ( size_t a = 0; a < toDelete.size(); a++ ) {
prevJobs.erase(a);
}*/
}
static void manageUnitDeathEvent(color_ostream& out) {
if ( handlers[EventType::UNIT_DEATH].empty() ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end());
for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) {
df::unit* unit = df::global::world->units.active[a];
if ( unit->counters.death_id == -1 ) {
livingUnits.insert(unit->id);
continue;
}
//dead: if dead since last check, trigger events
if ( livingUnits.find(unit->id) == livingUnits.end() )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)unit->id);
}
livingUnits.erase(unit->id);
}
}
static void manageItemCreationEvent(color_ostream& out) {
if ( handlers[EventType::ITEM_CREATED].empty() ) {
return;
}
if ( nextItem >= *df::global::item_next_id ) {
return;
}
multimap<Plugin*,EventHandler> copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end());
size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false);
for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) {
df::item* item = df::global::world->items.all[a];
//invaders
if ( item->flags.bits.foreign )
continue;
//traders who bring back your items?
if ( item->flags.bits.trader )
continue;
//migrants
if ( item->flags.bits.owned )
continue;
//spider webs don't count
if ( item->flags.bits.spider_web )
continue;
for ( auto i = copy.begin(); i != copy.end(); i++ ) {
(*i).second.eventHandler(out, (void*)item->id);
}
}
nextItem = *df::global::item_next_id;
}
static void manageBuildingEvent(color_ostream& out) {
/*
* TODO: could be faster
* consider looking at jobs: building creation / destruction
**/
if ( handlers[EventType::BUILDING].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end());
//first alert people about new buildings
for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) {
int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a);
if ( index == -1 ) {
//out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a);
//the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter
continue;
}
buildings.insert(a);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)a);
}
}
nextBuilding = *df::global::building_next_id;
//now alert people about destroyed buildings
unordered_set<int32_t> toDelete;
for ( auto a = buildings.begin(); a != buildings.end(); a++ ) {
int32_t id = *a;
int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id);
if ( index != -1 )
continue;
toDelete.insert(id);
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler bob = (*b).second;
bob.eventHandler(out, (void*)id);
}
}
for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) {
int32_t id = *a;
buildings.erase(id);
}
//out.print("Sent building event.\n %d", __LINE__);
}
static void manageConstructionEvent(color_ostream& out) {
if ( handlers[EventType::CONSTRUCTION].empty() )
return;
unordered_set<df::construction*> constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end());
multimap<Plugin*,EventHandler> copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end());
for ( auto a = constructions.begin(); a != constructions.end(); a++ ) {
df::construction* construction = *a;
if ( constructionsNow.find(construction) != constructionsNow.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) {
df::construction* construction = *a;
if ( constructions.find(construction) != constructions.end() )
continue;
for ( auto b = copy.begin(); b != copy.end(); b++ ) {
EventHandler handle = (*b).second;
handle.eventHandler(out, (void*)construction);
}
}
constructions.clear();
constructions.insert(constructionsNow.begin(), constructionsNow.end());
}
static void manageSyndromeEvent(color_ostream& out) {
if ( handlers[EventType::SYNDROME].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end());
for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) {
df::unit* unit = *a;
if ( unit->flags1.bits.dead )
continue;
for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) {
df::unit_syndrome* syndrome = unit->syndromes.active[b];
uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time;
if ( startTime <= eventLastTick[EventType::SYNDROME] )
continue;
SyndromeData data(unit->id, b);
for ( auto c = copy.begin(); c != copy.end(); c++ ) {
EventHandler handle = (*c).second;
handle.eventHandler(out, (void*)&data);
}
}
}
}
static void manageInvasionEvent(color_ostream& out) {
if ( handlers[EventType::INVASION].empty() )
return;
multimap<Plugin*,EventHandler> copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end());
if ( df::global::ui->invasions.next_id <= nextInvasion )
return;
nextInvasion = df::global::ui->invasions.next_id;
for ( auto a = copy.begin(); a != copy.end(); a++ ) {
EventHandler handle = (*a).second;
handle.eventHandler(out, (void*)nextInvasion);
}
}

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

@ -495,10 +495,10 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item)
item.id = itreal->id; item.id = itreal->id;
item.age = itreal->age; item.age = itreal->age;
item.flags = itreal->flags; item.flags = itreal->flags;
item.matdesc.itemType = itreal->getType(); item.matdesc.item_type = itreal->getType();
item.matdesc.subType = itreal->getSubtype(); item.matdesc.item_subtype = itreal->getSubtype();
item.matdesc.material = itreal->getMaterial(); item.matdesc.mat_type = itreal->getMaterial();
item.matdesc.index = itreal->getMaterialIndex(); item.matdesc.mat_index = itreal->getMaterialIndex();
item.wear_level = itreal->getWear(); item.wear_level = itreal->getWear();
item.quality = itreal->getQuality(); item.quality = itreal->getQuality();
item.quantity = itreal->getStackSize(); item.quantity = itreal->getStackSize();
@ -509,7 +509,7 @@ df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
return findRef(item->itemrefs, type); return findRef(item->general_refs, type);
} }
df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type) df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type)
@ -530,9 +530,9 @@ bool Items::setOwner(df::item *item, df::unit *unit)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref)) if (!strict_virtual_cast<df::general_ref_unit_itemownerst>(ref))
continue; continue;
@ -546,7 +546,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
} }
delete ref; delete ref;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
} }
item->flags.bits.owned = false; item->flags.bits.owned = false;
@ -561,7 +561,7 @@ bool Items::setOwner(df::item *item, df::unit *unit)
ref->unit_id = unit->id; ref->unit_id = unit->id;
insert_into_vector(unit->owned_items, item->id); insert_into_vector(unit->owned_items, item->id);
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
} }
return true; return true;
@ -580,9 +580,9 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
items->clear(); items->clear();
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
if (ref->getType() != general_ref_type::CONTAINS_ITEM) if (ref->getType() != general_ref_type::CONTAINS_ITEM)
continue; continue;
@ -592,6 +592,20 @@ void Items::getContainedItems(df::item *item, std::vector<df::item*> *items)
} }
} }
df::building *Items::getHolderBuilding(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::BUILDING_HOLDER);
return ref ? ref->getBuilding() : NULL;
}
df::unit *Items::getHolderUnit(df::item * item)
{
auto ref = getGeneralRef(item, general_ref_type::UNIT_HOLDER);
return ref ? ref->getUnit() : NULL;
}
df::coord Items::getPosition(df::item *item) df::coord Items::getPosition(df::item *item)
{ {
CHECK_NULL_POINTER(item); CHECK_NULL_POINTER(item);
@ -603,9 +617,9 @@ df::coord Items::getPosition(df::item *item)
if (item->flags.bits.in_inventory) if (item->flags.bits.in_inventory)
{ {
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -702,9 +716,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
if (item->world_data_id != -1) if (item->world_data_id != -1)
return false; return false;
for (size_t i = 0; i < item->itemrefs.size(); i++) for (size_t i = 0; i < item->general_refs.size(); i++)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -734,9 +748,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
{ {
bool found = false; bool found = false;
for (int i = item->itemrefs.size()-1; i >= 0; i--) for (int i = item->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = item->itemrefs[i]; df::general_ref *ref = item->general_refs[i];
switch (ref->getType()) switch (ref->getType())
{ {
@ -753,7 +767,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
item2->flags.bits.weight_computed = false; item2->flags.bits.weight_computed = false;
removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); removeRef(item2->general_refs, general_ref_type::CONTAINS_ITEM, item->id);
} }
break; break;
@ -785,7 +799,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
} }
found = true; found = true;
vector_erase_at(item->itemrefs, i); vector_erase_at(item->general_refs, i);
delete ref; delete ref;
} }
@ -864,10 +878,10 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df:
container->flags.bits.weight_computed = false; container->flags.bits.weight_computed = false;
ref1->item_id = item->id; ref1->item_id = item->id;
container->itemrefs.push_back(ref1); container->general_refs.push_back(ref1);
ref2->item_id = container->id; ref2->item_id = container->id;
item->itemrefs.push_back(ref2); item->general_refs.push_back(ref2);
return true; return true;
} }
@ -898,7 +912,7 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::
item->flags.bits.in_building=true; item->flags.bits.in_building=true;
ref->building_id=building->id; ref->building_id=building->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
auto con=new df::building_actual::T_contained_items; auto con=new df::building_actual::T_contained_items;
con->item=item; con->item=item;
@ -941,7 +955,7 @@ bool DFHack::Items::moveToInventory(
unit->inventory.push_back(newInventoryItem); unit->inventory.push_back(newInventoryItem);
holderReference->unit_id = unit->id; holderReference->unit_id = unit->id;
item->itemrefs.push_back(holderReference); item->general_refs.push_back(holderReference);
resetUnitInvFlags(unit, newInventoryItem); resetUnitInvFlags(unit, newInventoryItem);
@ -1002,7 +1016,7 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
proj->item = item; proj->item = item;
ref->projectile_id = proj->id; ref->projectile_id = proj->id;
item->itemrefs.push_back(ref); item->general_refs.push_back(ref);
linked_list_append(&world->proj_list, proj->link); linked_list_append(&world->proj_list, proj->link);

@ -35,6 +35,7 @@ using namespace std;
#include "Error.h" #include "Error.h"
#include "PluginManager.h" #include "PluginManager.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "Types.h"
#include "modules/Job.h" #include "modules/Job.h"
#include "modules/Materials.h" #include "modules/Materials.h"
@ -54,7 +55,7 @@ using namespace std;
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
df::job *DFHack::Job::cloneJobStruct(df::job *job) df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData)
{ {
CHECK_NULL_POINTER(job); CHECK_NULL_POINTER(job);
@ -71,14 +72,14 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job)
pnew->specific_refs.clear(); pnew->specific_refs.clear();
// Clone refs // Clone refs
for (int i = pnew->references.size()-1; i >= 0; i--) for (int i = pnew->general_refs.size()-1; i >= 0; i--)
{ {
df::general_ref *ref = pnew->references[i]; df::general_ref *ref = pnew->general_refs[i];
if (virtual_cast<df::general_ref_unit_workerst>(ref)) if (!keepWorkerData && virtual_cast<df::general_ref_unit_workerst>(ref))
vector_erase_at(pnew->references, i); vector_erase_at(pnew->general_refs, i);
else else
pnew->references[i] = ref->clone(); pnew->general_refs[i] = ref->clone();
} }
// Clone items // Clone items
@ -96,8 +97,8 @@ void DFHack::Job::deleteJobStruct(df::job *job)
// Only allow free-floating job structs // Only allow free-floating job structs
assert(!job->list_link && job->items.empty() && job->specific_refs.empty()); assert(!job->list_link && job->items.empty() && job->specific_refs.empty());
for (int i = job->references.size()-1; i >= 0; i--) for (int i = job->general_refs.size()-1; i >= 0; i--)
delete job->references[i]; delete job->general_refs[i];
for (int i = job->job_items.size()-1; i >= 0; i--) for (int i = job->job_items.size()-1; i >= 0; i--)
delete job->job_items[i]; delete job->job_items[i];
@ -228,13 +229,27 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job)
printItemDetails(out, job->job_items[i], i); printItemDetails(out, job->job_items[i], i);
} }
df::general_ref *Job::getGeneralRef(df::job *job, df::general_ref_type type)
{
CHECK_NULL_POINTER(job);
return findRef(job->general_refs, type);
}
df::specific_ref *Job::getSpecificRef(df::job *job, df::specific_ref_type type)
{
CHECK_NULL_POINTER(job);
return findRef(job->specific_refs, type);
}
df::building *DFHack::Job::getHolder(df::job *job) df::building *DFHack::Job::getHolder(df::job *job)
{ {
CHECK_NULL_POINTER(job); CHECK_NULL_POINTER(job);
for (size_t i = 0; i < job->references.size(); i++) for (size_t i = 0; i < job->general_refs.size(); i++)
{ {
VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]); VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->general_refs[i]);
if (ref) if (ref)
return ref->getBuilding(); return ref->getBuilding();
} }
@ -246,9 +261,9 @@ df::unit *DFHack::Job::getWorker(df::job *job)
{ {
CHECK_NULL_POINTER(job); CHECK_NULL_POINTER(job);
for (size_t i = 0; i < job->references.size(); i++) for (size_t i = 0; i < job->general_refs.size(); i++)
{ {
VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->references[i]); VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]);
if (ref) if (ref)
return ref->getUnit(); return ref->getUnit();
} }

@ -30,10 +30,12 @@ distribution.
#include <map> #include <map>
#include <set> #include <set>
#include <cstdlib> #include <cstdlib>
#include <iostream>
using namespace std; using namespace std;
#include "modules/Maps.h" #include "modules/Maps.h"
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "ColorText.h"
#include "Error.h" #include "Error.h"
#include "VersionInfo.h" #include "VersionInfo.h"
#include "MemAccess.h" #include "MemAccess.h"
@ -59,6 +61,8 @@ using namespace std;
#include "df/z_level_flags.h" #include "df/z_level_flags.h"
#include "df/region_map_entry.h" #include "df/region_map_entry.h"
#include "df/flow_info.h" #include "df/flow_info.h"
#include "df/plant.h"
#include "df/building_type.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -535,6 +539,129 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2)
return tile1 && tile1 == tile2; return tile1 && tile1 == tile2;
} }
bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
{
color_ostream& out = Core::getInstance().getConsole();
int32_t dx = pos2.x-pos1.x;
int32_t dy = pos2.y-pos1.y;
int32_t dz = pos2.z-pos1.z;
if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 )
return false;
if ( pos2.z < pos1.z ) {
df::coord temp = pos1;
pos1 = pos2;
pos2 = temp;
}
df::map_block* block1 = getTileBlock(pos1);
df::map_block* block2 = getTileBlock(pos2);
if ( !block1 || !block2 )
return false;
if ( !index_tile<uint16_t>(block1->walkable,pos1) || !index_tile<uint16_t>(block2->walkable,pos2) ) {
return false;
}
if ( dz == 0 )
return true;
df::tiletype* type1 = Maps::getTileType(pos1);
df::tiletype* type2 = Maps::getTileType(pos2);
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1);
df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2);
if ( dx == 0 && dy == 0 ) {
//check for forbidden hatches and floors and such
df::enums::tile_building_occ::tile_building_occ upOcc = index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building;
if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored )
return false;
if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 )
return true;
if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN )
return true;
if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN )
return true;
if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN )
return true;
if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP_TOP ) {
//it depends
//there has to be a wall next to the ramp
bool foundWall = false;
for ( int32_t x = -1; x <= 1; x++ ) {
for ( int32_t y = -1; y <= 1; y++ ) {
if ( x == 0 && y == 0 )
continue;
df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z));
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type);
if ( shape1 == tiletype_shape::WALL ) {
foundWall = true;
x = 2;
break;
}
}
}
if ( !foundWall )
return false; //unusable ramp
//there has to be an unforbidden hatch above the ramp
if ( index_tile<df::tile_occupancy>(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic )
return false;
//note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy
df::building* building = Buildings::findAtTile(pos2);
if ( building == NULL ) {
out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n";
return false;
}
if ( building->getType() != df::enums::building_type::Hatch ) {
return false;
}
return true;
}
return false;
}
//diagonal up: has to be a ramp
if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) {
df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1);
bool foundWall = false;
for ( int32_t x = -1; x <= 1; x++ ) {
for ( int32_t y = -1; y <= 1; y++ ) {
if ( x == 0 && y == 0 )
continue;
df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z));
df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type);
if ( shape1 == tiletype_shape::WALL ) {
foundWall = true;
x = 2;
break;
}
}
}
if ( !foundWall )
return false; //unusable ramp
df::tiletype* typeUp = Maps::getTileType(up);
df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp);
if ( shapeUp != tiletype_shape::RAMP_TOP )
return false;
df::map_block* blockUp = getTileBlock(up);
if ( !blockUp )
return false;
df::enums::tile_building_occ::tile_building_occ occupancy = index_tile<df::tile_occupancy>(blockUp->occupancy,up).bits.building;
if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable )
return false;
return true;
}
return false;
}
#define COPY(a,b) memcpy(&a,&b,sizeof(a)) #define COPY(a,b) memcpy(&a,&b,sizeof(a))
MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent)
@ -650,15 +777,15 @@ void MapExtras::Block::TileInfo::init_coninfo()
con_info = new ConInfo(); con_info = new ConInfo();
con_info->constructed.clear(); con_info->constructed.clear();
COPY(con_info->tiles, base_tiles); COPY(con_info->tiles, base_tiles);
memset(con_info->mattype, -1, sizeof(con_info->mattype)); memset(con_info->mat_type, -1, sizeof(con_info->mat_type));
memset(con_info->matindex, -1, sizeof(con_info->matindex)); memset(con_info->mat_index, -1, sizeof(con_info->mat_index));
} }
MapExtras::Block::BasematInfo::BasematInfo() MapExtras::Block::BasematInfo::BasematInfo()
{ {
dirty.clear(); dirty.clear();
memset(mattype,0,sizeof(mattype)); memset(mat_type,0,sizeof(mat_type));
memset(matindex,-1,sizeof(matindex)); memset(mat_index,-1,sizeof(mat_index));
memset(layermat,-1,sizeof(layermat)); memset(layermat,-1,sizeof(layermat));
} }
@ -719,8 +846,8 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles)
is_con = true; is_con = true;
tiles->con_info->constructed.setassignment(x,y,true); tiles->con_info->constructed.setassignment(x,y,true);
tiles->con_info->tiles[x][y] = tt; tiles->con_info->tiles[x][y] = tt;
tiles->con_info->mattype[x][y] = con->mat_type; tiles->con_info->mat_type[x][y] = con->mat_type;
tiles->con_info->matindex[x][y] = con->mat_index; tiles->con_info->mat_index[x][y] = con->mat_index;
tt = con->original_tile; tt = con->original_tile;
} }
@ -753,14 +880,14 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats)
auto tt = tiles->base_tiles[x][y]; auto tt = tiles->base_tiles[x][y];
auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); auto mat = info.getBaseMaterial(tt, df::coord2d(x,y));
bmats->mattype[x][y] = mat.mat_type; bmats->mat_type[x][y] = mat.mat_type;
bmats->matindex[x][y] = mat.mat_index; bmats->mat_index[x][y] = mat.mat_index;
// Copy base info back to construction layer // Copy base info back to construction layer
if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y))
{ {
tiles->con_info->mattype[x][y] = mat.mat_type; tiles->con_info->mat_type[x][y] = mat.mat_type;
tiles->con_info->matindex[x][y] = mat.mat_index; tiles->con_info->mat_index[x][y] = mat.mat_index;
} }
} }
} }

@ -190,6 +190,8 @@ bool MaterialInfo::find(const std::vector<std::string> &items)
} }
else if (items.size() == 2) else if (items.size() == 2)
{ {
if (items[1] == "NONE" && findBuiltin(items[0]))
return true;
if (findPlant(items[0], items[1])) if (findPlant(items[0], items[1]))
return true; return true;
if (findCreature(items[0], items[1])) if (findCreature(items[0], items[1]))
@ -210,7 +212,7 @@ bool MaterialInfo::findBuiltin(const std::string &token)
} }
df::world_raws &raws = world->raws; df::world_raws &raws = world->raws;
for (int i = 1; i < NUM_BUILTIN; i++) for (int i = 0; i < NUM_BUILTIN; i++)
{ {
auto obj = raws.mat_table.builtin[i]; auto obj = raws.mat_table.builtin[i];
if (obj && obj->id == token) if (obj && obj->id == token)
@ -312,7 +314,7 @@ std::string MaterialInfo::getToken()
else if (index == 1) else if (index == 1)
return "COAL:CHARCOAL"; return "COAL:CHARCOAL";
} }
return material->id + ":NONE"; return material->id;
case Inorganic: case Inorganic:
return "INORGANIC:" + inorganic->id; return "INORGANIC:" + inorganic->id;
case Creature: case Creature:
@ -423,6 +425,8 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat)
TEST(glass, IS_GLASS); TEST(glass, IS_GLASS);
if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0) if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0)
return true; return true;
if (cat.bits.milk && linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0)
return true;
return false; return false;
} }
@ -762,7 +766,7 @@ bool Materials::ReadCreatureTypesEx (void)
for(size_t k = 0; k < sizecolormod;k++) for(size_t k = 0; k < sizecolormod;k++)
{ {
// color mod [0] -> color list // color mod [0] -> color list
auto & indexes = colorings[k]->color_indexes; auto & indexes = colorings[k]->pattern_index;
size_t sizecolorlist = indexes.size(); size_t sizecolorlist = indexes.size();
caste.ColorModifier[k].colorlist.resize(sizecolorlist); caste.ColorModifier[k].colorlist.resize(sizecolorlist);
for(size_t l = 0; l < sizecolorlist; l++) for(size_t l = 0; l < sizecolorlist; l++)
@ -836,7 +840,7 @@ bool Materials::ReadAllMaterials(void)
std::string Materials::getDescription(const t_material & mat) std::string Materials::getDescription(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
if (mi.creature) if (mi.creature)
return mi.creature->creature_id + " " + mi.material->id; return mi.creature->creature_id + " " + mi.material->id;
else if (mi.plant) else if (mi.plant)
@ -849,7 +853,7 @@ std::string Materials::getDescription(const t_material & mat)
// This is completely worthless now // This is completely worthless now
std::string Materials::getType(const t_material & mat) std::string Materials::getType(const t_material & mat)
{ {
MaterialInfo mi(mat.material, mat.index); MaterialInfo mi(mat.mat_type, mat.mat_index);
switch (mi.mode) switch (mi.mode)
{ {
case MaterialInfo::Builtin: case MaterialInfo::Builtin:

@ -55,6 +55,7 @@ using namespace DFHack;
#include "df/item.h" #include "df/item.h"
#include "df/job.h" #include "df/job.h"
#include "df/building.h" #include "df/building.h"
#include "df/renderer.h"
using namespace df::enums; using namespace df::enums;
using df::global::init; using df::global::init;
@ -109,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y)
{ {
if (!gps || !pen.valid()) return false; if (!gps || !pen.valid()) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false;
doSetTile(pen, x*dimy + y); doSetTile(pen, x*dim.y + y);
return true; return true;
} }
@ -120,11 +121,11 @@ Pen Screen::readTile(int x, int y)
{ {
if (!gps) return Pen(0,0,0,-1); if (!gps) return Pen(0,0,0,-1);
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
if (x < 0 || x >= dimx || y < 0 || y >= dimy) if (x < 0 || x >= dim.x || y < 0 || y >= dim.y)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
int index = x*dimy + y; int index = x*dim.y + y;
auto screen = gps->screen + index*4; auto screen = gps->screen + index*4;
if (screen[3] & 0x80) if (screen[3] & 0x80)
return Pen(0,0,0,-1); return Pen(0,0,0,-1);
@ -153,14 +154,15 @@ Pen Screen::readTile(int x, int y)
bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
{ {
if (!gps || y < 0 || y >= gps->dimy) return false; auto dim = getWindowSize();
if (!gps || y < 0 || y >= dim.y) return false;
Pen tmp(pen); Pen tmp(pen);
bool ok = false; bool ok = false;
for (size_t i = -std::min(0,x); i < text.size(); i++) for (size_t i = -std::min(0,x); i < text.size(); i++)
{ {
if (x + i >= size_t(gps->dimx)) if (x + i >= size_t(dim.x))
break; break;
tmp.ch = text[i]; tmp.ch = text[i];
@ -174,17 +176,18 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text)
bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2)
{ {
auto dim = getWindowSize();
if (!gps || !pen.valid()) return false; if (!gps || !pen.valid()) return false;
if (x1 < 0) x1 = 0; if (x1 < 0) x1 = 0;
if (y1 < 0) y1 = 0; if (y1 < 0) y1 = 0;
if (x2 >= gps->dimx) x2 = gps->dimx-1; if (x2 >= dim.x) x2 = dim.x-1;
if (y2 >= gps->dimy) y2 = gps->dimy-1; if (y2 >= dim.y) y2 = dim.y-1;
if (x1 > x2 || y1 > y2) return false; if (x1 > x2 || y1 > y2) return false;
for (int x = x1; x <= x2; x++) for (int x = x1; x <= x2; x++)
{ {
int index = x*gps->dimy; int index = x*dim.y;
for (int y = y1; y <= y2; y++) for (int y = y1; y <= y2; y++)
doSetTile(pen, index+y); doSetTile(pen, index+y);
@ -197,32 +200,33 @@ bool Screen::drawBorder(const std::string &title)
{ {
if (!gps) return false; if (!gps) return false;
int dimx = gps->dimx, dimy = gps->dimy; auto dim = getWindowSize();
Pen border('\xDB', 8); Pen border('\xDB', 8);
Pen text(0, 0, 7); Pen text(0, 0, 7);
Pen signature(0, 0, 8); Pen signature(0, 0, 8);
for (int x = 0; x < dimx; x++) for (int x = 0; x < dim.x; x++)
{ {
doSetTile(border, x * dimy + 0); doSetTile(border, x * dim.y + 0);
doSetTile(border, x * dimy + dimy - 1); doSetTile(border, x * dim.y + dim.y - 1);
} }
for (int y = 0; y < dimy; y++) for (int y = 0; y < dim.y; y++)
{ {
doSetTile(border, 0 * dimy + y); doSetTile(border, 0 * dim.y + y);
doSetTile(border, (dimx - 1) * dimy + y); doSetTile(border, (dim.x - 1) * dim.y + y);
} }
paintString(signature, dimx-8, dimy-1, "DFHack"); paintString(signature, dim.x-8, dim.y-1, "DFHack");
return paintString(text, (dimx - title.length()) / 2, 0, title); return paintString(text, (dim.x - title.length()) / 2, 0, title);
} }
bool Screen::clear() bool Screen::clear()
{ {
if (!gps) return false; if (!gps) return false;
return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); auto dim = getWindowSize();
return fillRect(Pen(' ',0,0,false), 0, 0, dim.x-1, dim.y-1);
} }
bool Screen::invalidate() bool Screen::invalidate()
@ -233,6 +237,21 @@ bool Screen::invalidate()
return true; return true;
} }
const Pen Screen::Painter::default_pen(0,COLOR_GREY,0);
const Pen Screen::Painter::default_key_pen(0,COLOR_LIGHTGREEN,0);
void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen)
{
if (gcursor.y < clip.first.y || gcursor.y > clip.second.y)
return;
int dx = std::max(0, int(clip.first.x - gcursor.x));
int len = std::min((int)str.size(), int(clip.second.x - gcursor.x + 1));
if (len > dx)
paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx));
}
bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs)
{ {
if (!gps || !texture || x < 0 || y < 0) return false; if (!gps || !texture || x < 0 || y < 0) return false;
@ -312,6 +331,47 @@ class DFHACK_EXPORT enabler_inputst {
public: public:
std::string GetKeyDisplay(int binding); std::string GetKeyDisplay(int binding);
}; };
class DFHACK_EXPORT renderer {
unsigned char *screen;
long *screentexpos;
char *screentexpos_addcolor;
unsigned char *screentexpos_grayscale;
unsigned char *screentexpos_cf;
unsigned char *screentexpos_cbr;
// For partial printing:
unsigned char *screen_old;
long *screentexpos_old;
char *screentexpos_addcolor_old;
unsigned char *screentexpos_grayscale_old;
unsigned char *screentexpos_cf_old;
unsigned char *screentexpos_cbr_old;
public:
virtual void update_tile(int x, int y) {};
virtual void update_all() {};
virtual void render() {};
virtual void set_fullscreen();
virtual void zoom(df::zoom_commands cmd);
virtual void resize(int w, int h) {};
virtual void grid_resize(int w, int h) {};
renderer() {
screen = NULL;
screentexpos = NULL;
screentexpos_addcolor = NULL;
screentexpos_grayscale = NULL;
screentexpos_cf = NULL;
screentexpos_cbr = NULL;
screen_old = NULL;
screentexpos_old = NULL;
screentexpos_addcolor_old = NULL;
screentexpos_grayscale_old = NULL;
screentexpos_cf_old = NULL;
screentexpos_cbr_old = NULL;
}
virtual ~renderer();
virtual bool get_mouse_coords(int &x, int &y) { return false; }
virtual bool uses_opengl();
};
#else #else
struct less_sz { struct less_sz {
bool operator() (const string &a, const string &b) const { bool operator() (const string &a, const string &b) const {
@ -326,7 +386,9 @@ static std::map<df::interface_key,std::set<string,less_sz> > *keydisplay = NULL;
void init_screen_module(Core *core) void init_screen_module(Core *core)
{ {
#ifdef _LINUX #ifdef _LINUX
core = core; renderer tmp;
if (!strict_virtual_cast<df::renderer>((virtual_ptr)&tmp))
cerr << "Could not fetch the renderer vtable." << std::endl;
#else #else
if (!core->vinfo->getAddress("keydisplay", keydisplay)) if (!core->vinfo->getAddress("keydisplay", keydisplay))
keydisplay = NULL; keydisplay = NULL;
@ -602,14 +664,24 @@ int dfhack_lua_viewscreen::do_input(lua_State *L)
if (enabler && enabler->tracking_on) if (enabler && enabler->tracking_on)
{ {
if (enabler->mouse_lbut) { if (enabler->mouse_lbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L"); lua_setfield(L, -2, "_MOUSE_L");
} }
if (enabler->mouse_rbut) { if (enabler->mouse_rbut_down) {
lua_pushboolean(L, true); lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R"); lua_setfield(L, -2, "_MOUSE_R");
} }
if (enabler->mouse_lbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_L_DOWN");
enabler->mouse_lbut = 0;
}
if (enabler->mouse_rbut) {
lua_pushboolean(L, true);
lua_setfield(L, -2, "_MOUSE_R_DOWN");
enabler->mouse_rbut = 0;
}
} }
lua_call(L, 2, 0); lua_call(L, 2, 0);
@ -639,7 +711,12 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen()
void dfhack_lua_viewscreen::render() void dfhack_lua_viewscreen::render()
{ {
if (Screen::isDismissed(this)) return; if (Screen::isDismissed(this))
{
if (parent)
parent->render();
return;
}
dfhack_viewscreen::render(); dfhack_viewscreen::render();

@ -115,6 +115,9 @@ void Translation::setNickname(df::language_name *name, std::string nick)
if (!name->has_name) if (!name->has_name)
{ {
if (nick.empty())
return;
*name = df::language_name(); *name = df::language_name();
name->language = 0; name->language = 0;
@ -122,6 +125,18 @@ void Translation::setNickname(df::language_name *name, std::string nick)
} }
name->nickname = nick; name->nickname = nick;
// If the nick is empty, check if this made the whole name empty
if (name->nickname.empty() && name->first_name.empty())
{
bool has_words = false;
for (int i = 0; i < 7; i++)
if (name->words[i] >= 0)
has_words = true;
if (!has_words)
name->has_name = false;
}
} }
string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart)

@ -405,7 +405,7 @@ bool Creatures::WriteJob(const t_creature * furball, std::vector<t_material> con
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); p->writeWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType);
p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].subType); p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype);
p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex);
p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index);
p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags);
@ -475,7 +475,7 @@ bool Creatures::ReadJob(const t_creature * furball, vector<t_material> & mat)
for(i=0;i<cmats.size();i++) for(i=0;i<cmats.size();i++)
{ {
mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o); mat[i].itemType = p->readWord(cmats[i] + off.job_material_itemtype_o);
mat[i].subType = p->readWord(cmats[i] + off.job_material_subtype_o); mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o);
mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o);
mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o);
mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o);
@ -519,18 +519,25 @@ df::coord Units::getPosition(df::unit *unit)
return unit->pos; return unit->pos;
} }
df::item *Units::getContainer(df::unit *unit) df::general_ref *Units::getGeneralRef(df::unit *unit, df::general_ref_type type)
{ {
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
for (size_t i = 0; i < unit->refs.size(); i++) return findRef(unit->general_refs, type);
{ }
df::general_ref *ref = unit->refs[i];
if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM)
return ref->getItem();
}
return NULL; df::specific_ref *Units::getSpecificRef(df::unit *unit, df::specific_ref_type type)
{
CHECK_NULL_POINTER(unit);
return findRef(unit->specific_refs, type);
}
df::item *Units::getContainer(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
return findItemRef(unit->general_refs, general_ref_type::CONTAINED_IN_ITEM);
} }
static df::assumed_identity *getFigureIdentity(df::historical_figure *figure) static df::assumed_identity *getFigureIdentity(df::historical_figure *figure)
@ -607,9 +614,9 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
if (!unit) if (!unit)
return NULL; return NULL;
for (unsigned i = 0; i < unit->refs.size(); i++) for (unsigned i = 0; i < unit->general_refs.size(); i++)
{ {
df::nemesis_record *rv = unit->refs[i]->getNemesis(); df::nemesis_record *rv = unit->general_refs[i]->getNemesis();
if (rv && rv->unit == unit) if (rv && rv->unit == unit)
return rv; return rv;
} }
@ -778,10 +785,10 @@ bool DFHack::Units::isSane(df::unit *unit)
if (unit->flags1.bits.dead || if (unit->flags1.bits.dead ||
unit->flags3.bits.ghostly || unit->flags3.bits.ghostly ||
isOpposedToLife(unit) || isOpposedToLife(unit) ||
unit->unknown8.unk2) unit->enemy.undead)
return false; return false;
if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit)) if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit))
return false; return false;
switch (unit->mood) switch (unit->mood)
@ -832,7 +839,7 @@ bool DFHack::Units::isDwarf(df::unit *unit)
CHECK_NULL_POINTER(unit); CHECK_NULL_POINTER(unit);
return unit->race == ui->race_id || return unit->race == ui->race_id ||
unit->unknown8.normal_race == ui->race_id; unit->enemy.normal_race == ui->race_id;
} }
double DFHack::Units::getAge(df::unit *unit, bool true_age) double DFHack::Units::getAge(df::unit *unit, bool true_age)
@ -889,8 +896,7 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust
// Retrieve skill from unit soul: // Retrieve skill from unit soul:
df::enum_field<df::job_skill,int16_t> key(skill_id); auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id);
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key);
if (skill) if (skill)
{ {
@ -903,6 +909,24 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust
return 0; return 0;
} }
int Units::getExperience(df::unit *unit, df::job_skill skill_id, bool total)
{
CHECK_NULL_POINTER(unit);
if (!unit->status.current_soul)
return 0;
auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id);
if (!skill)
return 0;
int xp = skill->experience;
// exact formula used by the game:
if (total && skill->rating > 0)
xp += 500*skill->rating + 100*skill->rating*(skill->rating - 1)/2;
return xp;
}
int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
{ {
/* /*
@ -1201,12 +1225,12 @@ int Units::computeMovementSpeed(df::unit *unit)
// Stance // Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) if (!unit->flags1.bits.on_ground && unit->status2.limbs_stand_max > 2)
{ {
// WTF // WTF
int as = unit->status2.able_stand; int as = unit->status2.limbs_stand_max;
int x = (as-1) - (as>>1); int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair; int y = as - unit->status2.limbs_stand_count;
if (unit->flags3.bits.on_crutch) y--; if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x; y = y * 500 / x;
if (y > 0) speed += y; if (y > 0) speed += y;

@ -1,85 +0,0 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "Internal.h"
#include <string>
#include <vector>
#include <map>
using namespace std;
#include "VersionInfo.h"
#include "MemAccess.h"
#include "Types.h"
#include "Core.h"
using namespace DFHack;
#include "modules/Vegetation.h"
#include "df/world.h"
using namespace DFHack;
using df::global::world;
bool Vegetation::isValid()
{
return (world != NULL);
}
uint32_t Vegetation::getCount()
{
return world->plants.all.size();
}
df::plant * Vegetation::getPlant(const int32_t index)
{
if (uint32_t(index) >= getCount())
return NULL;
return world->plants.all[index];
}
bool Vegetation::copyPlant(const int32_t index, t_plant &out)
{
if (uint32_t(index) >= getCount())
return false;
out.origin = world->plants.all[index];
out.name = out.origin->name;
out.flags = out.origin->flags;
out.material = out.origin->material;
out.pos = out.origin->pos;
out.grow_counter = out.origin->grow_counter;
out.temperature_1 = out.origin->temperature_1;
out.temperature_2 = out.origin->temperature_2;
out.is_burning = out.origin->is_burning;
out.hitpoints = out.origin->hitpoints;
out.update_order = out.origin->update_order;
//out.unk1 = out.origin->anon_1;
//out.unk2 = out.origin->anon_2;
//out.temperature_3 = out.origin->temperature_3;
//out.temperature_4 = out.origin->temperature_4;
//out.temperature_5 = out.origin->temperature_5;
return true;
}

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

@ -1 +1 @@
Subproject commit aaedb5148f87f71cc82d1320f43f90153531dafa Subproject commit c7e2c28febd6dca06ff7e9951090982fbbee12b5

@ -0,0 +1,142 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
0x2ac6b
CC CC CC CC CC
66 39 E8 EB 53
.text:0042B86B loc_42B86B:
.text:0042B86B cmp ax, bp
.text:0042B86E jmp short loc_42B8C3
0x2ac7b
CC CC CC CC CC
E9 96 A2 00 00
.text:0042B87B loc_42B87B:
.text:0042B87B jmp loc_435B16
0x2acc3
CC CC CC CC CC CC CC CC CC CC CC CC CC
75 0A 66 FF 4C 24 16 79 03 58 EB AC C3
.text:0042B8C3 loc_42B8C3:
.text:0042B8C3 jnz short locret_42B8CF
.text:0042B8C5 dec word ptr [esp+16h] ; 4+8+8+2
.text:0042B8CA jns short locret_42B8CF
.text:0042B8CC pop eax
.text:0042B8CD jmp short loc_42B87B
.text:0042B8CF locret_42B8CF:
.text:0042B8CF retn
0x2b2a1
CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC
66 C7 44 24 0E 01 00 8B 90 44 01 00 00 C3 CC
.text:0042BEA1 loc_42BEA1:
.text:0042BEA1 mov word ptr [esp+0Eh], 1 ; 4+8+2
.text:0042BEA8 mov edx, [eax+144h]
.text:0042BEAE retn
0x34d91
8B 90 44 01 00 00
E8 0B 65 FF FF 90
<<<<
.text:00435991 mov edx, [eax+144h]
====
.text:00435991 call loc_42BEA1
.text:00435996 nop
>>>>
0x34e53
0F 84 BD 00 00 00
E8 6B 5E FF FF 90
<<<<
.text:00435A53 jz loc_435B16
====
.text:00435A53 call loc_42B8C3
.text:00435A58 nop
>>>>
0x34ef3
66 3B C5 74 1E
E8 73 5D FF FF
<<<<
.text:00435AF3 cmp ax, bp
.text:00435AF6 jz short loc_435B16
====
.text:00435AF3 call loc_42B86B
>>>>
basically:
+ int allowed_count = 1; // to mean 2
...
- if (type(item) == new_type)
+ if (type(item) == new_type && --allowed_count < 0)
return false;
to allow up to two items of the same type at the same time
---8<---
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002AC6B: CC 66
0002AC6C: CC 39
0002AC6D: CC E8
0002AC6E: CC EB
0002AC6F: CC 53
0002AC7B: CC E9
0002AC7C: CC 96
0002AC7D: CC A2
0002AC7E: CC 00
0002AC7F: CC 00
0002ACC3: CC 75
0002ACC4: CC 0A
0002ACC5: CC 66
0002ACC6: CC FF
0002ACC7: CC 4C
0002ACC8: CC 24
0002ACC9: CC 16
0002ACCA: CC 79
0002ACCB: CC 03
0002ACCC: CC 58
0002ACCD: CC EB
0002ACCE: CC AC
0002ACCF: CC C3
0002B2A1: CC 66
0002B2A2: CC C7
0002B2A3: CC 44
0002B2A4: CC 24
0002B2A5: CC 0E
0002B2A6: CC 01
0002B2A7: CC 00
0002B2A8: CC 8B
0002B2A9: CC 90
0002B2AA: CC 44
0002B2AB: CC 01
0002B2AC: CC 00
0002B2AD: CC 00
0002B2AE: CC C3
00034D91: 8B E8
00034D92: 90 0B
00034D93: 44 65
00034D94: 01 FF
00034D95: 00 FF
00034D96: 00 90
00034E53: 0F E8
00034E54: 84 6B
00034E55: BD 5E
00034E56: 00 FF
00034E57: 00 FF
00034E58: 00 90
00034EF3: 66 E8
00034EF4: 3B 73
00034EF5: C5 5D
00034EF6: 74 FF
00034EF7: 1E FF

@ -0,0 +1,91 @@
http://www.bay12games.com/dwarves/mantisbt/view.php?id=808
Original code:
.text:00916BCE mov edi, ebp
.text:00916BD0 call eax
.text:00916BD2 test eax, eax
.text:00916BD4 jnz short loc_916C1C
.text:00916C0A mov edi, ebp
.text:00916C14 mov edi, ebp
Patch:
0x2ac34:
CC CC CC CC CC CC CC CC CC CC CC CC
8B 7C 24 78 8B 3C B7 FF D0 EB 25 CC
.text:0042B834 loc_42B834:
.text:0042B834 mov edi, [esp+78h]
.text:0042B838 mov edi, [edi+esi*4]
.text:0042B83B call eax
.text:0042B83D jmp short unk_42B864
0x2ac64
CC CC CC CC CC CC CC CC CC CC CC CC
85 C0 E9 69 B3 4E 00 CC CC CC CC CC
.text:0042B864 loc_42B864:
.text:0042B864 test eax, eax
.text:0042B866 jmp loc_916BD4
0x515fce
8B FD FF D0 85 C0
E9 61 4C B1 FF 90
.text:00916BCE jmp loc_42B834
.text:00916BD3 nop
.text:00916BD4 loc_916BD4:
0x51600a
8B FD
90 90
.text:00916C0A nop
.text:00916C0B nop
0x516014
8B FD
90 90
.text:00916C14 nop
.text:00916C15 nop
You can use this script to apply the generated patch below:
http://stalkr.net/files/ida/idadif.py
----8<----
This difference file is created by The Interactive Disassembler
Dwarf Fortress.exe
0002AC34: CC 8B
0002AC35: CC 7C
0002AC36: CC 24
0002AC37: CC 78
0002AC38: CC 8B
0002AC39: CC 3C
0002AC3A: CC B7
0002AC3B: CC FF
0002AC3C: CC D0
0002AC3D: CC EB
0002AC3E: CC 25
0002AC64: CC 85
0002AC65: CC C0
0002AC66: CC E9
0002AC67: CC 69
0002AC68: CC B3
0002AC69: CC 4E
0002AC6A: CC 00
00515FCE: 8B E9
00515FCF: FD 61
00515FD0: FF 4C
00515FD1: D0 B1
00515FD2: 85 FF
00515FD3: C0 90
0051600A: 8B 90
0051600B: FD 90
00516014: 8B 90
00516015: FD 90

Some files were not shown because too many files have changed in this diff Show More