From 7440e80e6c6af23284403db3af103a5f35358f0f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 13:49:31 +0400 Subject: [PATCH 01/21] Add an API function to retrieve interface key bindings for display. --- library/Core.cpp | 3 ++ library/LuaApi.cpp | 1 + library/include/modules/Screen.h | 3 ++ library/modules/Screen.cpp | 48 ++++++++++++++++++++++++++++++++ library/xml | 2 +- 5 files changed, 56 insertions(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index 1015194ad..bc90abc6b 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -821,6 +821,8 @@ std::string Core::getHackPath() #endif } +void init_screen_module(Core *); + bool Core::Init() { if(started) @@ -866,6 +868,7 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); + init_screen_module(this); cerr << "Initializing Console.\n"; // init the console. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 352e21ccf..aba6301d2 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1146,6 +1146,7 @@ static const LuaWrapper::FunctionReg dfhack_screen_module[] = { WRAPM(Screen, inGraphicsMode), WRAPM(Screen, clear), WRAPM(Screen, invalidate), + WRAPM(Screen, getKeyDisplay), { NULL, NULL } }; diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index a2e64a515..ccd7f2f8d 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -128,6 +128,9 @@ namespace DFHack DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL); DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false); DFHACK_EXPORT bool isDismissed(df::viewscreen *screen); + + /// Retrieve the string representation of the bound key. + DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); } class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 0b9279d2d..29f718266 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -28,6 +28,7 @@ distribution. #include #include #include +#include using namespace std; #include "modules/Screen.h" @@ -64,6 +65,8 @@ using df::global::enabler; using Screen::Pen; +using std::string; + /* * Screen painting API. */ @@ -303,6 +306,51 @@ bool Screen::isDismissed(df::viewscreen *screen) return screen->breakdown_level != interface_breakdown_types::NONE; } +#ifdef _LINUX +// Link to the libgraphics class directly: +class DFHACK_EXPORT enabler_inputst { + public: + std::string GetKeyDisplay(int binding); +}; +#else +struct less_sz { + bool operator() (const string &a, const string &b) const { + if (a.size() < b.size()) return true; + if (a.size() > b.size()) return false; + return a < b; + } +}; +static std::map > *keydisplay = NULL; +#endif + +void init_screen_module(Core *core) +{ +#ifdef _LINUX + core = core; +#else + if (!core->vinfo->getAddress("keydisplay", keydisplay)) + keydisplay = NULL; +#endif +} + +string Screen::getKeyDisplay(df::interface_key key) +{ +#ifdef _LINUX + auto enabler = (enabler_inputst*)df::global::enabler; + if (enabler) + return enabler->GetKeyDisplay(key); +#else + if (keydisplay) + { + auto it = keydisplay->find(key); + if (it != keydisplay->end() && !it->second.empty()) + return *it->second.begin(); + } +#endif + + return "?"; +} + /* * Base DFHack viewscreen. */ diff --git a/library/xml b/library/xml index 2f76de54d..4c210ddb5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b +Subproject commit 4c210ddb5338d3a519d03f768da828c33480e4fe From 9d5adf1b2fb3e79420966967528dcf96cb879eb2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 15:25:59 +0400 Subject: [PATCH 02/21] Update the lua screens to use the new key display string API function. --- library/lua/gui.lua | 18 ++++++++++++++++++ library/lua/gui/dialogs.lua | 7 +++---- scripts/gui/liquids.lua | 22 +++++++++++----------- scripts/gui/mechanisms.lua | 4 ++-- scripts/gui/power-meter.lua | 8 +++++--- scripts/gui/room-list.lua | 4 ++-- scripts/gui/siege-engine.lua | 32 +++++++++++++++++--------------- 7 files changed, 58 insertions(+), 37 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 6eaa98606..22066c077 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -93,6 +93,14 @@ function Painter.new(rect, pen) return Painter{ rect = rect, pen = pen } end +function Painter.new_xy(x1,y1,x2,y2,pen) + return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen } +end + +function Painter.new_wh(x,y,w,h,pen) + return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen } +end + function Painter:isValidPos() return self.x >= self.clip_x1 and self.x <= self.clip_x2 and self.y >= self.clip_y1 and self.y <= self.clip_y2 @@ -210,6 +218,16 @@ function Painter:string(text,pen,...) return self:advance(#text, nil) end +function Painter:key(code,pen,bg,...) + if type(code) == 'string' then + code = df.interface_key[code] + end + return self:string( + dscreen.getKeyDisplay(code), + pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... + ) +end + ------------------------ -- Base screen object -- ------------------------ diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index b1a96a558..707b36d52 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -51,10 +51,9 @@ function MessageBox:onRenderBody(dc) 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') + local fr = self.frame_rect + local dc2 = gui.Painter.new_xy(fr.x1+1,fr.y2+1,fr.x2-8,fr.y2+1) + dc2:key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM') end end diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index cddb9f01d..322ae6952 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -17,8 +17,8 @@ local brushes = { } local paints = { - { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'w' }, - { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'l' }, + { tag = 'water', caption = 'Water', liquid = true, flow = true, key = 'D_LOOK_ARENA_WATER' }, + { tag = 'magma', caption = 'Magma', liquid = true, flow = true, key = 'D_LOOK_ARENA_MAGMA' }, { tag = 'obsidian', caption = 'Obsidian Wall' }, { tag = 'obsidian_floor', caption = 'Obsidian Floor' }, { tag = 'riversource', caption = 'River Source' }, @@ -64,7 +64,7 @@ function Toggle:render(dc) if item then dc:string(item.caption) if item.key then - dc:string(" ("):string(item.key, COLOR_LIGHTGREEN):string(")") + dc:string(" ("):key(item.key):string(")") end else dc:string('NONE', COLOR_RED) @@ -160,9 +160,9 @@ function LiquidsUI:onRenderBody(dc) dc:newline():pen(COLOR_GREY) - dc:newline(1):string("b", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_B'):string(": ") self.brush:render(dc) - dc:newline(1):string("p", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_P'):string(": ") self.paint:render(dc) local paint = self.paint:get() @@ -170,8 +170,8 @@ function LiquidsUI:onRenderBody(dc) dc:newline() 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(": ") + dc:advance(1):string("("):key('SECONDSCROLL_UP'):key('SECONDSCROLL_DOWN'):string(")") + dc:newline(3):key('CUSTOM_S'):string(": ") self.set:render(dc) else dc:advance(0,2) @@ -179,17 +179,17 @@ function LiquidsUI:onRenderBody(dc) dc:newline() if paint.flow then - dc:newline(1):string("f", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_F'):string(": ") self.flow:render(dc) - dc:newline(1):string("r", COLOR_LIGHTGREEN):string(": ") + dc:newline(1):key('CUSTOM_R'):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, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('SELECT'):string(": Paint") end function ensure_blocks(cursor, size, cb) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index d1e8ec803..607625524 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -89,8 +89,8 @@ function MechanismList:onRenderBody(dc) end dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('SELECT'):string(": Switch") end function MechanismList:changeSelected(delta) diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 6c2f699ac..81b56e29f 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -34,7 +34,8 @@ function PowerMeter:onRenderBody(dc) dc:string("Excess power range:") - dc:newline(3):string("as", COLOR_LIGHTGREEN) + dc:newline(3):key('BUILDING_TRIGGER_MIN_WATER_DOWN') + dc:key('BUILDING_TRIGGER_MIN_WATER_UP') dc:string(": Min ") if self.min_power <= 0 then dc:string("(any)") @@ -42,7 +43,8 @@ function PowerMeter:onRenderBody(dc) dc:string(''..self.min_power) end - dc:newline(3):string("zx", COLOR_LIGHTGREEN) + dc:newline(3):key('BUILDING_TRIGGER_MAX_WATER_DOWN') + dc:key('BUILDING_TRIGGER_MAX_WATER_UP') dc:string(": Max ") if self.max_power < 0 then dc:string("(any)") @@ -51,7 +53,7 @@ function PowerMeter:onRenderBody(dc) end dc:newline():newline(1) - dc:string("i",COLOR_LIGHTGREEN):string(": ") + dc:key('CUSTOM_I'):string(": ") if self.invert then dc:string("Inverted") else diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua index 0de82db5f..1f3e663da 100644 --- a/scripts/gui/room-list.lua +++ b/scripts/gui/room-list.lua @@ -185,10 +185,10 @@ function RoomList:onRenderBody(dc) end dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back") + dc:key('LEAVESCREEN'):string(": Back") if can_modify(sel_item) then - dc:string(", "):string("Enter", COLOR_LIGHTGREEN) + dc:string(", "):key('SELECT') if sel_item.obj.owner == sel_item.owner then dc:string(": Unassign") else diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index f460bb211..c518a9cf8 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -204,14 +204,14 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("None (default)") end - dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") + dc:newline(3):key('CUSTOM_R'):string(": Rectangle") if last_target_min then - dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") + dc:string(", "):key('CUSTOM_P'):string(": Paste") end dc:newline(3) if target_min then - dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") - dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") + dc:key('CUSTOM_X'):string(": Clear, ") + dc:key('CUSTOM_Z'):string(": Zoom") end dc:newline():newline(1) @@ -219,7 +219,7 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("Uses ballista arrows") else local item = plugin.getAmmoItem(self.building) - dc:string("u",COLOR_LIGHTGREEN):string(": Use ") + dc:key('CUSTOM_U'):string(": Use ") if item_choice_idx[item] then dc:string(item_choices[item_choice_idx[item]].caption) else @@ -228,18 +228,20 @@ function SiegeEngine:onRenderBody_main(dc) end dc:newline():newline(1) - dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3) + dc:key('CUSTOM_T'):string(": Take from stockpile"):newline(3) local links = plugin.getStockpileLinks(self.building) local bottom = dc.height - 5 if links then - dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ") - dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline() + dc:key('CUSTOM_D'):string(": Delete, ") + dc:key('CUSTOM_O'):string(": Zoom"):newline() self:renderStockpiles(dc, links, bottom-2-dc:localY()) dc:newline():newline() end local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ') + dc:seek(1,math.max(dc:localY(),19)) + dc:key('CUSTOM_G'):key('CUSTOM_H'):key('CUSTOM_J'):key('CUSTOM_K') + dc:string(': ') dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) dc:newline():newline() @@ -357,7 +359,7 @@ function SiegeEngine:onRenderBody_aim(dc) dc:newline(2):string('ERROR', COLOR_RED) end - dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) + dc:newline():newline(1):key('SELECT') if first then dc:string(": Finish rectangle") else @@ -367,7 +369,7 @@ function SiegeEngine:onRenderBody_aim(dc) 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") + dc:newline(1):key('CUSTOM_Z'):string(": Zoom to current target") end if first then @@ -412,9 +414,9 @@ function SiegeEngine:onRenderBody_pile(dc) if plugin.isLinkedToPile(self.building, sel) then dc:string("Already taking from here"):newline():newline(2) - dc:string("d", COLOR_LIGHTGREEN):string(": Delete link") + dc:key('CUSTOM_D'):string(": Delete link") else - dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile") + dc:key('SELECT'):string(": Take from this pile") end elseif sel then dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) @@ -460,8 +462,8 @@ function SiegeEngine:onRenderBody(dc) 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") + dc:key('LEAVESCREEN'):string(": Back, ") + dc:key('CUSTOM_C'):string(": Recenter") end function SiegeEngine:onInput(keys) From abf503fcdca4dd6c07171f93e4c4bc3ed85759d2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 16:45:17 +0400 Subject: [PATCH 03/21] Fix the ListBox dialog to behave in a more consistent way. --- library/lua/gui.lua | 3 +- library/lua/gui/dialogs.lua | 112 +++++++++++++++++++++------------- library/lua/gui/dwarfmode.lua | 5 +- 3 files changed, 74 insertions(+), 46 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 22066c077..a5e4f7503 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -310,6 +310,7 @@ function Screen:onResize(w,h) end function Screen:updateLayout() + self:invoke_after('postUpdateLayout') end ------------------------ @@ -399,7 +400,7 @@ function FramedScreen:updateFrameSize() self.frame_opaque = (gw == 0 and gh == 0) end -function FramedScreen:updateLayout() +function FramedScreen:postUpdateLayout() self:updateFrameSize() end diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 707b36d52..553bb3a4b 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -170,10 +170,10 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' ListBox.ATTRS{ - selection = 0, + selection = 1, choices = {}, select_pen = DEFAULT_NIL, - on_input = DEFAULT_NIL + on_select = DEFAULT_NIL } function InputBox:preinit(info) @@ -181,84 +181,112 @@ function InputBox:preinit(info) end function ListBox:init(info) - self.page_top = 0 + self.page_top = 1 +end + +local function choice_text(entry) + if type(entry)=="table" then + return entry.caption or entry[1] + else + return entry + end end function ListBox:getWantedFrameSize() local mw, mh = ListBox.super.getWantedFrameSize(self) - return mw, mh+#self.choices + for _,v in ipairs(self.choices) do + local text = choice_text(v) + mw = math.max(mw, #text+2) + end + return mw, mh+#self.choices+1 end -function ListBox:onRenderBody(dc) - ListBox.super.onRenderBody(self, dc) - - dc:newline(1) +function ListBox:postUpdateLayout() + self.page_size = self.frame_rect.height - #self.text - 3 + self:moveCursor(0) +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] +function ListBox:moveCursor(delta) + local page = math.max(1, self.page_size) + local cnt = #self.choices + local off = self.selection+delta-1 + local ds = math.abs(delta) + + if ds > 1 then + if off >= cnt+ds-1 then + off = 0 + else + off = math.min(cnt-1, off) 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) + if off <= -ds then + off = cnt-1 + else + off = math.max(0, off) end end + + self.selection = 1 + off % cnt + self.page_top = 1 + page * math.floor((self.selection-1) / page) 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 +function ListBox:onRenderBody(dc) + ListBox.super.onRenderBody(self, dc) + + dc:newline(1):pen(self.select_pen or COLOR_CYAN) + + local choices = self.choices + local iend = math.min(#choices, self.page_top+self.page_size-1) + + for i = self.page_top,iend do + local text = choice_text(choices[i]) + if text then + dc.cur_pen.bold = (i == self.selection); + dc:string(text) + else + dc:string('?ERROR?', COLOR_LIGHTRED) end + dc:newline(1) 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) + if self.on_select then + self.on_select(self.selection, choice) end - if choice and choice[2] then - choice[2](choice,self.selection) -- maybe reverse the arguments? + if choice then + local callback = choice.on_select or choice[2] + if callback then + callback(choice, self.selection) + end end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.CURSOR_UP then + elseif keys.STANDARDSCROLL_UP then self:moveCursor(-1) - elseif keys.CURSOR_DOWN then + elseif keys.STANDARDSCROLL_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.STANDARDSCROLL_PAGEUP then + self:moveCursor(-self.page_size) + elseif keys.STANDARDSCROLL_PAGEDOWN then + self:moveCursor(self.page_size) end end -function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) +function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) ListBox{ frame_title = title, text = text, text_pen = tcolor, choices = choices, - on_input = on_input, + on_select = on_select, on_cancel = on_cancel, frame_width = min_width, }:show() diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index ba3cfbe6c..125e71220 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -253,7 +253,7 @@ end DwarfOverlay = defclass(DwarfOverlay, gui.Screen) -function DwarfOverlay:updateLayout() +function DwarfOverlay:postUpdateLayout() self.df_layout = getPanelLayout() end @@ -352,8 +352,7 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) -function MenuOverlay:updateLayout() - MenuOverlay.super.updateLayout(self) +function MenuOverlay:postUpdateLayout() self.frame_rect = self.df_layout.menu end From bd3d3061ae72973fa0ddf5349bf6a9f59b662e28 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 18:01:28 +0400 Subject: [PATCH 04/21] Add a module that wraps the dialogs as "blocking" coroutine functions. --- library/lua/gui/script.lua | 151 +++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 library/lua/gui/script.lua diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua new file mode 100644 index 000000000..021a4fa52 --- /dev/null +++ b/library/lua/gui/script.lua @@ -0,0 +1,151 @@ +-- Support for scripted interaction sequences via coroutines. + +local _ENV = mkmodule('gui.script') + +local dlg = require('gui.dialogs') + +--[[ + Example: + + start(function() + sleep(100, 'frames') + print(showYesNoPrompt('test', 'print true?')) + end) +]] + +-- Table of running background scripts. +if not scripts then + scripts = {} + setmetatable(scripts, { __mode = 'k' }) +end + +local function do_resume(inst, ...) + inst.gen = inst.gen + 1 + return (dfhack.saferesume(inst.coro, ...)) +end + +-- Starts a new background script by calling the function. +function start(fn,...) + local coro = coroutine.create(fn) + local inst = { + coro = coro, + gen = 0, + } + scripts[coro] = inst + return do_resume(inst, ...) +end + +-- Checks if called from a background script +function in_script() + return scripts[coroutine.running()] ~= nil +end + +local function getinst() + local inst = scripts[coroutine.running()] + if not inst then + error('Not in a gui script coroutine.') + end + return inst +end + +local function invoke_resume(inst,gen,quiet,...) + local state = coroutine.status(inst.coro) + if state ~= 'suspended' then + if state ~= 'dead' then + dfhack.printerr(debug.traceback('resuming a non-waiting continuation')) + end + elseif inst.gen > gen then + if not quiet then + dfhack.printerr(debug.traceback('resuming an expired continuation')) + end + else + do_resume(inst, ...) + end +end + +-- Returns a callback that resumes the script from wait with given return values +function mkresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, false, ...) +end + +-- Like mkresume, but does not complain if already resumed from this wait +function qresume(...) + local inst = getinst() + return curry(invoke_resume, inst, inst.gen, true, ...) +end + +-- Wait until a mkresume callback is called, then return its arguments. +-- Once it returns, all mkresume callbacks created before are invalidated. +function wait() + getinst() -- check that the context is right + return coroutine.yield() +end + +-- Wraps dfhack.timeout for coroutines. +function sleep(time, quantity) + if dfhack.timeout(time, quantity, mkresume()) then + wait() + return true + else + return false + end +end + +-- Some dialog wrappers: + +function showMessage(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_close = qresume(nil) + }:show() + + return wait() +end + +function showYesNoPrompt(title, text, tcolor) + dlg.MessageBox{ + frame_title = title, + text = text, + text_pen = tcolor, + on_accept = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showInputPrompt(title, text, tcolor, input, min_width) + dlg.InputBox{ + frame_title = title, + text = text, + text_pen = tcolor, + input = input, + frame_width = min_width, + on_input = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +function showListPrompt(title, text, tcolor, choices, min_width) + dlg.ListBox{ + frame_title = title, + text = text, + text_pen = tcolor, + choices = choices, + frame_width = min_width, + on_select = mkresume(true), + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + +return _ENV From 7962f24cce8f9049a0e280cd3f1d04c6c9520a65 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 2 Oct 2012 10:20:54 -0500 Subject: [PATCH 05/21] Display actual key bindings for Manipulator --- plugins/manipulator.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 164b13dd7..633098a10 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1105,30 +1105,30 @@ void viewscreen_unitlaborsst::render() } int x = 2; - OutputString(10, x, gps->dimy - 3, "Enter"); // SELECT key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); - OutputString(10, x, gps->dimy - 3, "Shift+Enter"); // SELECT_ALL key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, "); - OutputString(10, x, gps->dimy - 3, "v"); // UNITJOB_VIEW key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); OutputString(15, x, gps->dimy - 3, ": ViewCre, "); - OutputString(10, x, gps->dimy - 3, "c"); // UNITJOB_ZOOM_CRE key + OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); x = 2; - OutputString(10, x, gps->dimy - 2, "Esc"); // LEAVESCREEN key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); 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(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); 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 + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); OutputString(15, x, gps->dimy - 2, ": Sort by ("); - OutputString(10, x, gps->dimy - 2, "Tab"); // CHANGETAB key + OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::CHANGETAB)); OutputString(15, x, gps->dimy - 2, ") "); switch (altsort) { @@ -1182,7 +1182,7 @@ struct unitlist_hook : df::viewscreen_unitlistst if (units[page].size()) { int x = 2; - OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key + OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); } } From 33aead34b43799e440e58586a215df69c8c3c02e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 19:53:16 +0400 Subject: [PATCH 06/21] Implement a more automated search mode based on keys for some globals. --- library/lua/memscan.lua | 7 +- scripts/devel/find-offsets.lua | 306 +++++++++++++++++++++++++++++---- 2 files changed, 279 insertions(+), 34 deletions(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 970f821c2..6764a0d58 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -358,7 +358,7 @@ end -- Interactive search utility -function DiffSearcher:find_interactive(prompt,data_type,condition_cb) +function DiffSearcher:find_interactive(prompt,data_type,condition_cb,iter_limit) enum = enum or {} -- Loop for restarting search from scratch @@ -374,6 +374,11 @@ function DiffSearcher:find_interactive(prompt,data_type,condition_cb) while true do print('') + if iter_limit and ccursor >= iter_limit then + dfhack.printerr(' Iteration limit reached without a solution.') + break + end + local ok, value, delta = condition_cb(ccursor) ccursor = ccursor + 1 diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 07a058474..45f59e154 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -2,6 +2,7 @@ local utils = require 'utils' local ms = require 'memscan' +local gui = require 'gui' local is_known = dfhack.internal.getAddress @@ -53,6 +54,35 @@ end local searcher = ms.DiffSearcher.new(data) +local function get_screen(class, prompt) + if not is_known('gview') then + print('Please navigate to '..prompt) + if not utils.prompt_yes_no('Proceed?', true) then + return nil + end + return true + end + + while true do + local cs = dfhack.gui.getCurViewscreen(true) + if not df.is_instance(class, cs) then + print('Please navigate to '..prompt) + if not utils.prompt_yes_no('Proceed?', true) then + return nil + end + else + return cs + end + end +end + +local function screen_title() + return get_screen(df.viewscreen_titlest, 'the title screen') +end +local function screen_dwarfmode() + return get_screen(df.viewscreen_dwarfmodest, 'the main dwarf mode screen') +end + local function validate_offset(name,validator,addr,tname,...) local obj = data:object_by_field(addr,tname,...) if obj and not validator(obj) then @@ -136,13 +166,95 @@ local function list_index_choices(length_func) end end +local function can_feed() + return not force_scan['nofeed'] and is_known 'gview' +end + +local function dwarfmode_feed_input(...) + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + qerror('could not retrieve dwarfmode screen') + end + for _,v in ipairs({...}) do + gui.simulateInput(screen, v) + end +end + +local function dwarfmode_to_top() + if not can_feed() then + return false + end + + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + return false + end + + for i=0,10 do + if is_known 'ui' and df.global.ui.main.mode == df.ui_sidebar_mode.Default then + break + end + gui.simulateInput(screen, 'LEAVESCREEN') + end + + -- force pause just in case + screen.keyRepeat = 1 + return true +end + +local function feed_menu_choice(catnames,catkeys,enum) + return function (idx) + idx = idx % #catnames + 1 + dwarfmode_feed_input(catkeys[idx]) + if enum then + return true, enum[catnames[idx]] + else + return true, catnames[idx] + end + end +end + +local function feed_list_choice(count,upkey,downkey) + return function(idx) + if idx > 0 then + local ok, len + if type(count) == 'number' then + ok, len = true, count + else + ok, len = pcall(count) + end + if not ok then + len = 5 + elseif len > 10 then + len = 10 + end + + local hcnt = len-1 + local rix = 1 + (idx-1) % (hcnt*2) + + if rix >= hcnt then + dwarfmode_feed_input(upkey or 'SECONDSCROLL_UP') + return true, hcnt*2 - rix + else + dwarfmode_feed_input(donwkey or 'SECONDSCROLL_DOWN') + return true, rix + end + else + print(' Please select the first list item.') + if not utils.prompt_yes_no(' Proceed?') then + return false + end + return true, 0 + end + end +end + -- -- Cursor group -- local function find_cursor() - print('\nPlease navigate to the title screen to find cursor.') - if not utils.prompt_yes_no('Proceed?', true) then + if not screen_title() then return false end @@ -354,13 +466,35 @@ local function is_valid_world(world) end local function find_world() - local addr = searcher:find_menu_cursor([[ + local catnames = { + 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' + } + local catkeys = { + 'STOCKPILE_GRAVEYARD', 'STOCKPILE_REFUSE', 'STOCKPILE_STONE', 'STOCKPILE_WOOD', + 'STOCKPILE_GEM', 'STOCKPILE_BARBLOCK', 'STOCKPILE_CLOTH', 'STOCKPILE_LEATHER', + 'STOCKPILE_AMMO', 'STOCKPILE_COINS' + } + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_STOCKPILES') + + addr = searcher:find_interactive( + 'Auto-searching for world.', + 'int32_t', + feed_menu_choice(catnames, catkeys, df.stockpile_category), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for world. Please open the stockpile creation menu, and select different types as instructed below:]], - 'int32_t', - { 'Corpses', 'Refuse', 'Stone', 'Wood', 'Gems', 'Bars', 'Cloth', 'Leather', 'Ammo', 'Coins' }, - df.stockpile_category - ) + 'int32_t', catnames, df.stockpile_category + ) + end + validate_offset('world', is_valid_world, addr, df.world, 'selected_stockpile_type') end @@ -385,14 +519,37 @@ local function is_valid_ui(ui) end local function find_ui() - local addr = searcher:find_menu_cursor([[ + local catnames = { + 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', + 'DesignateUpStair', 'DesignateDownStair', 'DesignateUpDownStair', + 'DesignateUpRamp', 'DesignateChopTrees' + } + local catkeys = { + 'DESIGNATE_DIG', 'DESIGNATE_CHANNEL', 'DESIGNATE_DIG_REMOVE_STAIRS_RAMPS', + 'DESIGNATE_STAIR_UP', 'DESIGNATE_STAIR_DOWN', 'DESIGNATE_STAIR_UPDOWN', + 'DESIGNATE_RAMP', 'DESIGNATE_CHOP' + } + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_DESIGNATE') + + addr = searcher:find_interactive( + 'Auto-searching for ui.', + 'int16_t', + feed_menu_choice(catnames, catkeys, df.ui_sidebar_mode), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui. Please open the designation menu, and switch modes as instructed below:]], - 'int16_t', - { 'DesignateMine', 'DesignateChannel', 'DesignateRemoveRamps', 'DesignateUpStair', - 'DesignateDownStair', 'DesignateUpDownStair', 'DesignateUpRamp', 'DesignateChopTrees' }, - df.ui_sidebar_mode - ) + 'int16_t', catnames, df.ui_sidebar_mode + ) + end + validate_offset('ui', is_valid_ui, addr, df.ui, 'main', 'mode') end @@ -421,14 +578,32 @@ local function is_valid_ui_sidebar_menus(usm) end local function find_ui_sidebar_menus() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_sidebar_menus. Please select a Mason, +Craftsdwarfs, or Carpenters workshop, open the Add Job +menu, and move the cursor within:]], + 'int32_t', + feed_list_choice(7), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_sidebar_menus. Please switch to 'q' mode, select a Mason, Craftsdwarfs, or Carpenters workshop, open the Add Job menu, and move the cursor within:]], - 'int32_t', - { 0, 1, 2, 3, 4, 5, 6 }, - ordinal_names - ) + 'int32_t', + { 0, 1, 2, 3, 4, 5, 6 }, + ordinal_names + ) + end + validate_offset('ui_sidebar_menus', is_valid_ui_sidebar_menus, addr, df.ui_sidebar_menus, 'workshop_job', 'cursor') end @@ -455,14 +630,41 @@ local function is_valid_ui_build_selector(ubs) end local function find_ui_build_selector() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive([[ +Auto-searching for ui_build_selector. This requires mechanisms.]], + 'int32_t', + function(idx) + if idx == 0 then + dwarfmode_to_top() + dwarfmode_feed_input( + 'D_BUILDING', + 'HOTKEY_BUILDING_TRAP', + 'HOTKEY_BUILDING_TRAP_TRIGGER', + 'BUILDING_TRIGGER_ENABLE_CREATURE' + ) + else + dwarfmode_feed_input('BUILDING_TRIGGER_MIN_SIZE_DOWN') + end + return true, 50000 - 1000*idx + end, + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_build_selector. Please start constructing a pressure plate, and enable creatures. Then change the min weight as requested, remembering that the ui truncates the number, so when it shows "Min (5000df", it means 50000:]], - 'int32_t', - { 50000, 49000, 48000, 47000, 46000, 45000, 44000 } - ) + 'int32_t', + { 50000, 49000, 48000, 47000, 46000, 45000, 44000 } + ) + end + validate_offset('ui_build_selector', is_valid_ui_build_selector, addr, df.ui_build_selector, 'plate_info', 'unit_min') end @@ -601,13 +803,33 @@ end -- local function find_ui_unit_view_mode() - local addr = searcher:find_menu_cursor([[ + local catnames = { 'General', 'Inventory', 'Preferences', 'Wounds' } + local catkeys = { 'UNITVIEW_GEN', 'UNITVIEW_INV', 'UNITVIEW_PRF', 'UNITVIEW_WND' } + local addr + + if dwarfmode_to_top() and is_known('ui_selected_unit') then + dwarfmode_feed_input('D_VIEWUNIT') + + if df.global.ui_selected_unit < 0 then + df.global.ui_selected_unit = 0 + end + + addr = searcher:find_interactive( + 'Auto-searching for ui_unit_view_mode.', + 'int32_t', + feed_menu_choice(catnames, catkeys, df.ui_unit_view_mode.T_value), + 10 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_unit_view_mode. Having selected a unit with 'v', switch the pages as requested:]], - 'int32_t', - { 'General', 'Inventory', 'Preferences', 'Wounds' }, - df.ui_unit_view_mode.T_value - ) + 'int32_t', catnames, df.ui_unit_view_mode.T_value + ) + end + ms.found_offset('ui_unit_view_mode', addr) end @@ -620,14 +842,32 @@ local function look_item_list_count() end local function find_ui_look_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_LOOK') + + addr = searcher:find_interactive([[ +Auto-searching for ui_look_cursor. Please select a tile +with at least 5 items or units on the ground, and move +the cursor as instructed:]], + 'int32_t', + feed_list_choice(look_item_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_look_cursor. Please activate the 'k' mode, find a tile with many items or units on the ground, and select list entries as instructed:]], - 'int32_t', - list_index_choices(look_item_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(look_item_list_count), + ordinal_names + ) + end + ms.found_offset('ui_look_cursor', addr) end @@ -989,10 +1229,10 @@ end print('\nInitial globals (need title screen):\n') +exec_finder(find_gview, 'gview') exec_finder(find_cursor, { 'cursor', 'selection_rect', 'gamemode', 'gametype' }) exec_finder(find_announcements, 'announcements') exec_finder(find_d_init, 'd_init') -exec_finder(find_gview, 'gview') exec_finder(find_enabler, 'enabler') exec_finder(find_gps, 'gps') From 9f687f64a431e44f0c2f122589358d78b34d71b5 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 3 Oct 2012 12:58:05 +0400 Subject: [PATCH 07/21] Fix build. --- library/xml | 2 +- plugins/advtools.cpp | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/library/xml b/library/xml index 4c210ddb5..20c6d9c74 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4c210ddb5338d3a519d03f768da828c33480e4fe +Subproject commit 20c6d9c743f1c5a20bb288f427b101e9b2a138d7 diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index d674f5528..d6224f5c2 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -30,6 +30,7 @@ #include "df/viewscreen_optionst.h" #include "df/viewscreen_dungeonmodest.h" #include "df/viewscreen_dungeon_monsterstatusst.h" +#include "df/nemesis_flags.h" #include @@ -683,17 +684,19 @@ command_result adv_bodyswap (color_ostream &out, std::vector & par // Permanently re-link everything if (permanent) { + using namespace df::enums::nemesis_flags; + ui_advmode->player_id = linear_index(world->nemesis.all, new_nemesis); // Flag 0 appears to be the 'active adventurer' flag, and // the player_id field above seems to be computed using it // when a savegame is loaded. // Also, unless this is set, it is impossible to retire. - real_nemesis->flags.set(0, false); - new_nemesis->flags.set(0, true); + real_nemesis->flags.set(ACTIVE_ADVENTURER, false); + new_nemesis->flags.set(ACTIVE_ADVENTURER, true); - real_nemesis->flags.set(1, true); // former retired adventurer - new_nemesis->flags.set(2, true); // blue color in legends + real_nemesis->flags.set(RETIRED_ADVENTURER, true); // former retired adventurer + new_nemesis->flags.set(ADVENTURER, true); // blue color in legends // Reassign companions and acquaintances if (!no_make_leader) From 3a522768a2d16d9cd3a0bfcf53f1c877450d4bdd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 3 Oct 2012 19:07:04 +0400 Subject: [PATCH 08/21] Implement feed-based finders for the remaining applicable globals. --- scripts/devel/find-offsets.lua | 373 +++++++++++++++++++++++++++------ 1 file changed, 304 insertions(+), 69 deletions(-) diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index 45f59e154..2f7f1287c 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -180,6 +180,21 @@ local function dwarfmode_feed_input(...) end end +local function dwarfmode_step_frames(count) + local screen = screen_dwarfmode() + if not df.isvalid(screen) then + qerror('could not retrieve dwarfmode screen') + end + + for i = 1,(count or 1) do + gui.simulateInput(screen, 'D_ONESTEP') + if screen.keyRepeat ~= 1 then + qerror('Could not step one frame: . did not work') + end + screen:logic() + end +end + local function dwarfmode_to_top() if not can_feed() then return false @@ -241,9 +256,33 @@ local function feed_list_choice(count,upkey,downkey) end else print(' Please select the first list item.') - if not utils.prompt_yes_no(' Proceed?') then + if not utils.prompt_yes_no(' Proceed?', true) then + return false + end + return true, 0 + end + end +end + +local function feed_menu_bool(enter_seq, exit_seq) + return function(idx) + if idx == 0 then + if not utils.prompt_yes_no(' Proceed?', true) then + return false + end + return true, 0 + end + if idx == 5 then + print(' Please resize the game window.') + if not utils.prompt_yes_no(' Proceed?', true) then return false end + end + if idx%2 == 1 then + dwarfmode_feed_input(table.unpack(enter_seq)) + return true, 1 + else + dwarfmode_feed_input(table.unpack(exit_seq)) return true, 0 end end @@ -880,14 +919,32 @@ local function building_item_list_count() end local function find_ui_building_item_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDITEM') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_item_cursor. Please highlight a +workshop, trade depot or other building with at least 5 contained +items, and select as instructed:]], + 'int32_t', + feed_list_choice(building_item_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_item_cursor. Please activate the 't' mode, find a cluttered workshop, trade depot, or other building with many contained items, and select as instructed:]], - 'int32_t', - list_index_choices(building_item_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(building_item_list_count), + ordinal_names + ) + end + ms.found_offset('ui_building_item_cursor', addr) end @@ -896,17 +953,37 @@ end -- local function find_ui_workshop_in_add() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_workshop_in_add. Please select a +workshop, e.g. Carpenters or Masons.]], + 'int8_t', + feed_menu_bool( + { 'BUILDJOB_CANCEL', 'BUILDJOB_ADD' }, + { 'SELECT', 'SELECT', 'SELECT', 'SELECT', 'SELECT' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_workshop_in_add. Please activate the 'q' mode, find a workshop without jobs (or delete jobs), and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the add job menu', - [0] = 'add job, thus exiting the menu' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the add job menu', + [0] = 'add job, thus exiting the menu' } + ) + end + ms.found_offset('ui_workshop_in_add', addr) end @@ -919,13 +996,30 @@ local function workshop_job_list_count() end local function find_ui_workshop_job_cursor() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_workshop_job_cursor. Please highlight a +workshop with at least 5 contained jobs, and select as instructed:]], + 'int32_t', + feed_list_choice(workshop_job_list_count), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_workshop_job_cursor. Please activate the 'q' mode, find a workshop with many jobs, and select as instructed:]], - 'int32_t', - list_index_choices(workshop_job_list_count), - ordinal_names - ) + 'int32_t', + list_index_choices(workshop_job_list_count), + ordinal_names + ) + end + ms.found_offset('ui_workshop_job_cursor', addr) end @@ -934,17 +1028,39 @@ end -- local function find_ui_building_in_assign() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_in_assign. Please select a room, +i.e. a bedroom, tomb, office, dining room or statue garden.]], + 'int8_t', + feed_menu_bool( + { { 'BUILDJOB_STATUE_ASSIGN', 'BUILDJOB_COFFIN_ASSIGN', + 'BUILDJOB_CHAIR_ASSIGN', 'BUILDJOB_TABLE_ASSIGN', + 'BUILDJOB_BED_ASSIGN' } }, + { 'LEAVESCREEN' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_in_assign. Please activate the 'q' mode, select a room building (e.g. a bedroom) and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Assign owner menu', - [0] = 'press Esc to exit assign' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the Assign owner menu', + [0] = 'press Esc to exit assign' } + ) + end + ms.found_offset('ui_building_in_assign', addr) end @@ -953,17 +1069,39 @@ end -- local function find_ui_building_in_resize() - local addr = searcher:find_menu_cursor([[ + local addr + + if dwarfmode_to_top() then + dwarfmode_feed_input('D_BUILDJOB') + + addr = searcher:find_interactive([[ +Auto-searching for ui_building_in_resize. Please select a room, +i.e. a bedroom, tomb, office, dining room or statue garden.]], + 'int8_t', + feed_menu_bool( + { { 'BUILDJOB_STATUE_SIZE', 'BUILDJOB_COFFIN_SIZE', + 'BUILDJOB_CHAIR_SIZE', 'BUILDJOB_TABLE_SIZE', + 'BUILDJOB_BED_SIZE' } }, + { 'LEAVESCREEN' } + ), + 20 + ) + end + + if not addr then + addr = searcher:find_menu_cursor([[ Searching for ui_building_in_resize. Please activate the 'q' mode, select a room building (e.g. a bedroom) and do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int8_t', - { 1, 0 }, - { [1] = 'enter the Resize room mode', - [0] = 'press Esc to exit resize' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'enter the Resize room mode', + [0] = 'press Esc to exit resize' } + ) + end + ms.found_offset('ui_building_in_resize', addr) end @@ -971,13 +1109,40 @@ end -- window_x -- +local function feed_window_xyz(dec,inc,step) + return function(idx) + if idx == 0 then + for i = 1,30 do dwarfmode_feed_input(dec) end + else + dwarfmode_feed_input(inc) + end + return true, nil, step + end +end + local function find_window_x() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_x.', + 'int32_t', + feed_window_xyz('CURSOR_LEFT_FAST', 'CURSOR_RIGHT', 10), + 20 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_x. Please exit to main dwarfmode menu, scroll to the LEFT edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Right to scroll right one step.' - ) + 'int32_t', 10, + 'Please press Right to scroll right one step.' + ) + end + ms.found_offset('window_x', addr) end @@ -986,12 +1151,28 @@ end -- local function find_window_y() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_y.', + 'int32_t', + feed_window_xyz('CURSOR_UP_FAST', 'CURSOR_DOWN', 10), + 20 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_y. Please exit to main dwarfmode menu, scroll to the TOP edge, then do as instructed:]], - 'int32_t', 10, - 'Please press Down to scroll down one step.' - ) + 'int32_t', 10, + 'Please press Down to scroll down one step.' + ) + end + ms.found_offset('window_y', addr) end @@ -1000,14 +1181,30 @@ end -- local function find_window_z() - local addr = searcher:find_counter([[ + local addr + + if dwarfmode_to_top() then + addr = searcher:find_interactive( + 'Auto-searching for window_z.', + 'int32_t', + feed_window_xyz('CURSOR_UP_Z', 'CURSOR_DOWN_Z', -1), + 30 + ) + + dwarfmode_feed_input('D_HOTKEY1') + end + + if not addr then + addr = searcher:find_counter([[ Searching for window_z. Please exit to main dwarfmode menu, scroll to a Z level near surface, then do as instructed below. NOTE: If not done after first 3-4 steps, resize the game window.]], - 'int32_t', -1, - "Please press '>' to scroll one Z level down." - ) + 'int32_t', -1, + "Please press '>' to scroll one Z level down." + ) + end + ms.found_offset('window_z', addr) end @@ -1056,6 +1253,27 @@ function stop_autosave() end end +function step_n_frames(cnt, feed) + local world = df.global.world + local ctick = world.frame_counter + + if feed then + print(" Auto-stepping "..cnt.." frames.") + dwarfmode_step_frames(cnt) + return world.frame_counter-ctick + end + + local more = '' + while world.frame_counter-ctick < cnt do + print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") + more = ' more' + if not utils.prompt_yes_no(' Proceed?', true) then + return nil + end + end + return world.frame_counter-ctick +end + local function find_cur_year_tick() local zone if os_type == 'windows' then @@ -1070,12 +1288,21 @@ local function find_cur_year_tick() stop_autosave() - local addr = zone:find_counter([[ -Searching for cur_year_tick. Please exit to main dwarfmode -menu, then do as instructed below:]], - 'int32_t', 1, - "Please press '.' to step the game one frame." + local feed = dwarfmode_to_top() + local addr = zone:find_interactive( + 'Searching for cur_year_tick.', + 'int32_t', + function(idx) + if idx > 0 then + if not step_n_frames(1, feed) then + return false + end + end + return true, nil, 1 + end, + 20 ) + ms.found_offset('cur_year_tick', addr) end @@ -1083,20 +1310,6 @@ end -- cur_season_tick -- -function step_n_frames(cnt) - local world = df.global.world - local ctick = world.frame_counter - local more = '' - while world.frame_counter-ctick < cnt do - print(" Please step the game "..(cnt-world.frame_counter+ctick)..more.." frames.") - more = ' more' - if not utils.prompt_yes_no(' Done?', true) then - return nil - end - end - return world.frame_counter-ctick -end - local function find_cur_season_tick() if not (is_known 'cur_year_tick') then dfhack.printerr('Cannot search for cur_season_tick - prerequisites missing.') @@ -1105,13 +1318,14 @@ local function find_cur_season_tick() stop_autosave() + local feed = dwarfmode_to_top() local addr = searcher:find_interactive([[ Searching for cur_season_tick. Please exit to main dwarfmode menu, then do as instructed below:]], 'int32_t', function(ccursor) if ccursor > 0 then - if not step_n_frames(10) then + if not step_n_frames(10, feed) then return false end end @@ -1133,6 +1347,7 @@ local function find_cur_season() stop_autosave() + local feed = dwarfmode_to_top() local addr = searcher:find_interactive([[ Searching for cur_season. Please exit to main dwarfmode menu, then do as instructed below:]], @@ -1142,7 +1357,7 @@ menu, then do as instructed below:]], local cst = df.global.cur_season_tick df.global.cur_season_tick = 10079 df.global.cur_year_tick = df.global.cur_year_tick + (10079-cst)*10 - if not step_n_frames(10) then + if not step_n_frames(10, feed) then return false end end @@ -1203,7 +1418,7 @@ end -- local function find_pause_state() - local zone + local zone, addr if os_type == 'linux' or os_type == 'darwin' then zone = zoomed_searcher('ui_look_cursor', 32) elseif os_type == 'windows' then @@ -1213,13 +1428,33 @@ local function find_pause_state() stop_autosave() - local addr = zone:find_menu_cursor([[ + if dwarfmode_to_top() then + addr = zone:find_interactive( + 'Auto-searching for pause_state', + 'int8_t', + function(idx) + if idx%2 == 0 then + dwarfmode_feed_input('D_ONESTEP') + return true, 0 + else + screen_dwarfmode():logic() + return true, 1 + end + end, + 20 + ) + end + + if not addr then + addr = zone:find_menu_cursor([[ Searching for pause_state. Please do as instructed below:]], - 'int8_t', - { 1, 0 }, - { [1] = 'PAUSE the game', - [0] = 'UNPAUSE the game' } - ) + 'int8_t', + { 1, 0 }, + { [1] = 'PAUSE the game', + [0] = 'UNPAUSE the game' } + ) + end + ms.found_offset('pause_state', addr) end From faa131942c283cc7bcd809a469b2e22f411fa728 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:14:50 -0500 Subject: [PATCH 09/21] Partial rewrite of 'fastdwarf' plugin: * add "fastdwarf 2" to use DF builtin "turbo speed" debug setting * use Units::isCitizen() instead of race/civ check * only scan active units, not all of them * do both fastdwarf and teledwarf in the same loop * teledwarf: don't use MapCache - it's faster to do it directly * teledwarf: don't clear both occupancy flags - check unit 'prone' flag * teledwarf: set proper unit occupancy flag at destination tile * teledwarf: if destination tile has standing unit, make dwarf lie down * cleanup 'fastdwarf' command * improve help text --- plugins/fastdwarf.cpp | 201 ++++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 84 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index aea14e7f0..f1ecb0baa 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -2,134 +2,165 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/MapCache.h" +#include "modules/Units.h" +#include "modules/Maps.h" #include "DataDefs.h" -#include "df/ui.h" #include "df/world.h" #include "df/unit.h" +#include "df/map_block.h" using std::string; using std::vector; using namespace DFHack; using df::global::world; -using df::global::ui; // dfhack interface DFHACK_PLUGIN("fastdwarf"); +static bool enable_fastdwarf = false; +static bool enable_teledwarf = false; + DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; return CR_OK; } -static bool enable_fastdwarf = false; -static bool enable_teledwarf = false; - DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - // check run conditions - if(!world || !world->map.block_index) + // do we even need to do anything at all? + if (!enable_fastdwarf && !enable_teledwarf) + return CR_OK; + + // make sure the world is actually loaded + if (!world || !world->map.block_index) { enable_fastdwarf = enable_teledwarf = false; return CR_OK; } - int32_t race = ui->race_id; - int32_t civ = ui->civ_id; - - if ( enable_fastdwarf ) { - for (size_t i = 0; i < world->units.all.size(); i++) + + df::map_block *old_block, *new_block; + for (size_t i = 0; i < world->units.active.size(); i++) + { + df::unit *unit = world->units.active[i]; + // citizens only + if (!Units::isCitizen(unit)) + continue; + + if (enable_fastdwarf) { - df::unit *unit = world->units.all[i]; - - if (unit->race == race && unit->civ_id == civ && unit->counters.job_counter > 0) + + if (unit->counters.job_counter > 0) unit->counters.job_counter = 0; // could also patch the unit->job.current_job->completion_timer } - } - if ( enable_teledwarf ) { - MapExtras::MapCache *MCache = new MapExtras::MapCache(); - for (size_t i = 0; i < world->units.all.size(); i++) + if (enable_teledwarf) { - df::unit *unit = world->units.all[i]; - - if (unit->race != race || unit->civ_id != civ || unit->path.dest.x == -30000) + // don't do anything if the dwarf isn't going anywhere + if (unit->path.dest.x == -30000) continue; - if (unit->relations.draggee_id != -1 || unit->relations.dragger_id != -1) + + // skip dwarves that are dragging creatures or being dragged + if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) continue; + + // skip dwarves that are following other units if (unit->relations.following != 0) continue; - - MapExtras::Block* block = MCache->BlockAtTile(unit->pos); - df::coord2d pos(unit->pos.x % 16, unit->pos.y % 16); - df::tile_occupancy occ = block->OccupancyAt(pos); - occ.bits.unit = 0; - occ.bits.unit_grounded = 0; - block->setOccupancyAt(pos, occ); - - //move immediately to destination + + old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); + new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); + // just to be safe, prevent the dwarf from being moved to an unallocated map block! + if (!old_block || !new_block) + continue; + + // clear appropriate occupancy flags at old tile + if (unit->flags1.bits.on_ground) + // this is technically wrong, but the game will recompute this as needed + old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit_grounded = 0; + else + old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit = 0; + + // if there's already somebody standing at the destination, then force the unit to lay down + if (new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit) + unit->flags1.bits.on_ground = 1; + + // set appropriate occupancy flags at new tile + if (unit->flags1.bits.on_ground) + new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit_grounded = 1; + else + new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit = 1; + + // move unit to destination unit->pos.x = unit->path.dest.x; unit->pos.y = unit->path.dest.y; unit->pos.z = unit->path.dest.z; } - MCache->WriteAll(); - delete MCache; } return CR_OK; } static command_result fastdwarf (color_ostream &out, vector & parameters) { - if (parameters.size() == 1) { - if ( parameters[0] == "0" ) { - enable_fastdwarf = false; - enable_teledwarf = false; - } else if ( parameters[0] == "1" ) { - enable_fastdwarf = true; - enable_teledwarf = false; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + if (parameters.size() > 2) { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } + + if (parameters.size() <= 2) + { + if (parameters.size() == 2) + { + if (parameters[1] == "0") + enable_teledwarf = false; + else if (parameters[1] == "1") + enable_teledwarf = true; + else + { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } } - } else if (parameters.size() == 2) { - if ( parameters[0] == "0" ) { + else + enable_teledwarf = false; + if (parameters[0] == "0") + { enable_fastdwarf = false; - } else if ( parameters[0] == "1" ) { + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; + } + else if (parameters[0] == "1") + { enable_fastdwarf = true; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + if (df::global::debug_turbospeed) + *df::global::debug_turbospeed = false; } - if ( parameters[1] == "0" ) { - enable_teledwarf = false; - } else if ( parameters[1] == "1" ) { - enable_teledwarf = true; - } else { - out.print("Incorrect usage.\n"); - return CR_OK; + else if (parameters[0] == "2") + { + if (df::global::debug_turbospeed) + { + enable_fastdwarf = false; + *df::global::debug_turbospeed = true; + } + else + { + out.print("Speed level 2 not available.\n"); + return CR_FAILURE; + } } - } else if (parameters.size() == 0) { - //print status - out.print("Current state: fast = %d, teleport = %d.\n", enable_fastdwarf, enable_teledwarf); - } else { - out.print("Incorrect usage.\n"); - return CR_OK; - } - /*if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1")) - { - if (parameters[0] == "0") - enable_fastdwarf = 0; else - enable_fastdwarf = 1; - out.print("fastdwarf %sactivated.\n", (enable_fastdwarf ? "" : "de")); + { + out.print("Incorrect usage.\n"); + return CR_FAILURE; + } } - else - { - out.print("Makes your minions move at ludicrous speeds.\n" - "Activate with 'fastdwarf 1', deactivate with 'fastdwarf 0'.\n" - "Current state: %d.\n", enable_fastdwarf); - }*/ + + out.print("Current state: fast = %d, teleport = %d.\n", + (df::global::debug_turbospeed && *df::global::debug_turbospeed) ? 2 : (enable_fastdwarf ? 1 : 0), + enable_teledwarf ? 1 : 0); return CR_OK; } @@ -139,14 +170,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector (tele)\n" + "Valid values for speed:\n" + " * 0 - Make dwarves move and work at standard speed.\n" + " * 1 - Make dwarves move and work at maximum speed.\n" + " * 2 - Make ALL creatures move and work at maximum speed.\n" + "Valid values for (tele):\n" + " * 0 - Disable dwarf teleportation (default)\n" + " * 1 - Make dwarves teleport to their destinations instantly.\n" )); return CR_OK; From 6b3d85eb0feb556a3dd73144d1e61009e2753e56 Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:17:33 -0500 Subject: [PATCH 10/21] Don't make teledwarf skip everything after it if there's a problem --- plugins/fastdwarf.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index f1ecb0baa..4964b542a 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -52,12 +52,12 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_fastdwarf) { - if (unit->counters.job_counter > 0) unit->counters.job_counter = 0; // could also patch the unit->job.current_job->completion_timer } - if (enable_teledwarf) + + if (enable_teledwarf) do { // don't do anything if the dwarf isn't going anywhere if (unit->path.dest.x == -30000) @@ -73,7 +73,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); - // just to be safe, prevent the dwarf from being moved to an unallocated map block! + // make sure source and dest map blocks are valid if (!old_block || !new_block) continue; @@ -98,7 +98,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) unit->pos.x = unit->path.dest.x; unit->pos.y = unit->path.dest.y; unit->pos.z = unit->path.dest.z; - } + } while (0); } return CR_OK; } From ddcc2ee90da2cdd787a81aab09adfc118fd7a97d Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 4 Oct 2012 20:20:35 -0500 Subject: [PATCH 11/21] Should use "break" in this construct, not "continue" --- plugins/fastdwarf.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 4964b542a..3f3905ecc 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -61,21 +61,21 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { // don't do anything if the dwarf isn't going anywhere if (unit->path.dest.x == -30000) - continue; + break; // skip dwarves that are dragging creatures or being dragged if ((unit->relations.draggee_id != -1) || (unit->relations.dragger_id != -1)) - continue; + break; // skip dwarves that are following other units if (unit->relations.following != 0) - continue; + break; old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); // make sure source and dest map blocks are valid if (!old_block || !new_block) - continue; + break; // clear appropriate occupancy flags at old tile if (unit->flags1.bits.on_ground) From 5396a67465a374260f81baef03c03381d8a09059 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 12:40:46 +0400 Subject: [PATCH 12/21] Some tweaking and edited NEWS. --- NEWS | 5 +++- .../include/df/custom/coord_path.methods.inc | 7 +++++ library/include/modules/MapCache.h | 8 ------ library/include/modules/Maps.h | 15 +++++++++++ library/modules/Maps.cpp | 6 ++--- plugins/fastdwarf.cpp | 27 ++++++++++--------- 6 files changed, 44 insertions(+), 24 deletions(-) diff --git a/NEWS b/NEWS index 65339ad64..e066b7431 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ DFHack future - Nothing yet! + Internals: + - support for displaying active keybindings properly. + Misc improvements: + - fastdwarf: new mode using debug flags, and some internal consistency fixes. DFHack v0.34.11-r2 diff --git a/library/include/df/custom/coord_path.methods.inc b/library/include/df/custom/coord_path.methods.inc index 9acebb82a..5421796e3 100644 --- a/library/include/df/custom/coord_path.methods.inc +++ b/library/include/df/custom/coord_path.methods.inc @@ -1,5 +1,12 @@ +bool empty() const { return x.empty(); } unsigned size() const { return x.size(); } +void clear() { + x.clear(); + y.clear(); + z.clear(); +} + coord operator[] (unsigned idx) const { if (idx >= x.size()) return coord(); diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index ac083075f..c1c478bc6 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -47,14 +47,6 @@ namespace MapExtras class DFHACK_EXPORT MapCache; -template inline R index_tile(T &v, df::coord2d p) { - return v[p.x&15][p.y&15]; -} - -inline bool is_valid_tile_coord(df::coord2d p) { - return (p.x & ~15) == 0 && (p.y & ~15) == 0; -} - class Block; class BlockInfo diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 3150acccf..632e8ec13 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -151,6 +151,21 @@ typedef uint8_t biome_indices40d [9]; */ typedef uint16_t t_temperatures [16][16]; +/** + * Index a tile array by a 2D coordinate, clipping it to mod 16 + */ +template inline R index_tile(T &v, df::coord2d p) { + return v[p.x&15][p.y&15]; +} + +/** + * Check if a 2D coordinate is in the 0-15 range. + */ +inline bool is_valid_tile_coord(df::coord2d p) { + return (p.x & ~15) == 0 && (p.y & ~15) == 0; +} + + /** * The Maps module * \ingroup grp_modules diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 5ef4ce829..482b950ba 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -454,7 +454,7 @@ df::coord2d Maps::getBlockTileBiomeRgn(df::map_block *block, df::coord2d pos) if (!block || !world->world_data) return df::coord2d(); - auto des = MapExtras::index_tile(block->designation,pos); + auto des = index_tile(block->designation,pos); unsigned idx = des.bits.biome; if (idx < 9) { @@ -529,8 +529,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) if (!block1 || !block2) return false; - auto tile1 = MapExtras::index_tile(block1->walkable, pos1); - auto tile2 = MapExtras::index_tile(block2->walkable, pos2); + auto tile1 = index_tile(block1->walkable, pos1); + auto tile2 = index_tile(block2->walkable, pos2); return tile1 && tile1 == tile2; } diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 3f3905ecc..28104b909 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -60,7 +60,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (enable_teledwarf) do { // don't do anything if the dwarf isn't going anywhere - if (unit->path.dest.x == -30000) + if (!unit->path.dest.isValid()) break; // skip dwarves that are dragging creatures or being dragged @@ -71,33 +71,36 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (unit->relations.following != 0) break; - old_block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); - new_block = Maps::getTileBlock(unit->path.dest.x, unit->path.dest.y, unit->path.dest.z); + // skip unconscious units + if (unit->counters.unconscious > 0) + break; + // make sure source and dest map blocks are valid - if (!old_block || !new_block) + auto old_occ = Maps::getTileOccupancy(unit->pos); + auto new_occ = Maps::getTileOccupancy(unit->path.dest); + if (!old_occ || !new_occ) break; // clear appropriate occupancy flags at old tile if (unit->flags1.bits.on_ground) // this is technically wrong, but the game will recompute this as needed - old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit_grounded = 0; + old_occ->bits.unit_grounded = 0; else - old_block->occupancy[unit->pos.x & 0xF][unit->pos.y & 0xF].bits.unit = 0; + old_occ->bits.unit = 0; // if there's already somebody standing at the destination, then force the unit to lay down - if (new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit) + if (new_occ->bits.unit) unit->flags1.bits.on_ground = 1; // set appropriate occupancy flags at new tile if (unit->flags1.bits.on_ground) - new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit_grounded = 1; + new_occ->bits.unit_grounded = 1; else - new_block->occupancy[unit->path.dest.x & 0xF][unit->path.dest.y & 0xF].bits.unit = 1; + new_occ->bits.unit = 1; // move unit to destination - unit->pos.x = unit->path.dest.x; - unit->pos.y = unit->path.dest.y; - unit->pos.z = unit->path.dest.z; + unit->pos = unit->path.dest; + unit->path.path.clear(); } while (0); } return CR_OK; From 6fefd0907281b41bbcbe27df1bea113ab8c46c1b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 12:51:34 +0400 Subject: [PATCH 13/21] Fix re-enabling autobutcher after being disabled. --- NEWS | 2 ++ plugins/zone.cpp | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index e066b7431..b36d2f121 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,8 @@ DFHack future Internals: - support for displaying active keybindings properly. + Notable bugfixes: + - autobutcher can be re-enabled again after being stopped. Misc improvements: - fastdwarf: new mode using debug flags, and some internal consistency fixes. diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c496f49b6..f32a77a2a 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3408,10 +3408,10 @@ command_result start_autobutcher(color_ostream &out) auto pworld = Core::getInstance().getWorld(); enable_autobutcher = true; + if (!config_autobutcher.isValid()) { config_autobutcher = pworld->AddPersistentData("autobutcher/config"); - config_autobutcher.ival(0) = enable_autobutcher; config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(3) = default_fk; @@ -3420,6 +3420,8 @@ command_result start_autobutcher(color_ostream &out) config_autobutcher.ival(6) = default_ma; } + config_autobutcher.ival(0) = enable_autobutcher; + out << "Starting autobutcher." << endl; init_autobutcher(out); return CR_OK; From 696cc4a911a077463def422189444a3acf3e46db Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 13:07:11 +0400 Subject: [PATCH 14/21] Stop autobutcher and autonestbox crashing if started without world. --- plugins/zone.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index f32a77a2a..6c1af4768 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3412,6 +3412,13 @@ command_result start_autobutcher(color_ostream &out) if (!config_autobutcher.isValid()) { config_autobutcher = pworld->AddPersistentData("autobutcher/config"); + + if (!config_autobutcher.isValid()) + { + out << "Cannot enable autobutcher without a world!" << endl; + return CR_OK; + } + config_autobutcher.ival(1) = sleep_autobutcher; config_autobutcher.ival(2) = enable_autobutcher_autowatch; config_autobutcher.ival(3) = default_fk; @@ -3431,11 +3438,6 @@ command_result init_autobutcher(color_ostream &out) { cleanup_autobutcher(out); auto pworld = Core::getInstance().getWorld(); - if(!pworld) - { - out << "Autobutcher has no world to read from!" << endl; - return CR_OK; - } config_autobutcher = pworld->GetPersistentData("autobutcher/config"); if(config_autobutcher.isValid()) @@ -3502,12 +3504,22 @@ command_result start_autonestbox(color_ostream &out) { auto pworld = Core::getInstance().getWorld(); enable_autonestbox = true; + if (!config_autobutcher.isValid()) { config_autonestbox = pworld->AddPersistentData("autonestbox/config"); - config_autonestbox.ival(0) = enable_autonestbox; + + if (!config_autobutcher.isValid()) + { + out << "Cannot enable autonestbox without a world!" << endl; + return CR_OK; + } + config_autonestbox.ival(1) = sleep_autonestbox; } + + config_autonestbox.ival(0) = enable_autonestbox; + out << "Starting autonestbox." << endl; init_autonestbox(out); return CR_OK; @@ -3517,11 +3529,6 @@ command_result init_autonestbox(color_ostream &out) { cleanup_autonestbox(out); auto pworld = Core::getInstance().getWorld(); - if(!pworld) - { - out << "Autonestbox has no world to read from!" << endl; - return CR_OK; - } config_autonestbox = pworld->GetPersistentData("autonestbox/config"); if(config_autonestbox.isValid()) From 459c69046bbbde9877b29858495f2593731983b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 13:46:20 +0400 Subject: [PATCH 15/21] Dissolve the World module class into a namespace. It made accessing persistent data way too cumbersome. --- library/Core.cpp | 8 +- library/LuaApi.cpp | 14 ++-- library/include/Core.h | 4 - library/include/modules/World.h | 68 ++++++++--------- library/modules/World.cpp | 126 +++++++++----------------------- plugins/autolabor.cpp | 14 ++-- plugins/burrows.cpp | 4 +- plugins/follow.cpp | 5 +- plugins/mode.cpp | 8 +- plugins/power-meter.cpp | 6 +- plugins/rename.cpp | 6 +- plugins/reveal.cpp | 18 ++--- plugins/seedwatch.cpp | 6 +- plugins/siege-engine.cpp | 45 +++++------- plugins/weather.cpp | 9 +-- plugins/workflow.cpp | 16 ++-- plugins/zone.cpp | 22 ++---- 17 files changed, 134 insertions(+), 245 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index bc90abc6b..a8000070f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -627,8 +627,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve } else if(first == "fpause") { - World * w = getWorld(); - w->SetPauseState(true); + World::SetPauseState(true); con.print("The game was forced to pause!\n"); } else if(first == "cls") @@ -1125,7 +1124,7 @@ void Core::doUpdate(color_ostream &out, bool first_update) last_world_data_ptr = new_wdata; last_local_map_ptr = new_mapdata; - getWorld()->ClearPersistentCache(); + World::ClearPersistentCache(); // and if the world is going away, we report the map change first if(had_map) @@ -1143,7 +1142,7 @@ void Core::doUpdate(color_ostream &out, bool first_update) if (isMapLoaded() != had_map) { - getWorld()->ClearPersistentCache(); + World::ClearPersistentCache(); onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); } } @@ -1681,7 +1680,6 @@ TYPE * Core::get##TYPE() \ return s_mods.p##TYPE;\ } -MODULE_GETTER(World); MODULE_GETTER(Materials); MODULE_GETTER(Notes); MODULE_GETTER(Graphic); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index aba6301d2..0df96f066 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -258,7 +258,7 @@ static PersistentDataItem persistent_by_struct(lua_State *state, int idx) int id = lua_tointeger(state, -1); lua_pop(state, 1); - PersistentDataItem ref = Core::getInstance().getWorld()->GetPersistentData(id); + PersistentDataItem ref = World::GetPersistentData(id); if (ref.isValid()) { @@ -323,7 +323,7 @@ static PersistentDataItem get_persistent(lua_State *state) { const char *str = luaL_checkstring(state, 1); - return Core::getInstance().getWorld()->GetPersistentData(str); + return World::GetPersistentData(str); } } @@ -342,7 +342,7 @@ static int dfhack_persistent_delete(lua_State *state) auto ref = get_persistent(state); - bool ok = Core::getInstance().getWorld()->DeletePersistentData(ref); + bool ok = World::DeletePersistentData(ref); lua_pushboolean(state, ok); return 1; @@ -356,7 +356,7 @@ static int dfhack_persistent_get_all(lua_State *state) bool prefix = (lua_gettop(state)>=2 ? lua_toboolean(state,2) : false); std::vector data; - Core::getInstance().getWorld()->GetPersistentData(&data, str, prefix); + World::GetPersistentData(&data, str, prefix); if (data.empty()) { @@ -396,7 +396,7 @@ static int dfhack_persistent_save(lua_State *state) if (add) { - ref = Core::getInstance().getWorld()->AddPersistentData(str); + ref = World::AddPersistentData(str); added = true; } else if (lua_getmetatable(state, 1)) @@ -409,13 +409,13 @@ static int dfhack_persistent_save(lua_State *state) } else { - ref = Core::getInstance().getWorld()->GetPersistentData(str); + ref = World::GetPersistentData(str); } // Auto-add if not found if (!ref.isValid()) { - ref = Core::getInstance().getWorld()->AddPersistentData(str); + ref = World::AddPersistentData(str); if (!ref.isValid()) luaL_error(state, "cannot create persistent entry"); added = true; diff --git a/library/include/Core.h b/library/include/Core.h index d2d7080da..b3db50c74 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -54,7 +54,6 @@ namespace DFHack { class Process; class Module; - class World; class Materials; class Notes; struct VersionInfo; @@ -120,8 +119,6 @@ namespace DFHack /// Is everything OK? bool isValid(void) { return !errorstate; } - /// get the world module - World * getWorld(); /// get the materials module Materials * getMaterials(); /// get the notes module @@ -205,7 +202,6 @@ namespace DFHack // Module storage struct { - World * pWorld; Materials * pMaterials; Notes * pNotes; Graphic * pGraphic; diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 1cd57e2f2..f4c31dcf3 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -55,8 +55,6 @@ namespace DFHack class DFContextShared; class DFHACK_EXPORT PersistentDataItem { - friend class World; - int id; std::string key_value; @@ -65,13 +63,17 @@ namespace DFHack public: static const int NumInts = 7; - bool isValid() { return id != 0; } - int entry_id() { return -id; } + bool isValid() const { return id != 0; } + int entry_id() const { return -id; } + + int raw_id() const { return id; } - const std::string &key() { return key_value; } + const std::string &key() const { return key_value; } std::string &val() { return *str_value; } + const std::string &val() const { return *str_value; } int &ival(int i) { return int_values[i]; } + int ival(int i) const { return int_values[i]; } PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) @@ -83,54 +85,42 @@ namespace DFHack * \ingroup grp_modules * \ingroup grp_world */ - class DFHACK_EXPORT World : public Module + namespace World { - public: - World(); - ~World(); - bool Start(); - bool Finish(); - ///true if paused, false if not - bool ReadPauseState(); + DFHACK_EXPORT bool ReadPauseState(); ///true if paused, false if not - void SetPauseState(bool paused); - - uint32_t ReadCurrentTick(); - uint32_t ReadCurrentYear(); - uint32_t ReadCurrentMonth(); - uint32_t ReadCurrentDay(); - uint8_t ReadCurrentWeather(); - void SetCurrentWeather(uint8_t weather); - bool ReadGameMode(t_gamemodes& rd); - bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous - std::string ReadWorldFolder(); + DFHACK_EXPORT void SetPauseState(bool paused); + + DFHACK_EXPORT uint32_t ReadCurrentTick(); + DFHACK_EXPORT uint32_t ReadCurrentYear(); + DFHACK_EXPORT uint32_t ReadCurrentMonth(); + DFHACK_EXPORT uint32_t ReadCurrentDay(); + DFHACK_EXPORT uint8_t ReadCurrentWeather(); + DFHACK_EXPORT void SetCurrentWeather(uint8_t weather); + DFHACK_EXPORT bool ReadGameMode(t_gamemodes& rd); + DFHACK_EXPORT bool WriteGameMode(const t_gamemodes & wr); // this is very dangerous + DFHACK_EXPORT std::string ReadWorldFolder(); // Store data in fake historical figure names. // This ensures that the values are stored in save games. - PersistentDataItem AddPersistentData(const std::string &key); - PersistentDataItem GetPersistentData(const std::string &key); - PersistentDataItem GetPersistentData(int entry_id); + DFHACK_EXPORT PersistentDataItem AddPersistentData(const std::string &key); + DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key); + DFHACK_EXPORT PersistentDataItem GetPersistentData(int entry_id); // Calls GetPersistentData(key); if not found, adds and sets added to true. // The result can still be not isValid() e.g. if the world is not loaded. - PersistentDataItem GetPersistentData(const std::string &key, bool *added); + DFHACK_EXPORT PersistentDataItem GetPersistentData(const std::string &key, bool *added); // Lists all items with the given key. // If prefix is true, search for keys starting with key+"/". // GetPersistentData(&vec,"",true) returns all items. // Items have alphabetic order by key; same key ordering is undefined. - void GetPersistentData(std::vector *vec, - const std::string &key, bool prefix = false); + DFHACK_EXPORT void GetPersistentData(std::vector *vec, + const std::string &key, bool prefix = false); // Deletes the item; returns true if success. - bool DeletePersistentData(const PersistentDataItem &item); + DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item); - void ClearPersistentCache(); - - private: - struct Private; - Private *d; - - bool BuildPersistentCache(); - }; + DFHACK_EXPORT void ClearPersistentCache(); + } } #endif diff --git a/library/modules/World.cpp b/library/modules/World.cpp index a4c4c5604..65d0c7cb8 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -48,89 +48,34 @@ using namespace DFHack; using df::global::world; -Module* DFHack::createWorld() -{ - return new World(); -} - -struct World::Private -{ - Private() - { - Inited = PauseInited = StartedWeather = StartedMode = false; - next_persistent_id = 0; - } - bool Inited; - - bool PauseInited; - bool StartedWeather; - bool StartedMode; - - int next_persistent_id; - std::multimap persistent_index; - - Process * owner; -}; - +static int next_persistent_id = 0; +static std::multimap persistent_index; typedef std::pair T_persistent_item; -World::World() -{ - Core & c = Core::getInstance(); - d = new Private; - d->owner = c.p; - - if(df::global::pause_state) - d->PauseInited = true; - - if(df::global::current_weather) - d->StartedWeather = true; - if (df::global::gamemode && df::global::gametype) - d->StartedMode = true; - - d->Inited = true; -} - -World::~World() -{ - delete d; -} - -bool World::Start() -{ - return true; -} - -bool World::Finish() -{ - return true; -} - bool World::ReadPauseState() { - if(!d->PauseInited) return false; - return *df::global::pause_state; + return DF_GLOBAL_VALUE(pause_state, false); } void World::SetPauseState(bool paused) { - if (d->PauseInited) - *df::global::pause_state = paused; + bool dummy; + DF_GLOBAL_VALUE(pause_state, dummy) = paused; } uint32_t World::ReadCurrentYear() { - return *df::global::cur_year; + return DF_GLOBAL_VALUE(cur_year, 0); } uint32_t World::ReadCurrentTick() { - return *df::global::cur_year_tick; + return DF_GLOBAL_VALUE(cur_year_tick, 0); } bool World::ReadGameMode(t_gamemodes& rd) { - if(d->Inited && d->StartedMode) + if(df::global::gamemode && df::global::gametype) { rd.g_mode = (DFHack::GameMode)*df::global::gamemode; rd.g_type = (DFHack::GameType)*df::global::gametype; @@ -140,7 +85,7 @@ bool World::ReadGameMode(t_gamemodes& rd) } bool World::WriteGameMode(const t_gamemodes & wr) { - if(d->Inited && d->StartedMode) + if(df::global::gamemode && df::global::gametype) { *df::global::gamemode = wr.g_mode; *df::global::gametype = wr.g_type; @@ -173,24 +118,24 @@ specified by memory.xml gets me the current month/date. */ uint32_t World::ReadCurrentMonth() { - return this->ReadCurrentTick() / 1200 / 28; + return ReadCurrentTick() / 1200 / 28; } uint32_t World::ReadCurrentDay() { - return ((this->ReadCurrentTick() / 1200) % 28) + 1; + return ((ReadCurrentTick() / 1200) % 28) + 1; } uint8_t World::ReadCurrentWeather() { - if (d->Inited && d->StartedWeather) + if (df::global::current_weather) return (*df::global::current_weather)[2][2]; return 0; } void World::SetCurrentWeather(uint8_t weather) { - if (d->Inited && d->StartedWeather) + if (df::global::current_weather) memset(df::global::current_weather, weather, 25); } @@ -206,13 +151,13 @@ static PersistentDataItem dataFromHFig(df::historical_figure *hfig) void World::ClearPersistentCache() { - d->next_persistent_id = 0; - d->persistent_index.clear(); + next_persistent_id = 0; + persistent_index.clear(); } -bool World::BuildPersistentCache() +static bool BuildPersistentCache() { - if (d->next_persistent_id) + if (next_persistent_id) return true; if (!Core::getInstance().isWorldLoaded()) return false; @@ -220,20 +165,20 @@ bool World::BuildPersistentCache() std::vector &hfvec = df::historical_figure::get_vector(); // Determine the next entry id as min(-100, lowest_id-1) - d->next_persistent_id = -100; + next_persistent_id = -100; if (hfvec.size() > 0 && hfvec[0]->id <= -100) - d->next_persistent_id = hfvec[0]->id-1; + next_persistent_id = hfvec[0]->id-1; // Add the entries to the lookup table - d->persistent_index.clear(); + persistent_index.clear(); for (size_t i = 0; i < hfvec.size() && hfvec[i]->id <= -100; i++) { if (!hfvec[i]->name.has_name || hfvec[i]->name.first_name.empty()) continue; - d->persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); + persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id)); } return true; @@ -247,14 +192,14 @@ PersistentDataItem World::AddPersistentData(const std::string &key) std::vector &hfvec = df::historical_figure::get_vector(); df::historical_figure *hfig = new df::historical_figure(); - hfig->id = d->next_persistent_id--; + hfig->id = next_persistent_id--; hfig->name.has_name = true; hfig->name.first_name = key; memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); hfvec.insert(hfvec.begin(), hfig); - d->persistent_index.insert(T_persistent_item(key, -hfig->id)); + persistent_index.insert(T_persistent_item(key, -hfig->id)); return dataFromHFig(hfig); } @@ -264,8 +209,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key) if (!BuildPersistentCache()) return PersistentDataItem(); - auto it = d->persistent_index.find(key); - if (it != d->persistent_index.end()) + auto it = persistent_index.find(key); + if (it != persistent_index.end()) return GetPersistentData(it->second); return PersistentDataItem(); @@ -305,24 +250,24 @@ void World::GetPersistentData(std::vector *vec, const std::s if (!BuildPersistentCache()) return; - auto eqrange = d->persistent_index.equal_range(key); + auto eqrange = persistent_index.equal_range(key); if (prefix) { if (key.empty()) { - eqrange.first = d->persistent_index.begin(); - eqrange.second = d->persistent_index.end(); + eqrange.first = persistent_index.begin(); + eqrange.second = persistent_index.end(); } else { std::string bound = key; if (bound[bound.size()-1] != '/') bound += "/"; - eqrange.first = d->persistent_index.lower_bound(bound); + eqrange.first = persistent_index.lower_bound(bound); bound[bound.size()-1]++; - eqrange.second = d->persistent_index.lower_bound(bound); + eqrange.second = persistent_index.lower_bound(bound); } } @@ -336,25 +281,26 @@ void World::GetPersistentData(std::vector *vec, const std::s bool World::DeletePersistentData(const PersistentDataItem &item) { - if (item.id > -100) + int id = item.raw_id(); + if (id > -100) return false; if (!BuildPersistentCache()) return false; std::vector &hfvec = df::historical_figure::get_vector(); - auto eqrange = d->persistent_index.equal_range(item.key_value); + auto eqrange = persistent_index.equal_range(item.key()); for (auto it2 = eqrange.first; it2 != eqrange.second; ) { auto it = it2; ++it2; - if (it->second != -item.id) + if (it->second != -id) continue; - d->persistent_index.erase(it); + persistent_index.erase(it); - int idx = binsearch_index(hfvec, item.id); + int idx = binsearch_index(hfvec, id); if (idx >= 0) { delete hfvec[idx]; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c39b126c9..42ccae4c2 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -547,9 +547,7 @@ static void reset_labor(df::unit_labor labor) static void init_state() { - auto pworld = Core::getInstance().getWorld(); - - config = pworld->GetPersistentData("autolabor/config"); + config = World::GetPersistentData("autolabor/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -558,7 +556,7 @@ static void init_state() if (!enable_autolabor) return; - auto cfg_haulpct = pworld->GetPersistentData("autolabor/haulpct"); + auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); if (cfg_haulpct.isValid()) { hauler_pct = cfg_haulpct.ival(0); @@ -572,7 +570,7 @@ static void init_state() labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - pworld->GetPersistentData(&items, "autolabor/labors/", true); + World::GetPersistentData(&items, "autolabor/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { @@ -594,7 +592,7 @@ static void init_state() std::stringstream name; name << "autolabor/labors/" << i; - labor_infos[i].config = pworld->AddPersistentData(name.str()); + labor_infos[i].config = World::AddPersistentData(name.str()); labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].active_dwarfs = 0; @@ -633,11 +631,9 @@ static void generate_labor_to_skill_map() static void enable_plugin(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - if (!config.isValid()) { - config = pworld->AddPersistentData("autolabor/config"); + config = World::AddPersistentData("autolabor/config"); config.ival(0) = 0; } diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index 0b66a7b9a..edcc01ecf 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -281,7 +281,7 @@ static void reset_tracking() static void init_map(color_ostream &out) { - auto config = Core::getInstance().getWorld()->GetPersistentData("burrows/config"); + auto config = World::GetPersistentData("burrows/config"); if (config.isValid()) { auto_grow = !!(config.ival(0) & 1); @@ -307,7 +307,7 @@ static void deinit_map(color_ostream &out) static PersistentDataItem create_config(color_ostream &out) { bool created; - auto rv = Core::getInstance().getWorld()->GetPersistentData("burrows/config", &created); + auto rv = World::GetPersistentData("burrows/config", &created); if (created && rv.isValid()) rv.ival(0) = 0; if (!rv.isValid()) diff --git a/plugins/follow.cpp b/plugins/follow.cpp index 96693df8d..5c14780a3 100644 --- a/plugins/follow.cpp +++ b/plugins/follow.cpp @@ -65,8 +65,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (!followedUnit) return CR_OK; //Don't do anything if we're not following a unit - DFHack::World *world = Core::getInstance().getWorld(); - if (world->ReadPauseState() && prevX==-1) return CR_OK; //Wait until the game is unpaused after first running "follow" to begin following + if (World::ReadPauseState() && prevX==-1) return CR_OK; //Wait until the game is unpaused after first running "follow" to begin following df::coord &unitPos = followedUnit->pos; @@ -120,7 +119,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) Gui::setViewCoords(x, y, z); //If, for some reason, the cursor is active and the screen is still moving, move the cursor along with the screen - if (c_x != -30000 && !world->ReadPauseState()) + if (c_x != -30000 && !World::ReadPauseState()) Gui::setCursorCoords(c_x - (prevX-x), c_y - (prevY-y), z); //Save this round's stuff for next time so we can monitor for changes made by the user diff --git a/plugins/mode.cpp b/plugins/mode.cpp index f9e6cd10c..d18cf7529 100644 --- a/plugins/mode.cpp +++ b/plugins/mode.cpp @@ -117,13 +117,9 @@ command_result mode (color_ostream &out_, vector & parameters) return CR_WRONG_USAGE; } - World *world; - { CoreSuspender suspend; - world = Core::getInstance().getWorld(); - world->Start(); - world->ReadGameMode(gm); + World::ReadGameMode(gm); } printCurrentModes(gm, out); @@ -202,7 +198,7 @@ command_result mode (color_ostream &out_, vector & parameters) { CoreSuspender suspend; - world->WriteGameMode(gm); + World::WriteGameMode(gm); } out << endl; diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp index 17261adb2..8db6c6f30 100644 --- a/plugins/power-meter.cpp +++ b/plugins/power-meter.cpp @@ -177,8 +177,7 @@ static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max if (!enabled) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("power-meter/enabled", NULL); + auto entry = World::GetPersistentData("power-meter/enabled", NULL); if (!entry.isValid()) return false; @@ -202,8 +201,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan switch (event) { case SC_WORLD_LOADED: { - auto pworld = Core::getInstance().getWorld(); - bool enable = pworld->GetPersistentData("power-meter/enabled").isValid(); + bool enable = World::GetPersistentData("power-meter/enabled").isValid(); if (enable) { diff --git a/plugins/rename.cpp b/plugins/rename.cpp index ecebbb90c..bffb33111 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -215,8 +215,7 @@ static void init_buildings(bool enable) if (enable) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("rename/building_types"); + auto entry = World::GetPersistentData("rename/building_types"); if (entry.isValid()) { @@ -245,8 +244,7 @@ static bool renameBuilding(df::building *bld, std::string name) if (!name.empty() && !is_enabled_building(code)) { - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("rename/building_types", NULL); + auto entry = World::GetPersistentData("rename/building_types", NULL); if (!entry.isValid()) return false; diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index f35424309..8db7c9112 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -87,19 +87,18 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode == game_mode::DWARF) { // if the map is revealed and we're in fortress mode, force the game to pause. if(revealed == REVEALED) { - World->SetPauseState(true); + World::SetPauseState(true); } else if(nopause_state) { - World->SetPauseState(false); + World::SetPauseState(false); } } return CR_OK; @@ -185,14 +184,13 @@ command_result reveal(color_ostream &out, vector & params) CoreSuspender suspend; - World *World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode == game_mode::ADVENTURE) { revealAdventure(out); @@ -234,7 +232,7 @@ command_result reveal(color_ostream &out, vector & params) if(pause) { revealed = REVEALED; - World->SetPauseState(true); + World::SetPauseState(true); } else revealed = DEMON_REVEALED; @@ -264,14 +262,13 @@ command_result unreveal(color_ostream &out, vector & params) } CoreSuspender suspend; - World *World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_mode != game_mode::DWARF) { con.printerr("Only in fortress mode.\n"); @@ -337,7 +334,6 @@ command_result revflood(color_ostream &out, vector & params) } CoreSuspender suspend; uint32_t x_max,y_max,z_max; - World * World = Core::getInstance().getWorld(); if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); @@ -349,7 +345,7 @@ command_result revflood(color_ostream &out, vector & params) return CR_FAILURE; } t_gamemodes gm; - World->ReadGameMode(gm); + World::ReadGameMode(gm); if(gm.g_type != game_type::DWARF_MAIN && gm.g_mode != game_mode::DWARF ) { out.printerr("Only in proper dwarf mode.\n"); diff --git a/plugins/seedwatch.cpp b/plugins/seedwatch.cpp index 676c02ed5..ae87e4381 100755 --- a/plugins/seedwatch.cpp +++ b/plugins/seedwatch.cpp @@ -106,9 +106,8 @@ command_result df_seedwatch(color_ostream &out, vector& parameters) materialsReverser[world->raws.plants.all[i]->id] = i; } - World *w = Core::getInstance().getWorld(); t_gamemodes gm; - w->ReadGameMode(gm);// FIXME: check return value + World::ReadGameMode(gm);// FIXME: check return value // if game mode isn't fortress mode if(gm.g_mode != game_mode::DWARF || @@ -296,9 +295,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) return CR_OK; counter = 0; - World *w = Core::getInstance().getWorld(); t_gamemodes gm; - w->ReadGameMode(gm);// FIXME: check return value + World::ReadGameMode(gm);// FIXME: check return value // if game mode isn't fortress mode if(gm.g_mode != game_mode::DWARF || !(gm.g_type == game_type::DWARF_MAIN || gm.g_type == game_type::DWARF_RECLAIM)) diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 7c880351e..324c924c4 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -347,10 +347,9 @@ static void load_engines() { clear_engines(); - auto pworld = Core::getInstance().getWorld(); std::vector vec; - pworld->GetPersistentData(&vec, "siege-engine/target/", true); + World::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); @@ -359,7 +358,7 @@ static void load_engines() engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6)); } - pworld->GetPersistentData(&vec, "siege-engine/ammo/", true); + World::GetPersistentData(&vec, "siege-engine/ammo/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -368,7 +367,7 @@ static void load_engines() engine->ammo_item_type = (df::item_type)it->ival(2); } - pworld->GetPersistentData(&vec, "siege-engine/stockpiles/", true); + World::GetPersistentData(&vec, "siege-engine/stockpiles/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -377,14 +376,14 @@ static void load_engines() auto pile = df::building::find(it->ival(1)); if (!pile || pile->getType() != building_type::Stockpile) { - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); continue;; } engine->stockpiles.insert(it->ival(1)); } - pworld->GetPersistentData(&vec, "siege-engine/profiles/", true); + World::GetPersistentData(&vec, "siege-engine/profiles/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -393,7 +392,7 @@ static void load_engines() engine->profile.max_level = it->ival(2); } - pworld->GetPersistentData(&vec, "siege-engine/profile-workers/", true); + World::GetPersistentData(&vec, "siege-engine/profile-workers/", true); for (auto it = vec.begin(); it != vec.end(); ++it) { auto engine = find_engine(df::building::find(it->ival(0)), true); @@ -402,7 +401,7 @@ static void load_engines() auto unit = df::unit::find(it->ival(1)); if (!unit || !Units::isCitizen(unit)) { - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); continue; } engine->profile.permitted_workers.push_back(it->ival(1)); @@ -434,9 +433,8 @@ static void clearTargetArea(df::building_siegeenginest *bld) if (auto engine = find_engine(bld)) engine->target = coord_range(); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); + World::DeletePersistentData(World::GetPersistentData(key)); } static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max) @@ -447,9 +445,8 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, 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); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return false; @@ -491,9 +488,8 @@ static int setAmmoItem(lua_State *L) if (!is_valid_enum_item(item_type)) luaL_argerror(L, 2, "invalid item type"); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/ammo/%d", engine->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return 0; @@ -523,9 +519,8 @@ static void forgetStockpileLink(EngineInfo *engine, int pile_id) { engine->stockpiles.erase(pile_id); - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", engine->id, pile_id); - pworld->DeletePersistentData(pworld->GetPersistentData(key)); + World::DeletePersistentData(World::GetPersistentData(key)); } static void update_stockpile_links(EngineInfo *engine) @@ -583,9 +578,8 @@ static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stock if (!enable_plugin()) return false; - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return false; @@ -620,9 +614,8 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld return NULL; // Save skill limits - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/profiles/%d", bld->id); - auto entry = pworld->GetPersistentData(key, NULL); + auto entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) return NULL; @@ -637,18 +630,18 @@ static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld auto &workers = engine->profile.permitted_workers; key = stl_sprintf("siege-engine/profile-workers/%d", bld->id); - pworld->GetPersistentData(&vec, key, true); + World::GetPersistentData(&vec, key, true); for (auto it = vec.begin(); it != vec.end(); ++it) { if (linear_index(workers, it->ival(1)) < 0) - pworld->DeletePersistentData(*it); + World::DeletePersistentData(*it); } for (size_t i = 0; i < workers.size(); i++) { key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[i]); - entry = pworld->GetPersistentData(key, NULL); + entry = World::GetPersistentData(key, NULL); if (!entry.isValid()) continue; entry.ival(0) = engine->id; @@ -1802,8 +1795,7 @@ static bool enable_plugin() if (is_enabled) return true; - auto pworld = Core::getInstance().getWorld(); - auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL); + auto entry = World::GetPersistentData("siege-engine/enabled", NULL); if (!entry.isValid()) return false; @@ -1828,8 +1820,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan case SC_MAP_LOADED: if (!gamemode || *gamemode == game_mode::DWARF) { - auto pworld = Core::getInstance().getWorld(); - bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid(); + bool enable = World::GetPersistentData("siege-engine/enabled").isValid(); if (enable) { diff --git a/plugins/weather.cpp b/plugins/weather.cpp index 33fa45fd3..98476ef2c 100644 --- a/plugins/weather.cpp +++ b/plugins/weather.cpp @@ -84,7 +84,6 @@ command_result weather (color_ostream &con, vector & parameters) CoreSuspender suspend; - DFHack::World * w = Core::getInstance().getWorld(); if(!df::global::current_weather) { con << "Weather support seems broken :(" << std::endl; @@ -123,22 +122,22 @@ command_result weather (color_ostream &con, vector & parameters) if(rain) { con << "Here comes the rain." << std::endl; - w->SetCurrentWeather(weather_type::Rain); + World::SetCurrentWeather(weather_type::Rain); } if(snow) { con << "Snow everywhere!" << std::endl; - w->SetCurrentWeather(weather_type::Snow); + World::SetCurrentWeather(weather_type::Snow); } if(clear) { con << "Suddenly, sunny weather!" << std::endl; - w->SetCurrentWeather(weather_type::None); + World::SetCurrentWeather(weather_type::None); } if(val_override != -1) { con << "I have no damn idea what this is... " << val_override << std::endl; - w->SetCurrentWeather(val_override); + World::SetCurrentWeather(val_override); } // FIXME: weather lock needs map ID to work reliably... needs to be implemented. } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 98258682e..2720baa83 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -438,9 +438,7 @@ static void start_protect(color_ostream &out) static void init_state(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - - config = pworld->GetPersistentData("workflow/config"); + config = World::GetPersistentData("workflow/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -448,14 +446,14 @@ static void init_state(color_ostream &out) // Parse constraints std::vector items; - pworld->GetPersistentData(&items, "workflow/constraints"); + World::GetPersistentData(&items, "workflow/constraints"); for (int i = items.size()-1; i >= 0; i--) { if (get_constraint(out, items[i].val(), &items[i])) continue; out.printerr("Lost constraint %s\n", items[i].val().c_str()); - pworld->DeletePersistentData(items[i]); + World::DeletePersistentData(items[i]); } last_tick_frame_count = world->frame_counter; @@ -469,11 +467,9 @@ static void init_state(color_ostream &out) static void enable_plugin(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - if (!config.isValid()) { - config = pworld->AddPersistentData("workflow/config"); + config = World::AddPersistentData("workflow/config"); config.ival(0) = 0; } @@ -729,7 +725,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->config = *cfg; else { - nct->config = Core::getInstance().getWorld()->AddPersistentData("workflow/constraints"); + nct->config = World::AddPersistentData("workflow/constraints"); nct->init(str); } @@ -743,7 +739,7 @@ static void delete_constraint(ItemConstraint *cv) if (idx >= 0) vector_erase_at(constraints, idx); - Core::getInstance().getWorld()->DeletePersistentData(cv->config); + World::DeletePersistentData(cv->config); delete cv; } diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 6c1af4768..542ce8a02 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -2769,10 +2769,7 @@ public: if(!rconfig.isValid()) { string keyname = "autobutcher/watchlist/" + getRaceName(raceId); - auto pworld = Core::getInstance().getWorld(); - rconfig = pworld->GetPersistentData(keyname); - if(!rconfig.isValid()) - rconfig = pworld->AddPersistentData(keyname); + rconfig = World::GetPersistentData(keyname, NULL); } if(rconfig.isValid()) { @@ -2795,7 +2792,7 @@ public: { if(!rconfig.isValid()) return; - Core::getInstance().getWorld()->DeletePersistentData(rconfig); + World::DeletePersistentData(rconfig); } void SortUnitsByAge() @@ -3405,13 +3402,11 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) command_result start_autobutcher(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); - enable_autobutcher = true; if (!config_autobutcher.isValid()) { - config_autobutcher = pworld->AddPersistentData("autobutcher/config"); + config_autobutcher = World::AddPersistentData("autobutcher/config"); if (!config_autobutcher.isValid()) { @@ -3437,9 +3432,8 @@ command_result start_autobutcher(color_ostream &out) command_result init_autobutcher(color_ostream &out) { cleanup_autobutcher(out); - auto pworld = Core::getInstance().getWorld(); - config_autobutcher = pworld->GetPersistentData("autobutcher/config"); + config_autobutcher = World::GetPersistentData("autobutcher/config"); if(config_autobutcher.isValid()) { if (config_autobutcher.ival(0) == -1) @@ -3471,7 +3465,7 @@ command_result init_autobutcher(color_ostream &out) // read watchlist from save std::vector items; - pworld->GetPersistentData(&items, "autobutcher/watchlist/", true); + World::GetPersistentData(&items, "autobutcher/watchlist/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); @@ -3502,12 +3496,11 @@ command_result cleanup_autobutcher(color_ostream &out) command_result start_autonestbox(color_ostream &out) { - auto pworld = Core::getInstance().getWorld(); enable_autonestbox = true; if (!config_autobutcher.isValid()) { - config_autonestbox = pworld->AddPersistentData("autonestbox/config"); + config_autonestbox = World::AddPersistentData("autonestbox/config"); if (!config_autobutcher.isValid()) { @@ -3528,9 +3521,8 @@ command_result start_autonestbox(color_ostream &out) command_result init_autonestbox(color_ostream &out) { cleanup_autonestbox(out); - auto pworld = Core::getInstance().getWorld(); - config_autonestbox = pworld->GetPersistentData("autonestbox/config"); + config_autonestbox = World::GetPersistentData("autonestbox/config"); if(config_autonestbox.isValid()) { if (config_autonestbox.ival(0) == -1) From ab90e3eefe4966139d38f33bf629969c1cf8fa4e Mon Sep 17 00:00:00 2001 From: Quietust Date: Sun, 7 Oct 2012 17:35:41 -0500 Subject: [PATCH 16/21] Manipulator - take false identities into account --- plugins/manipulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 633098a10..b3852437c 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -443,8 +443,8 @@ void viewscreen_unitlaborsst::refreshNames() UnitInfo *cur = units[i]; df::unit *unit = cur->unit; - cur->name = Translation::TranslateName(&unit->name, false); - cur->transname = Translation::TranslateName(&unit->name, true); + cur->name = Translation::TranslateName(Units::getVisibleName(unit), false); + cur->transname = Translation::TranslateName(Units::getVisibleName(unit), true); cur->profession = Units::getProfessionName(unit); } calcSize(); From 408f0cb06e682c437dbe82b64eb229d41b65fe44 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 12:10:02 +0400 Subject: [PATCH 17/21] Add a small stand-alone utility for managing binary patches. --- Lua API.html | 6 + Lua API.rst | 8 + NEWS | 2 + Readme.html | 577 ++++++++++++++++++++++------------------- Readme.rst | 30 +++ library/CMakeLists.txt | 3 +- library/binpatch.cpp | 308 ++++++++++++++++++++++ 7 files changed, 660 insertions(+), 274 deletions(-) create mode 100644 library/binpatch.cpp diff --git a/Lua API.html b/Lua API.html index 047ef9786..06fa5418e 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1914,6 +1914,12 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')

(For an explanation of new=true, see table assignment in the wrapper section)

+
  • utils.erase_sorted_key(vector,key,field,cmpfun)

    +

    Removes the item with the given key from the list. Returns: did_erase, vector[idx], idx.

    +
  • +
  • utils.erase_sorted(vector,item,field,cmpfun)

    +

    Exactly like erase_sorted_key, but if field is specified, takes the key from item[field].

    +
  • utils.prompt_yes_no(prompt, default)

    Presents a yes/no prompt to the user. If default is not nil, allows just pressing Enter to submit the default choice. diff --git a/Lua API.rst b/Lua API.rst index bf7ee45a7..fbb4b7d82 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1806,6 +1806,14 @@ utils (For an explanation of ``new=true``, see table assignment in the wrapper section) +* ``utils.erase_sorted_key(vector,key,field,cmpfun)`` + + Removes the item with the given key from the list. Returns: *did_erase, vector[idx], idx*. + +* ``utils.erase_sorted(vector,item,field,cmpfun)`` + + Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. + * ``utils.prompt_yes_no(prompt, default)`` Presents a yes/no prompt to the user. If ``default`` is not *nil*, diff --git a/NEWS b/NEWS index b36d2f121..5072cebc3 100644 --- a/NEWS +++ b/NEWS @@ -4,8 +4,10 @@ DFHack future - support for displaying active keybindings properly. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. + - stopped Dwarf Manipulator from unmasking vampires. Misc improvements: - fastdwarf: new mode using debug flags, and some internal consistency fixes. + - added a small stand-alone utility for applying and removing binary patches. DFHack v0.34.11-r2 diff --git a/Readme.html b/Readme.html index caf1f6a0e..c1d2d0d3c 100644 --- a/Readme.html +++ b/Readme.html @@ -338,187 +338,190 @@ access DF memory and allow for easier development of new tools.

  • Getting DFHack
  • Compatibility
  • Installation/Removal
  • -
  • Using DFHack
  • -
  • Something doesn't work, help!
  • -
  • The init file
      -
    • Setting keybindings
    • +
    • Using DFHack
    • -
    • Commands
        -
      • Game progress
          -
        • die
        • -
        • forcepause
        • -
        • nopause
        • -
        • fastdwarf
        • +
        • Something doesn't work, help!
        • +
        • The init file
        • -
        • Game interface
            -
          • follow
          • -
          • tidlers
          • -
          • twaterlvl
          • -
          • copystock
          • -
          • rename
          • +
          • Commands
              +
            • Game progress
            • -
            • Adventure mode
                -
              • adv-bodyswap
              • -
              • advtools
              • +
              • Game interface
              • -
              • Map modification
                  -
                • changelayer
                • -
                • changevein
                • -
                • changeitem
                • -
                • colonies
                • -
                • deramp (by zilpin)
                • -
                • feature
                • -
                • liquids
                • -
                • liquids-here
                • -
                • tiletypes
                • -
                • tiletypes-commands
                • -
                • tiletypes-here
                • -
                • tiletypes-here-point
                • -
                • tubefill
                • -
                • extirpate
                • -
                • grow
                • -
                • immolate
                • -
                • regrass
                • -
                • weather
                • +
                • Adventure mode
                • -
                • Map inspection
                    -
                  • cursecheck
                  • -
                  • flows
                  • -
                  • probe
                  • -
                  • prospect
                      -
                    • Pre-embark estimate
                    • +
                    • Map modification
                    • -
                    • reveal
                    • -
                    • unreveal
                    • -
                    • revtoggle
                    • -
                    • revflood
                    • -
                    • revforget
                    • -
                    • showmood
                    • +
                    • Map inspection
                        +
                      • cursecheck
                      • +
                      • flows
                      • +
                      • probe
                      • +
                      • prospect
                      • -
                      • Designations
                      • -
                      • Cleanup and garbage disposal
                          -
                        • clean
                        • -
                        • spotclean
                        • -
                        • autodump
                        • -
                        • autodump-destroy-here
                        • -
                        • autodump-destroy-item
                        • -
                        • cleanowned
                        • +
                        • Designations
                        • -
                        • Bugfixes
                            -
                          • drybuckets
                          • -
                          • fixdiplomats
                          • -
                          • fixmerchants
                          • -
                          • fixveins
                          • -
                          • tweak
                          • +
                          • Cleanup and garbage disposal
                          • -
                          • Mode switch and reclaim
                              -
                            • lair
                            • -
                            • mode
                            • +
                            • Bugfixes
                            • -
                            • Visualizer and data export
                                -
                              • ssense / stonesense
                              • -
                              • mapexport
                              • -
                              • dwarfexport
                              • +
                              • Mode switch and reclaim
                              • -
                              • Job management
                                  -
                                • job
                                • -
                                • job-material
                                • -
                                • job-duplicate
                                • -
                                • workflow
                                    -
                                  • Function
                                  • -
                                  • Constraint examples
                                  • +
                                  • Visualizer and data export
                                  • +
                                  • Job management
                                      +
                                    • job
                                    • +
                                    • job-material
                                    • +
                                    • job-duplicate
                                    • +
                                    • workflow
                                    • -
                                    • Fortress activity management
                                        -
                                      • seedwatch
                                      • -
                                      • zone
                                      • -
                                      • autonestbox
                                      • -
                                      • autobutcher
                                      • -
                                      • autolabor
                                      • +
                                      • Fortress activity management
                                          +
                                        • seedwatch
                                        • +
                                        • zone
                                        • -
                                        • Other
                                        • +
                                        • Other
                                        • -
                                        • Scripts
                                        • -
                                        • In-game interface tools
                                            -
                                          • Dwarf Manipulator
                                          • -
                                          • gui/liquids
                                          • -
                                          • gui/mechanisms
                                          • -
                                          • gui/rename
                                          • -
                                          • gui/room-list
                                          • -
                                          • gui/choose-weapons
                                          • +
                                          • Scripts
                                          • -
                                          • Behavior Mods
                                              -
                                            • Siege Engine
                                                -
                                              • Rationale
                                              • -
                                              • Configuration UI
                                              • +
                                              • In-game interface tools
                                              • -
                                              • Power Meter
                                              • -
                                              • Steam Engine @@ -585,9 +588,37 @@ of the game, so it becomes necessary to use the dfhack.init file to ensure that they are re-created every time it is loaded.

                                                Interactive commands like 'liquids' cannot be used as hotkeys.

                                                Most of the commands come from plugins. Those reside in 'hack/plugins/'.

                                                +
                                                +

                                                Patched binaries

                                                +

                                                On linux and OSX, users of patched binaries may have to find the relevant +section in symbols.xml, and add a new line with the checksum of their +executable:

                                                +
                                                +<md5-hash value='????????????????????????????????'/>
                                                +
                                                +

                                                In order to find the correct value of the hash, look into stderr.log; +DFHack prints an error there if it does not recognize the hash.

                                                +

                                                DFHack includes a small stand-alone utility for applying and removing +binary patches from the game executable. Use it from the regular operating +system console:

                                                +
                                                +
                                                  +
                                                • binpatch check "Dwarf Fortress.exe" patch.dif

                                                  +

                                                  Checks and prints if the patch is currently applied.

                                                  +
                                                • +
                                                • binpatch apply "Dwarf Fortress.exe" patch.dif

                                                  +

                                                  Applies the patch, unless it is already applied or in conflict.

                                                  +
                                                • +
                                                • binpatch remove "Dwarf Fortress.exe" patch.dif

                                                  +

                                                  Removes the patch, unless it is already removed.

                                                  +
                                                • +
                                                +
                                                +

                                                The patches are expected to be encoded in text format used by IDA.

                                                +
                                                -

                                                Something doesn't work, help!

                                                +

                                                Something doesn't work, help!

                                                First, don't panic :) Second, dfhack keeps a few log files in DF's folder - stderr.log and stdout.log. You can look at those and possibly find out what's happening. @@ -596,13 +627,13 @@ the issues tracker on github, contact me ( -

                                                The init file

                                                +

                                                The init file

                                                If your DF folder contains a file named dfhack.init, its contents will be run every time you start DF. This allows setting up keybindings. An example file is provided as dfhack.init-example - you can tweak it and rename to dfhack.init if you want to use this functionality.

                                                -

                                                Setting keybindings

                                                +

                                                Setting keybindings

                                                To set keybindings, use the built-in keybinding command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file.

                                                @@ -647,7 +678,7 @@ for context foo/bar/baz, possible matches are
                                                -

                                                Commands

                                                +

                                                Commands

                                                DFHack command syntax consists of a command name, followed by arguments separated by whitespace. To include whitespace in an argument, quote it in double quotes. To include a double quote character, use \" inside double quotes.

                                                @@ -669,13 +700,13 @@ The following two command lines are exactly equivalent:

                                                to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

                                                -

                                                Game progress

                                                +

                                                Game progress

                                                -

                                                die

                                                +

                                                die

                                                Instantly kills DF without saving.

                                                -

                                                forcepause

                                                +

                                                forcepause

                                                Forces DF to pause. This is useful when your FPS drops below 1 and you lose control of the game.

                                                @@ -686,12 +717,12 @@ control of the game.

                                                -

                                                nopause

                                                +

                                                nopause

                                                Disables pausing (both manual and automatic) with the exception of pause forced by 'reveal hell'. This is nice for digging under rivers.

                                                -

                                                fastdwarf

                                                +

                                                fastdwarf

                                                Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed.

                                                  @@ -706,29 +737,29 @@ by 'reveal hell'. This is nice for digging under rivers.

                                                -

                                                Game interface

                                                +

                                                Game interface

                                                -

                                                follow

                                                +

                                                follow

                                                Makes the game view follow the currently highlighted unit after you exit from current menu/cursor mode. Handy for watching dwarves running around. Deactivated by moving the view manually.

                                                -

                                                tidlers

                                                +

                                                tidlers

                                                Toggle between all possible positions where the idlers count can be placed.

                                                -

                                                twaterlvl

                                                +

                                                twaterlvl

                                                Toggle between displaying/not displaying liquid depth as numbers.

                                                -

                                                copystock

                                                +

                                                copystock

                                                Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily.

                                                -

                                                rename

                                                +

                                                rename

                                                Allows renaming various things.

                                                Options:

                                                @@ -762,9 +793,9 @@ siege engine or an activity zone.
                                                -

                                                Adventure mode

                                                +

                                                Adventure mode

                                                -

                                                adv-bodyswap

                                                +

                                                adv-bodyswap

                                                This allows taking control over your followers and other creatures in adventure mode. For example, you can make them pick up new arms and armor and equip them properly.

                                                @@ -777,7 +808,7 @@ properly.

                                                -

                                                advtools

                                                +

                                                advtools

                                                A package of different adventure mode tools (currently just one)

                                                Usage:

                                                @@ -800,9 +831,9 @@ on item type and being in shop.
                                                -

                                                Map modification

                                                +

                                                Map modification

                                                -

                                                changelayer

                                                +

                                                changelayer

                                                Changes material of the geology layer under cursor to the specified inorganic RAW material. Can have impact on all surrounding regions, not only your embark! By default changing stone to soil and vice versa is not allowed. By default @@ -877,7 +908,7 @@ You did save your game, right?

                                              • -

                                                changevein

                                                +

                                                changevein

                                                Changes material of the vein under cursor to the specified inorganic RAW material. Only affects tiles within the current 16x16 block - for veins and large clusters, you will need to use this command multiple times.

                                                @@ -890,7 +921,7 @@ large clusters, you will need to use this command multiple times.

                                                -

                                                changeitem

                                                +

                                                changeitem

                                                Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials @@ -930,7 +961,7 @@ crafters/haulers.

                                                -

                                                colonies

                                                +

                                                colonies

                                                Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

                                                Options:

                                                @@ -945,12 +976,12 @@ crafters/haulers.

                                                -

                                                deramp (by zilpin)

                                                +

                                                deramp (by zilpin)

                                                Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

                                                -

                                                feature

                                                +

                                                feature

                                                Enables management of map features.

                                                • Discovering a magma feature (magma pool, volcano, magma sea, or curious @@ -975,7 +1006,7 @@ that cavern to grow within your fortress.
                                                -

                                                liquids

                                                +

                                                liquids

                                                Allows adding magma, water and obsidian to the game. It replaces the normal dfhack command line and can't be used from a hotkey. Settings will be remembered as long as dfhack runs. Intended for use in combination with the command @@ -988,13 +1019,13 @@ temperatures (creating heat traps). You've been warned.

                                                -

                                                liquids-here

                                                +

                                                liquids-here

                                                Run the liquid spawner with the current/last settings made in liquids (if no settings in liquids were made it paints a point of 7/7 magma by default).

                                                Intended to be used as keybinding. Requires an active in-game cursor.

                                                -

                                                tiletypes

                                                +

                                                tiletypes

                                                Can be used for painting map tiles and is an interactive command, much like liquids.

                                                The tool works with two set of options and a brush. The brush determines which @@ -1055,27 +1086,27 @@ up.

                                                For more details, see the 'help' command while using this.

                                                -

                                                tiletypes-commands

                                                +

                                                tiletypes-commands

                                                Runs tiletypes commands, separated by ;. This makes it possible to change tiletypes modes from a hotkey.

                                                -

                                                tiletypes-here

                                                +

                                                tiletypes-here

                                                Apply the current tiletypes options at the in-game cursor position, including the brush. Can be used from a hotkey.

                                                -

                                                tiletypes-here-point

                                                +

                                                tiletypes-here-point

                                                Apply the current tiletypes options at the in-game cursor position to a single tile. Can be used from a hotkey.

                                                -

                                                tubefill

                                                +

                                                tubefill

                                                Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).

                                                -

                                                extirpate

                                                +

                                                extirpate

                                                A tool for getting rid of trees and shrubs. By default, it only kills a tree/shrub under the cursor. The plants are turned into ashes instantly.

                                                Options:

                                                @@ -1095,20 +1126,20 @@ a tree/shrub under the cursor. The plants are turned into ashes instantly.

                                                -

                                                grow

                                                +

                                                grow

                                                Makes all saplings present on the map grow into trees (almost) instantly.

                                                -

                                                immolate

                                                +

                                                immolate

                                                Very similar to extirpate, but additionally sets the plants on fire. The fires can and will spread ;)

                                                -

                                                regrass

                                                +

                                                regrass

                                                Regrows grass. Not much to it ;)

                                                -

                                                weather

                                                +

                                                weather

                                                Prints the current weather map by default.

                                                Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

                                                Options:

                                                @@ -1129,9 +1160,9 @@ can and will spread ;)

                                                -

                                                Map inspection

                                                +

                                                Map inspection

                                                -

                                                cursecheck

                                                +

                                                cursecheck

                                                Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).

                                                With an active in-game cursor only the selected tile will be observed. @@ -1186,17 +1217,17 @@ of curses, for example.

                                                -

                                                flows

                                                +

                                                flows

                                                A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map.

                                                -

                                                probe

                                                +

                                                probe

                                                Can be used to determine tile properties like temperature.

                                                -

                                                prospect

                                                +

                                                prospect

                                                Prints a big list of all the present minerals and plants. By default, only the visible part of the map is scanned.

                                                Options:

                                                @@ -1215,7 +1246,7 @@ the visible part of the map is scanned.

                                                -

                                                Pre-embark estimate

                                                +

                                                Pre-embark estimate

                                                If prospect is called during the embark selection screen, it displays an estimate of layer stone availability.

                                                @@ -1240,7 +1271,7 @@ that is actually present.

                                                -

                                                reveal

                                                +

                                                reveal

                                                This reveals the map. By default, HFS will remain hidden so that the demons don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, you won't be able to unpause until you hide the map again. If you really want @@ -1249,34 +1280,34 @@ to unpause with hell revealed, use 'reveal demons'.

                                                you move. When you use it this way, you don't need to run 'unreveal'.

                                                -

                                                unreveal

                                                +

                                                unreveal

                                                Reverts the effects of 'reveal'.

                                                -

                                                revtoggle

                                                +

                                                revtoggle

                                                Switches between 'reveal' and 'unreveal'.

                                                -

                                                revflood

                                                +

                                                revflood

                                                This command will hide the whole map and then reveal all the tiles that have a path to the in-game cursor.

                                                -

                                                revforget

                                                +

                                                revforget

                                                When you use reveal, it saves information about what was/wasn't visible before revealing everything. Unreveal uses this information to hide things again. This command throws away the information. For example, use in cases where you abandoned with the fort revealed and no longer want the data.

                                                -

                                                showmood

                                                +

                                                showmood

                                                Shows all items needed for the currently active strange mood.

                                                -

                                                Designations

                                                +

                                                Designations

                                                -

                                                burrow

                                                +

                                                burrow

                                                Miscellaneous burrow control. Allows manipulating burrows and automated burrow expansion while digging.

                                                Options:

                                                @@ -1324,17 +1355,17 @@ Digging 1-wide corridors with the miner inside the burrow is SLOW.
                                                -

                                                digv

                                                +

                                                digv

                                                Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

                                                -

                                                digvx

                                                +

                                                digvx

                                                A permanent alias for 'digv x'.

                                                -

                                                digl

                                                +

                                                digl

                                                Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it @@ -1342,11 +1373,11 @@ will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

                                                -

                                                diglx

                                                +

                                                diglx

                                                A permanent alias for 'digl x'.

                                                -

                                                digexp

                                                +

                                                digexp

                                                This command can be used for exploratory mining.

                                                See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

                                                There are two variables that can be set: pattern and filter.

                                                @@ -1409,7 +1440,7 @@ z-level deep layer was not such a good idea after all).

                                                -

                                                digcircle

                                                +

                                                digcircle

                                                A command for easy designation of filled and hollow circles. It has several types of options.

                                                Shape:

                                                @@ -1472,7 +1503,7 @@ repeats with the last selected parameters.

                                              -

                                              digtype

                                              +

                                              digtype

                                              For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated. If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.

                                              Options:

                                              @@ -1500,7 +1531,7 @@ If an argument is given, the designation of the selected tile is ignored, and al
                                              -

                                              filltraffic

                                              +

                                              filltraffic

                                              Set traffic designations using flood-fill starting at the cursor.

                                              Traffic Type Codes:

                                              @@ -1539,7 +1570,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
                                              -

                                              alltraffic

                                              +

                                              alltraffic

                                              Set traffic designations for every single tile of the map (useful for resetting traffic designations).

                                              Traffic Type Codes:

                                              @@ -1563,7 +1594,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'alltraffic N' - Set traffic to 'normal' for all tiles.
                                              -

                                              getplants

                                              +

                                              getplants

                                              This tool allows plant gathering and tree cutting by RAW ID. Specify the types of trees to cut down and/or shrubs to gather by their plant names, separated by spaces.

                                              @@ -1590,9 +1621,9 @@ all valid plant IDs will be listed.

                                              -

                                              Cleanup and garbage disposal

                                              +

                                              Cleanup and garbage disposal

                                              -

                                              clean

                                              +

                                              clean

                                              Cleans all the splatter that get scattered all over the map, items and creatures. In an old fortress, this can significantly reduce FPS lag. It can also spoil your !!FUN!!, so think before you use it.

                                              @@ -1626,12 +1657,12 @@ also spoil your !!FUN!!, so think before you use it.

                                              -

                                              spotclean

                                              +

                                              spotclean

                                              Works like 'clean map snow mud', but only for the tile under the cursor. Ideal if you want to keep that bloody entrance 'clean map' would clean up.

                                              -

                                              autodump

                                              +

                                              autodump

                                              This utility lets you quickly move all items designated to be dumped. Items are instantly moved to the cursor position, the dump flag is unset, and the forbid flag is set, as if it had been dumped normally. @@ -1658,17 +1689,17 @@ Be aware that any active dump item tasks still point at the item.

                                              -

                                              autodump-destroy-here

                                              +

                                              autodump-destroy-here

                                              Destroy items marked for dumping under cursor. Identical to autodump destroy-here, but intended for use as keybinding.

                                              -

                                              autodump-destroy-item

                                              +

                                              autodump-destroy-item

                                              Destroy the selected item. The item may be selected in the 'k' list, or inside a container. If called again before the game is resumed, cancels destroy.

                                              -

                                              cleanowned

                                              +

                                              cleanowned

                                              Confiscates items owned by dwarfs. By default, owned food on the floor and rotten items are confistacted and dumped.

                                              Options:

                                              @@ -1702,13 +1733,13 @@ worn items with 'X' damage and above.
                                              -

                                              Bugfixes

                                              +

                                              Bugfixes

                                              -

                                              drybuckets

                                              +

                                              drybuckets

                                              This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

                                              -

                                              fixdiplomats

                                              +

                                              fixdiplomats

                                              Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring about excess tree cutting. This command adds a Diplomat position to all Elven @@ -1717,19 +1748,19 @@ to violate them and potentially start wars) in case you haven't already modified your raws accordingly.

                                              -

                                              fixmerchants

                                              +

                                              fixmerchants

                                              This command adds the Guild Representative position to all Human civilizations, allowing them to make trade agreements (just as they did back in 0.28.181.40d and earlier) in case you haven't already modified your raws accordingly.

                                              -

                                              fixveins

                                              +

                                              fixveins

                                              Removes invalid references to mineral inclusions and restores missing ones. Use this if you broke your embark with tools like tiletypes, or if you accidentally placed a construction on top of a valuable mineral floor.

                                              -

                                              tweak

                                              +

                                              tweak

                                              Contains various tweaks for minor bugs.

                                              One-shot subcommands:

                                              @@ -1817,9 +1848,9 @@ to make them stand out more in the list.
                                              -

                                              Mode switch and reclaim

                                              +

                                              Mode switch and reclaim

                                              -

                                              lair

                                              +

                                              lair

                                              This command allows you to mark the map as 'monster lair', preventing item scatter on abandon. When invoked as 'lair reset', it does the opposite.

                                              Unlike reveal, this command doesn't save the information about tiles - you @@ -1839,7 +1870,7 @@ won't be able to restore state of real monster lairs using 'lair reset'.

                                              -

                                              mode

                                              +

                                              mode

                                              This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though.

                                              @@ -1859,9 +1890,9 @@ You just created a returnable mountain home and gained an adventurer.

                                              -

                                              Visualizer and data export

                                              +

                                              Visualizer and data export

                                              -

                                              ssense / stonesense

                                              +

                                              ssense / stonesense

                                              An isometric visualizer that runs in a second window. This requires working graphics acceleration and at least a dual core CPU (otherwise it will slow down DF).

                                              @@ -1874,19 +1905,19 @@ thread: http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

                                              -

                                              mapexport

                                              +

                                              mapexport

                                              Export the current loaded map as a file. This will be eventually usable with visualizers.

                                              -

                                              dwarfexport

                                              +

                                              dwarfexport

                                              Export dwarves to RuneSmith-compatible XML.

                                              -

                                              Job management

                                              +

                                              Job management

                                              -

                                              job

                                              +

                                              job

                                              Command for general job query and manipulation.

                                              Options:
                                              @@ -1905,7 +1936,7 @@ in a workshop, or the unit/jobs screen.
                                              -

                                              job-material

                                              +

                                              job-material

                                              Alter the material of the selected job.

                                              Invoked as:

                                              @@ -1923,7 +1954,7 @@ over the first available choice with the matching material.
                                               
                                               
                                              -

                                              job-duplicate

                                              +

                                              job-duplicate

                                              Duplicate the selected job in a workshop:
                                                @@ -1934,7 +1965,7 @@ instantly duplicates the job.
                                              -

                                              workflow

                                              +

                                              workflow

                                              Manage control of repeat jobs.

                                              Usage:

                                              @@ -1958,7 +1989,7 @@ Otherwise, enables or disables any of the following options:

                                              -

                                              Function

                                              +

                                              Function

                                              When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

                                              @@ -1969,7 +2000,7 @@ the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

                                              -

                                              Constraint examples

                                              +

                                              Constraint examples

                                              Keep metal bolts within 900-1000, and wood/bone within 150-200.

                                               workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
                                              @@ -2008,15 +2039,15 @@ command.
                                               
                                              -

                                              Fortress activity management

                                              +

                                              Fortress activity management

                                              -

                                              seedwatch

                                              +

                                              seedwatch

                                              Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

                                              See 'seedwatch help' for detailed description.

                                              -

                                              zone

                                              +

                                              zone

                                              Helps a bit with managing activity zones (pens, pastures and pits) and cages.

                                              Options:

                                              @@ -2115,7 +2146,7 @@ for war/hunt). Negatable.
                                              -

                                              Usage with single units

                                              +

                                              Usage with single units

                                              One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2124,7 +2155,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

                                              -

                                              Usage with filters

                                              +

                                              Usage with filters

                                              All filters can be used together with the 'assign' command.

                                              Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2142,14 +2173,14 @@ are not properly added to your own stocks; slaughtering them should work).

                                              Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

                                              -

                                              Mass-renaming

                                              +

                                              Mass-renaming

                                              Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

                                              -

                                              Cage zones

                                              +

                                              Cage zones

                                              Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2160,7 +2191,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

                                              -

                                              Examples

                                              +

                                              Examples

                                              zone assign all own ALPACA minage 3 maxage 10
                                              Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2186,7 +2217,7 @@ on the current default zone.
                                              -

                                              autonestbox

                                              +

                                              autonestbox

                                              Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2215,7 +2246,7 @@ frames between runs.

                                              -

                                              autobutcher

                                              +

                                              autobutcher

                                              Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

                                              Named units will be completely ignored (to protect specific animals from @@ -2323,7 +2354,7 @@ autobutcher.bat

                                              -

                                              autolabor

                                              +

                                              autolabor

                                              Automatically manage dwarf labors.

                                              When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2337,14 +2368,14 @@ while it is enabled.

                                              -

                                              Other

                                              +

                                              Other

                                              -

                                              catsplosion

                                              +

                                              catsplosion

                                              Makes cats just multiply. It is not a good idea to run this more than once or twice.

                                              -

                                              dfusion

                                              +

                                              dfusion

                                              This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin.

                                              See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15

                                              Confirmed working DFusion plugins:

                                              @@ -2366,7 +2397,7 @@ twice.

                                              -

                                              misery

                                              +

                                              misery

                                              When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

                                              Usage:

                                              @@ -2390,7 +2421,7 @@ twice.

                                              -

                                              Scripts

                                              +

                                              Scripts

                                              Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

                                              @@ -2399,7 +2430,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

                                              Some notable scripts:

                                              -

                                              fix/*

                                              +

                                              fix/*

                                              Scripts in this subdirectory fix various bugs and issues, some of them obscure.

                                              • fix/dead-units

                                                @@ -2425,22 +2456,22 @@ caused by autodump bugs or other hacking mishaps.

                                              -

                                              gui/*

                                              +

                                              gui/*

                                              Scripts that implement dialogs inserted into the main game window are put in this directory.

                                              -

                                              quicksave

                                              +

                                              quicksave

                                              If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

                                              -

                                              setfps

                                              +

                                              setfps

                                              Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

                                              -

                                              siren

                                              +

                                              siren

                                              Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -2448,7 +2479,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

                                              -

                                              growcrops

                                              +

                                              growcrops

                                              Instantly grow seeds inside farming plots.

                                              With no argument, this command list the various seed types currently in use in your farming plots. @@ -2460,7 +2491,7 @@ growcrops plump 40

                                              -

                                              removebadthoughts

                                              +

                                              removebadthoughts

                                              This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

                                              With a selected unit in 'v' mode, will clear this unit's mind, otherwise @@ -2473,7 +2504,7 @@ you unpause.

                                              it removed.

                                              -

                                              slayrace

                                              +

                                              slayrace

                                              Kills any unit of a given race.

                                              With no argument, lists the available races.

                                              With the special argument 'him', targets only the selected creature.

                                              @@ -2499,7 +2530,7 @@ slayrace elve magma
                                              -

                                              magmasource

                                              +

                                              magmasource

                                              Create an infinite magma source on a tile.

                                              This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

                                              @@ -2514,7 +2545,7 @@ To remove all placed sources, call magmasource stop

                                              With no argument, this command shows an help message and list existing sources.

                                              -

                                              digfort

                                              +

                                              digfort

                                              A script to designate an area for digging according to a plan in csv format.

                                              This script, inspired from quickfort, can designate an area for digging. Your plan should be stored in a .csv file like this:

                                              @@ -2532,7 +2563,7 @@ To skip a row in your design, use a single ;.<

                                              The script takes the plan filename, starting from the root df folder.

                                              -

                                              superdwarf

                                              +

                                              superdwarf

                                              Similar to fastdwarf, per-creature.

                                              To make any creature superfast, target it ingame using 'v' and:

                                              @@ -2542,17 +2573,17 @@ superdwarf add
                                               

                                              This plugin also shortens the 'sleeping' and 'on break' periods of targets.

                                              -

                                              drainaquifer

                                              +

                                              drainaquifer

                                              Remove all 'aquifer' tag from the map blocks. Irreversible.

                                              -

                                              deathcause

                                              +

                                              deathcause

                                              Focus a body part ingame, and this script will display the cause of death of the creature.

                                              -

                                              In-game interface tools

                                              +

                                              In-game interface tools

                                              These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

                                              @@ -2563,7 +2594,7 @@ display the word "DFHack" on the screen somewhere while active.

                                              guideline because it arguably just fixes small usability bugs in the game UI.

                                              -

                                              Dwarf Manipulator

                                              +

                                              Dwarf Manipulator

                                              Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

                                              This tool implements a Dwarf Therapist-like interface within the game UI. The @@ -2599,13 +2630,13 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

                                              -

                                              gui/liquids

                                              +

                                              gui/liquids

                                              To use, bind to a key and activate in the 'k' mode.

                                              While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

                                              -

                                              gui/mechanisms

                                              +

                                              gui/mechanisms

                                              To use, bind to a key and activate in the 'q' mode.

                                              Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings.

                                              @@ -2614,7 +2645,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

                                              -

                                              gui/rename

                                              +

                                              gui/rename

                                              Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

                                                @@ -2630,14 +2661,14 @@ It is also possible to rename zones from the 'i' menu.

                                                The building or unit options are automatically assumed when in relevant ui state.

                                              -

                                              gui/room-list

                                              +

                                              gui/room-list

                                              To use, bind to a key and activate in the 'q' mode, either immediately or after opening the assign owner page.

                                              The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

                                              -

                                              gui/choose-weapons

                                              +

                                              gui/choose-weapons

                                              Bind to a key, and activate in the Equip->View/Customize page of the military screen.

                                              Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned @@ -2648,7 +2679,7 @@ and may lead to inappropriate weapons being selected.

                                              -

                                              Behavior Mods

                                              +

                                              Behavior Mods

                                              These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

                                              @@ -2659,20 +2690,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

                                              -

                                              Siege Engine

                                              +

                                              Siege Engine

                                              The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

                                              -

                                              Rationale

                                              +

                                              Rationale

                                              Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

                                              -

                                              Configuration UI

                                              +

                                              Configuration UI

                                              The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

                                              The main mode displays the current target, selected ammo item type, linked stockpiles and @@ -2693,7 +2724,7 @@ menu.

                                              -

                                              Power Meter

                                              +

                                              Power Meter

                                              The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

                                              The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -2702,11 +2733,11 @@ key and activate after selecting Pressure Plate in the build menu.

                                              configuration page, but configures parameters relevant to the modded power meter building.

                                              -

                                              Steam Engine

                                              +

                                              Steam Engine

                                              The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

                                              -

                                              Rationale

                                              +

                                              Rationale

                                              The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -2717,7 +2748,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

                                              -

                                              Construction

                                              +

                                              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.

                                              @@ -2741,7 +2772,7 @@ short axles that can be built later than both of the engines.

                                              -

                                              Operation

                                              +

                                              Operation

                                              In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -2772,7 +2803,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

                                              -

                                              Explosions

                                              +

                                              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 @@ -2781,7 +2812,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

                                              -

                                              Save files

                                              +

                                              Save files

                                              It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -2792,7 +2823,7 @@ being generated.

                                              -

                                              Add Spatter

                                              +

                                              Add Spatter

                                              This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index b5c0f335d..bed837f0b 100644 --- a/Readme.rst +++ b/Readme.rst @@ -88,6 +88,36 @@ Interactive commands like 'liquids' cannot be used as hotkeys. Most of the commands come from plugins. Those reside in 'hack/plugins/'. +Patched binaries +================ + +On linux and OSX, users of patched binaries may have to find the relevant +section in symbols.xml, and add a new line with the checksum of their +executable:: + + + +In order to find the correct value of the hash, look into stderr.log; +DFHack prints an error there if it does not recognize the hash. + +DFHack includes a small stand-alone utility for applying and removing +binary patches from the game executable. Use it from the regular operating +system console: + + * ``binpatch check "Dwarf Fortress.exe" patch.dif`` + + Checks and prints if the patch is currently applied. + + * ``binpatch apply "Dwarf Fortress.exe" patch.dif`` + + Applies the patch, unless it is already applied or in conflict. + + * ``binpatch remove "Dwarf Fortress.exe" patch.dif`` + + Removes the patch, unless it is already removed. + +The patches are expected to be encoded in text format used by IDA. + ============================= Something doesn't work, help! ============================= diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 536f4d34d..a6ce58877 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -249,6 +249,7 @@ ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${ ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) +ADD_EXECUTABLE(binpatch binpatch.cpp) IF(BUILD_EGGY) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) @@ -329,7 +330,7 @@ install(FILES xml/symbols.xml install(FILES ../dfhack.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) -install(TARGETS dfhack-run dfhack-client +install(TARGETS dfhack-run dfhack-client binpatch LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/library/binpatch.cpp b/library/binpatch.cpp new file mode 100644 index 000000000..10188db8c --- /dev/null +++ b/library/binpatch.cpp @@ -0,0 +1,308 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2011 Petr Mrázek + +A thread-safe logging console with a line editor for windows. + +Based on linenoise win32 port, +copyright 2010, Jon Griffiths . +All rights reserved. +Based on linenoise, copyright 2010, Salvatore Sanfilippo . +The original linenoise can be found at: http://github.com/antirez/linenoise + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Redis nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +using std::cout; +using std::cerr; +using std::endl; + +typedef unsigned char patch_byte; + +struct BinaryPatch { + struct Byte { + unsigned offset; + patch_byte old_val, new_val; + }; + enum State { + Conflict = 0, + Unapplied = 1, + Applied = 2, + Partial = 3 + }; + + std::vector entries; + + bool loadDIF(std::string name); + State checkState(const patch_byte *ptr, size_t len); + + void apply(patch_byte *ptr, size_t len, bool newv); +}; + +inline bool is_hex(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); +} + +bool BinaryPatch::loadDIF(std::string name) +{ + entries.clear(); + + std::ifstream infile(name); + if(infile.bad()) + { + cerr << "Cannot open file: " << name << endl; + return false; + } + + std::string s; + while(std::getline(infile, s)) + { + // Parse lines that begin with "[0-9a-f]+:" + size_t idx = s.find(':'); + if (idx == std::string::npos || idx == 0 || idx > 8) + continue; + + bool ok = true; + for (size_t i = 0; i < idx; i++) + if (!is_hex(s[i])) + ok = false; + if (!ok) + continue; + + unsigned off, oval, nval; + int nchar = 0; + int cnt = sscanf(s.c_str(), "%x: %x %x%n", &off, &oval, &nval, &nchar); + + if (cnt < 3) + { + cerr << "Could not parse: " << s << endl; + return false; + } + + for (size_t i = nchar; i < s.size(); i++) + { + if (!isspace(s[i])) + { + cerr << "Garbage at end of line: " << s << endl; + return false; + } + } + + if (oval >= 256 || nval >= 256) + { + cerr << "Invalid byte values: " << s << endl; + return false; + } + + Byte bv = { off, patch_byte(oval), patch_byte(nval) }; + entries.push_back(bv); + } + + if (entries.empty()) + { + cerr << "No lines recognized." << endl; + return false; + } + + return true; +} + +BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len) +{ + int state = 0; + + for (size_t i = 0; i < entries.size(); i++) + { + Byte &bv = entries[i]; + + if (bv.offset >= len) + { + cerr << "Offset out of range: 0x" << std::hex << bv.offset << std::dec << endl; + return Conflict; + } + + patch_byte cv = ptr[bv.offset]; + if (bv.old_val == cv) + state |= Unapplied; + else if (bv.new_val == cv) + state |= Applied; + else + { + cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val + << ", but currently " << cv << std::dec << endl; + return Conflict; + } + } + + return State(state); +} + +void BinaryPatch::apply(patch_byte *ptr, size_t len, bool newv) +{ + for (size_t i = 0; i < entries.size(); i++) + { + Byte &bv = entries[i]; + assert (bv.offset < len); + + ptr[bv.offset] = (newv ? bv.new_val : bv.old_val); + } +} + +bool load_file(std::vector *pvec, std::string fname) +{ + FILE *f = fopen(fname.c_str(), "rb"); + if (!f) + { + cerr << "Cannot open file: " << fname << endl; + return false; + } + + fseek(f, 0, SEEK_END); + pvec->resize(ftell(f)); + fseek(f, 0, SEEK_SET); + size_t cnt = fread(pvec->data(), 1, pvec->size(), f); + fclose(f); + + return cnt == pvec->size(); +} + +bool save_file(const std::vector &pvec, std::string fname) +{ + FILE *f = fopen(fname.c_str(), "wb"); + if (!f) + { + cerr << "Cannot open file: " << fname << endl; + return false; + } + + size_t cnt = fwrite(pvec.data(), 1, pvec.size(), f); + fclose(f); + + return cnt == pvec.size(); +} + +int main (int argc, char *argv[]) +{ + if (argc <= 3) + { + cerr << "Usage: binpatch check|apply|remove " << endl; + return 2; + } + + std::string cmd = argv[1]; + + if (cmd != "check" && cmd != "apply" && cmd != "remove") + { + cerr << "Invalid command: " << cmd << endl; + return 2; + } + + std::string exe_file = argv[2]; + std::vector bindata; + if (!load_file(&bindata, exe_file)) + return 2; + + BinaryPatch patch; + if (!patch.loadDIF(argv[3])) + return 2; + + BinaryPatch::State state = patch.checkState(bindata.data(), bindata.size()); + if (state == BinaryPatch::Conflict) + return 1; + + if (cmd == "check") + { + switch (state) + { + case BinaryPatch::Unapplied: + cout << "Currently not applied." << endl; + break; + case BinaryPatch::Applied: + cout << "Currently applied." << endl; + break; + case BinaryPatch::Partial: + cout << "Currently partially applied." << endl; + break; + default: + break; + } + + return 0; + } + else if (cmd == "apply") + { + if (state == BinaryPatch::Applied) + { + cout << "Already applied." << endl; + return 0; + } + + patch.apply(bindata.data(), bindata.size(), true); + } + else if (cmd == "remove") + { + if (state == BinaryPatch::Unapplied) + { + cout << "Already removed." << endl; + return 0; + } + + patch.apply(bindata.data(), bindata.size(), false); + } + + std::string bak_file = exe_file + ".bak"; + remove(bak_file.c_str()); + + if (rename(exe_file.c_str(), bak_file.c_str()) != 0) + { + cerr << "Could not create backup." << endl; + return 1; + } + + if (!save_file(bindata, exe_file)) + return 1; + + cout << "Patched " << patch.entries.size() << " bytes." << endl; + return 0; +} From 28f0fed0aa1d339d60228969b0e6b174da27e42c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 16:22:35 +0400 Subject: [PATCH 18/21] Redo the way binpatch backs up, so as not to lose the executable perms. --- library/binpatch.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 10188db8c..88ef2f004 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -291,10 +291,7 @@ int main (int argc, char *argv[]) patch.apply(bindata.data(), bindata.size(), false); } - std::string bak_file = exe_file + ".bak"; - remove(bak_file.c_str()); - - if (rename(exe_file.c_str(), bak_file.c_str()) != 0) + if (!save_file(bindata, exe_file + ".bak")) { cerr << "Could not create backup." << endl; return 1; From 7224c8746aa44249d1b1c12088e6d4aeefa4ac22 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 8 Oct 2012 16:47:52 +0400 Subject: [PATCH 19/21] Print the new md5 hash after modification in binpatch. --- depends/md5/md5wrapper.cpp | 9 +++------ depends/md5/md5wrapper.h | 6 +++++- library/CMakeLists.txt | 2 ++ library/binpatch.cpp | 11 ++++++++++- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/depends/md5/md5wrapper.cpp b/depends/md5/md5wrapper.cpp index e12b65780..d9f857c5d 100644 --- a/depends/md5/md5wrapper.cpp +++ b/depends/md5/md5wrapper.cpp @@ -36,16 +36,14 @@ * internal hash function, calling * the basic methods from md5.h */ -std::string md5wrapper::hashit(std::string text) +std::string md5wrapper::hashit(unsigned char *data, size_t length) { MD5Context ctx; //init md5 MD5Init(&ctx); //update with our string - MD5Update(&ctx, - (unsigned char*)text.c_str(), - text.length()); + MD5Update(&ctx, data, length); //create the hash unsigned char buff[16] = ""; @@ -95,10 +93,9 @@ md5wrapper::~md5wrapper() */ std::string md5wrapper::getHashFromString(std::string text) { - return this->hashit(text); + return this->hashit((unsigned char*)text.data(), text.length()); } - /* * creates a MD5 hash from * a file specified in "filename" and diff --git a/depends/md5/md5wrapper.h b/depends/md5/md5wrapper.h index 1a41192a1..0b534b61d 100644 --- a/depends/md5/md5wrapper.h +++ b/depends/md5/md5wrapper.h @@ -31,7 +31,7 @@ class md5wrapper * internal hash function, calling * the basic methods from md5.h */ - std::string hashit(std::string text); + std::string hashit(unsigned char *data, size_t length); /* * converts the numeric giets to @@ -52,6 +52,10 @@ class md5wrapper */ std::string getHashFromString(std::string text); + std::string getHashFromBytes(const unsigned char *data, size_t size) { + return hashit(const_cast(data),size); + } + /* * creates a MD5 hash from * a file specified in "filename" and diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index a6ce58877..6f33d5c8a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -249,7 +249,9 @@ ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${ ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) + ADD_EXECUTABLE(binpatch binpatch.cpp) +TARGET_LINK_LIBRARIES(binpatch dfhack-md5) IF(BUILD_EGGY) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 88ef2f004..815ac5b92 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -53,6 +53,8 @@ POSSIBILITY OF SUCH DAMAGE. #include +#include + using std::cout; using std::cerr; using std::endl; @@ -222,6 +224,12 @@ bool save_file(const std::vector &pvec, std::string fname) return cnt == pvec.size(); } +std::string compute_hash(const std::vector &pvec) +{ + md5wrapper md5; + return md5.getHashFromBytes(pvec.data(), pvec.size()); +} + int main (int argc, char *argv[]) { if (argc <= 3) @@ -300,6 +308,7 @@ int main (int argc, char *argv[]) if (!save_file(bindata, exe_file)) return 1; - cout << "Patched " << patch.entries.size() << " bytes." << endl; + cout << "Patched " << patch.entries.size() + << " bytes, new hash: " << compute_hash(bindata) << endl; return 0; } From 7c969f774f5ea86aaf9251c02e187014c73986c6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 10 Oct 2012 18:22:01 +0400 Subject: [PATCH 20/21] Split the liquipowder fix-dimensions hook into separate liquid and powder. The item_liquipowder vtable is completely optimized out by MSVC. --- plugins/tweak.cpp | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index d54c4a5ec..8488ba3e8 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -34,7 +34,8 @@ #include "df/ui_build_selector.h" #include "df/building_trapst.h" #include "df/item_actual.h" -#include "df/item_liquipowder.h" +#include "df/item_liquid_miscst.h" +#include "df/item_powder_miscst.h" #include "df/item_barst.h" #include "df/item_threadst.h" #include "df/item_clothst.h" @@ -402,8 +403,8 @@ static void correct_dimension(df::item_actual *self, int32_t &delta, int32_t dim if (copy) copy->categorize(true); } -struct dimension_lqp_hook : df::item_liquipowder { - typedef df::item_liquipowder interpose_base; +struct dimension_liquid_hook : df::item_liquid_miscst { + typedef df::item_liquid_miscst interpose_base; DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) { @@ -412,7 +413,19 @@ struct dimension_lqp_hook : df::item_liquipowder { } }; -IMPLEMENT_VMETHOD_INTERPOSE(dimension_lqp_hook, subtractDimension); +IMPLEMENT_VMETHOD_INTERPOSE(dimension_liquid_hook, subtractDimension); + +struct dimension_powder_hook : df::item_powder_miscst { + typedef df::item_powder_miscst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_powder_hook, subtractDimension); struct dimension_bar_hook : df::item_barst { typedef df::item_barst interpose_base; @@ -795,7 +808,8 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "fix-dimensions") { - enable_hook(out, INTERPOSE_HOOK(dimension_lqp_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_liquid_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_powder_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); From 41de37a5c116d14175de54e3dfa9367454967868 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 10 Oct 2012 18:29:59 +0400 Subject: [PATCH 21/21] Add a special workshop for add spatter reactions into example raws. --- plugins/raw/building_spatter.txt | 22 ++++++++++++++++++++++ plugins/raw/reaction_spatter.txt | 30 +++++++++++++++--------------- 2 files changed, 37 insertions(+), 15 deletions(-) create mode 100644 plugins/raw/building_spatter.txt diff --git a/plugins/raw/building_spatter.txt b/plugins/raw/building_spatter.txt new file mode 100644 index 000000000..7bde8297b --- /dev/null +++ b/plugins/raw/building_spatter.txt @@ -0,0 +1,22 @@ +building_spatter + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:GREASING_STATION] + [NAME:Greasing Station] + [NAME_COLOR:2:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BUILD_LABOR:DYER] + [BUILD_KEY:CUSTOM_ALT_G] + [BLOCK:1:0] + [TILE:0:1:150] + [COLOR:0:1:0:0:1] + [TILE:1:1:150] + [COLOR:1:1:MAT] + [TILE:2:1:8] + [COLOR:2:1:MAT] + [TILE:3:1:8] + [COLOR:3:1:7:5:0] + [BUILD_ITEM:1:BUCKET:NONE:NONE:NONE][CAN_USE_ARTIFACT] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE][BUILDMAT] diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt index 81f9a2b62..95b9f1d33 100644 --- a/plugins/raw/reaction_spatter.txt +++ b/plugins/raw/reaction_spatter.txt @@ -7,7 +7,7 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_OBJECT_LIQUID] [NAME:coat object with liquid] [ADVENTURE_MODE_ENABLED] - [SKILL:WAX_WORKING] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] [MIN_DIMENSION:150] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] @@ -28,10 +28,10 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_EXTRACT] [NAME:coat weapon with extract] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] - [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] - [MIN_DIMENSION:150] + [BUILDING:GREASING_STATION:CUSTOM_W] + [SKILL:DYER] + [REAGENT:extract:100:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:100] [REACTION_CLASS:CREATURE_EXTRACT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] @@ -51,8 +51,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_EXTRACT] [NAME:coat ammo with extract] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:CUSTOM_A] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] @@ -75,8 +75,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_GCS] [NAME:coat weapon with GCS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [MIN_DIMENSION:150] [REACTION_CLASS:CREATURE_EXTRACT] @@ -95,8 +95,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_GCS] [NAME:coat ammo with GCS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] @@ -115,8 +115,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_WEAPON_GDS] [NAME:coat weapon with GDS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [MIN_DIMENSION:150] [REACTION_CLASS:CREATURE_EXTRACT] @@ -135,8 +135,8 @@ Reaction name must start with 'SPATTER_ADD_': [REACTION:SPATTER_ADD_AMMO_GDS] [NAME:coat ammo with GDS venom] - [BUILDING:CRAFTSMAN:NONE] - [SKILL:WAX_WORKING] + [BUILDING:GREASING_STATION:NONE] + [SKILL:DYER] [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT]