Merge remote-tracking branch 'angavrilov/master'

develop
Kelly Martin 2012-09-04 12:05:05 -05:00
commit 96fec768c7
25 changed files with 1702 additions and 194 deletions

@ -764,10 +764,20 @@ Gui module
Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.
* ``dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])``
Like above, but also specifies a position you can zoom to from the announcement menu.
* ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])``
Pops up a titan-style modal announcement window.
* ``dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])``
Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.
Job module
----------
@ -959,6 +969,10 @@ Maps module
Returns a map block object for given x,y,z in local block coordinates.
* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)``
Checks if the given df::coord or x,y,z in local tile coordinates are valid.
* ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)``
Returns a map block object for given df::coord or x,y,z in local tile coordinates.
@ -971,6 +985,11 @@ Maps module
Enables updates for liquid flow or temperature, unless already active.
* ``dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)``
Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or *nil* if unsuccessful.
* ``dfhack.maps.getGlobalInitFeature(index)``
Returns the global feature object with the given index.

@ -1019,9 +1019,16 @@ the container itself.</p>
<p>Adds a regular announcement with given text, color, and brightness.
The is_bright boolean actually seems to invert the brightness.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showZoomAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Like above, but also specifies a position you can zoom to from the announcement menu.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showPopupAnnouncement(text,color[,is_bright])</span></tt></p>
<p>Pops up a titan-style modal announcement window.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.gui.showAutoAnnouncement(type,pos,text,color[,is_bright])</span></tt></p>
<p>Uses the type to look up options from announcements.txt, and calls the
above operations accordingly. If enabled, pauses and zooms to position.</p>
</li>
</ul>
</div>
<div class="section" id="job-module">
@ -1177,6 +1184,9 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getBlock(x,y,z)</tt></p>
<p>Returns a map block object for given x,y,z in local block coordinates.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.isValidTilePos(coords)</tt>, or isValidTilePos(x,y,z)``</p>
<p>Checks if the given df::coord or x,y,z in local tile coordinates are valid.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getTileBlock(coords)</tt>, or <tt class="docutils literal">getTileBlock(x,y,z)</tt></p>
<p>Returns a map block object for given df::coord or x,y,z in local tile coordinates.</p>
</li>
@ -1186,6 +1196,10 @@ Returns <em>false</em> in case of error.</p>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.maps.enableBlockUpdates(block[,flow,temperature])</span></tt></p>
<p>Enables updates for liquid flow or temperature, unless already active.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.spawnFlow(pos,type,mat_type,mat_index,dimension)</tt></p>
<p>Spawns a new flow (i.e. steam/mist/dust/etc) at the given pos, and with
the given parameters. Returns it, or <em>nil</em> if unsuccessful.</p>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getGlobalInitFeature(index)</tt></p>
<p>Returns the global feature object with the given index.</p>
</li>

@ -58,3 +58,12 @@ keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# stabilize the cursor of dwarfmode when switching menus
tweak stable-cursor
# stop military from considering training as 'patrol duty'
tweak patrol-duty
# display creature weight in build plate menu as ??K, instead of (???df: Max
tweak readable-build-plate
# improve FPS by squashing endless item temperature update loops
tweak stable-temp

