Merge branch 'master' of https://github.com/angavrilov/dfhack into experimental-dontmerge

Conflicts:
	dfhack.init-example
	library/xml
	plugins/CMakeLists.txt
develop
Warmist 2012-09-18 23:46:16 +03:00
commit a02a120e2d
64 changed files with 5184 additions and 552 deletions

@ -868,6 +868,26 @@ Units module
Returns the nemesis record of the unit if it has one, or *nil*. Returns the nemesis record of the unit if it has one, or *nil*.
* ``dfhack.units.isHidingCurse(unit)``
Checks if the unit hides improved attributes from its curse.
* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)``
* ``dfhack.units.getMentalAttrValue(unit, attr_type)``
Computes the effective attribute value, including curse effect.
* ``dfhack.units.isCrazed(unit)``
* ``dfhack.units.isOpposedToLife(unit)``
* ``dfhack.units.hasExtravision(unit)``
* ``dfhack.units.isBloodsucker(unit)``
Simple checks of caste attributes that can be modified by curses.
* ``dfhack.units.getMiscTrait(unit, type[, create])``
Finds (or creates if requested) a misc trait object with the given id.
* ``dfhack.units.isDead(unit)`` * ``dfhack.units.isDead(unit)``
The unit is completely dead and passive, or a ghost. The unit is completely dead and passive, or a ghost.
@ -894,6 +914,14 @@ Units module
Returns the age of the unit in years as a floating-point value. Returns the age of the unit in years as a floating-point value.
If ``true_age`` is true, ignores false identities. If ``true_age`` is true, ignores false identities.
* ``dfhack.units.getEffectiveSkill(unit, skill)``
Computes the effective rating for the given skill, taking into account exhaustion, pain etc.
* ``dfhack.units.computeMovementSpeed(unit)``
Computes number of frames * 100 it takes the unit to move in its current state of mind and body.
* ``dfhack.units.getNoblePositions(unit)`` * ``dfhack.units.getNoblePositions(unit)``
Returns a list of tables describing noble position assignments, or *nil*. Returns a list of tables describing noble position assignments, or *nil*.
@ -971,6 +999,14 @@ Items module
Move the item to the unit inventory. Returns *false* if impossible. Move the item to the unit inventory. Returns *false* if impossible.
* ``dfhack.items.remove(item[, no_uncat])``
Removes the item, and marks it for garbage collection unless ``no_uncat`` is true.
* ``dfhack.items.makeProjectile(item)``
Turns the item into a projectile, and returns the new object, or *nil* if impossible.
Maps module Maps module
----------- -----------

@ -1106,6 +1106,26 @@ a lua list containing them.</p>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNemesis(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getNemesis(unit)</tt></p>
<p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p> <p>Returns the nemesis record of the unit if it has one, or <em>nil</em>.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isHidingCurse(unit)</tt></p>
<p>Checks if the unit hides improved attributes from its curse.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPhysicalAttrValue(unit, attr_type)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getMentalAttrValue(unit, attr_type)</tt></p>
<p>Computes the effective attribute value, including curse effect.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isCrazed(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isOpposedToLife(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.hasExtravision(unit)</tt></p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isBloodsucker(unit)</tt></p>
<p>Simple checks of caste attributes that can be modified by curses.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getMiscTrait(unit, type[, create])</tt></p>
<p>Finds (or creates if requested) a misc trait object with the given id.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.isDead(unit)</tt></p>
<p>The unit is completely dead and passive, or a ghost.</p> <p>The unit is completely dead and passive, or a ghost.</p>
</li> </li>
@ -1126,6 +1146,12 @@ same checks the game uses to decide game-over by extinction.</p>
<p>Returns the age of the unit in years as a floating-point value. <p>Returns the age of the unit in years as a floating-point value.
If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p> If <tt class="docutils literal">true_age</tt> is true, ignores false identities.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getEffectiveSkill(unit, skill)</tt></p>
<p>Computes the effective rating for the given skill, taking into account exhaustion, pain etc.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.computeMovementSpeed(unit)</tt></p>
<p>Computes number of frames * 100 it takes the unit to move in its current state of mind and body.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getNoblePositions(unit)</tt></p>
<p>Returns a list of tables describing noble position assignments, or <em>nil</em>. <p>Returns a list of tables describing noble position assignments, or <em>nil</em>.
Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p> Every table has fields <tt class="docutils literal">entity</tt>, <tt class="docutils literal">assignment</tt> and <tt class="docutils literal">position</tt>.</p>
@ -1187,6 +1213,12 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.moveToInventory(item,unit,use_mode,body_part)</tt></p>
<p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p> <p>Move the item to the unit inventory. Returns <em>false</em> if impossible.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.items.remove(item[, no_uncat])</tt></p>
<p>Removes the item, and marks it for garbage collection unless <tt class="docutils literal">no_uncat</tt> is true.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.items.makeProjectile(item)</tt></p>
<p>Turns the item into a projectile, and returns the new object, or <em>nil</em> if impossible.</p>
</li>
</ul> </ul>
</div> </div>
<div class="section" id="maps-module"> <div class="section" id="maps-module">

75
NEWS

@ -0,0 +1,75 @@
DFHack v0.34.11-r2 (UNRELEASED)
Internals:
- full support for Mac OS X.
- a plugin that adds scripting in ruby.
- support for interposing virtual methods in DF from C++ plugins.
- support for creating new interface screens from C++ and lua.
- added various other API functions.
Notable bugfixes:
- better terminal reset after exit on linux.
- seedwatch now works on reclaim.
- the sort plugin won't crash on cages anymore.
Misc improvements:
- autodump: can move items to any walkable tile, not just floors.
- stripcaged: by default keep armor, new dumparmor option.
- zone: allow non-domesticated birds in nestboxes.
- workflow: quality range in constraints.
- cleanplants: new command to remove rain water from plants.
- liquids: can paint permaflow, i.e. what makes rivers power water wheels.
- prospect: pre-embark prospector accounts for caves & magma sea in its estimate.
- rename: supports renaming stockpiles, workshops, traps, siege engines.
New tweaks:
- tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus.
- tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does.
- tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui.
- tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort.
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
- tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc.
- tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode.
New scripts:
- fixnaked: removes thoughts about nakedness.
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
- fix/population-cap: run after every migrant wave to prevent exceeding the cap.
- fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp.
New GUI scripts:
- gui/mechanisms: browse mechanism links of the current building.
- gui/room-list: browse other rooms owned by the unit when assigning one.
- gui/liquids: a GUI front-end for the liquids plugin.
- gui/rename: renaming stockpiles, workshops and units via an in-game dialog.
- gui/power-meter: front-end for the Power Meter plugin.
- gui/siege-engine: front-end for the Siege Engine plugin.
Autolabor plugin:
- can set nonidle hauler percentage.
- broker excluded from all labors when needed at depot.
- likewise, anybody with a scheduled diplomat meeting.
New Dwarf Manipulator plugin:
Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game.
New Steam Engine plugin:
Dwarven Water Reactors don't make any sense whatsoever and cause lag, so this may be
a replacement for those concerned by it. The plugin detects if a workshop with a
certain name is in the raws used by the current world, and provides the necessary
behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions.
Note: Stuff like animal treadmills might be more period, but absolutely can't be
done with tools dfhack has access to.
New Power Meter plugin:
When activated, implements a pressure plate modification that detects power in gear
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
the necessary build configuration UI.
New Siege Engine plugin:
When enabled and configured via gui/siege-engine, allows aiming siege engines
at a designated rectangular area with 360 degree fire range and across Z levels;
this works by rewriting the projectile trajectory immediately after it appears.
Also supports loading catapults with non-boulder projectiles, taking from a stockpile,
and restricting operator skill range like with ordinary workshops.
Disclaimer: not in any way to undermine the future siege update from Toady, but
the aiming logic of existing engines hasn't been updated since 2D, and is almost
useless above ground :(. Again, things like making siegers bring their own engines
is totally out of the scope of dfhack and can only be done by Toady.
New Add Spatter plugin:
Detects reactions with certain names in the raws, and changes them from adding
improvements to adding item contaminants. This allows directly covering items
with poisons. The added spatters are immune both to water and 'clean items'.
Intended to give some use to all those giant cave spider poison barrels brought
by the caravans.

@ -7,10 +7,10 @@ IF(CMAKE_COMPILER_IS_GNUCC)
STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION})
LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR)
LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR)
IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) #IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2))
#GCC is too old #GCC is too old
SET(STL_HASH_OLD_GCC 1) # SET(STL_HASH_OLD_GCC 1)
ENDIF() #ENDIF()
#SET(CMAKE_CXX_FLAGS "-std=c++0x") #SET(CMAKE_CXX_FLAGS "-std=c++0x")
SET(HAVE_HASH_MAP 0) SET(HAVE_HASH_MAP 0)

@ -51,7 +51,7 @@ keybinding add Shift-G "job-material GLASS_GREEN"
keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms 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.work keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list
# interface for the liquids plugin # interface for the liquids plugin
keybinding add Alt-L@dwarfmode/LookAround gui/liquids keybinding add Alt-L@dwarfmode/LookAround gui/liquids
@ -59,12 +59,12 @@ keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction # machine power sensitive pressure plate construction
keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
# interface for universal game master's editor # siege engine control
keybinding add Alt-Shift-E gui/gm-editor keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
################### ############################
# UI logic tweaks # # UI and game logic tweaks #
################### ############################
# stabilize the cursor of dwarfmode when switching menus # stabilize the cursor of dwarfmode when switching menus
tweak stable-cursor tweak stable-cursor
@ -77,3 +77,16 @@ tweak readable-build-plate
# improve FPS by squashing endless item temperature update loops # improve FPS by squashing endless item temperature update loops
tweak stable-temp tweak stable-temp
# speed up items reaching temp equilibrium with environment by
# capping the rate to no less than 1 degree change per 500 frames
# Note: will also cause stuff to melt faster in magma etc
tweak fast-heat 500
# stop stacked liquid/bar/thread/cloth items from lasting forever
# if used in reactions that use only a fraction of the dimension.
tweak fix-dimensions
# make reactions requiring containers usable in advmode - the issue is
# that the screen asks for those reagents to be selected directly
tweak advmode-contained

@ -286,6 +286,10 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "")
TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
if(APPLE)
add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...")
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

@ -79,6 +79,8 @@ distribution.
#include "df/building_civzonest.h" #include "df/building_civzonest.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/unit_misc_trait.h"
#include "df/proj_itemst.h"
#include <lua.h> #include <lua.h>
#include <lauxlib.h> #include <lauxlib.h>
@ -813,12 +815,20 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = {
WRAPM(Units, getVisibleName), WRAPM(Units, getVisibleName),
WRAPM(Units, getIdentity), WRAPM(Units, getIdentity),
WRAPM(Units, getNemesis), WRAPM(Units, getNemesis),
WRAPM(Units, isCrazed),
WRAPM(Units, isOpposedToLife),
WRAPM(Units, hasExtravision),
WRAPM(Units, isBloodsucker),
WRAPM(Units, isMischievous),
WRAPM(Units, getMiscTrait),
WRAPM(Units, isDead), WRAPM(Units, isDead),
WRAPM(Units, isAlive), WRAPM(Units, isAlive),
WRAPM(Units, isSane), WRAPM(Units, isSane),
WRAPM(Units, isDwarf), WRAPM(Units, isDwarf),
WRAPM(Units, isCitizen), WRAPM(Units, isCitizen),
WRAPM(Units, getAge), WRAPM(Units, getAge),
WRAPM(Units, getEffectiveSkill),
WRAPM(Units, computeMovementSpeed),
WRAPM(Units, getProfessionName), WRAPM(Units, getProfessionName),
WRAPM(Units, getCasteProfessionName), WRAPM(Units, getCasteProfessionName),
WRAPM(Units, getProfessionColor), WRAPM(Units, getProfessionColor),
@ -876,6 +886,18 @@ static bool items_moveToInventory
return Items::moveToInventory(mc, item, unit, mode, body_part); return Items::moveToInventory(mc, item, unit, mode, body_part);
} }
static bool items_remove(df::item *item, bool no_uncat)
{
MapExtras::MapCache mc;
return Items::remove(mc, item, no_uncat);
}
static df::proj_itemst *items_makeProjectile(df::item *item)
{
MapExtras::MapCache mc;
return Items::makeProjectile(mc, item);
}
static const LuaWrapper::FunctionReg dfhack_items_module[] = { static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPM(Items, getGeneralRef), WRAPM(Items, getGeneralRef),
WRAPM(Items, getSpecificRef), WRAPM(Items, getSpecificRef),
@ -887,6 +909,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToContainer, items_moveToContainer),
WRAPN(moveToBuilding, items_moveToBuilding), WRAPN(moveToBuilding, items_moveToBuilding),
WRAPN(moveToInventory, items_moveToInventory), WRAPN(moveToInventory, items_moveToInventory),
WRAPN(makeProjectile, items_makeProjectile),
WRAPN(remove, items_remove),
{ NULL, NULL } { NULL, NULL }
}; };
@ -1060,7 +1084,9 @@ static int buildings_getCorrectSize(lua_State *state)
return 5; return 5;
} }
static int buildings_setSize(lua_State *state) namespace {
int buildings_setSize(lua_State *state)
{ {
auto bld = Lua::CheckDFObject<df::building>(state, 1); auto bld = Lua::CheckDFObject<df::building>(state, 1);
df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1)); df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1));
@ -1081,11 +1107,13 @@ static int buildings_setSize(lua_State *state)
return 1; return 1;
} }
}
static const luaL_Reg dfhack_buildings_funcs[] = { static const luaL_Reg dfhack_buildings_funcs[] = {
{ "findAtTile", buildings_findAtTile }, { "findAtTile", buildings_findAtTile },
{ "findCivzonesAt", buildings_findCivzonesAt }, { "findCivzonesAt", buildings_findCivzonesAt },
{ "getCorrectSize", buildings_getCorrectSize }, { "getCorrectSize", buildings_getCorrectSize },
{ "setSize", buildings_setSize }, { "setSize", &Lua::CallWithCatchWrapper<buildings_setSize> },
{ NULL, NULL } { NULL, NULL }
}; };

@ -127,6 +127,9 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
char permissions[5]; // r/-, w/-, x/-, p/s, 0 char permissions[5]; // r/-, w/-, x/-, p/s, 0
FILE *mapFile = ::fopen("/proc/self/maps", "r"); FILE *mapFile = ::fopen("/proc/self/maps", "r");
if (!mapFile)
return;
size_t start, end, offset, device1, device2, node; size_t start, end, offset, device1, device2, node;
while (fgets(buffer, 1024, mapFile)) while (fgets(buffer, 1024, mapFile))
@ -148,6 +151,8 @@ void Process::getMemRanges( vector<t_memrange> & ranges )
temp.valid = true; temp.valid = true;
ranges.push_back(temp); ranges.push_back(temp);
} }
fclose(mapFile);
} }
uint32_t Process::getBase() uint32_t Process::getBase()

@ -394,7 +394,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out,
//out.print("Received %d:%d\n", header.id, header.size); //out.print("Received %d:%d\n", header.id, header.size);
if (header.id == RPC_REPLY_FAIL) if ((DFHack::DFHackReplyCode)header.id == RPC_REPLY_FAIL)
return header.size == CR_OK ? CR_FAILURE : command_result(header.size); return header.size == CR_OK ? CR_FAILURE : command_result(header.size);
if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)

@ -250,7 +250,7 @@ void ServerConnection::threadFn()
break; break;
} }
if (header.id == RPC_REQUEST_QUIT) if ((DFHack::DFHackReplyCode)header.id == RPC_REQUEST_QUIT)
break; break;
if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE)

@ -287,7 +287,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit,
if (mask && mask->profession()) if (mask && mask->profession())
{ {
if (unit->profession >= 0) if (unit->profession >= (df::profession)0)
info->set_profession(unit->profession); info->set_profession(unit->profession);
if (!unit->custom_profession.empty()) if (!unit->custom_profession.empty())
info->set_custom_profession(unit->custom_profession); info->set_custom_profession(unit->custom_profession);

@ -438,6 +438,8 @@ void VMethodInterposeLinkBase::remove()
if (next) if (next)
prev->child_next.insert(next); prev->child_next.insert(next);
else
prev->child_hosts.insert(host);
} }
} }

@ -518,7 +518,7 @@ namespace DFHack {
template<class T> template<class T>
inline const char *enum_item_raw_key(T val) { inline const char *enum_item_raw_key(T val) {
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
return traits::is_valid(val) ? traits::key_table[val - traits::first_item_value] : NULL; return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL;
} }
/** /**

@ -85,7 +85,7 @@ namespace df {
static const bool is_method = true; \ static const bool is_method = true; \
}; };
#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ #define INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \
template<FW_TARGS> struct function_wrapper<void (*) FArgs, true> { \ template<FW_TARGS> struct function_wrapper<void (*) FArgs, true> { \
static const int num_args = Count; \ static const int num_args = Count; \
static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \ static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \
@ -105,79 +105,103 @@ namespace df {
LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \
}; };
#define INSTANTIATE_WRAPPERS(Count, FArgs, OFArgs, Args, OArgs, Loads) \
INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \
INSTANTIATE_WRAPPERS2(Count, OFArgs, OArgs, LOAD_OSTREAM(out); Loads)
#define FW_TARGSC #define FW_TARGSC
#define FW_TARGS #define FW_TARGS
INSTANTIATE_RETURN_TYPE(()) INSTANTIATE_RETURN_TYPE(())
INSTANTIATE_WRAPPERS(0, (), (), ;) INSTANTIATE_WRAPPERS(0, (), (OSTREAM_ARG), (), (out), ;)
INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);)
#undef FW_TARGS #undef FW_TARGS
#undef FW_TARGSC #undef FW_TARGSC
#define FW_TARGSC FW_TARGS, #define FW_TARGSC FW_TARGS,
#define FW_TARGS class A1 #define FW_TARGS class A1
INSTANTIATE_RETURN_TYPE((A1)) INSTANTIATE_RETURN_TYPE((A1))
INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);) INSTANTIATE_WRAPPERS(1, (A1), (OSTREAM_ARG,A1), (vA1), (out,vA1), LOAD_ARG(A1);)
INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2 #define FW_TARGS class A1, class A2
INSTANTIATE_RETURN_TYPE((A1,A2)) INSTANTIATE_RETURN_TYPE((A1,A2))
INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);) INSTANTIATE_WRAPPERS(2, (A1,A2), (OSTREAM_ARG,A1,A2), (vA1,vA2), (out,vA1,vA2),
INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);)
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3 #define FW_TARGS class A1, class A2, class A3
INSTANTIATE_RETURN_TYPE((A1,A2,A3)) INSTANTIATE_RETURN_TYPE((A1,A2,A3))
INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (OSTREAM_ARG,A1,A2,A3), (vA1,vA2,vA3), (out,vA1,vA2,vA3),
INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);)
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4 #define FW_TARGS class A1, class A2, class A3, class A4
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4))
INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (OSTREAM_ARG,A1,A2,A3,A4),
(vA1,vA2,vA3,vA4), (out,vA1,vA2,vA3,vA4),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);)
INSTANTIATE_WRAPPERS(4, (OSTREAM_ARG,A1,A2,A3,A4), (out,vA1,vA2,vA3,vA4),
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5 #define FW_TARGS class A1, class A2, class A3, class A4, class A5
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5)) INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5))
INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5), INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (OSTREAM_ARG,A1,A2,A3,A4,A5),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);) (vA1,vA2,vA3,vA4,vA5), (out,vA1,vA2,vA3,vA4,vA5),
INSTANTIATE_WRAPPERS(5, (OSTREAM_ARG,A1,A2,A3,A4,A5), (out,vA1,vA2,vA3,vA4,vA5), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A5);)
LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6 #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6)) INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6))
INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (vA1,vA2,vA3,vA4,vA5,vA6), INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); (vA1,vA2,vA3,vA4,vA5,vA6), (out,vA1,vA2,vA3,vA4,vA5,vA6),
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);) LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
INSTANTIATE_WRAPPERS(6, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6), (out,vA1,vA2,vA3,vA4,vA5,vA6), LOAD_ARG(A5); LOAD_ARG(A6);)
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7 #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7)) INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7))
INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (vA1,vA2,vA3,vA4,vA5,vA6,vA7), INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); (vA1,vA2,vA3,vA4,vA5,vA6,vA7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7),
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A7);) LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);)
INSTANTIATE_WRAPPERS(7, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7),
LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);)
#undef FW_TARGS #undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8 #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8)) INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8))
INSTANTIATE_WRAPPERS(8, (A1,A2,A3,A4,A5,A6,A7,A8), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8),
(vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9))
INSTANTIATE_WRAPPERS(9, (A1,A2,A3,A4,A5,A6,A7,A8,A9),
(OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9),
(vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9),
(out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
LOAD_ARG(A9);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10))
INSTANTIATE_WRAPPERS(10, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10),
(OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10),
(vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10),
(out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
LOAD_ARG(A9); LOAD_ARG(A10);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11))
#undef FW_TARGS #undef FW_TARGS
#undef FW_TARGSC #undef FW_TARGSC
#undef INSTANTIATE_WRAPPERS #undef INSTANTIATE_WRAPPERS
#undef INSTANTIATE_WRAPPERS2
#undef INVOKE_VOID #undef INVOKE_VOID
#undef INVOKE_RV #undef INVOKE_RV
#undef LOAD_CLASS #undef LOAD_CLASS

@ -287,6 +287,11 @@ namespace DFHack {namespace Lua {
PushDFObject(state, ptr); PushDFObject(state, ptr);
} }
template<class T> inline void SetField(lua_State *L, T val, int idx, const char *name) {
if (idx < 0) idx = lua_absindex(L, idx);
Push(L, val); lua_setfield(L, idx, name);
}
template<class T> template<class T>
void PushVector(lua_State *state, const T &pvec, bool addn = false) void PushVector(lua_State *state, const T &pvec, bool addn = false)
{ {

@ -88,7 +88,7 @@ namespace DFHack
{ {
typedef df::enum_traits<T> traits; typedef df::enum_traits<T> traits;
int base = traits::first_item; int base = traits::first_item;
int size = traits::last_item - base + 1; int size = (int)traits::last_item - base + 1;
describeEnum(pf, base, size, traits::key_table); describeEnum(pf, base, size, traits::key_table);
} }

@ -44,6 +44,7 @@ distribution.
namespace df namespace df
{ {
struct itemdef; struct itemdef;
struct proj_itemst;
} }
namespace MapExtras { namespace MapExtras {
@ -155,5 +156,11 @@ DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::
DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode);
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
/// Makes the item removed and marked for garbage collection
DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false);
/// Detaches the items from its current location and turns it into a projectile
DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item *item);
} }
} }

@ -131,6 +131,11 @@ namespace DFHack
bool findPlant(const std::string &token, const std::string &subtoken); bool findPlant(const std::string &token, const std::string &subtoken);
bool findCreature(const std::string &token, const std::string &subtoken); bool findCreature(const std::string &token, const std::string &subtoken);
bool findProduct(df::material *material, const std::string &name);
bool findProduct(const MaterialInfo &info, const std::string &name) {
return findProduct(info.material, name);
}
std::string getToken(); std::string getToken();
std::string toString(uint16_t temp = 10015, bool named = true); std::string toString(uint16_t temp = 10015, bool named = true);

@ -32,6 +32,10 @@ distribution.
#include "modules/Items.h" #include "modules/Items.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/misc_trait_type.h"
#include "df/physical_attribute_type.h"
#include "df/mental_attribute_type.h"
#include "df/job_skill.h"
namespace df namespace df
{ {
@ -41,6 +45,7 @@ namespace df
struct historical_entity; struct historical_entity;
struct entity_position_assignment; struct entity_position_assignment;
struct entity_position; struct entity_position;
struct unit_misc_trait;
} }
/** /**
@ -208,6 +213,18 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit);
DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit);
DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit);
DFHACK_EXPORT bool isHidingCurse(df::unit *unit);
DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr);
DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr);
DFHACK_EXPORT bool isCrazed(df::unit *unit);
DFHACK_EXPORT bool isOpposedToLife(df::unit *unit);
DFHACK_EXPORT bool hasExtravision(df::unit *unit);
DFHACK_EXPORT bool isBloodsucker(df::unit *unit);
DFHACK_EXPORT bool isMischievous(df::unit *unit);
DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false);
DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit);
DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit);
DFHACK_EXPORT bool isSane(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit);
@ -216,6 +233,9 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit);
DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false);
DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id);
DFHACK_EXPORT int computeMovementSpeed(df::unit *unit);
struct NoblePosition { struct NoblePosition {
df::historical_entity *entity; df::historical_entity *entity;
df::entity_position_assignment *assignment; df::entity_position_assignment *assignment;

@ -0,0 +1,150 @@
-- A trivial reloadable class system
local _ENV = mkmodule('class')
-- Metatable template for a class
class_obj = {} or class_obj
-- Methods shared by all classes
common_methods = {} or common_methods
-- Forbidden names for class fields and methods.
reserved_names = { super = true, ATTRS = true }
-- Attribute table metatable
attrs_meta = {} or attrs_meta
-- Create or updates a class; a class has metamethods and thus own metatable.
function defclass(class,parent)
class = class or {}
local meta = getmetatable(class)
if not meta then
meta = {}
setmetatable(class, meta)
end
for k,v in pairs(class_obj) do meta[k] = v end
meta.__index = parent or common_methods
local attrs = rawget(class, 'ATTRS') or {}
setmetatable(attrs, attrs_meta)
rawset(class, 'super', parent)
rawset(class, 'ATTRS', attrs)
rawset(class, '__index', rawget(class, '__index') or class)
return class
end
-- An instance uses the class as metatable
function mkinstance(class,table)
table = table or {}
setmetatable(table, class)
return table
end
-- Patch the stubs in the global environment
dfhack.BASE_G.defclass = _ENV.defclass
dfhack.BASE_G.mkinstance = _ENV.mkinstance
-- Just verify the name, and then set.
function class_obj:__newindex(name,val)
if reserved_names[name] or common_methods[name] then
error('Method name '..name..' is reserved.')
end
rawset(self, name, val)
end
function attrs_meta:__call(attrs)
for k,v in pairs(attrs) do
self[k] = v
end
end
local function apply_attrs(obj, attrs, init_table)
for k,v in pairs(attrs) do
if v == DEFAULT_NIL then
v = nil
end
obj[k] = init_table[k] or v
end
end
local function invoke_before_rec(self, class, method, ...)
local meta = getmetatable(class)
if meta then
local fun = rawget(class, method)
if fun then
fun(self, ...)
end
invoke_before_rec(self, meta.__index, method, ...)
end
end
local function invoke_after_rec(self, class, method, ...)
local meta = getmetatable(class)
if meta then
invoke_after_rec(self, meta.__index, method, ...)
local fun = rawget(class, method)
if fun then
fun(self, ...)
end
end
end
local function init_attrs_rec(obj, class, init_table)
local meta = getmetatable(class)
if meta then
init_attrs_rec(obj, meta.__index, init_table)
apply_attrs(obj, rawget(class, 'ATTRS'), init_table)
end
end
-- Call metamethod constructs the object
function class_obj:__call(init_table)
-- The table is assumed to be a scratch temporary.
-- If it is not, copy it yourself before calling.
init_table = init_table or {}
local obj = mkinstance(self)
-- This initialization sequence is broadly based on how the
-- Common Lisp initialize-instance generic function works.
-- preinit screens input arguments in subclass to superclass order
invoke_before_rec(obj, self, 'preinit', init_table)
-- initialize the instance table from init table
init_attrs_rec(obj, self, init_table)
-- init in superclass -> subclass
invoke_after_rec(obj, self, 'init', init_table)
-- postinit in superclass -> subclass
invoke_after_rec(obj, self, 'postinit', init_table)
return obj
end
-- Common methods for all instances:
function common_methods:callback(method, ...)
return dfhack.curry(self[method], self, ...)
end
function common_methods:assign(data)
for k,v in pairs(data) do
self[k] = v
end
end
function common_methods:invoke_before(method, ...)
return invoke_before_rec(self, getmetatable(self), method, ...)
end
function common_methods:invoke_after(method, ...)
return invoke_after_rec(self, getmetatable(self), method, ...)
end
return _ENV

@ -113,26 +113,14 @@ function rawset_default(target,source)
end end
end end
function defclass(class,parent) DEFAULT_NIL = DEFAULT_NIL or {} -- Unique token
class = class or {}
rawset_default(class, { __index = class }) function defclass(...)
if parent then return require('class').defclass(...)
setmetatable(class, parent)
else
rawset_default(class, {
init_fields = rawset_default,
callback = function(self, name, ...)
return dfhack.curry(self[name], self, ...)
end
})
end
return class
end end
function mkinstance(class,table) function mkinstance(...)
table = table or {} return require('class').mkinstance(...)
setmetatable(table, class)
return table
end end
-- Misc functions -- Misc functions
@ -169,6 +157,23 @@ function xyz2pos(x,y,z)
end end
end end
function pos2xy(pos)
if pos then
local x = pos.x
if x and x ~= -30000 then
return x, pos.y
end
end
end
function xy2pos(x,y)
if x then
return {x=x,y=y}
else
return {x=-30000,y=-30000}
end
end
function safe_index(obj,idx,...) function safe_index(obj,idx,...)
if obj == nil or idx == nil then if obj == nil or idx == nil then
return nil return nil

@ -74,18 +74,23 @@ end
Painter = defclass(Painter, nil) Painter = defclass(Painter, nil)
function Painter.new(rect, pen) function Painter:init(args)
rect = rect or mkdims_wh(0,0,dscreen.getWindowSize()) local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
local self = { local crect = args.clip_rect or rect
x1 = rect.x1, clip_x1 = rect.x1, self:assign{
y1 = rect.y1, clip_y1 = rect.y1, x = rect.x1, y = rect.y1,
x2 = rect.x2, clip_x2 = rect.x2, x1 = rect.x1, clip_x1 = crect.x1,
y2 = rect.y2, clip_y2 = rect.y2, y1 = rect.y1, clip_y1 = crect.y1,
x2 = rect.x2, clip_x2 = crect.x2,
y2 = rect.y2, clip_y2 = crect.y2,
width = rect.x2-rect.x1+1, width = rect.x2-rect.x1+1,
height = rect.y2-rect.y1+1, height = rect.y2-rect.y1+1,
cur_pen = to_pen(nil, pen or COLOR_GREY) cur_pen = to_pen(nil, args.pen or COLOR_GREY)
} }
return mkinstance(Painter, self):seek(0,0) end
function Painter.new(rect, pen)
return Painter{ rect = rect, pen = pen }
end end
function Painter:isValidPos() function Painter:isValidPos()
@ -213,9 +218,8 @@ Screen = defclass(Screen)
Screen.text_input_mode = false Screen.text_input_mode = false
function Screen:init() function Screen:postinit()
self:updateLayout() self:updateLayout()
return self
end end
Screen.isDismissed = dscreen.isDismissed Screen.isDismissed = dscreen.isDismissed
@ -344,7 +348,12 @@ end
FramedScreen = defclass(FramedScreen, Screen) FramedScreen = defclass(FramedScreen, Screen)
FramedScreen.frame_style = BOUNDARY_FRAME FramedScreen.ATTRS{
frame_style = BOUNDARY_FRAME,
frame_title = DEFAULT_NIL,
frame_width = DEFAULT_NIL,
frame_height = DEFAULT_NIL,
}
local function hint_coord(gap,hint) local function hint_coord(gap,hint)
if hint and hint > 0 then if hint and hint > 0 then

@ -10,24 +10,21 @@ local dscreen = dfhack.screen
MessageBox = defclass(MessageBox, gui.FramedScreen) MessageBox = defclass(MessageBox, gui.FramedScreen)
MessageBox.focus_path = 'MessageBox' MessageBox.focus_path = 'MessageBox'
MessageBox.frame_style = gui.GREY_LINE_FRAME
MessageBox.ATTRS{
function MessageBox:init(info) frame_style = gui.GREY_LINE_FRAME,
info = info or {} -- new attrs
self:init_fields{ text = {},
text = info.text or {}, on_accept = DEFAULT_NIL,
frame_title = info.title, on_cancel = DEFAULT_NIL,
frame_width = info.frame_width, on_close = DEFAULT_NIL,
on_accept = info.on_accept, text_pen = DEFAULT_NIL,
on_cancel = info.on_cancel, }
on_close = info.on_close,
text_pen = info.text_pen function MessageBox:preinit(info)
} if type(info.text) == 'string' then
if type(self.text) == 'string' then info.text = utils.split_string(info.text, "\n")
self.text = utils.split_string(self.text, "\n")
end end
gui.FramedScreen.init(self, info)
return self
end end
function MessageBox:getWantedFrameSize() function MessageBox:getWantedFrameSize()
@ -82,9 +79,8 @@ function MessageBox:onInput(keys)
end end
function showMessage(title, text, tcolor, on_close) function showMessage(title, text, tcolor, on_close)
mkinstance(MessageBox):init{ MessageBox{
text = text, frame_title = title,
title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
on_close = on_close on_close = on_close
@ -92,8 +88,8 @@ function showMessage(title, text, tcolor, on_close)
end end
function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
mkinstance(MessageBox):init{ MessageBox{
title = title, frame_title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
on_accept = on_accept, on_accept = on_accept,
@ -105,25 +101,23 @@ InputBox = defclass(InputBox, MessageBox)
InputBox.focus_path = 'InputBox' InputBox.focus_path = 'InputBox'
function InputBox:init(info) InputBox.ATTRS{
info = info or {} input = '',
self:init_fields{ input_pen = DEFAULT_NIL,
input = info.input or '', on_input = DEFAULT_NIL,
input_pen = info.input_pen, }
on_input = info.on_input,
} function InputBox:preinit(info)
MessageBox.init(self, info) info.on_accept = nil
self.on_accept = nil
return self
end end
function InputBox:getWantedFrameSize() function InputBox:getWantedFrameSize()
local mw, mh = MessageBox.getWantedFrameSize(self) local mw, mh = InputBox.super.getWantedFrameSize(self)
return mw, mh+2 return mw, mh+2
end end
function InputBox:onRenderBody(dc) function InputBox:onRenderBody(dc)
MessageBox.onRenderBody(self, dc) InputBox.super.onRenderBody(self, dc)
dc:newline(1) dc:newline(1)
dc:pen(self.input_pen or COLOR_LIGHTCYAN) dc:pen(self.input_pen or COLOR_LIGHTCYAN)
@ -161,8 +155,8 @@ function InputBox:onInput(keys)
end end
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
mkinstance(InputBox):init{ InputBox{
title = title, frame_title = title,
text = text, text = text,
text_pen = tcolor, text_pen = tcolor,
input = input, input = input,
@ -172,5 +166,103 @@ function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_wi
}:show() }:show()
end end
ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox'
ListBox.ATTRS{
selection = 0,
choices = {},
select_pen = DEFAULT_NIL,
on_input = DEFAULT_NIL
}
function InputBox:preinit(info)
info.on_accept = nil
end
function ListBox:init(info)
self.page_top = 0
end
function ListBox:getWantedFrameSize()
local mw, mh = ListBox.super.getWantedFrameSize(self)
return mw, mh+#self.choices
end
function ListBox:onRenderBody(dc)
ListBox.super.onRenderBody(self, dc)
dc:newline(1)
if self.selection>dc.height-3 then
self.page_top=self.selection-(dc.height-3)
elseif self.selection<self.page_top and self.selection >0 then
self.page_top=self.selection-1
end
for i,entry in ipairs(self.choices) do
if type(entry)=="table" then
entry=entry[1]
end
if i>self.page_top then
if i == self.selection then
dc:pen(self.select_pen or COLOR_LIGHTCYAN)
else
dc:pen(self.text_pen or COLOR_GREY)
end
dc:string(entry)
dc:newline(1)
end
end
end
function ListBox:moveCursor(delta)
local newsel=self.selection+delta
if #self.choices ~=0 then
if newsel<1 or newsel>#self.choices then
newsel=newsel % #self.choices
end
end
self.selection=newsel
end
function ListBox:onInput(keys)
if keys.SELECT then
self:dismiss()
local choice=self.choices[self.selection]
if self.on_input then
self.on_input(self.selection,choice)
end
if choice and choice[2] then
choice[2](choice,self.selection) -- maybe reverse the arguments?
end
elseif keys.LEAVESCREEN then
self:dismiss()
if self.on_cancel then
self.on_cancel()
end
elseif keys.CURSOR_UP then
self:moveCursor(-1)
elseif keys.CURSOR_DOWN then
self:moveCursor(1)
elseif keys.CURSOR_UP_FAST then
self:moveCursor(-10)
elseif keys.CURSOR_DOWN_FAST then
self:moveCursor(10)
end
end
function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width)
ListBox{
frame_title = title,
text = text,
text_pen = tcolor,
choices = choices,
on_input = on_input,
on_cancel = on_cancel,
frame_width = min_width,
}:show()
end
return _ENV return _ENV

@ -46,7 +46,7 @@ function getPanelLayout()
end end
function getCursorPos() function getCursorPos()
if g_cursor ~= -30000 then if g_cursor.x ~= -30000 then
return copyall(g_cursor) return copyall(g_cursor)
end end
end end
@ -136,6 +136,14 @@ function Viewport:set()
return vp return vp
end end
function Viewport:getPos()
return xyz2pos(self.x1, self.y1, self.z)
end
function Viewport:getSize()
return xy2pos(self.width, self.height)
end
function Viewport:clip(x,y,z) function Viewport:clip(x,y,z)
return self:make( return self:make(
math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), math.max(0, math.min(x or self.x1, world_map.x_count-self.width)),
@ -159,6 +167,18 @@ function Viewport:isVisible(target,gap)
return self:isVisibleXY(target,gap) and target.z == self.z return self:isVisibleXY(target,gap) and target.z == self.z
end end
function Viewport:tileToScreen(coord)
return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z)
end
function Viewport:getCenter()
return xyz2pos(
math.floor((self.x2+self.x1)/2),
math.floor((self.y2+self.y1)/2),
self.z
)
end
function Viewport:centerOn(target) function Viewport:centerOn(target)
return self:clip( return self:clip(
target.x - math.floor(self.width/2), target.x - math.floor(self.width/2),
@ -207,16 +227,24 @@ MOVEMENT_KEYS = {
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
} }
function Viewport:scrollByKey(key) local function get_movement_delta(key, delta, big_step)
local info = MOVEMENT_KEYS[key] local info = MOVEMENT_KEYS[key]
if info then if info then
local delta = 10 if info[4] then
if info[4] then delta = 20 end delta = big_step
end
return delta*info[1], delta*info[2], info[3]
end
end
function Viewport:scrollByKey(key)
local dx, dy, dz = get_movement_delta(key, 10, 20)
if dx then
return self:clip( return self:clip(
self.x1 + delta*info[1], self.x1 + dx,
self.y1 + delta*info[2], self.y1 + dy,
self.z + info[3] self.z + dz
) )
else else
return self return self
@ -237,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp)
end end
end end
function DwarfOverlay:moveCursorTo(cursor,viewport) function DwarfOverlay:moveCursorTo(cursor,viewport,gap)
setCursorPos(cursor) setCursorPos(cursor)
self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() self:zoomViewportTo(cursor,viewport,gap)
end
function DwarfOverlay:zoomViewportTo(target, viewport, gap)
if gap and self:getViewport():isVisible(target, gap) then
return
end
self:getViewport(viewport):reveal(target, 5, 0, 10):set()
end end
function DwarfOverlay:selectBuilding(building,cursor,viewport) function DwarfOverlay:selectBuilding(building,cursor,viewport,gap)
cursor = cursor or utils.getBuildingCenter(building) cursor = cursor or utils.getBuildingCenter(building)
df.global.world.selected_building = building df.global.world.selected_building = building
self:moveCursorTo(cursor, viewport) self:moveCursorTo(cursor, viewport, gap)
end end
function DwarfOverlay:propagateMoveKeys(keys) function DwarfOverlay:propagateMoveKeys(keys)
@ -282,6 +317,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor)
end end
end end
function DwarfOverlay:simulateCursorMovement(keys, anchor)
local layout = self.df_layout
local cursor = getCursorPos()
local cx, cy, cz = pos2xyz(cursor)
if anchor and keys.A_MOVE_SAME_SQUARE then
setCursorPos(anchor)
self:getViewport():centerOn(anchor):set()
return 'A_MOVE_SAME_SQUARE'
end
for code,_ in pairs(MOVEMENT_KEYS) do
if keys[code] then
local dx, dy, dz = get_movement_delta(code, 1, 10)
local ncur = xyz2pos(cx+dx, cy+dy, cz+dz)
if dfhack.maps.isValidTilePos(ncur) then
setCursorPos(ncur)
self:getViewport():reveal(ncur,4,10,6,true):set()
return code
end
end
end
end
function DwarfOverlay:onAboutToShow(below) function DwarfOverlay:onAboutToShow(below)
local screen = dfhack.gui.getCurViewscreen() local screen = dfhack.gui.getCurViewscreen()
if below then screen = below.parent end if below then screen = below.parent end
@ -293,7 +353,7 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:updateLayout() function MenuOverlay:updateLayout()
DwarfOverlay.updateLayout(self) MenuOverlay.super.updateLayout(self)
self.frame_rect = self.df_layout.menu self.frame_rect = self.df_layout.menu
end end
@ -301,7 +361,7 @@ MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize
MenuOverlay.getMousePos = gui.FramedScreen.getMousePos MenuOverlay.getMousePos = gui.FramedScreen.getMousePos
function MenuOverlay:onAboutToShow(below) function MenuOverlay:onAboutToShow(below)
DwarfOverlay.onAboutToShow(self,below) MenuOverlay.super.onAboutToShow(self,below)
self:updateLayout() self:updateLayout()
if not self.df_layout.menu then if not self.df_layout.menu then

@ -54,7 +54,7 @@ using namespace DFHack;
#include "df/viewscreen_joblistst.h" #include "df/viewscreen_joblistst.h"
#include "df/viewscreen_unitlistst.h" #include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_itemst.h" #include "df/viewscreen_itemst.h"
#include "df/viewscreen_layerst.h" #include "df/viewscreen_layer.h"
#include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_workshop_profilest.h"
#include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h" #include "df/viewscreen_layer_overall_healthst.h"
@ -95,7 +95,7 @@ using df::global::selection_rect;
using df::global::ui_menu_width; using df::global::ui_menu_width;
using df::global::ui_area_map_width; using df::global::ui_area_map_width;
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx) static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx)
{ {
return virtual_cast<df::layer_object_listst>(vector_get(layer->layer_objects,idx)); return virtual_cast<df::layer_object_listst>(vector_get(layer->layer_objects,idx));
} }
@ -173,10 +173,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
else if (id == &df::building_trapst::_identity) else if (id == &df::building_trapst::_identity)
{ {
auto trap = (df::building_trapst*)selected; auto trap = (df::building_trapst*)selected;
if (trap->trap_type == trap_type::Lever) { focus += "/" + enum_item_key(trap->trap_type);
focus += "/Lever"; if (trap->trap_type == trap_type::Lever)
jobs = true; jobs = true;
}
} }
else if (ui_building_in_assign && *ui_building_in_assign && else if (ui_building_in_assign && *ui_building_in_assign &&
ui_building_assign_type && ui_building_assign_units && ui_building_assign_type && ui_building_assign_units &&
@ -189,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode)
focus += unit ? "/Unit" : "/None"; focus += unit ? "/Unit" : "/None";
} }
} }
else
focus += "/" + enum_item_key(selected->getType());
if (jobs) if (jobs)
{ {
@ -331,9 +332,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military)
focus += "/" + enum_item_key(screen->page); focus += "/" + enum_item_key(screen->page);
int cur_list; int cur_list;
if (list1->bright) cur_list = 0; if (list1->active) cur_list = 0;
else if (list2->bright) cur_list = 1; else if (list2->active) cur_list = 1;
else if (list3->bright) cur_list = 2; else if (list3->active) cur_list = 2;
else return; else return;
switch (screen->page) switch (screen->page)
@ -419,7 +420,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_assigntrade)
if (unsigned(list_idx) >= num_lists) if (unsigned(list_idx) >= num_lists)
return; return;
if (list1->bright) if (list1->active)
focus += "/Groups"; focus += "/Groups";
else else
focus += "/Items"; focus += "/Items";
@ -457,10 +458,10 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
focus += "/On"; focus += "/On";
if (list2->bright || list3->bright || screen->list_ids.empty()) { if (list2->active || list3->active || screen->list_ids.empty()) {
focus += "/" + enum_item_key(screen->cur_list); focus += "/" + enum_item_key(screen->cur_list);
if (list3->bright) if (list3->active)
focus += (screen->item_names.empty() ? "/None" : "/Item"); focus += (screen->item_names.empty() ? "/None" : "/Item");
} }
} }
@ -843,7 +844,7 @@ static df::item *getAnyItem(df::viewscreen *top)
{ {
auto list1 = getLayerList(screen, 0); auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1); auto list2 = getLayerList(screen, 1);
if (!list1 || !list2 || !list2->bright) if (!list1 || !list2 || !list2->active)
return NULL; return NULL;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1); int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);

@ -72,8 +72,11 @@ using namespace std;
#include "df/general_ref_contains_itemst.h" #include "df/general_ref_contains_itemst.h"
#include "df/general_ref_contained_in_itemst.h" #include "df/general_ref_contained_in_itemst.h"
#include "df/general_ref_building_holderst.h" #include "df/general_ref_building_holderst.h"
#include "df/general_ref_projectile.h"
#include "df/viewscreen_itemst.h" #include "df/viewscreen_itemst.h"
#include "df/vermin.h" #include "df/vermin.h"
#include "df/proj_itemst.h"
#include "df/proj_list_link.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/body_part_raw.h" #include "df/body_part_raw.h"
@ -88,6 +91,7 @@ using namespace df::enums;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
using df::global::ui_selected_unit; using df::global::ui_selected_unit;
using df::global::proj_next_id;
#define ITEMDEF_VECTORS \ #define ITEMDEF_VECTORS \
ITEM(WEAPON, weapons, itemdef_weaponst) \ ITEM(WEAPON, weapons, itemdef_weaponst) \
@ -726,6 +730,18 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
item->flags.bits.in_inventory = false; item->flags.bits.in_inventory = false;
return true; return true;
} }
else if (item->flags.bits.removed)
{
item->flags.bits.removed = false;
if (item->flags.bits.garbage_collect)
{
item->flags.bits.garbage_collect = false;
item->categorize(true);
}
return true;
}
else else
return false; return false;
} }
@ -866,3 +882,64 @@ bool DFHack::Items::moveToInventory(
return true; return true;
} }
bool Items::remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat)
{
CHECK_NULL_POINTER(item);
auto pos = getPosition(item);
if (!detachItem(mc, item))
return false;
if (pos.isValid())
item->pos = pos;
if (!no_uncat)
item->uncategorize();
item->flags.bits.removed = true;
item->flags.bits.garbage_collect = !no_uncat;
return true;
}
df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
{
CHECK_NULL_POINTER(item);
if (!world || !proj_next_id)
return NULL;
auto pos = getPosition(item);
if (!pos.isValid())
return NULL;
auto ref = df::allocate<df::general_ref_projectile>();
if (!ref)
return NULL;
if (!detachItem(mc, item))
{
delete ref;
return NULL;
}
item->pos = pos;
item->flags.bits.in_job = true;
auto proj = new df::proj_itemst();
proj->link = new df::proj_list_link();
proj->link->item = proj;
proj->id = (*proj_next_id)++;
proj->origin_pos = proj->target_pos = pos;
proj->cur_pos = proj->prev_pos = pos;
proj->item = item;
ref->projectile_id = proj->id;
item->itemrefs.push_back(ref);
linked_list_append(&world->proj_list, proj->link);
return proj;
}

@ -181,7 +181,7 @@ void DFHack::Job::printItemDetails(color_ostream &out, df::job_item *item, int i
out << " reaction class: " << item->reaction_class << endl; out << " reaction class: " << item->reaction_class << endl;
if (!item->has_material_reaction_product.empty()) if (!item->has_material_reaction_product.empty())
out << " reaction product: " << item->has_material_reaction_product << endl; out << " reaction product: " << item->has_material_reaction_product << endl;
if (item->has_tool_use >= 0) if (item->has_tool_use >= (df::tool_uses)0)
out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl; out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl;
} }

@ -307,7 +307,7 @@ df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index)
df::coord2d bigregion = rgn_pos / 16; df::coord2d bigregion = rgn_pos / 16;
// bigregion is 16x16 regions. for each bigregion in X dimension: // bigregion is 16x16 regions. for each bigregion in X dimension:
auto fptr = data->unk_204[bigregion.x][bigregion.y].features; auto fptr = data->feature_map[bigregion.x][bigregion.y].features;
if (!fptr) if (!fptr)
return NULL; return NULL;

@ -283,6 +283,19 @@ bool MaterialInfo::findCreature(const std::string &token, const std::string &sub
return decode(-1); return decode(-1);
} }
bool MaterialInfo::findProduct(df::material *material, const std::string &name)
{
if (!material || name.empty())
return decode(-1);
auto &pids = material->reaction_product.id;
for (size_t i = 0; i < pids.size(); i++)
if ((*pids[i]) == name)
return decode(material->reaction_product.material, i);
return decode(-1);
}
std::string MaterialInfo::getToken() std::string MaterialInfo::getToken()
{ {
if (isNone()) if (isNone())

@ -63,11 +63,15 @@ using namespace std;
#include "df/burrow.h" #include "df/burrow.h"
#include "df/creature_raw.h" #include "df/creature_raw.h"
#include "df/caste_raw.h" #include "df/caste_raw.h"
#include "df/game_mode.h"
#include "df/unit_misc_trait.h"
#include "df/unit_skill.h"
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
using df::global::world; using df::global::world;
using df::global::ui; using df::global::ui;
using df::global::gamemode;
bool Units::isValid() bool Units::isValid()
{ {
@ -613,6 +617,58 @@ df::nemesis_record *Units::getNemesis(df::unit *unit)
return NULL; return NULL;
} }
bool Units::isHidingCurse(df::unit *unit)
{
if (!unit->job.hunt_target)
{
auto identity = Units::getIdentity(unit);
if (identity && identity->unk_4c == 0)
return true;
}
return false;
}
int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr)
{
auto &aobj = unit->body.physical_attrs[attr];
int value = std::max(0, aobj.value - aobj.soft_demotion);
if (auto mod = unit->curse.attr_change)
{
int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr];
if (isHidingCurse(unit))
value = std::min(value, mvalue);
else
value = mvalue;
}
return std::max(0, value);
}
int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr)
{
auto soul = unit->status.current_soul;
if (!soul) return 0;
auto &aobj = soul->mental_attrs[attr];
int value = std::max(0, aobj.value - aobj.soft_demotion);
if (auto mod = unit->curse.attr_change)
{
int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr];
if (isHidingCurse(unit))
value = std::min(value, mvalue);
else
value = mvalue;
}
return std::max(0, value);
}
static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
{ {
auto creature = df::creature_raw::find(race); auto creature = df::creature_raw::find(race);
@ -626,8 +682,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag)
return craw->flags.is_set(flag); return craw->flags.is_set(flag);
} }
static bool isCrazed(df::unit *unit) bool Units::isCrazed(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit);
if (unit->flags3.bits.scuttle) if (unit->flags3.bits.scuttle)
return false; return false;
if (unit->curse.rem_tags1.bits.CRAZED) if (unit->curse.rem_tags1.bits.CRAZED)
@ -637,13 +694,64 @@ static bool isCrazed(df::unit *unit)
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED);
} }
static bool isOpposedToLife(df::unit *unit) bool Units::isOpposedToLife(df::unit *unit)
{ {
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE)
return false; return false;
if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE)
return true; return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD); return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE);
}
bool Units::hasExtravision(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.EXTRAVISION)
return false;
if (unit->curse.add_tags1.bits.EXTRAVISION)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION);
}
bool Units::isBloodsucker(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.BLOODSUCKER)
return false;
if (unit->curse.add_tags1.bits.BLOODSUCKER)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER);
}
bool Units::isMischievous(df::unit *unit)
{
CHECK_NULL_POINTER(unit);
if (unit->curse.rem_tags1.bits.MISCHIEVOUS)
return false;
if (unit->curse.add_tags1.bits.MISCHIEVOUS)
return true;
return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS);
}
df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create)
{
CHECK_NULL_POINTER(unit);
auto &vec = unit->status.misc_traits;
for (size_t i = 0; i < vec.size(); i++)
if (vec[i]->id == type)
return vec[i];
if (create)
{
auto obj = new df::unit_misc_trait();
obj->id = type;
vec.push_back(obj);
return obj;
}
return NULL;
} }
bool DFHack::Units::isDead(df::unit *unit) bool DFHack::Units::isDead(df::unit *unit)
@ -753,6 +861,371 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age)
return cur_time - birth_time; return cur_time - birth_time;
} }
inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2)
{
if (is_adventure)
{
if (value >= adv1_2) rating >>= 1;
else if (value >= adv3_4) rating = rating*3/4;
else if (value >= adv9_10) rating = rating*9/10;
}
else
{
if (value >= dwarf1_2) rating >>= 1;
else if (value >= dwarf3_4) rating = rating*3/4;
}
}
int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id)
{
CHECK_NULL_POINTER(unit);
/*
* This is 100% reverse-engineered from DF code.
*/
if (!unit->status.current_soul)
return 0;
// 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, key);
int rating = 0;
if (skill)
rating = std::max(0, int(skill->rating) - skill->rusty);
// Apply special states
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) rating >>= 1;
if (unit->counters.winded > 0) rating >>= 1;
if (unit->counters.stunned > 0) rating >>= 1;
if (unit->counters.dizziness > 0) rating >>= 1;
if (unit->counters2.fever > 0) rating >>= 1;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle &&
!unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged &&
!hasExtravision(unit))
{
rating >>= 2;
}
if (unit->counters.pain >= 100 && unit->mood == -1)
{
rating >>= 1;
}
if (unit->counters2.exhaustion >= 2000)
{
rating = rating*3/4;
if (unit->counters2.exhaustion >= 4000)
{
rating = rating*3/4;
if (unit->counters2.exhaustion >= 6000)
rating = rating*3/4;
}
}
}
// Hunger etc timers
bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
if (!unit->flags3.bits.scuttle && isBloodsucker(unit))
{
using namespace df::enums::misc_trait_type;
if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood))
{
adjust_skill_rating(
rating, is_adventure, trait->value,
302400, 403200, // dwf 3/4; 1/2
1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2
);
}
}
adjust_skill_rating(
rating, is_adventure, unit->counters2.thirst_timer,
50000, 50000, 115200, 172800, 345600
);
adjust_skill_rating(
rating, is_adventure, unit->counters2.hunger_timer,
75000, 75000, 172800, 1209600, 2592000
);
if (is_adventure && unit->counters2.sleepiness_timer >= 846000)
rating >>= 2;
else
adjust_skill_rating(
rating, is_adventure, unit->counters2.sleepiness_timer,
150000, 150000, 172800, 259200, 345600
);
return rating;
}
inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200)
{
if (is_adventure)
{
if (value >= adv200) rating += 200;
else if (value >= adv100) rating += 100;
else if (value >= adv75) rating += 75;
else if (value >= adv50) rating += 50;
}
else
{
if (value >= dwarf200) rating += 200;
else if (value >= dwarf100) rating += 100;
}
}
static int calcInventoryWeight(df::unit *unit)
{
int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR);
int armor_mul = 15 - std::min(15, armor_skill);
int inv_weight = 0, inv_weight_fraction = 0;
for (size_t i = 0; i < unit->inventory.size(); i++)
{
auto item = unit->inventory[i]->item;
if (!item->flags.bits.weight_computed)
continue;
int wval = item->weight;
int wfval = item->weight_fraction;
auto mode = unit->inventory[i]->mode;
if ((mode == df::unit_inventory_item::Worn ||
mode == df::unit_inventory_item::WrappedAround) &&
item->isArmor() && armor_skill > 1)
{
wval = wval * armor_mul / 16;
wfval = wfval * armor_mul / 16;
}
inv_weight += wval;
inv_weight_fraction += wfval;
}
return inv_weight*100 + inv_weight_fraction/10000;
}
int Units::computeMovementSpeed(df::unit *unit)
{
using namespace df::enums::physical_attribute_type;
/*
* Pure reverse-engineered computation of unit _slowness_,
* i.e. number of ticks to move * 100.
*/
// Base speed
auto creature = df::creature_raw::find(unit->race);
if (!creature)
return 0;
auto craw = vector_get(creature->caste, unit->caste);
if (!craw)
return 0;
int speed = craw->misc.speed;
if (unit->flags3.bits.ghostly)
return speed;
// Curse multiplier
if (unit->curse.speed_mul_percent != 100)
{
speed *= 100;
if (unit->curse.speed_mul_percent != 0)
speed /= unit->curse.speed_mul_percent;
}
speed += unit->curse.speed_add;
// Swimming
auto cur_liquid = unit->status2.liquid_type.bits.liquid_type;
bool in_magma = (cur_liquid == tile_liquid::Magma);
if (unit->flags2.bits.swimming)
{
speed = craw->misc.swim_speed;
if (in_magma)
speed *= 2;
if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING);
// Originally a switch:
if (skill > 1)
speed = speed * std::max(6, 21-skill) / 20;
}
}
else
{
int delta = 150*unit->status2.liquid_depth;
if (in_magma)
delta *= 2;
speed += delta;
}
// General counters and flags
if (unit->profession == profession::BABY)
speed += 3000;
if (unit->flags3.bits.unk15)
speed /= 20;
if (unit->counters2.exhaustion >= 2000)
{
speed += 200;
if (unit->counters2.exhaustion >= 4000)
{
speed += 200;
if (unit->counters2.exhaustion >= 6000)
speed += 200;
}
}
if (unit->flags2.bits.gutted) speed += 2000;
if (unit->counters.soldier_mood == df::unit::T_counters::None)
{
if (unit->counters.nausea > 0) speed += 1000;
if (unit->counters.winded > 0) speed += 1000;
if (unit->counters.stunned > 0) speed += 1000;
if (unit->counters.dizziness > 0) speed += 1000;
if (unit->counters2.fever > 0) speed += 1000;
}
if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance)
{
if (unit->counters.pain >= 100 && unit->mood == -1)
speed += 1000;
}
// Hunger etc timers
bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE);
if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit))
{
using namespace df::enums::misc_trait_type;
if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood))
{
adjust_speed_rating(
speed, is_adventure, trait->value,
302400, 403200, // dwf 100; 200
1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200
);
}
}
adjust_speed_rating(
speed, is_adventure, unit->counters2.thirst_timer,
50000, 0x7fffffff, 172800, 172800, 172800, 345600
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.hunger_timer,
75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000
);
adjust_speed_rating(
speed, is_adventure, unit->counters2.sleepiness_timer,
57600, 150000, 172800, 259200, 345600, 864000
);
// Activity state
if (unit->relations.draggee_id != -1) speed += 1000;
if (unit->flags1.bits.on_ground)
speed += 2000;
else if (unit->flags3.bits.on_crutch)
{
int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK);
speed += 2000 - 100*std::min(20, skill);
}
if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit))
{
int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK);
speed += 2000 - 100*std::min(20, skill);
}
if (unsigned(unit->counters2.paralysis-1) <= 98)
speed += unit->counters2.paralysis*10;
if (unsigned(unit->counters.webbed-1) <= 8)
speed += unit->counters.webbed*100;
// Muscle weight vs vascular tissue (?)
auto &attr_tissue = unit->body.physical_attr_tissues;
int muscle = attr_tissue[STRENGTH];
int blood = attr_tissue[AGILITY];
speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood)));
// Attributes
int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH);
int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY);
int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr));
speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600;
// Stance
if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2)
{
// WTF
int as = unit->status2.able_stand;
int x = (as-1) - (as>>1);
int y = as - unit->status2.able_stand_impair;
if (unit->flags3.bits.on_crutch) y--;
y = y * 500 / x;
if (y > 0) speed += y;
}
// Mood
if (unit->mood == mood_type::Melancholy) speed += 8000;
// Inventory encumberance
int total_weight = calcInventoryWeight(unit);
int free_weight = std::max(1, muscle/10 + strength_attr*3);
if (free_weight < total_weight)
{
int delta = (total_weight - free_weight)/10 + 1;
if (!is_adventure)
delta = std::min(5000, delta);
speed += delta;
}
// skipped: unknown loop on inventory items that amounts to 0 change
if (is_adventure)
{
auto player = vector_get(world->units.active, 0);
if (player && player->id == unit->relations.group_leader_id)
speed = std::min(speed, computeMovementSpeed(player));
}
return std::min(10000, std::max(0, speed));
}
static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b)
{ {
if (a.position->precedence < b.position->precedence) if (a.position->precedence < b.position->precedence)
@ -838,7 +1311,7 @@ std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::pro
{ {
std::string prof, race_prefix; std::string prof, race_prefix;
if (pid < 0 || !is_valid_enum_item(pid)) if (pid < (df::profession)0 || !is_valid_enum_item(pid))
return ""; return "";
bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id); bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id);

@ -300,6 +300,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix) void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix)
{ {
vec->clear();
if (!BuildPersistentCache()) if (!BuildPersistentCache())
return; return;
@ -343,8 +345,10 @@ bool World::DeletePersistentData(const PersistentDataItem &item)
auto eqrange = d->persistent_index.equal_range(item.key_value); auto eqrange = d->persistent_index.equal_range(item.key_value);
for (auto it = eqrange.first; it != eqrange.second; ++it) for (auto it2 = eqrange.first; it2 != eqrange.second; )
{ {
auto it = it2; ++it2;
if (it->second != -item.id) if (it->second != -item.id)
continue; continue;

@ -114,7 +114,7 @@ void Kitchen::fillWatchMap(std::map<t_materialIndex, unsigned int>& watchMap)
watchMap.clear(); watchMap.clear();
for(std::size_t i = 0; i < size(); ++i) for(std::size_t i = 0; i < size(); ++i)
{ {
if(ui->kitchen.item_subtypes[i] == limitType && ui->kitchen.item_subtypes[i] == limitSubtype && ui->kitchen.exc_types[i] == limitExclusion) if(ui->kitchen.item_subtypes[i] == (short)limitType && ui->kitchen.item_subtypes[i] == (short)limitSubtype && ui->kitchen.exc_types[i] == limitExclusion)
{ {
watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i]; watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i];
} }

@ -1 +1 @@
Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29

@ -3,7 +3,6 @@
DF_DIR=$(dirname "$0") DF_DIR=$(dirname "$0")
cd "${DF_DIR}" cd "${DF_DIR}"
export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
export DYLD_FRAMEWORK_PATH=${PWD}/hack${PWD}/libs
exec hack/dfhack-run "$@" exec hack/dfhack-run "$@"

@ -0,0 +1,21 @@
#!/bin/bash
BUILD_DIR=`pwd`
echo "Fixing library dependencies in $BUILD_DIR/library"
install_name_tool -change $BUILD_DIR/library/libdfhack.1.0.0.dylib @executable_path/hack/libdfhack.1.0.0.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/libdfhack-client.dylib
install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/dfhack-run
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack-client.dylib
install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/dfhack-run
install_name_tool -change $BUILD_DIR/depends/lua/liblua.dylib @executable_path/hack/liblua.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change @executable_path/../Frameworks/SDL.framework/Versions/A/SDL @executable_path/libs/SDL.framework/Versions/A/SDL library/libdfhack.1.0.0.dylib
install_name_tool -change /usr/local/lib/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack-client.dylib
install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/dfhack-run
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack.1.0.0.dylib
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack-client.dylib
install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/dfhack-run

@ -44,6 +44,12 @@ endif()
install(DIRECTORY lua/ install(DIRECTORY lua/
DESTINATION ${DFHACK_LUA_DESTINATION}/plugins DESTINATION ${DFHACK_LUA_DESTINATION}/plugins
FILES_MATCHING PATTERN "*.lua") FILES_MATCHING PATTERN "*.lua")
install(DIRECTORY raw/
DESTINATION ${DFHACK_DATA_DESTINATION}/raw
FILES_MATCHING PATTERN "*.txt")
install(DIRECTORY raw/
DESTINATION ${DFHACK_DATA_DESTINATION}/raw
FILES_MATCHING PATTERN "*.diff")
# Protobuf # Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
@ -114,6 +120,10 @@ if (BUILD_SUPPORTED)
# this one exports functions to lua # this one exports functions to lua
DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(add-spatter add-spatter.cpp)
# not yet. busy with other crud again... # not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp) #DFHACK_PLUGIN(versionosd versionosd.cpp)
endif() endif()

@ -0,0 +1,433 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include <modules/Maps.h>
#include <modules/Job.h>
#include <modules/Items.h>
#include <modules/Units.h>
#include <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <string.h>
#include <VTableInterpose.h>
#include "df/item_liquid_miscst.h"
#include "df/item_constructed.h"
#include "df/builtin_mats.h"
#include "df/world.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/job_item_ref.h"
#include "df/ui.h"
#include "df/report.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_product_item_improvementst.h"
#include "df/reaction_product_improvement_flags.h"
#include "df/matter_state.h"
#include "df/contaminant.h"
#include "MiscUtils.h"
using std::vector;
using std::string;
using std::stack;
using namespace DFHack;
using namespace df::enums;
using df::global::gps;
using df::global::world;
using df::global::ui;
typedef df::reaction_product_item_improvementst improvement_product;
DFHACK_PLUGIN("add-spatter");
struct ReagentSource {
int idx;
df::reaction_reagent *reagent;
ReagentSource() : idx(-1), reagent(NULL) {}
};
struct MaterialSource : ReagentSource {
bool product;
std::string product_name;
int mat_type, mat_index;
MaterialSource() : product(false), mat_type(-1), mat_index(-1) {}
};
struct ProductInfo {
df::reaction *react;
improvement_product *product;
ReagentSource object;
MaterialSource material;
bool isValid() {
return object.reagent && (material.mat_type >= 0 || material.reagent);
}
};
struct ReactionInfo {
df::reaction *react;
std::vector<ProductInfo> products;
};
static std::map<std::string, ReactionInfo> reactions;
static std::map<df::reaction_product*, ProductInfo*> products;
static ReactionInfo *find_reaction(const std::string &name)
{
auto it = reactions.find(name);
return (it != reactions.end()) ? &it->second : NULL;
}
static bool is_add_spatter(const std::string &name)
{
return name.size() > 12 && memcmp(name.data(), "SPATTER_ADD_", 12) == 0;
}
static void find_material(int *type, int *index, df::item *input, MaterialSource &mat)
{
if (input && mat.reagent)
{
MaterialInfo info(input);
if (mat.product)
{
if (!info.findProduct(info, mat.product_name))
{
color_ostream_proxy out(Core::getInstance().getConsole());
out.printerr("Cannot find product '%s'\n", mat.product_name.c_str());
}
}
*type = info.type;
*index = info.index;
}
else
{
*type = mat.mat_type;
*index = mat.mat_index;
}
}
static int has_contaminant(df::item_actual *item, int type, int index)
{
auto cont = item->contaminants;
if (!cont)
return 0;
int size = 0;
for (size_t i = 0; i < cont->size(); i++)
{
auto cur = (*cont)[i];
if (cur->mat_type == type && cur->mat_index == index)
size += cur->size;
}
return size;
}
/*
* Hooks
*/
typedef std::map<int, std::vector<df::item*> > item_table;
static void index_items(item_table &table, df::job *job, ReactionInfo *info)
{
for (int i = job->items.size()-1; i >= 0; i--)
{
auto iref = job->items[i];
if (iref->job_item_idx < 0) continue;
auto iitem = job->job_items[iref->job_item_idx];
if (iitem->contains.empty())
{
table[iitem->reagent_index].push_back(iref->item);
}
else
{
std::vector<df::item*> contents;
Items::getContainedItems(iref->item, &contents);
for (int j = contents.size()-1; j >= 0; j--)
{
for (int k = iitem->contains.size()-1; k >= 0; k--)
{
int ridx = iitem->contains[k];
auto reag = info->react->reagents[ridx];
if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
table[ridx].push_back(contents[j]);
}
}
}
}
}
df::item* find_item(ReagentSource &info, item_table &table)
{
if (!info.reagent)
return NULL;
if (table[info.idx].empty())
return NULL;
return table[info.idx].back();
}
struct item_hook : df::item_constructed {
typedef df::item_constructed interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, isImprovable, (df::job *job, int16_t mat_type, int32_t mat_index))
{
ReactionInfo *info;
if (job && job->job_type == job_type::CustomReaction &&
(info = find_reaction(job->reaction_name)) != NULL)
{
if (!contaminants || contaminants->empty())
return true;
item_table table;
index_items(table, job, info);
for (size_t i = 0; i < info->products.size(); i++)
{
auto &product = info->products[i];
int mattype, matindex;
auto material = find_item(info->products[i].material, table);
find_material(&mattype, &matindex, material, product.material);
if (mattype < 0 || has_contaminant(this, mattype, matindex) >= 50)
return false;
}
return true;
}
return INTERPOSE_NEXT(isImprovable)(job, mat_type, mat_index);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(item_hook, isImprovable);
df::item* find_item(
ReagentSource &info,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items
) {
if (!info.reagent)
return NULL;
for (int i = in_items->size(); i >= 0; i--)
if ((*in_reag)[i] == info.reagent)
return (*in_items)[i];
return NULL;
}
struct product_hook : improvement_product {
typedef improvement_product interpose_base;
DEFINE_VMETHOD_INTERPOSE(
void, produce,
(df::unit *unit, std::vector<df::item*> *out_items,
std::vector<df::reaction_reagent*> *in_reag,
std::vector<df::item*> *in_items,
int32_t quantity, int16_t skill,
df::historical_entity *entity, df::world_site *site)
) {
if (auto product = products[this])
{
auto object = find_item(product->object, in_reag, in_items);
auto material = find_item(product->material, in_reag, in_items);
if (object && (material || !product->material.reagent))
{
using namespace df::enums::improvement_type;
int mattype, matindex;
find_material(&mattype, &matindex, material, product->material);
df::matter_state state = matter_state::Liquid;
switch (improvement_type)
{
case COVERED:
if (flags.is_set(reaction_product_improvement_flags::GLAZED))
state = matter_state::Solid;
break;
case BANDS:
state = matter_state::Paste;
break;
case SPIKES:
state = matter_state::Powder;
break;
default:
break;
}
int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0;
int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary
object->addContaminant(
mattype, matindex, state,
object->getTemperature(),
size, -1,
0x8000 // not washed by water, and 'clean items' safe.
);
}
return;
}
INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce);
/*
* Scan raws for matching reactions.
*/
static void find_reagent(
color_ostream &out, ReagentSource &info, df::reaction *react, std::string name
) {
for (size_t i = 0; i < react->reagents.size(); i++)
{
if (react->reagents[i]->code != name)
continue;
info.idx = i;
info.reagent = react->reagents[i];
return;
}
out.printerr("Invalid reagent name '%s' in '%s'\n", name.c_str(), react->code.c_str());
}
static void parse_product(
color_ostream &out, ProductInfo &info, df::reaction *react, improvement_product *prod
) {
using namespace df::enums::reaction_product_improvement_flags;
info.react = react;
info.product = prod;
find_reagent(out, info.object, react, prod->target_reagent);
auto ritem = strict_virtual_cast<df::reaction_reagent_itemst>(info.object.reagent);
if (ritem)
ritem->flags1.bits.improvable = true;
info.material.mat_type = prod->mat_type;
info.material.mat_index = prod->mat_index;
if (prod->flags.is_set(GET_MATERIAL_PRODUCT))
{
find_reagent(out, info.material, react, prod->get_material.reagent_code);
info.material.product = true;
info.material.product_name = prod->get_material.product_code;
}
else if (prod->flags.is_set(GET_MATERIAL_SAME))
{
find_reagent(out, info.material, react, prod->get_material.reagent_code);
}
}
static bool find_reactions(color_ostream &out)
{
reactions.clear();
products.clear();
auto &rlist = world->raws.reactions;
for (size_t i = 0; i < rlist.size(); i++)
{
if (!is_add_spatter(rlist[i]->code))
continue;
reactions[rlist[i]->code].react = rlist[i];
}
for (auto it = reactions.begin(); it != reactions.end(); ++it)
{
auto &prod = it->second.react->products;
auto &out_prod = it->second.products;
for (size_t i = 0; i < prod.size(); i++)
{
auto itprod = strict_virtual_cast<improvement_product>(prod[i]);
if (!itprod) continue;
out_prod.push_back(ProductInfo());
parse_product(out, out_prod.back(), it->second.react, itprod);
}
for (size_t i = 0; i < prod.size(); i++)
{
if (out_prod[i].isValid())
products[out_prod[i].product] = &out_prod[i];
}
}
return !products.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(item_hook, isImprovable).apply(enable);
INTERPOSE_HOOK(product_hook, produce).apply(enable);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
if (find_reactions(out))
{
out.print("Detected spatter add reactions - enabling plugin.\n");
enable_hooks(true);
}
else
enable_hooks(false);
break;
case SC_MAP_UNLOADED:
enable_hooks(false);
reactions.clear();
products.clear();
break;
default:
break;
}
return CR_OK;
}
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
if (Core::getInstance().isMapLoaded())
plugin_onstatechange(out, SC_MAP_LOADED);
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
enable_hooks(false);
return CR_OK;
}

@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include <algorithm> #include <algorithm>
#include "modules/Units.h"
#include "modules/World.h" #include "modules/World.h"
// DF data structure definition headers // DF data structure definition headers
@ -358,11 +359,11 @@ static const dwarf_state dwarf_states[] = {
OTHER /* DrinkBlood */, OTHER /* DrinkBlood */,
OTHER /* ReportCrime */, OTHER /* ReportCrime */,
OTHER /* ExecuteCriminal */, OTHER /* ExecuteCriminal */,
BUSY /* TrainAnimal */, BUSY /* TrainAnimal */,
BUSY /* CarveTrack */, BUSY /* CarveTrack */,
BUSY /* PushTrackVehicle */, BUSY /* PushTrackVehicle */,
BUSY /* PlaceTrackVehicle */, BUSY /* PlaceTrackVehicle */,
BUSY /* StoreItemInVehicle */ BUSY /* StoreItemInVehicle */
}; };
struct labor_info struct labor_info
@ -397,108 +398,108 @@ static int hauler_pct = 33;
static std::vector<struct labor_info> labor_infos; static std::vector<struct labor_info> labor_infos;
static const struct labor_default default_labor_infos[] = { static const struct labor_default default_labor_infos[] = {
/* MINE */ {AUTOMATIC, true, 2, 200, 0}, /* MINE */ {AUTOMATIC, true, 2, 200, 0},
/* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, /* HAUL_STONE */ {HAULERS, false, 1, 200, 0},
/* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, /* HAUL_BODY */ {HAULERS, false, 1, 200, 0},
/* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0},
/* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0},
/* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0},
/* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0},
/* CLEAN */ {HAULERS, false, 1, 200, 0}, /* CLEAN */ {HAULERS, false, 1, 200, 0},
/* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0},
/* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0},
/* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, /* DETAIL */ {AUTOMATIC, false, 1, 200, 0},
/* MASON */ {AUTOMATIC, false, 1, 200, 0}, /* MASON */ {AUTOMATIC, false, 1, 200, 0},
/* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0},
/* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0},
/* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0},
/* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, /* SURGERY */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0},
/* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, /* SUTURING */ {AUTOMATIC, false, 1, 200, 0},
/* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0},
/* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0},
/* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0},
/* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0},
/* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0},
/* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, /* LEATHER */ {AUTOMATIC, false, 1, 200, 0},
/* TANNER */ {AUTOMATIC, false, 1, 200, 0}, /* TANNER */ {AUTOMATIC, false, 1, 200, 0},
/* BREWER */ {AUTOMATIC, false, 1, 200, 0}, /* BREWER */ {AUTOMATIC, false, 1, 200, 0},
/* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0},
/* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0},
/* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, /* WEAVER */ {AUTOMATIC, false, 1, 200, 0},
/* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* MILLER */ {AUTOMATIC, false, 1, 200, 0}, /* MILLER */ {AUTOMATIC, false, 1, 200, 0},
/* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0},
/* MILK */ {AUTOMATIC, false, 1, 200, 0}, /* MILK */ {AUTOMATIC, false, 1, 200, 0},
/* COOK */ {AUTOMATIC, false, 1, 200, 0}, /* COOK */ {AUTOMATIC, false, 1, 200, 0},
/* PLANT */ {AUTOMATIC, false, 1, 200, 0}, /* PLANT */ {AUTOMATIC, false, 1, 200, 0},
/* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0},
/* FISH */ {AUTOMATIC, false, 1, 1, 0}, /* FISH */ {AUTOMATIC, false, 1, 1, 0},
/* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0},
/* HUNT */ {AUTOMATIC, true, 1, 1, 0}, /* HUNT */ {AUTOMATIC, true, 1, 1, 0},
/* SMELT */ {AUTOMATIC, false, 1, 200, 0}, /* SMELT */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0},
/* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0},
/* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0},
/* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0},
/* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0},
/* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0},
/* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0},
/* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, /* BOWYER */ {AUTOMATIC, false, 1, 200, 0},
/* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0},
/* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0},
/* DYER */ {AUTOMATIC, false, 1, 200, 0}, /* DYER */ {AUTOMATIC, false, 1, 200, 0},
/* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0},
/* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0},
/* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, /* SHEARER */ {AUTOMATIC, false, 1, 200, 0},
/* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, /* SPINNER */ {AUTOMATIC, false, 1, 200, 0},
/* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, /* POTTERY */ {AUTOMATIC, false, 1, 200, 0},
/* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, /* GLAZING */ {AUTOMATIC, false, 1, 200, 0},
/* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, /* PRESSING */ {AUTOMATIC, false, 1, 200, 0},
/* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981)
/* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0},
/* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0}
}; };
static const int responsibility_penalties[] = { static const int responsibility_penalties[] = {
0, /* LAW_MAKING */ 0, /* LAW_MAKING */
0, /* LAW_ENFORCEMENT */ 0, /* LAW_ENFORCEMENT */
3000, /* RECEIVE_DIPLOMATS */ 3000, /* RECEIVE_DIPLOMATS */
0, /* MEET_WORKERS */ 0, /* MEET_WORKERS */
1000, /* MANAGE_PRODUCTION */ 1000, /* MANAGE_PRODUCTION */
3000, /* TRADE */ 3000, /* TRADE */
1000, /* ACCOUNTING */ 1000, /* ACCOUNTING */
0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */
0, /* MAKE_INTRODUCTIONS */ 0, /* MAKE_INTRODUCTIONS */
0, /* MAKE_PEACE_AGREEMENTS */ 0, /* MAKE_PEACE_AGREEMENTS */
0, /* MAKE_TOPIC_AGREEMENTS */ 0, /* MAKE_TOPIC_AGREEMENTS */
0, /* COLLECT_TAXES */ 0, /* COLLECT_TAXES */
0, /* ESCORT_TAX_COLLECTOR */ 0, /* ESCORT_TAX_COLLECTOR */
0, /* EXECUTIONS */ 0, /* EXECUTIONS */
0, /* TAME_EXOTICS */ 0, /* TAME_EXOTICS */
0, /* RELIGION */ 0, /* RELIGION */
0, /* ATTACK_ENEMIES */ 0, /* ATTACK_ENEMIES */
0, /* PATROL_TERRITORY */ 0, /* PATROL_TERRITORY */
0, /* MILITARY_GOALS */ 0, /* MILITARY_GOALS */
0, /* MILITARY_STRATEGY */ 0, /* MILITARY_STRATEGY */
0, /* UPGRADE_SQUAD_EQUIPMENT */ 0, /* UPGRADE_SQUAD_EQUIPMENT */
0, /* EQUIPMENT_MANIFESTS */ 0, /* EQUIPMENT_MANIFESTS */
0, /* SORT_AMMUNITION */ 0, /* SORT_AMMUNITION */
0, /* BUILD_MORALE */ 0, /* BUILD_MORALE */
5000 /* HEALTH_MANAGEMENT */ 5000 /* HEALTH_MANAGEMENT */
}; };
struct dwarf_info_t struct dwarf_info_t
@ -537,7 +538,7 @@ static void cleanup_state()
labor_infos.clear(); labor_infos.clear();
} }
static void reset_labor(df::enums::unit_labor::unit_labor labor) static void reset_labor(df::unit_labor labor)
{ {
labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs);
labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs);
@ -576,7 +577,7 @@ static void init_state()
for (auto p = items.begin(); p != items.end(); p++) for (auto p = items.begin(); p != items.end(); p++)
{ {
string key = p->key(); string key = p->key();
df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str());
if (labor >= 0 && labor <= labor_infos.size()) if (labor >= 0 && labor <= labor_infos.size())
{ {
labor_infos[labor].config = *p; labor_infos[labor].config = *p;
@ -597,7 +598,7 @@ static void init_state()
labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive;
labor_infos[i].active_dwarfs = 0; labor_infos[i].active_dwarfs = 0;
reset_labor((df::enums::unit_labor::unit_labor) i); reset_labor((df::unit_labor) i);
} }
generate_labor_to_skill_map(); generate_labor_to_skill_map();
@ -611,12 +612,12 @@ static void generate_labor_to_skill_map()
// Generate labor -> skill mapping // Generate labor -> skill mapping
for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++)
labor_to_skill[i] = df::enums::job_skill::NONE; labor_to_skill[i] = job_skill::NONE;
FOR_ENUM_ITEMS(job_skill, skill) FOR_ENUM_ITEMS(job_skill, skill)
{ {
int labor = ENUM_ATTR(job_skill, labor, skill); int labor = ENUM_ATTR(job_skill, labor, skill);
if (labor != df::enums::unit_labor::NONE) if (labor != unit_labor::NONE)
{ {
/* /*
assert(labor >= 0); assert(labor >= 0);
@ -779,7 +780,7 @@ static void assign_labor(unit_labor::unit_labor labor,
int value = dwarf_info[dwarf].mastery_penalty; int value = dwarf_info[dwarf].mastery_penalty;
if (skill != df::enums::job_skill::NONE) if (skill != job_skill::NONE)
{ {
int skill_level = 0; int skill_level = 0;
int skill_experience = 0; int skill_experience = 0;
@ -843,9 +844,9 @@ static void assign_labor(unit_labor::unit_labor labor,
int max_dwarfs = labor_infos[labor].maximum_dwarfs(); int max_dwarfs = labor_infos[labor].maximum_dwarfs();
// Special - don't assign hunt without a butchers, or fish without a fishery // Special - don't assign hunt without a butchers, or fish without a fishery
if (df::enums::unit_labor::HUNT == labor && !has_butchers) if (unit_labor::HUNT == labor && !has_butchers)
min_dwarfs = max_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
if (df::enums::unit_labor::FISH == labor && !has_fishery) if (unit_labor::FISH == labor && !has_fishery)
min_dwarfs = max_dwarfs = 0; min_dwarfs = max_dwarfs = 0;
bool want_idle_dwarf = true; bool want_idle_dwarf = true;
@ -956,15 +957,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::Workshop == type) if (building_type::Workshop == type)
{ {
auto subType = build->getSubtype(); df::workshop_type subType = (df::workshop_type)build->getSubtype();
if (df::enums::workshop_type::Butchers == subType) if (workshop_type::Butchers == subType)
has_butchers = true; has_butchers = true;
if (df::enums::workshop_type::Fishery == subType) if (workshop_type::Fishery == subType)
has_fishery = true; has_fishery = true;
} }
else if (df::enums::building_type::TradeDepot == type) else if (building_type::TradeDepot == type)
{ {
df::building_tradedepotst* depot = (df::building_tradedepotst*) build; df::building_tradedepotst* depot = (df::building_tradedepotst*) build;
trader_requested = depot->trade_flags.bits.trader_requested; trader_requested = depot->trade_flags.bits.trader_requested;
@ -978,11 +979,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
} }
} }
for (int i = 0; i < world->units.all.size(); ++i) for (int i = 0; i < world->units.active.size(); ++i)
{ {
df::unit* cre = world->units.all[i]; df::unit* cre = world->units.active[i];
if (cre->race == race && cre->civ_id == civ && !cre->flags1.bits.marauder && !cre->flags1.bits.diplomat && !cre->flags1.bits.merchant && if (Units::isCitizen(cre))
!cre->flags1.bits.dead && !cre->flags1.bits.forest)
{ {
if (cre->burrows.size() > 0) if (cre->burrows.size() > 0)
continue; // dwarfs assigned to burrows are skipped entirely continue; // dwarfs assigned to burrows are skipped entirely
@ -1003,9 +1003,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
dwarf_info[dwarf].single_labor = -1; dwarf_info[dwarf].single_labor = -1;
// assert(dwarfs[dwarf]->status.souls.size() > 0);
// assert fails can cause DF to crash, so don't do that
if (dwarfs[dwarf]->status.souls.size() <= 0) if (dwarfs[dwarf]->status.souls.size() <= 0)
continue; continue;
@ -1076,7 +1073,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
// Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.)
if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical) if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical)
continue; continue;
if (dwarf_info[dwarf].highest_skill < skill_level) if (dwarf_info[dwarf].highest_skill < skill_level)
@ -1093,16 +1090,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill;
dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty;
for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor])
dwarf_info[dwarf].mastery_penalty -= 100; dwarf_info[dwarf].mastery_penalty -= 100;
} }
@ -1120,15 +1112,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++)
{ {
// 7 / 0x7 = Newly arrived migrant, will not work yet if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak)
// 17 / 0x11 = On break
if ((*p)->id == 0x07 || (*p)->id == 0x11)
is_on_break = true; is_on_break = true;
} }
if (dwarfs[dwarf]->profession == df::enums::profession::BABY || if (dwarfs[dwarf]->profession == profession::BABY ||
dwarfs[dwarf]->profession == df::enums::profession::CHILD || dwarfs[dwarf]->profession == profession::CHILD ||
dwarfs[dwarf]->profession == df::enums::profession::DRUNK) dwarfs[dwarf]->profession == profession::DRUNK)
{ {
dwarf_info[dwarf].state = CHILD; dwarf_info[dwarf].state = CHILD;
} }
@ -1146,18 +1136,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
else else
{ {
int job = dwarfs[dwarf]->job.current_job->job_type; int job = dwarfs[dwarf]->job.current_job->job_type;
if (job >= 0 && job < ARRAY_COUNT(dwarf_states))
/* dwarf_info[dwarf].state = dwarf_states[job];
assert(job >= 0); else
assert(job < ARRAY_COUNT(dwarf_states)); {
*/ out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) dwarf_info[dwarf].state = OTHER;
dwarf_info[dwarf].state = dwarf_states[job]; }
else
{
out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job);
dwarf_info[dwarf].state = OTHER;
}
} }
state_count[dwarf_info[dwarf].state]++; state_count[dwarf_info[dwarf].state]++;
@ -1170,14 +1155,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
labor_infos[labor].active_dwarfs = 0; labor_infos[labor].active_dwarfs = 0;
labors.push_back(labor); labors.push_back(labor);
@ -1217,11 +1197,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
auto labor = *lp; auto labor = *lp;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out);
} }
@ -1241,7 +1216,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{ {
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
@ -1264,14 +1239,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
/*
assert(labor >= 0);
assert(labor < ARRAY_COUNT(labor_infos));
*/
if (labor_infos[labor].mode() != HAULERS) if (labor_infos[labor].mode() != HAULERS)
continue; continue;
@ -1311,7 +1281,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
return CR_OK; return CR_OK;
} }
void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out) void print_labor (df::unit_labor labor, color_ostream &out)
{ {
string labor_name = ENUM_KEY_STR(unit_labor, labor); string labor_name = ENUM_KEY_STR(unit_labor, labor);
out << labor_name << ": "; out << labor_name << ": ";
@ -1358,7 +1328,6 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 2 && parameters[0] == "haulpct") else if (parameters.size() == 2 && parameters[0] == "haulpct")
{ {
if (!enable_autolabor) if (!enable_autolabor)
@ -1371,15 +1340,15 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
hauler_pct = pct; hauler_pct = pct;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 2 || parameters.size() == 3) { else if (parameters.size() == 2 || parameters.size() == 3)
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
return CR_FAILURE; return CR_FAILURE;
} }
df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE; df::unit_labor labor = unit_labor::NONE;
FOR_ENUM_ITEMS(unit_labor, test_labor) FOR_ENUM_ITEMS(unit_labor, test_labor)
{ {
@ -1387,7 +1356,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
labor = test_labor; labor = test_labor;
} }
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
{ {
out.printerr("Could not find labor %s.\n", parameters[0].c_str()); out.printerr("Could not find labor %s.\n", parameters[0].c_str());
return CR_WRONG_USAGE; return CR_WRONG_USAGE;
@ -1430,7 +1399,8 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "reset-all") { else if (parameters.size() == 1 && parameters[0] == "reset-all")
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
@ -1439,12 +1409,13 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
for (int i = 0; i < labor_infos.size(); i++) for (int i = 0; i < labor_infos.size(); i++)
{ {
reset_labor((df::enums::unit_labor::unit_labor) i); reset_labor((df::unit_labor) i);
} }
out << "All labors reset." << endl; out << "All labors reset." << endl;
return CR_OK; return CR_OK;
} }
else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status")
{
if (!enable_autolabor) if (!enable_autolabor)
{ {
out << "Error: The plugin is not enabled." << endl; out << "Error: The plugin is not enabled." << endl;
@ -1467,7 +1438,7 @@ command_result autolabor (color_ostream &out, std::vector <std::string> & parame
{ {
FOR_ENUM_ITEMS(unit_labor, labor) FOR_ENUM_ITEMS(unit_labor, labor)
{ {
if (labor == df::enums::unit_labor::NONE) if (labor == unit_labor::NONE)
continue; continue;
print_labor(labor, out); print_labor(labor, out);
@ -1571,7 +1542,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
{ {
df::building *build = world->buildings.all[i]; df::building *build = world->buildings.all[i];
auto type = build->getType(); auto type = build->getType();
if (df::enums::building_type::Stockpile == type) if (building_type::Stockpile == type)
{ {
df::building_stockpilest *sp = virtual_cast<df::building_stockpilest>(build); df::building_stockpilest *sp = virtual_cast<df::building_stockpilest>(build);
StockpileInfo *spi = new StockpileInfo(sp); StockpileInfo *spi = new StockpileInfo(sp);
@ -1580,7 +1551,7 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
} }
std::vector<df::item*> &items = world->items.other[df::enums::items_other_id::ANY_FREE]; std::vector<df::item*> &items = world->items.other[items_other_id::ANY_FREE];
// Precompute a bitmask with the bad flags // Precompute a bitmask with the bad flags
df::item_flags bad_flags; df::item_flags bad_flags;
@ -1602,13 +1573,13 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
// we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG
df::item_type typ = item->getType(); df::item_type typ = item->getType();
if (typ != df::enums::item_type::MEAT && if (typ != item_type::MEAT &&
typ != df::enums::item_type::FISH && typ != item_type::FISH &&
typ != df::enums::item_type::FISH_RAW && typ != item_type::FISH_RAW &&
typ != df::enums::item_type::PLANT && typ != item_type::PLANT &&
typ != df::enums::item_type::CHEESE && typ != item_type::CHEESE &&
typ != df::enums::item_type::FOOD && typ != item_type::FOOD &&
typ != df::enums::item_type::EGG) typ != item_type::EGG)
continue; continue;
df::item *container = 0; df::item *container = 0;
@ -1673,11 +1644,11 @@ static int stockcheck(color_ostream &out, vector <string> & parameters)
if (building) { if (building) {
df::building_type btype = building->getType(); df::building_type btype = building->getType();
if (btype == df::enums::building_type::TradeDepot || if (btype == building_type::TradeDepot ||
btype == df::enums::building_type::Wagon) btype == building_type::Wagon)
continue; // items in trade depot or the embark wagon do not rot continue; // items in trade depot or the embark wagon do not rot
if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) if (typ == item_type::EGG && btype ==building_type::NestBox)
continue; // eggs in nest box do not rot continue; // eggs in nest box do not rot
} }

@ -50,12 +50,12 @@ command_result cleanmap (color_ostream &out, bool snow, bool mud)
// filter snow // filter snow
if(!snow if(!snow
&& spatter->mat_type == builtin_mats::WATER && spatter->mat_type == builtin_mats::WATER
&& spatter->mat_state == matter_state::Powder) && spatter->mat_state == (short)matter_state::Powder)
continue; continue;
// filter mud // filter mud
if(!mud if(!mud
&& spatter->mat_type == builtin_mats::MUD && spatter->mat_type == builtin_mats::MUD
&& spatter->mat_state == matter_state::Solid) && spatter->mat_state == (short)matter_state::Solid)
continue; continue;
delete evt; delete evt;
@ -81,11 +81,18 @@ command_result cleanitems (color_ostream &out)
df::item_actual *item = (df::item_actual *)world->items.all[i]; df::item_actual *item = (df::item_actual *)world->items.all[i];
if (item->contaminants && item->contaminants->size()) if (item->contaminants && item->contaminants->size())
{ {
std::vector<df::contaminant*> saved;
for (size_t j = 0; j < item->contaminants->size(); j++) for (size_t j = 0; j < item->contaminants->size(); j++)
delete item->contaminants->at(j); {
auto obj = (*item->contaminants)[j];
if (obj->flags.whole & 0x8000) // DFHack-generated contaminant
saved.push_back(obj);
else
delete obj;
}
cleaned_items++; cleaned_items++;
cleaned_total += item->contaminants->size(); cleaned_total += item->contaminants->size() - saved.size();
item->contaminants->clear(); item->contaminants->swap(saved);
} }
} }
if (cleaned_total) if (cleaned_total)

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

@ -11,6 +11,7 @@
#include "df/matter_state.h" #include "df/matter_state.h"
#include "df/descriptor_color.h" #include "df/descriptor_color.h"
#include "df/item_type.h" #include "df/item_type.h"
#include "df/strain_type.h"
using std::string; using std::string;
using std::vector; using std::vector;
@ -195,47 +196,17 @@ command_result df_dumpmats (color_ostream &out, vector<string> &parameters)
if (mat->molar_mass != 0xFBBC7818) if (mat->molar_mass != 0xFBBC7818)
out.print("\t[MOLAR_MASS:%i]\n", mat->molar_mass); out.print("\t[MOLAR_MASS:%i]\n", mat->molar_mass);
if (mat->strength.impact_yield != 10000) FOR_ENUM_ITEMS(strain_type, strain)
out.print("\t[IMPACT_YIELD:%i]\n", mat->strength.impact_yield); {
if (mat->strength.impact_fracture != 10000) auto name = ENUM_KEY_STR(strain_type,strain);
out.print("\t[IMPACT_FRACTURE:%i]\n", mat->strength.impact_fracture);
if (mat->strength.impact_strain_at_yield != 0) if (mat->strength.yield[strain] != 10000)
out.print("\t[IMPACT_STRAIN_AT_YIELD:%i]\n", mat->strength.impact_strain_at_yield); out.print("\t[%s_YIELD:%i]\n", name.c_str(), mat->strength.yield[strain]);
if (mat->strength.fracture[strain] != 10000)
if (mat->strength.compressive_yield != 10000) out.print("\t[%s_FRACTURE:%i]\n", name.c_str(), mat->strength.fracture[strain]);
out.print("\t[COMPRESSIVE_YIELD:%i]\n", mat->strength.compressive_yield); if (mat->strength.strain_at_yield[strain] != 0)
if (mat->strength.compressive_fracture != 10000) out.print("\t[%s_STRAIN_AT_YIELD:%i]\n", name.c_str(), mat->strength.strain_at_yield[strain]);
out.print("\t[COMPRESSIVE_FRACTURE:%i]\n", mat->strength.compressive_fracture); }
if (mat->strength.compressive_strain_at_yield != 0)
out.print("\t[COMPRESSIVE_STRAIN_AT_YIELD:%i]\n", mat->strength.compressive_strain_at_yield);
if (mat->strength.tensile_yield != 10000)
out.print("\t[TENSILE_YIELD:%i]\n", mat->strength.tensile_yield);
if (mat->strength.tensile_fracture != 10000)
out.print("\t[TENSILE_FRACTURE:%i]\n", mat->strength.tensile_fracture);
if (mat->strength.tensile_strain_at_yield != 0)
out.print("\t[TENSILE_STRAIN_AT_YIELD:%i]\n", mat->strength.tensile_strain_at_yield);
if (mat->strength.torsion_yield != 10000)
out.print("\t[TORSION_YIELD:%i]\n", mat->strength.torsion_yield);
if (mat->strength.torsion_fracture != 10000)
out.print("\t[TORSION_FRACTURE:%i]\n", mat->strength.torsion_fracture);
if (mat->strength.torsion_strain_at_yield != 0)
out.print("\t[TORSION_STRAIN_AT_YIELD:%i]\n", mat->strength.torsion_strain_at_yield);
if (mat->strength.shear_yield != 10000)
out.print("\t[SHEAR_YIELD:%i]\n", mat->strength.shear_yield);
if (mat->strength.shear_fracture != 10000)
out.print("\t[SHEAR_FRACTURE:%i]\n", mat->strength.shear_fracture);
if (mat->strength.shear_strain_at_yield != 0)
out.print("\t[SHEAR_STRAIN_AT_YIELD:%i]\n", mat->strength.shear_strain_at_yield);
if (mat->strength.bending_yield != 10000)
out.print("\t[BENDING_YIELD:%i]\n", mat->strength.bending_yield);
if (mat->strength.bending_fracture != 10000)
out.print("\t[BENDING_FRACTURE:%i]\n", mat->strength.bending_fracture);
if (mat->strength.bending_strain_at_yield != 0)
out.print("\t[BENDING_STRAIN_AT_YIELD:%i]\n", mat->strength.bending_strain_at_yield);
if (mat->strength.max_edge != 0) if (mat->strength.max_edge != 0)
out.print("\t[MAX_EDGE:%i]\n", mat->strength.max_edge); out.print("\t[MAX_EDGE:%i]\n", mat->strength.max_edge);

@ -31,6 +31,40 @@ static command_result nestboxes(color_ostream &out, vector <string> & parameters
DFHACK_PLUGIN("nestboxes"); DFHACK_PLUGIN("nestboxes");
static bool enabled = false;
static void eggscan(color_ostream &out)
{
CoreSuspender suspend;
for (int i = 0; i < world->buildings.all.size(); ++i)
{
df::building *build = world->buildings.all[i];
auto type = build->getType();
if (df::enums::building_type::NestBox == type)
{
bool fertile = false;
df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build);
if (nb->claimed_by != -1)
{
df::unit* u = df::unit::find(nb->claimed_by);
if (u && u->relations.pregnancy_timer > 0)
fertile = true;
}
for (int j = 1; j < nb->contained_items.size(); j++)
{
df::item* item = nb->contained_items[j]->item;
if (item->flags.bits.forbid != fertile)
{
item->flags.bits.forbid = fertile;
out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl;
}
}
}
}
}
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
if (world && ui) { if (world && ui) {
@ -49,6 +83,19 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK; return CR_OK;
} }
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
if (!enabled)
return CR_OK;
static unsigned cnt = 0;
if ((++cnt % 5) != 0)
return CR_OK;
eggscan(out);
return CR_OK;
}
static command_result nestboxes(color_ostream &out, vector <string> & parameters) static command_result nestboxes(color_ostream &out, vector <string> & parameters)
{ {
@ -57,60 +104,16 @@ static command_result nestboxes(color_ostream &out, vector <string> & parameters
int dump_count = 0; int dump_count = 0;
int good_egg = 0; int good_egg = 0;
if (parameters.size() == 1 && parameters[0] == "clean") if (parameters.size() == 1) {
{ if (parameters[0] == "enable")
clean = true; enabled = true;
} else if (parameters[0] == "disable")
for (int i = 0; i < world->buildings.all.size(); ++i) enabled = false;
{ else
df::building *build = world->buildings.all[i]; return CR_WRONG_USAGE;
auto type = build->getType(); } else {
if (df::enums::building_type::NestBox == type) out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl;
{ }
bool needs_clean = false;
df::building_nest_boxst *nb = virtual_cast<df::building_nest_boxst>(build);
out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl;
if (nb->contained_items.size() > 1)
needs_clean = true;
if (nb->claimed_by != -1)
{
df::unit* u = df::unit::find(nb->claimed_by);
if (u)
{
out << " Claimed by ";
if (u->name.has_name)
out << u->name.first_name << ", ";
df::creature_raw *raw = df::global::world->raws.creatures.all[u->race];
out << raw->creature_id
<< ", pregnancy timer " << u->relations.pregnancy_timer << endl;
if (u->relations.pregnancy_timer > 0)
needs_clean = false;
}
}
for (int j = 1; j < nb->contained_items.size(); j++)
{
df::item* item = nb->contained_items[j]->item;
if (needs_clean) {
if (clean && !item->flags.bits.dump)
{
item->flags.bits.dump = 1;
dump_count += item->getStackSize();
}
} else {
good_egg += item->getStackSize();
}
}
}
}
if (clean)
{
out << dump_count << " eggs dumped." << endl;
}
out << good_egg << " fertile eggs found." << endl;
return CR_OK; return CR_OK;
} }

@ -372,7 +372,7 @@ static command_result job_cmd(color_ostream &out, vector <string> & parameters)
out << "Job item updated." << endl; out << "Job item updated." << endl;
if (item->item_type < 0 && minfo.isValid()) if (item->item_type < (df::item_type)0 && minfo.isValid())
out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n" out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n"
" is ignored unless the item type is also specified.\n"); " is ignored unless the item type is also specified.\n");

@ -0,0 +1,229 @@
local _ENV = mkmodule('plugins.siege-engine')
--[[
Native functions:
* getTargetArea(building) -> point1, point2
* clearTargetArea(building)
* setTargetArea(building, point1, point2) -> true/false
* isLinkedToPile(building,pile) -> true/false
* getStockpileLinks(building) -> {pile}
* addStockpileLink(building,pile) -> true/false
* removeStockpileLink(building,pile) -> true/false
* saveWorkshopProfile(building) -> profile
* getAmmoItem(building) -> item_type
* setAmmoItem(building,item_type) -> true/false
* isPassableTile(pos) -> true/false
* isTreeTile(pos) -> true/false
* isTargetableTile(pos) -> true/false
* getTileStatus(building,pos) -> 'invalid/ok/out_of_range/blocked/semiblocked'
* paintAimScreen(building,view_pos_xyz,left_top_xy,size_xy)
* canTargetUnit(unit) -> true/false
proj_info = { target = pos, [delta = float/pos], [factor = int] }
* projPosAtStep(building,proj_info,step) -> pos
* projPathMetrics(building,proj_info) -> {
hit_type = 'wall/floor/ceiling/map_edge/tree',
collision_step = int,
collision_z_step = int,
goal_distance = int,
goal_step = int/nil,
goal_z_step = int/nil,
status = 'ok/out_of_range/blocked'
}
* adjustToTarget(building,pos) -> pos,ok=true/false
* traceUnitPath(unit) -> { {x=int,y=int,z=int[,from=time][,to=time]} }
* unitPosAtTime(unit, time) -> pos
* proposeUnitHits(building) -> { {
pos=pos, unit=unit, time=float, dist=int,
[lmargin=float,] [rmargin=float,]
} }
* computeNearbyWeight(building,hits,{[id/unit]=score}[,fname])
]]
Z_STEP_COUNT = 15
Z_STEP = 1/31
function getMetrics(engine, path)
path.metrics = path.metrics or projPathMetrics(engine, path)
return path.metrics
end
function findShotHeight(engine, target)
local path = { target = target, delta = 0.0 }
if getMetrics(engine, path).goal_step then
return path
end
local tpath = { target = target, delta = Z_STEP_COUNT*Z_STEP }
if getMetrics(engine, tpath).goal_step then
for i = 1,Z_STEP_COUNT-1 do
path = { target = target, delta = i*Z_STEP }
if getMetrics(engine, path).goal_step then
return path
end
end
return tpath
end
tpath = { target = target, delta = -Z_STEP_COUNT*Z_STEP }
if getMetrics(engine, tpath).goal_step then
for i = 1,Z_STEP_COUNT-1 do
path = { target = target, delta = -i*Z_STEP }
if getMetrics(engine, path).goal_step then
return path
end
end
return tpath
end
end
function findReachableTargets(engine, targets)
local reachable = {}
for _,tgt in ipairs(targets) do
tgt.path = findShotHeight(engine, tgt.pos)
if tgt.path then
table.insert(reachable, tgt)
end
end
return reachable
end
recent_targets = recent_targets or {}
if dfhack.is_core_context then
dfhack.onStateChange[_ENV] = function(code)
if code == SC_MAP_LOADED then
recent_targets = {}
end
end
end
function saveRecent(unit)
local id = unit.id
local tgt = recent_targets
tgt[id] = (tgt[id] or 0) + 1
dfhack.timeout(3, 'days', function()
tgt[id] = math.max(0, tgt[id]-1)
end)
end
function getBaseUnitWeight(unit)
if dfhack.units.isCitizen(unit) then
return -10
elseif unit.flags1.diplomat or unit.flags1.merchant then
return -2
elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then
return -1
else
local rv = 1
if unit.flags1.marauder then rv = rv + 0.5 end
if unit.flags1.active_invader then rv = rv + 1 end
if unit.flags1.invader_origin then rv = rv + 1 end
if unit.flags1.invades then rv = rv + 1 end
if unit.flags1.hidden_ambusher then rv = rv + 1 end
return rv
end
end
function getUnitWeight(unit)
local base = getBaseUnitWeight(unit)
return base * math.pow(0.7, recent_targets[unit.id] or 0)
end
function unitWeightCache()
local cache = {}
return cache, function(unit)
local id = unit.id
cache[id] = cache[id] or getUnitWeight(unit)
return cache[id]
end
end
function scoreTargets(engine, reachable)
local ucache, get_weight = unitWeightCache()
for _,tgt in ipairs(reachable) do
tgt.score = get_weight(tgt.unit)
if tgt.lmargin and tgt.lmargin < 3 then
tgt.score = tgt.score * tgt.lmargin / 3
end
if tgt.rmargin and tgt.rmargin < 3 then
tgt.score = tgt.score * tgt.rmargin / 3
end
end
computeNearbyWeight(engine, reachable, ucache)
for _,tgt in ipairs(reachable) do
tgt.score = (tgt.score + tgt.nearby_weight*0.7) * math.pow(0.995, tgt.time/3)
end
table.sort(reachable, function(a,b)
return a.score > b.score or (a.score == b.score and a.time < b.time)
end)
end
function pickUniqueTargets(reachable)
local unique = {}
if #reachable > 0 then
local pos_table = {}
local first_score = reachable[1].score
for i,tgt in ipairs(reachable) do
if tgt.score < 0 or tgt.score < 0.1*first_score then
break
end
local x,y,z = pos2xyz(tgt.pos)
local key = x..':'..y..':'..z
if pos_table[key] then
table.insert(pos_table[key].units, tgt.unit)
else
table.insert(unique, tgt)
pos_table[key] = tgt
tgt.units = { tgt.unit }
end
end
end
return unique
end
function doAimProjectile(engine, item, target_min, target_max, skill)
print(item, df.skill_rating[skill])
local targets = proposeUnitHits(engine)
local reachable = findReachableTargets(engine, targets)
scoreTargets(engine, reachable)
local unique = pickUniqueTargets(reachable)
if #unique > 0 then
local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2)))
local rnd = math.random(cnt)
for _,u in ipairs(unique[rnd].units) do
saveRecent(u)
end
return unique[rnd].path
end
end
return _ENV

@ -252,6 +252,7 @@ struct UnitInfo
enum altsort_mode { enum altsort_mode {
ALTSORT_NAME, ALTSORT_NAME,
ALTSORT_PROFESSION, ALTSORT_PROFESSION,
ALTSORT_HAPPINESS,
ALTSORT_MAX ALTSORT_MAX
}; };
@ -275,6 +276,14 @@ bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2)
return (d1->profession < d2->profession); return (d1->profession < d2->profession);
} }
bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2)
{
if (descending)
return (d1->unit->status.happiness > d2->unit->status.happiness);
else
return (d1->unit->status.happiness < d2->unit->status.happiness);
}
bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
{ {
if (sort_skill != job_skill::NONE) if (sort_skill != job_skill::NONE)
@ -310,6 +319,14 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2)
return sortByName(d1, d2); return sortByName(d1, d2);
} }
enum display_columns {
DISP_COLUMN_HAPPINESS,
DISP_COLUMN_NAME,
DISP_COLUMN_PROFESSION,
DISP_COLUMN_LABORS,
DISP_COLUMN_MAX,
};
class viewscreen_unitlaborsst : public dfhack_viewscreen { class viewscreen_unitlaborsst : public dfhack_viewscreen {
public: public:
void feed(set<df::interface_key> *events); void feed(set<df::interface_key> *events);
@ -328,10 +345,11 @@ protected:
vector<UnitInfo *> units; vector<UnitInfo *> units;
altsort_mode altsort; altsort_mode altsort;
int first_row, sel_row; int first_row, sel_row, num_rows;
int first_column, sel_column; int first_column, sel_column;
int height, name_width, prof_width, labors_width; int col_widths[DISP_COLUMN_MAX];
int col_offsets[DISP_COLUMN_MAX];
void calcSize (); void calcSize ();
}; };
@ -374,34 +392,52 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector<df::unit*> &src)
void viewscreen_unitlaborsst::calcSize() void viewscreen_unitlaborsst::calcSize()
{ {
height = gps->dimy - 10; num_rows = gps->dimy - 10;
if (height > units.size()) if (num_rows > units.size())
height = units.size(); num_rows = units.size();
name_width = prof_width = labors_width = 0; int num_columns = gps->dimx - DISP_COLUMN_MAX - 1;
for (int i = 4; i < gps->dimx; i++) for (int i = 0; i < DISP_COLUMN_MAX; i++)
col_widths[i] = 0;
while (num_columns > 0)
{ {
// 20% for Name, 20% for Profession, 60% for Labors num_columns--;
switch ((i - 4) % 5) // need at least 4 digits for happiness
if (col_widths[DISP_COLUMN_HAPPINESS] < 4)
{
col_widths[DISP_COLUMN_HAPPINESS]++;
continue;
}
// of remaining, 20% for Name, 20% for Profession, 60% for Labors
switch (num_columns % 5)
{ {
case 0: case 2: case 4: case 0: case 2: case 4:
labors_width++; col_widths[DISP_COLUMN_LABORS]++;
break; break;
case 1: case 1:
name_width++; col_widths[DISP_COLUMN_NAME]++;
break; break;
case 3: case 3:
prof_width++; col_widths[DISP_COLUMN_PROFESSION]++;
break; break;
} }
} }
while (labors_width > NUM_COLUMNS)
while (col_widths[DISP_COLUMN_LABORS] > NUM_COLUMNS)
{
col_widths[DISP_COLUMN_LABORS]--;
if (col_widths[DISP_COLUMN_LABORS] & 1)
col_widths[DISP_COLUMN_NAME]++;
else
col_widths[DISP_COLUMN_PROFESSION]++;
}
for (int i = 0; i < DISP_COLUMN_MAX; i++)
{ {
if (labors_width & 1) if (i == 0)
name_width++; col_offsets[i] = 1;
else else
prof_width++; col_offsets[i] = col_offsets[i - 1] + col_widths[i - 1] + 1;
labors_width--;
} }
// don't adjust scroll position immediately after the window opened // don't adjust scroll position immediately after the window opened
@ -409,20 +445,20 @@ void viewscreen_unitlaborsst::calcSize()
return; return;
// if the window grows vertically, scroll upward to eliminate blank rows from the bottom // if the window grows vertically, scroll upward to eliminate blank rows from the bottom
if (first_row > units.size() - height) if (first_row > units.size() - num_rows)
first_row = units.size() - height; first_row = units.size() - num_rows;
// if it shrinks vertically, scroll downward to keep the cursor visible // if it shrinks vertically, scroll downward to keep the cursor visible
if (first_row < sel_row - height + 1) if (first_row < sel_row - num_rows + 1)
first_row = sel_row - height + 1; first_row = sel_row - num_rows + 1;
// if the window grows horizontally, scroll to the left to eliminate blank columns from the right // if the window grows horizontally, scroll to the left to eliminate blank columns from the right
if (first_column > NUM_COLUMNS - labors_width) if (first_column > NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS])
first_column = NUM_COLUMNS - labors_width; first_column = NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS];
// if it shrinks horizontally, scroll to the right to keep the cursor visible // if it shrinks horizontally, scroll to the right to keep the cursor visible
if (first_column < sel_column - labors_width + 1) if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - labors_width + 1; first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
} }
void viewscreen_unitlaborsst::feed(set<df::interface_key> *events) void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
@ -453,8 +489,8 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (sel_row < first_row) if (sel_row < first_row)
first_row = sel_row; first_row = sel_row;
if (first_row < sel_row - height + 1) if (first_row < sel_row - num_rows + 1)
first_row = sel_row - height + 1; first_row = sel_row - num_rows + 1;
if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT)) if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT))
sel_column--; sel_column--;
@ -489,8 +525,8 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (sel_column < first_column) if (sel_column < first_column)
first_column = sel_column; first_column = sel_column;
if (first_column < sel_column - labors_width + 1) if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - labors_width + 1; first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
UnitInfo *cur = units[sel_row]; UnitInfo *cur = units[sel_row];
if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE)) if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE))
@ -556,6 +592,9 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
std::sort(units.begin(), units.end(), sortByProfession); std::sort(units.begin(), units.end(), sortByProfession);
break; break;
case ALTSORT_HAPPINESS:
std::sort(units.begin(), units.end(), sortByHappiness);
break;
} }
} }
if (events->count(interface_key::CHANGETAB)) if (events->count(interface_key::CHANGETAB))
@ -566,6 +605,9 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
altsort = ALTSORT_PROFESSION; altsort = ALTSORT_PROFESSION;
break; break;
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
altsort = ALTSORT_HAPPINESS;
break;
case ALTSORT_HAPPINESS:
altsort = ALTSORT_NAME; altsort = ALTSORT_NAME;
break; break;
} }
@ -603,9 +645,9 @@ void viewscreen_unitlaborsst::render()
dfhack_viewscreen::render(); dfhack_viewscreen::render();
Screen::clear(); Screen::clear();
Screen::drawBorder(" Manage Labors "); Screen::drawBorder(" Dwarf Manipulator - Manage Labors ");
for (int col = 0; col < labors_width; col++) for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{ {
int col_offset = col + first_column; int col_offset = col + first_column;
if (col_offset >= NUM_COLUMNS) if (col_offset >= NUM_COLUMNS)
@ -620,21 +662,21 @@ void viewscreen_unitlaborsst::render()
bg = 7; bg = 7;
} }
Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 1);
Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 2);
df::profession profession = columns[col_offset].profession; df::profession profession = columns[col_offset].profession;
if (profession != profession::NONE) if ((profession != profession::NONE) && (ui->race_id != -1))
{ {
auto graphics = world->raws.creatures.all[ui->race_id]->graphics; auto graphics = world->raws.creatures.all[ui->race_id]->graphics;
Screen::paintTile( Screen::paintTile(
Screen::Pen(' ', fg, 0, Screen::Pen(' ', fg, 0,
graphics.profession_add_color[creature_graphics_role::DEFAULT][profession], graphics.profession_add_color[creature_graphics_role::DEFAULT][profession],
graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]), graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]),
1 + name_width + 1 + prof_width + 1 + col, 3); col_offsets[DISP_COLUMN_LABORS] + col, 3);
} }
} }
for (int row = 0; row < height; row++) for (int row = 0; row < num_rows; row++)
{ {
int row_offset = row + first_row; int row_offset = row + first_row;
if (row_offset >= units.size()) if (row_offset >= units.size())
@ -643,6 +685,26 @@ void viewscreen_unitlaborsst::render()
UnitInfo *cur = units[row_offset]; UnitInfo *cur = units[row_offset];
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int8_t fg = 15, bg = 0; int8_t fg = 15, bg = 0;
int happy = cur->unit->status.happiness;
string happiness = stl_sprintf("%4i", happy);
if (happy == 0) // miserable
fg = 13; // 5:1
else if (happy <= 25) // very unhappy
fg = 12; // 4:1
else if (happy <= 50) // unhappy
fg = 4; // 4:0
else if (happy < 75) // fine
fg = 14; // 6:1
else if (happy < 125) // quite content
fg = 6; // 6:0
else if (happy < 150) // happy
fg = 2; // 2:0
else // ecstatic
fg = 10; // 2:1
Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_HAPPINESS], 4 + row, happiness);
fg = 15;
if (row_offset == sel_row) if (row_offset == sel_row)
{ {
fg = 0; fg = 0;
@ -650,18 +712,18 @@ void viewscreen_unitlaborsst::render()
} }
string name = cur->name; string name = cur->name;
name.resize(name_width); name.resize(col_widths[DISP_COLUMN_NAME]);
Screen::paintString(Screen::Pen(' ', fg, bg), 1, 4 + row, name); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_NAME], 4 + row, name);
string profession = cur->profession; string profession = cur->profession;
profession.resize(prof_width); profession.resize(col_widths[DISP_COLUMN_PROFESSION]);
fg = cur->color; fg = cur->color;
bg = 0; bg = 0;
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 4 + row, profession); Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION], 4 + row, profession);
// Print unit's skills and labor assignments // Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++) for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{ {
int col_offset = col + first_column; int col_offset = col + first_column;
fg = 15; fg = 15;
@ -693,7 +755,7 @@ void viewscreen_unitlaborsst::render()
} }
else else
bg = 4; bg = 4;
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 4 + row); Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row);
} }
} }
@ -703,17 +765,17 @@ void viewscreen_unitlaborsst::render()
{ {
df::unit *unit = cur->unit; df::unit *unit = cur->unit;
int x = 1; int x = 1;
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->transname);
x += cur->transname.length(); x += cur->transname.length();
if (cur->transname.length()) if (cur->transname.length())
{ {
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", "); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ", ");
x += 2; x += 2;
} }
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->profession);
x += cur->profession.length(); x += cur->profession.length();
Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": "); Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ": ");
x += 2; x += 2;
string str; string str;
@ -740,7 +802,7 @@ void viewscreen_unitlaborsst::render()
else else
str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill));
} }
Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str); Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + num_rows + 2, str);
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
} }
@ -779,6 +841,9 @@ void viewscreen_unitlaborsst::render()
case ALTSORT_PROFESSION: case ALTSORT_PROFESSION:
OutputString(15, x, gps->dimy - 2, "Profession"); OutputString(15, x, gps->dimy - 2, "Profession");
break; break;
case ALTSORT_HAPPINESS:
OutputString(15, x, gps->dimy - 2, "Happiness");
break;
default: default:
OutputString(15, x, gps->dimy - 2, "Unknown"); OutputString(15, x, gps->dimy - 2, "Unknown");
break; break;
@ -810,7 +875,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
{ {
int x = 2; int x = 2;
OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key
OutputString(15, x, gps->dimy - 2, ": Manage labors"); OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)");
} }
} }
}; };

