From 41ad42d0fdeae7d387186e66d9eb4117a3cf9e7d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 25 Aug 2012 10:37:03 +0400 Subject: [PATCH 01/61] Expose the liquids plugin engine to lua, and make a wrapper gui script. --- dfhack.init-example | 3 + library/include/modules/Gui.h | 3 + library/lua/gui.lua | 5 +- library/lua/gui/dwarfmode.lua | 52 +++++- library/modules/Gui.cpp | 17 ++ plugins/liquids.cpp | 328 +++++++++++++++++++++------------- plugins/lua/liquids.lua | 11 ++ scripts/gui/liquids.lua | 201 +++++++++++++++++++++ 8 files changed, 491 insertions(+), 129 deletions(-) create mode 100644 plugins/lua/liquids.lua create mode 100644 scripts/gui/liquids.lua diff --git a/dfhack.init-example b/dfhack.init-example index c9408e375..552b2b3a1 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -48,3 +48,6 @@ keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms # browse rooms of same owner keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work + +# interface for the liquids plugin +keybinding add Alt-L@dwarfmode/LookAround gui/liquids diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 273d84cee..58f222419 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -97,6 +97,9 @@ namespace DFHack /* * Cursor and window coords */ + DFHACK_EXPORT df::coord getViewportPos(); + DFHACK_EXPORT df::coord getCursorPos(); + 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/lua/gui.lua b/library/lua/gui.lua index ac032166f..9e189ea13 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -18,7 +18,7 @@ function simulateInput(screen,...) error('Invalid keycode: '..arg) end end - if type(arg) == 'number' then + if type(kv) == 'number' then keys[#keys+1] = kv end end @@ -277,6 +277,9 @@ end function Screen:onDismiss() end +function Screen:onDestroy() +end + function Screen:onResize(w,h) self:updateLayout() end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index c1a8bcb95..1f7ae1b03 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -6,6 +6,9 @@ local gui = require('gui') local utils = require('utils') local dscreen = dfhack.screen + +local g_cursor = df.global.cursor +local g_sel_rect = df.global.selection_rect local world_map = df.global.world.map AREA_MAP_WIDTH = 23 @@ -43,8 +46,8 @@ function getPanelLayout() end function getCursorPos() - if df.global.cursor.x ~= -30000 then - return copyall(df.global.cursor) + if g_cursor ~= -30000 then + return copyall(g_cursor) end end @@ -56,6 +59,51 @@ function clearCursorPos() df.global.cursor = xyz2pos(nil) end +function getSelection() + local p1, p2 + if g_sel_rect.start_x ~= -30000 then + p1 = xyz2pos(g_sel_rect.start_x, g_sel_rect.start_y, g_sel_rect.start_z) + end + if g_sel_rect.end_x ~= -30000 then + p2 = xyz2pos(g_sel_rect.end_x, g_sel_rect.end_y, g_sel_rect.end_z) + end + return p1, p2 +end + +function setSelectionStart(pos) + g_sel_rect.start_x = pos.x + g_sel_rect.start_y = pos.y + g_sel_rect.start_z = pos.z +end + +function setSelectionEnd(pos) + g_sel_rect.end_x = pos.x + g_sel_rect.end_y = pos.y + g_sel_rect.end_z = pos.z +end + +function clearSelection() + g_sel_rect.start_x = -30000 + g_sel_rect.start_y = -30000 + g_sel_rect.start_z = -30000 + g_sel_rect.end_x = -30000 + g_sel_rect.end_y = -30000 + g_sel_rect.end_z = -30000 +end + +function getSelectionRange(p1, p2) + local r1 = xyz2pos( + math.min(p1.x, p2.x), math.min(p1.y, p2.y), math.min(p1.z, p2.z) + ) + local r2 = xyz2pos( + math.max(p1.x, p2.x), math.max(p1.y, p2.y), math.max(p1.z, p2.z) + ) + local sz = xyz2pos( + r2.x - r1.x + 1, r2.y - r1.y + 1, r2.z - r1.z + 1 + ) + return r1, sz, r2 +end + Viewport = defclass(Viewport) function Viewport.make(map,x,y,z) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 91a17e998..0f28860bf 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -998,6 +998,23 @@ df::viewscreen *Gui::getCurViewscreen(bool skip_dismissed) return ws; } +df::coord Gui::getViewportPos() +{ + if (!df::global::window_x || !df::global::window_y || !df::global::window_z) + return df::coord(0,0,0); + + return df::coord(*df::global::window_x, *df::global::window_y, *df::global::window_z); +} + +df::coord Gui::getCursorPos() +{ + using df::global::cursor; + if (!cursor) + return df::coord(); + + return df::coord(cursor->x, cursor->y, cursor->z); +} + bool Gui::getViewCoords (int32_t &x, int32_t &y, int32_t &z) { x = *df::global::window_x; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index b036e4fa8..b078b48fd 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -27,6 +27,7 @@ #include #include #include +#include using std::vector; using std::string; using std::endl; @@ -41,6 +42,7 @@ using std::set; #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" +#include "LuaTools.h" #include "Brushes.h" using namespace MapExtras; using namespace DFHack; @@ -50,7 +52,6 @@ CommandHistory liquids_hist; command_result df_liquids (color_ostream &out, vector & parameters); command_result df_liquids_here (color_ostream &out, vector & parameters); -command_result df_liquids_execute (color_ostream &out); DFHACK_PLUGIN("liquids"); @@ -74,13 +75,49 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// static stuff to be remembered between sessions -static string brushname = "point"; -static string mode="magma"; -static string flowmode="f+"; -static string _setmode ="s."; -static unsigned int amount = 7; -static int width = 1, height = 1, z_levels = 1; +enum BrushType { + B_POINT, B_RANGE, B_BLOCK, B_COLUMN, B_FLOOD +}; + +static const char *brush_name[] = { + "point", "range", "block", "column", "flood", NULL +}; + +enum PaintMode { + P_WATER, P_MAGMA, P_OBSIDIAN, P_OBSIDIAN_FLOOR, + P_RIVER_SOURCE, P_FLOW_BITS, P_WCLEAN +}; + +static const char *paint_mode_name[] = { + "water", "magma", "obsidian", "obsidian_floor", + "riversource", "flowbits", "wclean", NULL +}; + +enum ModifyMode { + M_INC, M_KEEP, M_DEC +}; + +static const char *modify_mode_name[] = { + "+", ".", "-", NULL +}; + +struct OperationMode { + BrushType brush; + PaintMode paint; + ModifyMode flowmode; + ModifyMode setmode; + unsigned int amount; + df::coord size; + + OperationMode() : + brush(B_POINT), paint(P_MAGMA), + flowmode(M_INC), setmode(M_KEEP), amount(7), + size(1,1,1) + {} +} cur_mode; + +command_result df_liquids_execute(color_ostream &out); +command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos); command_result df_liquids (color_ostream &out_, vector & parameters) { @@ -117,10 +154,11 @@ command_result df_liquids (color_ostream &out_, vector & parameters) string input = ""; std::stringstream str; - str <<"[" << mode << ":" << brushname; - if (brushname == "range") - str << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - str << ":" << amount << ":" << flowmode << ":" << _setmode << "]#"; + str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":s" << modify_mode_name[cur_mode.setmode] << "]#"; if(out.lineedit(str.str(),input,liquids_hist) == -1) return CR_FAILURE; liquids_hist.add(input); @@ -168,38 +206,39 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "m") { - mode = "magma"; + cur_mode.paint = P_MAGMA; } else if(command == "o") { - mode = "obsidian"; + cur_mode.paint = P_OBSIDIAN; } else if(command == "of") { - mode = "obsidian_floor"; + cur_mode.paint = P_OBSIDIAN_FLOOR; } else if(command == "w") { - mode = "water"; + cur_mode.paint = P_WATER; } else if(command == "f") { - mode = "flowbits"; + cur_mode.paint = P_FLOW_BITS; } else if(command == "rs") { - mode = "riversource"; + cur_mode.paint = P_RIVER_SOURCE; } else if(command == "wclean") { - mode = "wclean"; + cur_mode.paint = P_WCLEAN; } else if(command == "point" || command == "p") { - brushname = "point"; + cur_mode.brush = B_POINT; } else if(command == "range" || command == "r") { + int width, height, z_levels; command_result res = parseRectangle(out, commands, 1, commands.size(), width, height, z_levels); if (res != CR_OK) @@ -209,24 +248,26 @@ command_result df_liquids (color_ostream &out_, vector & parameters) if (width == 1 && height == 1 && z_levels == 1) { - brushname = "point"; + cur_mode.brush = B_POINT; + cur_mode.size = df::coord(1, 1, 1); } else { - brushname = "range"; + cur_mode.brush = B_RANGE; + cur_mode.size = df::coord(width, height, z_levels); } } else if(command == "block") { - brushname = "block"; + cur_mode.brush = B_BLOCK; } else if(command == "column") { - brushname = "column"; + cur_mode.brush = B_COLUMN; } else if(command == "flood") { - brushname = "flood"; + cur_mode.brush = B_FLOOD; } else if(command == "q") { @@ -234,45 +275,45 @@ command_result df_liquids (color_ostream &out_, vector & parameters) } else if(command == "f+") { - flowmode = "f+"; + cur_mode.flowmode = M_INC; } else if(command == "f-") { - flowmode = "f-"; + cur_mode.flowmode = M_DEC; } else if(command == "f.") { - flowmode = "f."; + cur_mode.flowmode = M_KEEP; } else if(command == "s+") { - _setmode = "s+"; + cur_mode.setmode = M_INC; } else if(command == "s-") { - _setmode = "s-"; + cur_mode.setmode = M_DEC; } else if(command == "s.") { - _setmode = "s."; + cur_mode.setmode = M_KEEP; } // blah blah, bad code, bite me. else if(command == "0") - amount = 0; + cur_mode.amount = 0; else if(command == "1") - amount = 1; + cur_mode.amount = 1; else if(command == "2") - amount = 2; + cur_mode.amount = 2; else if(command == "3") - amount = 3; + cur_mode.amount = 3; else if(command == "4") - amount = 4; + cur_mode.amount = 4; else if(command == "5") - amount = 5; + cur_mode.amount = 5; else if(command == "6") - amount = 6; + cur_mode.amount = 6; else if(command == "7") - amount = 7; + cur_mode.amount = 7; else if(command.empty()) { df_liquids_execute(out); @@ -298,78 +339,78 @@ command_result df_liquids_here (color_ostream &out, vector & parameters } out.print("Run liquids-here with these parameters: "); - out << "[" << mode << ":" << brushname; - if (brushname == "range") - out << "(w" << width << ":h" << height << ":z" << z_levels << ")"; - out << ":" << amount << ":" << flowmode << ":" << _setmode << "]\n"; + out << "[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + out << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + out << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":" << modify_mode_name[cur_mode.setmode] << "]\n"; return df_liquids_execute(out); } command_result df_liquids_execute(color_ostream &out) { - // create brush type depending on old parameters - Brush * brush; + CoreSuspender suspend; - if (brushname == "point") - { - brush = new RectangleBrush(1,1,1,0,0,0); - //width = 1; - //height = 1; - //z_levels = 1; - } - else if (brushname == "range") + auto cursor = Gui::getCursorPos(); + if (!cursor.isValid()) { - brush = new RectangleBrush(width,height,z_levels,0,0,0); + out.printerr("Can't get cursor coords! Make sure you have a cursor active in DF.\n"); + return CR_WRONG_USAGE; } - else if(brushname == "block") + + auto rv = df_liquids_execute(out, cur_mode, cursor); + if (rv == CR_OK) + out << "OK" << endl; + return rv; +} + +command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, df::coord cursor) +{ + // create brush type depending on old parameters + Brush *brush; + + switch (cur_mode.brush) { + case B_POINT: + brush = new RectangleBrush(1,1,1,0,0,0); + break; + case B_RANGE: + brush = new RectangleBrush(cur_mode.size.x,cur_mode.size.y,cur_mode.size.z,0,0,0); + break; + case B_BLOCK: brush = new BlockBrush(); - } - else if(brushname == "column") - { + break; + case B_COLUMN: brush = new ColumnBrush(); - } - else if(brushname == "flood") - { + break; + case B_FLOOD: brush = new FloodBrush(&Core::getInstance()); - } - else - { + break; + default: // this should never happen! out << "Old brushtype is invalid! Resetting to point brush.\n"; - brushname = "point"; - width = 1; - height = 1; - z_levels = 1; - brush = new RectangleBrush(width,height,z_levels,0,0,0); + cur_mode.brush = B_POINT; + brush = new RectangleBrush(1,1,1,0,0,0); } - CoreSuspender suspend; + std::auto_ptr brush_ref(brush); - do + if (!Maps::IsValid()) { - if (!Maps::IsValid()) - { - out << "Can't see any DF map loaded." << endl; - break;; - } - int32_t x,y,z; - if(!Gui::getCursorCoords(x,y,z)) - { - out << "Can't get cursor coords! Make sure you have a cursor active in DF." << endl; - break; - } - out << "cursor coords: " << x << "/" << y << "/" << z << endl; - MapCache mcache; - DFHack::DFCoord cursor(x,y,z); - coord_vec all_tiles = brush->points(mcache,cursor); - out << "working..." << endl; + out << "Can't see any DF map loaded." << endl; + return CR_FAILURE; + } + + MapCache mcache; + coord_vec all_tiles = brush->points(mcache,cursor); - // Force the game to recompute its walkability cache - df::global::world->reindex_pathfinding = true; + // Force the game to recompute its walkability cache + df::global::world->reindex_pathfinding = true; - if(mode == "obsidian") + switch (cur_mode.paint) + { + case P_OBSIDIAN: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -383,8 +424,9 @@ command_result df_liquids_execute(color_ostream &out) mcache.setDesignationAt(*iter, des); iter ++; } + break; } - if(mode == "obsidian_floor") + case P_OBSIDIAN_FLOOR: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -392,8 +434,9 @@ command_result df_liquids_execute(color_ostream &out) mcache.setTiletypeAt(*iter, findRandomVariant(tiletype::LavaFloor1)); iter ++; } + break; } - else if(mode == "riversource") + case P_RIVER_SOURCE: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -413,8 +456,9 @@ command_result df_liquids_execute(color_ostream &out) iter++; } + break; } - else if(mode=="wclean") + case P_WCLEAN: { coord_vec::iterator iter = all_tiles.begin(); while (iter != all_tiles.end()) @@ -426,8 +470,11 @@ command_result df_liquids_execute(color_ostream &out) mcache.setDesignationAt(current,des); iter++; } + break; } - else if(mode== "magma" || mode== "water" || mode == "flowbits") + case P_MAGMA: + case P_WATER: + case P_FLOW_BITS: { set seen_blocks; coord_vec::iterator iter = all_tiles.begin(); @@ -450,30 +497,29 @@ command_result df_liquids_execute(color_ostream &out) iter++; continue; } - if(mode != "flowbits") + if(cur_mode.paint != P_FLOW_BITS) { unsigned old_amount = des.bits.flow_size; unsigned new_amount = old_amount; df::tile_liquid old_liquid = des.bits.liquid_type; df::tile_liquid new_liquid = old_liquid; // Compute new liquid type and amount - if(_setmode == "s.") - { - new_amount = amount; - } - else if(_setmode == "s+") - { - if(old_amount < amount) - new_amount = amount; - } - else if(_setmode == "s-") + switch (cur_mode.setmode) { - if (old_amount > amount) - new_amount = amount; + case M_KEEP: + new_amount = cur_mode.amount; + break; + case M_INC: + if(old_amount < cur_mode.amount) + new_amount = cur_mode.amount; + break; + case M_DEC: + if (old_amount > cur_mode.amount) + new_amount = cur_mode.amount; } - if (mode == "magma") + if (cur_mode.paint == P_MAGMA) new_liquid = tile_liquid::Magma; - else if (mode == "water") + else if (cur_mode.paint == P_WATER) new_liquid = tile_liquid::Water; // Store new amount and type des.bits.flow_size = new_amount; @@ -508,34 +554,64 @@ command_result df_liquids_execute(color_ostream &out) set ::iterator biter = seen_blocks.begin(); while (biter != seen_blocks.end()) { - if(flowmode == "f+") + switch (cur_mode.flowmode) { + case M_INC: (*biter)->enableBlockUpdates(true); - } - else if(flowmode == "f-") - { + break; + case M_DEC: if (auto block = (*biter)->getRaw()) { block->flags.bits.update_liquid = false; block->flags.bits.update_liquid_twice = false; } - } - else - { - auto bflags = (*biter)->BlockFlags(); - out << "flow bit 1 = " << bflags.bits.update_liquid << endl; - out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; + break; + case M_KEEP: + { + auto bflags = (*biter)->BlockFlags(); + out << "flow bit 1 = " << bflags.bits.update_liquid << endl; + out << "flow bit 2 = " << bflags.bits.update_liquid_twice << endl; + } } biter ++; } + break; } - if(mcache.WriteAll()) - out << "OK" << endl; - else - out << "Something failed horribly! RUN!" << endl; - } while (0); + } + + if(!mcache.WriteAll()) + { + out << "Something failed horribly! RUN!" << endl; + return CR_FAILURE; + } - // cleanup - delete brush; return CR_OK; } + +static int paint(lua_State *L) +{ + df::coord pos; + OperationMode mode; + + lua_settop(L, 7); + Lua::CheckDFAssign(L, &pos, 1); + if (!pos.isValid()) + luaL_argerror(L, 1, "invalid cursor position"); + mode.brush = (BrushType)luaL_checkoption(L, 2, NULL, brush_name); + mode.paint = (PaintMode)luaL_checkoption(L, 3, NULL, paint_mode_name); + mode.amount = luaL_optint(L, 4, 7); + if (mode.amount < 0 || mode.amount > 7) + luaL_argerror(L, 4, "invalid liquid amount"); + if (!lua_isnil(L, 5)) + Lua::CheckDFAssign(L, &mode.size, 5); + mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name); + mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name); + + lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos)); + return 1; +} + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(paint), + DFHACK_LUA_END +}; diff --git a/plugins/lua/liquids.lua b/plugins/lua/liquids.lua new file mode 100644 index 000000000..22ce4da35 --- /dev/null +++ b/plugins/lua/liquids.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.liquids') + +--[[ + + Native functions: + + * paint(pos,brush,paint,amount,size,setmode,flowmode) + +--]] + +return _ENV \ No newline at end of file diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua new file mode 100644 index 000000000..27df49e9a --- /dev/null +++ b/scripts/gui/liquids.lua @@ -0,0 +1,201 @@ +-- Interface front-end for liquids plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' + +local liquids = require('plugins.liquids') + +local sel_rect = df.global.selection_rect + +local brushes = { + { tag = 'range', caption = 'Rectangle', range = true }, + { tag = 'block', caption = '16x16 block' }, + { tag = 'column', caption = 'Column' }, + { tag = 'flood', caption = 'Flood' }, +} + +local paints = { + { tag = 'water', caption = 'Water', liquid = true, key = 'w' }, + { tag = 'magma', caption = 'Magma', liquid = true, key = 'l' }, + { tag = 'obsidian', caption = 'Obsidian Wall' }, + { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, + { tag = 'riversource', caption = 'River Source' }, + { tag = 'flowbits', caption = 'Flow Updates' }, + { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, +} + +local flowbits = { + { tag = '+', caption = 'Enable Updates' }, + { tag = '-', caption = 'Disable Updates' }, + { tag = '.', caption = 'Keep Updates' }, +} + +local setmode = { + { tag = '.', caption = 'Set Exactly' }, + { tag = '+', caption = 'Only Increase' }, + { tag = '-', caption = 'Only Decrease' }, +} + +Toggle = defclass(Toggle) + +function Toggle:init(items) + self:init_fields{ + items = items, + selected = 1 + } + return self +end + +function Toggle:get() + return self.items[self.selected] +end + +function Toggle:render(dc) + local item = self:get() + if item then + dc:string(item.caption) + if item.key then + dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")") + end + else + dc:string('NONE', COLOR_RED) + end +end + +function Toggle:step(delta) + if #self.items > 1 then + delta = delta or 1 + self.selected = 1 + (self.selected + delta - 1) % #self.items + end +end + +LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay) + +LiquidsUI.focus_path = 'liquids' + +function LiquidsUI:init() + self:init_fields{ + brush = mkinstance(Toggle):init(brushes), + paint = mkinstance(Toggle):init(paints), + flow = mkinstance(Toggle):init(flowbits), + set = mkinstance(Toggle):init(setmode), + amount = 7, + } + guidm.MenuOverlay.init(self) + return self +end + +function LiquidsUI:onDestroy() + guidm.clearSelection() +end + +function LiquidsUI:onRenderBody(dc) + dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) + + local cursor = guidm.getCursorPos() + local block = dfhack.maps.getTileBlock(cursor) + local tile = block.tiletype[cursor.x%16][cursor.y%16] + local dsgn = block.designation[cursor.x%16][cursor.y%16] + + dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN):newline(2) + + if dsgn.flow_size > 0 then + if dsgn.liquid_type == df.tile_liquid.Magma then + dc:pen(COLOR_RED):string("Magma") + else + dc:pen(COLOR_BLUE) + if dsgn.water_stagnant then dc:string("Stagnant ") end + if dsgn.water_salt then dc:string("Salty ") end + dc:string("Water") + end + dc:string(" ["..dsgn.flow_size.."/7]") + else + dc:string('No Liquid', COLOR_DARKGREY) + end + + dc:newline():pen(COLOR_GREY) + + dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ") + self.brush:render(dc) + dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") + self.paint:render(dc) + + local liquid = self.paint:get().liquid + + dc:newline() + if liquid then + dc:newline(1):string("Amount: "..self.amount) + dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") + dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") + self.set:render(dc) + else + dc:advance(0,2) + end + + dc:newline():newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + self.flow:render(dc) + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") +end + +function LiquidsUI:onInput(keys) + local liquid = self.paint:get().liquid + if keys.CUSTOM_B then + self.brush:step() + elseif keys.CUSTOM_P then + self.paint:step() + elseif liquid and keys.SECONDSCROLL_UP then + self.amount = math.max(0, self.amount-1) + elseif liquid and keys.SECONDSCROLL_DOWN then + self.amount = math.min(7, self.amount+1) + elseif liquid and keys.CUSTOM_S then + self.set:step() + elseif keys.CUSTOM_F then + self.flow:step() + elseif keys.LEAVESCREEN then + if guidm.getSelection() then + guidm.clearSelection() + return + end + self:dismiss() + self:sendInputToParent('CURSOR_DOWN_Z') + self:sendInputToParent('CURSOR_UP_Z') + elseif keys.SELECT then + local cursor = guidm.getCursorPos() + local sp = guidm.getSelection() + local size = nil + if self.brush:get().range then + if not sp then + guidm.setSelectionStart(cursor) + return + else + guidm.clearSelection() + cursor, size = guidm.getSelectionRange(cursor, sp) + end + else + guidm.clearSelection() + end + liquids.paint( + cursor, + self.brush:get().tag, self.paint:get().tag, + self.amount, size, + self.set:get().tag, self.flow:get().tag + ) + elseif self:propagateMoveKeys(keys) then + return + elseif keys.D_LOOK_ARENA_WATER then + self.paint.selected = 1 + elseif keys.D_LOOK_ARENA_MAGMA then + self.paint.selected = 2 + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then + qerror("This script requires the main dwarfmode view in 'k' mode") +end + +local list = mkinstance(LiquidsUI):init() +list:show() From dcdff40c853dcef2d351635d43b4592affe95317 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 25 Aug 2012 10:57:50 -0500 Subject: [PATCH 02/61] Add sorting, command help; replace filtering with just using the current page of the UnitList viewscreen (and don't close it) --- plugins/manipulator.cpp | 295 ++++++++++++++++++++++++++-------------- 1 file changed, 191 insertions(+), 104 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 434ba08c8..9ebf093f1 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include "df/world.h" @@ -41,36 +42,36 @@ DFHACK_PLUGIN("manipulator"); struct SkillLevel { - const char *name; - int points; - char abbrev; + const char *name; + int points; + char abbrev; }; -#define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel)) +#define NUM_SKILL_LEVELS (sizeof(skill_levels) / sizeof(SkillLevel)) // The various skill rankings. Zero skill is hardcoded to "Not" and '-'. const SkillLevel skill_levels[] = { - {"Dabbling", 500, '0'}, - {"Novice", 600, '1'}, - {"Adequate", 700, '2'}, - {"Competent", 800, '3'}, - {"Skilled", 900, '4'}, - {"Proficient", 1000, '5'}, - {"Talented", 1100, '6'}, - {"Adept", 1200, '7'}, - {"Expert", 1300, '8'}, - {"Professional",1400, '9'}, - {"Accomplished",1500, 'A'}, - {"Great", 1600, 'B'}, - {"Master", 1700, 'C'}, - {"High Master", 1800, 'D'}, - {"Grand Master",1900, 'E'}, - {"Legendary", 2000, 'U'}, - {"Legendary+1", 2100, 'V'}, - {"Legendary+2", 2200, 'W'}, - {"Legendary+3", 2300, 'X'}, - {"Legendary+4", 2400, 'Y'}, - {"Legendary+5", 0, 'Z'} + {"Dabbling", 500, '0'}, + {"Novice", 600, '1'}, + {"Adequate", 700, '2'}, + {"Competent", 800, '3'}, + {"Skilled", 900, '4'}, + {"Proficient", 1000, '5'}, + {"Talented", 1100, '6'}, + {"Adept", 1200, '7'}, + {"Expert", 1300, '8'}, + {"Professional",1400, '9'}, + {"Accomplished",1500, 'A'}, + {"Great", 1600, 'B'}, + {"Master", 1700, 'C'}, + {"High Master", 1800, 'D'}, + {"Grand Master",1900, 'E'}, + {"Legendary", 2000, 'U'}, + {"Legendary+1", 2100, 'V'}, + {"Legendary+2", 2200, 'W'}, + {"Legendary+3", 2300, 'X'}, + {"Legendary+4", 2400, 'Y'}, + {"Legendary+5", 0, 'Z'} }; struct SkillColumn @@ -82,7 +83,7 @@ struct SkillColumn bool special; // specified labor is mutually exclusive with all other special labors }; -#define NUM_COLUMNS (sizeof(columns) / sizeof(SkillColumn)) +#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 const SkillColumn columns[] = { @@ -247,17 +248,69 @@ struct UnitInfo int8_t color; }; -#define FILTER_NONWORKERS 0x0001 -#define FILTER_NONDWARVES 0x0002 -#define FILTER_NONCIV 0x0004 -#define FILTER_ANIMALS 0x0008 -#define FILTER_LIVING 0x0010 -#define FILTER_DEAD 0x0020 +enum altsort_mode { + ALTSORT_NAME, + ALTSORT_PROFESSION, + ALTSORT_MAX +}; + +bool descending; +df::job_skill sort_skill; +df::unit_labor sort_labor; + +bool sortByName (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->name > d2->name); + else + return (d1->name < d2->name); +} + +bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->profession > d2->profession); + else + return (d1->profession < d2->profession); +} + +bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) +{ + if (sort_skill != job_skill::NONE) + { + df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s2 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + int l1 = s1 ? s1->rating : 0; + int l2 = s2 ? s2->rating : 0; + int e1 = s1 ? s1->experience : 0; + int e2 = s2 ? s2->experience : 0; + if (descending) + { + if (l1 != l2) + return l1 > l2; + if (e1 != e2) + return e1 > e2; + } + else + { + if (l1 != l2) + return l1 < l2; + if (e1 != e2) + return e1 < e2; + } + } + if (sort_labor != unit_labor::NONE) + { + if (descending) + return d1->unit->status.labors[sort_labor] > d2->unit->status.labors[sort_labor]; + else + return d1->unit->status.labors[sort_labor] < d2->unit->status.labors[sort_labor]; + } + return sortByName(d1, d2); +} class viewscreen_unitlaborsst : public dfhack_viewscreen { public: - static viewscreen_unitlaborsst *create (char pushtype, df::viewscreen *scr = NULL); - void feed(set *events); void render(); @@ -267,86 +320,41 @@ public: std::string getFocusString() { return "unitlabors"; } - viewscreen_unitlaborsst(); + viewscreen_unitlaborsst(vector &src); ~viewscreen_unitlaborsst() { }; protected: vector units; - int filter; + altsort_mode altsort; int first_row, sel_row; int first_column, sel_column; int height, name_width, prof_width, labors_width; -// bool descending; -// int sort_skill; -// int sort_labor; - - void readUnits (); void calcSize (); }; -viewscreen_unitlaborsst::viewscreen_unitlaborsst() -{ - filter = FILTER_LIVING; - first_row = sel_row = 0; - first_column = sel_column = 0; - calcSize(); - readUnits(); -} - -void viewscreen_unitlaborsst::readUnits () +viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src) { - for (size_t i = 0; i < units.size(); i++) - delete units[i]; - units.clear(); - - UnitInfo *cur = new UnitInfo; - for (size_t i = 0; i < world->units.active.size(); i++) + for (size_t i = 0; i < src.size(); i++) { - df::unit *unit = world->units.active[i]; + UnitInfo *cur = new UnitInfo; + df::unit *unit = src[i]; cur->unit = unit; cur->allowEdit = true; if (unit->race != ui->race_id) - { cur->allowEdit = false; - if (!(filter & FILTER_NONDWARVES)) - continue; - } if (unit->civ_id != ui->civ_id) - { cur->allowEdit = false; - if (!(filter & FILTER_NONCIV)) - continue; - } if (unit->flags1.bits.dead) - { cur->allowEdit = false; - if (!(filter & FILTER_DEAD)) - continue; - } - else - { - if (!(filter & FILTER_LIVING)) - continue; - } if (!ENUM_ATTR(profession, can_assign_labor, unit->profession)) - { cur->allowEdit = false; - if (!(filter & FILTER_NONWORKERS)) - continue; - } - - if (!unit->name.first_name.length()) - { - if (!(filter & FILTER_ANIMALS)) - continue; - } cur->name = Translation::TranslateName(&unit->name, false); cur->transname = Translation::TranslateName(&unit->name, true); @@ -354,9 +362,13 @@ void viewscreen_unitlaborsst::readUnits () cur->color = Units::getProfessionColor(unit); units.push_back(cur); - cur = new UnitInfo; } - delete cur; + std::sort(units.begin(), units.end(), sortByName); + + altsort = ALTSORT_NAME; + first_row = sel_row = 0; + first_column = sel_column = 0; + calcSize(); } void viewscreen_unitlaborsst::calcSize() @@ -421,8 +433,6 @@ void viewscreen_unitlaborsst::feed(set *events) return; } - // TODO - allow modifying filters - if (!units.size()) return; @@ -501,9 +511,46 @@ void viewscreen_unitlaborsst::feed(set *events) unit->status.labors[col.labor] = !unit->status.labors[col.labor]; } - // TODO: add sorting + if (events->count(interface_key::SECONDSCROLL_UP) || events->count(interface_key::SECONDSCROLL_DOWN)) + { + descending = events->count(interface_key::SECONDSCROLL_UP); + sort_skill = columns[sel_column].skill; + sort_labor = columns[sel_column].labor; + std::sort(units.begin(), units.end(), sortBySkill); + } + + if (events->count(interface_key::SECONDSCROLL_PAGEUP) || events->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + descending = events->count(interface_key::SECONDSCROLL_PAGEUP); + switch (altsort) + { + case ALTSORT_NAME: + std::sort(units.begin(), units.end(), sortByName); + break; + case ALTSORT_PROFESSION: + std::sort(units.begin(), units.end(), sortByProfession); + break; + } + } + if (events->count(interface_key::CHANGETAB)) + { + switch (altsort) + { + case ALTSORT_NAME: + altsort = ALTSORT_PROFESSION; + break; + case ALTSORT_PROFESSION: + altsort = ALTSORT_NAME; + break; + } + } } +void OutputString(int8_t color, int &x, int y, const std::string &text) +{ + Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); + x += text.length(); +} void viewscreen_unitlaborsst::render() { if (Screen::isDismissed(this)) @@ -528,6 +575,7 @@ void viewscreen_unitlaborsst::render() fg = 0; bg = 7; } + Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); } @@ -537,6 +585,7 @@ void viewscreen_unitlaborsst::render() int row_offset = row + first_row; if (row_offset >= units.size()) break; + UnitInfo *cur = units[row_offset]; df::unit *unit = cur->unit; int8_t fg = 15, bg = 0; @@ -554,7 +603,8 @@ void viewscreen_unitlaborsst::render() profession.resize(prof_width); fg = cur->color; bg = 0; - Screen::paintString(Screen::Pen(' ', fg, bg), 1 + prof_width + 1, 3 + row, profession); + + Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 3 + row, profession); // Print unit's skills and labor assignments for (int col = 0; col < labors_width; col++) @@ -586,15 +636,21 @@ void viewscreen_unitlaborsst::render() if (cur != NULL) { df::unit *unit = cur->unit; - string str = cur->transname; - if (str.length()) - str += ", "; - str += cur->profession; - str += ":"; + int x = 1; + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname); + x += cur->transname.length(); - Screen::paintString(Screen::Pen(' ', 15, 0), 1, 3 + height + 2, str); - int y = 1 + str.length() + 1; + if (cur->transname.length()) + { + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", "); + x += 2; + } + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession); + x += cur->profession.length(); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": "); + x += 2; + string str; if (columns[sel_column].skill == job_skill::NONE) { str = ENUM_ATTR_STR(unit_labor, caption, columns[sel_column].labor); @@ -602,7 +658,6 @@ void viewscreen_unitlaborsst::render() str += " Enabled"; else str += " Not Enabled"; - Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str); } else { @@ -618,11 +673,41 @@ void viewscreen_unitlaborsst::render() } else str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); - Screen::paintString(Screen::Pen(' ', 9, 0), y, 3 + height + 2, str); } + Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str); } - // TODO - print command help info + int x = 1; + OutputString( 2, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(15, x, gps->dimy - 3, ": Toggle labor"); + x += 2; + + OutputString( 2, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key + OutputString(15, x, gps->dimy - 3, ": Done"); + + x = 1; + OutputString( 2, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key + OutputString( 2, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key + OutputString(15, x, gps->dimy - 2, ": Sort by Skill"); + x += 2; + + OutputString( 2, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key + OutputString( 2, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key + OutputString(15, x, gps->dimy - 2, ": Sort by ("); + OutputString( 2, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(15, x, gps->dimy - 2, ") "); + switch (altsort) + { + case ALTSORT_NAME: + OutputString(15, x, gps->dimy - 2, "Name"); + break; + case ALTSORT_PROFESSION: + OutputString(15, x, gps->dimy - 2, "Profession"); + break; + default: + OutputString(15, x, gps->dimy - 2, "Unknown"); + break; + } } struct unitlist_hook : df::viewscreen_unitlistst @@ -633,9 +718,11 @@ struct unitlist_hook : df::viewscreen_unitlistst { if (input->count(interface_key::UNITVIEW_PRF_PROF)) { - Screen::dismiss(this); - Screen::show(new viewscreen_unitlaborsst()); - return; + if (units[page].size()) + { + Screen::show(new viewscreen_unitlaborsst(units[page])); + return; + } } INTERPOSE_NEXT(feed)(input); } From bd9800055dd0c6d346d9741f537dd87993920f2e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 25 Aug 2012 20:01:03 +0400 Subject: [PATCH 03/61] Link to the lua library in the liquids plugin. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 023cd6e83..a2e520178 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -81,7 +81,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(weather weather.cpp) DFHACK_PLUGIN(colonies colonies.cpp) DFHACK_PLUGIN(mode mode.cpp) - DFHACK_PLUGIN(liquids liquids.cpp Brushes.h) + DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(tiletypes tiletypes.cpp Brushes.h) DFHACK_PLUGIN(tubefill tubefill.cpp) DFHACK_PLUGIN(autodump autodump.cpp) From f6e4969e1988422f854989fdde85a8b691d64c73 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 25 Aug 2012 11:07:42 -0500 Subject: [PATCH 04/61] Key names are bright green, not dark green --- plugins/manipulator.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 9ebf093f1..3895b0650 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -678,23 +678,23 @@ void viewscreen_unitlaborsst::render() } int x = 1; - OutputString( 2, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key OutputString(15, x, gps->dimy - 3, ": Toggle labor"); x += 2; - OutputString( 2, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key + OutputString(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 3, ": Done"); x = 1; - OutputString( 2, x, gps->dimy - 2, "+"); // SECONDSCROLL_DOWN key - OutputString( 2, x, gps->dimy - 2, "-"); // SECONDSCROLL_UP key + 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"); x += 2; - OutputString( 2, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key - OutputString( 2, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key + OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key + OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key OutputString(15, x, gps->dimy - 2, ": Sort by ("); - OutputString( 2, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key OutputString(15, x, gps->dimy - 2, ") "); switch (altsort) { From 7f1e4b46bc102014533c015f09a20eef38aab13c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 13:23:59 +0400 Subject: [PATCH 05/61] Implement inheritance-aware vmethod interposing. I.e. overwriting the vmethod in all vtables that use it, not only one. --- library/DataDefs.cpp | 4 +- library/VTableInterpose.cpp | 260 +++++++++++++++++++++++++++--- library/include/DataDefs.h | 2 +- library/include/VTableInterpose.h | 12 +- 4 files changed, 248 insertions(+), 30 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index d6604cdb3..4428a2f76 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -218,8 +218,8 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, virtual_identity::~virtual_identity() { // Remove interpose entries, so that they don't try accessing this object later - for (int i = interpose_list.size()-1; i >= 0; i--) - interpose_list[i]->remove(); + while (!interpose_list.empty()) + interpose_list.begin()->second->on_host_delete(this); } /* Vtable name to identity lookup. */ diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 3725ccba7..47110cece 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -154,6 +154,73 @@ bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); } +/* + VMethod interposing data structures. + + In order to properly support adding and removing hooks, + it is necessary to track them. This is what this class + is for. The task is further complicated by propagating + hooks to child classes that use exactly the same original + vmethod implementation. + + Every applied link contains in the saved_chain field a + pointer to the next vmethod body that should be called + by the hook the link represents. This is the actual + control flow structure that needs to be maintained. + + There also are connections between link objects themselves, + which constitute the bookkeeping for doing that. Finally, + every link is associated with a fixed virtual_identity host, + which represents the point in the class hierarchy where + the hook is applied. + + When there are no subclasses (i.e. only one host), the + structures look like this: + + +--------------+ +------------+ + | link1 |-next------->| link2 |-next=NULL + |s_c: original |<-------prev-|s_c: $link1 |<--+ + +--------------+ +------------+ | + | + host->interpose_list[vmethod_idx] ------+ + vtable: $link2 + + The original vtable entry is stored in the saved_chain of the + first link. The interpose_list map points to the last one. + The hooks are called in order: link2 -> link1 -> original. + + When there are subclasses that use the same vmethod, but don't + hook it, the topmost link gets a set of the child_hosts, and + the hosts have the link added to their interpose_list: + + +--------------+ +----------------+ + | link0 @host0 |<--+-interpose_list-| host1 | + | |-child_hosts-+----->| vtable: $link | + +--------------+ | | +----------------+ + | | + | | +----------------+ + +-interpose_list-| host2 | + +----->| vtable: $link | + +----------------+ + + When a child defines its own hook, the child_hosts link is + severed and replaced with a child_next pointer to the new + hook. The hook still points back the chain with prev. + All child links to subclasses of host2 are migrated from + link1 to link2. + + +--------------+-next=NULL +--------------+-next=NULL + | link1 @host1 |-child_next------->| link2 @host2 |-child_*--->subclasses + | |<-------------prev-|s_c: $link1 | + +--------------+<-------+ +--------------+<-------+ + | | + +--------------+ | +--------------+ | + | host1 |-i_list-+ | host2 |-i_list-+ + |vtable: $link1| |vtable: $link2| + +--------------+ +--------------+ + + */ + void VMethodInterposeLinkBase::set_chain(void *chain) { saved_chain = chain; @@ -162,7 +229,7 @@ void VMethodInterposeLinkBase::set_chain(void *chain) VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr) : host(host), vmethod_idx(vmethod_idx), interpose_method(interpose_method), chain_mptr(chain_mptr), - saved_chain(NULL), next(NULL), prev(NULL) + applied(false), saved_chain(NULL), next(NULL), prev(NULL) { if (vmethod_idx < 0 || interpose_method == NULL) { @@ -179,6 +246,75 @@ VMethodInterposeLinkBase::~VMethodInterposeLinkBase() remove(); } +VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_identity *id) +{ + auto item = id->interpose_list[vmethod_idx]; + if (!item) + return NULL; + + if (item->host != id) + return NULL; + while (item->prev && item->prev->host == id) + item = item->prev; + + return item; +} + +void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) +{ + auto &children = cur->getChildren(); + + for (size_t i = 0; i < children.size(); i++) + { + auto child = static_cast(children[i]); + auto base = get_first_interpose(child); + + if (base) + { + assert(base->prev == NULL); + + if (base->saved_chain != vmptr) + continue; + + child_next.insert(base); + } + else + { + void *cptr = child->get_vmethod_ptr(vmethod_idx); + if (cptr != vmptr) + continue; + + child_hosts.insert(child); + find_child_hosts(child, vmptr); + } + } +} + +void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) +{ + if (from == host) + { + // When in own host, fully delete + remove(); + } + else + { + // Otherwise, drop the link to that child: + assert(child_hosts.count(from) != 0 && + from->interpose_list[vmethod_idx] == this); + + // Find and restore the original vmethod ptr + auto last = this; + while (last->prev) last = last->prev; + + from->set_vmethod_ptr(vmethod_idx, last->saved_chain); + + // Unlink the chains + child_hosts.erase(from); + from->interpose_list.erase(vmethod_idx); + } +} + bool VMethodInterposeLinkBase::apply() { if (is_applied()) @@ -188,33 +324,73 @@ bool VMethodInterposeLinkBase::apply() // Retrieve the current vtable entry void *old_ptr = host->get_vmethod_ptr(vmethod_idx); - assert(old_ptr != NULL); + VMethodInterposeLinkBase *old_link = host->interpose_list[vmethod_idx]; - // Check if there are other interpose entries for the same slot - VMethodInterposeLinkBase *old_link = NULL; - - for (int i = host->interpose_list.size()-1; i >= 0; i--) - { - if (host->interpose_list[i]->vmethod_idx != vmethod_idx) - continue; - - old_link = host->interpose_list[i]; - assert(old_link->next == NULL && old_ptr == old_link->interpose_method); - break; - } + assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr + set_chain(old_ptr); + if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + { + set_chain(NULL); return false; + } - set_chain(old_ptr); - host->interpose_list.push_back(this); + // Push the current link into the home host + applied = true; + host->interpose_list[vmethod_idx] = this; + prev = old_link; - // Link into the chain if any - if (old_link) + child_hosts.clear(); + child_next.clear(); + + if (old_link && old_link->host == host) { + // If the old link is home, just push into the plain chain + assert(old_link->next == NULL); old_link->next = this; - prev = old_link; + + // Child links belong to the topmost local entry + child_hosts.swap(old_link->child_hosts); + child_next.swap(old_link->child_next); + } + else + { + // If creating a new local chain, find children with same vmethod + find_child_hosts(host, old_ptr); + + if (old_link) + { + // Enter the child chain set + assert(old_link->child_hosts.count(host)); + old_link->child_hosts.erase(host); + old_link->child_next.insert(this); + + // Subtract our own children from the parent's sets + for (auto it = child_next.begin(); it != child_next.end(); ++it) + old_link->child_next.erase(*it); + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + old_link->child_hosts.erase(*it); + } + } + + // Chain subclass hooks + for (auto it = child_next.begin(); it != child_next.end(); ++it) + { + auto nlink = *it; + assert(nlink->saved_chain == old_ptr && nlink->prev == old_link); + nlink->set_chain(interpose_method); + nlink->prev = this; + } + + // Chain passive subclass hosts + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + { + auto nhost = *it; + assert(nhost->interpose_list[vmethod_idx] == old_link); + nhost->set_vmethod_ptr(vmethod_idx, interpose_method); + nhost->interpose_list[vmethod_idx] = this; } return true; @@ -225,25 +401,57 @@ void VMethodInterposeLinkBase::remove() if (!is_applied()) return; - // Remove from the list in the identity - for (int i = host->interpose_list.size()-1; i >= 0; i--) - if (host->interpose_list[i] == this) - vector_erase_at(host->interpose_list, i); - - // Remove from the chain + // Remove the link from prev to this if (prev) - prev->next = next; + { + if (prev->host == host) + prev->next = next; + else + { + prev->child_next.erase(this); + + if (next) + prev->child_next.insert(next); + } + } if (next) { next->set_chain(saved_chain); next->prev = prev; + + assert(child_next.empty() && child_hosts.empty()); } else { + // Remove from the list in the identity and vtable + host->interpose_list[vmethod_idx] = prev; host->set_vmethod_ptr(vmethod_idx, saved_chain); + + for (auto it = child_next.begin(); it != child_next.end(); ++it) + { + auto nlink = *it; + assert(nlink->saved_chain == interpose_method && nlink->prev == this); + nlink->set_chain(saved_chain); + nlink->prev = prev; + if (prev) + prev->child_next.insert(nlink); + } + + for (auto it = child_hosts.begin(); it != child_hosts.end(); ++it) + { + auto nhost = *it; + assert(nhost->interpose_list[vmethod_idx] == this); + nhost->interpose_list[vmethod_idx] = prev; + nhost->set_vmethod_ptr(vmethod_idx, saved_chain); + if (prev) + prev->child_hosts.insert(nhost); + } } + applied = false; prev = next = NULL; + child_next.clear(); + child_hosts.clear(); set_chain(NULL); } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index ccb29b0e7..591a0c3ff 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -303,7 +303,7 @@ namespace DFHack void *vtable_ptr; friend class VMethodInterposeLinkBase; - std::vector interpose_list; + std::map interpose_list; protected: virtual void doInit(Core *core); diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index bb7a37ce8..c9482f82c 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -134,21 +134,31 @@ namespace DFHack 1) Allow multiple hooks into the same vmethod 2) Auto-remove hooks when a plugin is unloaded. */ + friend class virtual_identity; virtual_identity *host; // Class with the vtable int vmethod_idx; void *interpose_method; // Pointer to the code of the interposing method void *chain_mptr; // Pointer to the chain field below + bool applied; void *saved_chain; // Previous pointer to the code VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method + // inherited vtable members + std::set child_hosts; + std::set child_next; + void set_chain(void *chain); + void on_host_delete(virtual_identity *host); + + VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); + void find_child_hosts(virtual_identity *cur, void *vmptr); public: VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr); ~VMethodInterposeLinkBase(); - bool is_applied() { return saved_chain != NULL; } + bool is_applied() { return applied; } bool apply(); void remove(); }; From 3402a3cd5d473d26c115145e19f4113db51e8435 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 13:24:37 +0400 Subject: [PATCH 06/61] Fix a deadlock problem between suspend in (un)load, and onupdate. --- library/PluginManager.cpp | 24 +++++++++++++++++------- library/include/PluginManager.h | 4 +++- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d8b9ff27d..ceb644e60 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -186,14 +186,17 @@ Plugin::~Plugin() bool Plugin::load(color_ostream &con) { - RefAutolock lock(access); - if(state == PS_BROKEN) - { - return false; - } - else if(state == PS_LOADED) { - return true; + RefAutolock lock(access); + if(state == PS_LOADED) + { + return true; + } + else if(state != PS_UNLOADED) + { + return false; + } + state = PS_LOADING; } // enter suspend CoreSuspender suspend; @@ -202,6 +205,7 @@ bool Plugin::load(color_ostream &con) if(!plug) { con.printerr("Can't load plugin %s\n", filename.c_str()); + RefAutolock lock(access); state = PS_BROKEN; return false; } @@ -211,6 +215,7 @@ bool Plugin::load(color_ostream &con) { con.printerr("Plugin %s has no name or version.\n", filename.c_str()); ClosePlugin(plug); + RefAutolock lock(access); state = PS_BROKEN; return false; } @@ -219,9 +224,11 @@ bool Plugin::load(color_ostream &con) con.printerr("Plugin %s was not built for this version of DFHack.\n" "Plugin: %s, DFHack: %s\n", *plug_name, *plug_version, DFHACK_VERSION); ClosePlugin(plug); + RefAutolock lock(access); state = PS_BROKEN; return false; } + RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); if(!plugin_init) { @@ -273,8 +280,11 @@ bool Plugin::unload(color_ostream &con) } // wait for all calls to finish access->wait(); + state = PS_UNLOADING; + access->unlock(); // enter suspend CoreSuspender suspend; + access->lock(); // notify plugin about shutdown, if it has a shutdown function command_result cr = CR_OK; if(plugin_shutdown) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 25b05ad40..38f0e2e50 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -128,7 +128,9 @@ namespace DFHack { PS_UNLOADED, PS_LOADED, - PS_BROKEN + PS_BROKEN, + PS_LOADING, + PS_UNLOADING }; friend class PluginManager; friend class RPCService; From bee33fd486b6eeb09926a781a29d0a0e7b278bfd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 14:42:36 +0400 Subject: [PATCH 07/61] Add a performance test for location caching in general refs. --- plugins/devel/CMakeLists.txt | 3 + plugins/devel/ref-index.cpp | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 plugins/devel/ref-index.cpp diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 8274accfb..134d5cb67 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,3 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +IF(UNIX) +DFHACK_PLUGIN(ref-index ref-index.cpp) +ENDIF() diff --git a/plugins/devel/ref-index.cpp b/plugins/devel/ref-index.cpp new file mode 100644 index 000000000..686f6918b --- /dev/null +++ b/plugins/devel/ref-index.cpp @@ -0,0 +1,149 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/item.h" +#include "df/unit.h" +#include "df/world.h" +#include "df/general_ref_item.h" +#include "df/general_ref_unit.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; + +using df::global::gps; + +DFHACK_PLUGIN("ref-index"); + +#define global_id id + +template +T get_from_global_id_vector(int32_t id, const std::vector &vect, int32_t *cache) +{ + size_t size = vect.size(); + int32_t start=0; + int32_t end=(int32_t)size-1; + + // Check the cached location. If it is a match, this provides O(1) lookup. + // Otherwise it works like one binsearch iteration. + if (size_t(*cache) < size) + { + T cptr = vect[*cache]; + if (cptr->global_id == id) + return cptr; + if (cptr->global_id < id) + start = *cache+1; + else + end = *cache-1; + } + + // Regular binsearch. The end check provides O(1) caching for missing item. + if (start <= end && vect[end]->global_id >= id) + { + do { + int32_t mid=(start+end)>>1; + + T cptr=vect[mid]; + if(cptr->global_id==id) + { + *cache = mid; + return cptr; + } + else if(cptr->global_id>id)end=mid-1; + else start=mid+1; + } while(start<=end); + } + + *cache = end+1; + return NULL; +} + +template T *find_object(int32_t id, int32_t *cache); +template<> df::item *find_object(int32_t id, int32_t *cache) { + return get_from_global_id_vector(id, df::global::world->items.all, cache); +} +template<> df::unit *find_object(int32_t id, int32_t *cache) { + return get_from_global_id_vector(id, df::global::world->units.all, cache); +} + +template +struct CachedRef { + int32_t id; + int32_t cache; + CachedRef(int32_t id = -1) : id(id), cache(-1) {} + T *target() { return find_object(id, &cache); } +}; + +#ifdef LINUX_BUILD +struct item_hook : df::general_ref_item { + typedef df::general_ref_item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(df::item*, getItem, ()) + { + // HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT + // This actually is true with glibc allocator due to granularity. + return find_object(item_id, 1+&item_id); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(item_hook, getItem); + +struct unit_hook : df::general_ref_unit { + typedef df::general_ref_unit interpose_base; + + DEFINE_VMETHOD_INTERPOSE(df::unit*, getUnit, ()) + { + // HUGE HACK: ASSUMES THERE ARE 4 USABLE BYTES AFTER THE OBJECT + // This actually is true with glibc allocator due to granularity. + return find_object(unit_id, 1+&unit_id); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(unit_hook, getUnit); + +command_result hook_refs(color_ostream &out, vector & parameters) +{ + auto &hook = INTERPOSE_HOOK(item_hook, getItem); + if (hook.is_applied()) + { + hook.remove(); + INTERPOSE_HOOK(unit_hook, getUnit).remove(); + } + else + { + hook.apply(); + INTERPOSE_HOOK(unit_hook, getUnit).apply(); + } + + if (hook.is_applied()) + out.print("Hook is applied.\n"); + else + out.print("Hook is not applied.\n"); + return CR_OK; +} +#endif + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ +#ifdef LINUX_BUILD + commands.push_back(PluginCommand("hook-refs","Inject O(1) cached lookup into general refs.",hook_refs)); +#endif + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} From 81716523238823625d09e186204801a413e41210 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 20:08:28 +0400 Subject: [PATCH 08/61] Support permaflow in liquids, including the gui. --- plugins/liquids.cpp | 74 ++++++++++++++++++++++++----- scripts/gui/liquids.lua | 102 ++++++++++++++++++++++++++++++++-------- 2 files changed, 144 insertions(+), 32 deletions(-) diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index b078b48fd..6df530a92 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -101,17 +101,36 @@ static const char *modify_mode_name[] = { "+", ".", "-", NULL }; +enum PermaflowMode { + PF_KEEP, PF_NONE, + PF_NORTH, PF_SOUTH, PF_EAST, PF_WEST, + PF_NORTHEAST, PF_NORTHWEST, PF_SOUTHEAST, PF_SOUTHWEST +}; + +static const char *permaflow_name[] = { + ".", "-", "N", "S", "E", "W", + "NE", "NW", "SE", "SW", NULL +}; + +#define X(name) tile_liquid_flow_dir::name +static const df::tile_liquid_flow_dir permaflow_id[] = { + X(none), X(none), X(north), X(south), X(east), X(west), + X(northeast), X(northwest), X(southeast), X(southwest) +}; +#undef X + struct OperationMode { BrushType brush; PaintMode paint; ModifyMode flowmode; ModifyMode setmode; + PermaflowMode permaflow; unsigned int amount; df::coord size; OperationMode() : brush(B_POINT), paint(P_MAGMA), - flowmode(M_INC), setmode(M_KEEP), amount(7), + flowmode(M_INC), setmode(M_KEEP), permaflow(PF_KEEP), amount(7), size(1,1,1) {} } cur_mode; @@ -119,6 +138,17 @@ struct OperationMode { command_result df_liquids_execute(color_ostream &out); command_result df_liquids_execute(color_ostream &out, OperationMode &mode, df::coord pos); +static void print_prompt(std::ostream &str, OperationMode &cur_mode) +{ + str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; + if (cur_mode.brush == B_RANGE) + str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; + str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] + << ":s" << modify_mode_name[cur_mode.setmode] + << ":pf" << permaflow_name[cur_mode.permaflow] + << "]"; +} + command_result df_liquids (color_ostream &out_, vector & parameters) { if(!out_.is_console()) @@ -154,11 +184,8 @@ command_result df_liquids (color_ostream &out_, vector & parameters) string input = ""; std::stringstream str; - str <<"[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; - if (cur_mode.brush == B_RANGE) - str << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; - str << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] - << ":s" << modify_mode_name[cur_mode.setmode] << "]#"; + print_prompt(str, cur_mode); + str << "# "; if(out.lineedit(str.str(),input,liquids_hist) == -1) return CR_FAILURE; liquids_hist.add(input); @@ -185,6 +212,10 @@ command_result df_liquids (color_ostream &out_, vector & parameters) << "f+ - make the spawned liquid flow" << endl << "f. - don't change flow state (read state in flow mode)" << endl << "f- - make the spawned liquid static" << endl + << "Permaflow (only for water):" << endl + << "pf. - don't change permaflow state" << endl + << "pf- - make the spawned liquid static" << endl + << "pf[NS][EW] - make the spawned liquid permanently flow" << endl << "0-7 - set liquid amount" << endl << "Brush:" << endl << "point - single tile [p]" << endl @@ -297,6 +328,20 @@ command_result df_liquids (color_ostream &out_, vector & parameters) { cur_mode.setmode = M_KEEP; } + else if (command.size() > 2 && memcmp(command.c_str(), "pf", 2) == 0) + { + auto *tail = command.c_str()+2; + for (int pm = PF_KEEP; pm <= PF_SOUTHWEST; pm++) + { + if (strcmp(tail, permaflow_name[pm]) != 0) + continue; + cur_mode.permaflow = PermaflowMode(pm); + tail = NULL; + break; + } + if (tail) + out << command << " : invalid permaflow mode" << endl; + } // blah blah, bad code, bite me. else if(command == "0") cur_mode.amount = 0; @@ -339,11 +384,8 @@ command_result df_liquids_here (color_ostream &out, vector & parameters } out.print("Run liquids-here with these parameters: "); - out << "[" << paint_mode_name[cur_mode.paint] << ":" << brush_name[cur_mode.brush]; - if (cur_mode.brush == B_RANGE) - out << "(w" << cur_mode.size.x << ":h" << cur_mode.size.y << ":z" << cur_mode.size.z << ")"; - out << ":" << cur_mode.amount << ":f" << modify_mode_name[cur_mode.flowmode] - << ":" << modify_mode_name[cur_mode.setmode] << "]\n"; + print_prompt(out, cur_mode); + out << endl; return df_liquids_execute(out); } @@ -489,6 +531,7 @@ command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, d iter ++; continue; } + auto raw_block = block->getRaw(); df::tile_designation des = mcache.designationAt(current); df::tiletype tt = mcache.tiletypeAt(current); // don't put liquids into places where they don't belong... @@ -548,6 +591,12 @@ command_result df_liquids_execute(color_ostream &out, OperationMode &cur_mode, d // request flow engine updates block->enableBlockUpdates(new_amount != old_amount, new_liquid != old_liquid); } + if (cur_mode.permaflow != PF_KEEP && raw_block) + { + auto &flow = raw_block->liquid_flow[current.x&15][current.y&15]; + flow.bits.perm_flow_dir = permaflow_id[cur_mode.permaflow]; + flow.bits.temp_flow_timer = 0; + } seen_blocks.insert(block); iter++; } @@ -593,7 +642,7 @@ static int paint(lua_State *L) df::coord pos; OperationMode mode; - lua_settop(L, 7); + lua_settop(L, 8); Lua::CheckDFAssign(L, &pos, 1); if (!pos.isValid()) luaL_argerror(L, 1, "invalid cursor position"); @@ -606,6 +655,7 @@ static int paint(lua_State *L) Lua::CheckDFAssign(L, &mode.size, 5); mode.setmode = (ModifyMode)luaL_checkoption(L, 6, ".", modify_mode_name); mode.flowmode = (ModifyMode)luaL_checkoption(L, 7, "+", modify_mode_name); + mode.permaflow = (PermaflowMode)luaL_checkoption(L, 8, ".", permaflow_name); lua_pushboolean(L, df_liquids_execute(*Lua::GetOutput(L), mode, pos)); return 1; diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index 27df49e9a..869cac908 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -16,12 +16,12 @@ local brushes = { } local paints = { - { tag = 'water', caption = 'Water', liquid = true, key = 'w' }, - { tag = 'magma', caption = 'Magma', liquid = true, key = 'l' }, + { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' }, + { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' }, { tag = 'obsidian', caption = 'Obsidian Wall' }, { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, { tag = 'riversource', caption = 'River Source' }, - { tag = 'flowbits', caption = 'Flow Updates' }, + { tag = 'flowbits', caption = 'Flow Updates', flow = true }, { tag = 'wclean', caption = 'Clean Salt/Stagnant' }, } @@ -37,6 +37,19 @@ local setmode = { { tag = '-', caption = 'Only Decrease' }, } +local permaflows = { + { tag = '.', caption = "Keep Permaflow" }, + { tag = '-', caption = 'Remove Permaflow' }, + { tag = 'N', caption = 'Set Permaflow N' }, + { tag = 'S', caption = 'Set Permaflow S' }, + { tag = 'E', caption = 'Set Permaflow E' }, + { tag = 'W', caption = 'Set Permaflow W' }, + { tag = 'NE', caption = 'Set Permaflow NE' }, + { tag = 'NW', caption = 'Set Permaflow NW' }, + { tag = 'SE', caption = 'Set Permaflow SE' }, + { tag = 'SW', caption = 'Set Permaflow SW' }, +} + Toggle = defclass(Toggle) function Toggle:init(items) @@ -80,6 +93,7 @@ function LiquidsUI:init() paint = mkinstance(Toggle):init(paints), flow = mkinstance(Toggle):init(flowbits), set = mkinstance(Toggle):init(setmode), + permaflow = mkinstance(Toggle):init(permaflows), amount = 7, } guidm.MenuOverlay.init(self) @@ -90,15 +104,8 @@ function LiquidsUI:onDestroy() guidm.clearSelection() end -function LiquidsUI:onRenderBody(dc) - dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) - - local cursor = guidm.getCursorPos() - local block = dfhack.maps.getTileBlock(cursor) - local tile = block.tiletype[cursor.x%16][cursor.y%16] - local dsgn = block.designation[cursor.x%16][cursor.y%16] - - dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN):newline(2) +function render_liquid(dc, block, x, y) + local dsgn = block.designation[x%16][y%16] if dsgn.flow_size > 0 then if dsgn.liquid_type == df.tile_liquid.Magma then @@ -111,7 +118,51 @@ function LiquidsUI:onRenderBody(dc) end dc:string(" ["..dsgn.flow_size.."/7]") else - dc:string('No Liquid', COLOR_DARKGREY) + dc:string('No Liquid') + end +end + +local permaflow_abbr = { + north = 'N', south = 'S', east = 'E', west = 'W', + northeast = 'NE', northwest = 'NW', southeast = 'SE', southwest = 'SW' +} + +function render_flow_state(dc, block, x, y) + local flow = block.liquid_flow[x%16][y%16] + + if block.flags.update_liquid then + dc:string("Updating", COLOR_GREEN) + else + dc:string("Static") + end + dc:string(", ") + if flow.perm_flow_dir ~= 0 then + local tag = df.tile_liquid_flow_dir[flow.perm_flow_dir] + dc:string("Permaflow "..(permaflow_abbr[tag] or tag), COLOR_CYAN) + elseif flow.temp_flow_timer > 0 then + dc:string("Flowing "..flow.temp_flow_timer, COLOR_GREEN) + else + dc:string("No Flow") + end +end + +function LiquidsUI:onRenderBody(dc) + dc:clear():seek(1,1):string("Paint Liquids Cheat", COLOR_WHITE) + + local cursor = guidm.getCursorPos() + local block = dfhack.maps.getTileBlock(cursor) + + if block then + local x, y = pos2xyz(cursor) + local tile = block.tiletype[x%16][y%16] + + dc:seek(2,3):string(df.tiletype.attrs[tile].caption, COLOR_CYAN) + dc:newline(2):pen(COLOR_DARKGREY) + render_liquid(dc, block, x, y) + dc:newline(2):pen(COLOR_DARKGREY) + render_flow_state(dc, block, x, y) + else + dc:seek(2,3):string("No map data", COLOR_RED):advance(0,2) end dc:newline():pen(COLOR_GREY) @@ -121,10 +172,10 @@ function LiquidsUI:onRenderBody(dc) dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") self.paint:render(dc) - local liquid = self.paint:get().liquid + local paint = self.paint:get() dc:newline() - if liquid then + if paint.liquid then dc:newline(1):string("Amount: "..self.amount) dc:advance(1):string("("):string("-+", COLOR_LIGHTGREEN):string(")") dc:newline(3):string("s", COLOR_LIGHTGREEN):string(": ") @@ -133,8 +184,15 @@ function LiquidsUI:onRenderBody(dc) dc:advance(0,2) end - dc:newline():newline(1):string("f", COLOR_LIGHTGREEN):string(": ") - self.flow:render(dc) + dc:newline() + if paint.flow then + dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + self.flow:render(dc) + dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ") + self.permaflow:render(dc) + else + dc:advance(0,2) + end dc:newline():newline(1):pen(COLOR_WHITE) dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") @@ -142,7 +200,8 @@ function LiquidsUI:onRenderBody(dc) end function LiquidsUI:onInput(keys) - local liquid = self.paint:get().liquid + local paint = self.paint:get() + local liquid = paint.liquid if keys.CUSTOM_B then self.brush:step() elseif keys.CUSTOM_P then @@ -153,8 +212,10 @@ function LiquidsUI:onInput(keys) self.amount = math.min(7, self.amount+1) elseif liquid and keys.CUSTOM_S then self.set:step() - elseif keys.CUSTOM_F then + elseif paint.flow and keys.CUSTOM_F then self.flow:step() + elseif paint.flow and keys.CUSTOM_R then + self.permaflow:step() elseif keys.LEAVESCREEN then if guidm.getSelection() then guidm.clearSelection() @@ -182,7 +243,8 @@ function LiquidsUI:onInput(keys) cursor, self.brush:get().tag, self.paint:get().tag, self.amount, size, - self.set:get().tag, self.flow:get().tag + self.set:get().tag, self.flow:get().tag, + self.permaflow:get().tag ) elseif self:propagateMoveKeys(keys) then return From 84f6663a078ee8907fb1287fc25672c5f226f420 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 21:19:56 +0400 Subject: [PATCH 09/61] Add a tweak to save the cursor position of dwarfmode between menus. --- dfhack.init-example | 7 +++++ plugins/tweak.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 68 insertions(+), 3 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 552b2b3a1..380bdd04f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -51,3 +51,10 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work # interface for the liquids plugin keybinding add Alt-L@dwarfmode/LookAround gui/liquids + +################### +# UI logic tweaks # +################### + +# stabilize the cursor of dwarfmode when switching menus +tweak stable-cursor diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 2daa9063b..70acf7607 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -13,6 +13,7 @@ #include "MiscUtils.h" #include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -26,6 +27,7 @@ #include "df/death_info.h" #include "df/criminal_case.h" #include "df/unit_inventory_item.h" +#include "df/viewscreen_dwarfmodest.h" #include @@ -67,6 +69,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector & params); - - // to be called by tweak-fixmigrant // units forced into the fort by removing the flags do not own their clothes // which has the result that they drop all their clothes and become unhappy because they are naked @@ -136,6 +138,54 @@ command_result fix_clothing_ownership(color_ostream &out, df::unit* unit) return CR_OK; } +/* + * Save or restore cursor position on change to/from main dwarfmode menu. + */ + +static df::coord last_view, last_cursor; + +struct stable_cursor_hook : df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default); + df::coord view = Gui::getViewportPos(); + df::coord cursor = Gui::getCursorPos(); + + INTERPOSE_NEXT(feed)(input); + + bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default); + df::coord cur_cursor = Gui::getCursorPos(); + + if (is_default && !was_default) + { + last_view = view; last_cursor = cursor; + } + else if (!is_default && was_default && + Gui::getViewportPos() == last_view && + last_cursor.isValid() && cur_cursor.isValid()) + { + Gui::setCursorCoords(last_cursor.x, last_cursor.y, last_cursor.z); + + // Force update of ui state + set tmp; + tmp.insert(interface_key::CURSOR_DOWN_Z); + INTERPOSE_NEXT(feed)(&tmp); + tmp.clear(); + tmp.insert(interface_key::CURSOR_UP_Z); + INTERPOSE_NEXT(feed)(&tmp); + } + else if (cur_cursor.isValid()) + { + last_cursor = df::coord(); + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(stable_cursor_hook, feed); + static command_result tweak(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -234,6 +284,14 @@ static command_result tweak(color_ostream &out, vector ¶meters) unit->profession2 = df::profession::TRADER; return fix_clothing_ownership(out, unit); } + else if (cmd == "stable-cursor") + { + auto &hook = INTERPOSE_HOOK(stable_cursor_hook, feed); + if (vector_get(parameters, 1) == "disable") + hook.remove(); + else + hook.apply(); + } else return CR_WRONG_USAGE; From b2bdc199cb6aec64034e672f7590aa2a7a27194c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 26 Aug 2012 22:43:18 +0400 Subject: [PATCH 10/61] Fix NULL pointer access in ~virtual_identity. --- library/DataDefs.cpp | 6 ++++-- library/VTableInterpose.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 4428a2f76..341164441 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -218,8 +218,10 @@ virtual_identity::virtual_identity(size_t size, TAllocateFn alloc, virtual_identity::~virtual_identity() { // Remove interpose entries, so that they don't try accessing this object later - while (!interpose_list.empty()) - interpose_list.begin()->second->on_host_delete(this); + for (auto it = interpose_list.begin(); it != interpose_list.end(); ++it) + if (it->second) + it->second->on_host_delete(this); + interpose_list.clear(); } /* Vtable name to identity lookup. */ diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 47110cece..04c436ba7 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -311,7 +311,7 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) // Unlink the chains child_hosts.erase(from); - from->interpose_list.erase(vmethod_idx); + from->interpose_list[vmethod_idx] = NULL; } } From f56287186754ac90056ea79581a30a5c45828961 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 26 Aug 2012 13:58:37 -0500 Subject: [PATCH 11/61] Add ViewCre and Zoom-Cre to Manipulator (by forwarding them to Unitlist) --- plugins/manipulator.cpp | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3895b0650..55fee5db0 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -544,6 +544,24 @@ void viewscreen_unitlaborsst::feed(set *events) break; } } + + if (VIRTUAL_CAST_VAR(unitlist, df::viewscreen_unitlistst, parent)) + { + if (events->count(interface_key::UNITJOB_VIEW) || events->count(interface_key::UNITJOB_ZOOM_CRE)) + { + for (int i = 0; i < unitlist->units[unitlist->page].size(); i++) + { + if (unitlist->units[unitlist->page][i] == units[sel_row]->unit) + { + unitlist->cursor_pos[unitlist->page] = i; + unitlist->feed(events); + if (Screen::isDismissed(unitlist)) + Screen::dismiss(this); + break; + } + } + } + } } void OutputString(int8_t color, int &x, int y, const std::string &text) @@ -679,8 +697,13 @@ void viewscreen_unitlaborsst::render() int x = 1; OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key - OutputString(15, x, gps->dimy - 3, ": Toggle labor"); - x += 2; + OutputString(15, x, gps->dimy - 3, ": Toggle labor, "); + + 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(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 3, ": Done"); @@ -688,8 +711,7 @@ void viewscreen_unitlaborsst::render() 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"); - x += 2; + OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); OutputString(10, x, gps->dimy - 2, "*"); // SECONDSCROLL_PAGEDOWN key OutputString(10, x, gps->dimy - 2, "/"); // SECONDSCROLL_PAGEUP key From 5fed060d7dbedd3014eee2675c172d086dbf53a4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 16:01:11 +0400 Subject: [PATCH 12/61] Follow field rename in xml. --- library/xml | 2 +- plugins/devel/rprobe.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/xml b/library/xml index abcb667bc..3fc2e1569 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit abcb667bc832048552d8cbc8f4830936f8b63399 +Subproject commit 3fc2e1569ff19953d11b6ea651bc9b8ca832b8a3 diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index 7a091a962..e19dbf9ba 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -79,7 +79,7 @@ command_result rprobe (color_ostream &out, vector & parameters) if (parameters.size() == 2) { - if (parameters[0] == "wet") + if (parameters[0] == "rai") set_field = 0; else if (parameters[0] == "veg") set_field = 1; @@ -87,7 +87,7 @@ command_result rprobe (color_ostream &out, vector & parameters) set_field = 2; else if (parameters[0] == "evi") set_field = 3; - else if (parameters[0] == "hil") + else if (parameters[0] == "dra") set_field = 4; else if (parameters[0] == "sav") set_field = 5; @@ -117,7 +117,7 @@ command_result rprobe (color_ostream &out, vector & parameters) if (set && i == to_set) { if (set_field == 0) - rd->wetness = set_val; + rd->rainfall = set_val; else if (set_field == 1) rd->vegetation = set_val; else if (set_field == 2) @@ -125,11 +125,11 @@ command_result rprobe (color_ostream &out, vector & parameters) else if (set_field == 3) rd->evilness = set_val; else if (set_field == 4) - rd->hilliness = set_val; + rd->drainage = set_val; else if (set_field == 5) rd->savagery = set_val; else if (set_field == 6) - rd->saltiness = set_val; + rd->salinity = set_val; } out << i << ": x = " << rg.x << ", y = " << rg.y; @@ -140,13 +140,13 @@ command_result rprobe (color_ostream &out, vector & parameters) " landmass_id: " << rd->landmass_id << " flags: " << hex << rd->flags.as_int() << dec << endl; out << - "wet: " << rd->wetness << " " << + "rai: " << rd->rainfall << " " << "veg: " << rd->vegetation << " " << "tem: " << rd->temperature << " " << "evi: " << rd->evilness << " " << - "hil: " << rd->hilliness << " " << + "dra: " << rd->drainage << " " << "sav: " << rd->savagery << " " << - "sal: " << rd->saltiness; + "sal: " << rd->salinity; int32_t *p = (int32_t *)rd; int c = sizeof(*rd) / sizeof(int32_t); From faf3bdf2b7e7b4d01de2c746397cf6be12cde702 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 27 Aug 2012 09:04:32 -0500 Subject: [PATCH 13/61] Adjust grid display - labors without skills use different tiles, and skills without labors have a red background --- plugins/manipulator.cpp | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 55fee5db0..71b1fc907 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -630,11 +630,9 @@ void viewscreen_unitlaborsst::render() int col_offset = col + first_column; fg = 15; bg = 0; + char c = 0xFA; if ((col_offset == sel_column) && (row_offset == sel_row)) fg = 9; - if ((columns[col_offset].labor != unit_labor::NONE) && (unit->status.labors[columns[col_offset].labor])) - bg = 7; - char c = '-'; if (columns[col_offset].skill != job_skill::NONE) { df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); @@ -645,7 +643,20 @@ void viewscreen_unitlaborsst::render() level = NUM_SKILL_LEVELS - 1; c = skill_levels[level].abbrev; } + else + c = '-'; } + if (columns[col_offset].labor != unit_labor::NONE) + { + if (unit->status.labors[columns[col_offset].labor]) + { + bg = 7; + if (columns[col_offset].skill == job_skill::NONE) + c = 0xF9; + } + } + else + bg = 4; Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 3 + row); } } From f1915915b48395e9180c03dc8bf26f303eff877f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 23:03:02 +0400 Subject: [PATCH 14/61] Follow change in xml again. --- library/LuaApi.cpp | 4 ++-- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 3 ++- library/xml | 2 +- plugins/changelayer.cpp | 1 + plugins/devel/rprobe.cpp | 3 ++- plugins/probe.cpp | 4 ++-- plugins/prospector.cpp | 3 +++ 8 files changed, 14 insertions(+), 8 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 00d4c517d..6dfb2f354 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -77,6 +77,7 @@ distribution. #include "df/job_material_category.h" #include "df/burrow.h" #include "df/building_civzonest.h" +#include "df/region_map_entry.h" #include #include @@ -931,8 +932,7 @@ static int maps_getRegionBiome(lua_State *L) static int maps_getTileBiomeRgn(lua_State *L) { auto pos = CheckCoordXYZ(L, 1, true); - Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos)); - return 1; + return Lua::PushPosXY(L, Maps::getTileBiomeRgn(pos)); } static const luaL_Reg dfhack_maps_funcs[] = { diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index e63eef733..e6e9682eb 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -258,7 +258,7 @@ inline df::tile_occupancy *getTileOccupancy(df::coord pos) { /** * Returns biome info about the specified world region. */ -DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos); +DFHACK_EXPORT df::region_map_entry *getRegionBiome(df::coord2d rgn_pos); /** * Returns biome world region coordinates for the given tile within given block. diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 3ab156d77..4107680b0 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -57,6 +57,7 @@ using namespace std; #include "df/builtin_mats.h" #include "df/block_square_event_grassst.h" #include "df/z_level_flags.h" +#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; @@ -166,7 +167,7 @@ df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z) return block ? &block->occupancy[x&15][y&15] : NULL; } -df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos) +df::region_map_entry *Maps::getRegionBiome(df::coord2d rgn_pos) { auto data = world->world_data; if (!data) diff --git a/library/xml b/library/xml index 3fc2e1569..328a8dbdc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 3fc2e1569ff19953d11b6ea651bc9b8ca832b8a3 +Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f diff --git a/plugins/changelayer.cpp b/plugins/changelayer.cpp index 317a0fa36..3ab1899af 100644 --- a/plugins/changelayer.cpp +++ b/plugins/changelayer.cpp @@ -19,6 +19,7 @@ #include "df/world_data.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index e19dbf9ba..805489d5e 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world_region_details.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -113,7 +114,7 @@ command_result rprobe (color_ostream &out, vector & parameters) { coord2d rg = screen->biome_rgn[i]; - df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y]; + auto rd = &data->region_map[rg.x][rg.y]; if (set && i == to_set) { if (set_field == 0) diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 2ae6846d5..45ef1bbfb 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world.h" #include "df/world_raws.h" #include "df/building_def.h" +#include "df/region_map_entry.h" using std::vector; using std::string; @@ -224,8 +225,7 @@ command_result df_probe (color_ostream &out, vector & parameters) int bx = clip_range(block.region_pos.x + (offset % 3) - 1, 0, world->world_data->world_width-1); int by = clip_range(block.region_pos.y + (offset / 3) - 1, 0, world->world_data->world_height-1); - df::world_data::T_region_map* biome = - &world->world_data->region_map[bx][by]; + auto biome = &world->world_data->region_map[bx][by]; int sav = biome->savagery; int evi = biome->evilness; diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index e2f1e9534..6836b38c2 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -27,6 +27,7 @@ using namespace std; #include "df/world_region_details.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -536,6 +537,8 @@ command_result prospector (color_ostream &con, vector & parameters) case tiletype_material::LAVA_STONE: // TODO ? break; + default: + break; } } } From f73cebff68922801e4eaeffbd2b0487d868a3b56 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 27 Aug 2012 23:03:17 +0400 Subject: [PATCH 15/61] Account for caves and magma sea in pre-embark prospector. --- plugins/prospector.cpp | 292 +++++++++++++++++++++++++++++------------ 1 file changed, 211 insertions(+), 81 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 6836b38c2..dce44e35a 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -25,8 +25,11 @@ using namespace std; #include "df/world.h" #include "df/world_data.h" #include "df/world_region_details.h" +#include "df/world_region_feature.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" +#include "df/world_underground_region.h" +#include "df/feature_init.h" #include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" @@ -109,9 +112,10 @@ struct compare_pair_second } }; -static void printMatdata(color_ostream &con, const matdata &data) +static void printMatdata(color_ostream &con, const matdata &data, bool only_z = false) { - con << std::setw(9) << data.count; + if (!only_z) + con << std::setw(9) << data.count; if(data.lower_z != data.upper_z) con <<" Z:" << std::setw(4) << data.lower_z << ".." << data.upper_z << std::endl; @@ -226,116 +230,239 @@ static coord2d biome_delta[] = { coord2d(-1,-1), coord2d(0,-1), coord2d(1,-1) }; -static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, - bool showHidden, bool showValue) +struct EmbarkTileLayout { + int elevation; + int min_z, base_z; + std::map penalty; +}; + +bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y) { - if (!world || !world->world_data) + tile.elevation = ( + details->elevation[x][y] + details->elevation[x][y+1] + + details->elevation[x+1][y] + details->elevation[x+1][y+1] + ) / 4; + tile.base_z = tile.elevation; + tile.penalty.clear(); + + auto &features = details->features[x][y]; + + // Collect global feature layer depths and apply penalties + std::map layer_bottom, layer_top; + bool sea_found = false; + + for (size_t i = 0; i < features.size(); i++) { - out.printerr("World data is not available.\n"); - return CR_FAILURE; + auto feature = features[i]; + auto layer = df::world_underground_region::find(feature->layer); + if (!layer || feature->min_z == -30000) continue; + + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + tile.base_z = std::min(tile.base_z, (int)feature->min_z); + + float penalty = 1.0f; + switch (layer->type) { + case df::world_underground_region::Cavern: + penalty = 0.75f; + break; + case df::world_underground_region::MagmaSea: + sea_found = true; + tile.min_z = feature->min_z; + for (int i = feature->min_z; i <= feature->max_z; i++) + tile.penalty[i] = 0.2 + 0.6f*(i-feature->min_z)/(feature->max_z-feature->min_z+1); + break; + case df::world_underground_region::Underworld: + penalty = 0.0f; + break; + } + + if (penalty != 1.0f) + { + for (int i = feature->min_z; i <= feature->max_z; i++) + tile.penalty[i] = penalty; + } } - df::world_data *data = world->world_data; - coord2d cur_region = screen->region_pos; - int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region); - auto cur_details = vector_get(data->region_details, d_idx); + if (!sea_found) + { + out.printerr("Could not find magma sea.\n"); + return false; + } - if (!cur_details) + // Scan for big local features and apply their penalties + for (size_t i = 0; i < features.size(); i++) { - out.printerr("Current region details are not available.\n"); - return CR_FAILURE; + auto feature = features[i]; + auto lfeature = Maps::getLocalInitFeature(details->pos, feature->feature_idx); + if (!lfeature) + continue; + + switch (lfeature->getType()) + { + case feature_type::pit: + case feature_type::magma_pool: + case feature_type::volcano: + for (int i = layer_bottom[lfeature->end_depth]; + i <= layer_top[lfeature->start_depth]; i++) + tile.penalty[i] = std::min(0.4f, map_find(tile.penalty, i, 1.0f)); + break; + default: + break; + } } - // Compute biomes - std::map biomes; + return true; +} + +void add_materials(EmbarkTileLayout &tile, matdata &data, float amount, int min_z, int max_z) +{ + for (int z = min_z; z <= max_z; z++) + data.add(z, int(map_find(tile.penalty, z, 1)*amount)); +} + +bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats, df::coord2d biome) +{ + using namespace geo_layer_type; - if (screen->biome_highlighted) + df::world_data *data = world->world_data; + int bx = clip_range(biome.x, 0, data->world_width-1); + int by = clip_range(biome.y, 0, data->world_height-1); + auto ®ion = data->region_map[bx][by]; + df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + + if (!geo_biome) { - out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); - biomes[screen->biome_rgn[screen->biome_idx]]++; + out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); + return false; } - else + + // soil depth increases by 1 every 5 levels below 150 + int top_z_level = tile.elevation - std::max((154-tile.elevation)/5,0); + + for (unsigned i = 0; i < geo_biome->layers.size(); i++) { - for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++) + auto layer = geo_biome->layers[i]; + switch (layer->type) { - for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) - { - int bv = clip_range(cur_details->biome[x][y], 1, 9); - biomes[cur_region + biome_delta[bv-1]]++; - } + case SOIL: + case SOIL_OCEAN: + case SOIL_SAND: + top_z_level += layer->top_height - layer->bottom_height + 1; + break; + default:; } } - // Compute material maps - MatMap layerMats; - MatMap veinMats; + top_z_level = std::max(top_z_level, tile.elevation)-1; - for (auto biome_it = biomes.begin(); biome_it != biomes.end(); ++biome_it) + for (unsigned i = 0; i < geo_biome->layers.size(); i++) { - int bx = clip_range(biome_it->first.x, 0, data->world_width-1); - int by = clip_range(biome_it->first.y, 0, data->world_height-1); - auto ®ion = data->region_map[bx][by]; - df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + auto layer = geo_biome->layers[i]; + + int top_z = std::min(layer->top_height + top_z_level, tile.elevation-1); + int bottom_z = std::max(layer->bottom_height + top_z_level, tile.min_z); + if (i+1 == geo_biome->layers.size()) // stretch layer if needed + bottom_z = tile.min_z; + if (top_z < bottom_z) + continue; + + float layer_size = 48*48; + + int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; + + for (unsigned j = 0; j < layer->vein_mat.size(); j++) + if (is_valid_enum_item(layer->vein_type[j])) + sums[layer->vein_type[j]] += layer->vein_unk_38[j]; - if (!geo_biome) + for (unsigned j = 0; j < layer->vein_mat.size(); j++) { - out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); - return CR_FAILURE; + // TODO: find out how to estimate the real density + // this code assumes that vein_unk_38 is the weight + // used when choosing the vein material + float size = float(layer->vein_unk_38[j]); + df::inclusion_type type = layer->vein_type[j]; + + switch (type) + { + case inclusion_type::VEIN: + // 3 veins of 80 tiles avg + size = size * 80 * 3 / sums[type]; + break; + case inclusion_type::CLUSTER: + // 1 cluster of 700 tiles avg + size = size * 700 * 1 / sums[type]; + break; + case inclusion_type::CLUSTER_SMALL: + size = size * 6 * 7 / sums[type]; + break; + case inclusion_type::CLUSTER_ONE: + size = size * 1 * 5 / sums[type]; + break; + default: + // shouldn't actually happen + size = 1; + } + + layer_size -= size; + + add_materials(tile, veinMats[layer->vein_mat[j]], size, bottom_z, top_z); } - int cnt = biome_it->second; + add_materials(tile, layerMats[layer->mat_index], layer_size, bottom_z, top_z); + } - for (unsigned i = 0; i < geo_biome->layers.size(); i++) - { - auto layer = geo_biome->layers[i]; + return true; +} - layerMats[layer->mat_index].add(layer->bottom_height, 0); +static command_result embark_prospector(color_ostream &out, df::viewscreen_choose_start_sitest *screen, + bool showHidden, bool showValue) +{ + if (!world || !world->world_data) + { + out.printerr("World data is not available.\n"); + return CR_FAILURE; + } - int level_cnt = layer->top_height - layer->bottom_height + 1; - int layer_size = 48*48*cnt*level_cnt; + df::world_data *data = world->world_data; + coord2d cur_region = screen->region_pos; + int d_idx = linear_index(data->region_details, &df::world_region_details::pos, cur_region); + auto cur_details = vector_get(data->region_details, d_idx); - int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; + if (!cur_details) + { + out.printerr("Current region details are not available.\n"); + return CR_FAILURE; + } - for (unsigned j = 0; j < layer->vein_mat.size(); j++) - if (is_valid_enum_item(layer->vein_type[j])) - sums[layer->vein_type[j]] += layer->vein_unk_38[j]; + // Compute material maps + MatMap layerMats; + MatMap veinMats; + matdata world_bottom; - for (unsigned j = 0; j < layer->vein_mat.size(); j++) - { - // TODO: find out how to estimate the real density - // this code assumes that vein_unk_38 is the weight - // used when choosing the vein material - int size = layer->vein_unk_38[j]*cnt*level_cnt; - df::inclusion_type type = layer->vein_type[j]; + // Compute biomes + std::map biomes; - switch (type) - { - case inclusion_type::VEIN: - // 3 veins of 80 tiles avg - size = size * 80 * 3 / sums[type]; - break; - case inclusion_type::CLUSTER: - // 1 cluster of 700 tiles avg - size = size * 700 * 1 / sums[type]; - break; - case inclusion_type::CLUSTER_SMALL: - size = size * 6 * 7 / sums[type]; - break; - case inclusion_type::CLUSTER_ONE: - size = size * 1 * 5 / sums[type]; - break; - default: - // shouldn't actually happen - size = cnt*level_cnt; - } + /*if (screen->biome_highlighted) + { + out.print("Processing one embark tile of biome F%d.\n\n", screen->biome_idx+1); + biomes[screen->biome_rgn[screen->biome_idx]]++; + }*/ - veinMats[layer->vein_mat[j]].add(layer->bottom_height, 0); - veinMats[layer->vein_mat[j]].add(layer->top_height, size); + for (int x = screen->embark_pos_min.x; x <= screen->embark_pos_max.x; x++) + { + for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) + { + int bv = clip_range(cur_details->biome[x][y] & 15, 1, 9); + df::coord2d rgn = cur_region + biome_delta[bv-1]; - layer_size -= size; - } + EmbarkTileLayout tile; + if (!estimate_underground(out, tile, cur_details, x, y) || + !estimate_materials(out, tile, layerMats, veinMats, rgn)) + return CR_FAILURE; - layerMats[layer->mat_index].add(layer->top_height, std::max(0,layer_size)); + world_bottom.add(tile.base_z, 0); + world_bottom.add(tile.elevation-1, 0); } } @@ -349,7 +476,10 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos mats->Finish(); } - out << "Warning: the above data is only a very rough estimate." << std::endl; + out << "Embark depth: " << (world_bottom.upper_z-world_bottom.lower_z+1) << " "; + printMatdata(out, world_bottom, true); + + out << std::endl << "Warning: the above data is only a very rough estimate." << std::endl; return CR_OK; } From c587ea2c74056b392d004f8e5186c076fe0ed783 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 27 Aug 2012 14:06:10 -0500 Subject: [PATCH 16/61] Add new tweak command to make Train orders no longer count as patrol duty --- plugins/tweak.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 70acf7607..591125c5e 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -28,6 +28,7 @@ #include "df/criminal_case.h" #include "df/unit_inventory_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/squad_order_trainst.h" #include @@ -72,6 +73,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ¶meters) { @@ -292,6 +308,14 @@ static command_result tweak(color_ostream &out, vector ¶meters) else hook.apply(); } + else if (cmd == "patrol-duty") + { + auto &hook = INTERPOSE_HOOK(patrol_duty_hook, isPatrol); + if (vector_get(parameters, 1) == "disable") + hook.remove(); + else + hook.apply(); + } else return CR_WRONG_USAGE; From 834d7fa1faa09f1c6586b8d3994e05ae29943f15 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 28 Aug 2012 11:52:54 +0400 Subject: [PATCH 17/61] Tweak prospector: try using the biome elevation for soil depth. Until a better idea presents itself (or maybe it is the right way). Soil depth computation affects which soil layers are reported, and Z level alignment of the layer stack. --- plugins/prospector.cpp | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index dce44e35a..5eab897c0 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -231,17 +231,31 @@ static coord2d biome_delta[] = { }; struct EmbarkTileLayout { - int elevation; + coord2d biome_off, biome_pos; + df::region_map_entry *biome; + int elevation, max_soil_depth; int min_z, base_z; std::map penalty; }; bool estimate_underground(color_ostream &out, EmbarkTileLayout &tile, df::world_region_details *details, int x, int y) { + // Find actual biome + int bv = clip_range(details->biome[x][y] & 15, 1, 9); + tile.biome_off = biome_delta[bv-1]; + + df::world_data *data = world->world_data; + int bx = clip_range(details->pos.x + tile.biome_off.x, 0, data->world_width-1); + int by = clip_range(details->pos.y + tile.biome_off.y, 0, data->world_height-1); + tile.biome_pos = coord2d(bx, by); + tile.biome = &data->region_map[bx][by]; + + // Compute surface elevation tile.elevation = ( details->elevation[x][y] + details->elevation[x][y+1] + details->elevation[x+1][y] + details->elevation[x+1][y+1] ) / 4; + tile.max_soil_depth = std::max((154-tile.biome->elevation)/5,0); tile.base_z = tile.elevation; tile.penalty.clear(); @@ -321,24 +335,21 @@ void add_materials(EmbarkTileLayout &tile, matdata &data, float amount, int min_ data.add(z, int(map_find(tile.penalty, z, 1)*amount)); } -bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats, df::coord2d biome) +bool estimate_materials(color_ostream &out, EmbarkTileLayout &tile, MatMap &layerMats, MatMap &veinMats) { using namespace geo_layer_type; - df::world_data *data = world->world_data; - int bx = clip_range(biome.x, 0, data->world_width-1); - int by = clip_range(biome.y, 0, data->world_height-1); - auto ®ion = data->region_map[bx][by]; - df::world_geo_biome *geo_biome = df::world_geo_biome::find(region.geo_index); + df::world_geo_biome *geo_biome = df::world_geo_biome::find(tile.biome->geo_index); if (!geo_biome) { - out.printerr("Region geo-biome not found: (%d,%d)\n", bx, by); + out.printerr("Region geo-biome not found: (%d,%d)\n", + tile.biome_pos.x, tile.biome_pos.y); return false; } // soil depth increases by 1 every 5 levels below 150 - int top_z_level = tile.elevation - std::max((154-tile.elevation)/5,0); + int top_z_level = tile.elevation - tile.max_soil_depth; for (unsigned i = 0; i < geo_biome->layers.size(); i++) { @@ -453,12 +464,9 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos { for (int y = screen->embark_pos_min.y; y <= screen->embark_pos_max.y; y++) { - int bv = clip_range(cur_details->biome[x][y] & 15, 1, 9); - df::coord2d rgn = cur_region + biome_delta[bv-1]; - EmbarkTileLayout tile; if (!estimate_underground(out, tile, cur_details, x, y) || - !estimate_materials(out, tile, layerMats, veinMats, rgn)) + !estimate_materials(out, tile, layerMats, veinMats)) return CR_FAILURE; world_bottom.add(tile.base_z, 0); From b2587c1e6dde92b38759bc5f47050fcf7bf1e45c Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 28 Aug 2012 15:33:22 -0500 Subject: [PATCH 18/61] Fix skill sort --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 71b1fc907..ff8cf68f9 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -279,7 +279,7 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) if (sort_skill != job_skill::NONE) { df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); - df::unit_skill *s2 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s2 = binsearch_in_vector>(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); int l1 = s1 ? s1->rating : 0; int l2 = s2 ? s2->rating : 0; int e1 = s1 ? s1->experience : 0; From 4e169558f58c86d6263fdbe125df73d3b769eeeb Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 28 Aug 2012 15:52:26 -0500 Subject: [PATCH 19/61] Tweak column labels a bit --- plugins/manipulator.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index ff8cf68f9..124dc768b 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -97,7 +97,7 @@ const SkillColumn columns[] = { {profession::STONEWORKER, unit_labor::MASON, job_skill::MASONRY, "Ma"}, {profession::STONEWORKER, unit_labor::DETAIL, job_skill::DETAILSTONE, "En"}, // Hunting/Related - {profession::RANGER, unit_labor::ANIMALTRAIN, job_skill::ANIMALTRAIN, "Tr"}, + {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"}, @@ -190,19 +190,19 @@ const SkillColumn columns[] = { {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, "Pu"}, + {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, "Fi"}, - {profession::WRESTLER, unit_labor::NONE, job_skill::RANGED_COMBAT, "Ar"}, + {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, "Le"}, + {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, "Lr"}, + {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, "Aw"}, + {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"}, @@ -210,13 +210,15 @@ const SkillColumn columns[] = { {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, "Ly"}, + {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"}, + +// 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"}, From 8a617edb10c79671255ad8cb7d757ab0d0faec64 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 29 Aug 2012 19:03:53 +0400 Subject: [PATCH 20/61] Support "ls -a" to list scripts in subdirs. --- library/Core.cpp | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 6a0dea7c2..735359a7f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -219,28 +219,30 @@ static std::string getScriptHelp(std::string path, std::string helpprefix) return "No help available."; } -static std::map listScripts(PluginManager *plug_mgr, std::string path) +static void listScripts(PluginManager *plug_mgr, std::map &pset, std::string path, bool all, std::string prefix = "") { std::vector files; getdir(path, files); - std::map pset; for (size_t i = 0; i < files.size(); i++) { if (hasEnding(files[i], ".lua")) { std::string help = getScriptHelp(path + files[i], "-- "); - pset[files[i].substr(0, files[i].size()-4)] = help; + pset[prefix + files[i].substr(0, files[i].size()-4)] = help; } else if (plug_mgr->eval_ruby && hasEnding(files[i], ".rb")) { std::string help = getScriptHelp(path + files[i], "# "); - pset[files[i].substr(0, files[i].size()-3)] = help; + pset[prefix + files[i].substr(0, files[i].size()-3)] = help; + } + else if (all && !files[i].empty() && files[i][0] != '.') + { + listScripts(plug_mgr, pset, path+files[i]+"/", all, prefix+files[i]+"/"); } } - return pset; } static bool fileExists(std::string path) @@ -335,7 +337,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve con.print("Basic commands:\n" " help|?|man - This text.\n" " help COMMAND - Usage help for the given command.\n" - " ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n" + " ls|dir [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" " cls - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" @@ -469,6 +471,12 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve } else if(first == "ls" || first == "dir") { + bool all = false; + if (parts.size() && parts[0] == "-a") + { + all = true; + vector_erase_at(parts, 0); + } if(parts.size()) { string & plugname = parts[0]; @@ -491,7 +499,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve con.print( "builtin:\n" " help|?|man - This text or help specific to a plugin.\n" - " ls [PLUGIN] - List available commands. Optionally for single plugin.\n" + " ls [-a] [PLUGIN] - List available commands. Optionally for single plugin.\n" " cls - Clear the console.\n" " fpause - Force DF to pause.\n" " die - Force DF to close immediately\n" @@ -523,7 +531,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); con.reset_color(); } - auto scripts = listScripts(plug_mgr, getHackPath() + "scripts/"); + std::map scripts; + listScripts(plug_mgr, scripts, getHackPath() + "scripts/", all); if (!scripts.empty()) { con.print("\nscripts:\n"); From cb125f3d89382c4c075ea69d5178868c6e325938 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 29 Aug 2012 19:20:38 +0400 Subject: [PATCH 21/61] Add a script to fix population cap problems. --- scripts/devel/migrants-now.lua | 9 ++++++++ scripts/fix/population-cap.lua | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 scripts/devel/migrants-now.lua create mode 100644 scripts/fix/population-cap.lua diff --git a/scripts/devel/migrants-now.lua b/scripts/devel/migrants-now.lua new file mode 100644 index 000000000..8eb4b0c8f --- /dev/null +++ b/scripts/devel/migrants-now.lua @@ -0,0 +1,9 @@ +-- Force a migrants event in next 10 ticks. + +df.global.timed_events:insert('#',{ + new = true, + type = df.timed_event_type.Migrants, + season = df.global.cur_season, + season_ticks = df.global.cur_season_tick+1, + entity = df.historical_entity.find(df.global.ui.civ_id) +}) diff --git a/scripts/fix/population-cap.lua b/scripts/fix/population-cap.lua new file mode 100644 index 000000000..a34098c57 --- /dev/null +++ b/scripts/fix/population-cap.lua @@ -0,0 +1,40 @@ +-- Communicates current population to mountainhomes to avoid cap overshooting. + +-- The reason for population cap problems is that the population value it +-- is compared against comes from the last dwarven caravan that successfully +-- left for mountainhomes. This script instantly updates it. +-- Note that a migration wave can still overshoot the limit by 1-2 dwarves because +-- of the last migrant bringing his family. Likewise, king arrival ignores cap. + +local args = {...} + +local ui = df.global.ui +local ui_stats = ui.tasks +local civ = df.historical_entity.find(ui.civ_id) + +if not civ then + qerror('No active fortress.') +end + +local civ_stats = civ.activity_stats + +if not civ_stats then + if args[1] ~= 'force' then + qerror('No caravan report object; use "fix/population-cap force" to create one') + end + print('Creating an empty statistics structure...') + civ.activity_stats = { + new = true, + created_weapons = { resize = #ui_stats.created_weapons }, + known_creatures1 = { resize = #ui_stats.known_creatures1 }, + known_creatures = { resize = #ui_stats.known_creatures }, + known_plants1 = { resize = #ui_stats.known_plants1 }, + known_plants = { resize = #ui_stats.known_plants }, + } + civ_stats = civ.activity_stats +end + +-- Use max to keep at least some of the original caravan communication idea +civ_stats.population = math.max(civ_stats.population, ui_stats.population) + +print('Home civ notified about current population.') From 7676f07b06e4472211773e277d2767fe3940bd52 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 30 Aug 2012 09:46:09 -0500 Subject: [PATCH 22/61] Display creature graphics tiles beneath the column headers --- plugins/manipulator.cpp | 307 +++++++++++++++++++++------------------- 1 file changed, 159 insertions(+), 148 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 124dc768b..345050b90 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" @@ -76,168 +77,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::NONE, 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 +471,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++; } @@ -587,7 +588,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 +599,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 +628,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++) @@ -659,7 +670,7 @@ 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); } } From 1dee51abb02abe30647ed83f126ba986ce456f48 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 30 Aug 2012 19:01:43 +0400 Subject: [PATCH 23/61] Use generic DOCTOR tile for wound dresser labor. --- dfhack.init-example | 3 +++ plugins/manipulator.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/dfhack.init-example b/dfhack.init-example index 380bdd04f..d3a28b9b0 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -58,3 +58,6 @@ 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 diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 345050b90..023463c9f 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -110,7 +110,7 @@ const SkillColumn columns[] = { {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::NONE, unit_labor::DRESSING_WOUNDS, job_skill::DRESS_WOUNDS, "Dr"}, + {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 From c414aafc593acf7a4a2b6f38297667c6f06f7ede Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 30 Aug 2012 10:03:12 -0500 Subject: [PATCH 24/61] Add "Toggle Group" key --- plugins/manipulator.cpp | 45 ++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 023463c9f..3410b69e3 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -499,9 +499,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++) { @@ -511,7 +512,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)) @@ -675,6 +700,7 @@ void viewscreen_unitlaborsst::render() } UnitInfo *cur = units[sel_row]; + bool canToggle = false; if (cur != NULL) { df::unit *unit = cur->unit; @@ -717,22 +743,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; 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(10, x, gps->dimy - 3, "Esc"); // LEAVESCREEN key - OutputString(15, x, gps->dimy - 3, ": Done"); + OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); x = 1; + OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key + OutputString(15, x, gps->dimy - 2, ": Done, "); + 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, "); From 750eefe48ad3250cc51682508cc30e78accff7e1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 30 Aug 2012 19:28:53 +0400 Subject: [PATCH 25/61] Follow unconditional JMP chains in MSVC vmethod ptr detection. --- library/VTableInterpose.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 04c436ba7..079890fe4 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; From e6b2cb73af68eeff346baf02359df325c107bd7a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 30 Aug 2012 20:48:06 +0400 Subject: [PATCH 26/61] Add a tweak for pressure plate creature weight limit rendering. Print them as readable "???K", instead of the normal truncated numbers. --- plugins/tweak.cpp | 86 +++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 75 insertions(+), 11 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 591125c5e..fa99f39e5 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,8 @@ #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 @@ -40,6 +43,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 +83,8 @@ 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 wsize = Screen::getWindowSize(); + int x = wsize.x - MENU_WIDTH - 1; + if (*ui_menu_width == 1 || *ui_area_map_width == 2) + x -= AREA_MAP_WIDTH + 1; + + 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); + +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 +364,21 @@ 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 return CR_WRONG_USAGE; From 0f1be286378744903d20ea3b865e05adbe3f5765 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 00:17:08 +0400 Subject: [PATCH 27/61] Follow xml changes. --- library/modules/Buildings.cpp | 2 +- library/xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/xml b/library/xml index 328a8dbdc..9b3ded158 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 328a8dbdc7d9e1e838798abf79861cc18a387e3f +Subproject commit 9b3ded15848e830784ef2dc4dea6093175669bc9 From ece0833c931948a1b2f60611baa7ef9a42a9d68d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 00:22:55 +0400 Subject: [PATCH 28/61] Prototype steam engine workshop :) Very broken and incomplete still. --- plugins/devel/CMakeLists.txt | 1 + plugins/devel/building_zsteam_engine.txt | 86 ++++++ plugins/devel/reaction_zsteam_engine.txt | 12 + plugins/devel/steam-engine.cpp | 349 +++++++++++++++++++++++ 4 files changed, 448 insertions(+) create mode 100644 plugins/devel/building_zsteam_engine.txt create mode 100644 plugins/devel/reaction_zsteam_engine.txt create mode 100644 plugins/devel/steam-engine.cpp 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_zsteam_engine.txt b/plugins/devel/building_zsteam_engine.txt new file mode 100644 index 000000000..f76b237d5 --- /dev/null +++ b/plugins/devel/building_zsteam_engine.txt @@ -0,0 +1,86 @@ +building_zsteam_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:MAT:0:0:0:7:0:0] + [COLOR:0:2:0:0:0:0:0:0:7:0:0] + [COLOR:0:3:6:0:0:0:0:0:0:0:0] + [TILE:1:1:246:128:' '] + [TILE:1:2:' ':' ':254] + [TILE:1:3:254:240:240] + [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [COLOR:1:2:0:0:0:0:0:0:7:0:0] + [COLOR:1:3:7:0:0:MAT:MAT] + [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:6:0:0] + [COLOR:2:3:7:0:0:MAT:7:0:0] + [TILE:3:1:15:246:15] + [TILE:3:2:'\':19:'/'] + [TILE:3:3:7:' ':7] + [COLOR:3:1:6:0:0:6:0:0:6:0:0] + [COLOR:3:2:6:7:0:0:0:1:6:7: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:WEAPON:WEAPON_MACE: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:2: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:MAT:0:0:0:7:0:0] + [COLOR:0:2:0:0:0:0:0:0:7:0:0] + [COLOR:0:3:6:0:0:0:0:0:0:0:0] + [TILE:1:1:246:128:' '] + [TILE:1:2:' ':' ':254] + [TILE:1:3:254:240:240] + [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [COLOR:1:2:0:0:0:0:0:0:7:0:0] + [COLOR:1:3:7:0:0:MAT:MAT] + [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:6:0:0] + [COLOR:2:3:7:0:0:MAT:7:0:0] + [TILE:3:1:15:246:15] + [TILE:3:2:'\':19:'/'] + [TILE:3:3:7:' ':7] + [COLOR:3:1:6:0:0:6:0:0:6:0:0] + [COLOR:3:2:6:7:0:0:0:1:6:7: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:WEAPON:WEAPON_MACE: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:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE] diff --git a/plugins/devel/reaction_zsteam_engine.txt b/plugins/devel/reaction_zsteam_engine.txt new file mode 100644 index 000000000..b8267cf55 --- /dev/null +++ b/plugins/devel/reaction_zsteam_engine.txt @@ -0,0 +1,12 @@ +reaction_other + +[OBJECT:REACTION] + +[REACTION:STOKE_BOILER] + [NAME:stoke the boiler] + [BUILDING:STEAM_ENGINE:CUSTOM_S] + [BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S] + [FUEL] + [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:333] + [SKILL:SMELT] + diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp new file mode 100644 index 000000000..23af12177 --- /dev/null +++ b/plugins/devel/steam-engine.cpp @@ -0,0 +1,349 @@ +#include "Core.h" +#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 "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_build_selector; + +DFHACK_PLUGIN("steam-engine"); + +struct steam_engine_workshop { + int id; + df::building_def_workshopst *def; + std::vector gear_tiles; + df::coord2d hearth_tile; + df::coord2d water_tile; + df::coord2d magma_tile; +}; + +std::vector engines; + +struct workshop_hook : df::building_workshopst { + typedef df::building_workshopst interpose_base; + + steam_engine_workshop *get_steam_engine() + { + if (type == workshop_type::Custom) + for (size_t i = 0; i < engines.size(); i++) + if (engines[i].id == custom_type) + return &engines[i]; + + return NULL; + } + + int get_steam_amount() + { + int cnt = 0; + + for (size_t i = 0; i < contained_items.size(); i++) + { + if (contained_items[i]->use_mode == 0 && + contained_items[i]->item->flags.bits.in_building) + cnt++; + } + + return cnt; + } + + int get_power_output(steam_engine_workshop *engine) + { + int maxv = engine->def->needs_magma ? 5 : 3; + return std::min(get_steam_amount(), maxv)*100; + } + + df::item_liquid_miscst *collect_steam() + { + df::item_liquid_miscst *first = NULL; + + 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->dimension != 333 || + liquid->wear != 0) + continue; + + liquid->flags.bits.in_building = true; + } + + first = liquid; + } + + return first; + } + + DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ()) + { + if (get_steam_engine()) + return true; + + return INTERPOSE_NEXT(needsDesign)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info)) + { + if (auto engine = get_steam_engine()) + { + info->produced = get_power_output(engine); + info->consumed = 10; + 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++) + { + 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); + } + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (auto engine = get_steam_engine()) + { + int output = get_power_output(engine); + + if (auto first = collect_steam()) + { + if (first->incWearTimer(output)) + { + while (first->wear_timer >= 806400) + { + first->wear_timer -= 806400; + first->wear++; + } + + if (first->wear > 3) + { + first->flags.bits.in_building = 0; + first->temperature = first->getBoilingPoint()+50; + } + } + } + + int new_out = get_power_output(engine); + if (new_out != output) + { + auto mptr = df::machine::find(machine.machine_id); + if (mptr) + mptr->cur_power += (new_out - output); + } + } + + INTERPOSE_NEXT(updateAction)(); + } +}; + +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, updateAction); + +static void 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++) + { + if (ws.def->tile[bs][x][y] == 15) + ws.gear_tiles.push_back(df::coord2d(x,y)); + + if (ws.def->tile_color[2][bs][x][y]) + { + switch (ws.def->tile_color[0][bs][x][y]) + { + case 0: + ws.hearth_tile = df::coord2d(x,y); + break; + case 1: + ws.water_tile = df::coord2d(x,y); + break; + case 4: + ws.magma_tile = df::coord2d(x,y); + break; + default: + break; + } + } + } + } + + engines.push_back(ws); + } +} + +static void enable_hooks() +{ + INTERPOSE_HOOK(workshop_hook, needsDesign).apply(); + INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(); + INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(); + INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(); + INTERPOSE_HOOK(workshop_hook, categorize).apply(); + INTERPOSE_HOOK(workshop_hook, uncategorize).apply(); + INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(); + INTERPOSE_HOOK(workshop_hook, updateAction).apply(); +} + +static void disable_hooks() +{ + INTERPOSE_HOOK(workshop_hook, needsDesign).remove(); + INTERPOSE_HOOK(workshop_hook, getPowerInfo).remove(); + INTERPOSE_HOOK(workshop_hook, getMachineInfo).remove(); + INTERPOSE_HOOK(workshop_hook, isPowerSource).remove(); + INTERPOSE_HOOK(workshop_hook, categorize).remove(); + INTERPOSE_HOOK(workshop_hook, uncategorize).remove(); + INTERPOSE_HOOK(workshop_hook, canConnectToMachine).remove(); + INTERPOSE_HOOK(workshop_hook, updateAction).remove(); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + find_engines(); + if (!engines.empty()) + { + out.print("Detected steam engine workshops - enabling plugin.\n"); + enable_hooks(); + } + break; + case SC_MAP_UNLOADED: + disable_hooks(); + 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 ) +{ + disable_hooks(); + return CR_OK; +} From c68afdaad23ccb9e1c33ec118514c78e5376a72e Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 31 Aug 2012 20:35:35 -0500 Subject: [PATCH 29/61] Display command key helper for Manipulator on Unit List, various tweaks --- plugins/manipulator.cpp | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3410b69e3..1a90d2eea 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -39,8 +39,6 @@ using df::global::ui; using df::global::gps; using df::global::enabler; -DFHACK_PLUGIN("manipulator"); - struct SkillLevel { const char *name; @@ -668,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) @@ -747,7 +745,7 @@ void viewscreen_unitlaborsst::render() 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(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); @@ -760,7 +758,7 @@ void viewscreen_unitlaborsst::render() OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); - x = 1; + x = 2; OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key OutputString(15, x, gps->dimy - 2, ": Done, "); @@ -803,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; } From e0097d8d43013d72d729e2ae71cdd6a73a110d8d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 11:25:24 +0400 Subject: [PATCH 30/61] Fix access to unnamed bits in bitfields, and allow hook.apply(false) --- library/DataDefs.cpp | 2 +- library/LuaTypes.cpp | 4 ++-- library/VTableInterpose.cpp | 8 +++++++- library/include/VTableInterpose.h | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) 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/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 079890fe4..583ef5184 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -335,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/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(); }; From f158e1894d15c2d835f8ee8b65ce3b094b71e5f6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 11:29:05 +0400 Subject: [PATCH 31/61] Further work on steam engine. - Display water inside as 'boiling' by hooking item_liquid_miscst. - Store current power in flags to avoid mess if items disappear etc. - Suspend/unsuspend stoke jobs depending on steam level. - Implement intelligent steam use rate and boiler capacity cap. - Modify appearance of special tiles to display status. --- plugins/devel/building_zsteam_engine.txt | 2 + plugins/devel/reaction_zsteam_engine.txt | 3 +- plugins/devel/steam-engine.cpp | 244 +++++++++++++++++------ 3 files changed, 192 insertions(+), 57 deletions(-) diff --git a/plugins/devel/building_zsteam_engine.txt b/plugins/devel/building_zsteam_engine.txt index f76b237d5..572eb4074 100644 --- a/plugins/devel/building_zsteam_engine.txt +++ b/plugins/devel/building_zsteam_engine.txt @@ -30,9 +30,11 @@ building_zsteam_engine [COLOR:2:1:6:0:0:0:0:0:7:0:0] [COLOR:2:2:7:0:0:0:0:0:6:0:0] [COLOR:2:3:7:0:0:MAT:7:0:0] + Tile 15 marks places where machines can connect: [TILE:3:1:15:246:15] [TILE:3:2:'\':19:'/'] [TILE:3:3:7:' ':7] + Color 0:?:1 marks hearth, 1:?:1 water indicator, 4:?:1 magma indicator: [COLOR:3:1:6:0:0:6:0:0:6:0:0] [COLOR:3:2:6:7:0:0:0:1:6:7:0] [COLOR:3:3:1:7:1:0:0:0:4:7:1] diff --git a/plugins/devel/reaction_zsteam_engine.txt b/plugins/devel/reaction_zsteam_engine.txt index b8267cf55..1018510f4 100644 --- a/plugins/devel/reaction_zsteam_engine.txt +++ b/plugins/devel/reaction_zsteam_engine.txt @@ -7,6 +7,7 @@ reaction_other [BUILDING:STEAM_ENGINE:CUSTOM_S] [BUILDING:MAGMA_STEAM_ENGINE:CUSTOM_S] [FUEL] - [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:333] [SKILL:SMELT] + Dimension is the number of days it can produce 100 power * 100. + [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:1500] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 23af12177..5a26fb246 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -22,6 +22,8 @@ #include "df/world.h" #include "df/buildings_other_id.h" #include "df/machine.h" +#include "df/job.h" +#include "df/building_drawbuffer.h" #include "MiscUtils.h" @@ -40,6 +42,8 @@ DFHACK_PLUGIN("steam-engine"); struct steam_engine_workshop { int id; df::building_def_workshopst *def; + bool is_magma; + int max_power, max_capacity; std::vector gear_tiles; df::coord2d hearth_tile; df::coord2d water_tile; @@ -48,42 +52,94 @@ struct steam_engine_workshop { 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; +} + +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 } +}; + +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); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(liquid_hook, getItemDescription); + struct workshop_hook : df::building_workshopst { typedef df::building_workshopst interpose_base; steam_engine_workshop *get_steam_engine() { if (type == workshop_type::Custom) - for (size_t i = 0; i < engines.size(); i++) - if (engines[i].id == custom_type) - return &engines[i]; + return find_steam_engine(custom_type); return NULL; } + // Use high bits of flags to store current steam amount. + // This is necessary for consistency if items disappear unexpectedly. + int get_steam_amount() { - int cnt = 0; + return (flags.whole >> 28) & 15; + } - for (size_t i = 0; i < contained_items.size(); i++) - { - if (contained_items[i]->use_mode == 0 && - contained_items[i]->item->flags.bits.in_building) - cnt++; - } + void set_steam_amount(int count) + { + flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28); + } + + void absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) + { + liquid->flags.bits.in_building = true; + liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; - return cnt; + // 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); } - int get_power_output(steam_engine_workshop *engine) + bool boil_unit(df::item_liquid_miscst *liquid) { - int maxv = engine->def->needs_magma ? 5 : 3; - return std::min(get_steam_amount(), maxv)*100; + 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; } - df::item_liquid_miscst *collect_steam() + 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--) { @@ -98,19 +154,54 @@ struct workshop_hook : df::building_workshopst { if (!liquid->flags.bits.in_building) { if (liquid->mat_type != builtin_mats::WATER || - liquid->dimension != 333 || + liquid->age > 1 || liquid->wear != 0) continue; - liquid->flags.bits.in_building = true; + absorb_unit(engine, liquid); } - first = liquid; + if (*count < engine->max_capacity) + { + first = liquid; + ++*count; + } + else + { + // Overpressure valve + boil_unit(liquid); + } } return first; } + static const int WEAR_TICKS = 806400; + + 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; + } + // apply rate; 10% steam is wasted anyway + ticks *= (0.1f + 0.9f*power_rate)*power_level; + // end result + return std::max(1, int(ticks)); + } + + // Furnaces need architecture, and this is a workshop + // only because furnaces cannot connect to machines. DEFINE_VMETHOD_INTERPOSE(bool, needsDesign, ()) { if (get_steam_engine()) @@ -119,11 +210,12 @@ struct workshop_hook : df::building_workshopst { return INTERPOSE_NEXT(needsDesign)(); } + // Machine interface DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info)) { if (auto engine = get_steam_engine()) { - info->produced = get_power_output(engine); + info->produced = std::min(engine->max_power, get_steam_amount())*100; info->consumed = 10; return; } @@ -178,6 +270,7 @@ struct workshop_hook : df::building_workshopst { 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; @@ -199,37 +292,76 @@ struct workshop_hook : df::building_workshopst { { if (auto engine = get_steam_engine()) { - int output = get_power_output(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()) + if (auto first = collect_steam(engine, &cur_count)) { - if (first->incWearTimer(output)) + int rate = get_steam_use_rate(engine, first->dimension, old_power); + + if (first->incWearTimer(rate)) { - while (first->wear_timer >= 806400) + while (first->wear_timer >= WEAR_TICKS) { - first->wear_timer -= 806400; + first->wear_timer -= WEAR_TICKS; first->wear++; } if (first->wear > 3) { - first->flags.bits.in_building = 0; - first->temperature = first->getBoilingPoint()+50; + boil_unit(first); + cur_count--; } } } - int new_out = get_power_output(engine); - if (new_out != output) + 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 += (new_out - output); + mptr->cur_power += (cur_power - old_power)*100; } } 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 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]; + } + } + } }; IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign); @@ -240,8 +372,9 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, categorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); -static void find_engines() +static bool find_engines() { engines.clear(); @@ -284,47 +417,46 @@ static void find_engines() } } - engines.push_back(ws); + ws.is_magma = ws.def->needs_magma; + ws.max_power = ws.is_magma ? 5 : 3; + ws.max_capacity = ws.is_magma ? 10 : 6; + + if (!ws.gear_tiles.empty()) + engines.push_back(ws); } -} -static void enable_hooks() -{ - INTERPOSE_HOOK(workshop_hook, needsDesign).apply(); - INTERPOSE_HOOK(workshop_hook, getPowerInfo).apply(); - INTERPOSE_HOOK(workshop_hook, getMachineInfo).apply(); - INTERPOSE_HOOK(workshop_hook, isPowerSource).apply(); - INTERPOSE_HOOK(workshop_hook, categorize).apply(); - INTERPOSE_HOOK(workshop_hook, uncategorize).apply(); - INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(); - INTERPOSE_HOOK(workshop_hook, updateAction).apply(); + return !engines.empty(); } -static void disable_hooks() +static void enable_hooks(bool enable) { - INTERPOSE_HOOK(workshop_hook, needsDesign).remove(); - INTERPOSE_HOOK(workshop_hook, getPowerInfo).remove(); - INTERPOSE_HOOK(workshop_hook, getMachineInfo).remove(); - INTERPOSE_HOOK(workshop_hook, isPowerSource).remove(); - INTERPOSE_HOOK(workshop_hook, categorize).remove(); - INTERPOSE_HOOK(workshop_hook, uncategorize).remove(); - INTERPOSE_HOOK(workshop_hook, canConnectToMachine).remove(); - INTERPOSE_HOOK(workshop_hook, updateAction).remove(); + INTERPOSE_HOOK(liquid_hook, getItemDescription).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, updateAction).apply(enable); + INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable); } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { case SC_MAP_LOADED: - find_engines(); - if (!engines.empty()) + if (find_engines()) { out.print("Detected steam engine workshops - enabling plugin.\n"); - enable_hooks(); + enable_hooks(true); } + else + enable_hooks(false); break; case SC_MAP_UNLOADED: - disable_hooks(); + enable_hooks(false); engines.clear(); break; default: @@ -344,6 +476,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sat, 1 Sep 2012 14:42:19 +0400 Subject: [PATCH 32/61] Try preventing "boiling water" from freezing, and dump steam on destroy. --- plugins/devel/steam-engine.cpp | 54 ++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 5a26fb246..6f83c41d9 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -82,9 +82,27 @@ struct liquid_hook : df::item_liquid_miscst { 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); struct workshop_hook : df::building_workshopst { typedef df::building_workshopst interpose_base; @@ -114,6 +132,8 @@ struct workshop_hook : df::building_workshopst { { 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()) @@ -176,6 +196,28 @@ struct workshop_hook : df::building_workshopst { 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 || random() < RAND_MAX/2) + { + cnt++; + boil_unit(liquid); + } + } + } + static const int WEAR_TICKS = 806400; int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) @@ -362,6 +404,14 @@ struct workshop_hook : df::building_workshopst { } } } + + DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost)) + { + if (get_steam_engine()) + random_boil(); + + INTERPOSE_NEXT(deconstructItems)(noscatter, lost); + } }; IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, needsDesign); @@ -373,6 +423,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, uncategorize); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, canConnectToMachine); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, updateAction); IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, drawBuilding); +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, deconstructItems); static bool find_engines() { @@ -431,6 +482,8 @@ static bool find_engines() 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); @@ -441,6 +494,7 @@ static void enable_hooks(bool enable) INTERPOSE_HOOK(workshop_hook, canConnectToMachine).apply(enable); INTERPOSE_HOOK(workshop_hook, updateAction).apply(enable); INTERPOSE_HOOK(workshop_hook, drawBuilding).apply(enable); + INTERPOSE_HOOK(workshop_hook, deconstructItems).apply(enable); } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) From bae85ac77d977ce543fc2e36f42aed3a10f79a5c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 17:52:51 +0400 Subject: [PATCH 33/61] Make the steam engine consume liquids from Z level below. --- plugins/devel/steam-engine.cpp | 195 ++++++++++++++++++++++++++++++++- 1 file changed, 193 insertions(+), 2 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 6f83c41d9..1f45e511a 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include #include @@ -24,6 +26,9 @@ #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 "MiscUtils.h" @@ -35,6 +40,7 @@ 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"); @@ -70,6 +76,29 @@ static const int hearth_colors[6][2] = { { 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, 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); +} + struct liquid_hook : df::item_liquid_miscst { typedef df::item_liquid_miscst interpose_base; @@ -128,8 +157,65 @@ struct workshop_hook : df::building_workshopst { flags.whole = (flags.whole & 0x0FFFFFFFU) | uint32_t((count & 15) << 28); } - void absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) + bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_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 == 0) + continue; + + if (pldes->bits.liquid_type == tile_liquid::Magma) + { + if (!pmagma || (!any_level && pldes->bits.flow_size < 4)) + continue; + + *pmagma = df::coord(x,y,z-1); + if (pwater->isValid()) + return true; + } + else + { + if (!any_level && pldes->bits.flow_size < 3) + continue; + + *pwater = df::coord(x,y,z-1); + if (!pmagma || pmagma->isValid()) + return true; + } + } + } + + return false; + } + + bool absorb_unit(steam_engine_workshop *engine, df::item_liquid_miscst *liquid) { + df::coord water, magma; + + if (!find_liquids(&water, &magma, engine->is_magma, true)) + { + liquid->addWear(WEAR_TICKS*4+1, true, false); + return false; + } + + decrement_flow(water, 1); + if (engine->is_magma) + decrement_flow(magma, 1); + liquid->flags.bits.in_building = true; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; liquid->temperature = liquid->getBoilingPoint()-1; @@ -138,6 +224,9 @@ struct workshop_hook : df::building_workshopst { // 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_updates_at(liquid->pos, false, true); + return true; } bool boil_unit(df::item_liquid_miscst *liquid) @@ -178,7 +267,8 @@ struct workshop_hook : df::building_workshopst { liquid->wear != 0) continue; - absorb_unit(engine, liquid); + if (!absorb_unit(engine, liquid)) + continue; } if (*count < engine->max_capacity) @@ -330,6 +420,18 @@ struct workshop_hook : df::building_workshopst { 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, false); + } + + return INTERPOSE_NEXT(isUnpowered)(); + } + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) { if (auto engine = get_steam_engine()) @@ -402,6 +504,18 @@ struct workshop_hook : df::building_workshopst { 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, false); + + if (engine->water_tile.isValid() && !water.isValid()) + db->fore[engine->water_tile.x][engine->water_tile.y] = 0; + if (engine->magma_tile.isValid() && engine->is_magma && !magma.isValid()) + db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0; + } } } @@ -421,10 +535,84 @@ 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); +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 - use down stair."; + 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 + check_hanging_tiles(get_steam_engine()); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); + static bool find_engines() { engines.clear(); @@ -492,9 +680,12 @@ static void enable_hooks(bool 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) From 8536785d1d8b1a87e34e35de36064dd5d9b32333 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Sep 2012 20:46:34 +0400 Subject: [PATCH 34/61] Boilers made out of unsuitable materials should explode! --- library/xml | 2 +- plugins/devel/steam-engine.cpp | 84 ++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 9b3ded158..df8178a98 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 9b3ded15848e830784ef2dc4dea6093175669bc9 +Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 1f45e511a..2ff12ad42 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -29,6 +29,8 @@ #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" @@ -50,6 +52,7 @@ struct steam_engine_workshop { df::building_def_workshopst *def; bool is_magma; int max_power, max_capacity; + int wear_temp; std::vector gear_tiles; df::coord2d hearth_tile; df::coord2d water_tile; @@ -99,6 +102,26 @@ void decrement_flow(df::coord pos, int amount) enable_updates_at(pos, true, false); } +bool make_explosion(df::coord pos, int mat_type, int mat_index, int density) +{ + using df::global::flows; + + auto block = Maps::getTileBlock(pos); + if (!flows || !block) + return false; + + auto flow = new df::flow_info(); + flow->type = flow_type::MaterialDust; + 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 true; +} + struct liquid_hook : df::item_liquid_miscst { typedef df::item_liquid_miscst interpose_base; @@ -308,6 +331,60 @@ struct workshop_hook : df::building_workshopst { } } + void explode() + { + int mat_type = builtin_mats::ASH, mat_index = -1; + int cx = (x1+x2)/2, cy = (y1+y2)/2; + int power = std::min(240, get_steam_amount()*80); + + make_explosion(df::coord(cx, cy, z), mat_type, mat_index, power); + make_explosion(df::coord(cx-1, cy, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx, cy-1, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx+1, cy, z), mat_type, mat_index, power/3); + make_explosion(df::coord(cx, cy+1, z), mat_type, mat_index, power/3); + + *df::global::pause_state = true; + + Gui::showAnnouncement("A boiler has exploded!", COLOR_RED, true); + auto ann = world->status.announcements.back(); + ann->type = announcement_type::CAVE_COLLAPSE; + ann->pos = df::coord(cx, cy, z); + } + + bool check_component_wear(steam_engine_workshop *engine, int count, int power) + { + for (int i = contained_items.size()-1; i >= 0; i--) + { + auto item = contained_items[i]; + if (item->use_mode != 2) + continue; + + int melt_temp = item->item->getMeltingPoint(); + if (melt_temp >= engine->wear_temp) + continue; + if (item->item->isBuildMat()) + continue; + + auto type = item->item->getType(); + if (type == item_type::TRAPPARTS || item_type::CHAIN) + continue; + + int coeff = power; + if (type == item_type::BARREL) + coeff = count; + + int ticks = coeff*(engine->wear_temp - melt_temp); + + if (item->item->addWear(ticks, true, true)) + { + explode(); + return true; + } + } + + return false; + } + static const int WEAR_TICKS = 806400; int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) @@ -458,6 +535,9 @@ struct workshop_hook : df::building_workshopst { cur_count--; } } + + if (check_component_wear(engine, old_count, old_power)) + return; } if (old_count < engine->max_capacity && cur_count == engine->max_capacity) @@ -524,6 +604,9 @@ struct workshop_hook : df::building_workshopst { if (get_steam_engine()) random_boil(); + if (lost) + explode(); + INTERPOSE_NEXT(deconstructItems)(noscatter, lost); } }; @@ -659,6 +742,7 @@ static bool find_engines() 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); From febfc9aa5b87928432060c9c55f2710a2a6201cc Mon Sep 17 00:00:00 2001 From: warmist Date: Sat, 1 Sep 2012 23:33:49 +0300 Subject: [PATCH 35/61] Fixed bug with gui/mechanism Fixes script not allowing to ran on e.g. levers (focus string: dwarfmode/QueryBuilding/Some/Lever/Empty ) --- scripts/gui/mechanisms.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 3713c5ea9eaab262be1d08a92781f62bbb6cf95a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 14:10:58 +0400 Subject: [PATCH 36/61] Add some APIs required by steam engine to the core. --- LUA_API.rst | 19 +++++ Lua API.html | 14 +++ dfhack.init-example | 3 + library/LuaApi.cpp | 12 +++ library/include/modules/Gui.h | 19 +++++ library/include/modules/Maps.h | 6 ++ library/modules/Gui.cpp | 151 ++++++++++++++++++++++++++++++++- library/modules/Maps.cpp | 34 +++++++- plugins/tweak.cpp | 9 +- 9 files changed, 255 insertions(+), 12 deletions(-) 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 d3a28b9b0..9ee5ecc46 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -61,3 +61,6 @@ 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 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/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/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/plugins/tweak.cpp b/plugins/tweak.cpp index fa99f39e5..9853f7f95 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -211,9 +211,6 @@ struct patrol_duty_hook : df::squad_order_trainst IMPLEMENT_VMETHOD_INTERPOSE(patrol_duty_hook, isPatrol); -static const int AREA_MAP_WIDTH = 23; -static const int MENU_WIDTH = 30; - struct readable_build_plate_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; @@ -228,10 +225,8 @@ struct readable_build_plate_hook : df::viewscreen_dwarfmodest ui_build_selector->building_subtype == trap_type::PressurePlate && ui_build_selector->plate_info.flags.bits.units) { - auto wsize = Screen::getWindowSize(); - int x = wsize.x - MENU_WIDTH - 1; - if (*ui_menu_width == 1 || *ui_area_map_width == 2) - x -= AREA_MAP_WIDTH + 1; + auto dims = Gui::getDwarfmodeViewDims(); + int x = dims.menu_x1; Screen::Pen pen(' ',COLOR_WHITE); From 9c3843c1d48c55b718fb1a770340f05170590b38 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 14:59:13 +0400 Subject: [PATCH 37/61] Use new API in steam engine; always explode if destroyed with steam inside. --- plugins/devel/steam-engine.cpp | 287 ++++++++++++++++++++++----------- 1 file changed, 189 insertions(+), 98 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 2ff12ad42..ac317687e 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -47,12 +47,18 @@ 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; @@ -70,6 +76,10 @@ steam_engine_workshop *find_steam_engine(int id) return NULL; } +/* + * Misc utilities. + */ + static const int hearth_colors[6][2] = { { COLOR_BLACK, 1 }, { COLOR_BROWN, 0 }, @@ -102,26 +112,57 @@ void decrement_flow(df::coord pos, int amount) enable_updates_at(pos, true, false); } -bool make_explosion(df::coord pos, int mat_type, int mat_index, int density) +void make_explosion(df::coord center, int power) { - using df::global::flows; + static const int bias[9] = { + 60, 30, 60, + 30, 0, 30, + 60, 30, 60 + }; - auto block = Maps::getTileBlock(pos); - if (!flows || !block) - return false; + 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; - auto flow = new df::flow_info(); - flow->type = flow_type::MaterialDust; - flow->mat_type = mat_type; - flow->mat_index = mat_index; - flow->density = std::min(100, density); - flow->pos = pos; +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++; + } + } - block->flows.push_back(flow); - flows->push_back(flow); - return true; + 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; @@ -156,9 +197,15 @@ 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) @@ -167,6 +214,11 @@ struct workshop_hook : df::building_workshopst { 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. @@ -180,6 +232,8 @@ struct workshop_hook : df::building_workshopst { 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, bool any_level) { if (!is_magma) @@ -225,13 +279,17 @@ struct workshop_hook : df::building_workshopst { 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, true)) { - liquid->addWear(WEAR_TICKS*4+1, true, false); + // Destroy the item with enormous wear amount. + liquid->addWear(WEAR_TICKS*5, true, false); return false; } @@ -239,6 +297,7 @@ struct workshop_hook : df::building_workshopst { 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; @@ -248,6 +307,7 @@ struct workshop_hook : df::building_workshopst { 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; } @@ -268,6 +328,8 @@ struct workshop_hook : df::building_workshopst { 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; @@ -290,6 +352,7 @@ struct workshop_hook : df::building_workshopst { liquid->wear != 0) continue; + // This may destroy the item if (!absorb_unit(engine, liquid)) continue; } @@ -303,6 +366,7 @@ struct workshop_hook : df::building_workshopst { { // Overpressure valve boil_unit(liquid); + suspend_jobs(true); } } @@ -331,62 +395,47 @@ struct workshop_hook : df::building_workshopst { } } - void explode() + int classify_component(df::building_actual::T_contained_items *item) { - int mat_type = builtin_mats::ASH, mat_index = -1; - int cx = (x1+x2)/2, cy = (y1+y2)/2; - int power = std::min(240, get_steam_amount()*80); + if (item->use_mode != 2 || item->item->isBuildMat()) + return -1; - make_explosion(df::coord(cx, cy, z), mat_type, mat_index, power); - make_explosion(df::coord(cx-1, cy, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx, cy-1, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx+1, cy, z), mat_type, mat_index, power/3); - make_explosion(df::coord(cx, cy+1, z), mat_type, mat_index, power/3); - - *df::global::pause_state = true; - - Gui::showAnnouncement("A boiler has exploded!", COLOR_RED, true); - auto ann = world->status.announcements.back(); - ann->type = announcement_type::CAVE_COLLAPSE; - ann->pos = df::coord(cx, cy, z); + 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--) { - auto item = contained_items[i]; - if (item->use_mode != 2) - continue; - - int melt_temp = item->item->getMeltingPoint(); - if (melt_temp >= engine->wear_temp) - continue; - if (item->item->isBuildMat()) + int type = classify_component(contained_items[i]); + if (type < 0) continue; - auto type = item->item->getType(); - if (type == item_type::TRAPPARTS || item_type::CHAIN) + df::item *item = contained_items[i]->item; + int melt_temp = item->getMeltingPoint(); + if (coeffs[type] == 0 || melt_temp >= engine->wear_temp) continue; - int coeff = power; - if (type == item_type::BARREL) - coeff = count; - - int ticks = coeff*(engine->wear_temp - melt_temp); - - if (item->item->addWear(ticks, true, true)) - { - explode(); + // 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; } - static const int WEAR_TICKS = 806400; - int get_steam_use_rate(steam_engine_workshop *engine, int dimension, int power_level) { // total ticks to wear off completely @@ -409,6 +458,61 @@ struct workshop_hook : df::building_workshopst { 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, ()) @@ -513,47 +617,13 @@ struct workshop_hook : df::building_workshopst { { if (auto engine = get_steam_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 (first->incWearTimer(rate)) - { - while (first->wear_timer >= WEAR_TICKS) - { - first->wear_timer -= WEAR_TICKS; - first->wear++; - } - - if (first->wear > 3) - { - 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); + if (is_fully_built()) + update_working(engine); + else + update_under_construction(engine); - 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; - } + if (flags.bits.almost_deleted) + return; } INTERPOSE_NEXT(updateAction)(); @@ -565,6 +635,9 @@ struct workshop_hook : df::building_workshopst { 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) @@ -602,10 +675,18 @@ struct workshop_hook : df::building_workshopst { DEFINE_VMETHOD_INTERPOSE(void, deconstructItems, (bool noscatter, bool lost)) { if (get_steam_engine()) - random_boil(); + { + // 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 + ); - if (lost) - explode(); + random_boil(); + } + } INTERPOSE_NEXT(deconstructItems)(noscatter, lost); } @@ -623,6 +704,11 @@ 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; @@ -669,7 +755,7 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest if (error) { - const char *msg = "Hanging - use down stair."; + const char *msg = "Hanging - cover channels with down stairs."; ui_build_selector->errors.push_back(new std::string(msg)); } } @@ -689,13 +775,18 @@ struct dwarfmode_hook : df::viewscreen_dwarfmodest if (engine) engine->def->needs_magma = engine->is_magma; - // And now, check for open space + // 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(); From 67630776eedc096f4386b515a6d856602d070f93 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:17:18 +0400 Subject: [PATCH 38/61] Tweak steam engine raws. --- ...m_engine.txt => building_steam_engine.txt} | 54 ++++++++++--------- plugins/devel/item_trapcomp_steam_engine.txt | 12 +++++ ...m_engine.txt => reaction_steam_engine.txt} | 5 +- plugins/devel/steam-engine.cpp | 14 ++--- 4 files changed, 52 insertions(+), 33 deletions(-) rename plugins/devel/{building_zsteam_engine.txt => building_steam_engine.txt} (57%) create mode 100644 plugins/devel/item_trapcomp_steam_engine.txt rename plugins/devel/{reaction_zsteam_engine.txt => reaction_steam_engine.txt} (57%) diff --git a/plugins/devel/building_zsteam_engine.txt b/plugins/devel/building_steam_engine.txt similarity index 57% rename from plugins/devel/building_zsteam_engine.txt rename to plugins/devel/building_steam_engine.txt index 572eb4074..48657b0c1 100644 --- a/plugins/devel/building_zsteam_engine.txt +++ b/plugins/devel/building_steam_engine.txt @@ -1,4 +1,4 @@ -building_zsteam_engine +building_steam_engine [OBJECT:BUILDING] @@ -15,35 +15,36 @@ building_zsteam_engine [TILE:0:1:240:' ':254] [TILE:0:2:' ':' ':128] [TILE:0:3:246:' ':' '] - [COLOR:0:1:MAT:0:0:0:7:0:0] + [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:6:0:0:0:0:0:0: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:240] - [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [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:MAT:MAT] + [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:6:0:0] - [COLOR:2:3:7:0:0:MAT:7:0:0] - Tile 15 marks places where machines can connect: + [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 0:?:1 marks hearth, 1:?:1 water indicator, 4:?:1 magma indicator: - [COLOR:3:1:6:0:0:6:0:0:6:0:0] - [COLOR:3:2:6:7:0:0:0:1:6:7:0] + 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:WEAPON:WEAPON_MACE: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:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE] + [BUILD_ITEM:1:BLOCKS:NONE:NONE:NONE][BUILDMAT][FIRE_BUILD_SAFE] [BUILDING_WORKSHOP:MAGMA_STEAM_ENGINE] [NAME:Magma Steam Engine] @@ -59,30 +60,33 @@ building_zsteam_engine [TILE:0:1:240:' ':254] [TILE:0:2:' ':' ':128] [TILE:0:3:246:' ':' '] - [COLOR:0:1:MAT:0:0:0:7:0:0] + [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:6:0:0:0:0:0:0: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:240] - [COLOR:1:1:6:0:0:7:0:0:0:0:0] + [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:MAT:MAT] + [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:6:0:0] - [COLOR:2:3:7:0:0:MAT: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:3:1:6:0:0:6:0:0:6:0:0] - [COLOR:3:2:6:7:0:0:0:1:6:7:0] + 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:WEAPON:WEAPON_MACE: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:2:BLOCKS:NONE:NONE:NONE][BUILDMAT][MAGMA_BUILD_SAFE] + [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_zsteam_engine.txt b/plugins/devel/reaction_steam_engine.txt similarity index 57% rename from plugins/devel/reaction_zsteam_engine.txt rename to plugins/devel/reaction_steam_engine.txt index 1018510f4..175ffdd50 100644 --- a/plugins/devel/reaction_zsteam_engine.txt +++ b/plugins/devel/reaction_steam_engine.txt @@ -1,4 +1,4 @@ -reaction_other +reaction_steam_engine [OBJECT:REACTION] @@ -9,5 +9,6 @@ reaction_other [FUEL] [SKILL:SMELT] Dimension is the number of days it can produce 100 power * 100. - [PRODUCT:100:1:LIQUID_MISC:NONE:WATER][PRODUCT_DIMENSION:1500] + 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 index ac317687e..edaba0e82 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -807,24 +807,26 @@ static bool find_engines() { for (int y = 0; y < ws.def->dim_y; y++) { - if (ws.def->tile[bs][x][y] == 15) + 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 0: - ws.hearth_tile = df::coord2d(x,y); - break; case 1: ws.water_tile = df::coord2d(x,y); break; case 4: ws.magma_tile = df::coord2d(x,y); break; - default: - break; } } } From 3b8e3d1459edb9fbf8df4b6055023b0d0905a15f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:18:01 +0400 Subject: [PATCH 39/61] Fix wrong assumptions in lua wrapper for BitArray. --- library/include/BitArray.h | 4 ++-- library/include/DataIdentity.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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; } From 3b08ee44d1560f7610187fb1d428fefaa37ed9ab Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 17:32:44 +0400 Subject: [PATCH 40/61] Vary the internal power consumption of the engine depending on quality. --- plugins/devel/steam-engine.cpp | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index edaba0e82..7a67eadd7 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -436,6 +436,23 @@ struct workshop_hook : df::building_workshopst { 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 @@ -452,8 +469,10 @@ struct workshop_hook : df::building_workshopst { else power_rate = 0.0f; } - // apply rate; 10% steam is wasted anyway - ticks *= (0.1f + 0.9f*power_rate)*power_level; + // 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)); } @@ -529,7 +548,7 @@ struct workshop_hook : df::building_workshopst { if (auto engine = get_steam_engine()) { info->produced = std::min(engine->max_power, get_steam_amount())*100; - info->consumed = 10; + info->consumed = 10 - int(get_component_quality(0)); return; } From 2249cb14fab6fd778ef8e6502cbd749d2f4bfc14 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Sep 2012 18:57:10 +0400 Subject: [PATCH 41/61] Require level 3 both for water & magma, and indicate level 5 by brightness. --- plugins/devel/steam-engine.cpp | 39 ++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 7a67eadd7..ef708d11f 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -234,7 +234,7 @@ struct workshop_hook : df::building_workshopst { // Find liquids to consume below the engine. - bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, bool any_level) + bool find_liquids(df::coord *pwater, df::coord *pmagma, bool is_magma, int min_level) { if (!is_magma) pmagma = NULL; @@ -252,23 +252,18 @@ struct workshop_hook : df::building_workshopst { continue; auto pldes = Maps::getTileDesignation(x,y,z-1); - if (!pldes || pldes->bits.flow_size == 0) + if (!pldes || pldes->bits.flow_size < min_level) continue; if (pldes->bits.liquid_type == tile_liquid::Magma) { - if (!pmagma || (!any_level && pldes->bits.flow_size < 4)) - continue; - - *pmagma = df::coord(x,y,z-1); + if (pmagma) + *pmagma = df::coord(x,y,z-1); if (pwater->isValid()) return true; } else { - if (!any_level && pldes->bits.flow_size < 3) - continue; - *pwater = df::coord(x,y,z-1); if (!pmagma || pmagma->isValid()) return true; @@ -286,7 +281,7 @@ struct workshop_hook : df::building_workshopst { // Consume liquid inputs df::coord water, magma; - if (!find_liquids(&water, &magma, engine->is_magma, true)) + if (!find_liquids(&water, &magma, engine->is_magma, 1)) { // Destroy the item with enormous wear amount. liquid->addWear(WEAR_TICKS*5, true, false); @@ -626,7 +621,7 @@ struct workshop_hook : df::building_workshopst { if (auto engine = get_steam_engine()) { df::coord water, magma; - return !find_liquids(&water, &magma, engine->is_magma, false); + return !find_liquids(&water, &magma, engine->is_magma, 3); } return INTERPOSE_NEXT(isUnpowered)(); @@ -681,12 +676,24 @@ struct workshop_hook : df::building_workshopst { if (engine->water_tile.isValid() || engine->magma_tile.isValid()) { df::coord water, magma; - find_liquids(&water, &magma, engine->is_magma, false); + 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() && !water.isValid()) - db->fore[engine->water_tile.x][engine->water_tile.y] = 0; - if (engine->magma_tile.isValid() && engine->is_magma && !magma.isValid()) - db->fore[engine->magma_tile.x][engine->magma_tile.y] = 0; + 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; + } } } } From 1618ccf5bb4044cdde97d45ef9e6173150cdddbc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 3 Sep 2012 10:28:17 +0400 Subject: [PATCH 42/61] Fix steam-engine build on msvc, and add a script for listing mem ranges. --- plugins/devel/steam-engine.cpp | 4 ++-- scripts/devel/lsmem.lua | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 scripts/devel/lsmem.lua diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index ef708d11f..5c344f788 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -105,7 +105,7 @@ void decrement_flow(df::coord pos, int amount) auto pldes = Maps::getTileDesignation(pos); if (!pldes) return; - int nsize = std::max(0, pldes->bits.flow_size - amount); + 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); @@ -382,7 +382,7 @@ struct workshop_hook : df::building_workshopst { if (!liquid) continue; - if (cnt == 0 || random() < RAND_MAX/2) + if (cnt == 0 || rand() < RAND_MAX/2) { cnt++; boil_unit(liquid); 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 From aa449a2180144961308e1e357c013724921175b3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 3 Sep 2012 21:11:35 +0400 Subject: [PATCH 43/61] Add a tweak to work around the endless temperature update bug. This obsoletes fix/stable-temp.lua, which only fixes items once. --- dfhack.init-example | 3 +++ plugins/tweak.cpp | 55 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index 9ee5ecc46..39c0e61df 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -64,3 +64,6 @@ 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/plugins/tweak.cpp b/plugins/tweak.cpp index 9853f7f95..fbea30231 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -32,6 +32,8 @@ #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 @@ -85,6 +87,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector 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") @@ -375,6 +425,11 @@ static command_result tweak(color_ostream &out, vector ¶meters) 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; From 9c6fcee9a9b03b42648152a549fc019f05fff468 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 11:23:00 +0400 Subject: [PATCH 44/61] Add steam engine documentation, and use barrel quality in efficiency calc. --- plugins/devel/item_trapcomp_steam_engine.txt | 4 +- plugins/devel/steam-engine.cpp | 82 +++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/devel/item_trapcomp_steam_engine.txt index bae6f5b22..c35f6ef45 100644 --- a/plugins/devel/item_trapcomp_steam_engine.txt +++ b/plugins/devel/item_trapcomp_steam_engine.txt @@ -4,8 +4,8 @@ item_trapcomp_steam_engine [ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] [NAME:piston:pistons] -[ADJECTIVE:huge] -[SIZE:1600] +[ADJECTIVE:heavy] +[SIZE:1800] [HITS:1] [MATERIAL_SIZE:6] [METAL] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 5c344f788..cacfc6e16 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -34,6 +34,82 @@ #include "MiscUtils.h" +/* + * This plugin implements a steam engine workshop. It activates + * if there are any workshops in the raws with STEAM_ENGINE in + * their token, and provides the necessary behavior. + * + * Construction: + * + * The workshop needs water as its input, which it takes via a + * passable floor tile below it, like usual magma workshops do. + * The magma version also needs magma. + * + * ISSUE: Since this building is a machine, and machine collapse + * code cannot be modified, it would collapse over true open space. + * As a loophole, down stair provides support to machines, while + * being passable, so use them. + * + * After constructing the building itself, machines can be connected + * to the edge tiles that look like gear boxes. Their exact position + * is extracted from the workshop raws. + * + * ISSUE: Like with collapse above, part of the code involved in + * machine connection cannot be modified. As a result, the workshop + * can only immediately connect to machine components built AFTER it. + * This also means that engines cannot be chained without intermediate + * short axles that can be built later. + * + * Operation: + * + * In order to operate the engine, queue the Stoke Boiler job. + * A furnace operator will come, possibly bringing a bar of fuel, + * and perform it. As a result, a "boiling water" item will appear + * in the 't' view of the workshop. + * + * Note: The completion of the job will actually consume one unit + * of appropriate liquids from below the workshop. + * + * Every such item gives 100 power, up to a limit of 300 for coal, + * and 500 for a magma engine. The building can host twice that + * amount of items to provide longer autonomous running. When the + * boiler gets filled to capacity, all queued jobs are suspended; + * once it drops back to 3+1 or 5+1 items, they are re-enabled. + * + * While the engine is providing power, steam is being consumed. + * The consumption speed includes a fixed 10% waste rate, and + * the remaining 90% are applied proportionally to the actual + * load in the machine. With the engine at nominal 300 power with + * 150 load in the system, it will consume steam for actual + * 300*(10% + 90%*150/300) = 165 power. + * + * Masterpiece mechanism and chain will decrease the mechanical + * power drawn by the engine itself from 10 to 5. Masterpiece + * barrel decreases waste rate by 4%. Masterpiece piston and pipe + * decrease it by further 4%, and also decrease the whole steam + * use rate by 10%. + * + * Explosions: + * + * The engine must be constructed using barrel, pipe and piston + * from fire-safe, or in the magma version magma-safe metals. + * + * During operation weak parts get gradually worn out, and + * eventually the engine explodes. It should also explode if + * toppled during operation by a building destroyer, or a + * tantruming dwarf. + * + * Save files: + * + * It should be safe to load and view fortresses using engines + * from a DF version without DFHack installed, except that in such + * case the engines won't work. However actually making modifications + * to them, or machines they connect to (including by pulling levers), + * can easily result in inconsistent state once this plugin is + * available again. The effects may be as weird as negative power + * being generated. + */ + using std::vector; using std::string; using std::stack; @@ -465,9 +541,11 @@ struct workshop_hook : df::building_workshopst { power_rate = 0.0f; } // waste rate: 1-10% depending on piston assembly quality - float waste = 0.1f - 0.015f * get_component_quality(1); + float piston_qual = get_component_quality(1); + float waste = 0.1f - 0.016f * 0.5f * (piston_qual + get_component_quality(2)); + float efficiency_coeff = 1.0f - 0.02f * piston_qual; // apply rate and waste factor - ticks *= (waste + 0.9f*power_rate)*power_level; + ticks *= (waste + 0.9f*power_rate)*power_level*efficiency_coeff; // end result return std::max(1, int(ticks)); } From 27f169e298e658f3957aa2db1f76fe8aa20caef7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 17:37:36 +0400 Subject: [PATCH 45/61] Provide a partial application utility function to lua. Implemented in C++ for efficiency. --- LUA_API.rst | 10 ++++ Lua API.html | 107 +++++++++++++++++++++++------------------ library/LuaTools.cpp | 34 +++++++++++++ library/lua/dfhack.lua | 8 ++- 4 files changed, 110 insertions(+), 49 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 542034f40..22130efd6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -550,6 +550,16 @@ Exception handling The default value of the ``verbose`` argument of ``err:tostring()``. +Miscellaneous +------------- + +* ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` + + Returns a closure that invokes the function with args combined + both from the curry call and the closure call itself. I.e. + ``curry(func,a,b)(c,d)`` equals ``func(a,b,c,d)``. + + Locking and finalization ------------------------ diff --git a/Lua API.html b/Lua API.html index 63a4c8547..f6f2d42b3 100644 --- a/Lua API.html +++ b/Lua API.html @@ -337,42 +337,43 @@ ul.auto-toc {
  • Native utilities
  • -
  • C++ function wrappers
  • The current version of DFHack has extensive support for @@ -842,8 +843,18 @@ following properties:

    +
    +

    Miscellaneous

    +
      +
    • dfhack.curry(func,args...), or curry(func,args...)

      +

      Returns a closure that invokes the function with args combined +both from the curry call and the closure call itself. I.e. +curry(func,a,b)(c,d) equals func(a,b,c,d).

      +
    • +
    +
    -

    Locking and finalization

    +

    Locking and finalization

    • dfhack.with_suspend(f[,args...])

      Calls f with arguments after grabbing the DF core suspend lock. @@ -876,7 +887,7 @@ Implemented using call_with_final

    -

    Persistent configuration storage

    +

    Persistent configuration storage

    This api is intended for storing configuration options in the world itself. It probably should be restricted to data that is world-dependent.

    Entries are identified by a string key, but it is also possible to manage @@ -911,7 +922,7 @@ functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

    -

    Material info lookup

    +

    Material info lookup

    A material info record has fields:

    • type, index, material

      @@ -956,7 +967,7 @@ Accept dfhack_material_category auto-assign table.

    -

    C++ function wrappers

    +

    C++ function wrappers

    Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments @@ -985,7 +996,7 @@ can be omitted.

    -

    Gui module

    +

    Gui module

    • dfhack.gui.getCurViewscreen([skip_dismissed])

      Returns the topmost viewscreen. If skip_dismissed is true, @@ -1032,7 +1043,7 @@ above operations accordingly. If enabled, pauses and zooms to position.

    -

    Job module

    +

    Job module

    • dfhack.job.cloneJobStruct(job)

      Creates a deep copy of the given job.

      @@ -1069,7 +1080,7 @@ a lua list containing them.

    -

    Units module

    +

    Units module

    • dfhack.units.getPosition(unit)

      Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.

      @@ -1130,7 +1141,7 @@ or raws. The ignore_noble boolean disables the
    -

    Items module

    +

    Items module

    • dfhack.items.getPosition(item)

      Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.

      @@ -1173,7 +1184,7 @@ Returns false in case of error.

    -

    Maps module

    +

    Maps module

    • dfhack.maps.getSize()

      Returns map size in blocks: x, y, z

      @@ -1221,7 +1232,7 @@ burrows, or the presence of invaders.

    -

    Burrows module

    +

    Burrows module

    • dfhack.burrows.findByName(name)

      Returns the burrow pointer or nil.

      @@ -1256,7 +1267,7 @@ burrows, or the presence of invaders.

    -

    Buildings module

    +

    Buildings module

    • dfhack.buildings.setOwner(item,unit)

      Replaces the owner of the building. If unit is nil, removes ownership. @@ -1400,7 +1411,7 @@ can be determined this way, constructBuilding

    -

    Constructions module

    +

    Constructions module

    • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

      Designates a new construction at given position. If there already is @@ -1416,7 +1427,7 @@ Returns true, was_only_planned if removed; or false if none fo

    -

    Screen API

    +

    Screen API

    The screen module implements support for drawing to the tiled screen of the game. Note that drawing only has any effect when done from callbacks, so it can only be feasibly used in the core context.

    @@ -1555,7 +1566,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
    -

    Internal API

    +

    Internal API

    These functions are intended for the use by dfhack developers, and are only documented here for completeness:

      @@ -1603,7 +1614,7 @@ Returns: found_index, or nil if end reached.

    -

    Core interpreter context

    +

    Core interpreter context

    While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.

    @@ -1634,7 +1645,7 @@ Using timeout_active(id,nil) cancels the timer
    -

    Event type

    +

    Event type

    An event is a native object transparently wrapping a lua table, and implementing a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1666,7 +1677,7 @@ order using dfhack.safecall.

    -

    Lua Modules

    +

    Lua Modules

    DFHack sets up the lua interpreter so that the built-in require function can be used to load shared lua code from hack/lua/. The dfhack namespace reference itself may be obtained via @@ -1695,7 +1706,7 @@ in this document.

    -

    Global environment

    +

    Global environment

    A number of variables and functions are provided in the base global environment by the mandatory init file dfhack.lua:

      @@ -1736,7 +1747,7 @@ Returns nil if any of obj or indices is nil, or a numeric inde
    -

    utils

    +

    utils

    • utils.compare(a,b)

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      @@ -1849,7 +1860,7 @@ throws an error.

    -

    dumper

    +

    dumper

    A third-party lua table dumper module from http://lua-users.org/wiki/DataDumper. Defines one function:

    @@ -1863,14 +1874,14 @@ the other arguments see the original documentation link above.

    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1908,13 +1919,13 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 7c2c8f8d6..9f0477538 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +static int dfhack_curry_wrap(lua_State *L) +{ + int nargs = lua_gettop(L); + int ncurry = lua_tointeger(L, lua_upvalueindex(1)); + int scount = nargs + ncurry; + + luaL_checkstack(L, ncurry, "stack overflow in curry"); + + // Insert values in O(N+M) by first shifting the existing data + lua_settop(L, scount); + for (int i = 0; i < nargs; i++) + lua_copy(L, nargs-i, scount-i); + for (int i = 1; i <= ncurry; i++) + lua_copy(L, lua_upvalueindex(i+1), i); + + lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop); + + return lua_gettop(L); +} + +static int dfhack_curry(lua_State *L) +{ + luaL_checkany(L, 1); + if (lua_isnil(L, 1)) + luaL_argerror(L, 1, "nil function in curry"); + if (lua_gettop(L) == 1) + return 1; + lua_pushinteger(L, lua_gettop(L)); + lua_insert(L, 1); + lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L)); + return 1; +} + bool Lua::IsCoreContext(lua_State *state) { // This uses a private field of the lua state to @@ -1234,6 +1267,7 @@ static const luaL_Reg dfhack_funcs[] = { { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { "open_plugin", dfhack_open_plugin }, + { "curry", dfhack_curry }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 2cbd019a6..a1e899761 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -46,6 +46,7 @@ end -- Error handling safecall = dfhack.safecall +curry = dfhack.curry function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) @@ -118,7 +119,12 @@ function defclass(class,parent) if parent then setmetatable(class, parent) else - rawset_default(class, { init_fields = rawset_default }) + rawset_default(class, { + init_fields = rawset_default, + callback = function(self, name, ...) + return dfhack.curry(self[name], self, ...) + end + }) end return class end From 57086ac56eb489abd0c7759aed084020edc71148 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 19:45:45 +0400 Subject: [PATCH 46/61] Add stock MessageBox and InputBox dialog screens for lua scripts. --- LUA_API.rst | 8 ++ Lua API.html | 6 ++ library/LuaApi.cpp | 2 + library/LuaTools.cpp | 3 + library/Process-darwin.cpp | 8 ++ library/Process-linux.cpp | 8 ++ library/Process-windows.cpp | 5 ++ library/include/MemAccess.h | 3 + library/lua/gui.lua | 12 ++- library/lua/gui/dialogs.lua | 175 ++++++++++++++++++++++++++++++++++++ library/lua/utils.lua | 13 +++ 11 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 library/lua/gui/dialogs.lua diff --git a/LUA_API.rst b/LUA_API.rst index 22130efd6..799f623eb 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -553,6 +553,10 @@ Exception handling Miscellaneous ------------- +* ``dfhack.VERSION`` + + DFHack version string constant. + * ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` Returns a closure that invokes the function with args combined @@ -719,6 +723,10 @@ can be omitted. Returns the dfhack directory path, i.e. ``".../df/hack/"``. +* ``dfhack.getTickCount()`` + + Returns the tick count in ms, exactly as DF ui uses. + * ``dfhack.isWorldLoaded()`` Checks if the world is loaded. diff --git a/Lua API.html b/Lua API.html index f6f2d42b3..f05ee5511 100644 --- a/Lua API.html +++ b/Lua API.html @@ -846,6 +846,9 @@ following properties:

    Miscellaneous

      +
    • dfhack.VERSION

      +

      DFHack version string constant.

      +
    • dfhack.curry(func,args...), or curry(func,args...)

      Returns a closure that invokes the function with args combined both from the curry call and the closure call itself. I.e. @@ -985,6 +988,9 @@ can be omitted.

    • dfhack.getHackPath()

      Returns the dfhack directory path, i.e. ".../df/hack/".

    • +
    • dfhack.getTickCount()

      +

      Returns the tick count in ms, exactly as DF ui uses.

      +
    • dfhack.isWorldLoaded()

      Checks if the world is loaded.

    • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4e57b113f..1dcb001f1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -728,6 +728,7 @@ static std::string getOSType() } static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } +static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } static std::string getDFPath() { return Core::getInstance().p->getPath(); } static std::string getHackPath() { return Core::getInstance().getHackPath(); } @@ -739,6 +740,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), WRAP(getDFPath), + WRAP(getTickCount), WRAP(getHackPath), WRAP(isWorldLoaded), WRAP(isMapLoaded), diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 9f0477538..a283d215c 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1580,6 +1580,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); lua_setfield(state, -2, "BASE_G"); + lua_pushstring(state, DFHACK_VERSION); + lua_setfield(state, -2, "VERSION"); + lua_pushboolean(state, IsCoreContext(state)); lua_setfield(state, -2, "is_core_context"); diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 5a97d9e00..3893cfc5f 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include @@ -262,6 +263,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { char path[1024]; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index fe8647845..4a66470f9 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include #include @@ -192,6 +193,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { const char * cwd_name = "/proc/self/cwd"; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 7eb6ff5f7..db58c4d33 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -410,6 +410,11 @@ string Process::doReadClassName (void * vptr) return raw; } +uint32_t Process::getTickCount() +{ + return GetTickCount(); +} + string Process::getPath() { HMODULE hmod; diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 0e5f618e2..a226018a6 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -281,6 +281,9 @@ namespace DFHack /// get the DF Process FilePath std::string getPath(); + /// millisecond tick count, exactly as DF uses + uint32_t getTickCount(); + /// modify permisions of memory range bool setPermisions(const t_memrange & range,const t_memrange &trgrange); diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 9e189ea13..23904c14f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -94,6 +94,9 @@ function Painter:isValidPos() end function Painter:viewport(x,y,w,h) + if type(x) == 'table' then + x,y,w,h = x.x1, x.y1, x.width, x.height + end local x1,y1 = self.x1+x, self.y1+y local x2,y2 = x1+w-1, y1+h-1 local vp = { @@ -353,11 +356,16 @@ local function hint_coord(gap,hint) end end +function FramedScreen:getWantedFrameSize() + return self.frame_width, self.frame_height +end + function FramedScreen:updateFrameSize() local sw, sh = dscreen.getWindowSize() local iw, ih = sw-2, sh-2 - local width = math.min(self.frame_width or iw, iw) - local height = math.min(self.frame_height or ih, ih) + local fw, fh = self:getWantedFrameSize() + local width = math.min(fw or iw, iw) + local height = math.min(fh or ih, ih) local gw, gh = iw-width, ih-height local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua new file mode 100644 index 000000000..e6d30c970 --- /dev/null +++ b/library/lua/gui/dialogs.lua @@ -0,0 +1,175 @@ +-- Some simple dialog screens + +local _ENV = mkmodule('gui.dialogs') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +MessageBox = defclass(MessageBox, gui.FramedScreen) + +MessageBox.frame_style = gui.GREY_LINE_FRAME + +function MessageBox:init(info) + info = info or {} + self:init_fields{ + text = info.text or {}, + frame_title = info.title, + frame_width = info.frame_width, + on_accept = info.on_accept, + on_cancel = info.on_cancel, + on_close = info.on_close, + text_pen = info.text_pen + } + if type(self.text) == 'string' then + self.text = utils.split_string(self.text, "\n") + end + gui.FramedScreen.init(self, info) + return self +end + +function MessageBox:getWantedFrameSize() + local text = self.text + local w = #(self.frame_title or '') + 2 + w = math.max(w, 20) + w = math.max(self.frame_width or w, w) + for _, l in ipairs(text) do + w = math.max(w, #l) + end + local h = #text+1 + if h > 1 then + h = h+1 + end + return w, #text+2 +end + +function MessageBox:onRenderBody(dc) + if #self.text > 0 then + dc:newline(1):pen(self.text_pen or COLOR_GREY) + for _, l in ipairs(self.text or {}) do + dc:string(l):newline(1) + end + end + + if self.on_accept then + local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 + dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC') + dscreen.paintString({fg=COLOR_GREY},x+3,y,'/') + dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y') + end +end + +function MessageBox:onDestroy() + if self.on_close then + self.on_close() + end +end + +function MessageBox:onInput(keys) + if keys.MENU_CONFIRM then + self:dismiss() + if self.on_accept then + self.on_accept() + end + elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end +end + +function showMessage(title, text, tcolor, on_close) + mkinstance(MessageBox):init{ + text = text, + title = title, + text = text, + text_pen = tcolor, + on_close = on_close + }:show() +end + +function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) + mkinstance(MessageBox):init{ + title = title, + text = text, + text_pen = tcolor, + on_accept = on_accept, + on_cancel = on_cancel, + }:show() +end + +InputBox = defclass(InputBox, MessageBox) + +function InputBox:init(info) + info = info or {} + self:init_fields{ + input = info.input or '', + input_pen = info.input_pen, + on_input = info.on_input, + } + MessageBox.init(self, info) + self.on_accept = nil + return self +end + +function InputBox:getWantedFrameSize() + local mw, mh = MessageBox.getWantedFrameSize(self) + return mw, mh+2 +end + +function InputBox:onRenderBody(dc) + MessageBox.onRenderBody(self, dc) + + dc:newline(1) + dc:pen(self.input_pen or COLOR_LIGHTCYAN) + dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) + + local cursor = '_' + if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + cursor = ' ' + end + local txt = self.input .. cursor + if #txt > dc.width-2 then + txt = string.sub(txt, #txt-dc.width+3) + -- Add prefix arrow + dc:advance(-1):char(27) + end + dc:string(txt) +end + +function InputBox:onInput(keys) + if keys.SELECT then + self:dismiss() + if self.on_input then + self.on_input(self.input) + end + elseif keys.LEAVESCREEN then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + elseif keys._STRING then + if keys._STRING == 0 then + self.input = string.sub(self.input, 1, #self.input-1) + else + self.input = self.input .. string.char(keys._STRING) + end + end +end + +function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) + mkinstance(InputBox):init{ + title = title, + text = text, + text_pen = tcolor, + input = input, + on_input = on_input, + on_cancel = on_cancel, + frame_width = min_width, + }:show() +end + + +return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 19a4e6f6a..9fa473ed8 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -381,6 +381,19 @@ function getBuildingCenter(building) return xyz2pos(building.centerx, building.centery, building.z) end +function split_string(self, delimiter) + local result = { } + local from = 1 + local delim_from, delim_to = string.find( self, delimiter, from ) + while delim_from do + table.insert( result, string.sub( self, from , delim_from-1 ) ) + from = delim_to + 1 + delim_from, delim_to = string.find( self, delimiter, from ) + end + table.insert( result, string.sub( self, from ) ) + return result +end + -- Ask a yes-no question function prompt_yes_no(msg,default) local prompt = msg From 8d876cc7d92faf1616d914e03c890772256ebb83 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 21:27:42 +0400 Subject: [PATCH 47/61] Support renaming some buildings, and arbitrary units, via gui script. --- dfhack.init-example | 4 + library/lua/gui/dialogs.lua | 9 +- library/modules/World.cpp | 4 +- plugins/CMakeLists.txt | 2 +- plugins/lua/rename.lua | 13 +++ plugins/proto/rename.proto | 7 ++ plugins/rename.cpp | 201 +++++++++++++++++++++++++++++++++++- scripts/gui/rename.lua | 63 +++++++++++ 8 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 plugins/lua/rename.lua create mode 100644 scripts/gui/rename.lua diff --git a/dfhack.init-example b/dfhack.init-example index 39c0e61df..af8b17f0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# gui/rename script +keybinding add Ctrl-Shift-N gui/rename +keybinding add Ctrl-Shift-P "gui/rename unit-profession" + ############################## # Generic adv mode bindings # ############################## diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index e6d30c970..35720f871 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -9,6 +9,7 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) +MessageBox.focus_path = 'MessageBox' MessageBox.frame_style = gui.GREY_LINE_FRAME function MessageBox:init(info) @@ -31,7 +32,7 @@ end function MessageBox:getWantedFrameSize() local text = self.text - local w = #(self.frame_title or '') + 2 + local w = #(self.frame_title or '') + 4 w = math.max(w, 20) w = math.max(self.frame_width or w, w) for _, l in ipairs(text) do @@ -41,7 +42,7 @@ function MessageBox:getWantedFrameSize() if h > 1 then h = h+1 end - return w, #text+2 + return w+2, #text+2 end function MessageBox:onRenderBody(dc) @@ -102,6 +103,8 @@ end InputBox = defclass(InputBox, MessageBox) +InputBox.focus_path = 'InputBox' + function InputBox:init(info) info = info or {} self:init_fields{ @@ -127,7 +130,7 @@ function InputBox:onRenderBody(dc) dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) local cursor = '_' - if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + if math.floor(dfhack.getTickCount()/300) % 2 == 0 then cursor = ' ' end local txt = self.input .. cursor diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 393e7cbfe..e14aa02a0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) { - *added = false; + if (added) *added = false; PersistentDataItem rv = GetPersistentData(key); if (!rv.isValid()) { - *added = true; + if (added) *added = true; rv = AddPersistentData(key); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a2e520178..04da3e6c8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,7 +92,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(seedwatch seedwatch.cpp) DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp) - DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename) + DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(workflow workflow.cpp) DFHACK_PLUGIN(showmood showmood.cpp) diff --git a/plugins/lua/rename.lua b/plugins/lua/rename.lua new file mode 100644 index 000000000..0e7128f57 --- /dev/null +++ b/plugins/lua/rename.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.rename') + +--[[ + + Native functions: + + * canRenameBuilding(building) + * isRenamingBuilding(building) + * renameBuilding(building, name) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index aa1e95f48..810091fc7 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -17,3 +17,10 @@ message RenameUnitIn { optional string nickname = 2; optional string profession = 3; } + +// RPC RenameBuilding : RenameBuildingIn -> EmptyMessage +message RenameBuildingIn { + required int32 building_id = 1; + + optional string name = 2; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1871d0f73..99dc6848a 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -3,11 +3,15 @@ #include "Export.h" #include "PluginManager.h" +#include +#include + #include "modules/Gui.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/World.h" -#include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -18,6 +22,11 @@ #include "df/historical_figure_info.h" #include "df/assumed_identity.h" #include "df/language_name.h" +#include "df/building_stockpilest.h" +#include "df/building_workshopst.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_siegeenginest.h" #include "RemoteServer.h" #include "rename.pb.h" @@ -36,6 +45,8 @@ using namespace dfproto; using df::global::ui; using df::global::world; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + static command_result rename(color_ostream &out, vector & parameters); DFHACK_PLUGIN("rename"); @@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector clear(); \ + *buf += name; \ + *buf += " ("; \ + if (tag) *buf += (const char*)tag; \ + else { std::string tmp; INTERPOSE_NEXT(getName)(&tmp); *buf += tmp; } \ + *buf += ")"; \ + return; \ + } \ + else \ + INTERPOSE_NEXT(getName)(buf); \ + } \ + }; \ + IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); +KNOWN_BUILDINGS +#undef BUILDING + +static char getBuildingCode(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + +#define BUILDING(code, cname, tag) \ + if (strict_virtual_cast(bld)) return code; +KNOWN_BUILDINGS +#undef BUILDING + + return 0; +} + +static bool enable_building_rename(char code, bool enable) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void disable_building_rename() +{ +#define BUILDING(code, cname, tag) \ + INTERPOSE_HOOK(cname##_hook, getName).remove(); +KNOWN_BUILDINGS +#undef BUILDING +} + +static bool is_enabled_building(char code) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).is_applied(); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void init_buildings(bool enable) +{ + disable_building_rename(); + + if (enable) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types"); + + if (entry.isValid()) + { + std::string val = entry.val(); + for (size_t i = 0; i < val.size(); i++) + enable_building_rename(val[i], true); + } + } +} + +static bool canRenameBuilding(df::building *bld) +{ + return getBuildingCode(bld) != 0; +} + +static bool isRenamingBuilding(df::building *bld) +{ + return is_enabled_building(getBuildingCode(bld)); +} + +static bool renameBuilding(df::building *bld, std::string name) +{ + char code = getBuildingCode(bld); + if (code == 0 && !name.empty()) + return false; + + if (!name.empty() && !is_enabled_building(code)) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types", NULL); + if (!entry.isValid()) + return false; + + if (!enable_building_rename(code, true)) + return false; + + entry.val().push_back(code); + } + + bld->name = name; + return true; +} + static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -101,14 +263,37 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_OK; } +static command_result RenameBuilding(color_ostream &stream, const RenameBuildingIn *in) +{ + auto building = df::building::find(in->building_id()); + if (!building) + return CR_NOT_FOUND; + + if (in->has_name()) + { + if (!renameBuilding(building, in->name())) + return CR_FAILURE; + } + + return CR_OK; +} + DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) { RPCService *svc = new RPCService(); svc->addFunction("RenameSquad", RenameSquad); svc->addFunction("RenameUnit", RenameUnit); + svc->addFunction("RenameBuilding", RenameBuilding); return svc; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(canRenameBuilding), + DFHACK_LUA_FUNCTION(isRenamingBuilding), + DFHACK_LUA_FUNCTION(renameBuilding), + DFHACK_LUA_END +}; + static command_result rename(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector ¶meters) unit->custom_profession = parameters[1]; } + else if (cmd == "building") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + return CR_WRONG_USAGE; + + if (!renameBuilding(world->selected_building, parameters[1])) + { + out.printerr("This type of building is not supported.\n"); + return CR_FAILURE; + } + } else { if (!parameters.empty() && cmd != "?") diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua new file mode 100644 index 000000000..a457a0bfd --- /dev/null +++ b/scripts/gui/rename.lua @@ -0,0 +1,63 @@ +-- Rename various objects via gui. + +local gui = require 'gui' +local dlg = require 'gui.dialogs' +local plugin = require 'plugins.rename' + +local mode = ... +local focus = dfhack.gui.getCurFocus() + +local function verify_mode(expected) + if mode ~= nil and mode ~= expected then + qerror('Invalid UI state for mode '..mode) + end +end + +if string.match(focus, '^dwarfmode/QueryBuilding/Some') then + verify_mode('building') + + local building = df.global.world.selected_building + if plugin.canRenameBuilding(building) then + dlg.showInputPrompt( + 'Rename Building', + 'Enter a new name for the building:', COLOR_GREEN, + building.name, + curry(plugin.renameBuilding, building) + ) + else + dlg.showMessage( + 'Rename Building', + 'Cannot rename this type of building.', COLOR_LIGHTRED + ) + end +elseif dfhack.gui.getSelectedUnit(true) then + local unit = dfhack.gui.getSelectedUnit(true) + + if mode == 'unit-profession' then + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new profession for the unit:', COLOR_GREEN, + unit.custom_profession, + function(newval) + unit.custom_profession = newval + end + ) + else + verify_mode('unit') + + local vname = dfhack.units.getVisibleName(unit) + local vnick = '' + if vname and vname.has_name then + vnick = vname.nickname + end + + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new nickname for the unit:', COLOR_GREEN, + vnick, + curry(dfhack.units.setNickname, unit) + ) + end +elseif mode then + verify_mode(nil) +end From d5ea05ebb88b40483b62aaf5dfe20e1b24e8a28a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 12:37:29 +0400 Subject: [PATCH 48/61] Implement a pressure plate sensitive to machine power. When built next to a gearbox, it will monitor its powered state. --- dfhack.init-example | 3 + library/lua/dfhack.lua | 2 +- library/lua/gui.lua | 8 +- library/lua/gui/dialogs.lua | 6 +- library/modules/Gui.cpp | 9 +- plugins/CMakeLists.txt | 1 + plugins/lua/power-meter.lua | 11 ++ plugins/power-meter.cpp | 237 ++++++++++++++++++++++++++++++++++++ scripts/gui/power-meter.lua | 116 ++++++++++++++++++ 9 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 plugins/lua/power-meter.lua create mode 100644 plugins/power-meter.cpp create mode 100644 scripts/gui/power-meter.lua diff --git a/dfhack.init-example b/dfhack.init-example index af8b17f0f..5af527099 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -56,6 +56,9 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work # interface for the liquids plugin keybinding add Alt-L@dwarfmode/LookAround gui/liquids +# machine power sensitive pressure plate construction +keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter + ################### # UI logic tweaks # ################### diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index a1e899761..e96bb0f4b 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -84,7 +84,7 @@ function mkmodule(module,env) error("Not a table in package.loaded["..module.."]") end end - local plugname = string.match(module,'^plugins%.(%w+)$') + local plugname = string.match(module,'^plugins%.([%w%-]+)$') if plugname then dfhack.open_plugin(pkg,plugname) end diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 23904c14f..f9b6ab6d2 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -162,10 +162,10 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold) if type(x1) == 'table' then x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2 end - x1 = math.max(x1,self.clip_x1) - y1 = math.max(y1,self.clip_y1) - x2 = math.min(x2,self.clip_x2) - y2 = math.min(y2,self.clip_y2) + x1 = math.max(x1+self.x1,self.clip_x1) + y1 = math.max(y1+self.y1,self.clip_y1) + x2 = math.min(x2+self.x1,self.clip_x2) + y2 = math.min(y2+self.y1,self.clip_y2) dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) return self end diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 35720f871..c4f15c9ac 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -127,7 +127,7 @@ function InputBox:onRenderBody(dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) + dc:fill(1,dc:localY(),dc.width-2,dc:localY()) local cursor = '_' if math.floor(dfhack.getTickCount()/300) % 2 == 0 then @@ -135,9 +135,7 @@ function InputBox:onRenderBody(dc) end local txt = self.input .. cursor if #txt > dc.width-2 then - txt = string.sub(txt, #txt-dc.width+3) - -- Add prefix arrow - dc:advance(-1):char(27) + txt = string.char(27)..string.sub(txt, #txt-dc.width+4) end dc:string(txt) end diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 1ea4bf687..8de908734 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -211,7 +211,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) if (ui_build_selector->building_type < 0) focus += "/Type"; else if (ui_build_selector->stage != 2) - focus += "/Position"; + { + if (ui_build_selector->stage != 1) + focus += "/NoMaterials"; + else + focus += "/Position"; + + focus += "/" + enum_item_key(ui_build_selector->building_type); + } else { focus += "/Material"; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 04da3e6c8..9093a493f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -114,6 +114,7 @@ if (BUILD_SUPPORTED) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/lua/power-meter.lua b/plugins/lua/power-meter.lua new file mode 100644 index 000000000..310e51c4e --- /dev/null +++ b/plugins/lua/power-meter.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.power-meter') + +--[[ + + Native functions: + + * makePowerMeter(plate_info,min_power,max_power,invert) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp new file mode 100644 index 000000000..0466b68e4 --- /dev/null +++ b/plugins/power-meter.cpp @@ -0,0 +1,237 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/graphic.h" +#include "df/building_trapst.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/buildings_other_id.h" +#include "df/machine.h" +#include "df/machine_info.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("power-meter"); + +static const uint32_t METER_BIT = 0x80000000U; + +static void init_plate_info(df::pressure_plate_info &plate_info) +{ + plate_info.water_min = 1; + plate_info.water_max = 7; + plate_info.flags.whole = METER_BIT; + plate_info.flags.bits.water = true; + plate_info.flags.bits.resets = true; +} + +/* + * Hook for the pressure plate itself. Implements core logic. + */ + +struct trap_hook : df::building_trapst { + typedef df::building_trapst interpose_base; + + // Engine detection + + bool is_power_meter() + { + return trap_type == trap_type::PressurePlate && + (plate_info.flags.whole & METER_BIT) != 0; + } + + inline bool is_fully_built() + { + return getBuildStage() >= getMaxBuildStage(); + } + + DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf)) + { + if (is_power_meter()) + { + buf->clear(); + *buf += "Power Meter"; + return; + } + + INTERPOSE_NEXT(getName)(buf); + } + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (is_power_meter()) + { + auto pdsgn = Maps::getTileDesignation(centerx,centery,z); + + if (pdsgn) + { + bool active = false; + auto &gears = world->buildings.other[buildings_other_id::GEAR_ASSEMBLY]; + + for (size_t i = 0; i < gears.size(); i++) + { + // Adjacent + auto gear = gears[i]; + int deltaxy = abs(centerx - gear->centerx) + abs(centery - gear->centery); + if (gear->z != z || deltaxy != 1) + continue; + // Linked to machine + auto info = gears[i]->getMachineInfo(); + if (!info || info->machine_id < 0) + continue; + // an active machine + auto machine = df::machine::find(info->machine_id); + if (!machine || !machine->flags.bits.active) + continue; + // with adequate power? + int power = machine->cur_power - machine->min_power; + if (power < 0 || machine->cur_power <= 0) + continue; + if (power < plate_info.track_min) + continue; + if (power > plate_info.track_max && plate_info.track_max >= 0) + continue; + + active = true; + break; + } + + if (plate_info.flags.bits.citizens) + active = !active; + + // Temporarily set the tile water amount based on power state + auto old_dsgn = *pdsgn; + pdsgn->bits.liquid_type = tile_liquid::Water; + pdsgn->bits.flow_size = (active ? 7 : 0); + + INTERPOSE_NEXT(updateAction)(); + + *pdsgn = old_dsgn; + return; + } + } + + INTERPOSE_NEXT(updateAction)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk)) + { + INTERPOSE_NEXT(drawBuilding)(db, unk); + + if (is_power_meter() && is_fully_built()) + { + db->fore[0][0] = 3; + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, getName); +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, updateAction); +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, drawBuilding); + +static bool enabled = false; + +static void enable_hooks(bool enable) +{ + enabled = enable; + + INTERPOSE_HOOK(trap_hook, getName).apply(enable); + INTERPOSE_HOOK(trap_hook, updateAction).apply(enable); + INTERPOSE_HOOK(trap_hook, drawBuilding).apply(enable); +} + +static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max_power, bool invert) +{ + CHECK_NULL_POINTER(info); + + if (!enabled) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("power-meter/enabled", NULL); + if (!entry.isValid()) + return false; + + enable_hooks(true); + } + + init_plate_info(*info); + info->track_min = min_power; + info->track_max = max_power; + info->flags.bits.citizens = invert; + return true; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(makePowerMeter), + DFHACK_LUA_END +}; + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + { + auto pworld = Core::getInstance().getWorld(); + bool enable = pworld->GetPersistentData("power-meter/enabled").isValid(); + + if (enable) + { + out.print("Enabling the power meter plugin.\n"); + enable_hooks(true); + } + } + break; + case SC_MAP_UNLOADED: + enable_hooks(false); + 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/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua new file mode 100644 index 000000000..8baf43e7e --- /dev/null +++ b/scripts/gui/power-meter.lua @@ -0,0 +1,116 @@ +-- Interface front-end for power-meter plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local plugin = require('plugins.power-meter') +local bselector = df.global.ui_build_selector + +PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) + +PowerMeter.focus_path = 'power-meter' + +function PowerMeter:init() + self:init_fields{ + min_power = 0, max_power = -1, invert = false, + } + guidm.MenuOverlay.init(self) + return self +end + +function PowerMeter:onShow() + guidm.MenuOverlay.onShow(self) + + -- Send an event to update the errors + bselector.plate_info.flags.whole = 0 + self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER') +end + +function PowerMeter:onRenderBody(dc) + dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN) + dc:seek(1,1):pen(COLOR_WHITE) + dc:string("Power Meter"):newline():newline(1) + dc:string("Placement"):newline():newline(1) + + dc:string("Excess power range:") + + dc:newline(3):string("as", COLOR_LIGHTGREEN) + dc:string(": Min ") + if self.min_power <= 0 then + dc:string("(any)") + else + dc:string(''..self.min_power) + end + + dc:newline(3):string("zx", COLOR_LIGHTGREEN) + dc:string(": Max ") + if self.max_power < 0 then + dc:string("(any)") + else + dc:string(''..self.max_power) + end + dc:newline():newline(1) + + dc:string("i",COLOR_LIGHTGREEN):string(": ") + if self.invert then + dc:string("Inverted") + else + dc:string("Not inverted") + end +end + +function PowerMeter:onInput(keys) + if keys.CUSTOM_I then + self.invert = not self.invert + elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then + self.min_power = self.min_power + 10 + elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then + self.min_power = math.max(0, self.min_power - 10) + elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then + if self.max_power < 0 then + self.max_power = 0 + else + self.max_power = self.max_power + 10 + end + elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then + self.max_power = math.max(-1, self.max_power - 10) + elseif keys.LEAVESCREEN then + self:dismiss() + self:sendInputToParent('LEAVESCREEN') + elseif keys.SELECT then + if #bselector.errors == 0 then + if not plugin.makePowerMeter( + bselector.plate_info, + self.min_power, self.max_power, self.invert + ) + then + dlg.showMessage( + 'Power Meter', + 'Could not initialize.', COLOR_LIGHTRED + ) + + self:dismiss() + self:sendInputToParent('LEAVESCREEN') + return + end + + self:sendInputToParent('SELECT') + if bselector.stage ~= 1 then + self:dismiss() + end + end + elseif self:propagateMoveKeys(keys) then + return + end +end + +if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap' +or bselector.building_subtype ~= df.trap_type.PressurePlate +then + qerror("This script requires the main dwarfmode view in build pressure plate mode") +end + +local list = mkinstance(PowerMeter):init() +list:show() From d0e630d4c35717bad682894e33e7dd57f86ac126 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 17:10:58 +0400 Subject: [PATCH 49/61] Move steam engine out of devel, since it should be fully functional. --- plugins/CMakeLists.txt | 4 ++++ plugins/devel/CMakeLists.txt | 1 - plugins/{devel => raw}/building_steam_engine.txt | 0 plugins/{devel => raw}/item_trapcomp_steam_engine.txt | 0 plugins/{devel => raw}/reaction_steam_engine.txt | 0 plugins/{devel => }/steam-engine.cpp | 0 6 files changed, 4 insertions(+), 1 deletion(-) rename plugins/{devel => raw}/building_steam_engine.txt (100%) rename plugins/{devel => raw}/item_trapcomp_steam_engine.txt (100%) rename plugins/{devel => raw}/reaction_steam_engine.txt (100%) rename plugins/{devel => }/steam-engine.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9093a493f..6e207385e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -44,6 +44,9 @@ endif() install(DIRECTORY lua/ DESTINATION ${DFHACK_LUA_DESTINATION}/plugins FILES_MATCHING PATTERN "*.lua") +install(DIRECTORY raw/ + DESTINATION ${DFHACK_DATA_DESTINATION}/raw + FILES_MATCHING PATTERN "*.txt") # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) @@ -114,6 +117,7 @@ if (BUILD_SUPPORTED) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index f126ae53b..134d5cb67 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,7 +18,6 @@ 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/raw/building_steam_engine.txt similarity index 100% rename from plugins/devel/building_steam_engine.txt rename to plugins/raw/building_steam_engine.txt diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/raw/item_trapcomp_steam_engine.txt similarity index 100% rename from plugins/devel/item_trapcomp_steam_engine.txt rename to plugins/raw/item_trapcomp_steam_engine.txt diff --git a/plugins/devel/reaction_steam_engine.txt b/plugins/raw/reaction_steam_engine.txt similarity index 100% rename from plugins/devel/reaction_steam_engine.txt rename to plugins/raw/reaction_steam_engine.txt diff --git a/plugins/devel/steam-engine.cpp b/plugins/steam-engine.cpp similarity index 100% rename from plugins/devel/steam-engine.cpp rename to plugins/steam-engine.cpp From c971a819def1c5cc29dc926f62456c336a1dfa17 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 22:45:19 +0400 Subject: [PATCH 50/61] Experimental creation of map blocks in gui/liquids script. --- LUA_API.rst | 4 +++ Lua API.html | 3 ++ library/LuaApi.cpp | 8 +++++ library/include/modules/MapCache.h | 10 ++++++ library/include/modules/Maps.h | 2 ++ library/modules/Maps.cpp | 58 +++++++++++++++++++++++++++++- library/xml | 2 +- scripts/gui/liquids.lua | 41 ++++++++++++++++++++- 8 files changed, 125 insertions(+), 3 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 799f623eb..d13c1e52e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -995,6 +995,10 @@ Maps module Returns a map block object for given df::coord or x,y,z in local tile coordinates. +* ``dfhack.maps.ensureTileBlock(coords)``, or ``ensureTileBlock(x,y,z)`` + + Like ``getTileBlock``, but if the block is not allocated, try creating it. + * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` Returns the biome info struct for the given global map region. diff --git a/Lua API.html b/Lua API.html index f05ee5511..546d27403 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1207,6 +1207,9 @@ Returns false in case of error.

    • 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.

    • +
    • dfhack.maps.ensureTileBlock(coords), or ensureTileBlock(x,y,z)

      +

      Like getTileBlock, but if the block is not allocated, try creating it.

      +
    • dfhack.maps.getRegionBiome(region_coord2d), or getRegionBiome(x,y)

      Returns the biome info struct for the given global map region.

    • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 1dcb001f1..63838d356 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -935,6 +935,13 @@ static int maps_getTileBlock(lua_State *L) return 1; } +static int maps_ensureTileBlock(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Maps::ensureTileBlock(pos)); + return 1; +} + static int maps_getRegionBiome(lua_State *L) { auto pos = CheckCoordXY(L, 1, true); @@ -951,6 +958,7 @@ static int maps_getTileBiomeRgn(lua_State *L) static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "getTileBlock", maps_getTileBlock }, + { "ensureTileBlock", maps_ensureTileBlock }, { "getRegionBiome", maps_getRegionBiome }, { "getTileBiomeRgn", maps_getTileBiomeRgn }, { NULL, NULL } diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 109a20a41..262e70bbf 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -253,6 +253,8 @@ public: bool is_valid() { return valid; } df::map_block *getRaw() { return block; } + bool Allocate(); + MapCache *getParent() { return parent; } private: @@ -262,6 +264,8 @@ private: MapCache *parent; df::map_block *block; + void init(); + int biomeIndexAt(df::coord2d p); bool valid; @@ -347,6 +351,12 @@ class DFHACK_EXPORT MapCache return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z)); } + bool ensureBlockAt(df::coord coord) + { + Block *b = BlockAtTile(coord); + return b ? b->Allocate() : false; + } + df::tiletype baseTiletypeAt (DFCoord tilecoord) { Block *b = BlockAtTile(tilecoord); diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 984cf16cf..869b21580 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -241,9 +241,11 @@ inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, */ extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz); extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z); +extern DFHACK_EXPORT df::map_block * ensureTileBlock (int32_t x, int32_t y, int32_t z); inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); } +inline df::map_block * ensureTileBlock (df::coord pos) { return ensureTileBlock(pos.x, pos.y, pos.z); } extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z); extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 305f1296d..d0401164a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -157,6 +157,39 @@ df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) return world->map.block_index[x >> 4][y >> 4][z]; } +df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z) +{ + if (!isValidTilePos(x,y,z)) + return NULL; + + auto column = world->map.block_index[x >> 4][y >> 4]; + auto &slot = column[z]; + if (slot) + return slot; + + // Find another block below + int z2 = z; + while (z2 >= 0 && !column[z2]) z2--; + if (z2 < 0) + return NULL; + + slot = new df::map_block(); + slot->region_pos = column[z2]->region_pos; + slot->map_pos = column[z2]->map_pos; + slot->map_pos.z = z; + + // Assume sky + df::tile_designation dsgn(0); + dsgn.bits.light = true; + dsgn.bits.outside = true; + + for (int tx = 0; tx < 16; tx++) + for (int ty = 0; ty < 16; ty++) + slot->designation[tx][ty] = dsgn; + + return slot; +} + df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z) { df::map_block *block = getTileBlock(x,y,z); @@ -513,8 +546,14 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) valid = false; bcoord = _bcoord; block = Maps::getBlock(bcoord); - item_counts = NULL; tags = NULL; + + init(); +} + +void MapExtras::Block::init() +{ + item_counts = NULL; tiles = NULL; basemats = NULL; @@ -537,6 +576,23 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) } } +bool MapExtras::Block::Allocate() +{ + if (block) + return true; + + block = Maps::ensureTileBlock(bcoord.x*16, bcoord.y*16, bcoord.z); + if (!block) + return false; + + delete item_counts; + delete tiles; + delete basemats; + init(); + + return true; +} + MapExtras::Block::~Block() { delete[] item_counts; diff --git a/library/xml b/library/xml index df8178a98..a914f3b75 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 +Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29 diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index 869cac908..89f08b7cf 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -3,6 +3,7 @@ local utils = require 'utils' local gui = require 'gui' local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' local liquids = require('plugins.liquids') @@ -199,6 +200,42 @@ function LiquidsUI:onRenderBody(dc) dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") end +function ensure_blocks(cursor, size, cb) + local cx,cy,cz = pos2xyz(cursor) + local all = true + for x=1,size.x or 1,16 do + for y=1,size.y or 1,16 do + for z=1,size.z do + if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then + all = false + end + end + end + end + if all then + cb() + return + end + dlg.showYesNoPrompt( + 'Instantiate Blocks', + 'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.', + COLOR_YELLOW, + function() + for x=1,size.x or 1,16 do + for y=1,size.y or 1,16 do + for z=1,size.z do + dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1) + end + end + end + cb() + end, + function() + cb() + end + ) +end + function LiquidsUI:onInput(keys) local paint = self.paint:get() local liquid = paint.liquid @@ -239,13 +276,15 @@ function LiquidsUI:onInput(keys) else guidm.clearSelection() end - liquids.paint( + local cb = curry( + liquids.paint, cursor, self.brush:get().tag, self.paint:get().tag, self.amount, size, self.set:get().tag, self.flow:get().tag, self.permaflow:get().tag ) + ensure_blocks(cursor, size, cb) elseif self:propagateMoveKeys(keys) then return elseif keys.D_LOOK_ARENA_WATER then From e925d8f4d999817d3ccf7f3180e7abee382e03b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Sep 2012 11:36:45 +0400 Subject: [PATCH 51/61] Add an API function for reading tiles from the screen buffers. --- LUA_API.rst | 5 ++++ Lua API.html | 4 ++++ library/LuaApi.cpp | 40 ++++++++++++++++++++++++++++++++ library/include/modules/Screen.h | 6 +++++ library/modules/Screen.cpp | 39 +++++++++++++++++++++++++++++-- 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index d13c1e52e..4d9170d6e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -1308,6 +1308,11 @@ Basic painting functions: Returns *false* if coordinates out of bounds, or other error. +* ``dfhack.screen.readTile(x,y)`` + + Retrieves the contents of the specified tile from the screen buffers. + Returns a pen, or *nil* if invalid or TrueType. + * ``dfhack.screen.paintString(pen,x,y,text)`` Paints the string starting at *x,y*. Uses the string characters diff --git a/Lua API.html b/Lua API.html index 546d27403..dc9c8d73e 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1480,6 +1480,10 @@ Otherwise should be true/false.

      Returns false if coordinates out of bounds, or other error.

      +
    • dfhack.screen.readTile(x,y)

      +

      Retrieves the contents of the specified tile from the screen buffers. +Returns a pen, or nil if invalid or TrueType.

      +
    • dfhack.screen.paintString(pen,x,y,text)

      Paints the string starting at x,y. Uses the string characters in sequence to override the ch field of pen.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 63838d356..807cbf539 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1154,6 +1154,45 @@ static int screen_paintTile(lua_State *L) return 1; } +static int screen_readTile(lua_State *L) +{ + int x = luaL_checkint(L, 1); + int y = luaL_checkint(L, 2); + Pen pen = Screen::readTile(x, y); + + if (!pen.valid()) + { + lua_pushnil(L); + } + else + { + lua_newtable(L); + lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch"); + lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); + lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); + lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); + + if (pen.tile) + { + lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); + + switch (pen.tile_mode) { + case Pen::CharColor: + lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); + break; + case Pen::TileColor: + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); + break; + default: + break; + } + } + } + + return 1; +} + static int screen_paintString(lua_State *L) { Pen pen; @@ -1258,6 +1297,7 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "getMousePos", screen_getMousePos }, { "getWindowSize", screen_getWindowSize }, { "paintTile", screen_paintTile }, + { "readTile", screen_readTile }, { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 492e1eecc..4f47205f2 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -65,6 +65,9 @@ namespace DFHack } tile_mode; int8_t tile_fg, tile_bg; + bool valid() const { return tile >= 0; } + bool empty() const { return ch == 0 && tile == 0; } + Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) @@ -92,6 +95,9 @@ namespace DFHack /// Paint one screen tile with the given pen DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y); + /// Retrieves one screen tile from the buffer + DFHACK_EXPORT Pen readTile(int x, int y); + /// Paint a string onto the screen. Ignores ch and tile of pen. DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index c2377f2ca..9f258fe02 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -100,7 +100,7 @@ static void doSetTile(const Pen &pen, int index) bool Screen::paintTile(const Pen &pen, int x, int y) { - if (!gps) return false; + if (!gps || !pen.valid()) return false; int dimx = gps->dimx, dimy = gps->dimy; if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; @@ -109,6 +109,41 @@ bool Screen::paintTile(const Pen &pen, int x, int y) return true; } +Pen Screen::readTile(int x, int y) +{ + if (!gps) return Pen(0,0,0,-1); + + int dimx = gps->dimx, dimy = gps->dimy; + if (x < 0 || x >= dimx || y < 0 || y >= dimy) + return Pen(0,0,0,-1); + + int index = x*dimy + y; + auto screen = gps->screen + index*4; + if (screen[3] & 0x80) + return Pen(0,0,0,-1); + + Pen pen( + screen[0], screen[1], screen[2], screen[3]?true:false, + gps->screentexpos[index] + ); + + if (pen.tile) + { + if (gps->screentexpos_grayscale[index]) + { + pen.tile_mode = Screen::Pen::TileColor; + pen.tile_fg = gps->screentexpos_cf[index]; + pen.tile_bg = gps->screentexpos_cbr[index]; + } + else if (gps->screentexpos_addcolor[index]) + { + pen.tile_mode = Screen::Pen::CharColor; + } + } + + return pen; +} + bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) { if (!gps || y < 0 || y >= gps->dimy) return false; @@ -132,7 +167,7 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) { - if (!gps) return false; + if (!gps || !pen.valid()) return false; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; From 325e294af2bbd2ba0381a476756eddbbfceb1b36 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Sep 2012 19:54:32 +0400 Subject: [PATCH 52/61] Start the siege engine plugin with code to highlight obstacles on screen. --- library/lua/dfhack.lua | 17 ++ library/lua/gui/dwarfmode.lua | 53 +++- plugins/devel/CMakeLists.txt | 1 + plugins/devel/siege-engine.cpp | 432 +++++++++++++++++++++++++++++++++ plugins/lua/siege-engine.lua | 13 + scripts/gui/siege-engine.lua | 72 ++++++ 6 files changed, 582 insertions(+), 6 deletions(-) create mode 100644 plugins/devel/siege-engine.cpp create mode 100644 plugins/lua/siege-engine.lua create mode 100644 scripts/gui/siege-engine.lua diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index e96bb0f4b..baf0d42e0 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -169,6 +169,23 @@ function xyz2pos(x,y,z) end end +function pos2xy(pos) + if pos then + local x = pos.x + if x and x ~= -30000 then + return x, pos.y + end + end +end + +function xy2pos(x,y) + if x then + return {x=x,y=y} + else + return {x=-30000,y=-30000} + end +end + function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 1f7ae1b03..21a942174 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -136,6 +136,14 @@ function Viewport:set() return vp end +function Viewport:getPos() + return xyz2pos(self.x1, self.y1, self.z) +end + +function Viewport:getSize() + return xy2pos(self.width, self.height) +end + function Viewport:clip(x,y,z) return self:make( math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), @@ -207,16 +215,24 @@ MOVEMENT_KEYS = { CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, } -function Viewport:scrollByKey(key) +local function get_movement_delta(key, delta, big_step) local info = MOVEMENT_KEYS[key] if info then - local delta = 10 - if info[4] then delta = 20 end + if info[4] then + delta = big_step + end + return delta*info[1], delta*info[2], info[3] + end +end + +function Viewport:scrollByKey(key) + local dx, dy, dz = get_movement_delta(key, 10, 20) + if dx then return self:clip( - self.x1 + delta*info[1], - self.y1 + delta*info[2], - self.z + info[3] + self.x1 + dx, + self.y1 + dy, + self.z + dz ) else return self @@ -282,6 +298,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) end end +function DwarfOverlay:simulateCursorMovement(keys, anchor) + local layout = self.df_layout + local cursor = getCursorPos() + local cx, cy, cz = pos2xyz(cursor) + + if anchor and keys.A_MOVE_SAME_SQUARE then + setCursorPos(anchor) + self:getViewport():centerOn(anchor):set() + return 'A_MOVE_SAME_SQUARE' + end + + for code,_ in pairs(MOVEMENT_KEYS) do + if keys[code] then + local dx, dy, dz = get_movement_delta(code, 1, 10) + local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) + + if dfhack.maps.isValidTilePos(ncur) then + setCursorPos(ncur) + self:getViewport():reveal(ncur,4,10,6,true):set() + return code + end + end + end +end + function DwarfOverlay:onAboutToShow(below) local screen = dfhack.gui.getCurViewscreen() if below then screen = below.parent end diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..39e8f7b60 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(siege-engine siege-engine.cpp LINK_LIBRARIES lua) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp new file mode 100644 index 000000000..189c43ad5 --- /dev/null +++ b/plugins/devel/siege-engine.cpp @@ -0,0 +1,432 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/graphic.h" +#include "df/building_siegeenginest.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/buildings_other_id.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; + +using Screen::Pen; + +DFHACK_PLUGIN("siege-engine"); + +/* + * Configuration management + */ + +static bool enable_plugin(); + +struct EngineInfo { + int id; + df::coord target_min, target_max; + + bool hasTarget() { + return target_min.isValid() && target_max.isValid(); + } + bool onTarget(df::coord pos) { + return hasTarget() && + target_min.x <= pos.x && pos.x <= target_max.x && + target_min.y <= pos.y && pos.y <= target_max.y && + target_min.z <= pos.z && pos.z <= target_max.z; + } +}; + +static std::map engines; + +static EngineInfo *find_engine(df::building *bld, bool create = false) +{ + if (!bld) + return NULL; + + auto it = engines.find(bld); + if (it != engines.end()) + return &it->second; + if (!create) + return NULL; + + auto *obj = &engines[bld]; + obj->id = bld->id; + return obj; +} + +static void load_engines() +{ + engines.clear(); + + auto pworld = Core::getInstance().getWorld(); + std::vector vec; + + pworld->GetPersistentData(&vec, "siege-engine/target/", true); + for (auto it = vec.begin(); it != vec.end(); ++it) + { + auto engine = find_engine(df::building::find(it->ival(0)), true); + if (!engine) continue; + engine->target_min = df::coord(it->ival(1), it->ival(2), it->ival(3)); + engine->target_max = df::coord(it->ival(4), it->ival(5), it->ival(6)); + } +} + +static int getTargetArea(lua_State *L) +{ + auto bld = Lua::CheckDFObject(L, 1); + if (!bld) luaL_argerror(L, 1, "null building"); + auto engine = find_engine(bld); + + if (engine && engine->target_min.isValid()) + { + Lua::Push(L, engine->target_min); + Lua::Push(L, engine->target_max); + } + else + { + lua_pushnil(L); + lua_pushnil(L); + } + + return 2; +} + +static void clearTargetArea(df::building_siegeenginest *bld) +{ + CHECK_NULL_POINTER(bld); + + if (auto engine = find_engine(bld)) + engine->target_min = engine->target_max = df::coord(); + + auto pworld = Core::getInstance().getWorld(); + auto key = stl_sprintf("siege-engine/target/%d", bld->id); + pworld->DeletePersistentData(pworld->GetPersistentData(key)); +} + +static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max) +{ + CHECK_NULL_POINTER(bld); + CHECK_INVALID_ARGUMENT(target_min.isValid() && target_max.isValid()); + + if (!enable_plugin()) + return false; + + auto pworld = Core::getInstance().getWorld(); + auto key = stl_sprintf("siege-engine/target/%d", bld->id); + auto entry = pworld->GetPersistentData(key, NULL); + if (!entry.isValid()) + return false; + + auto engine = find_engine(bld, true); + + entry.ival(0) = bld->id; + entry.ival(1) = engine->target_min.x = std::min(target_min.x, target_max.x); + entry.ival(2) = engine->target_min.y = std::min(target_min.y, target_max.y); + entry.ival(3) = engine->target_min.z = std::min(target_min.z, target_max.z); + entry.ival(4) = engine->target_max.x = std::max(target_min.x, target_max.x); + entry.ival(5) = engine->target_max.y = std::max(target_min.y, target_max.y); + entry.ival(6) = engine->target_max.z = std::max(target_min.z, target_max.z); + + return true; +} + +/* + * Trajectory + */ + +struct ProjectilePath { + df::coord origin, target; + int divisor; + df::coord speed, direction; + + ProjectilePath(df::coord origin, df::coord target) : + origin(origin), target(target) + { + speed = target - origin; + divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); + if (divisor <= 0) divisor = 1; + direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); + } + + df::coord operator[] (int i) const { + int div2 = divisor * 2; + int bias = divisor-1; + return origin + df::coord( + (2*speed.x*i + direction.x*bias)/div2, + (2*speed.y*i + direction.y*bias)/div2, + (2*speed.z*i + direction.z*bias)/div2 + ); + } +}; + +static bool isPassableTile(df::coord pos) +{ + auto ptile = Maps::getTileType(pos); + return !ptile || FlowPassable(*ptile); +} + +struct PathMetrics { + enum CollisionType { + Impassable, + Floor, + Ceiling, + MapEdge + } hit_type; + + int collision_step; + int goal_step, goal_z_step; + std::vector coords; + + bool hits() { return goal_step != -1 && collision_step > goal_step; } + + PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + { + coords.clear(); + collision_step = goal_step = goal_z_step = -1; + + int step = 0; + df::coord prev_pos = path.origin; + if (list_coords) + coords.push_back(prev_pos); + + for (;;) { + df::coord cur_pos = path[++step]; + if (cur_pos == prev_pos) + break; + if (list_coords) + coords.push_back(cur_pos); + + if (cur_pos.z == goal.z) + { + if (goal_z_step == -1) + goal_z_step = step; + if (cur_pos == goal) + goal_step = step; + } + + if (!Maps::isValidTilePos(cur_pos)) + { + hit_type = PathMetrics::MapEdge; + break; + } + + if (!isPassableTile(cur_pos)) + { + hit_type = Impassable; + break; + } + + if (cur_pos.z != prev_pos.z) + { + int top_z = std::max(prev_pos.z, cur_pos.z); + auto ptile = Maps::getTileType(cur_pos.x, cur_pos.y, top_z); + + if (ptile && !LowPassable(*ptile)) + { + hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor); + break; + } + } + + prev_pos = cur_pos; + } + + collision_step = step; + } +}; + +void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); + + df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + + auto engine = find_engine(bld); + int min_distance, max_distance; + + if (bld->type == siegeengine_type::Ballista) + { + min_distance = 0; + max_distance = 200; + } + else + { + min_distance = 30; + max_distance = 100; + } + + df::coord cursor = Gui::getCursorPos(); + + for (int x = 0; x < size.x; x++) + { + for (int y = 0; y < size.y; y++) + { + df::coord tile_pos = view + df::coord(x,y,0); + if (tile_pos == cursor) + continue; + if (tile_pos.z == bld->z && + tile_pos.x >= bld->x1 && tile_pos.x <= bld->x2 && + tile_pos.y >= bld->y1 && tile_pos.y <= bld->y2) + continue; + + Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); + if (!cur_tile.valid()) + continue; + + ProjectilePath path(origin, tile_pos); + + if (path.speed.z != 0 && abs(path.speed.z) != path.divisor) { + path.divisor *= 20; + path.speed.x *= 20; + path.speed.y *= 20; + path.speed.z *= 20; + path.speed.z += 9; + } + + PathMetrics raytrace(path, tile_pos); + + int color; + if (raytrace.hits()) + { + if (raytrace.goal_step >= min_distance && + raytrace.goal_step <= max_distance) + color = COLOR_GREEN; + else + color = COLOR_CYAN; + } + else + color = COLOR_RED; + + if (cur_tile.fg && cur_tile.ch != ' ') + { + cur_tile.fg = color; + cur_tile.bg = 0; + } + else + { + cur_tile.fg = 0; + cur_tile.bg = color; + } + + cur_tile.bold = (engine && engine->onTarget(tile_pos)); + + if (cur_tile.tile) + cur_tile.tile_mode = Pen::CharColor; + + Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); + } + } +} + +/* + * Initialization + */ + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(clearTargetArea), + DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(paintAimScreen), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_END +}; + +static bool is_enabled = false; + +static void enable_hooks(bool enable) +{ + is_enabled = enable; + + if (enable) + load_engines(); +} + +static bool enable_plugin() +{ + if (is_enabled) + return true; + + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL); + if (!entry.isValid()) + return false; + + enable_hooks(true); + return true; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + { + auto pworld = Core::getInstance().getWorld(); + bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid(); + + if (enable) + { + out.print("Enabling the siege engine plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + } + break; + case SC_MAP_UNLOADED: + enable_hooks(false); + 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/lua/siege-engine.lua b/plugins/lua/siege-engine.lua new file mode 100644 index 000000000..01b5d1447 --- /dev/null +++ b/plugins/lua/siege-engine.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.siege-engine') + +--[[ + + Native functions: + + * getTargetArea(building) -> point1, point2 + * clearTargetArea(building) + * setTargetArea(building, point1, point2) -> true/false + +--]] + +return _ENV \ No newline at end of file diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua new file mode 100644 index 000000000..466657e56 --- /dev/null +++ b/scripts/gui/siege-engine.lua @@ -0,0 +1,72 @@ +-- Front-end for the siege engine plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local plugin = require 'plugins.siege-engine' + +SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) + +SiegeEngine.focus_path = 'siege-engine' + +function SiegeEngine:init(building) + self:init_fields{ + building = building, + center = utils.getBuildingCenter(building), + links = {}, selected = 1 + } + guidm.MenuOverlay.init(self) + return self +end + +function SiegeEngine:onShow() + guidm.MenuOverlay.onShow(self) + + self.old_cursor = guidm.getCursorPos() + self.old_viewport = self:getViewport() +end + +function SiegeEngine:onDestroy() + guidm.setCursorPos(self.old_cursor) + self:getViewport(self.old_viewport):set() +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() + + local view = self:getViewport() + local map = self.df_layout.map + + plugin.paintAimScreen( + self.building, view:getPos(), + xy2pos(map.x1, map.y1), view:getSize() + ) + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") +end + +function SiegeEngine:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + elseif self:simulateCursorMovement(keys, self.center) then + return + end +end + +if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then + qerror("This script requires the main dwarfmode view in 'q' mode") +end + +local building = df.global.world.selected_building + +if not df.building_siegeenginest:is_instance(building) then + qerror("A siege engine must be selected") +end + +local list = mkinstance(SiegeEngine):init(df.global.world.selected_building) +list:show() From bfa6ed3e0868c7a2fa9851c3feb8f9055b0330bb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 13:46:02 +0400 Subject: [PATCH 53/61] Support setting the target area for the siege engine. --- library/lua/gui/dwarfmode.lua | 29 +++- library/modules/Gui.cpp | 7 +- plugins/devel/siege-engine.cpp | 160 +++++++++++++------- scripts/devel/pop-screen.lua | 3 + scripts/gui/mechanisms.lua | 2 +- scripts/gui/siege-engine.lua | 264 +++++++++++++++++++++++++++++++-- 6 files changed, 390 insertions(+), 75 deletions(-) create mode 100644 scripts/devel/pop-screen.lua diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 21a942174..661e15591 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -46,7 +46,7 @@ function getPanelLayout() end function getCursorPos() - if g_cursor ~= -30000 then + if g_cursor.x ~= -30000 then return copyall(g_cursor) end end @@ -167,6 +167,18 @@ function Viewport:isVisible(target,gap) return self:isVisibleXY(target,gap) and target.z == self.z end +function Viewport:tileToScreen(coord) + return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z) +end + +function Viewport:getCenter() + return xyz2pos( + math.floor((self.x2+self.x1)/2), + math.floor((self.y2+self.y1)/2), + self.z + ) +end + function Viewport:centerOn(target) return self:clip( target.x - math.floor(self.width/2), @@ -253,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp) end end -function DwarfOverlay:moveCursorTo(cursor,viewport) +function DwarfOverlay:moveCursorTo(cursor,viewport,gap) setCursorPos(cursor) - self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() + self:zoomViewportTo(cursor,viewport,gap) +end + +function DwarfOverlay:zoomViewportTo(target, viewport, gap) + if gap and self:getViewport():isVisible(target, gap) then + return + end + self:getViewport(viewport):reveal(target, 5, 0, 10):set() end -function DwarfOverlay:selectBuilding(building,cursor,viewport) +function DwarfOverlay:selectBuilding(building,cursor,viewport,gap) cursor = cursor or utils.getBuildingCenter(building) df.global.world.selected_building = building - self:moveCursorTo(cursor, viewport) + self:moveCursorTo(cursor, viewport, gap) end function DwarfOverlay:propagateMoveKeys(keys) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 8de908734..91df14eaf 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -173,10 +173,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) else if (id == &df::building_trapst::_identity) { auto trap = (df::building_trapst*)selected; - if (trap->trap_type == trap_type::Lever) { - focus += "/Lever"; + focus += "/" + enum_item_key(trap->trap_type); + if (trap->trap_type == trap_type::Lever) jobs = true; - } } else if (ui_building_in_assign && *ui_building_in_assign && ui_building_assign_type && ui_building_assign_units && @@ -189,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) focus += unit ? "/Unit" : "/None"; } } + else + focus += "/" + enum_item_key(selected->getType()); if (jobs) { diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 189c43ad5..6906f540c 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -47,6 +47,65 @@ using Screen::Pen; DFHACK_PLUGIN("siege-engine"); +/* + * Misc. utils + */ + +typedef std::pair coord_range; + +static void set_range(coord_range *target, df::coord p1, df::coord p2) +{ + if (!p1.isValid() || !p2.isValid()) + { + *target = coord_range(); + } + else + { + target->first.x = std::min(p1.x, p2.x); + target->first.y = std::min(p1.y, p2.y); + target->first.z = std::min(p1.z, p2.z); + target->second.x = std::max(p1.x, p2.x); + target->second.y = std::max(p1.y, p2.y); + target->second.z = std::max(p1.z, p2.z); + } +} + +static bool is_range_valid(const coord_range &target) +{ + return target.first.isValid() && target.second.isValid(); +} + +static bool is_in_range(const coord_range &target, df::coord pos) +{ + return target.first.isValid() && target.second.isValid() && + target.first.x <= pos.x && pos.x <= target.second.x && + target.first.y <= pos.y && pos.y <= target.second.y && + target.first.z <= pos.z && pos.z <= target.second.z; +} + +static std::pair get_engine_range(df::building_siegeenginest *bld) +{ + if (bld->type == siegeengine_type::Ballista) + return std::make_pair(0, 200); + else + return std::make_pair(30, 100); +} + +static void orient_engine(df::building_siegeenginest *bld, df::coord target) +{ + int dx = target.x - bld->centerx; + int dy = target.y - bld->centery; + + if (abs(dx) > abs(dy)) + bld->facing = (dx > 0) ? + df::building_siegeenginest::Right : + df::building_siegeenginest::Left; + else + bld->facing = (dy > 0) ? + df::building_siegeenginest::Down : + df::building_siegeenginest::Up; +} + /* * Configuration management */ @@ -55,17 +114,10 @@ static bool enable_plugin(); struct EngineInfo { int id; - df::coord target_min, target_max; + coord_range target; - bool hasTarget() { - return target_min.isValid() && target_max.isValid(); - } - bool onTarget(df::coord pos) { - return hasTarget() && - target_min.x <= pos.x && pos.x <= target_max.x && - target_min.y <= pos.y && pos.y <= target_max.y && - target_min.z <= pos.z && pos.z <= target_max.z; - } + bool hasTarget() { return is_range_valid(target); } + bool onTarget(df::coord pos) { return is_in_range(target, pos); } }; static std::map engines; @@ -98,8 +150,8 @@ static void load_engines() { auto engine = find_engine(df::building::find(it->ival(0)), true); if (!engine) continue; - engine->target_min = df::coord(it->ival(1), it->ival(2), it->ival(3)); - engine->target_max = df::coord(it->ival(4), it->ival(5), it->ival(6)); + engine->target.first = df::coord(it->ival(1), it->ival(2), it->ival(3)); + engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6)); } } @@ -109,10 +161,10 @@ static int getTargetArea(lua_State *L) if (!bld) luaL_argerror(L, 1, "null building"); auto engine = find_engine(bld); - if (engine && engine->target_min.isValid()) + if (engine && engine->hasTarget()) { - Lua::Push(L, engine->target_min); - Lua::Push(L, engine->target_max); + Lua::Push(L, engine->target.first); + Lua::Push(L, engine->target.second); } else { @@ -128,7 +180,7 @@ static void clearTargetArea(df::building_siegeenginest *bld) CHECK_NULL_POINTER(bld); if (auto engine = find_engine(bld)) - engine->target_min = engine->target_max = df::coord(); + engine->target = coord_range(); auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); @@ -151,13 +203,18 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, auto engine = find_engine(bld, true); + set_range(&engine->target, target_min, target_max); + entry.ival(0) = bld->id; - entry.ival(1) = engine->target_min.x = std::min(target_min.x, target_max.x); - entry.ival(2) = engine->target_min.y = std::min(target_min.y, target_max.y); - entry.ival(3) = engine->target_min.z = std::min(target_min.z, target_max.z); - entry.ival(4) = engine->target_max.x = std::max(target_min.x, target_max.x); - entry.ival(5) = engine->target_max.y = std::max(target_min.y, target_max.y); - entry.ival(6) = engine->target_max.z = std::max(target_min.z, target_max.z); + entry.ival(1) = engine->target.first.x; + entry.ival(2) = engine->target.first.y; + entry.ival(3) = engine->target.first.z; + entry.ival(4) = engine->target.second.x; + entry.ival(5) = engine->target.second.y; + entry.ival(6) = engine->target.second.z; + + df::coord sum = target_min + target_max; + orient_engine(bld, df::coord(sum.x/2, sum.y/2, sum.z/2)); return true; } @@ -267,38 +324,45 @@ struct PathMetrics { } }; -void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - CHECK_NULL_POINTER(bld); + df::coord origin(bld->centerx, bld->centery, bld->z); + auto fire_range = get_engine_range(bld); - df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + ProjectilePath path(origin, tile_pos); + PathMetrics raytrace(path, tile_pos); - auto engine = find_engine(bld); - int min_distance, max_distance; - - if (bld->type == siegeengine_type::Ballista) + if (raytrace.hits()) { - min_distance = 0; - max_distance = 200; + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) + return "ok"; + else + return "out_of_range"; } else - { - min_distance = 30; - max_distance = 100; - } + return "blocked"; +} + +static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); - df::coord cursor = Gui::getCursorPos(); + df::coord origin(bld->centerx, bld->centery, bld->z); + coord_range building_rect( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + + auto engine = find_engine(bld); + auto fire_range = get_engine_range(bld); for (int x = 0; x < size.x; x++) { for (int y = 0; y < size.y; y++) { df::coord tile_pos = view + df::coord(x,y,0); - if (tile_pos == cursor) - continue; - if (tile_pos.z == bld->z && - tile_pos.x >= bld->x1 && tile_pos.x <= bld->x2 && - tile_pos.y >= bld->y1 && tile_pos.y <= bld->y2) + if (is_in_range(building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); @@ -306,22 +370,13 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d continue; ProjectilePath path(origin, tile_pos); - - if (path.speed.z != 0 && abs(path.speed.z) != path.divisor) { - path.divisor *= 20; - path.speed.x *= 20; - path.speed.y *= 20; - path.speed.z *= 20; - path.speed.z += 9; - } - PathMetrics raytrace(path, tile_pos); int color; if (raytrace.hits()) { - if (raytrace.goal_step >= min_distance && - raytrace.goal_step <= max_distance) + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -357,6 +412,7 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(clearTargetArea), DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(getTileStatus), DFHACK_LUA_FUNCTION(paintAimScreen), DFHACK_LUA_END }; diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua new file mode 100644 index 000000000..f1ee072c5 --- /dev/null +++ b/scripts/devel/pop-screen.lua @@ -0,0 +1,3 @@ +-- For killing bugged out gui script screens. + +dfhack.screen.dismiss(dfhack.gui.getCurViewscreen()) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 4468e1dcb..c14bfcbe9 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -122,7 +122,7 @@ function MechanismList:onInput(keys) end end -if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then qerror("This script requires the main dwarfmode view in 'q' mode") end diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 466657e56..bba6bce89 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -6,6 +6,11 @@ local guidm = require 'gui.dwarfmode' local dlg = require 'gui.dialogs' local plugin = require 'plugins.siege-engine' +local wmap = df.global.world.map + +-- Globals kept between script calls +last_target_min = last_target_min or nil +last_target_max = last_target_max or nil SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) @@ -15,9 +20,17 @@ function SiegeEngine:init(building) self:init_fields{ building = building, center = utils.getBuildingCenter(building), - links = {}, selected = 1 + links = {}, selected = 1, } guidm.MenuOverlay.init(self) + self.mode_main = { + render = self:callback 'onRenderBody_main', + input = self:callback 'onInput_main', + } + self.mode_aim = { + render = self:callback 'onRenderBody_aim', + input = self:callback 'onInput_aim', + } return self end @@ -26,40 +39,263 @@ function SiegeEngine:onShow() self.old_cursor = guidm.getCursorPos() self.old_viewport = self:getViewport() + + self.mode = self.mode_main + self:showCursor(false) end function SiegeEngine:onDestroy() - guidm.setCursorPos(self.old_cursor) - self:getViewport(self.old_viewport):set() + self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) end -function SiegeEngine:onRenderBody(dc) - dc:clear() - dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() +function SiegeEngine:showCursor(enable) + local cursor = guidm.getCursorPos() + if cursor and not enable then + self.cursor = cursor + self.target_select_first = nil + guidm.clearCursorPos() + elseif not cursor and enable then + local view = self:getViewport() + cursor = self.cursor + if not cursor or not view:isVisible(cursor) then + cursor = view:getCenter() + end + self.cursor = nil + guidm.setCursorPos(cursor) + end +end +function SiegeEngine:centerViewOn(pos) + local cursor = guidm.getCursorPos() + if cursor then + guidm.setCursorPos(pos) + else + self.cursor = pos + end + self:getViewport():centerOn(pos):set() +end + +function SiegeEngine:zoomToTarget() + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + local cx = math.floor((target_min.x + target_max.x)/2) + local cy = math.floor((target_min.y + target_max.y)/2) + local cz = math.floor((target_min.z + target_max.z)/2) + for z = cz,target_max.z do + if plugin.getTileStatus(self.building, xyz2pos(cx,cy,z)) ~= 'blocked' then + cz = z + break + end + end + self:centerViewOn(xyz2pos(cx,cy,cz)) + end +end + +function paint_target_grid(dc, view, origin, p1, p2) + local r1, sz, r2 = guidm.getSelectionRange(p1, p2) + + if view.z < r1.z or view.z > r2.z then + return + end + + local p1 = view:tileToScreen(r1) + local p2 = view:tileToScreen(r2) + local org = view:tileToScreen(origin) + dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true } + + -- Frame + dc:fill(p1.x,p1.y,p1.x,p2.y) + dc:fill(p1.x,p1.y,p2.x,p1.y) + dc:fill(p2.x,p1.y,p2.x,p2.y) + dc:fill(p1.x,p2.y,p2.x,p2.y) + + -- Grid + local gxmin = org.x+10*math.ceil((p1.x-org.x)/10) + local gxmax = org.x+10*math.floor((p2.x-org.x)/10) + local gymin = org.y+10*math.ceil((p1.y-org.y)/10) + local gymax = org.y+10*math.floor((p2.y-org.y)/10) + for x = gxmin,gxmax,10 do + for y = gymin,gymax,10 do + dc:fill(p1.x,y,p2.x,y) + dc:fill(x,p1.y,x,p2.y) + end + end +end + +function SiegeEngine:renderTargetView(target_min, target_max) local view = self:getViewport() local map = self.df_layout.map + local map_dc = gui.Painter.new(map) plugin.paintAimScreen( self.building, view:getPos(), xy2pos(map.x1, map.y1), view:getSize() ) - dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") + if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then + paint_target_grid(map_dc, view, self.center, target_min, target_max) + end + + local cursor = guidm.getCursorPos() + if cursor then + local cx, cy, cz = pos2xyz(view:tileToScreen(cursor)) + if cz == 0 then + map_dc:seek(cx,cy):char('X', COLOR_YELLOW) + end + end +end + +function SiegeEngine:onRenderBody_main(dc) + dc:newline(1):pen(COLOR_WHITE):string("Target: ") + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:string( + (target_max.x-target_min.x+1).."x".. + (target_max.y-target_min.y+1).."x".. + (target_max.z-target_min.z+1).." Rect" + ) + else + dc:string("None (default)") + end + + dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") + if last_target_min then + dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") + end + dc:newline(3) + if target_min then + dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") + dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") + end + + if self.target_select_first then + self:renderTargetView(self.target_select_first, guidm.getCursorPos()) + else + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:setTargetArea(p1, p2) + self.target_select_first = nil + + if not plugin.setTargetArea(self.building, p1, p2) then + dlg.showMessage( + 'Set Target Area', + 'Could not set the target area', COLOR_LIGHTRED + ) + else + last_target_min = p1 + last_target_max = p2 + end +end + +function SiegeEngine:onInput_main(keys) + if keys.CUSTOM_R then + self:showCursor(true) + self.target_select_first = nil + self.mode = self.mode_aim + elseif keys.CUSTOM_P and last_target_min then + self:setTargetArea(last_target_min, last_target_max) + elseif keys.CUSTOM_Z then + self:zoomToTarget() + elseif keys.CUSTOM_X then + plugin.clearTargetArea(self.building) + elseif self:simulateViewScroll(keys) then + self.cursor = nil + else + return false + end + return true +end + +local status_table = { + ok = { pen = COLOR_GREEN, msg = "Target accessible" }, + out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, + blocked = { pen = COLOR_RED, msg = "Target obstructed" }, +} + +function SiegeEngine:onRenderBody_aim(dc) + local cursor = guidm.getCursorPos() + local first = self.target_select_first + + dc:newline(1):string('Select target rectangle'):newline() + + local info = status_table[plugin.getTileStatus(self.building, cursor)] + if info then + dc:newline(2):string(info.msg, info.pen) + else + dc:newline(2):string('ERROR', COLOR_RED) + end + + dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) + if first then + dc:string(": Finish rectangle") + else + dc:string(": Start rectangle") + end + dc:newline() + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target") + end + + if first then + self:renderTargetView(first, cursor) + else + local target_min, target_max = plugin.getTargetArea(self.building) + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:onInput_aim(keys) + if keys.SELECT then + local cursor = guidm.getCursorPos() + if self.target_select_first then + self:setTargetArea(self.target_select_first, cursor) + + self.mode = self.mode_main + self:showCursor(false) + else + self.target_select_first = cursor + end + elseif keys.CUSTOM_Z then + self:zoomToTarget() + elseif keys.LEAVESCREEN then + self.mode = self.mode_main + self:showCursor(false) + elseif self:simulateCursorMovement(keys) then + self.cursor = nil + else + return false + end + return true +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() + + self.mode.render(dc) + + dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) + dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("c", COLOR_LIGHTGREEN):string(": Recenter") end function SiegeEngine:onInput(keys) - if keys.LEAVESCREEN then + if self.mode.input(keys) then + -- + elseif keys.CUSTOM_C then + self:centerViewOn(self.center) + elseif keys.LEAVESCREEN then self:dismiss() - elseif self:simulateCursorMovement(keys, self.center) then - return end end -if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then - qerror("This script requires the main dwarfmode view in 'q' mode") +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then + qerror("This script requires a siege engine selected in 'q' mode") end local building = df.global.world.selected_building From 003c3391d1a1712c79ef3c151441bfc798fee3d0 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 15:49:46 +0400 Subject: [PATCH 54/61] Implement aiming projectiles at random points in the designated area. --- plugins/devel/siege-engine.cpp | 252 ++++++++++++++++++++++++++++----- 1 file changed, 219 insertions(+), 33 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 6906f540c..86fe3ba5e 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -29,6 +29,7 @@ #include "df/ui_build_selector.h" #include "df/flow_info.h" #include "df/report.h" +#include "df/proj_itemst.h" #include "MiscUtils.h" @@ -106,6 +107,11 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target) df::building_siegeenginest::Up; } +static int random_int(int val) +{ + return int(int64_t(rand())*val/RAND_MAX); +} + /* * Configuration management */ @@ -115,12 +121,15 @@ static bool enable_plugin(); struct EngineInfo { int id; coord_range target; + df::coord center; bool hasTarget() { return is_range_valid(target); } bool onTarget(df::coord pos) { return is_in_range(target, pos); } + df::coord getTargetSize() { return target.second - target.first; } }; static std::map engines; +static std::map coord_engines; static EngineInfo *find_engine(df::building *bld, bool create = false) { @@ -135,12 +144,26 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) auto *obj = &engines[bld]; obj->id = bld->id; + obj->center = df::coord(bld->centerx, bld->centery, bld->z); + + coord_engines[obj->center] = bld; return obj; } -static void load_engines() +static EngineInfo *find_engine(df::coord pos) +{ + return find_engine(coord_engines[pos]); +} + +static void clear_engines() { engines.clear(); + coord_engines.clear(); +} + +static void load_engines() +{ + clear_engines(); auto pworld = Core::getInstance().getWorld(); std::vector vec; @@ -224,12 +247,30 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, */ struct ProjectilePath { - df::coord origin, target; - int divisor; + df::coord origin, goal, target, fudge_delta; + int divisor, fudge_factor; df::coord speed, direction; - ProjectilePath(df::coord origin, df::coord target) : - origin(origin), target(target) + ProjectilePath(df::coord origin, df::coord goal) : + origin(origin), goal(goal), target(goal), fudge_factor(1) + { + fudge_delta = df::coord(0,0,0); + calc_line(); + } + + void fudge(int factor, df::coord delta) + { + fudge_factor = factor; + fudge_delta = delta; + auto diff = goal - origin; + diff.x *= fudge_factor; + diff.y *= fudge_factor; + diff.z *= fudge_factor; + target = origin + diff + fudge_delta; + calc_line(); + } + + void calc_line() { speed = target - origin; divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); @@ -237,7 +278,8 @@ struct ProjectilePath { direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); } - df::coord operator[] (int i) const { + df::coord operator[] (int i) const + { int div2 = divisor * 2; int bias = divisor-1; return origin + df::coord( @@ -266,12 +308,12 @@ struct PathMetrics { int goal_step, goal_z_step; std::vector coords; - bool hits() { return goal_step != -1 && collision_step > goal_step; } + bool hits() { return collision_step > goal_step; } - PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + PathMetrics(const ProjectilePath &path, bool list_coords = false) { coords.clear(); - collision_step = goal_step = goal_z_step = -1; + collision_step = goal_step = goal_z_step = 1000000; int step = 0; df::coord prev_pos = path.origin; @@ -285,11 +327,10 @@ struct PathMetrics { if (list_coords) coords.push_back(cur_pos); - if (cur_pos.z == goal.z) + if (cur_pos.z == path.goal.z) { - if (goal_z_step == -1) - goal_z_step = step; - if (cur_pos == goal) + goal_z_step = std::min(step, goal_z_step); + if (cur_pos == path.goal) goal_step = step; } @@ -324,18 +365,68 @@ struct PathMetrics { } }; +struct AimContext { + df::building_siegeenginest *bld; + df::coord origin; + coord_range building_rect; + EngineInfo *engine; + std::pair fire_range; + + AimContext(df::building_siegeenginest *bld, EngineInfo *engine) + : bld(bld), engine(engine) + { + origin = df::coord(bld->centerx, bld->centery, bld->z); + building_rect = coord_range( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + fire_range = get_engine_range(bld); + } + + bool isInRange(const PathMetrics &raytrace) + { + return raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second; + } + + bool adjustToPassable(df::coord *pos) + { + if (isPassableTile(*pos)) + return true; + + for (df::coord fudge = *pos; + fudge.z < engine->target.second.z; fudge.z++) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + for (df::coord fudge = *pos; + fudge.z > engine->target.first.z; fudge.z--) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + return false; + } + +}; + static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - df::coord origin(bld->centerx, bld->centery, bld->z); - auto fire_range = get_engine_range(bld); + AimContext context(bld, NULL); - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) return "ok"; else return "out_of_range"; @@ -348,35 +439,27 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: { CHECK_NULL_POINTER(bld); - df::coord origin(bld->centerx, bld->centery, bld->z); - coord_range building_rect( - df::coord(bld->x1, bld->y1, bld->z), - df::coord(bld->x2, bld->y2, bld->z) - ); - - auto engine = find_engine(bld); - auto fire_range = get_engine_range(bld); + AimContext context(bld, find_engine(bld)); for (int x = 0; x < size.x; x++) { for (int y = 0; y < size.y; y++) { df::coord tile_pos = view + df::coord(x,y,0); - if (is_in_range(building_rect, tile_pos)) + if (is_in_range(context.building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); if (!cur_tile.valid()) continue; - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); int color; if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -395,7 +478,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: cur_tile.bg = color; } - cur_tile.bold = (engine && engine->onTarget(tile_pos)); + cur_tile.bold = (context.engine && context.engine->onTarget(tile_pos)); if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; @@ -405,6 +488,105 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: } } +/* + * Projectile hook + */ + +struct projectile_hook : df::proj_itemst { + typedef df::proj_itemst interpose_base; + + void aimAtPoint(AimContext &context, ProjectilePath &path, bool bad_shot = false) + { + target_pos = path.target; + + PathMetrics raytrace(path); + + // Materialize map blocks, or the projectile will crash into them + for (int i = 0; i < raytrace.collision_step; i++) + Maps::ensureTileBlock(path[i]); + + if (flags.bits.piercing) + { + if (bad_shot) + fall_threshold = std::min(raytrace.goal_z_step, raytrace.collision_step); + } + else + { + if (bad_shot) + fall_threshold = context.fire_range.second; + else + fall_threshold = raytrace.goal_step; + } + + fall_threshold = std::max(fall_threshold, context.fire_range.first); + fall_threshold = std::min(fall_threshold, context.fire_range.second); + } + + void aimAtArea(AimContext &context) + { + df::coord target, last_passable; + df::coord tbase = context.engine->target.first; + df::coord tsize = context.engine->getTargetSize(); + bool success = false; + + for (int i = 0; i < 50; i++) + { + target = tbase + df::coord( + random_int(tsize.x), random_int(tsize.y), random_int(tsize.z) + ); + + if (context.adjustToPassable(&target)) + last_passable = target; + else + continue; + + ProjectilePath path(context.origin, target); + PathMetrics raytrace(path); + + if (raytrace.hits() && context.isInRange(raytrace)) + { + aimAtPoint(context, path); + return; + } + } + + if (!last_passable.isValid()) + last_passable = target; + + ProjectilePath path(context.origin, last_passable); + aimAtPoint(context, path, true); + } + + void doCheckMovement() + { + if (distance_flown != 0 || fall_counter != fall_delay) + return; + + auto engine = find_engine(origin_pos); + if (!engine || !engine->hasTarget()) + return; + + auto bld0 = df::building::find(engine->id); + auto bld = strict_virtual_cast(bld0); + if (!bld) + return; + + AimContext context(bld, engine); + + aimAtArea(context); + } + + DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) + { + if (flags.bits.high_flying || flags.bits.piercing) + doCheckMovement(); + + return INTERPOSE_NEXT(checkMovement)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); + /* * Initialization */ @@ -428,8 +610,12 @@ static void enable_hooks(bool enable) { is_enabled = enable; + INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + if (enable) load_engines(); + else + clear_engines(); } static bool enable_plugin() From fb88aad51d9a49f489ed99fb1c33f9212b38c068 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 21:07:18 +0400 Subject: [PATCH 55/61] Reverse-engineer unit speed computation from DF code. --- library/xml | 2 +- plugins/devel/siege-engine.cpp | 293 +++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index a914f3b75..18e76d8bd 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29 +Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 86fe3ba5e..60035b276 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,14 @@ #include "df/flow_info.h" #include "df/report.h" #include "df/proj_itemst.h" +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/unit_skill.h" +#include "df/physical_attribute_type.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" +#include "df/caste_raw_flags.h" +#include "df/assumed_identity.h" #include "MiscUtils.h" @@ -488,6 +497,290 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: } } +/* + * Unit tracking + */ + +static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) +{ + auto creature = df::creature_raw::find(race); + if (!creature) + return false; + + auto craw = vector_get(creature->caste, caste); + if (!craw) + return false; + + return craw->flags.is_set(flag); +} + +static bool hasExtravision(df::unit *unit) +{ + if (unit->curse.rem_tags1.bits.EXTRAVISION) + return false; + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); +} + +int getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + CHECK_NULL_POINTER(unit); + + if (!unit->status.current_soul) + return 0; + + int rating = 0; + + df::enum_field key(skill_id); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + if (skill) + rating = std::max(0, int(skill->rating) - skill->rusty); + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) rating >>= 1; + if (unit->counters.winded > 0) rating >>= 1; + if (unit->counters.stunned > 0) rating >>= 1; + if (unit->counters.dizziness > 0) rating >>= 1; + if (unit->counters2.fever > 0) rating >>= 1; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood == -1) rating >>= 1; + + if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && + !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && + !hasExtravision(unit)) + { + rating >>= 2; + } + if (unit->counters2.exhaustion >= 2000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 4000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 6000) + rating = rating*3/4; + } + } + } + + // TODO: bloodsucker; advmode + + if (unit->counters2.thirst_timer >= 50000) rating >>= 1; + if (unit->counters2.hunger_timer >= 75000) rating >>= 1; + if (unit->counters2.sleepiness_timer >= 150000) rating >>= 1; + + return rating; +} + +static int getAttrValue(const df::unit_attribute &attr) +{ + return std::max(0, attr.value - attr.soft_demotion); +} + +static int getPhysAttrValue(df::unit *unit, df::physical_attribute_type attr, bool hide_curse) +{ + int value = getAttrValue(unit->body.physical_attrs[attr]); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + + if (hide_curse) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + +int getSpeedRating(df::unit *unit) +{ + using namespace df::enums::physical_attribute_type; + + // Base speed + auto creature = df::creature_raw::find(unit->race); + if (!creature) + return 0; + + auto craw = vector_get(creature->caste, unit->caste); + if (!craw) + return 0; + + int speed = craw->misc.speed; + + if (unit->flags3.bits.ghostly) + return speed; + + // Curse multiplier + if (unit->curse.speed_mul_percent != 100) + { + speed *= 100; + if (unit->curse.speed_mul_percent != 0) + speed /= unit->curse.speed_mul_percent; + } + + speed += unit->curse.speed_add; + + // Swimming + if (unit->flags2.bits.swimming) + { + speed = craw->misc.swim_speed; + if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) + speed *= 2; + if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) + { + int skill = getEffectiveSkill(unit, job_skill::SWIMMING); + if (skill > 1) + skill = skill * std::max(6, 21-skill) / 20; + } + } + else + { + if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Water) + speed += 150*unit->status2.liquid_depth; + else if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) + speed += 300*unit->status2.liquid_depth; + } + + // General counters + if (unit->profession == profession::BABY) + speed += 3000; + + if (unit->counters2.exhaustion >= 2000) + { + speed += 200; + if (unit->counters2.exhaustion >= 4000) + { + speed += 200; + if (unit->counters2.exhaustion >= 6000) + speed += 200; + } + } + + if (unit->flags2.bits.gutted) speed += 2000; + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) speed += 1000; + if (unit->counters.winded > 0) speed += 1000; + if (unit->counters.stunned > 0) speed += 1000; + if (unit->counters.dizziness > 0) speed += 1000; + if (unit->counters2.fever > 0) speed += 1000; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood < 0) + speed += 1000; + } + + // TODO: bloodsucker + + if (unit->counters2.thirst_timer >= 50000) speed += 100; + if (unit->counters2.hunger_timer >= 75000) speed += 100; + if (unit->counters2.sleepiness_timer >= 150000) speed += 200; + else if (unit->counters2.sleepiness_timer >= 57600) speed += 100; + + if (unit->relations.draggee_id != -1) speed += 1000; + + if (unit->flags1.bits.on_ground) + speed += 2000; + else if (unit->flags3.bits.on_crutch) + { + int skill = getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + speed += 2000 - 100*std::min(20, skill); + } + + // TODO: hidden_in_ambush + + if (unsigned(unit->counters2.paralysis-1) <= 98) + speed += unit->counters2.paralysis*10; + if (unsigned(unit->counters.webbed-1) <= 8) + speed += unit->counters.webbed*100; + + // Muscle weight vs agility + auto &attr_unk3 = unit->body.physical_attr_unk3; + int strength = attr_unk3[STRENGTH]; + int agility = attr_unk3[AGILITY]; + speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); + + // Attributes + bool hide_curse = false; + if (!unit->job.hunt_target) + { + auto identity = Units::getIdentity(unit); + if (identity && identity->unk_4c == 0) + hide_curse = true; + } + + int strength_attr = getPhysAttrValue(unit, STRENGTH, hide_curse); + int agility_attr = getPhysAttrValue(unit, AGILITY, hide_curse); + int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); + + speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? + + if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) + { + // WTF + int as = unit->status2.able_stand; + int x = (as-1) - (as>>1); + int y = as - unit->status2.able_stand_impair; + if (unit->flags3.bits.on_crutch) y--; + y = y * 500 / x; + if (y > 0) speed += y; + } + + if (unit->mood == mood_type::Melancholy) speed += 8000; + + // Inventory encumberance + int armor_skill = getEffectiveSkill(unit, job_skill::ARMOR); + armor_skill = std::min(15, armor_skill); + + int inv_weight = 0, inv_weight_fraction = 0; + + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]->item; + if (!item->flags.bits.weight_computed) + continue; + + int wval = item->weight; + int wfval = item->weight_fraction; + + auto mode = unit->inventory[i]->mode; + if ((mode == df::unit_inventory_item::Worn || + mode == df::unit_inventory_item::WrappedAround) && + item->isArmor() && armor_skill > 1) + { + wval = wval * (15 - armor_skill) / 16; + wfval = wfval * (15 - armor_skill) / 16; + } + + inv_weight += wval; + inv_weight_fraction += wfval; + } + + int total_weight = inv_weight*100 + inv_weight_fraction/10000; + int free_weight = std::max(1, attr_unk3[STRENGTH]/10 + strength_attr*3); + + if (free_weight < total_weight) + { + int delta = (total_weight - free_weight)/10; + delta = std::min(5000, delta); // dwarfmode only + speed += delta; + } + + // skipped: unknown loop on inventory items that amounts to 0 change + + return std::min(10000, std::max(0, speed)); +} + /* * Projectile hook */ From 8e0f3e3bce25484ecd37280e764302737acb8443 Mon Sep 17 00:00:00 2001 From: warmist Date: Sun, 9 Sep 2012 02:28:07 +0300 Subject: [PATCH 56/61] Added ListBox to gui.dialogs A listbox class. Can be either filled with table of strings, or string+callback tables. Needs some code revision :) --- library/lua/gui/dialogs.lua | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index c4f15c9ac..dc4358119 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -172,5 +172,100 @@ function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_wi }:show() end +ListBox = defclass(ListBox, MessageBox) + +ListBox.focus_path = 'ListBox' + +function ListBox:init(info) + info = info or {} + self:init_fields{ + selection = info.selection or 0, + choices = info.choices or {}, + select_pen = info.select_pen, + on_input = info.on_input, + page_top = 0 + } + MessageBox.init(self, info) + self.on_accept = nil + return self +end + +function ListBox:getWantedFrameSize() + local mw, mh = MessageBox.getWantedFrameSize(self) + return mw, mh+#self.choices +end + +function ListBox:onRenderBody(dc) + MessageBox.onRenderBody(self, dc) + + dc:newline(1) + + if self.selection>dc.height-3 then + self.page_top=self.selection-(dc.height-3) + elseif self.selection0 then + self.page_top=self.selection-1 + end + for i,entry in ipairs(self.choices) do + if type(entry)=="table" then + entry=entry[1] + end + if i>self.page_top then + if i == self.selection then + dc:pen(self.select_pen or COLOR_LIGHTCYAN) + else + dc:pen(self.text_pen or COLOR_GREY) + end + dc:string(entry) + dc:newline(1) + end + end +end +function ListBox:moveCursor(delta) + local newsel=self.selection+delta + if #self.choices ~=0 then + if newsel<1 or newsel>#self.choices then + newsel=newsel % #self.choices + end + end + self.selection=newsel +end +function ListBox:onInput(keys) + if keys.SELECT then + self:dismiss() + local choice=self.choices[self.selection] + if self.on_input then + self.on_input(self.selection,choice) + end + + if choice and choice[2] then + choice[2](choice,self.selection) -- maybe reverse the arguments? + end + elseif keys.LEAVESCREEN then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + elseif keys.CURSOR_UP then + self:moveCursor(-1) + elseif keys.CURSOR_DOWN then + self:moveCursor(1) + elseif keys.CURSOR_UP_FAST then + self:moveCursor(-10) + elseif keys.CURSOR_DOWN_FAST then + self:moveCursor(10) + end +end + +function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) + mkinstance(ListBox):init{ + title = title, + text = text, + text_pen = tcolor, + choices = choices, + on_input = on_input, + on_cancel = on_cancel, + frame_width = min_width, + }:show() +end return _ENV From 94b729579e924bb511523a1aa157cec7fdb66bdf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 10:53:08 +0400 Subject: [PATCH 57/61] Reindent to remove tabs. --- library/lua/gui/dialogs.lua | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index dc4358119..eb883465f 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -180,10 +180,10 @@ function ListBox:init(info) info = info or {} self:init_fields{ selection = info.selection or 0, - choices = info.choices or {}, + choices = info.choices or {}, select_pen = info.select_pen, on_input = info.on_input, - page_top = 0 + page_top = 0 } MessageBox.init(self, info) self.on_accept = nil @@ -197,62 +197,62 @@ end function ListBox:onRenderBody(dc) MessageBox.onRenderBody(self, dc) - + dc:newline(1) - - if self.selection>dc.height-3 then - self.page_top=self.selection-(dc.height-3) - elseif self.selection0 then - self.page_top=self.selection-1 - end - for i,entry in ipairs(self.choices) do - if type(entry)=="table" then - entry=entry[1] - end - if i>self.page_top then - if i == self.selection then - dc:pen(self.select_pen or COLOR_LIGHTCYAN) - else - dc:pen(self.text_pen or COLOR_GREY) - end - dc:string(entry) - dc:newline(1) - end - end + + if self.selection>dc.height-3 then + self.page_top=self.selection-(dc.height-3) + elseif self.selection0 then + self.page_top=self.selection-1 + end + for i,entry in ipairs(self.choices) do + if type(entry)=="table" then + entry=entry[1] + end + if i>self.page_top then + if i == self.selection then + dc:pen(self.select_pen or COLOR_LIGHTCYAN) + else + dc:pen(self.text_pen or COLOR_GREY) + end + dc:string(entry) + dc:newline(1) + end + end end function ListBox:moveCursor(delta) - local newsel=self.selection+delta - if #self.choices ~=0 then - if newsel<1 or newsel>#self.choices then - newsel=newsel % #self.choices - end - end - self.selection=newsel + local newsel=self.selection+delta + if #self.choices ~=0 then + if newsel<1 or newsel>#self.choices then + newsel=newsel % #self.choices + end + end + self.selection=newsel end function ListBox:onInput(keys) if keys.SELECT then self:dismiss() - local choice=self.choices[self.selection] + local choice=self.choices[self.selection] if self.on_input then self.on_input(self.selection,choice) end - - if choice and choice[2] then - choice[2](choice,self.selection) -- maybe reverse the arguments? - end + + if choice and choice[2] then + choice[2](choice,self.selection) -- maybe reverse the arguments? + end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.CURSOR_UP then - self:moveCursor(-1) - elseif keys.CURSOR_DOWN then - self:moveCursor(1) - elseif keys.CURSOR_UP_FAST then - self:moveCursor(-10) - elseif keys.CURSOR_DOWN_FAST then - self:moveCursor(10) + elseif keys.CURSOR_UP then + self:moveCursor(-1) + elseif keys.CURSOR_DOWN then + self:moveCursor(1) + elseif keys.CURSOR_UP_FAST then + self:moveCursor(-10) + elseif keys.CURSOR_DOWN_FAST then + self:moveCursor(10) end end From a36fe25e7249c60094a6347726f961779cf3b98a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 12:27:40 +0400 Subject: [PATCH 58/61] Finish the effective skill computation function, and move to core. --- LUA_API.rst | 15 +++ Lua API.html | 15 +++ library/LuaApi.cpp | 7 ++ library/include/modules/Units.h | 12 +++ library/modules/Units.cpp | 159 +++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/devel/siege-engine.cpp | 82 +--------------- 7 files changed, 209 insertions(+), 83 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 4d9170d6e..48d2c19e6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -868,6 +868,17 @@ Units module Returns the nemesis record of the unit if it has one, or *nil*. +* ``dfhack.units.isCrazed(unit)`` +* ``dfhack.units.isOpposedToLife(unit)`` +* ``dfhack.units.hasExtravision(unit)`` +* ``dfhack.units.isBloodsucker(unit)`` + + Simple checks of caste attributes that can be modified by curses. + +* ``dfhack.units.getMiscTrait(unit, type[, create])`` + + Finds (or creates if requested) a misc trait object with the given id. + * ``dfhack.units.isDead(unit)`` The unit is completely dead and passive, or a ghost. @@ -894,6 +905,10 @@ Units module Returns the age of the unit in years as a floating-point value. If ``true_age`` is true, ignores false identities. +* ``dfhack.units.getEffectiveSkill(unit, skill)`` + + Computes the effective rating for the given skill, taking into account exhaustion, pain etc. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/Lua API.html b/Lua API.html index dc9c8d73e..c302c29f7 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1106,6 +1106,18 @@ a lua list containing them.

    • dfhack.units.getNemesis(unit)

      Returns the nemesis record of the unit if it has one, or nil.

    • +
    • dfhack.units.isCrazed(unit)

      +
    • +
    • dfhack.units.isOpposedToLife(unit)

      +
    • +
    • dfhack.units.hasExtravision(unit)

      +
    • +
    • dfhack.units.isBloodsucker(unit)

      +

      Simple checks of caste attributes that can be modified by curses.

      +
    • +
    • dfhack.units.getMiscTrait(unit, type[, create])

      +

      Finds (or creates if requested) a misc trait object with the given id.

      +
    • dfhack.units.isDead(unit)

      The unit is completely dead and passive, or a ghost.

    • @@ -1126,6 +1138,9 @@ same checks the game uses to decide game-over by extinction.

      Returns the age of the unit in years as a floating-point value. If true_age is true, ignores false identities.

      +
    • dfhack.units.getEffectiveSkill(unit, skill)

      +

      Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

      +
    • dfhack.units.getNoblePositions(unit)

      Returns a list of tables describing noble position assignments, or nil. Every table has fields entity, assignment and position.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 807cbf539..6caf45575 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -79,6 +79,7 @@ distribution. #include "df/building_civzonest.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/unit_misc_trait.h" #include #include @@ -813,12 +814,18 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getVisibleName), WRAPM(Units, getIdentity), WRAPM(Units, getNemesis), + WRAPM(Units, isCrazed), + WRAPM(Units, isOpposedToLife), + WRAPM(Units, hasExtravision), + WRAPM(Units, isBloodsucker), + WRAPM(Units, getMiscTrait), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), WRAPM(Units, getAge), + WRAPM(Units, getEffectiveSkill), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 9003dc3af..ece151127 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -32,6 +32,8 @@ distribution. #include "modules/Items.h" #include "DataDefs.h" #include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/job_skill.h" namespace df { @@ -41,6 +43,7 @@ namespace df struct historical_entity; struct entity_position_assignment; struct entity_position; + struct unit_misc_trait; } /** @@ -208,6 +211,13 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); +DFHACK_EXPORT bool isCrazed(df::unit *unit); +DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); +DFHACK_EXPORT bool hasExtravision(df::unit *unit); +DFHACK_EXPORT bool isBloodsucker(df::unit *unit); + +DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); + DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); @@ -216,6 +226,8 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); +DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); + struct NoblePosition { df::historical_entity *entity; df::entity_position_assignment *assignment; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 874dabc3d..6a672b585 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -63,11 +63,15 @@ using namespace std; #include "df/burrow.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "df/game_mode.h" +#include "df/unit_misc_trait.h" +#include "df/unit_skill.h" using namespace DFHack; using namespace df::enums; using df::global::world; using df::global::ui; +using df::global::gamemode; bool Units::isValid() { @@ -626,8 +630,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) return craw->flags.is_set(flag); } -static bool isCrazed(df::unit *unit) +bool Units::isCrazed(df::unit *unit) { + CHECK_NULL_POINTER(unit); if (unit->flags3.bits.scuttle) return false; if (unit->curse.rem_tags1.bits.CRAZED) @@ -637,13 +642,54 @@ static bool isCrazed(df::unit *unit) return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); } -static bool isOpposedToLife(df::unit *unit) +bool Units::isOpposedToLife(df::unit *unit) { + CHECK_NULL_POINTER(unit); if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) return false; if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD); + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE); +} + +bool Units::hasExtravision(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.EXTRAVISION) + return false; + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); +} + +bool Units::isBloodsucker(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.BLOODSUCKER) + return false; + if (unit->curse.add_tags1.bits.BLOODSUCKER) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); +} + +df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) +{ + CHECK_NULL_POINTER(unit); + + auto &vec = unit->status.misc_traits; + for (size_t i = 0; i < vec.size(); i++) + if (vec[i]->id == type) + return vec[i]; + + if (create) + { + auto obj = new df::unit_misc_trait(); + obj->id = type; + vec.push_back(obj); + return obj; + } + + return NULL; } bool DFHack::Units::isDead(df::unit *unit) @@ -753,6 +799,113 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age) return cur_time - birth_time; } +inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) +{ + if (is_adventure) + { + if (value >= adv1_2) rating >>= 1; + else if (value >= adv3_4) rating = rating*3/4; + else if (value >= adv9_10) rating = rating*9/10; + } + else + { + if (value >= dwarf1_2) rating >>= 1; + else if (value >= dwarf3_4) return rating*3/4; + } +} + +int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + CHECK_NULL_POINTER(unit); + + /* + * This is 100% reverse-engineered from DF code. + */ + + if (!unit->status.current_soul) + return 0; + + // Retrieve skill from unit soul: + + df::enum_field key(skill_id); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + + int rating = 0; + if (skill) + rating = std::max(0, int(skill->rating) - skill->rusty); + + // Apply special states + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) rating >>= 1; + if (unit->counters.winded > 0) rating >>= 1; + if (unit->counters.stunned > 0) rating >>= 1; + if (unit->counters.dizziness > 0) rating >>= 1; + if (unit->counters2.fever > 0) rating >>= 1; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && + !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && + !hasExtravision(unit)) + { + rating >>= 2; + } + if (unit->counters.pain >= 100 && unit->mood == -1) + { + rating >>= 1; + } + if (unit->counters2.exhaustion >= 2000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 4000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 6000) + rating = rating*3/4; + } + } + } + + // Hunger etc timers + + bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE); + + if (!unit->flags3.bits.scuttle && isBloodsucker(unit)) + { + using namespace df::enums::misc_trait_type; + + if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood)) + { + adjust_skill_rating( + rating, is_adventure, trait->value, + 302400, 403200, // dwf 3/4; 1/2 + 1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2 + ); + } + } + + adjust_skill_rating( + rating, is_adventure, unit->counters2.thirst_timer, + 50000, 50000, 115200, 172800, 345600 + ); + adjust_skill_rating( + rating, is_adventure, unit->counters2.hunger_timer, + 75000, 75000, 172800, 1209600, 2592000 + ); + if (is_adventure && unit->counters2.sleepiness_timer >= 846000) + rating >>= 2; + else + adjust_skill_rating( + rating, is_adventure, unit->counters2.sleepiness_timer, + 150000, 150000, 172800, 259200, 345600 + ); + + return rating; +} + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/library/xml b/library/xml index 18e76d8bd..db765a65b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36 +Subproject commit db765a65b17099dbec115812b40a19b46ad59431 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 60035b276..8b5010194 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -501,82 +501,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) -{ - auto creature = df::creature_raw::find(race); - if (!creature) - return false; - - auto craw = vector_get(creature->caste, caste); - if (!craw) - return false; - - return craw->flags.is_set(flag); -} - -static bool hasExtravision(df::unit *unit) -{ - if (unit->curse.rem_tags1.bits.EXTRAVISION) - return false; - if (unit->curse.add_tags1.bits.EXTRAVISION) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); -} - -int getEffectiveSkill(df::unit *unit, df::job_skill skill_id) -{ - CHECK_NULL_POINTER(unit); - - if (!unit->status.current_soul) - return 0; - - int rating = 0; - - df::enum_field key(skill_id); - auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); - if (skill) - rating = std::max(0, int(skill->rating) - skill->rusty); - - if (unit->counters.soldier_mood == df::unit::T_counters::None) - { - if (unit->counters.nausea > 0) rating >>= 1; - if (unit->counters.winded > 0) rating >>= 1; - if (unit->counters.stunned > 0) rating >>= 1; - if (unit->counters.dizziness > 0) rating >>= 1; - if (unit->counters2.fever > 0) rating >>= 1; - } - - if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) - { - if (unit->counters.pain >= 100 && unit->mood == -1) rating >>= 1; - - if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && - !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && - !hasExtravision(unit)) - { - rating >>= 2; - } - if (unit->counters2.exhaustion >= 2000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 4000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 6000) - rating = rating*3/4; - } - } - } - - // TODO: bloodsucker; advmode - - if (unit->counters2.thirst_timer >= 50000) rating >>= 1; - if (unit->counters2.hunger_timer >= 75000) rating >>= 1; - if (unit->counters2.sleepiness_timer >= 150000) rating >>= 1; - - return rating; -} - static int getAttrValue(const df::unit_attribute &attr) { return std::max(0, attr.value - attr.soft_demotion); @@ -635,7 +559,7 @@ int getSpeedRating(df::unit *unit) speed *= 2; if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) { - int skill = getEffectiveSkill(unit, job_skill::SWIMMING); + int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); if (skill > 1) skill = skill * std::max(6, 21-skill) / 20; } @@ -693,7 +617,7 @@ int getSpeedRating(df::unit *unit) speed += 2000; else if (unit->flags3.bits.on_crutch) { - int skill = getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); speed += 2000 - 100*std::min(20, skill); } @@ -739,7 +663,7 @@ int getSpeedRating(df::unit *unit) if (unit->mood == mood_type::Melancholy) speed += 8000; // Inventory encumberance - int armor_skill = getEffectiveSkill(unit, job_skill::ARMOR); + int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); armor_skill = std::min(15, armor_skill); int inv_weight = 0, inv_weight_fraction = 0; From ec3d489bda19f8ab2a45fbb19d7259ea3f4ad75b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 12:51:08 +0400 Subject: [PATCH 59/61] Move curse-affected attribute value getters to the core. --- LUA_API.rst | 9 ++++++ Lua API.html | 8 +++++ library/include/modules/Units.h | 6 ++++ library/modules/Units.cpp | 52 +++++++++++++++++++++++++++++++++ plugins/devel/siege-engine.cpp | 34 ++------------------- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 48d2c19e6..686b90038 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -868,6 +868,15 @@ Units module Returns the nemesis record of the unit if it has one, or *nil*. +* ``dfhack.units.isHidingCurse(unit)`` + + Checks if the unit hides improved attributes from its curse. + +* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)`` +* ``dfhack.units.getMentalAttrValue(unit, attr_type)`` + + Computes the effective attribute value, including curse effect. + * ``dfhack.units.isCrazed(unit)`` * ``dfhack.units.isOpposedToLife(unit)`` * ``dfhack.units.hasExtravision(unit)`` diff --git a/Lua API.html b/Lua API.html index c302c29f7..12bcf5fa8 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1106,6 +1106,14 @@ a lua list containing them.

    • dfhack.units.getNemesis(unit)

      Returns the nemesis record of the unit if it has one, or nil.

    • +
    • dfhack.units.isHidingCurse(unit)

      +

      Checks if the unit hides improved attributes from its curse.

      +
    • +
    • dfhack.units.getPhysicalAttrValue(unit, attr_type)

      +
    • +
    • dfhack.units.getMentalAttrValue(unit, attr_type)

      +

      Computes the effective attribute value, including curse effect.

      +
    • dfhack.units.isCrazed(unit)

    • dfhack.units.isOpposedToLife(unit)

      diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index ece151127..3fba5c218 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -33,6 +33,8 @@ distribution. #include "DataDefs.h" #include "df/unit.h" #include "df/misc_trait_type.h" +#include "df/physical_attribute_type.h" +#include "df/mental_attribute_type.h" #include "df/job_skill.h" namespace df @@ -211,6 +213,10 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); +DFHACK_EXPORT bool isHidingCurse(df::unit *unit); +DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr); +DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr); + DFHACK_EXPORT bool isCrazed(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); DFHACK_EXPORT bool hasExtravision(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 6a672b585..1565dbbd3 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -617,6 +617,58 @@ df::nemesis_record *Units::getNemesis(df::unit *unit) return NULL; } + +bool Units::isHidingCurse(df::unit *unit) +{ + if (!unit->job.hunt_target) + { + auto identity = Units::getIdentity(unit); + if (identity && identity->unk_4c == 0) + return true; + } + + return false; +} + +int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr) +{ + auto &aobj = unit->body.physical_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + +int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) +{ + auto soul = unit->status.current_soul; + if (!soul) return 0; + + auto &aobj = soul->mental_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->ment_att_perc[attr]) + mod->ment_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) { auto creature = df::creature_raw::find(race); diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 8b5010194..648e4c3b2 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -501,28 +501,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -static int getAttrValue(const df::unit_attribute &attr) -{ - return std::max(0, attr.value - attr.soft_demotion); -} - -static int getPhysAttrValue(df::unit *unit, df::physical_attribute_type attr, bool hide_curse) -{ - int value = getAttrValue(unit->body.physical_attrs[attr]); - - if (auto mod = unit->curse.attr_change) - { - int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; - - if (hide_curse) - value = std::min(value, mvalue); - else - value = mvalue; - } - - return value; -} - int getSpeedRating(df::unit *unit) { using namespace df::enums::physical_attribute_type; @@ -635,18 +613,10 @@ int getSpeedRating(df::unit *unit) speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); // Attributes - bool hide_curse = false; - if (!unit->job.hunt_target) - { - auto identity = Units::getIdentity(unit); - if (identity && identity->unk_4c == 0) - hide_curse = true; - } + int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); + int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); - int strength_attr = getPhysAttrValue(unit, STRENGTH, hide_curse); - int agility_attr = getPhysAttrValue(unit, AGILITY, hide_curse); int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); - speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) From 9679b7729c79888d8bdac99484dfad7a5e07d13e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 17:04:58 +0400 Subject: [PATCH 60/61] Clean up the movement speed calculation function and move into the core. --- LUA_API.rst | 4 + Lua API.html | 3 + library/LuaApi.cpp | 2 + library/include/modules/Units.h | 2 + library/modules/Units.cpp | 280 +++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/devel/siege-engine.cpp | 177 +------------------- 7 files changed, 289 insertions(+), 181 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 686b90038..a5be09a0e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -918,6 +918,10 @@ Units module Computes the effective rating for the given skill, taking into account exhaustion, pain etc. +* ``dfhack.units.computeMovementSpeed(unit)`` + + Computes number of frames * 100 it takes the unit to move in its current state of mind and body. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/Lua API.html b/Lua API.html index 12bcf5fa8..7886fbc25 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1149,6 +1149,9 @@ If true_age is true, ignores false identities.
    • dfhack.units.getEffectiveSkill(unit, skill)

      Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

    • +
    • dfhack.units.computeMovementSpeed(unit)

      +

      Computes number of frames * 100 it takes the unit to move in its current state of mind and body.

      +
    • dfhack.units.getNoblePositions(unit)

      Returns a list of tables describing noble position assignments, or nil. Every table has fields entity, assignment and position.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6caf45575..d39a945dd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -818,6 +818,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isOpposedToLife), WRAPM(Units, hasExtravision), WRAPM(Units, isBloodsucker), + WRAPM(Units, isMischievous), WRAPM(Units, getMiscTrait), WRAPM(Units, isDead), WRAPM(Units, isAlive), @@ -826,6 +827,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isCitizen), WRAPM(Units, getAge), WRAPM(Units, getEffectiveSkill), + WRAPM(Units, computeMovementSpeed), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 3fba5c218..65f0b58a0 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -221,6 +221,7 @@ DFHACK_EXPORT bool isCrazed(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); DFHACK_EXPORT bool hasExtravision(df::unit *unit); DFHACK_EXPORT bool isBloodsucker(df::unit *unit); +DFHACK_EXPORT bool isMischievous(df::unit *unit); DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); @@ -233,6 +234,7 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); +DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); struct NoblePosition { df::historical_entity *entity; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 1565dbbd3..28c34b029 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -637,7 +637,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr if (auto mod = unit->curse.attr_change) { - int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr]; if (isHidingCurse(unit)) value = std::min(value, mvalue); @@ -645,7 +645,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr value = mvalue; } - return value; + return std::max(0, value); } int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) @@ -658,7 +658,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) if (auto mod = unit->curse.attr_change) { - int mvalue = (value * mod->ment_att_perc[attr]) + mod->ment_att_add[attr]; + int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr]; if (isHidingCurse(unit)) value = std::min(value, mvalue); @@ -666,7 +666,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) value = mvalue; } - return value; + return std::max(0, value); } static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) @@ -724,6 +724,16 @@ bool Units::isBloodsucker(df::unit *unit) return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); } +bool Units::isMischievous(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.MISCHIEVOUS) + return false; + if (unit->curse.add_tags1.bits.MISCHIEVOUS) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS); +} + df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) { CHECK_NULL_POINTER(unit); @@ -851,7 +861,7 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age) return cur_time - birth_time; } -inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) +inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) { if (is_adventure) { @@ -862,7 +872,7 @@ inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dw else { if (value >= dwarf1_2) rating >>= 1; - else if (value >= dwarf3_4) return rating*3/4; + else if (value >= dwarf3_4) rating = rating*3/4; } } @@ -958,6 +968,264 @@ int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) return rating; } +inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200) +{ + if (is_adventure) + { + if (value >= adv200) rating += 200; + else if (value >= adv100) rating += 100; + else if (value >= adv75) rating += 75; + else if (value >= adv50) rating += 50; + } + else + { + if (value >= dwarf200) rating += 200; + else if (value >= dwarf100) rating += 100; + } +} + +static int calcInventoryWeight(df::unit *unit) +{ + int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); + int armor_mul = 15 - std::min(15, armor_skill); + + int inv_weight = 0, inv_weight_fraction = 0; + + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]->item; + if (!item->flags.bits.weight_computed) + continue; + + int wval = item->weight; + int wfval = item->weight_fraction; + auto mode = unit->inventory[i]->mode; + + if ((mode == df::unit_inventory_item::Worn || + mode == df::unit_inventory_item::WrappedAround) && + item->isArmor() && armor_skill > 1) + { + wval = wval * armor_mul / 16; + wfval = wfval * armor_mul / 16; + } + + inv_weight += wval; + inv_weight_fraction += wfval; + } + + return inv_weight*100 + inv_weight_fraction/10000; +} + +int Units::computeMovementSpeed(df::unit *unit) +{ + using namespace df::enums::physical_attribute_type; + + /* + * Pure reverse-engineered computation of unit _slowness_, + * i.e. number of ticks to move * 100. + */ + + // Base speed + + auto creature = df::creature_raw::find(unit->race); + if (!creature) + return 0; + + auto craw = vector_get(creature->caste, unit->caste); + if (!craw) + return 0; + + int speed = craw->misc.speed; + + if (unit->flags3.bits.ghostly) + return speed; + + // Curse multiplier + + if (unit->curse.speed_mul_percent != 100) + { + speed *= 100; + if (unit->curse.speed_mul_percent != 0) + speed /= unit->curse.speed_mul_percent; + } + + speed += unit->curse.speed_add; + + // Swimming + + auto cur_liquid = unit->status2.liquid_type.bits.liquid_type; + bool in_magma = (cur_liquid == tile_liquid::Magma); + + if (unit->flags2.bits.swimming) + { + speed = craw->misc.swim_speed; + if (in_magma) + speed *= 2; + + if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) + { + int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); + + // Originally a switch: + if (skill > 1) + speed = speed * std::max(6, 21-skill) / 20; + } + } + else + { + int delta = 150*unit->status2.liquid_depth; + if (in_magma) + delta *= 2; + speed += delta; + } + + // General counters and flags + + if (unit->profession == profession::BABY) + speed += 3000; + + if (unit->flags3.bits.unk15) + speed /= 20; + + if (unit->counters2.exhaustion >= 2000) + { + speed += 200; + if (unit->counters2.exhaustion >= 4000) + { + speed += 200; + if (unit->counters2.exhaustion >= 6000) + speed += 200; + } + } + + if (unit->flags2.bits.gutted) speed += 2000; + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) speed += 1000; + if (unit->counters.winded > 0) speed += 1000; + if (unit->counters.stunned > 0) speed += 1000; + if (unit->counters.dizziness > 0) speed += 1000; + if (unit->counters2.fever > 0) speed += 1000; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood == -1) + speed += 1000; + } + + // Hunger etc timers + + bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE); + + if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit)) + { + using namespace df::enums::misc_trait_type; + + if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood)) + { + adjust_speed_rating( + speed, is_adventure, trait->value, + 302400, 403200, // dwf 100; 200 + 1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200 + ); + } + } + + adjust_speed_rating( + speed, is_adventure, unit->counters2.thirst_timer, + 50000, 0x7fffffff, 172800, 172800, 172800, 345600 + ); + adjust_speed_rating( + speed, is_adventure, unit->counters2.hunger_timer, + 75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000 + ); + adjust_speed_rating( + speed, is_adventure, unit->counters2.sleepiness_timer, + 57600, 150000, 172800, 259200, 345600, 864000 + ); + + // Activity state + + if (unit->relations.draggee_id != -1) speed += 1000; + + if (unit->flags1.bits.on_ground) + speed += 2000; + else if (unit->flags3.bits.on_crutch) + { + int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + speed += 2000 - 100*std::min(20, skill); + } + + if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit)) + { + int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK); + speed += 2000 - 100*std::min(20, skill); + } + + if (unsigned(unit->counters2.paralysis-1) <= 98) + speed += unit->counters2.paralysis*10; + if (unsigned(unit->counters.webbed-1) <= 8) + speed += unit->counters.webbed*100; + + // Muscle weight vs vascular tissue (?) + + auto &attr_tissue = unit->body.physical_attr_tissues; + int muscle = attr_tissue[STRENGTH]; + int blood = attr_tissue[AGILITY]; + speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood))); + + // Attributes + + int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); + int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); + + int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); + speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600; + + // Stance + + if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) + { + // WTF + int as = unit->status2.able_stand; + int x = (as-1) - (as>>1); + int y = as - unit->status2.able_stand_impair; + if (unit->flags3.bits.on_crutch) y--; + y = y * 500 / x; + if (y > 0) speed += y; + } + + // Mood + + if (unit->mood == mood_type::Melancholy) speed += 8000; + + // Inventory encumberance + + int total_weight = calcInventoryWeight(unit); + int free_weight = std::max(1, muscle/10 + strength_attr*3); + + if (free_weight < total_weight) + { + int delta = (total_weight - free_weight)/10 + 1; + if (!is_adventure) + delta = std::min(5000, delta); + speed += delta; + } + + // skipped: unknown loop on inventory items that amounts to 0 change + + if (is_adventure) + { + auto player = vector_get(world->units.active, 0); + if (player && player->id == unit->relations.group_leader_id) + speed = std::min(speed, computeMovementSpeed(player)); + } + + return std::min(10000, std::max(0, speed)); +} + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/library/xml b/library/xml index db765a65b..d55f1cf43 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit db765a65b17099dbec115812b40a19b46ad59431 +Subproject commit d55f1cf43dd71d3abee724bfa88a0a401b4ccaa3 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 648e4c3b2..4c6cebaac 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -39,6 +39,8 @@ #include "df/caste_raw.h" #include "df/caste_raw_flags.h" #include "df/assumed_identity.h" +#include "df/game_mode.h" +#include "df/unit_misc_trait.h" #include "MiscUtils.h" @@ -48,6 +50,7 @@ using std::stack; using namespace DFHack; using namespace df::enums; +using df::global::gamemode; using df::global::gps; using df::global::world; using df::global::ui; @@ -501,180 +504,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -int getSpeedRating(df::unit *unit) -{ - using namespace df::enums::physical_attribute_type; - - // Base speed - auto creature = df::creature_raw::find(unit->race); - if (!creature) - return 0; - - auto craw = vector_get(creature->caste, unit->caste); - if (!craw) - return 0; - - int speed = craw->misc.speed; - - if (unit->flags3.bits.ghostly) - return speed; - - // Curse multiplier - if (unit->curse.speed_mul_percent != 100) - { - speed *= 100; - if (unit->curse.speed_mul_percent != 0) - speed /= unit->curse.speed_mul_percent; - } - - speed += unit->curse.speed_add; - - // Swimming - if (unit->flags2.bits.swimming) - { - speed = craw->misc.swim_speed; - if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) - speed *= 2; - if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) - { - int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); - if (skill > 1) - skill = skill * std::max(6, 21-skill) / 20; - } - } - else - { - if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Water) - speed += 150*unit->status2.liquid_depth; - else if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) - speed += 300*unit->status2.liquid_depth; - } - - // General counters - if (unit->profession == profession::BABY) - speed += 3000; - - if (unit->counters2.exhaustion >= 2000) - { - speed += 200; - if (unit->counters2.exhaustion >= 4000) - { - speed += 200; - if (unit->counters2.exhaustion >= 6000) - speed += 200; - } - } - - if (unit->flags2.bits.gutted) speed += 2000; - - if (unit->counters.soldier_mood == df::unit::T_counters::None) - { - if (unit->counters.nausea > 0) speed += 1000; - if (unit->counters.winded > 0) speed += 1000; - if (unit->counters.stunned > 0) speed += 1000; - if (unit->counters.dizziness > 0) speed += 1000; - if (unit->counters2.fever > 0) speed += 1000; - } - - if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) - { - if (unit->counters.pain >= 100 && unit->mood < 0) - speed += 1000; - } - - // TODO: bloodsucker - - if (unit->counters2.thirst_timer >= 50000) speed += 100; - if (unit->counters2.hunger_timer >= 75000) speed += 100; - if (unit->counters2.sleepiness_timer >= 150000) speed += 200; - else if (unit->counters2.sleepiness_timer >= 57600) speed += 100; - - if (unit->relations.draggee_id != -1) speed += 1000; - - if (unit->flags1.bits.on_ground) - speed += 2000; - else if (unit->flags3.bits.on_crutch) - { - int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); - speed += 2000 - 100*std::min(20, skill); - } - - // TODO: hidden_in_ambush - - if (unsigned(unit->counters2.paralysis-1) <= 98) - speed += unit->counters2.paralysis*10; - if (unsigned(unit->counters.webbed-1) <= 8) - speed += unit->counters.webbed*100; - - // Muscle weight vs agility - auto &attr_unk3 = unit->body.physical_attr_unk3; - int strength = attr_unk3[STRENGTH]; - int agility = attr_unk3[AGILITY]; - speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); - - // Attributes - int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); - int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); - - int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); - speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? - - if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) - { - // WTF - int as = unit->status2.able_stand; - int x = (as-1) - (as>>1); - int y = as - unit->status2.able_stand_impair; - if (unit->flags3.bits.on_crutch) y--; - y = y * 500 / x; - if (y > 0) speed += y; - } - - if (unit->mood == mood_type::Melancholy) speed += 8000; - - // Inventory encumberance - int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); - armor_skill = std::min(15, armor_skill); - - int inv_weight = 0, inv_weight_fraction = 0; - - for (size_t i = 0; i < unit->inventory.size(); i++) - { - auto item = unit->inventory[i]->item; - if (!item->flags.bits.weight_computed) - continue; - - int wval = item->weight; - int wfval = item->weight_fraction; - - auto mode = unit->inventory[i]->mode; - if ((mode == df::unit_inventory_item::Worn || - mode == df::unit_inventory_item::WrappedAround) && - item->isArmor() && armor_skill > 1) - { - wval = wval * (15 - armor_skill) / 16; - wfval = wfval * (15 - armor_skill) / 16; - } - - inv_weight += wval; - inv_weight_fraction += wfval; - } - - int total_weight = inv_weight*100 + inv_weight_fraction/10000; - int free_weight = std::max(1, attr_unk3[STRENGTH]/10 + strength_attr*3); - - if (free_weight < total_weight) - { - int delta = (total_weight - free_weight)/10; - delta = std::min(5000, delta); // dwarfmode only - speed += delta; - } - - // skipped: unknown loop on inventory items that amounts to 0 change - - return std::min(10000, std::max(0, speed)); -} - /* * Projectile hook */ From 8ab615f6d0dce167a1952c4684922a7e48263c23 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 20:54:12 +0400 Subject: [PATCH 61/61] Implement unit path prediction in siege engine. --- plugins/devel/siege-engine.cpp | 317 ++++++++++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 1 deletion(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 4c6cebaac..c36e9fb32 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -124,6 +124,11 @@ static int random_int(int val) return int(int64_t(rand())*val/RAND_MAX); } +static int point_distance(df::coord speed) +{ + return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); +} + /* * Configuration management */ @@ -134,6 +139,7 @@ struct EngineInfo { int id; coord_range target; df::coord center; + int proj_speed, hit_delay; bool hasTarget() { return is_range_valid(target); } bool onTarget(df::coord pos) { return is_in_range(target, pos); } @@ -155,8 +161,11 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) return NULL; auto *obj = &engines[bld]; + obj->id = bld->id; obj->center = df::coord(bld->centerx, bld->centery, bld->z); + obj->proj_speed = 2; + obj->hit_delay = 3; coord_engines[obj->center] = bld; return obj; @@ -285,7 +294,7 @@ struct ProjectilePath { void calc_line() { speed = target - origin; - divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); + divisor = point_distance(speed); if (divisor <= 0) divisor = 1; direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); } @@ -504,6 +513,292 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ +static const float MAX_TIME = 1000000.0f; + +struct UnitPath { + df::unit *unit; + std::map path; + + struct Hit { + UnitPath *path; + df::coord pos; + int dist; + float time, lmargin, rmargin; + }; + + static std::map cache; + + static UnitPath *get(df::unit *unit) + { + auto &cv = cache[unit]; + if (!cv) cv = new UnitPath(unit); + return cv; + }; + + UnitPath(df::unit *unit) : unit(unit) + { + df::coord pos = unit->pos; + df::coord dest = unit->path.dest; + auto &upath = unit->path.path; + + if (dest.isValid() && !upath.x.empty()) + { + float time = unit->counters.job_counter+0.5f; + float speed = Units::computeMovementSpeed(unit)/100.0f; + + for (size_t i = 0; i < upath.size(); i++) + { + df::coord new_pos = upath[i]; + if (new_pos == pos) + continue; + + float delay = speed; + if (new_pos.x != pos.x && new_pos.y != pos.y) + delay *= 362.0/256.0; + + path[time] = pos; + pos = new_pos; + time += delay + 1; + } + } + + path[MAX_TIME] = pos; + } + + void get_margin(std::map::iterator &it, float time, float *lmargin, float *rmargin) + { + auto it2 = it; + *lmargin = (it == path.begin()) ? MAX_TIME : time - (--it2)->first; + *rmargin = (it->first == MAX_TIME) ? MAX_TIME : it->first - time; + } + + df::coord posAtTime(float time, float *lmargin = NULL, float *rmargin = NULL) + { + CHECK_INVALID_ARGUMENT(time < MAX_TIME); + + auto it = path.upper_bound(time); + if (lmargin) + get_margin(it, time, lmargin, rmargin); + return it->second; + } + + bool findHits(EngineInfo *engine, std::vector *hit_points, float bias) + { + df::coord origin = engine->center; + + Hit info; + info.path = this; + + for (auto it = path.begin(); it != path.end(); ++it) + { + info.pos = it->second; + info.dist = point_distance(origin - info.pos); + info.time = float(info.dist)*(engine->proj_speed+1) + engine->hit_delay + bias; + get_margin(it, info.time, &info.lmargin, &info.rmargin); + + if (info.lmargin > 0 && info.rmargin > 0) + { + if (engine->onTarget(info.pos)) + hit_points->push_back(info); + } + } + + return !hit_points->empty(); + } +}; + +std::map UnitPath::cache; + +static void push_margin(lua_State *L, float margin) +{ + if (margin == MAX_TIME) + lua_pushnil(L); + else + lua_pushnumber(L, margin); +} + +static int traceUnitPath(lua_State *L) +{ + auto unit = Lua::CheckDFObject(L, 1); + + CHECK_NULL_POINTER(unit); + + size_t idx = 1; + auto info = UnitPath::get(unit); + lua_createtable(L, info->path.size(), 0); + + float last_time = 0.0f; + for (auto it = info->path.begin(); it != info->path.end(); ++it) + { + Lua::Push(L, it->second); + if (idx > 1) + { + lua_pushnumber(L, last_time); + lua_setfield(L, -2, "from"); + } + if (idx < info->path.size()) + { + lua_pushnumber(L, it->first); + lua_setfield(L, -2, "to"); + } + lua_rawseti(L, -2, idx++); + last_time = it->first; + } + + return 1; +} + +static int unitPosAtTime(lua_State *L) +{ + auto unit = Lua::CheckDFObject(L, 1); + float time = luaL_checknumber(L, 2); + + CHECK_NULL_POINTER(unit); + + float lmargin, rmargin; + auto info = UnitPath::get(unit); + + Lua::Push(L, info->posAtTime(time, &lmargin, &rmargin)); + push_margin(L, lmargin); + push_margin(L, rmargin); + return 3; +} + +static void proposeUnitHits(EngineInfo *engine, std::vector *hits, float bias) +{ + auto &active = world->units.active; + + for (size_t i = 0; i < active.size(); i++) + { + auto unit = active[i]; + + if (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->flags1.bits.caged || + unit->flags1.bits.chained || + unit->flags1.bits.rider || + unit->flags1.bits.hidden_in_ambush) + continue; + + UnitPath::get(unit)->findHits(engine, hits, bias); + } +} + +static int proposeUnitHits(lua_State *L) +{ + auto bld = Lua::CheckDFObject(L, 1); + float bias = luaL_optnumber(L, 2, 0); + + auto engine = find_engine(bld); + if (!engine) + luaL_error(L, "no such engine"); + if (!engine->hasTarget()) + luaL_error(L, "target not set"); + + std::vector hits; + proposeUnitHits(engine, &hits, bias); + + lua_createtable(L, hits.size(), 0); + + for (size_t i = 0; i < hits.size(); i++) + { + auto &hit = hits[i]; + lua_createtable(L, 0, 6); + Lua::PushDFObject(L, hit.path->unit); lua_setfield(L, -2, "unit"); + Lua::Push(L, hit.pos); lua_setfield(L, -2, "pos"); + lua_pushnumber(L, hit.dist); lua_setfield(L, -2, "dist"); + lua_pushnumber(L, hit.time); lua_setfield(L, -2, "time"); + push_margin(L, hit.lmargin); lua_setfield(L, -2, "lmargin"); + push_margin(L, hit.rmargin); lua_setfield(L, -2, "rmargin"); + lua_rawseti(L, -2, i+1); + } + + return 1; +} + +#if 0 +struct UnitContext { + AimContext &ctx; + + struct UnitInfo { + df::unit *unit; + + UnitPath path; + float score; + + UnitInfo(df::unit *unit) : unit(unit), path(unit) {} + }; + + std::map units; + + UnitContext(AimContext &ctx) : ctx(ctx) {} + + ~UnitContext() + { + for (auto it = units.begin(); it != units.end(); ++it) + delete it->second; + } + + float unit_score(df::unit *unit) + { + float score = 1.0f; + + if (unit->flags1.bits.tame && unit->civ_id == ui->civ_id) + score = -1.0f; + if (unit->flags1.bits.diplomat || unit->flags1.bits.merchant) + score = -2.0f; + else if (Units::isCitizen(unit)) + score = -10.0f; + else + { + if (unit->flags1.bits.marauder) + score += 0.5f; + if (unit->flags1.bits.active_invader) + score += 1.0f; + if (unit->flags1.bits.invader_origin) + score += 1.0f; + if (unit->flags1.bits.invades) + score += 1.0f; + if (unit->flags1.bits.hidden_ambusher) + score += 1.0f; + } + + if (unit->flags1.bits.ridden) + { + for (size_t i = 0; i < unit->refs.size(); i++) + { + if (!unit->refs[i]->getType() == general_ref_type::UNIT_RIDER) + continue; + if (auto rider = unit->refs[i]->getUnit()) + score += unit_score(rider); + } + } + } + + void select_units() + { + auto &active = world->units.active; + + for (size_t i = 0; i < active.size(); i++) + { + auto unit = active[i]; + if (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->flags1.bits.caged || + unit->flags1.bits.chained || + unit->flags1.bits.rider || + unit->flags1.bits.hidden_in_ambush) + continue; + + auto info = units[unit] = new UnitInfo(unit); + + info->findHits(ctx, ctx.proj_hit_delay); + info->score = unit_score(unit); + } + } +}; +#endif + /* * Projectile hook */ @@ -617,6 +912,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_COMMAND(traceUnitPath), + DFHACK_LUA_COMMAND(unitPosAtTime), + DFHACK_LUA_COMMAND(proposeUnitHits), DFHACK_LUA_END }; @@ -648,6 +946,17 @@ static bool enable_plugin() return true; } +static void clear_caches() +{ + if (!UnitPath::cache.empty()) + { + for (auto it = UnitPath::cache.begin(); it != UnitPath::cache.end(); ++it) + delete it->second; + + UnitPath::cache.clear(); + } +} + DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { @@ -688,3 +997,9 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) enable_hooks(false); return CR_OK; } + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + clear_caches(); + return CR_OK; +}