@ -374,7 +374,7 @@ void DFHack::bitfieldToString(std::vector<std::string> *pvec, const void *p,
unsigned size, const bitfield_item_info *items)
{
for (unsigned i = 0; i < size; i++) {
int value = getBitfieldField(p, i, std::min(1,items[i].size));
int value = getBitfieldField(p, i, std::max(1,items[i].size));
if (value) {
std::string name = format_key(items[i].name, i);

@ -78,6 +78,7 @@ distribution.
#include "df/burrow.h"
#include "df/building_civzonest.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
#include <lua.h>
#include <lauxlib.h>
@ -756,7 +757,9 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = {
WRAPM(Gui, getSelectedUnit),
WRAPM(Gui, getSelectedItem),
WRAPM(Gui, showAnnouncement),
WRAPM(Gui, showZoomAnnouncement),
WRAPM(Gui, showPopupAnnouncement),
WRAPM(Gui, showAutoAnnouncement),
{ NULL, NULL }
};
@ -912,9 +915,17 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = {
WRAPM(Maps, getGlobalInitFeature),
WRAPM(Maps, getLocalInitFeature),
WRAPM(Maps, canWalkBetween),
WRAPM(Maps, spawnFlow),
{ NULL, NULL }
};
static int maps_isValidTilePos(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
lua_pushboolean(L, Maps::isValidTilePos(pos));
return 1;
}
static int maps_getTileBlock(lua_State *L)
{
auto pos = CheckCoordXYZ(L, 1, true);
@ -936,6 +947,7 @@ static int maps_getTileBiomeRgn(lua_State *L)
}
static const luaL_Reg dfhack_maps_funcs[] = {
{ "isValidTilePos", maps_isValidTilePos },
{ "getTileBlock", maps_getTileBlock },
{ "getRegionBiome", maps_getRegionBiome },
{ "getTileBiomeRgn", maps_getTileBiomeRgn },

@ -894,7 +894,7 @@ static int meta_bitfield_len(lua_State *state)
static void read_bitfield(lua_State *state, uint8_t *ptr, bitfield_identity *id, int idx)
{
int size = id->getBits()[idx].size;
int size = std::max(1, id->getBits()[idx].size);
int value = getBitfieldField(ptr, idx, size);
if (size <= 1)
@ -951,7 +951,7 @@ static int meta_bitfield_newindex(lua_State *state)
}
int idx = check_container_index(state, id->getNumBits(), 2, iidx, "write");
int size = id->getBits()[idx].size;
int size = std::max(1, id->getBits()[idx].size);
if (lua_isboolean(state, 3) || lua_isnil(state, 3))
setBitfieldField(ptr, idx, size, lua_toboolean(state, 3));

@ -48,6 +48,26 @@ struct MSVC_MPTR {
intptr_t this_shift;
};
static uint32_t *follow_jmp(void *ptr)
{
uint8_t *p = (uint8_t*)ptr;
for (;;)
{
switch (*p)
{
case 0xE9:
p += 5 + *(int32_t*)(p+1);
break;
case 0xEB:
p += 2 + *(int8_t*)(p+1);
break;
default:
return (uint32_t*)p;
}
}
}
bool DFHack::is_vmethod_pointer_(void *pptr)
{
auto pobj = (MSVC_MPTR*)pptr;
@ -55,7 +75,7 @@ bool DFHack::is_vmethod_pointer_(void *pptr)
// MSVC implements pointers to vmethods via thunks.
// This expects that they all follow a very specific pattern.
auto pval = (unsigned*)pobj->method;
auto pval = follow_jmp(pobj->method);
switch (pval[0]) {
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
case 0x60FF018BU: // mov eax, [ecx]; jmp [eax+0x??]
@ -71,7 +91,7 @@ int DFHack::vmethod_pointer_to_idx_(void *pptr)
auto pobj = (MSVC_MPTR*)pptr;
if (!pobj->method || pobj->this_shift != 0) return -1;
auto pval = (unsigned*)pobj->method;
auto pval = follow_jmp(pobj->method);
switch (pval[0]) {
case 0x20FF018BU: // mov eax, [ecx]; jmp [eax]
return 0;
@ -315,8 +335,14 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from)
}
}
bool VMethodInterposeLinkBase::apply()
bool VMethodInterposeLinkBase::apply(bool enable)
{
if (!enable)
{
remove();
return true;
}
if (is_applied())
return true;
if (!host->vtable_ptr)

@ -64,7 +64,7 @@ namespace DFHack
if (newsize == size)
return;
uint8_t* mem = (uint8_t *) realloc(bits, newsize);
if(!mem)
if(!mem && newsize != 0)
throw std::bad_alloc();
bits = mem;
if (newsize > size)
@ -207,7 +207,7 @@ namespace DFHack
else
{
T* mem = (T*) realloc(m_data, sizeof(T)*new_size);
if(!mem)
if(!mem && new_size != 0)
throw std::bad_alloc();
m_data = mem;
}

@ -390,7 +390,7 @@ namespace df
}
virtual bool resize(void *ptr, int size) {
((container*)ptr)->resize(size);
((container*)ptr)->resize(size*8);
return true;
}

@ -159,7 +159,7 @@ namespace DFHack
~VMethodInterposeLinkBase();
bool is_applied() { return applied; }
bool apply();
bool apply(bool enable = true);
void remove();
};

@ -32,6 +32,7 @@ distribution.
#include "DataDefs.h"
#include "df/init.h"
#include "df/ui.h"
#include "df/announcement_type.h"
namespace df {
struct viewscreen;
@ -92,14 +93,32 @@ namespace DFHack
// Show a plain announcement, or a titan-style popup message
DFHACK_EXPORT void showAnnouncement(std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showZoomAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
DFHACK_EXPORT void showPopupAnnouncement(std::string message, int color = 7, bool bright = true);
// Show an announcement with effects determined by announcements.txt
DFHACK_EXPORT void showAutoAnnouncement(df::announcement_type type, df::coord pos, std::string message, int color = 7, bool bright = true);
/*
* Cursor and window coords
*/
DFHACK_EXPORT df::coord getViewportPos();
DFHACK_EXPORT df::coord getCursorPos();
static const int AREA_MAP_WIDTH = 23;
static const int MENU_WIDTH = 30;
struct DwarfmodeDims {
int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2;
int y1, y2;
bool menu_on, area_on, menu_forced;
};
DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims();
DFHACK_EXPORT void resetDwarfmodeView(bool pause = false);
DFHACK_EXPORT bool revealInDwarfmodeMap(df::coord pos, bool center = false);
DFHACK_EXPORT bool getViewCoords (int32_t &x, int32_t &y, int32_t &z);
DFHACK_EXPORT bool setViewCoords (const int32_t x, const int32_t y, const int32_t z);

@ -50,6 +50,7 @@ distribution.
#include "df/tile_dig_designation.h"
#include "df/tile_traffic.h"
#include "df/feature_init.h"
#include "df/flow_type.h"
/**
* \defgroup grp_maps Maps module and its types
@ -232,6 +233,9 @@ extern DFHACK_EXPORT void getSize(uint32_t& x, uint32_t& y, uint32_t& z);
/// get the position of the map on world map
extern DFHACK_EXPORT void getPosition(int32_t& x, int32_t& y, int32_t& z);
extern DFHACK_EXPORT bool isValidTilePos(int32_t x, int32_t y, int32_t z);
inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, pos.z); }
/**
* Get the map block or NULL if block is not valid
*/
@ -272,6 +276,8 @@ inline df::coord2d getTileBiomeRgn(df::coord pos) {
// Enables per-frame updates for liquid flow and/or temperature.
DFHACK_EXPORT void enableBlockUpdates(df::map_block *blk, bool flow = false, bool temperature = false);
DFHACK_EXPORT df::flow_info *spawnFlow(df::coord pos, df::flow_type type, int mat_type = 0, int mat_index = -1, int density = 100);
/// sorts the block event vector into multiple vectors by type
/// mineral veins, what's under ice, blood smears and mud
extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,

@ -324,7 +324,7 @@ df::building *Buildings::allocInstance(df::coord pos, df::building_type type, in
{
auto obj = (df::building_trapst*)bld;
if (obj->trap_type == trap_type::PressurePlate)
obj->unk_cc = 500;
obj->ready_timeout = 500;
break;
}
default:

@ -43,6 +43,7 @@ using namespace DFHack;
#include "modules/Job.h"
#include "modules/Screen.h"
#include "modules/Maps.h"
#include "DataDefs.h"
#include "df/world.h"
@ -81,6 +82,8 @@ using namespace DFHack;
#include "df/graphic.h"
#include "df/layer_object_listst.h"
#include "df/assign_trade_status.h"
#include "df/announcement_flags.h"
#include "df/announcements.h"
using namespace df::enums;
using df::global::gview;
@ -88,6 +91,9 @@ using df::global::init;
using df::global::gps;
using df::global::ui;
using df::global::world;
using df::global::selection_rect;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx)
{
@ -921,8 +927,9 @@ df::item *Gui::getSelectedItem(color_ostream &out, bool quiet)
//
void Gui::showAnnouncement(std::string message, int color, bool bright)
{
static void doShowAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
using df::global::world;
using df::global::cur_year;
using df::global::cur_year_tick;
@ -948,6 +955,9 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
{
df::report *new_rep = new df::report();
new_rep->type = type;
new_rep->pos = pos;
new_rep->color = color;
new_rep->bright = bright;
new_rep->year = year;
@ -969,7 +979,17 @@ void Gui::showAnnouncement(std::string message, int color, bool bright)
world->status.announcements.push_back(new_rep);
world->status.display_timer = 2000;
}
}
void Gui::showAnnouncement(std::string message, int color, bool bright)
{
doShowAnnouncement(df::announcement_type(0), df::coord(), message, color, bright);
}
void Gui::showZoomAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
doShowAnnouncement(type, pos, message, color, bright);
}
void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
@ -983,6 +1003,29 @@ void Gui::showPopupAnnouncement(std::string message, int color, bool bright)
world->status.popups.push_back(popup);
}
void Gui::showAutoAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright
) {
using df::global::announcements;
df::announcement_flags flags;
if (is_valid_enum_item(type) && announcements)
flags = announcements->flags[type];
doShowAnnouncement(type, pos, message, color, bright);
if (flags.bits.DO_MEGA || flags.bits.PAUSE || flags.bits.RECENTER)
{
resetDwarfmodeView(flags.bits.DO_MEGA || flags.bits.PAUSE);
if (flags.bits.RECENTER && pos.isValid())
revealInDwarfmodeMap(pos, true);
}
if (flags.bits.DO_MEGA)
showPopupAnnouncement(message, color, bright);
}
df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed)
{
df::viewscreen * ws = &gview->view;
@ -1015,6 +1058,110 @@ df::coord Gui::getCursorPos()
return df::coord(cursor->x, cursor->y, cursor->z);
}
Gui::DwarfmodeDims Gui::getDwarfmodeViewDims()
{
DwarfmodeDims dims;
auto ws = Screen::getWindowSize();
dims.y1 = 1;
dims.y2 = ws.y-2;
dims.map_x1 = 1;
dims.map_x2 = ws.x-2;
dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1;
dims.menu_forced = false;
int menu_pos = (ui_menu_width ? *ui_menu_width : 2);
int area_pos = (ui_area_map_width ? *ui_area_map_width : 3);
if (ui && ui->main.mode && menu_pos >= area_pos)
{
dims.menu_forced = true;
menu_pos = area_pos-1;
}
dims.area_on = (area_pos < 3);
dims.menu_on = (menu_pos < area_pos);
if (dims.menu_on)
{
dims.menu_x2 = ws.x - 2;
dims.menu_x1 = dims.menu_x2 - Gui::MENU_WIDTH + 1;
if (menu_pos == 1)
dims.menu_x1 -= Gui::AREA_MAP_WIDTH + 1;
dims.map_x2 = dims.menu_x1 - 2;
}
if (dims.area_on)
{
dims.area_x2 = ws.x-2;
dims.area_x1 = dims.area_x2 - Gui::AREA_MAP_WIDTH + 1;
if (dims.menu_on)
dims.menu_x2 = dims.area_x1 - 2;
else
dims.map_x2 = dims.area_x1 - 2;
}
return dims;
}
void Gui::resetDwarfmodeView(bool pause)
{
using df::global::cursor;
if (ui)
{
ui->follow_unit = -1;
ui->follow_item = -1;
ui->main.mode = ui_sidebar_mode::Default;
}
if (selection_rect)
{
selection_rect->start_x = -30000;
selection_rect->end_x = -30000;
}
if (cursor)
cursor->x = cursor->y = cursor->z = -30000;
if (pause && df::global::pause_state)
*df::global::pause_state = true;
}
bool Gui::revealInDwarfmodeMap(df::coord pos, bool center)
{
using df::global::window_x;
using df::global::window_y;
using df::global::window_z;
if (!window_x || !window_y || !window_z || !world)
return false;
if (!Maps::isValidTilePos(pos))
return false;
auto dims = getDwarfmodeViewDims();
int w = dims.map_x2 - dims.map_x1 + 1;
int h = dims.y2 - dims.y1 + 1;
*window_z = pos.z;
if (center)
{
*window_x = pos.x - w/2;
*window_y = pos.y - h/2;
}
else
{
while (*window_x + w < pos.x+5) *window_x += 10;
while (*window_y + h < pos.y+5) *window_y += 10;
while (*window_x + 5 > pos.x) *window_x -= 10;
while (*window_y + 5 > pos.y) *window_y -= 10;
}
*window_x = std::max(0, std::min(*window_x, world->map.x_count-w));
*window_y = std::max(0, std::min(*window_y, world->map.y_count-h));
return true;
}
bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z)
{
x = *df::global::window_x;

@ -58,6 +58,7 @@ using namespace std;
#include "df/block_square_event_grassst.h"
#include "df/z_level_flags.h"
#include "df/region_map_entry.h"
#include "df/flow_info.h"
using namespace DFHack;
using namespace df::enums;
@ -138,13 +139,20 @@ df::map_block *Maps::getBlock (int32_t blockx, int32_t blocky, int32_t blockz)
return world->map.block_index[blockx][blocky][blockz];
}
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
bool Maps::isValidTilePos(int32_t x, int32_t y, int32_t z)
{
if (!IsValid())
return NULL;
return false;
if ((x < 0) || (y < 0) || (z < 0))
return NULL;
return false;
if ((x >= world->map.x_count) || (y >= world->map.y_count) || (z >= world->map.z_count))
return false;
return true;
}
df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z)
{
if (!isValidTilePos(x,y,z))
return NULL;
return world->map.block_index[x >> 4][y >> 4][z];
}
@ -204,6 +212,26 @@ void Maps::enableBlockUpdates(df::map_block *blk, bool flow, bool temperature)
}
}
df::flow_info *Maps::spawnFlow(df::coord pos, df::flow_type type, int mat_type, int mat_index, int density)
{
using df::global::flows;
auto block = getTileBlock(pos);
if (!flows || !block)
return NULL;
auto flow = new df::flow_info();
flow->type = type;
flow->mat_type = mat_type;
flow->mat_index = mat_index;
flow->density = std::min(100, density);
flow->pos = pos;
block->flows.push_back(flow);
flows->push_back(flow);
return flow;
}
df::feature_init *Maps::getGlobalInitFeature(int32_t index)
{
auto data = world->world_data;

@ -1 +1 @@
Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f
Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81

@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()

@ -0,0 +1,92 @@
building_steam_engine
[OBJECT:BUILDING]
[BUILDING_WORKSHOP:STEAM_ENGINE]
[NAME:Steam Engine]
[NAME_COLOR:4:0:1]
[DIM:3:3]
[WORK_LOCATION:2:3]
[BUILD_LABOR:MECHANIC]
[BUILD_KEY:CUSTOM_ALT_S]
[BLOCK:1:1:1:1]
[BLOCK:2:1:1:1]
[BLOCK:3:1:0:1]
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE]
[BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE]
[NAME:Magma Steam Engine]
[NAME_COLOR:4:0:1]
[DIM:3:3]
[WORK_LOCATION:2:3]
[BUILD_LABOR:MECHANIC]
[BUILD_KEY:CUSTOM_ALT_E]
[NEEDS_MAGMA]
[BLOCK:1:1:1:1]
[BLOCK:2:1:1:1]
[BLOCK:3:1:0:1]
[TILE:0:1:240:' ':254]
[TILE:0:2:' ':' ':128]
[TILE:0:3:246:' ':' ']
[COLOR:0:1:6:0:0:0:0:0:7:0:0]
[COLOR:0:2:0:0:0:0:0:0:7:0:0]
[COLOR:0:3:MAT:0:0:0:0:0:0]
[TILE:1:1:246:128:' ']
[TILE:1:2:' ':' ':254]
[TILE:1:3:254:'/':240]
[COLOR:1:1:MAT:7:0:0:0:0:0]
[COLOR:1:2:0:0:0:0:0:0:7:0:0]
[COLOR:1:3:7:0:0:6:0:0:6:0:0]
[TILE:2:1:21:' ':128]
[TILE:2:2:128:' ':246]
[TILE:2:3:177:19:177]
[COLOR:2:1:6:0:0:0:0:0:7:0:0]
[COLOR:2:2:7:0:0:0:0:0:MAT]
[COLOR:2:3:7:0:0:6:0:0:7:0:0]
Tile 15 marks places where machines can connect.
Tile 19 marks the hearth (color changed to reflect power).
[TILE:3:1:15:246:15]
[TILE:3:2:'\':19:'/']
[TILE:3:3:7:' ':7]
Color 1:?:1 water indicator, 4:?:1 magma indicator:
[COLOR:3:1:7:0:0:MAT:7:0:0]
[COLOR:3:2:6:0:0:0:0:1:6:0:0]
[COLOR:3:3:1:7:1:0:0:0:4:7:1]
[BUILD_ITEM:1:BARREL:NONE:INORGANIC:NONE][EMPTY][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:PIPE_SECTION:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:CHAIN:NONE:INORGANIC:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:TRAPPARTS:NONE:NONE:NONE][CAN_USE_ARTIFACT]
[BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE]

@ -0,0 +1,12 @@
item_trapcomp_steam_engine
[OBJECT:ITEM]
[ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
[NAME:piston:pistons]
[ADJECTIVE:huge]
[SIZE:1600]
[HITS:1]
[MATERIAL_SIZE:6]
[METAL]
[ATTACK:BLUNT:40:200:bash:bashes:NO_SUB:2000]

@ -0,0 +1,14 @@
reaction_steam_engine
[OBJECT:REACTION]
[REACTION:STOKE_BOILER]
[NAME:stoke the boiler]
[BUILDING:STEAM_ENGINE:CUSTOM_S]
[BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S]
[FUEL]
[SKILL:SMELT]
Dimension is the number of days it can produce 100 power * 100.
I.e. with 2000 it means energy of 1 job = 1 water wheel for 20 days.
[PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:2000]

@ -0,0 +1,929 @@
#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 <TileTypes.h>
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include <string.h>
#include <VTableInterpose.h>
#include "df/graphic.h"
#include "df/building_workshopst.h"
#include "df/building_def_workshopst.h"
#include "df/item_liquid_miscst.h"
#include "df/power_info.h"
#include "df/workshop_type.h"
#include "df/builtin_mats.h"
#include "df/world.h"
#include "df/buildings_other_id.h"
#include "df/machine.h"
#include "df/job.h"
#include "df/building_drawbuffer.h"
#include "df/ui.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/ui_build_selector.h"
#include "df/flow_info.h"
#include "df/report.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;
using df::global::ui_build_selector;
DFHACK_PLUGIN("steam-engine");
/*
* List of known steam engine workshop raws.
*/
struct steam_engine_workshop {
int id;
df::building_def_workshopst *def;
// Cached properties
bool is_magma;
int max_power, max_capacity;
int wear_temp;
// Special tiles (relative position)
std::vector<df::coord2d> gear_tiles;
df::coord2d hearth_tile;
df::coord2d water_tile;
df::coord2d magma_tile;
};
std::vector<steam_engine_workshop> engines;
steam_engine_workshop *find_steam_engine(int id)
{
for (size_t i = 0; i < engines.size(); i++)
if (engines[i].id == id)
return &engines[i];
return NULL;
}
/*
* Misc utilities.
*/
static const int hearth_colors[6][2] = {
{ COLOR_BLACK, 1 },
{ COLOR_BROWN, 0 },
{ COLOR_RED, 0 },
{ COLOR_RED, 1 },
{ COLOR_BROWN, 1 },
{ COLOR_GREY, 1 }
};
void enable_updates_at(df::coord pos, bool flow, bool temp)
{
static const int delta[4][2] = { { -1, -1 }, { 1, -1 }, { -1, 1 }, { 1, 1 } };
for (int i = 0; i < 4; i++)
{
auto blk = Maps::getTileBlock(pos.x+delta[i][0], pos.y+delta[i][1], pos.z);
Maps::enableBlockUpdates(blk, flow, temp);
}
}
void decrement_flow(df::coord pos, int amount)
{
auto pldes = Maps::getTileDesignation(pos);
if (!pldes) return;
int nsize = std::max(0, int(pldes->bits.flow_size - amount));
pldes->bits.flow_size = nsize;
pldes->bits.flow_forbid = (nsize > 3 || pldes->bits.liquid_type == tile_liquid::Magma);
enable_updates_at(pos, true, false);
}
void make_explosion(df::coord center, int power)
{
static const int bias[9] = {
60, 30, 60,
30, 0, 30,
60, 30, 60
};
int mat_type = builtin_mats::WATER, mat_index = -1;
int i = 0;
for (int dx = -1; dx <= 1; dx++)
{
for (int dy = -1; dy <= 1; dy++)
{
int size = power - bias[i++];
auto pos = center + df::coord(dx,dy,0);
if (size > 0)
Maps::spawnFlow(pos, flow_type::MaterialDust, mat_type, mat_index, size);
}
}
Gui::showAutoAnnouncement(
announcement_type::CAVE_COLLAPSE, center,
"A boiler has exploded!", COLOR_RED, true
);
}
static const int WEAR_TICKS = 806400;
bool add_wear_nodestroy(df::item_actual *item, int rate)
{
if (item->incWearTimer(rate))
{
while (item->wear_timer >= WEAR_TICKS)
{
item->wear_timer -= WEAR_TICKS;
item->wear++;
}
}
return item->wear > 3;
}
/*
* Hook for the liquid item. Implements a special 'boiling'
* matter state with a modified description and temperature
* locked at boiling-1.
*/
struct liquid_hook : df::item_liquid_miscst {
typedef df::item_liquid_miscst interpose_base;
static const uint32_t BOILING_FLAG = 0x80000000U;
DEFINE_VMETHOD_INTERPOSE(void, getItemDescription, (std::string *buf, int8_t mode))
{
if (mat_state.whole & BOILING_FLAG)
buf->append("boiling ");
INTERPOSE_NEXT(getItemDescription)(buf, mode);
}
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t unk))
{
if (mat_state.whole & BOILING_FLAG)
temp = std::max(int(temp), getBoilingPoint()-1);
return INTERPOSE_NEXT(adjustTemperature)(temp, unk);
}
DEFINE_VMETHOD_INTERPOSE(bool, checkTemperatureDamage, ())
{
if (mat_state.whole & BOILING_FLAG)
temperature = std::max(int(temperature), getBoilingPoint()-1);
return INTERPOSE_NEXT(checkTemperatureDamage)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription);
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, checkTemperatureDamage);
/*
* Hook for the workshop itself. Implements core logic.
*/
struct workshop_hook : df::building_workshopst {
typedef df::building_workshopst interpose_base;
// Engine detection
steam_engine_workshop *get_steam_engine()
{
if (type == workshop_type::Custom)
return find_steam_engine(custom_type);
return NULL;
}
inline bool is_fully_built()
{
return getBuildStage() >= getMaxBuildStage();
}
// Use high bits of flags to store current steam amount.
// This is necessary for consistency if items disappear unexpectedly.
int get_steam_amount()
{
return (flags.whole >> 28) & 15;
}
void set_steam_amount(int count)
{
flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28);
}
// Find liquids to consume below the engine.
bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, int min_level)
{
if (!is_magma)
pmagma = NULL;
for (int x = x1; x <= x2; x++)
{
for (int y = y1; y <= y2; y++)
{
auto ptile = Maps::getTileType(x,y,z);
if (!ptile || !LowPassable(*ptile))
continue;
auto pltile = Maps::getTileType(x,y,z-1);
if (!pltile || !FlowPassable(*pltile))
continue;
auto pldes = Maps::getTileDesignation(x,y,z-1);
if (!pldes || pldes->bits.flow_size < min_level)
continue;
if (pldes->bits.liquid_type == tile_liquid::Magma)
{
if (pmagma)
*pmagma = df::coord(x,y,z-1);
if (pwater->isValid())
return true;
}
else
{
*pwater = df::coord(x,y,z-1);
if (!pmagma || pmagma->isValid())
return true;
}
}
}
return false;
}
// Absorbs a water item produced by stoke reaction into the engine.
bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid)
{
// Consume liquid inputs
df::coord water, magma;
if (!find_liquids(&water, &magma, engine->is_magma, 1))
{
// Destroy the item with enormous wear amount.
liquid->addWear(WEAR_TICKS*5, true, false);
return false;
}
decrement_flow(water, 1);
if (engine->is_magma)
decrement_flow(magma, 1);
// Update flags
liquid->flags.bits.in_building = true;
liquid->mat_state.whole |= liquid_hook::BOILING_FLAG;
liquid->temperature = liquid->getBoilingPoint()-1;
liquid->temperature_fraction = 0;
// This affects where the steam appears to come from
if (engine->hearth_tile.isValid())
liquid->pos = df::coord(x1+engine->hearth_tile.x, y1+engine->hearth_tile.y, z);
// Enable block temperature updates
enable_updates_at(liquid->pos, false, true);
return true;
}
bool boil_unit(df::item_liquid_miscst *liquid)
{
liquid->wear = 4;
liquid->flags.bits.in_building = false;
liquid->temperature = liquid->getBoilingPoint() + 10;
return liquid->checkMeltBoil();
}
void suspend_jobs(bool suspend)
{
for (size_t i = 0; i < jobs.size(); i++)
if (jobs[i]->job_type == job_type::CustomReaction)
jobs[i]->flags.bits.suspend = suspend;
}
// Scan contained items for boiled steam to absorb.
df::item_liquid_miscst *collect_steam(steam_engine_workshop *engine, int *count)
{
df::item_liquid_miscst *first = NULL;
*count = 0;
for (int i = contained_items.size()-1; i >= 0; i--)
{
auto item = contained_items[i];
if (item->use_mode != 0)
continue;
auto liquid = strict_virtual_cast<df::item_liquid_miscst>(item->item);
if (!liquid)
continue;
if (!liquid->flags.bits.in_building)
{
if (liquid->mat_type != builtin_mats::WATER ||
liquid->age > 1 ||
liquid->wear != 0)
continue;
// This may destroy the item
if (!absorb_unit(engine, liquid))
continue;
}
if (*count < engine->max_capacity)
{
first = liquid;
++*count;
}
else
{
// Overpressure valve
boil_unit(liquid);
suspend_jobs(true);
}
}
return first;
}
void random_boil()
{
int cnt = 0;
for (int i = contained_items.size()-1; i >= 0; i--)
{
auto item = contained_items[i];
if (item->use_mode != 0 || !item->item->flags.bits.in_building)
continue;
auto liquid = strict_virtual_cast<df::item_liquid_miscst>(item->item);
if (!liquid)
continue;
if (cnt == 0 || rand() < RAND_MAX/2)
{
cnt++;
boil_unit(liquid);
}
}
}
int classify_component(df::building_actual::T_contained_items *item)
{
if (item->use_mode != 2 || item->item->isBuildMat())
return -1;
switch (item->item->getType())
{
case item_type::TRAPPARTS:
case item_type::CHAIN:
return 0;
case item_type::BARREL:
return 2;
default:
return 1;
}
}
bool check_component_wear(steam_engine_workshop *engine, int count, int power)
{
int coeffs[3] = { 0, power, count };
for (int i = contained_items.size()-1; i >= 0; i--)
{
int type = classify_component(contained_items[i]);
if (type < 0)
continue;
df::item *item = contained_items[i]->item;
int melt_temp = item->getMeltingPoint();
if (coeffs[type] == 0 || melt_temp >= engine->wear_temp)
continue;
// let 500 degree delta at 4 pressure work 1 season
float ticks = coeffs[type]*(engine->wear_temp - melt_temp)*3.0f/500.0f/4.0f;
if (item->addWear(int(8*(1 + ticks)), true, true))
return true;
}
return false;
}
float get_component_quality(int use_type)
{
float sum = 0, cnt = 0;
for (size_t i = 0; i < contained_items.size(); i++)
{
int type = classify_component(contained_items[i]);
if (type != use_type)
continue;
sum += contained_items[i]->item->getQuality();
cnt += 1;
}
return (cnt > 0 ? sum/cnt : 0);
}
int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level)
{
// total ticks to wear off completely
float ticks = WEAR_TICKS * 4.0f;
// dimension == days it lasts * 100
ticks /= 1200.0f * dimension / 100.0f;
// true power use
float power_rate = 1.0f;
// check the actual load
if (auto mptr = df::machine::find(machine.machine_id))
{
if (mptr->cur_power >= mptr->min_power)
power_rate = float(mptr->min_power) / mptr->cur_power;
else
power_rate = 0.0f;
}
// waste rate: 1-10% depending on piston assembly quality
float waste = 0.1f - 0.015f * get_component_quality(1);
// apply rate and waste factor
ticks *= (waste + 0.9f*power_rate)*power_level;
// end result
return std::max(1, int(ticks));
}
void update_under_construction(steam_engine_workshop *engine)
{
if (machine.machine_id != -1)
return;
int cur_count = 0;
if (auto first = collect_steam(engine, &cur_count))
{
if (add_wear_nodestroy(first, WEAR_TICKS*4/10))
{
boil_unit(first);
cur_count--;
}
}
set_steam_amount(cur_count);
}
void update_working(steam_engine_workshop *engine)
{
int old_count = get_steam_amount();
int old_power = std::min(engine->max_power, old_count);
int cur_count = 0;
if (auto first = collect_steam(engine, &cur_count))
{
int rate = get_steam_use_rate(engine, first->dimension, old_power);
if (add_wear_nodestroy(first, rate))
{
boil_unit(first);
cur_count--;
}
if (check_component_wear(engine, old_count, old_power))
return;
}
if (old_count < engine->max_capacity && cur_count == engine->max_capacity)
suspend_jobs(true);
else if (cur_count <= engine->max_power+1 && old_count > engine->max_power+1)
suspend_jobs(false);
set_steam_amount(cur_count);
int cur_power = std::min(engine->max_power, cur_count);
if (cur_power != old_power)
{
auto mptr = df::machine::find(machine.machine_id);
if (mptr)
mptr->cur_power += (cur_power - old_power)*100;
}
}
// Furnaces need architecture, and this is a workshop
// only because furnaces cannot connect to machines.
DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ())
{
if (get_steam_engine())
return true;
return INTERPOSE_NEXT(needsDesign)();
}
// Machine interface
DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info))
{
if (auto engine = get_steam_engine())
{
info->produced = std::min(engine->max_power, get_steam_amount())*100;
info->consumed = 10 - int(get_component_quality(0));
return;
}
INTERPOSE_NEXT(getPowerInfo)(info);
}
DEFINE_VMETHOD_INTERPOSE(df::machine_info*, getMachineInfo, ())
{
if (get_steam_engine())
return &machine;
return INTERPOSE_NEXT(getMachineInfo)();
}
DEFINE_VMETHOD_INTERPOSE(bool, isPowerSource, ())
{
if (get_steam_engine())
return true;
return INTERPOSE_NEXT(isPowerSource)();
}
DEFINE_VMETHOD_INTERPOSE(void, categorize, (bool free))
{
if (get_steam_engine())
{
auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE];
insert_into_vector(vec, &df::building::id, (df::building*)this);
}
INTERPOSE_NEXT(categorize)(free);
}
DEFINE_VMETHOD_INTERPOSE(void, uncategorize, ())
{
if (get_steam_engine())
{
auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE];
erase_from_vector(vec, &df::building::id, id);
}
INTERPOSE_NEXT(uncategorize)();
}
DEFINE_VMETHOD_INTERPOSE(bool, canConnectToMachine, (df::machine_tile_set *info))
{
if (auto engine = get_steam_engine())
{
int real_cx = centerx, real_cy = centery;
bool ok = false;
for (size_t i = 0; i < engine->gear_tiles.size(); i++)
{
// the original function connects to the center tile
centerx = x1 + engine->gear_tiles[i].x;
centery = y1 + engine->gear_tiles[i].y;
if (!INTERPOSE_NEXT(canConnectToMachine)(info))
continue;
ok = true;
break;
}
centerx = real_cx; centery = real_cy;
return ok;
}
else
return INTERPOSE_NEXT(canConnectToMachine)(info);
}
// Operation logic
DEFINE_VMETHOD_INTERPOSE(bool, isUnpowered, ())
{
if (auto engine = get_steam_engine())
{
df::coord water, magma;
return !find_liquids(&water, &magma, engine->is_magma, 3);
}
return INTERPOSE_NEXT(isUnpowered)();
}
DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
{
if (auto engine = get_steam_engine())
{
if (is_fully_built())
update_working(engine);
else
update_under_construction(engine);
if (flags.bits.almost_deleted)
return;
}
INTERPOSE_NEXT(updateAction)();
}
DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk))
{
INTERPOSE_NEXT(drawBuilding)(db, unk);
if (auto engine = get_steam_engine())
{
if (!is_fully_built())
return;
// If machine is running, tweak gear assemblies
auto mptr = df::machine::find(machine.machine_id);
if (mptr && (mptr->visual_phase & 1) != 0)
{
for (size_t i = 0; i < engine->gear_tiles.size(); i++)
{
auto pos = engine->gear_tiles[i];
db->tile[pos.x][pos.y] = 42;
}
}
// Use the hearth color to display power level
if (engine->hearth_tile.isValid())
{
auto pos = engine->hearth_tile;
int power = std::min(engine->max_power, get_steam_amount());
db->fore[pos.x][pos.y] = hearth_colors[power][0];
db->bright[pos.x][pos.y] = hearth_colors[power][1];
}
// Set liquid indicator state
if (engine->water_tile.isValid() || engine->magma_tile.isValid())
{
df::coord water, magma;
find_liquids(&water, &magma, engine->is_magma, 3);
df::coord dwater, dmagma;
find_liquids(&dwater, &dmagma, engine->is_magma, 5);
if (engine->water_tile.isValid())
{
if (!water.isValid())
db->fore[engine->water_tile.x][engine->water_tile.y] = 0;
else if (!dwater.isValid())
db->bright[engine->water_tile.x][engine->water_tile.y] = 0;
}
if (engine->magma_tile.isValid() && engine->is_magma)
{
if (!magma.isValid())
db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0;
else if (!dmagma.isValid())
db->bright[engine->magma_tile.x][engine->magma_tile.y] = 0;
}
}
}
}
DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost))
{
if (get_steam_engine())
{
// Explode if any steam left
if (int amount = get_steam_amount())
{
make_explosion(
df::coord((x1+x2)/2, (y1+y2)/2, z),
40 + amount * 20
);
random_boil();
}
}
INTERPOSE_NEXT(deconstructItems)(noscatter, lost);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, getPowerInfo);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, getMachineInfo);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isPowerSource);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, categorize);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, isUnpowered);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding);
IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems);
/*
* Hook for the dwarfmode screen. Tweaks the build menu
* behavior to suit the steam engine building more.
*/
struct dwarfmode_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
steam_engine_workshop *get_steam_engine()
{
if (ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector->stage == 1 &&
ui_build_selector->building_type == building_type::Workshop &&
ui_build_selector->building_subtype == workshop_type::Custom)
{
return find_steam_engine(ui_build_selector->custom_type);
}
return NULL;
}
void check_hanging_tiles(steam_engine_workshop *engine)
{
using df::global::cursor;
if (!engine) return;
bool error = false;
int x1 = cursor->x - engine->def->workloc_x;
int y1 = cursor->y - engine->def->workloc_y;
for (int x = 0; x < engine->def->dim_x; x++)
{
for (int y = 0; y < engine->def->dim_y; y++)
{
if (ui_build_selector->tiles[x][y] >= 5)
continue;
auto ptile = Maps::getTileType(x1+x,y1+y,cursor->z);
if (ptile && !isOpenTerrain(*ptile))
continue;
ui_build_selector->tiles[x][y] = 6;
error = true;
}
}
if (error)
{
const char *msg = "Hanging - cover channels with down stairs.";
ui_build_selector->errors.push_back(new std::string(msg));
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
steam_engine_workshop *engine = get_steam_engine();
// Selector insists that workshops cannot be placed hanging
// unless they require magma, so pretend we always do.
if (engine)
engine->def->needs_magma = true;
INTERPOSE_NEXT(feed)(input);
// Restore the flag
if (engine)
engine->def->needs_magma = engine->is_magma;
// And now, check for open space. Since these workshops
// are machines, they will collapse over true open space.
check_hanging_tiles(get_steam_engine());
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed);
/*
* Scan raws for matching workshop buildings.
*/
static bool find_engines()
{
engines.clear();
auto &wslist = world->raws.buildings.workshops;
for (size_t i = 0; i < wslist.size(); i++)
{
if (strstr(wslist[i]->code.c_str(), "STEAM_ENGINE") == NULL)
continue;
steam_engine_workshop ws;
ws.def = wslist[i];
ws.id = ws.def->id;
int bs = ws.def->build_stages;
for (int x = 0; x < ws.def->dim_x; x++)
{
for (int y = 0; y < ws.def->dim_y; y++)
{
switch (ws.def->tile[bs][x][y])
{
case 15:
ws.gear_tiles.push_back(df::coord2d(x,y));
break;
case 19:
ws.hearth_tile = df::coord2d(x,y);
break;
}
if (ws.def->tile_color[2][bs][x][y])
{
switch (ws.def->tile_color[0][bs][x][y])
{
case 1:
ws.water_tile = df::coord2d(x,y);
break;
case 4:
ws.magma_tile = df::coord2d(x,y);
break;
}
}
}
}
ws.is_magma = ws.def->needs_magma;
ws.max_power = ws.is_magma ? 5 : 3;
ws.max_capacity = ws.is_magma ? 10 : 6;
ws.wear_temp = ws.is_magma ? 12000 : 11000;
if (!ws.gear_tiles.empty())
engines.push_back(ws);
}
return !engines.empty();
}
static void enable_hooks(bool enable)
{
INTERPOSE_HOOK(liquid_hook, getItemDescription).apply(enable);
INTERPOSE_HOOK(liquid_hook, adjustTemperature).apply(enable);
INTERPOSE_HOOK(liquid_hook, checkTemperatureDamage).apply(enable);
INTERPOSE_HOOK(workshop_hook, needsDesign).apply(enable);
INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(enable);
INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(enable);
INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(enable);
INTERPOSE_HOOK(workshop_hook, categorize).apply(enable);
INTERPOSE_HOOK(workshop_hook, uncategorize).apply(enable);
INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable);
INTERPOSE_HOOK(workshop_hook, isUnpowered).apply(enable);
INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable);
INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable);
INTERPOSE_HOOK(workshop_hook, deconstructItems).apply(enable);
INTERPOSE_HOOK(dwarfmode_hook, feed).apply(enable);
}
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
if (find_engines())
{
out.print("Detected steam engine workshops - enabling plugin.\n");
enable_hooks(true);
}
else
enable_hooks(false);
break;
case SC_MAP_UNLOADED:
enable_hooks(false);
engines.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;
}