@ -0,0 +1,29 @@
--- ../objects.old/entity_default.txt 2012-09-17 17:59:28.853898702 +0400
+++ entity_default.txt 2012-09-17 17:59:28.684899429 +0400
@@ -49,6 +49,7 @@
[TRAPCOMP:ITEM_TRAPCOMP_SPIKEDBALL]
[TRAPCOMP:ITEM_TRAPCOMP_LARGESERRATEDDISC]
[TRAPCOMP:ITEM_TRAPCOMP_MENACINGSPIKE]
+ [TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
[TOY:ITEM_TOY_PUZZLEBOX]
[TOY:ITEM_TOY_BOAT]
[TOY:ITEM_TOY_HAMMER]
@@ -204,6 +205,8 @@
[PERMITTED_JOB:WAX_WORKER]
[PERMITTED_BUILDING:SOAP_MAKER]
[PERMITTED_BUILDING:SCREW_PRESS]
+ [PERMITTED_BUILDING:STEAM_ENGINE]
+ [PERMITTED_BUILDING:MAGMA_STEAM_ENGINE]
[PERMITTED_REACTION:TAN_A_HIDE]
[PERMITTED_REACTION:RENDER_FAT]
[PERMITTED_REACTION:MAKE_SOAP_FROM_TALLOW]
@@ -248,6 +251,9 @@
[PERMITTED_REACTION:ROSE_GOLD_MAKING]
[PERMITTED_REACTION:BISMUTH_BRONZE_MAKING]
[PERMITTED_REACTION:ADAMANTINE_WAFERS]
+ [PERMITTED_REACTION:STOKE_BOILER]
+ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_WEAPON]
+ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_AMMO]
[WORLD_CONSTRUCTION:TUNNEL]
[WORLD_CONSTRUCTION:BRIDGE]
[WORLD_CONSTRUCTION:ROAD]

