diff --git a/LUA_API.rst b/LUA_API.rst index a7dab21b0..542034f40 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -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. diff --git a/Lua API.html b/Lua API.html index b9f09cf96..63a4c8547 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1019,9 +1019,16 @@ the container itself.

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.

    +
  • @@ -1177,6 +1184,9 @@ Returns false in case of error.

  • dfhack.maps.getBlock(x,y,z)

    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.

  • @@ -1186,6 +1196,10 @@ Returns false in case of error.

  • dfhack.maps.enableBlockUpdates(block[,flow,temperature])

    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.

  • diff --git a/dfhack.init-example b/dfhack.init-example index 380bdd04f..39c0e61df 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -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 diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 341164441..fa2aacf78 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -374,7 +374,7 @@ void DFHack::bitfieldToString(std::vector *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); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6dfb2f354..4e57b113f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -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 #include @@ -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 }, diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index e71977960..9f2689fa9 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -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)); diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 04c436ba7..583ef5184 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -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) diff --git a/library/include/BitArray.h b/library/include/BitArray.h index fd9bd98fc..ff68ea1d1 100644 --- a/library/include/BitArray.h +++ b/library/include/BitArray.h @@ -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; } diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 0f5fd9e7c..21dc68d1a 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -390,7 +390,7 @@ namespace df } virtual bool resize(void *ptr, int size) { - ((container*)ptr)->resize(size); + ((container*)ptr)->resize(size*8); return true; } diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index c9482f82c..7ba6b67aa 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -159,7 +159,7 @@ namespace DFHack ~VMethodInterposeLinkBase(); bool is_applied() { return applied; } - bool apply(); + bool apply(bool enable = true); void remove(); }; diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 58f222419..97e8bd422 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -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); diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index e6e9682eb..984cf16cf 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -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, diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index d1aed8979..9e78edd3e 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -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: diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 0f28860bf..1ea4bf687 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -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; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4107680b0..305f1296d 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -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; diff --git a/library/xml b/library/xml index 328a8dbdc..df8178a98 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f +Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..f126ae53b 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -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() diff --git a/plugins/devel/building_steam_engine.txt b/plugins/devel/building_steam_engine.txt new file mode 100644 index 000000000..48657b0c1 --- /dev/null +++ b/plugins/devel/building_steam_engine.txt @@ -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] diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/devel/item_trapcomp_steam_engine.txt new file mode 100644 index 000000000..bae6f5b22 --- /dev/null +++ b/plugins/devel/item_trapcomp_steam_engine.txt @@ -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] diff --git a/plugins/devel/reaction_steam_engine.txt b/plugins/devel/reaction_steam_engine.txt new file mode 100644 index 000000000..175ffdd50 --- /dev/null +++ b/plugins/devel/reaction_steam_engine.txt @@ -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] + diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp new file mode 100644 index 000000000..5c344f788 --- /dev/null +++ b/plugins/devel/steam-engine.cpp @@ -0,0 +1,929 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#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 gear_tiles; + df::coord2d hearth_tile; + df::coord2d water_tile; + df::coord2d magma_tile; +}; + +std::vector 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(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(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 *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 &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; +} diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 124dc768b..1a90d2eea 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -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 *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 *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 *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 &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; } diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 591125c5e..fbea30231 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -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 @@ -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 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 ¶meters) +{ + 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 ¶meters) { CoreSuspender suspend; @@ -302,19 +409,26 @@ static command_result tweak(color_ostream &out, vector ¶meters) } 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; diff --git a/scripts/devel/lsmem.lua b/scripts/devel/lsmem.lua new file mode 100644 index 000000000..75586324d --- /dev/null +++ b/scripts/devel/lsmem.lua @@ -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 diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 6b4b4042b..4468e1dcb 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -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