@ -23,6 +23,7 @@
#include "df/unit.h"
#include "df/unit_soul.h"
#include "df/unit_skill.h"
#include "df/creature_graphics_role.h"
#include "df/creature_raw.h"
#include "df/caste_raw.h"
@ -38,8 +39,6 @@ using df::global::ui;
using df::global::gps;
using df::global::enabler;
DFHACK_PLUGIN("manipulator");
struct SkillLevel
{
const char *name;
@ -76,168 +75,168 @@ const SkillLevel skill_levels[] = {
struct SkillColumn
{
df::profession profession;
df::unit_labor labor;
df::job_skill skill;
char label[3];
int group; // for navigation and mass toggling
int8_t color; // for column headers
df::profession profession; // to display graphical tiles
df::unit_labor labor; // toggled when pressing Enter
df::job_skill skill; // displayed rating
char label[3]; // column header
bool special; // specified labor is mutually exclusive with all other special labors
};
#define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn))
// All of the skill/labor columns we want to display. Includes profession (for color), labor, skill, and 2 character label
// All of the skill/labor columns we want to display.
const SkillColumn columns[] = {
// Mining
{profession::MINER, unit_labor::MINE, job_skill::MINING, "Mi", true},
{0, 7, profession::MINER, unit_labor::MINE, job_skill::MINING, "Mi", true},
// Woodworking
{profession::WOODWORKER, unit_labor::CARPENTER, job_skill::CARPENTRY, "Ca"},
{profession::WOODWORKER, unit_labor::BOWYER, job_skill::BOWYER, "Bw"},
{profession::WOODWORKER, unit_labor::CUTWOOD, job_skill::WOODCUTTING, "WC", true},
{1, 14, profession::CARPENTER, unit_labor::CARPENTER, job_skill::CARPENTRY, "Ca"},
{1, 14, profession::BOWYER, unit_labor::BOWYER, job_skill::BOWYER, "Bw"},
{1, 14, profession::WOODCUTTER, unit_labor::CUTWOOD, job_skill::WOODCUTTING, "WC", true},
// Stoneworking
{profession::STONEWORKER, unit_labor::MASON, job_skill::MASONRY, "Ma"},
{profession::STONEWORKER, unit_labor::DETAIL, job_skill::DETAILSTONE, "En"},
{2, 15, profession::MASON, unit_labor::MASON, job_skill::MASONRY, "Ma"},
{2, 15, profession::ENGRAVER, unit_labor::DETAIL, job_skill::DETAILSTONE, "En"},
// Hunting/Related
{profession::RANGER, unit_labor::ANIMALTRAIN, job_skill::ANIMALTRAIN, "Tn"},
{profession::RANGER, unit_labor::ANIMALCARE, job_skill::ANIMALCARE, "Ca"},
{profession::RANGER, unit_labor::HUNT, job_skill::SNEAK, "Hu", true},
{profession::RANGER, unit_labor::TRAPPER, job_skill::TRAPPING, "Tr"},
{profession::RANGER, unit_labor::DISSECT_VERMIN, job_skill::DISSECT_VERMIN, "Di"},
{3, 2, profession::ANIMAL_TRAINER, unit_labor::ANIMALTRAIN, job_skill::ANIMALTRAIN, "Tn"},
{3, 2, profession::ANIMAL_CARETAKER, unit_labor::ANIMALCARE, job_skill::ANIMALCARE, "Ca"},
{3, 2, profession::HUNTER, unit_labor::HUNT, job_skill::SNEAK, "Hu", true},
{3, 2, profession::TRAPPER, unit_labor::TRAPPER, job_skill::TRAPPING, "Tr"},
{3, 2, profession::ANIMAL_DISSECTOR, unit_labor::DISSECT_VERMIN, job_skill::DISSECT_VERMIN, "Di"},
// Healthcare
{profession::DOCTOR, unit_labor::DIAGNOSE, job_skill::DIAGNOSE, "Di"},
{profession::DOCTOR, unit_labor::SURGERY, job_skill::SURGERY, "Su"},
{profession::DOCTOR, unit_labor::BONE_SETTING, job_skill::SET_BONE, "Bo"},
{profession::DOCTOR, unit_labor::SUTURING, job_skill::SUTURE, "St"},
{profession::DOCTOR, unit_labor::DRESSING_WOUNDS, job_skill::DRESS_WOUNDS, "Dr"},
{profession::DOCTOR, unit_labor::FEED_WATER_CIVILIANS, job_skill::NONE, "Fd"},
{profession::DOCTOR, unit_labor::RECOVER_WOUNDED, job_skill::NONE, "Re"},
{4, 5, profession::DIAGNOSER, unit_labor::DIAGNOSE, job_skill::DIAGNOSE, "Di"},
{4, 5, profession::SURGEON, unit_labor::SURGERY, job_skill::SURGERY, "Su"},
{4, 5, profession::BONE_SETTER, unit_labor::BONE_SETTING, job_skill::SET_BONE, "Bo"},
{4, 5, profession::SUTURER, unit_labor::SUTURING, job_skill::SUTURE, "St"},
{4, 5, profession::DOCTOR, unit_labor::DRESSING_WOUNDS, job_skill::DRESS_WOUNDS, "Dr"},
{4, 5, profession::NONE, unit_labor::FEED_WATER_CIVILIANS, job_skill::NONE, "Fd"},
{4, 5, profession::NONE, unit_labor::RECOVER_WOUNDED, job_skill::NONE, "Re"},
// Farming/Related
{profession::FARMER, unit_labor::BUTCHER, job_skill::BUTCHER, "Bu"},
{profession::FARMER, unit_labor::TANNER, job_skill::TANNER, "Ta"},
{profession::FARMER, unit_labor::PLANT, job_skill::PLANT, "Gr"},
{profession::FARMER, unit_labor::DYER, job_skill::DYER, "Dy"},
{profession::FARMER, unit_labor::SOAP_MAKER, job_skill::SOAP_MAKING, "So"},
{profession::FARMER, unit_labor::BURN_WOOD, job_skill::WOOD_BURNING, "WB"},
{profession::FARMER, unit_labor::POTASH_MAKING, job_skill::POTASH_MAKING, "Po"},
{profession::FARMER, unit_labor::LYE_MAKING, job_skill::LYE_MAKING, "Ly"},
{profession::FARMER, unit_labor::MILLER, job_skill::MILLING, "Ml"},
{profession::FARMER, unit_labor::BREWER, job_skill::BREWING, "Br"},
{profession::FARMER, unit_labor::HERBALIST, job_skill::HERBALISM, "He"},
{profession::FARMER, unit_labor::PROCESS_PLANT, job_skill::PROCESSPLANTS, "Th"},
{profession::FARMER, unit_labor::MAKE_CHEESE, job_skill::CHEESEMAKING, "Ch"},
{profession::FARMER, unit_labor::MILK, job_skill::MILK, "Mk"},
{profession::FARMER, unit_labor::SHEARER, job_skill::SHEARING, "Sh"},
{profession::FARMER, unit_labor::SPINNER, job_skill::SPINNING, "Sp"},
{profession::FARMER, unit_labor::COOK, job_skill::COOK, "Co"},
{profession::FARMER, unit_labor::PRESSING, job_skill::PRESSING, "Pr"},
{profession::FARMER, unit_labor::BEEKEEPING, job_skill::BEEKEEPING, "Be"},
{5, 6, profession::BUTCHER, unit_labor::BUTCHER, job_skill::BUTCHER, "Bu"},
{5, 6, profession::TANNER, unit_labor::TANNER, job_skill::TANNER, "Ta"},
{5, 6, profession::PLANTER, unit_labor::PLANT, job_skill::PLANT, "Gr"},
{5, 6, profession::DYER, unit_labor::DYER, job_skill::DYER, "Dy"},
{5, 6, profession::SOAP_MAKER, unit_labor::SOAP_MAKER, job_skill::SOAP_MAKING, "So"},
{5, 6, profession::WOOD_BURNER, unit_labor::BURN_WOOD, job_skill::WOOD_BURNING, "WB"},
{5, 6, profession::POTASH_MAKER, unit_labor::POTASH_MAKING, job_skill::POTASH_MAKING, "Po"},
{5, 6, profession::LYE_MAKER, unit_labor::LYE_MAKING, job_skill::LYE_MAKING, "Ly"},
{5, 6, profession::MILLER, unit_labor::MILLER, job_skill::MILLING, "Ml"},
{5, 6, profession::BREWER, unit_labor::BREWER, job_skill::BREWING, "Br"},
{5, 6, profession::HERBALIST, unit_labor::HERBALIST, job_skill::HERBALISM, "He"},
{5, 6, profession::THRESHER, unit_labor::PROCESS_PLANT, job_skill::PROCESSPLANTS, "Th"},
{5, 6, profession::CHEESE_MAKER, unit_labor::MAKE_CHEESE, job_skill::CHEESEMAKING, "Ch"},
{5, 6, profession::MILKER, unit_labor::MILK, job_skill::MILK, "Mk"},
{5, 6, profession::SHEARER, unit_labor::SHEARER, job_skill::SHEARING, "Sh"},
{5, 6, profession::SPINNER, unit_labor::SPINNER, job_skill::SPINNING, "Sp"},
{5, 6, profession::COOK, unit_labor::COOK, job_skill::COOK, "Co"},
{5, 6, profession::PRESSER, unit_labor::PRESSING, job_skill::PRESSING, "Pr"},
{5, 6, profession::BEEKEEPER, unit_labor::BEEKEEPING, job_skill::BEEKEEPING, "Be"},
// Fishing/Related
{profession::FISHERMAN, unit_labor::FISH, job_skill::FISH, "Fi"},
{profession::FISHERMAN, unit_labor::CLEAN_FISH, job_skill::PROCESSFISH, "Cl"},
{profession::FISHERMAN, unit_labor::DISSECT_FISH, job_skill::DISSECT_FISH, "Di"},
{6, 1, profession::FISHERMAN, unit_labor::FISH, job_skill::FISH, "Fi"},
{6, 1, profession::FISH_CLEANER, unit_labor::CLEAN_FISH, job_skill::PROCESSFISH, "Cl"},
{6, 1, profession::FISH_DISSECTOR, unit_labor::DISSECT_FISH, job_skill::DISSECT_FISH, "Di"},
// Metalsmithing
{profession::METALSMITH, unit_labor::SMELT, job_skill::SMELT, "Fu"},
{profession::METALSMITH, unit_labor::FORGE_WEAPON, job_skill::FORGE_WEAPON, "We"},
{profession::METALSMITH, unit_labor::FORGE_ARMOR, job_skill::FORGE_ARMOR, "Ar"},
{profession::METALSMITH, unit_labor::FORGE_FURNITURE, job_skill::FORGE_FURNITURE, "Bl"},
{profession::METALSMITH, unit_labor::METAL_CRAFT, job_skill::METALCRAFT, "Cr"},
{7, 8, profession::FURNACE_OPERATOR, unit_labor::SMELT, job_skill::SMELT, "Fu"},
{7, 8, profession::WEAPONSMITH, unit_labor::FORGE_WEAPON, job_skill::FORGE_WEAPON, "We"},
{7, 8, profession::ARMORER, unit_labor::FORGE_ARMOR, job_skill::FORGE_ARMOR, "Ar"},
{7, 8, profession::BLACKSMITH, unit_labor::FORGE_FURNITURE, job_skill::FORGE_FURNITURE, "Bl"},
{7, 8, profession::METALCRAFTER, unit_labor::METAL_CRAFT, job_skill::METALCRAFT, "Cr"},
// Jewelry
{profession::JEWELER, unit_labor::CUT_GEM, job_skill::CUTGEM, "Cu"},
{profession::JEWELER, unit_labor::ENCRUST_GEM, job_skill::ENCRUSTGEM, "Se"},
{8, 10, profession::GEM_CUTTER, unit_labor::CUT_GEM, job_skill::CUTGEM, "Cu"},
{8, 10, profession::GEM_SETTER, unit_labor::ENCRUST_GEM, job_skill::ENCRUSTGEM, "Se"},
// Crafts
{profession::CRAFTSMAN, unit_labor::LEATHER, job_skill::LEATHERWORK, "Le"},
{profession::CRAFTSMAN, unit_labor::WOOD_CRAFT, job_skill::WOODCRAFT, "Wo"},
{profession::CRAFTSMAN, unit_labor::STONE_CRAFT, job_skill::STONECRAFT, "St"},
{profession::CRAFTSMAN, unit_labor::BONE_CARVE, job_skill::BONECARVE, "Bo"},
{profession::CRAFTSMAN, unit_labor::GLASSMAKER, job_skill::GLASSMAKER, "Gl"},
{profession::CRAFTSMAN, unit_labor::WEAVER, job_skill::WEAVING, "We"},
{profession::CRAFTSMAN, unit_labor::CLOTHESMAKER, job_skill::CLOTHESMAKING, "Cl"},
{profession::CRAFTSMAN, unit_labor::EXTRACT_STRAND, job_skill::EXTRACT_STRAND, "Ad"},
{profession::CRAFTSMAN, unit_labor::POTTERY, job_skill::POTTERY, "Po"},
{profession::CRAFTSMAN, unit_labor::GLAZING, job_skill::GLAZING, "Gl"},
{profession::CRAFTSMAN, unit_labor::WAX_WORKING, job_skill::WAX_WORKING, "Wx"},
{9, 9, profession::LEATHERWORKER, unit_labor::LEATHER, job_skill::LEATHERWORK, "Le"},
{9, 9, profession::WOODCRAFTER, unit_labor::WOOD_CRAFT, job_skill::WOODCRAFT, "Wo"},
{9, 9, profession::STONECRAFTER, unit_labor::STONE_CRAFT, job_skill::STONECRAFT, "St"},
{9, 9, profession::BONE_CARVER, unit_labor::BONE_CARVE, job_skill::BONECARVE, "Bo"},
{9, 9, profession::GLASSMAKER, unit_labor::GLASSMAKER, job_skill::GLASSMAKER, "Gl"},
{9, 9, profession::WEAVER, unit_labor::WEAVER, job_skill::WEAVING, "We"},
{9, 9, profession::CLOTHIER, unit_labor::CLOTHESMAKER, job_skill::CLOTHESMAKING, "Cl"},
{9, 9, profession::STRAND_EXTRACTOR, unit_labor::EXTRACT_STRAND, job_skill::EXTRACT_STRAND, "Ad"},
{9, 9, profession::POTTER, unit_labor::POTTERY, job_skill::POTTERY, "Po"},
{9, 9, profession::GLAZER, unit_labor::GLAZING, job_skill::GLAZING, "Gl"},
{9, 9, profession::WAX_WORKER, unit_labor::WAX_WORKING, job_skill::WAX_WORKING, "Wx"},
// Engineering
{profession::ENGINEER, unit_labor::SIEGECRAFT, job_skill::SIEGECRAFT, "En"},
{profession::ENGINEER, unit_labor::SIEGEOPERATE, job_skill::SIEGEOPERATE, "Op"},
{profession::ENGINEER, unit_labor::MECHANIC, job_skill::MECHANICS, "Me"},
{profession::ENGINEER, unit_labor::OPERATE_PUMP, job_skill::OPERATE_PUMP, "Pu"},
{10, 12, profession::SIEGE_ENGINEER, unit_labor::SIEGECRAFT, job_skill::SIEGECRAFT, "En"},
{10, 12, profession::SIEGE_OPERATOR, unit_labor::SIEGEOPERATE, job_skill::SIEGEOPERATE, "Op"},
{10, 12, profession::MECHANIC, unit_labor::MECHANIC, job_skill::MECHANICS, "Me"},
{10, 12, profession::PUMP_OPERATOR, unit_labor::OPERATE_PUMP, job_skill::OPERATE_PUMP, "Pu"},
// Hauling
{profession::STANDARD, unit_labor::HAUL_STONE, job_skill::NONE, "St"},
{profession::STANDARD, unit_labor::HAUL_WOOD, job_skill::NONE, "Wo"},
{profession::STANDARD, unit_labor::HAUL_ITEM, job_skill::NONE, "It"},
{profession::STANDARD, unit_labor::HAUL_BODY, job_skill::NONE, "Bu"},
{profession::STANDARD, unit_labor::HAUL_FOOD, job_skill::NONE, "Fo"},
{profession::STANDARD, unit_labor::HAUL_REFUSE, job_skill::NONE, "Re"},
{profession::STANDARD, unit_labor::HAUL_FURNITURE, job_skill::NONE, "Fu"},
{profession::STANDARD, unit_labor::HAUL_ANIMAL, job_skill::NONE, "An"},
{profession::STANDARD, unit_labor::PUSH_HAUL_VEHICLE, job_skill::NONE, "Ve"},
{11, 3, profession::NONE, unit_labor::HAUL_STONE, job_skill::NONE, "St"},
{11, 3, profession::NONE, unit_labor::HAUL_WOOD, job_skill::NONE, "Wo"},
{11, 3, profession::NONE, unit_labor::HAUL_ITEM, job_skill::NONE, "It"},
{11, 3, profession::NONE, unit_labor::HAUL_BODY, job_skill::NONE, "Bu"},
{11, 3, profession::NONE, unit_labor::HAUL_FOOD, job_skill::NONE, "Fo"},
{11, 3, profession::NONE, unit_labor::HAUL_REFUSE, job_skill::NONE, "Re"},
{11, 3, profession::NONE, unit_labor::HAUL_FURNITURE, job_skill::NONE, "Fu"},
{11, 3, profession::NONE, unit_labor::HAUL_ANIMAL, job_skill::NONE, "An"},
{11, 3, profession::NONE, unit_labor::PUSH_HAUL_VEHICLE, job_skill::NONE, "Ve"},
// Other Jobs
{profession::CHILD, unit_labor::ARCHITECT, job_skill::DESIGNBUILDING, "Ar"},
{profession::CHILD, unit_labor::ALCHEMIST, job_skill::ALCHEMY, "Al"},
{profession::CHILD, unit_labor::CLEAN, job_skill::NONE, "Cl"},
// Military
{profession::WRESTLER, unit_labor::NONE, job_skill::WRESTLING, "Wr"},
{profession::WRESTLER, unit_labor::NONE, job_skill::AXE, "Ax"},
{profession::WRESTLER, unit_labor::NONE, job_skill::SWORD, "Sw"},
{profession::WRESTLER, unit_labor::NONE, job_skill::MACE, "Mc"},
{profession::WRESTLER, unit_labor::NONE, job_skill::HAMMER, "Ha"},
{profession::WRESTLER, unit_labor::NONE, job_skill::SPEAR, "Sp"},
{profession::WRESTLER, unit_labor::NONE, job_skill::DAGGER, "Kn"},
{profession::WRESTLER, unit_labor::NONE, job_skill::CROSSBOW, "Cb"},
{profession::WRESTLER, unit_labor::NONE, job_skill::BOW, "Bo"},
{profession::WRESTLER, unit_labor::NONE, job_skill::BLOWGUN, "Bl"},
{profession::WRESTLER, unit_labor::NONE, job_skill::PIKE, "Pk"},
{profession::WRESTLER, unit_labor::NONE, job_skill::WHIP, "La"},
{profession::WRESTLER, unit_labor::NONE, job_skill::ARMOR, "Ar"},
{profession::WRESTLER, unit_labor::NONE, job_skill::SHIELD, "Sh"},
{profession::WRESTLER, unit_labor::NONE, job_skill::BITE, "Bi"},
{profession::WRESTLER, unit_labor::NONE, job_skill::GRASP_STRIKE, "St"},
{profession::WRESTLER, unit_labor::NONE, job_skill::STANCE_STRIKE, "Ki"},
{profession::WRESTLER, unit_labor::NONE, job_skill::DODGING, "Do"},
{profession::WRESTLER, unit_labor::NONE, job_skill::MISC_WEAPON, "Mi"},
{profession::WRESTLER, unit_labor::NONE, job_skill::MELEE_COMBAT, "Fg"},
{profession::WRESTLER, unit_labor::NONE, job_skill::RANGED_COMBAT, "Ac"},
{profession::RECRUIT, unit_labor::NONE, job_skill::LEADERSHIP, "Ld"},
{profession::RECRUIT, unit_labor::NONE, job_skill::TEACHING, "Te"},
{profession::RECRUIT, unit_labor::NONE, job_skill::KNOWLEDGE_ACQUISITION, "St"},
{profession::RECRUIT, unit_labor::NONE, job_skill::DISCIPLINE, "Di"},
{profession::RECRUIT, unit_labor::NONE, job_skill::CONCENTRATION, "Co"},
{profession::RECRUIT, unit_labor::NONE, job_skill::SITUATIONAL_AWARENESS, "Ob"},
{profession::RECRUIT, unit_labor::NONE, job_skill::COORDINATION, "Cr"},
{profession::RECRUIT, unit_labor::NONE, job_skill::BALANCE, "Ba"},
{12, 4, profession::ARCHITECT, unit_labor::ARCHITECT, job_skill::DESIGNBUILDING, "Ar"},
{12, 4, profession::ALCHEMIST, unit_labor::ALCHEMIST, job_skill::ALCHEMY, "Al"},
{12, 4, profession::NONE, unit_labor::CLEAN, job_skill::NONE, "Cl"},
// Military - Weapons
{13, 7, profession::WRESTLER, unit_labor::NONE, job_skill::WRESTLING, "Wr"},
{13, 7, profession::AXEMAN, unit_labor::NONE, job_skill::AXE, "Ax"},
{13, 7, profession::SWORDSMAN, unit_labor::NONE, job_skill::SWORD, "Sw"},
{13, 7, profession::MACEMAN, unit_labor::NONE, job_skill::MACE, "Mc"},
{13, 7, profession::HAMMERMAN, unit_labor::NONE, job_skill::HAMMER, "Ha"},
{13, 7, profession::SPEARMAN, unit_labor::NONE, job_skill::SPEAR, "Sp"},
{13, 7, profession::CROSSBOWMAN, unit_labor::NONE, job_skill::CROSSBOW, "Cb"},
{13, 7, profession::THIEF, unit_labor::NONE, job_skill::DAGGER, "Kn"},
{13, 7, profession::BOWMAN, unit_labor::NONE, job_skill::BOW, "Bo"},
{13, 7, profession::BLOWGUNMAN, unit_labor::NONE, job_skill::BLOWGUN, "Bl"},
{13, 7, profession::PIKEMAN, unit_labor::NONE, job_skill::PIKE, "Pk"},
{13, 7, profession::LASHER, unit_labor::NONE, job_skill::WHIP, "La"},
// Military - Other Combat
{14, 15, profession::NONE, unit_labor::NONE, job_skill::BITE, "Bi"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::GRASP_STRIKE, "St"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::STANCE_STRIKE, "Ki"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::MISC_WEAPON, "Mi"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::MELEE_COMBAT, "Fg"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::RANGED_COMBAT, "Ac"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::ARMOR, "Ar"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::SHIELD, "Sh"},
{14, 15, profession::NONE, unit_labor::NONE, job_skill::DODGING, "Do"},
// Military - Misc
{15, 8, profession::NONE, unit_labor::NONE, job_skill::LEADERSHIP, "Ld"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::TEACHING, "Te"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::KNOWLEDGE_ACQUISITION, "St"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::DISCIPLINE, "Di"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::CONCENTRATION, "Co"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::SITUATIONAL_AWARENESS, "Ob"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::COORDINATION, "Cr"},
{15, 8, profession::NONE, unit_labor::NONE, job_skill::BALANCE, "Ba"},
// Social
{profession::STANDARD, unit_labor::NONE, job_skill::PERSUASION, "Pe"},
{profession::STANDARD, unit_labor::NONE, job_skill::NEGOTIATION, "Ne"},
{profession::STANDARD, unit_labor::NONE, job_skill::JUDGING_INTENT, "Ju"},
{profession::STANDARD, unit_labor::NONE, job_skill::LYING, "Li"},
{profession::STANDARD, unit_labor::NONE, job_skill::INTIMIDATION, "In"},
{profession::STANDARD, unit_labor::NONE, job_skill::CONVERSATION, "Cn"},
{profession::STANDARD, unit_labor::NONE, job_skill::COMEDY, "Cm"},
{profession::STANDARD, unit_labor::NONE, job_skill::FLATTERY, "Fl"},
{profession::STANDARD, unit_labor::NONE, job_skill::CONSOLE, "Cs"},
{profession::STANDARD, unit_labor::NONE, job_skill::PACIFY, "Pc"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::PERSUASION, "Pe"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::NEGOTIATION, "Ne"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::JUDGING_INTENT, "Ju"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::LYING, "Li"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::INTIMIDATION, "In"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::CONVERSATION, "Cn"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::COMEDY, "Cm"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::FLATTERY, "Fl"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::CONSOLE, "Cs"},
{16, 3, profession::NONE, unit_labor::NONE, job_skill::PACIFY, "Pc"},
// Noble
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::APPRAISAL, "Ap"},
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::ORGANIZATION, "Or"},
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::RECORD_KEEPING, "RK"},
{17, 5, profession::TRADER, unit_labor::NONE, job_skill::APPRAISAL, "Ap"},
{17, 5, profession::ADMINISTRATOR, unit_labor::NONE, job_skill::ORGANIZATION, "Or"},
{17, 5, profession::CLERK, unit_labor::NONE, job_skill::RECORD_KEEPING, "RK"},
// Miscellaneous
{profession::STANDARD, unit_labor::NONE, job_skill::THROW, "Th"},
{profession::STANDARD, unit_labor::NONE, job_skill::CRUTCH_WALK, "CW"},
{profession::STANDARD, unit_labor::NONE, job_skill::SWIMMING, "Sw"},
{profession::STANDARD, unit_labor::NONE, job_skill::KNAPPING, "Kn"},
{profession::DRUNK, unit_labor::NONE, job_skill::WRITING, "Wr"},
{profession::DRUNK, unit_labor::NONE, job_skill::PROSE, "Pr"},
{profession::DRUNK, unit_labor::NONE, job_skill::POETRY, "Po"},
{profession::DRUNK, unit_labor::NONE, job_skill::READING, "Rd"},
{profession::DRUNK, unit_labor::NONE, job_skill::SPEAKING, "Sp"},
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::MILITARY_TACTICS, "MT"},
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::TRACKING, "Tr"},
{profession::ADMINISTRATOR, unit_labor::NONE, job_skill::MAGIC_NATURE, "Dr"},
{18, 3, profession::NONE, unit_labor::NONE, job_skill::THROW, "Th"},
{18, 3, profession::NONE, unit_labor::NONE, job_skill::CRUTCH_WALK, "CW"},
{18, 3, profession::NONE, unit_labor::NONE, job_skill::SWIMMING, "Sw"},
{18, 3, profession::NONE, unit_labor::NONE, job_skill::KNAPPING, "Kn"},
{19, 6, profession::NONE, unit_labor::NONE, job_skill::WRITING, "Wr"},
{19, 6, profession::NONE, unit_labor::NONE, job_skill::PROSE, "Pr"},
{19, 6, profession::NONE, unit_labor::NONE, job_skill::POETRY, "Po"},
{19, 6, profession::NONE, unit_labor::NONE, job_skill::READING, "Rd"},
{19, 6, profession::NONE, unit_labor::NONE, job_skill::SPEAKING, "Sp"},
{20, 5, profession::NONE, unit_labor::NONE, job_skill::MILITARY_TACTICS, "MT"},
{20, 5, profession::NONE, unit_labor::NONE, job_skill::TRACKING, "Tr"},
{20, 5, profession::NONE, unit_labor::NONE, job_skill::MAGIC_NATURE, "Dr"},
};
struct UnitInfo
@ -470,16 +469,16 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
{
// go to beginning of current column group; if already at the beginning, go to the beginning of the previous one
sel_column--;
df::profession cur = columns[sel_column].profession;
while ((sel_column > 0) && columns[sel_column - 1].profession == cur)
int cur = columns[sel_column].group;
while ((sel_column > 0) && columns[sel_column - 1].group == cur)
sel_column--;
}
if ((sel_column != NUM_COLUMNS - 1) && events->count(interface_key::CURSOR_DOWN_Z))
{
// go to end of current column group; if already at the end, go to the end of the next one
sel_column++;
df::profession cur = columns[sel_column].profession;
while ((sel_column < NUM_COLUMNS - 1) && columns[sel_column + 1].profession == cur)
int cur = columns[sel_column].group;
while ((sel_column < NUM_COLUMNS - 1) && columns[sel_column + 1].group == cur)
sel_column++;
}
@ -498,9 +497,10 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
{
df::unit *unit = cur->unit;
const SkillColumn &col = columns[sel_column];
bool newstatus = !unit->status.labors[col.labor];
if (col.special)
{
if (!unit->status.labors[col.labor])
if (newstatus)
{
for (int i = 0; i < NUM_COLUMNS; i++)
{
@ -510,7 +510,31 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
}
unit->military.pickup_flags.bits.update = true;
}
unit->status.labors[col.labor] = !unit->status.labors[col.labor];
unit->status.labors[col.labor] = newstatus;
}
if (events->count(interface_key::SELECT_ALL) && (cur->allowEdit))
{
df::unit *unit = cur->unit;
const SkillColumn &col = columns[sel_column];
bool newstatus = !unit->status.labors[col.labor];
for (int i = 0; i < NUM_COLUMNS; i++)
{
if (columns[i].group != col.group)
continue;
if (columns[i].special)
{
if (newstatus)
{
for (int j = 0; j < NUM_COLUMNS; j++)
{
if ((columns[j].labor != unit_labor::NONE) && columns[j].special)
unit->status.labors[columns[j].labor] = false;
}
}
unit->military.pickup_flags.bits.update = true;
}
unit->status.labors[columns[i].labor] = newstatus;
}
}
if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN))
@ -587,7 +611,7 @@ void viewscreen_unitlaborsst::render()
if (col_offset >= NUM_COLUMNS)
break;
int8_t fg = Units::getCasteProfessionColor(ui->race_id, -1, columns[col_offset].profession);
int8_t fg = columns[col_offset].color;
int8_t bg = 0;
if (col_offset == sel_column)
@ -598,6 +622,16 @@ void viewscreen_unitlaborsst::render()
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[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2);
df::profession profession = columns[col_offset].profession;
if (profession != profession::NONE)
{
auto graphics = world->raws.creatures.all[ui->race_id]->graphics;
Screen::paintTile(
Screen::Pen(' ', fg, 0,
graphics.profession_add_color[creature_graphics_role::DEFAULT][profession],
graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]),
1 + name_width + 1 + prof_width + 1 + col, 3);
}
}
for (int row = 0; row < height; row++)
@ -617,14 +651,14 @@ void viewscreen_unitlaborsst::render()
string name = cur->name;
name.resize(name_width);
Screen::paintString(Screen::Pen(' ', fg, bg), 1, 3 + row, name);
Screen::paintString(Screen::Pen(' ', fg, bg), 1, 4 + row, name);
string profession = cur->profession;
profession.resize(prof_width);
fg = cur->color;
bg = 0;
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 3 + row, profession);
Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 4 + row, profession);
// Print unit's skills and labor assignments
for (int col = 0; col < labors_width; col++)
@ -632,7 +666,7 @@ void viewscreen_unitlaborsst::render()
int col_offset = col + first_column;
fg = 15;
bg = 0;
char c = 0xFA;
uint8_t c = 0xFA;
if ((col_offset == sel_column) && (row_offset == sel_row))
fg = 9;
if (columns[col_offset].skill != job_skill::NONE)
@ -659,11 +693,12 @@ void viewscreen_unitlaborsst::render()
}
else
bg = 4;
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 3 + row);
Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 4 + row);
}
}
UnitInfo *cur = units[sel_row];
bool canToggle = false;
if (cur != NULL)
{
df::unit *unit = cur->unit;
@ -706,22 +741,27 @@ void viewscreen_unitlaborsst::render()
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);
canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE);
}
int x = 1;
int x = 2;
OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key
OutputString(15, x, gps->dimy - 3, ": Toggle labor, ");
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, ");
OutputString(10, x, gps->dimy - 3, "Shift+Enter"); // SELECT_ALL key
OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, ");
OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key
OutputString(15, x, gps->dimy - 3, ": ViewCre, ");
OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre, ");
OutputString(15, x, gps->dimy - 3, ": Zoom-Cre");
OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key
OutputString(15, x, gps->dimy - 3, ": Done");
x = 2;
OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key
OutputString(15, x, gps->dimy - 2, ": Done, ");
x = 1;
OutputString(10, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key
OutputString(10, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key
OutputString(15, x, gps->dimy - 2, ": Sort by Skill, ");
@ -761,23 +801,35 @@ struct unitlist_hook : df::viewscreen_unitlistst
}
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (units[page].size())
{
int x = 2;
OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key
OutputString(15, x, gps->dimy - 2, ": Manage labors");
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(unitlist_hook, render);
DFHACK_PLUGIN("manipulator");
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
if (gps)
{
if (!INTERPOSE_HOOK(unitlist_hook, feed).apply())
out.printerr("Could not interpose viewscreen_unitlistst::feed\n");
}
if (!gps || !INTERPOSE_HOOK(unitlist_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_hook, render).apply())
out.printerr("Could not insert Dwarf Manipulator hooks!\n");
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
INTERPOSE_HOOK(unitlist_hook, feed).remove();
INTERPOSE_HOOK(unitlist_hook, render).remove();
return CR_OK;
}

@ -7,6 +7,7 @@
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Units.h"
#include "modules/Items.h"
@ -29,6 +30,10 @@
#include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/squad_order_trainst.h"
#include "df/ui_build_selector.h"
#include "df/building_trapst.h"
#include "df/item_actual.h"
#include "df/contaminant.h"
#include <stdlib.h>
@ -40,6 +45,9 @@ using namespace df::enums;
using df::global::ui;
using df::global::world;
using df::global::ui_build_selector;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
using namespace DFHack::Gui;
@ -77,6 +85,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Causes 'Train' orders to no longer be considered 'patrol duty' so\n"
" soldiers will stop getting unhappy thoughts. Does NOT fix the problem\n"
" when soldiers go off-duty (i.e. civilian).\n"
" tweak readable-build-plate [disable]\n"
" Fixes rendering of creature weight limits in pressure plate build menu.\n"
" tweak stable-temp [disable]\n"
" Fixes performance bug 6012 by squashing jitter in temperature updates.\n"
));
return CR_OK;
}
@ -189,6 +201,8 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed);
struct patrol_duty_hook : df::squad_order_trainst
{
typedef df::squad_order_trainst interpose_base;
@ -199,9 +213,102 @@ struct patrol_duty_hook : df::squad_order_trainst
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol);
struct readable_build_plate_hook : df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (ui->main.mode == ui_sidebar_mode::Build &&
ui_build_selector->stage == 1 &&
ui_build_selector->building_type == building_type::Trap &&
ui_build_selector->building_subtype == trap_type::PressurePlate &&
ui_build_selector->plate_info.flags.bits.units)
{
auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1;
Screen::Pen pen(' ',COLOR_WHITE);
int minv = ui_build_selector->plate_info.unit_min;
if ((minv % 1000) == 0)
Screen::paintString(pen, x+11, 14, stl_sprintf("%3dK ", minv/1000));
int maxv = ui_build_selector->plate_info.unit_max;
if (maxv < 200000 && (maxv % 1000) == 0)
Screen::paintString(pen, x+24, 14, stl_sprintf("%3dK ", maxv/1000));
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(readable_build_plate_hook, render);
struct stable_temp_hook : df::item_actual {
typedef df::item_actual interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult))
{
if (temperature != temp)
{
// Bug 6012 is caused by fixed-point precision mismatch jitter
// when an item is being pushed by two sources at N and N+1.
// This check suppresses it altogether.
if (temp == temperature+1 ||
(temp == temperature-1 && temperature_fraction == 0))
temp = temperature;
// When SPEC_HEAT is NONE, the original function seems to not
// change the temperature, yet return true, which is silly.
else if (getSpecHeat() == 60001)
temp = temperature;
}
return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult);
}
DEFINE_VMETHOD_INTERPOSE(bool, updateContaminants, ())
{
if (contaminants)
{
// Force 1-degree difference in contaminant temperature to 0
for (size_t i = 0; i < contaminants->size(); i++)
{
auto obj = (*contaminants)[i];
if (abs(obj->temperature - temperature) == 1)
{
obj->temperature = temperature;
obj->temperature_fraction = temperature_fraction;
}
}
}
return INTERPOSE_NEXT(updateContaminants)();
}
};
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, adjustTemperature);
IMPLEMENT_VMETHOD_INTERPOSE(stable_temp_hook, updateContaminants);
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector <string> &parameters)
{
if (vector_get(parameters, 1) == "disable")
{
hook.remove();
out.print("Disabled tweak %s\n", parameters[0].c_str());
}
else
{
if (hook.apply())
out.print("Enabled tweak %s\n", parameters[0].c_str());
else
out.printerr("Could not activate tweak %s\n", parameters[0].c_str());
}
}
static command_result tweak(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -302,19 +409,26 @@ static command_result tweak(color_ostream &out, vector <string> &parameters)
}
else if (cmd == "stable-cursor")
{
auto &hook = INTERPOSE_HOOK(stable_cursor_hook, feed);
if (vector_get(parameters, 1) == "disable")
hook.remove();
else
hook.apply();
enable_hook(out, INTERPOSE_HOOK(stable_cursor_hook, feed), parameters);
}
else if (cmd == "patrol-duty")
{
auto &hook = INTERPOSE_HOOK(patrol_duty_hook, isPatrol);
if (vector_get(parameters, 1) == "disable")
hook.remove();
else
hook.apply();
enable_hook(out, INTERPOSE_HOOK(patrol_duty_hook, isPatrol), parameters);
}
else if (cmd == "readable-build-plate")
{
if (!ui_build_selector || !ui_menu_width || !ui_area_map_width)
{
out.printerr("Necessary globals not known.\n");
return CR_FAILURE;
}
enable_hook(out, INTERPOSE_HOOK(readable_build_plate_hook, render), parameters);
}
else if (cmd == "stable-temp")
{
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters);
}
else
return CR_WRONG_USAGE;

@ -0,0 +1,14 @@
-- Prints memory ranges of the process.
for _,v in ipairs(dfhack.internal.getMemRanges()) do
local access = { '-', '-', '-', 'p' }
if v.read then access[1] = 'r' end
if v.write then access[2] = 'w' end
if v.execute then access[3] = 'x' end
if not v.valid then
access[4] = '?'
elseif v.shared then
access[4] = 's'
end
print(string.format('%08x-%08x %s %s', v.start_addr, v.end_addr, table.concat(access), v.name))
end

@ -122,7 +122,7 @@ function MechanismList:onInput(keys)
end
end
if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some' then
if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then
qerror("This script requires the main dwarfmode view in 'q' mode")
end