@ -0,0 +1,10 @@
--- ../objects.old/material_template_default.txt 2012-09-17 17:59:28.907898469 +0400
+++ material_template_default.txt 2012-09-17 17:59:28.695899382 +0400
@@ -2374,6 +2374,7 @@
[MAX_EDGE:500]
[ABSORPTION:100]
[LIQUID_MISC_CREATURE]
+ [REACTION_CLASS:CREATURE_EXTRACT]
[ROTS]
This is for creatures that are "made of fire". Right now there isn't a good format for that.

@ -0,0 +1,144 @@
reaction_spatter
[OBJECT:REACTION]
Reaction name must start with 'SPATTER_ADD_':
[REACTION:SPATTER_ADD_OBJECT_LIQUID]
[NAME:coat object with liquid]
[ADVENTURE_MODE_ENABLED]
[SKILL:WAX_WORKING]
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:150]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:NONE:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_WEAPON_EXTRACT]
[NAME:coat weapon with extract]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:150]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_AMMO_EXTRACT]
[NAME:coat ammo with extract]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE]
[MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_WEAPON_GCS]
[NAME:coat weapon with GCS venom]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
[MIN_DIMENSION:150]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_AMMO_GCS]
[NAME:coat ammo with GCS venom]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
[MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_WEAPON_GDS]
[NAME:coat weapon with GDS venom]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
[MIN_DIMENSION:150]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:WEAPON:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_AMMO_GDS]
[NAME:coat ammo with GDS venom]
[BUILDING:CRAFTSMAN:NONE]
[SKILL:WAX_WORKING]
[REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
[MIN_DIMENSION:50]
[REACTION_CLASS:CREATURE_EXTRACT]
[REAGENT:extract container:1:NONE:NONE:NONE:NONE]
[CONTAINS:extract]
[PRESERVE_REAGENT]
[DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
The object to improve must be after the input mat, so that it is known:
[REAGENT:object:1:AMMO:NONE:NONE:NONE]
[PRESERVE_REAGENT]
Need some excuse why the spatter is water-resistant:
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
[IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]

File diff suppressed because it is too large Load Diff

@ -228,7 +228,7 @@ static void sort_null_first(vector<string> &parameters)
vector_insert_at(parameters, 0, std::string("<exists")); vector_insert_at(parameters, 0, std::string("<exists"));
} }
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx) static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx)
{ {
return virtual_cast<df::layer_object_listst>(vector_get(layer->layer_objects,idx)); return virtual_cast<df::layer_object_listst>(vector_get(layer->layer_objects,idx));
} }

@ -1 +1 @@
Subproject commit 2a62ba5ed2607f4dbf0473e77502d4e19c19678e Subproject commit 37a823541538023b9f3d0d1e8039cf32851de68d

@ -767,7 +767,7 @@ command_result executePaintJob(color_ostream &out)
} }
// Remove liquid from walls, etc // Remove liquid from walls, etc
if (type != -1 && !DFHack::FlowPassable(type)) if (type != (df::tiletype)-1 && !DFHack::FlowPassable(type))
{ {
des.bits.flow_size = 0; des.bits.flow_size = 0;
//des.bits.liquid_type = DFHack::liquid_water; //des.bits.liquid_type = DFHack::liquid_water;

@ -29,11 +29,20 @@
#include "df/criminal_case.h" #include "df/criminal_case.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h" #include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_layer_unit_actionst.h"
#include "df/squad_order_trainst.h" #include "df/squad_order_trainst.h"
#include "df/ui_build_selector.h" #include "df/ui_build_selector.h"
#include "df/building_trapst.h" #include "df/building_trapst.h"
#include "df/item_actual.h" #include "df/item_actual.h"
#include "df/item_liquipowder.h"
#include "df/item_barst.h"
#include "df/item_threadst.h"
#include "df/item_clothst.h"
#include "df/contaminant.h" #include "df/contaminant.h"
#include "df/layer_object.h"
#include "df/reaction.h"
#include "df/reaction_reagent_itemst.h"
#include "df/reaction_reagent_flags.h"
#include <stdlib.h> #include <stdlib.h>
@ -89,6 +98,17 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Fixes rendering of creature weight limits in pressure plate build menu.\n" " Fixes rendering of creature weight limits in pressure plate build menu.\n"
" tweak stable-temp [disable]\n" " tweak stable-temp [disable]\n"
" Fixes performance bug 6012 by squashing jitter in temperature updates.\n" " Fixes performance bug 6012 by squashing jitter in temperature updates.\n"
" tweak fast-heat <max-ticks>\n"
" Further improves temperature updates by ensuring that 1 degree of\n"
" item temperature is crossed in no more than specified number of frames\n"
" when updating from the environment temperature. Use 0 to disable.\n"
" tweak fix-dimensions [disable]\n"
" Fixes subtracting small amount of thread/cloth/liquid from a stack\n"
" by splitting the stack and subtracting from the remaining single item.\n"
" tweak advmode-contained [disable]\n"
" Fixes custom reactions with container inputs in advmode. The issue is\n"
" that the screen tries to force you to select the contents separately\n"
" from the container. This forcefully skips child reagents.\n"
)); ));
return CR_OK; return CR_OK;
} }
@ -293,6 +313,187 @@ struct stable_temp_hook : df::item_actual {
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature); IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants); IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants);
static int map_temp_mult = -1;
static int max_heat_ticks = 0;
struct fast_heat_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(
bool, updateTempFromMap,
(bool local, bool contained, bool adjust, int32_t rate_mult)
) {
int cmult = map_temp_mult;
map_temp_mult = rate_mult;
bool rv = INTERPOSE_NEXT(updateTempFromMap)(local, contained, adjust, rate_mult);
map_temp_mult = cmult;
return rv;
}
DEFINE_VMETHOD_INTERPOSE(
bool, updateTemperature,
(uint16_t temp, bool local, bool contained, bool adjust, int32_t rate_mult)
) {
// Some items take ages to cross the last degree, so speed them up
if (map_temp_mult > 0 && temp != temperature && max_heat_ticks > 0)
{
int spec = getSpecHeat();
if (spec != 60001)
rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature));
}
return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (map_temp_mult > 0)
rate_mult = map_temp_mult;
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTempFromMap);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, adjustTemperature);
static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim)
{
// Zero dimension or remainder?
if (dim <= 0 || self->stack_size <= 1) return;
int rem = delta % dim;
if (rem == 0) return;
// If destroys, pass through
int intv = delta / dim;
if (intv >= self->stack_size) return;
// Subtract int part
delta = rem;
self->stack_size -= intv;
if (self->stack_size <= 1) return;
// If kills the item or cannot split, round up.
if (!self->flags.bits.in_inventory || !Items::getContainer(self))
{
delta = dim;
return;
}
// Otherwise split the stack
color_ostream_proxy out(Core::getInstance().getConsole());
out.print("fix-dimensions: splitting stack #%d for delta %d.\n", self->id, delta);
auto copy = self->splitStack(self->stack_size-1, true);
if (copy) copy->categorize(true);
}
struct dimension_lqp_hook : df::item_liquipowder {
typedef df::item_liquipowder interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
{
correct_dimension(this, delta, dimension);
return INTERPOSE_NEXT(subtractDimension)(delta);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dimension_lqp_hook, subtractDimension);
struct dimension_bar_hook : df::item_barst {
typedef df::item_barst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
{
correct_dimension(this, delta, dimension);
return INTERPOSE_NEXT(subtractDimension)(delta);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dimension_bar_hook, subtractDimension);
struct dimension_thread_hook : df::item_threadst {
typedef df::item_threadst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
{
correct_dimension(this, delta, dimension);
return INTERPOSE_NEXT(subtractDimension)(delta);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dimension_thread_hook, subtractDimension);
struct dimension_cloth_hook : df::item_clothst {
typedef df::item_clothst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
{
correct_dimension(this, delta, dimension);
return INTERPOSE_NEXT(subtractDimension)(delta);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension);
struct advmode_contained_hook : df::viewscreen_layer_unit_actionst {
typedef df::viewscreen_layer_unit_actionst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
auto old_reaction = cur_reaction;
auto old_reagent = reagent;
INTERPOSE_NEXT(feed)(input);
if (cur_reaction && (cur_reaction != old_reaction || reagent != old_reagent))
{
old_reagent = reagent;
// Skip reagents already contained by others
while (reagent < (int)cur_reaction->reagents.size()-1)
{
if (!cur_reaction->reagents[reagent]->flags.bits.IN_CONTAINER)
break;
reagent++;
}
if (old_reagent != reagent)
{
// Reproduces a tiny part of the orginal screen code
choice_items.clear();
auto preagent = cur_reaction->reagents[reagent];
reagent_amnt_left = preagent->quantity;
for (int i = held_items.size()-1; i >= 0; i--)
{
if (!preagent->matchesRoot(held_items[i], cur_reaction->index))
continue;
if (linear_index(sel_items, held_items[i]) >= 0)
continue;
choice_items.push_back(held_items[i]);
}
layer_objects[6]->setListLength(choice_items.size());
if (!choice_items.empty())
{
layer_objects[4]->active = layer_objects[5]->active = false;
layer_objects[6]->active = true;
}
else if (layer_objects[6]->active)
{
layer_objects[6]->active = false;
layer_objects[5]->active = true;
}
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(advmode_contained_hook, feed);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters) static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{ {
if (vector_get(parameters, 1) == "disable") if (vector_get(parameters, 1) == "disable")
@ -430,6 +631,28 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters); enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters); enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters);
} }
else if (cmd == "fast-heat")
{
if (parameters.size() < 2)
return CR_WRONG_USAGE;
max_heat_ticks = atoi(parameters[1].c_str());
if (max_heat_ticks <= 0)
parameters[1] = "disable";
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTempFromMap), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters);
}
else if (cmd == "fix-dimensions")
{
enable_hook(out, INTERPOSE_HOOK(dimension_lqp_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters);
}
else if (cmd == "advmode-contained")
{
enable_hook(out, INTERPOSE_HOOK(advmode_contained_hook, feed), parameters);
}
else else
return CR_WRONG_USAGE; return CR_WRONG_USAGE;

@ -828,7 +828,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job)
using namespace df::enums::reaction_product_item_flags; using namespace df::enums::reaction_product_item_flags;
VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]);
if (!prod || (prod->item_type < 0 && !prod->flags.is_set(CRAFTS))) if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS)))
continue; continue;
MaterialInfo mat(prod); MaterialInfo mat(prod);

@ -792,7 +792,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false)
bool isActivityZone(df::building * building) bool isActivityZone(df::building * building)
{ {
if( building->getType() == building_type::Civzone if( building->getType() == building_type::Civzone
&& building->getSubtype() == civzone_type::ActivityZone) && building->getSubtype() == (short)civzone_type::ActivityZone)
return true; return true;
else else
return false; return false;
@ -1603,7 +1603,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose)
if(building->getType()!= building_type::Civzone) if(building->getType()!= building_type::Civzone)
return; return;
if(building->getSubtype() != civzone_type::ActivityZone) if(building->getSubtype() != (short)civzone_type::ActivityZone)
return; return;
string name; string name;

@ -0,0 +1,3 @@
-- For killing bugged out gui script screens.
dfhack.screen.dismiss(dfhack.gui.getCurViewscreen())

@ -1,5 +1,9 @@
-- Reset item temperature to the value of their tile. -- Reset item temperature to the value of their tile.
local args = {...}
local apply = (args[1] == 'apply')
local count = 0 local count = 0
local types = {} local types = {}
@ -9,13 +13,16 @@ local function update_temp(item,btemp)
local tid = item:getType() local tid = item:getType()
types[tid] = (types[tid] or 0) + 1 types[tid] = (types[tid] or 0) + 1
end end
item.temperature = btemp
item.temperature_fraction = 0
if item.contaminants then if apply then
for _,c in ipairs(item.contaminants) do item.temperature = btemp
c.temperature = btemp item.temperature_fraction = 0
c.temperature_fraction = 0
if item.contaminants then
for _,c in ipairs(item.contaminants) do
c.temperature = btemp
c.temperature_fraction = 0
end
end end
end end
@ -23,7 +30,9 @@ local function update_temp(item,btemp)
update_temp(sub,btemp) update_temp(sub,btemp)
end end
item:checkTemperatureDamage() if apply then
item:checkTemperatureDamage()
end
end end
local last_frame = df.global.world.frame_counter-1 local last_frame = df.global.world.frame_counter-1
@ -39,7 +48,11 @@ for _,item in ipairs(df.global.world.items.all) do
end end
end end
print('Items updated: '..count) if apply then
print('Items updated: '..count)
else
print('Items not in equilibrium: '..count)
end
local tlist = {} local tlist = {}
for k,_ in pairs(types) do tlist[#tlist+1] = k end for k,_ in pairs(types) do tlist[#tlist+1] = k end
@ -47,3 +60,7 @@ table.sort(tlist, function(a,b) return types[a] > types[b] end)
for _,k in ipairs(tlist) do for _,k in ipairs(tlist) do
print(' '..df.item_type[k]..':', types[k]) print(' '..df.item_type[k]..':', types[k])
end end
if not apply then
print("Use 'fix/stable-temp apply' to force-change temperature.")
end

@ -4,19 +4,21 @@ local gui = require 'gui'
local text = 'Woohoo, lua viewscreen :)' local text = 'Woohoo, lua viewscreen :)'
local screen = mkinstance(gui.FramedScreen, { local screen = gui.FramedScreen{
frame_style = gui.GREY_LINE_FRAME, frame_style = gui.GREY_LINE_FRAME,
frame_title = 'Hello World', frame_title = 'Hello World',
frame_width = #text+6, frame_width = #text+6,
frame_height = 3, frame_height = 3,
onRenderBody = function(self, dc) }
dc:seek(3,1):string(text, COLOR_LIGHTGREEN)
end, function screen:onRenderBody(dc)
onInput = function(self,keys) dc:seek(3,1):string(text, COLOR_LIGHTGREEN)
if keys.LEAVESCREEN or keys.SELECT then end
self:dismiss()
end function screen:onInput(keys)
if keys.LEAVESCREEN or keys.SELECT then
self:dismiss()
end end
}):init() end
screen:show() screen:show()

@ -53,13 +53,7 @@ local permaflows = {
Toggle = defclass(Toggle) Toggle = defclass(Toggle)
function Toggle:init(items) Toggle.ATTRS{ items = {}, selected = 1 }
self:init_fields{
items = items,
selected = 1
}
return self
end
function Toggle:get() function Toggle:get()
return self.items[self.selected] return self.items[self.selected]
@ -89,16 +83,14 @@ LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay)
LiquidsUI.focus_path = 'liquids' LiquidsUI.focus_path = 'liquids'
function LiquidsUI:init() function LiquidsUI:init()
self:init_fields{ self:assign{
brush = mkinstance(Toggle):init(brushes), brush = Toggle{ items = brushes },
paint = mkinstance(Toggle):init(paints), paint = Toggle{ items = paints },
flow = mkinstance(Toggle):init(flowbits), flow = Toggle{ items = flowbits },
set = mkinstance(Toggle):init(setmode), set = Toggle{ items = setmode },
permaflow = mkinstance(Toggle):init(permaflows), permaflow = Toggle{ items = permaflows },
amount = 7, amount = 7,
} }
guidm.MenuOverlay.init(self)
return self
end end
function LiquidsUI:onDestroy() function LiquidsUI:onDestroy()
@ -201,6 +193,7 @@ function LiquidsUI:onRenderBody(dc)
end end
function ensure_blocks(cursor, size, cb) function ensure_blocks(cursor, size, cb)
size = size or xyz2pos(1,1,1)
local cx,cy,cz = pos2xyz(cursor) local cx,cy,cz = pos2xyz(cursor)
local all = true local all = true
for x=1,size.x or 1,16 do for x=1,size.x or 1,16 do
@ -298,5 +291,5 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then
qerror("This script requires the main dwarfmode view in 'k' mode") qerror("This script requires the main dwarfmode view in 'k' mode")
end end
local list = mkinstance(LiquidsUI):init() local list = LiquidsUI()
list:show() list:show()

@ -43,13 +43,11 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms' MechanismList.focus_path = 'mechanisms'
function MechanismList:init(building) function MechanismList:init(info)
self:init_fields{ self:assign{
links = {}, selected = 1 links = {}, selected = 1
} }
guidm.MenuOverlay.init(self) self:fillList(info.building)
self:fillList(building)
return self
end end
function MechanismList:fillList(building) function MechanismList:fillList(building)
@ -122,10 +120,10 @@ function MechanismList:onInput(keys)
end end
end end
if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then
qerror("This script requires the main dwarfmode view in 'q' mode") qerror("This script requires the main dwarfmode view in 'q' mode")
end end
local list = mkinstance(MechanismList):init(df.global.world.selected_building) local list = MechanismList{ building = df.global.world.selected_building }
list:show() list:show()
list:changeSelected(1) list:changeSelected(1)

@ -13,15 +13,13 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay)
PowerMeter.focus_path = 'power-meter' PowerMeter.focus_path = 'power-meter'
function PowerMeter:init() function PowerMeter:init()
self:init_fields{ self:assign{
min_power = 0, max_power = -1, invert = false, min_power = 0, max_power = -1, invert = false,
} }
guidm.MenuOverlay.init(self)
return self
end end
function PowerMeter:onShow() function PowerMeter:onShow()
guidm.MenuOverlay.onShow(self) PowerMeter.super.onShow(self)
-- Send an event to update the errors -- Send an event to update the errors
bselector.plate_info.flags.whole = 0 bselector.plate_info.flags.whole = 0
@ -112,5 +110,5 @@ then
qerror("This script requires the main dwarfmode view in build pressure plate mode") qerror("This script requires the main dwarfmode view in build pressure plate mode")
end end
local list = mkinstance(PowerMeter):init() local list = PowerMeter()
list:show() list:show()

@ -78,15 +78,17 @@ RoomList = defclass(RoomList, guidm.MenuOverlay)
RoomList.focus_path = 'room-list' RoomList.focus_path = 'room-list'
function RoomList:init(unit) RoomList.ATTRS{ unit = DEFAULT_NIL }
function RoomList:init(info)
local unit = info.unit
local base_bld = df.global.world.selected_building local base_bld = df.global.world.selected_building
self:init_fields{ self:assign{
unit = unit, base_building = base_bld, base_building = base_bld,
items = {}, selected = 1, items = {}, selected = 1,
own_rooms = {}, spouse_rooms = {} own_rooms = {}, spouse_rooms = {}
} }
guidm.MenuOverlay.init(self)
self.old_viewport = self:getViewport() self.old_viewport = self:getViewport()
self.old_cursor = guidm.getCursorPos() self.old_cursor = guidm.getCursorPos()
@ -115,8 +117,6 @@ function RoomList:init(unit)
self.items = concat_lists({self.base_item}, self.items) self.items = concat_lists({self.base_item}, self.items)
::found:: ::found::
end end
return self
end end
local sex_char = { [0] = 12, [1] = 11 } local sex_char = { [0] = 12, [1] = 11 }
@ -235,12 +235,13 @@ function RoomList:onInput(keys)
end end
local focus = dfhack.gui.getCurFocus() local focus = dfhack.gui.getCurFocus()
if focus == 'dwarfmode/QueryBuilding/Some' then
local base = df.global.world.selected_building if focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
mkinstance(RoomList):init(base.owner):show()
elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor]
mkinstance(RoomList):init(unit):show() RoomList{ unit = unit }:show()
elseif string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then
local base = df.global.world.selected_building
RoomList{ unit = base.owner }:show()
else else
qerror("This script requires the main dwarfmode view in 'q' mode") qerror("This script requires the main dwarfmode view in 'q' mode")
end end

@ -0,0 +1,490 @@
-- Front-end for the siege engine plugin.
local utils = require 'utils'
local gui = require 'gui'
local guidm = require 'gui.dwarfmode'
local dlg = require 'gui.dialogs'
local plugin = require 'plugins.siege-engine'
local wmap = df.global.world.map
local LEGENDARY = df.skill_rating.Legendary
-- Globals kept between script calls
last_target_min = last_target_min or nil
last_target_max = last_target_max or nil
local item_choices = {
{ caption = 'boulders (default)', item_type = df.item_type.BOULDER },
{ caption = 'blocks', item_type = df.item_type.BLOCKS },
{ caption = 'weapons', item_type = df.item_type.WEAPON },
{ caption = 'trap components', item_type = df.item_type.TRAPCOMP },
{ caption = 'bins', item_type = df.item_type.BIN },
{ caption = 'barrels', item_type = df.item_type.BARREL },
{ caption = 'cages', item_type = df.item_type.CAGE },
{ caption = 'anything', item_type = -1 },
}
local item_choice_idx = {}
for i,v in ipairs(item_choices) do
item_choice_idx[v.item_type] = i
end
SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay)
SiegeEngine.focus_path = 'siege-engine'
SiegeEngine.ATTRS{ building = DEFAULT_NIL }
function SiegeEngine:init()
self:assign{
center = utils.getBuildingCenter(self.building),
selected_pile = 1,
mode_main = {
render = self:callback 'onRenderBody_main',
input = self:callback 'onInput_main',
},
mode_aim = {
render = self:callback 'onRenderBody_aim',
input = self:callback 'onInput_aim',
},
mode_pile = {
render = self:callback 'onRenderBody_pile',
input = self:callback 'onInput_pile',
}
}
end
function SiegeEngine:onShow()
SiegeEngine.super.onShow(self)
self.old_cursor = guidm.getCursorPos()
self.old_viewport = self:getViewport()
self.mode = self.mode_main
self:showCursor(false)
end
function SiegeEngine:onDestroy()
if self.save_profile then
plugin.saveWorkshopProfile(self.building)
end
if not self.no_select_building then
self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10)
end
end
function SiegeEngine:showCursor(enable)
local cursor = guidm.getCursorPos()
if cursor and not enable then
self.cursor = cursor
self.target_select_first = nil
guidm.clearCursorPos()
elseif not cursor and enable then
local view = self:getViewport()
cursor = self.cursor
if not cursor or not view:isVisible(cursor) then
cursor = view:getCenter()
end
self.cursor = nil
guidm.setCursorPos(cursor)
end
end
function SiegeEngine:centerViewOn(pos)
local cursor = guidm.getCursorPos()
if cursor then
guidm.setCursorPos(pos)
else
self.cursor = pos
end
self:getViewport():centerOn(pos):set()
end
function SiegeEngine:zoomToTarget()
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
local cx = math.floor((target_min.x + target_max.x)/2)
local cy = math.floor((target_min.y + target_max.y)/2)
local cz = math.floor((target_min.z + target_max.z)/2)
local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz))
self:centerViewOn(pos)
end
end
function paint_target_grid(dc, view, origin, p1, p2)
local r1, sz, r2 = guidm.getSelectionRange(p1, p2)
if view.z < r1.z or view.z > r2.z then
return
end
local p1 = view:tileToScreen(r1)
local p2 = view:tileToScreen(r2)
local org = view:tileToScreen(origin)
dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true }
-- Frame
dc:fill(p1.x,p1.y,p1.x,p2.y)
dc:fill(p1.x,p1.y,p2.x,p1.y)
dc:fill(p2.x,p1.y,p2.x,p2.y)
dc:fill(p1.x,p2.y,p2.x,p2.y)
-- Grid
local gxmin = org.x+10*math.ceil((p1.x-org.x)/10)
local gxmax = org.x+10*math.floor((p2.x-org.x)/10)
local gymin = org.y+10*math.ceil((p1.y-org.y)/10)
local gymax = org.y+10*math.floor((p2.y-org.y)/10)
for x = gxmin,gxmax,10 do
for y = gymin,gymax,10 do
dc:fill(p1.x,y,p2.x,y)
dc:fill(x,p1.y,x,p2.y)
end
end
end
function SiegeEngine:renderTargetView(target_min, target_max)
local view = self:getViewport()
local map = self.df_layout.map
local map_dc = gui.Painter.new(map)
plugin.paintAimScreen(
self.building, view:getPos(),
xy2pos(map.x1, map.y1), view:getSize()
)
if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then
paint_target_grid(map_dc, view, self.center, target_min, target_max)
end
local cursor = guidm.getCursorPos()
if cursor then
local cx, cy, cz = pos2xyz(view:tileToScreen(cursor))
if cz == 0 then
map_dc:seek(cx,cy):char('X', COLOR_YELLOW)
end
end
end
function SiegeEngine:scrollPiles(delta)
local links = plugin.getStockpileLinks(self.building)
if links then
self.selected_pile = 1+(self.selected_pile+delta-1) % #links
return links[self.selected_pile]
end
end
function SiegeEngine:renderStockpiles(dc, links, nlines)
local idx = (self.selected_pile-1) % #links
local page = math.floor(idx/nlines)
for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do
local color = COLOR_BROWN
if i == idx then
color = COLOR_YELLOW
end
dc:newline(2):string(utils.getBuildingName(links[i+1]), color)
end
end
function SiegeEngine:onRenderBody_main(dc)
dc:newline(1):pen(COLOR_WHITE):string("Target: ")
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
dc:string(
(target_max.x-target_min.x+1).."x"..
(target_max.y-target_min.y+1).."x"..
(target_max.z-target_min.z+1).." Rect"
)
else
dc:string("None (default)")
end
dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle")
if last_target_min then
dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste")
end
dc:newline(3)
if target_min then
dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ")
dc:string("z",COLOR_LIGHTGREEN):string(": Zoom")
end
dc:newline():newline(1)
if self.building.type == df.siegeengine_type.Ballista then
dc:string("Uses ballista arrows")
else
local item = plugin.getAmmoItem(self.building)
dc:string("u",COLOR_LIGHTGREEN):string(": Use ")
if item_choice_idx[item] then
dc:string(item_choices[item_choice_idx[item]].caption)
else
dc:string(df.item_type[item])
end
end
dc:newline():newline(1)
dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3)
local links = plugin.getStockpileLinks(self.building)
local bottom = dc.height - 5
if links then
dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ")
dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline()
self:renderStockpiles(dc, links, bottom-2-dc:localY())
dc:newline():newline()
end
local prof = self.building:getWorkshopProfile() or {}
dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ')
dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-')
dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption)
dc:newline():newline()
if self.target_select_first then
self:renderTargetView(self.target_select_first, guidm.getCursorPos())
else
self:renderTargetView(target_min, target_max)
end
end
function SiegeEngine:setTargetArea(p1, p2)
self.target_select_first = nil
if not plugin.setTargetArea(self.building, p1, p2) then
dlg.showMessage(
'Set Target Area',
'Could not set the target area', COLOR_LIGHTRED
)
else
last_target_min = p1
last_target_max = p2
end
end
function SiegeEngine:setAmmoItem(choice)
if self.building.type == df.siegeengine_type.Ballista then
return
end
if not plugin.setAmmoItem(self.building, choice.item_type) then
dlg.showMessage(
'Set Ammo Item',
'Could not set the ammo item', COLOR_LIGHTRED
)
end
end
function SiegeEngine:onInput_main(keys)
if keys.CUSTOM_R then
self:showCursor(true)
self.target_select_first = nil
self.mode = self.mode_aim
elseif keys.CUSTOM_P and last_target_min then
self:setTargetArea(last_target_min, last_target_max)
elseif keys.CUSTOM_U then
local item = plugin.getAmmoItem(self.building)
local idx = 1 + (item_choice_idx[item] or 0) % #item_choices
self:setAmmoItem(item_choices[idx])
elseif keys.CUSTOM_Z then
self:zoomToTarget()
elseif keys.CUSTOM_X then
plugin.clearTargetArea(self.building)
elseif keys.SECONDSCROLL_UP then
self:scrollPiles(-1)
elseif keys.SECONDSCROLL_DOWN then
self:scrollPiles(1)
elseif keys.CUSTOM_D then
local pile = self:scrollPiles(0)
if pile then
plugin.removeStockpileLink(self.building, pile)
end
elseif keys.CUSTOM_O then
local pile = self:scrollPiles(0)
if pile then
self:centerViewOn(utils.getBuildingCenter(pile))
end
elseif keys.CUSTOM_T then
self:showCursor(true)
self.mode = self.mode_pile
self:sendInputToParent('CURSOR_DOWN_Z')
self:sendInputToParent('CURSOR_UP_Z')
elseif keys.CUSTOM_G then
local prof = plugin.saveWorkshopProfile(self.building)
prof.min_level = math.max(0, prof.min_level-1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_H then
local prof = plugin.saveWorkshopProfile(self.building)
prof.min_level = math.min(LEGENDARY, prof.min_level+1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_J then
local prof = plugin.saveWorkshopProfile(self.building)
prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1)
plugin.saveWorkshopProfile(self.building)
elseif keys.CUSTOM_K then
local prof = plugin.saveWorkshopProfile(self.building)
prof.max_level = math.min(LEGENDARY, prof.max_level+1)
if prof.max_level >= LEGENDARY then prof.max_level = 3000 end
plugin.saveWorkshopProfile(self.building)
elseif self:simulateViewScroll(keys) then
self.cursor = nil
else
return false
end
return true
end
local status_table = {
ok = { pen = COLOR_GREEN, msg = "Target accessible" },
out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" },
blocked = { pen = COLOR_RED, msg = "Target obstructed" },
semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" },
}
function SiegeEngine:onRenderBody_aim(dc)
local cursor = guidm.getCursorPos()
local first = self.target_select_first
dc:newline(1):string('Select target rectangle'):newline()
local info = status_table[plugin.getTileStatus(self.building, cursor)]
if info then
dc:newline(2):string(info.msg, info.pen)
else
dc:newline(2):string('ERROR', COLOR_RED)
end
dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN)
if first then
dc:string(": Finish rectangle")
else
dc:string(": Start rectangle")
end
dc:newline()
local target_min, target_max = plugin.getTargetArea(self.building)
if target_min then
dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target")
end
if first then
self:renderTargetView(first, cursor)
else
local target_min, target_max = plugin.getTargetArea(self.building)
self:renderTargetView(target_min, target_max)
end
end
function SiegeEngine:onInput_aim(keys)
if keys.SELECT then
local cursor = guidm.getCursorPos()
if self.target_select_first then
self:setTargetArea(self.target_select_first, cursor)
self.mode = self.mode_main
self:showCursor(false)
else
self.target_select_first = cursor
end
elseif keys.CUSTOM_Z then
self:zoomToTarget()
elseif keys.LEAVESCREEN then
self.mode = self.mode_main
self:showCursor(false)
elseif self:simulateCursorMovement(keys) then
self.cursor = nil
else
return false
end
return true
end
function SiegeEngine:onRenderBody_pile(dc)
dc:newline(1):string('Select pile to take from'):newline():newline(2)
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel) then
dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1)
if plugin.isLinkedToPile(self.building, sel) then
dc:string("Already taking from here"):newline():newline(2)
dc:string("d", COLOR_LIGHTGREEN):string(": Delete link")
else
dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile")
end
elseif sel then
dc:string(utils.getBuildingName(sel), COLOR_DARKGREY)
dc:newline():newline(1)
dc:string("Not a stockpile",COLOR_LIGHTRED)
else
dc:string("No building selected", COLOR_DARKGREY)
end
end
function SiegeEngine:onInput_pile(keys)
if keys.SELECT then
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel)
and not plugin.isLinkedToPile(self.building, sel) then
plugin.addStockpileLink(self.building, sel)
df.global.world.selected_building = self.building
self.mode = self.mode_main
self:showCursor(false)
end
elseif keys.CUSTOM_D then
local sel = df.global.world.selected_building
if df.building_stockpilest:is_instance(sel) then
plugin.removeStockpileLink(self.building, sel)
end
elseif keys.LEAVESCREEN then
df.global.world.selected_building = self.building
self.mode = self.mode_main
self:showCursor(false)
elseif self:propagateMoveKeys(keys) then
--
else
return false
end
return true
end
function SiegeEngine:onRenderBody(dc)
dc:clear()
dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline()
self.mode.render(dc)
dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE)
dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ")
dc:string("c", COLOR_LIGHTGREEN):string(": Recenter")
end
function SiegeEngine:onInput(keys)
if self.mode.input(keys) then
--
elseif keys.CUSTOM_C then
self:centerViewOn(self.center)
elseif keys.LEAVESCREEN then
self:dismiss()
elseif keys.LEAVESCREEN_ALL then
self:dismiss()
self.no_select_building = true
guidm.clearCursorPos()
df.global.ui.main.mode = df.ui_sidebar_mode.Default
df.global.world.selected_building = nil
end
end
if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then
qerror("This script requires a siege engine selected in 'q' mode")
end
local building = df.global.world.selected_building
if not df.building_siegeenginest:is_instance(building) then
qerror("A siege engine must be selected")
end
local list = SiegeEngine{ building = building }
list:show()

@ -0,0 +1,10 @@
-- Set the FPS cap at runtime.
local cap = ...
local capnum = tonumber(cap)
if not capnum or capnum < 1 then
qerror('Invalid FPS cap value: '..cap)
end
df.global.enabler.fps = capnum