From 27bdc9f2df02ccd566eddc5fe1ffa2a09d7e6143 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 23 Aug 2012 21:38:38 +0300 Subject: [PATCH 001/472] Start gutting dfusion. --- plugins/Dfusion/readme.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Dfusion/readme.txt b/plugins/Dfusion/readme.txt index c40ac3707..df3b85b1f 100644 --- a/plugins/Dfusion/readme.txt +++ b/plugins/Dfusion/readme.txt @@ -10,3 +10,5 @@ Similar to dfusion but not interactive. To be used with hotkeys (later will have Also dfuse/dfusion runs an init script located at 'save directory/dfusion/init.lua'. And 'initcustom.lua' if it exists More info http://dwarffortresswiki.org/index.php/Utility:DFusion + +a \ No newline at end of file From 90021b4e5e8451e1505f1344b4862a05cbe9711a Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 30 Aug 2012 20:41:10 +0300 Subject: [PATCH 002/472] simple_embark/plugin.lua sanitized --- plugins/Dfusion/luafiles/init.lua | 11 +++-------- .../Dfusion/luafiles/simple_embark/plugin.lua | 16 ++++++++++++---- plugins/Dfusion/readme.txt | 2 -- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua index 19f63d603..c8eebcd23 100644 --- a/plugins/Dfusion/luafiles/init.lua +++ b/plugins/Dfusion/luafiles/init.lua @@ -1,11 +1,7 @@ -function err(msg) --make local maybe... - print(msg) - print(debug.traceback()) -end function dofile(filename) --safer dofile, with traceback (very usefull) f,perr=loadfile(filename) if f~=nil then - return xpcall(f,err) + return safecall(f) else print(perr) end @@ -13,7 +9,7 @@ end function dofile_silent(filename) --safer dofile, with traceback, no file not found error f,perr=loadfile(filename) if f~=nil then - return xpcall(f,err) + return safecall(f) else if(string.sub(perr,1,11)~="cannot open") then --ugly hack print(perr) @@ -26,7 +22,6 @@ function loadall(t1) --loads all non interactive plugin parts, so that later the end end function mainmenu(t1) - while true do print("No. Name Desc") for k,v in pairs(t1) do @@ -58,7 +53,7 @@ dofile("dfusion/common.lua") dofile("dfusion/utils.lua") dofile("dfusion/offsets_misc.lua") dofile("dfusion/editor.lua") ---dofile("dfusion/xml_struct.lua") + unlockDF() plugins={} table.insert(plugins,{"simple_embark","A simple embark dwarf count editor"}) diff --git a/plugins/Dfusion/luafiles/simple_embark/plugin.lua b/plugins/Dfusion/luafiles/simple_embark/plugin.lua index c64aa7e68..f4f594e8d 100644 --- a/plugins/Dfusion/luafiles/simple_embark/plugin.lua +++ b/plugins/Dfusion/luafiles/simple_embark/plugin.lua @@ -1,10 +1,18 @@ function simple_embark(num) -stoff=VersionInfo.getAddress('start_dwarf_count') -print("Starting dwarves found:"..engine.peekd(stoff)) -engine.poked(stoff,num) + local stoff=dfhack.internal.getAddress('start_dwarf_count') + print("Starting dwarves found:"..df.reinterpret_cast('int32_t', stoff).value) + local tmp_val=df.new('int32_t') + local size,pos=tmp_val:sizeof() + tmp_val.value=num + local ret=dfhack.internal.patchMemory(stoff,tmp_val,size) + if ret then + print("Success!") + else + qerror("Failed to patch in number") + end end if not(FILE) then -print("Type in new ammount:") +print("Type in new ammount (more than 6, less than 15000):") repeat ans=tonumber(io.read()) if ans==nil or not(ans<=15000 and ans>0) then diff --git a/plugins/Dfusion/readme.txt b/plugins/Dfusion/readme.txt index df3b85b1f..c40ac3707 100644 --- a/plugins/Dfusion/readme.txt +++ b/plugins/Dfusion/readme.txt @@ -10,5 +10,3 @@ Similar to dfusion but not interactive. To be used with hotkeys (later will have Also dfuse/dfusion runs an init script located at 'save directory/dfusion/init.lua'. And 'initcustom.lua' if it exists More info http://dwarffortresswiki.org/index.php/Utility:DFusion - -a \ No newline at end of file From f8744e2ec259cdb72c77e6095cf2e7e329ba081d Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 31 Aug 2012 23:46:33 +0300 Subject: [PATCH 003/472] Experimental stuff editor (can and will crash DF ) --- scripts/gui/gm-Items.lua | 181 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 scripts/gui/gm-Items.lua diff --git a/scripts/gui/gm-Items.lua b/scripts/gui/gm-Items.lua new file mode 100644 index 000000000..82a1da154 --- /dev/null +++ b/scripts/gui/gm-Items.lua @@ -0,0 +1,181 @@ +-- Interface powered item editor. +-- TODO use this: MechanismList = defclass(MechanismList, guidm.MenuOverlay) +local gui = require 'gui' + +if dfhack.gui.getCurFocus() ~= 'item' then + qerror("This script requires the item view.") +end + +TextInputDialog = defclass(TextInputDialog, gui.FramedScreen) + +function TextInputDialog:init(prompt) + self.frame_style=GREY_LINE_FRAME + self.frame_title=prompt + self.input="" + return self +end +function TextInputDialog:onRenderBody(dc) + dc:seek(1,1):string(self.input, COLOR_WHITE):newline() +end + +local MODE_BROWSE=0 +local MODE_EDIT=1 +local item_screen={ + frame_style = gui.GREY_LINE_FRAME, + frame_title = "GameMaster's editor", + stack={}, + item_count=0, + mode=MODE_BROWSE, + + keys={}, + insertNew=function(self) + --[=[local trg=self:currentTarget() -- not sure if possible... + if trg.target and trg.target._kind and trg.target._kind=="container" then + local thing=df.new('general_ref_contained_itemst') + trg.target:insert('#',trg.keys[trg.selected]) + end]=] + end, + deleteSelected=function(self) + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="container" then + trg.target:erase(trg.keys[trg.selected]) + end + end, + currentTarget=function(self) + return self.stack[#self.stack] + end, + changeSelected = function (self,delta) + local trg=self:currentTarget() + if trg.item_count <= 1 then return end + trg.selected = 1 + (trg.selected + delta - 1) % trg.item_count + end, + editSelected = function(self) + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="bitfield" then + trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] + else + --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") + local trg_type=type(trg.target[trg.keys[trg.selected]]) + if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected + self.mode=MODE_EDIT + self.input=tostring(trg.target[trg.keys[trg.selected]]) + elseif trg_type=='userdata' then + self:pushTarget(trg.target[trg.keys[trg.selected]]) + --local screen = mkinstance(gui.FramedScreen,item_screen):init(trg.target[trg.keys[trg.selected]]) -- does not work + --screen:show() + else + print("Unknow type:"..trg_type) + print("Subtype:"..tostring(trg.target[trg.keys[trg.selected]]._kind)) + end + end + end, + cancelEdit = function(self) + self.mode=MODE_BROWSE + self.input="" + end, + commitEdit = function(self) + local trg=self:currentTarget() + self.mode=MODE_BROWSE + if type(trg.target[trg.keys[trg.selected]])=='number' then + trg.target[trg.keys[trg.selected]]=tonumber(self.input) + elseif type(trg.target[trg.keys[trg.selected]])=='string' then + trg.target[trg.keys[trg.selected]]=self.input + end + end, + onRenderBody = function(self, dc) + local trg=self:currentTarget() + dc:seek(2,1):string(tostring(trg.target), COLOR_RED) + local offset=2 + local page_offset=0 + local current_item=1 + local t_col + if math.floor(trg.selected / (self.frame_height-offset-2)) >0 then + page_offset=math.floor(trg.selected / (self.frame_height-offset-2))*(self.frame_height-offset-2)-1 + end + for k,v in pairs(trg.target) do + + if current_item==trg.selected then + t_col=COLOR_LIGHTGREEN + else + t_col=COLOR_GRAY + end + + if current_item-page_offset > 0 then + local y_pos=current_item-page_offset+offset + dc:seek(2,y_pos):string(tostring(k),t_col) + + if self.mode==MODE_EDIT and current_item==trg.selected then + dc:seek(20,y_pos):string(self.input..'_',COLOR_GREEN) + else + dc:seek(20,y_pos):string(tostring(v),t_col) + end + end + current_item=current_item+1 + end + end, + + onInput = function(self,keys) + if self.mode==MODE_BROWSE then + if keys.LEAVESCREEN then + self:popTarget() + elseif keys.CURSOR_UP then + self:changeSelected(-1) + elseif keys.CURSOR_DOWN then + self:changeSelected(1) + elseif keys.CURSOR_UP_FAST then + self:changeSelected(-10) + elseif keys.CURSOR_DOWN_FAST then + self:changeSelected(10) + elseif keys.SELECT then + self:editSelected() + elseif keys.CUSTOM_ALT_E then + --self:specialEditor() + local screen = mkinstance(TextInputDialog):init("Input new coordinates") + screen:show() + elseif keys.CUSTOM_ALT_I then --insert + self:insertNew() + elseif keys.CUSTOM_ALT_D then --delete + self:deleteSelected() + end + elseif self.mode==MODE_EDIT then + if keys.LEAVESCREEN then + self:cancelEdit() + elseif keys.SELECT then + self:commitEdit() + elseif keys._STRING then + if keys._STRING==0 then + self.input=string.sub(self.input,1,-2) + else + self.input=self.input.. string.char(keys._STRING) + end + end + end + end, + pushTarget=function(self,target_to_push) + local new_tbl={} + new_tbl.target=target_to_push + new_tbl.keys={} + new_tbl.selected=1 + for k,v in pairs(target_to_push) do + table.insert(new_tbl.keys,k) + end + new_tbl.item_count=#new_tbl.keys + table.insert(self.stack,new_tbl) + end, + popTarget=function(self) + table.remove(self.stack) --removes last element + if #self.stack==0 then + self:dismiss() + end + end, + init = function(self,item_to_edit) + self:pushTarget(item_to_edit) + self.frame_width,self.frame_height=dfhack.screen.getWindowSize() + return self + end + } + + + +local screen = mkinstance(gui.FramedScreen,item_screen):init(dfhack.gui.getCurViewscreen().item) +screen:show() \ No newline at end of file From af155db3beca208cf1f842246db342db09bdc5cc Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 01:22:51 +0300 Subject: [PATCH 004/472] Added whole bunch of editable things (units, jobs, flows) --- scripts/gui/gm-Items.lua | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/scripts/gui/gm-Items.lua b/scripts/gui/gm-Items.lua index 82a1da154..839d3158a 100644 --- a/scripts/gui/gm-Items.lua +++ b/scripts/gui/gm-Items.lua @@ -2,8 +2,27 @@ -- TODO use this: MechanismList = defclass(MechanismList, guidm.MenuOverlay) local gui = require 'gui' -if dfhack.gui.getCurFocus() ~= 'item' then - qerror("This script requires the item view.") +local my_trg +if dfhack.gui.getCurFocus() == 'item' then + my_trg=dfhack.gui.getCurViewscreen().item +elseif dfhack.gui.getCurFocus() == 'joblist' then + local t_screen=dfhack.gui.getCurViewscreen() + my_trg=t_screen.jobs[t_screen.cursor_pos] +elseif dfhack.gui.getCurFocus() == 'createquota' then + local t_screen=dfhack.gui.getCurViewscreen() + my_trg=t_screen.orders[t_screen.sel_idx] +elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then + local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor] + my_trg=t_look.flow + +elseif dfhack.gui.getSelectedUnit(true) then + my_trg=dfhack.gui.getSelectedUnit(true) +elseif dfhack.gui.getSelectedItem(true) then + my_trg=dfhack.gui.getSelectedItem(true) +elseif dfhack.gui.getSelectedJob(true) then + my_trg=dfhack.gui.getSelectedJob(true) +else + qerror("No valid target found") end TextInputDialog = defclass(TextInputDialog, gui.FramedScreen) @@ -177,5 +196,5 @@ local item_screen={ -local screen = mkinstance(gui.FramedScreen,item_screen):init(dfhack.gui.getCurViewscreen().item) +local screen = mkinstance(gui.FramedScreen,item_screen):init(my_trg) screen:show() \ No newline at end of file From c9c587af9aeae439313f64b23d40a862672726e2 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 01:27:01 +0300 Subject: [PATCH 005/472] small fix for boolean values --- scripts/gui/gm-Items.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/gui/gm-Items.lua b/scripts/gui/gm-Items.lua index 839d3158a..34e744ee2 100644 --- a/scripts/gui/gm-Items.lua +++ b/scripts/gui/gm-Items.lua @@ -78,6 +78,8 @@ local item_screen={ if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected self.mode=MODE_EDIT self.input=tostring(trg.target[trg.keys[trg.selected]]) + elseif trg_type=='boolean' then + trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] elseif trg_type=='userdata' then self:pushTarget(trg.target[trg.keys[trg.selected]]) --local screen = mkinstance(gui.FramedScreen,item_screen):init(trg.target[trg.keys[trg.selected]]) -- does not work From d784d4bc40e2b0b0d91513a22a3c22f5e042d59c Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 10:05:31 +0300 Subject: [PATCH 006/472] Static code segment search for memscan.lua --- library/lua/memscan.lua | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 970f821c2..521f7c7e8 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -195,6 +195,36 @@ function MemoryArea:delete() for k,v in pairs(self) do self[k] = nil end end +-- Static code segment search +local function find_code_segment() + local code_start, code_end + + for i,mem in ipairs(dfhack.internal.getMemRanges()) do + if code_end then + if mem.start_addr == code_end and mem.read and not mem.write then + code_end = mem.end_addr + else + break + end + elseif mem.read and not mem.write + and (string.match(mem.name,'/dwarfort%.exe$') + or string.match(mem.name,'/Dwarf_Fortress$') + or string.match(mem.name,'Dwarf Fortress%.exe')) + then + code_start = mem.start_addr + code_end = mem.end_addr + end + end + + return code_start,code_end +end + +function get_code_segment() + local s, e = find_code_segment() + if s and e then + return ms.MemoryArea.new(s, e) + end +end -- Static data segment search local function find_data_segment() From 7cabf1b8435779c74ed498745920b8edbbacdd15 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 10:13:08 +0300 Subject: [PATCH 007/472] Small bug fix --- library/lua/memscan.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 521f7c7e8..af2fe7435 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -222,7 +222,7 @@ end function get_code_segment() local s, e = find_code_segment() if s and e then - return ms.MemoryArea.new(s, e) + return MemoryArea.new(s, e) end end -- Static data segment search From 532839a4d5fd37fdbd52edd9cf5f7661f8232bdb Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 10:54:45 +0300 Subject: [PATCH 008/472] Embark anywhere ported --- library/lua/memscan.lua | 13 +++++++++++++ plugins/Dfusion/luafiles/simple_embark/plugin.lua | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index af2fe7435..85ed5624a 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -225,6 +225,19 @@ function get_code_segment() return MemoryArea.new(s, e) end end +function get_code_segments() + local ret={} + for i,mem in ipairs(dfhack.internal.getMemRanges()) do + if mem.read and not mem.write + and (string.match(mem.name,'/dwarfort%.exe$') + or string.match(mem.name,'/Dwarf_Fortress$') + or string.match(mem.name,'Dwarf Fortress%.exe')) + then + table.insert(ret,MemoryArea.new(mem.start_addr,mem.end_addr)) + end + end + return ret +end -- Static data segment search local function find_data_segment() diff --git a/plugins/Dfusion/luafiles/simple_embark/plugin.lua b/plugins/Dfusion/luafiles/simple_embark/plugin.lua index f4f594e8d..abc3530c2 100644 --- a/plugins/Dfusion/luafiles/simple_embark/plugin.lua +++ b/plugins/Dfusion/luafiles/simple_embark/plugin.lua @@ -12,7 +12,7 @@ function simple_embark(num) end end if not(FILE) then -print("Type in new ammount (more than 6, less than 15000):") +print("Type in new amount (more than 6, less than 15000):") repeat ans=tonumber(io.read()) if ans==nil or not(ans<=15000 and ans>0) then From 5b60dc296a1e039c0fb0084f63f07a3825ab0ad8 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 21:53:52 +0300 Subject: [PATCH 009/472] Renamed editor and added example keybinding --- dfhack.init-example | 3 +++ scripts/gui/{gm-Items.lua => gm-editor.lua} | 0 2 files changed, 3 insertions(+) rename scripts/gui/{gm-Items.lua => gm-editor.lua} (100%) diff --git a/dfhack.init-example b/dfhack.init-example index d3a28b9b0..a9de146c2 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -52,6 +52,9 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work # interface for the liquids plugin keybinding add Alt-L@dwarfmode/LookAround gui/liquids +# interface for universal game master's editor +keybinding add Alt-Shift-E gui/gm-editor + ################### # UI logic tweaks # ################### diff --git a/scripts/gui/gm-Items.lua b/scripts/gui/gm-editor.lua similarity index 100% rename from scripts/gui/gm-Items.lua rename to scripts/gui/gm-editor.lua From 2574bb1e3d277627f1855ec78e59c4a4ace5e7dd Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Sep 2012 21:58:01 +0300 Subject: [PATCH 010/472] embark anywhere upgrade. --- plugins/Dfusion/luafiles/tools/init.lua | 32 +++++++++++++++++++------ 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index e3a4607cf..b9978d020 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -1,3 +1,4 @@ +local ms=require "memscan" tools={} tools.menu=MakeMenu() function tools.setrace(name) @@ -60,14 +61,31 @@ function tools.GiveSentience(names) end end tools.menu:add("Give Sentience",tools.GiveSentience) -function tools.embark() - off=offsets.find(0,0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04) - if off~=0 then - engine.pokeb(off+5,0x90) - engine.pokeb(off+6,0x90) - print("Found and patched") +function embark() --windows only? + local seg=ms.get_code_segments() + printall(seg) + local idx,off + for k,v in ipairs(seg) do + idx,off=v.uint8_t:find_one{0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04} + if idx then + break + end + end + + if idx then + local tmp_val=df.new('uint8_t',2) + tmp_val[0]=0x90 + tmp_val[1]=0x90 + local size,pos=tmp_val:sizeof() + local ret=dfhack.internal.patchMemory(off+5,pos,size*2) + if ret then + print("Found and patched:",off+5) + else + print("Patching failed at:",off+5) + end + tmp_val:delete() else - print("not found") + qerror("Offset for embark patch not found!") end end tools.menu:add("Embark anywhere",tools.embark) From 6fc10fc2683e82dc6c0552507f1700214019a514 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 5 Sep 2012 21:52:54 +0300 Subject: [PATCH 011/472] Fixed embark anywhere to use more sane code segment search --- library/lua/memscan.lua | 41 ++++++------------------- plugins/Dfusion/luafiles/tools/init.lua | 12 ++------ 2 files changed, 12 insertions(+), 41 deletions(-) diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 85ed5624a..ba3efd708 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -196,48 +196,25 @@ function MemoryArea:delete() end -- Static code segment search -local function find_code_segment() - local code_start, code_end + +function get_code_segment() + local cstart, cend for i,mem in ipairs(dfhack.internal.getMemRanges()) do - if code_end then - if mem.start_addr == code_end and mem.read and not mem.write then - code_end = mem.end_addr - else - break - end - elseif mem.read and not mem.write + if mem.read and mem.execute and (string.match(mem.name,'/dwarfort%.exe$') or string.match(mem.name,'/Dwarf_Fortress$') or string.match(mem.name,'Dwarf Fortress%.exe')) then - code_start = mem.start_addr - code_end = mem.end_addr + cstart = mem.start_addr + cend = mem.end_addr end end - - return code_start,code_end -end - -function get_code_segment() - local s, e = find_code_segment() - if s and e then - return MemoryArea.new(s, e) + if cstart and cend then + return MemoryArea.new(cstart, cend) end end -function get_code_segments() - local ret={} - for i,mem in ipairs(dfhack.internal.getMemRanges()) do - if mem.read and not mem.write - and (string.match(mem.name,'/dwarfort%.exe$') - or string.match(mem.name,'/Dwarf_Fortress$') - or string.match(mem.name,'Dwarf Fortress%.exe')) - then - table.insert(ret,MemoryArea.new(mem.start_addr,mem.end_addr)) - end - end - return ret -end + -- Static data segment search local function find_data_segment() diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index b9978d020..7052715bc 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -61,17 +61,11 @@ function tools.GiveSentience(names) end end tools.menu:add("Give Sentience",tools.GiveSentience) -function embark() --windows only? - local seg=ms.get_code_segments() - printall(seg) +function tools.embark() --windows only? + local seg=ms.get_code_segment() local idx,off - for k,v in ipairs(seg) do - idx,off=v.uint8_t:find_one{0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04} - if idx then - break - end - end + idx,off=seg.uint8_t:find_one{0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04} if idx then local tmp_val=df.new('uint8_t',2) tmp_val[0]=0x90 From 85fc3384dd82f959be5fa0a8bedca995f674c67e Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 7 Sep 2012 17:25:39 +0300 Subject: [PATCH 012/472] Little cleanup and update to gm-editor --- .../luafiles/patterns/supplementary.xml | 7 --- .../luafiles/patterns/xml_angavrilov.lua | 55 ------------------- scripts/gui/gm-editor.lua | 24 ++++++-- 3 files changed, 19 insertions(+), 67 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/patterns/supplementary.xml delete mode 100644 plugins/Dfusion/luafiles/patterns/xml_angavrilov.lua diff --git a/plugins/Dfusion/luafiles/patterns/supplementary.xml b/plugins/Dfusion/luafiles/patterns/supplementary.xml deleted file mode 100644 index e341a1368..000000000 --- a/plugins/Dfusion/luafiles/patterns/supplementary.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/patterns/xml_angavrilov.lua b/plugins/Dfusion/luafiles/patterns/xml_angavrilov.lua deleted file mode 100644 index dbd3ad692..000000000 --- a/plugins/Dfusion/luafiles/patterns/xml_angavrilov.lua +++ /dev/null @@ -1,55 +0,0 @@ - -function parseargs(s) - local arg = {} - string.gsub(s, "([%w%-]+)=([\"'])(.-)%2", function (w, _, a) - arg[w] = a - end) - return arg -end - -function collect(s) - local stack = {} - local top = {} - table.insert(stack, top) - local ni,c,label,xarg, empty - local i, j = 1, 1 - while true do - ni,j,c,label,xarg, empty = string.find(s, "<(%/?)([%w:%-]+)(.-)(%/?)>", i) - if not ni then break end - local text = string.sub(s, i, ni-1) - if not string.find(text, "^%s*$") then - table.insert(top, text) - end - if empty == "/" then -- empty element tag - table.insert(top, {label=label, xarg=parseargs(xarg), empty=1}) - elseif c == "" then -- start tag - top = {label=label, xarg=parseargs(xarg)} - table.insert(stack, top) -- new level - else -- end tag - local toclose = table.remove(stack) -- remove top - top = stack[#stack] - if #stack < 1 then - error("nothing to close with "..label) - end - if toclose.label ~= label then - error("trying to close "..toclose.label.." with "..label) - end - table.insert(top, toclose) - end - i = j+1 - end - local text = string.sub(s, i) - if not string.find(text, "^%s*$") then - table.insert(stack[#stack], text) - end - if #stack > 1 then - error("unclosed "..stack[#stack].label) - end - return stack[1] -end - -function parseXmlFile(path) - local f, e = io.open(path, "r") - local xml = f:read("*a") - return collect(xml) -end \ No newline at end of file diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 34e744ee2..d95cb652b 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -1,6 +1,7 @@ -- Interface powered item editor. -- TODO use this: MechanismList = defclass(MechanismList, guidm.MenuOverlay) local gui = require 'gui' +local dialog = require 'gui.dialogs' local my_trg if dfhack.gui.getCurFocus() == 'item' then @@ -47,12 +48,25 @@ local item_screen={ mode=MODE_BROWSE, keys={}, - insertNew=function(self) - --[=[local trg=self:currentTarget() -- not sure if possible... + + insertNew=function(self,typename) + local tp=typename + if typename== nil then + dialog.showInputPrompt("Class type","Input class type\n:",COLOR_WHITE,"",dfhack.curry(self.insertNew,self)) + return + end + local ntype=df[tp] + if ntype== nil then + dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) + return + end + + local trg=self:currentTarget() if trg.target and trg.target._kind and trg.target._kind=="container" then - local thing=df.new('general_ref_contained_itemst') - trg.target:insert('#',trg.keys[trg.selected]) - end]=] + local thing=ntype:new() + dfhack.call_with_finalizer(1,false,df.delete,thing,trg.target.insert,trg.target,'#',thing) + + end end, deleteSelected=function(self) local trg=self:currentTarget() From 4f9732bfdae5e09ab0f7d3f2825e77e4c76c2dab Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 15 Sep 2012 15:44:15 +0300 Subject: [PATCH 013/472] Useless files removed, small bugfix --- plugins/Dfusion/luafiles/adv_tools/init.lua | 6 +- plugins/Dfusion/luafiles/buildingpatterns.lua | 24 - plugins/Dfusion/luafiles/editor.lua | 150 ---- plugins/Dfusion/luafiles/itempatterns.lua | 62 -- plugins/Dfusion/luafiles/patterns.lua | 248 ------ plugins/Dfusion/luafiles/patterns2.lua | 29 - plugins/Dfusion/luafiles/xml_struct.lua | 151 ---- plugins/Dfusion/luafiles/xml_types.lua | 734 ------------------ .../Dfusion/luafiles/xml_types_windows.lua | 159 ---- 9 files changed, 3 insertions(+), 1560 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/buildingpatterns.lua delete mode 100644 plugins/Dfusion/luafiles/editor.lua delete mode 100644 plugins/Dfusion/luafiles/itempatterns.lua delete mode 100644 plugins/Dfusion/luafiles/patterns.lua delete mode 100644 plugins/Dfusion/luafiles/patterns2.lua delete mode 100644 plugins/Dfusion/luafiles/xml_struct.lua delete mode 100644 plugins/Dfusion/luafiles/xml_types.lua delete mode 100644 plugins/Dfusion/luafiles/xml_types_windows.lua diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index 5504f32bc..566484f01 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -18,7 +18,7 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry if df.history_event_hist_figure_diedst:is_instance(events[i]) then --print("is instance:"..i) - if events[i].hfid==hist_fig.id then + if events[i].victim==hist_fig.id then --print("Is same id:"..i) trg_hist_fig=events[i].slayer if trg_hist_fig then @@ -29,12 +29,12 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess end end if trg_hist_fig ==nil then - error("Slayer not found") + qerror("Slayer not found") end local trg_unit=trg_hist_fig.unit_id if trg_unit==nil then - error("Unit id not found!") + qerror("Unit id not found!") end local trg_unit_final=df.unit.find(trg_unit) diff --git a/plugins/Dfusion/luafiles/buildingpatterns.lua b/plugins/Dfusion/luafiles/buildingpatterns.lua deleted file mode 100644 index 310c543cb..000000000 --- a/plugins/Dfusion/luafiles/buildingpatterns.lua +++ /dev/null @@ -1,24 +0,0 @@ -ptr_building={} -ptr_building.RTI={off=0,rtype=DWORD} -ptr_building.xs={off=4,rtype=DWORD} -ptr_building.ys={off=6,rtype=DWORD} -ptr_building.zs={off=8,rtype=DWORD} -ptr_building.xe={off=12,rtype=DWORD} -ptr_building.ye={off=16,rtype=DWORD} -ptr_building.ze={off=20,rtype=DWORD} -ptr_building.flags={off=24,rtype=ptt_dfflag.new(4)} -ptr_building.materials={off=28,rtype=DWORD} -ptr_building.builditems={off=228,rtype=ptr_vector} -function ptr_building.getname(self,RTI) - if RTI == nil then - return string.sub(RTTI_GetName(self.RTI),5,-3) - else - return string.sub(RTTI_GetName(RTI),5,-3) - end -end -ptr_subbuilding={} -ptr_subbuilding["building_trapst"]={} -ptr_subbuilding["building_trapst"].state={off=250,rtype=DWORD} -- atleast lever has this -ptr_subbuilding["building_doorst"]={} -ptr_subbuilding["building_doorst"].flg={off=248,rtype=WORD} --maybe flags? -ptr_subbuilding["building_doorst"].state={off=250,rtype=DWORD} diff --git a/plugins/Dfusion/luafiles/editor.lua b/plugins/Dfusion/luafiles/editor.lua deleted file mode 100644 index 06b07ce5e..000000000 --- a/plugins/Dfusion/luafiles/editor.lua +++ /dev/null @@ -1,150 +0,0 @@ -function getTypename(object) - local tbl - local ret={} - if getmetatable(object)~=nil then - local tbl=getmetatable(object) - for k,v in pairs(xtypes) do - if v==tbl then - return k - end - end - for k,v in pairs(xtypes.containers) do - if v==tbl then - return k - end - end - end - if object.name~= nil then - return object.name - end - return "?" -end -function getFields(object) - local tbl - local ret={} - if getmetatable(object)==xtypes["struct-type"].wrap then - tbl=rawget(object,"mtype") - elseif getmetatable(object)==xtypes["struct-type"] then - tbl=object - else - error("Not an class_type or a class_object") - end - for k,v in pairs(tbl.types) do - table.insert(ret,{k,v[2],getTypename(v[1])}) - --ret[v[2]]=k - --print(string.format("%s %x",k,v[2])) - end - table.sort(ret,function (a,b) return a[2]>b[2] end) - return ret -end -function editField(tbl,field,typename) - if EditType[typename] ~= nil then - EditType[typename](tbl[field]) - else - print("Cur value:"..tostring(tbl[field])) - val=getline("Enter newvalue:") - tbl[field]=val - end -end -EditType={} -EditType["df-flagarray"]=function(trg) - local fields=rawget(trg,"mtype").index.names - print("Flag count:"..trg.size) - print("Name count:"..#fields) - for i=0,#fields do - print(string.format("%3d %20s %s",i,fields[i],tostring(trg[i-1]))) - end - number=getline("enter flag id to flip:") - number=tonumber(number) - if number then - trg[fields[number]]= not trg[fields[number]] - end -end -EditType["enum-type"]=function(trg) - local fields=rawget(trg,"mtype").names - local typename=getTypename(rawget(trg,"mtype").etype) - for k,v in pairs(fields) do - print(string.format("%3d %s",k,v)) - end - local cval=trg:get() - if fields[cval]~= nil then - print(string.format("Current value:%d (%s)",cval,fields[cval])) - else - print(string.format("Current value:%d",cval)) - end - number=getline("enter new value:") - number=tonumber(number) - if number then - trg:set(number) - end -end -EditType["static-array"]=function(trg) - local item_type=rawget(trg,"mtype").item_type - local typename=getTypename(item_type) - number=getline(string.format("Select item (max %d, item-type '%s'):",trg.size,typename)) - number=tonumber(number) - if number then - EditType[typename](trg[number]) - end -end -EditType["stl-vector"]=EditType["static-array"] -EditType["df-array"]=EditType["static-array"] -EditType["struct-type"]=function(trg) - local mtype=rawget(trg,"mtype") - local fields=getFields(trg) - for k,v in pairs(fields) do - print(string.format("%4d %25s %s",k,v[1],v[3])) - end - number=getline("Choose field to edit:") - number=tonumber(number) - if number then - local v=fields[number] - editField(trg,v[1],v[3]) - end -end -EditType["pointer"]=function(trg) - local mtype=rawget(trg,"mtype").ptype - local typename=getTypename(mtype) - if(trg:tonumber()==0) then - print("pointer points to nowhere!") - return - end - print("Auto dereferencing pointer! type:"..typename) - if EditType[typename] ~= nil then - EditType[typename](trg:deref()) - else - print("Cur value:"..tostring(trg:deref())) - val=getline("Enter newvalue:") - trg:setref(val) - end -end - -function EditDF() - local i=1 - local tbl={} - for k,v in pairs(rawget(df,"types")) do - print(string.format("%4d %25s %s",i,k,getTypename(v))) - tbl[i]={k,getTypename(v)} - i=i+1 - end - number=dfhack.lineedit("select item to edit (q to quit):") - if number and tonumber(number) then - local entry=tbl[tonumber(number)] - if entry==nil then - return - end - editField(df,entry[1],entry[2]) - --[=[ - if EditType[entry[2]] ~= nil then - EditType[entry[2]](df[entry[1]]) - else - print("Cur value:"..tostring(df[entry[1]])) - val=getline("Enter newvalue:") - df[entry[1]]=val - end - --]=] - end -end -function EditObject(obj) - EditType[getTypename(obj)](obj) -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/itempatterns.lua b/plugins/Dfusion/luafiles/itempatterns.lua deleted file mode 100644 index da62c26d2..000000000 --- a/plugins/Dfusion/luafiles/itempatterns.lua +++ /dev/null @@ -1,62 +0,0 @@ ---dofile("patterns2.lua") moved to common.lua -ptr_item={} -ptr_item.RTI={off=0,rtype=DWORD} -ptr_item.x={off=4,rtype=WORD} -ptr_item.y={off=6,rtype=WORD} -ptr_item.z={off=8,rtype=WORD} -ptr_item.ref={off=0x28,rtype=ptr_vector} - -ptr_item.mat={off=0x78,rtype=WORD} -ptr_item.submat={off=0x7A,rtype=WORD} -ptr_item.submat2={off=0x7C,rtype=DWORD} -ptr_item.legendid={off=0x80,rtype=DWORD} -- i don't remember writing this... -ptr_item.decorations={off=0x90,rtype=ptr_vector} -ptr_item.flags={off=0xC,rtype=ptt_dfflag.new(8)} -ptr_item.ptr_covering={off=0x64,rtype=DWORD} -ptr_item.stack={off=0x58,rtype=WORD} -function ptr_item.getname(self,RTI) - if RTI == nil then - return string.sub(RTTI_GetName(self.RTI),5,-3) - else - return string.sub(RTTI_GetName(RTI),5,-3) - end -end -ptr_subitems={} -ptr_subitems["item_slabst"]={} -ptr_subitems["item_slabst"].msgptr={off=0xA0,rtype=ptt_dfstring} -ptr_subitems["item_slabst"].signtype={off=0xC0,rtype=DWORD} - -ptr_subitems["item_fisthst"]={} -ptr_subitems["item_fisthst"].fisthtype={off=0x78,rtype=WORD} - -ptr_subitems["item_eggst"]={} -ptr_subitems["item_eggst"].race={off=0x78,rtype=DWORD} -ptr_subitems["item_eggst"].isfertile={off=0xa0,rtype=DWORD} --0 or 1 -ptr_subitems["item_eggst"].hatchtime={off=0xa4,rtype=DWORD} - -ptr_decoration_gen={} -ptr_decoration_gen.RTI={off=0,rtype=DWORD} -ptr_decoration_gen.material={off=0x04,rtype=WORD} -- same for all? -ptr_decoration_gen.submat={off=0x08,rtype=DWORD} -function ptr_decoration_gen.getname(self,RTI) - if RTI == nil then - return string.sub(RTTI_GetName(self.RTI),21,-5) - else - return string.sub(RTTI_GetName(RTI),21,-5) - end -end -ptr_decoration={} -ptr_decoration["covered"]={} -ptr_decoration["covered"].material={off=0x04,rtype=WORD} -ptr_decoration["covered"].submat={off=0x08,rtype=DWORD} -ptr_decoration["art_image"]={} -ptr_decoration["art_image"].material={off=0x04,rtype=WORD} -ptr_decoration["art_image"].submat={off=0x08,rtype=DWORD} -ptr_decoration["art_image"].image={off=0x24,rtype=DWORD} -ptr_decoration["bands"]={} -ptr_decoration["bands"].material={off=0x04,rtype=WORD} -ptr_decoration["bands"].submat={off=0x08,rtype=DWORD} -ptr_cover={} --covering of various types (blood, water, etc) -ptr_cover.mat={off=0,rtype=WORD} -ptr_cover.submat={off=4,rtype=DWORD} -ptr_cover.state={off=8,rtype=WORD} \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/patterns.lua b/plugins/Dfusion/luafiles/patterns.lua deleted file mode 100644 index 34aa0c274..000000000 --- a/plugins/Dfusion/luafiles/patterns.lua +++ /dev/null @@ -1,248 +0,0 @@ -ptt_dfstring={} -if WINDOWS then - ptt_dfstring.ptr={off=0,rtype=DWORD} - ptt_dfstring.size={off=16,rtype=DWORD} - ptt_dfstring.alloc={off=20,rtype=DWORD} - - function ptt_dfstring:getval() - --print(string.format("GETTING FROM:%x",self.__offset)) - if self.size<16 then - --print(string.format("GETTING FROM:%x",self.__offset)) - return string.sub(engine.peekstr(self.__offset),1,self.size) - else - --print(string.format("GETTING FROM:%x",self.ptr)) - return string.sub(engine.peekstr(self.ptr),1,self.size) - end - end - function ptt_dfstring:setval(newstring) - local offset=self.__offset - local strl=string.len(newstring) - if strl<16 then - --print(string.format("GETTING FROM:%x",self.__offset)) - - engine.poked(offset+ptt_dfstring.size.off,strl) - engine.poked(offset+ptt_dfstring.alloc.off,15) - engine.pokestr(offset,newstring) - - else - local loc - if engine.peekd(offset+ptt_dfstring.alloc.off) > strl then - loc=engine.peekd(offset) - print("Will fit:"..loc.." len:"..strl) - else - loc=Allocate(strl+1) - engine.poked(offset+ptt_dfstring.alloc.off,strl) - print("Will not fit:"..loc.." len:"..strl) - end - --print(string.format("GETTING FROM:%x",self.ptr)) - engine.poked(self.__offset+ptt_dfstring.size.off,strl) - engine.pokestr(loc,newstring) - engine.poked(self.__offset,loc) - end - end -else - --ptt_dfstring.ptr={off=0,rtype=DWORD} - function ptt_dfstring:getval() - return engine.peekstr_stl(self.__offset) - end -end ---if(COMPATMODE) then ---ptr_vector={} ---ptr_vector.st={off=4,rtype=DWORD} ---ptr_vector.en={off=8,rtype=DWORD} ---else -ptr_vector={} -ptr_vector.st={off=0,rtype=DWORD} -ptr_vector.en={off=4,rtype=DWORD} -ptr_vector.alloc={off=8,rtype=DWORD} ---end -function ptr_vector:clone(settype) - local ret={} - for k,v in pairs(self) do - ret[k]=v - end - ret.type=settype - return ret -end -function ptr_vector:size() - return (self.en-self.st)/engine.sizeof(self.type) -end -ptr_vector.type=DWORD -function ptr_vector:getval(num) - if self.st==0 then return error("Vector empty.") end - --print("Wants:"..num.." size:"..self:size()) - if num>=self:size() then error("Index out of bounds in vector.") end - return engine.peek(self.st+engine.sizeof(self.type)*num,self.type) -end -function ptr_vector:setval(num,val) - return engine.poke(self.st+engine.sizeof(self.type)*num,self.type,val) -end -function ptr_vector:append(val) - if self.alloc - self.en > 0 then - local num=self:size() - self.en=self.en+engine.sizeof(self.type) - self:setval(val,num) - else - error("larger than allocated arrays not implemented yet") - local num=self:size() - local ptr=Allocate(num*2) - - end -end -ptt_dfflag={} -function ptt_dfflag.flip(self,num) --flip one bit in flags - local of=math.floor (num/8); - - self[of]=bit.bxor(self[of],bit.lshift(1,num%8)) -end - -function ptt_dfflag.get(self,num) -- get one bit in flags - local of=math.floor (num/8); - - if bit.band(self[of],bit.lshift(1,num%8))~=0 then - return true - else - return false - end -end -function ptt_dfflag.set(self,num,val) --set to on or off one bit in flags - if (self:get(num)~=val) then - self:flip(num) - end -end -function ptt_dfflag.new(size) -- create new flag pattern of size(in bytes) - local ret={} - for i=0,size-1 do - ret[i]={off=i,rtype=BYTE}; - end - ret.flip=ptt_dfflag.flip --change to metatable stuff... - ret.get=ptt_dfflag.get - ret.set=ptt_dfflag.set - return ret; -end ---[[ - Creature: - 0 name (df_string) 28 - 28 nick (df_string) 56 - 56 surname- namearray(7*dword(4)) 84 ... - 140 race (dword) 144 - 224 flags - 264 civ (dword) - 252 ID - 592 following ID - 904 bleed vector? hurt vector or sth... - 0x790 legends id? - 2128 known names? or knowledge? - flags: - 0 Can the dwarf move or are they waiting for their movement timer - 1 Dead (might also be set for incoming/leaving critters that are alive) - 2 Currently in mood - 3 Had a mood - 4 "marauder" -- wide class of invader/inside creature attackers - 5 Drowning - 6 Active merchant - 7 "forest" (used for units no longer linked to merchant/diplomacy, they just try to leave mostly) - 8 Left (left the map) - 9 Rider - 10 Incoming - 11 Diplomat - 12 Zombie - 13 Skeleton - 14 Can swap tiles during movement (prevents multiple swaps) - 15 On the ground (can be conscious) - 16 Projectile - 17 Active invader (for organized ones) - 18 Hidden in ambush - 19 Invader origin (could be inactive and fleeing) - 20 Will flee if invasion turns around - 21 Active marauder/invader moving inward - 22 Marauder resident/invader moving in all the way - 23 Check against flows next time you get a chance - 24 Ridden - 25 Caged - 26 Tame - 27 Chained - 28 Royal guard - 29 Fortress guard - 30 Suppress wield for beatings/etc - 31 Is an important historical figure - 32 swiming - -]]-- -ptr_Creature={} -local posoff=0 --VersionInfo.getGroup("Creatures"):getGroup("creature"):getOffset("position") -ptr_Creature.x={off=posoff,rtype=WORD} --ok -ptr_Creature.y={off=posoff+2,rtype=WORD} --ok -ptr_Creature.z={off=posoff+4,rtype=WORD} --ok -ptr_Creature.flags={off=0,rtype=ptt_dfflag.new(10)} -ptr_Creature.name={off=0,rtype=ptt_dfstring} -ptr_Creature.ID={off=252,rtype=DWORD} --ok i guess -ptr_Creature.followID={off=592,rtype=DWORD} --ok -ptr_Creature.race={off=140,rtype=DWORD} --ok -ptr_Creature.civ={off=264,rtype=DWORD} -ptr_Creature.legends={off=344,rtype=ptr_vector} --ok -ptr_Creature.hurt1={off=0x308,rtype=ptr_vector:clone(BYTE)} --byte vector... -ptr_Creature.hurt2={off=0x338,rtype=ptr_vector} -ptr_Creature.wounds={off=0x388,rtype=ptr_vector} -ptr_Creature.itemlist1={off=0x1D0,rtype=ptr_vector} -ptr_Creature.itemlist2={off=0x288,rtype=ptr_vector} -ptr_Creature.bloodlvl={off=0x490,rtype=DWORD} -ptr_Creature.bleedlvl={off=0x494,rtype=DWORD} - -ptr_CrGloss={} -ptr_CrGloss.token={off=0,rtype=ptt_dfstring} -ptr_CrGloss.castes={off=296,rtype=ptr_vector} - -ptr_CrCaste={} -ptr_CrCaste.name={off=0,rtype=ptt_dfstring} -ptr_CrCaste.flags_ptr={off=0x5A0,rtype=DWORD} --size 17? ---[=[ - Flags: - 57 - is sentient (allows setting labours) ---]=] -ptr_LEntry={} -- all size 256 -ptr_LEntry.name={off=36,rtype=ptt_dfstring} -ptr_LEntry.id1={off=160,rtype=DWORD} -ptr_LEntry.id2={off=164,rtype=DWORD} -ptr_LEntry.somlist={off=220,rtype=DWORD} - -ptr_dfname={} -for i=0,6 do -ptr_dfname[i]={off=i*4,rtype=DWORD} -end ---[[ - Site docs: - 0x38 name struct todo... - 0x78 type: - 0 - mountain halls (yours) - 1 - dark fort - 2 - cave - 3 - mountain hall (other) - 4 - forest - 5 - hamlet - 6 - imp location - 7 - lair - 8 - fort - 9 - camp - 0x7a some sort of id? - 0x84 some vec (ids) - 0x94 some other vec (ids) - 0xa4 some vec (prts) - 0x118 ptr to sth - - 0x14c ptr to mapdata -]]-- - - -ptr_site={} -ptr_site.type={off=0x78,rtype=WORD} -ptr_site.id={off=0x7a,rtype=DWORD} -ptr_site.name={off=0x38,rtype=ptr_dfname} -ptr_site.flagptr={off=0x118,rtype=DWORD} - -ptr_legends2={} -ptr_legends2.id={off=0,rtype=DWORD} -ptr_legends2.follow={off=0x18,rtype=DWORD} - -ptr_material={} -ptr_material.token={off=0,rtype=ptt_dfstring} diff --git a/plugins/Dfusion/luafiles/patterns2.lua b/plugins/Dfusion/luafiles/patterns2.lua deleted file mode 100644 index 09a3b0db8..000000000 --- a/plugins/Dfusion/luafiles/patterns2.lua +++ /dev/null @@ -1,29 +0,0 @@ -ptr_COL={} -- complete object locator... -ptr_COL.sig={off=0,rtype=DWORD} -ptr_COL.offset={off=4,rtype=DWORD} --offset of this vtable in the complete class -ptr_COL.cdoffset={off=8,rtype=DWORD} -- constructor displacement -ptr_COL.typePointer={off=12,rtype=DWORD} -ptr_COL.hierarchyPointer={off=16,rtype=DWORD} - -ptr_RTTI_Type={} -ptr_RTTI_Type.vftPointer={off=0,rtype=DWORD} -ptr_RTTI_Type.name={off=8,rtype=STD_STRING} - -function RTTI_GetName(vtable) - local COLoff=engine.peek(vtable-4,DWORD) - --print(string.format("Look:%x vtable:%x",vtable,engine.peek(vtable-4,DWORD))) - COL=engine.peek(COLoff,ptr_COL) - --print(string.format("COL:%x Typeptr:%x Type:%s",COLoff,COL.typePointer,engine.peek(COL.typePointer,ptr_RTTI_Type.name))) - return engine.peek(COL.typePointer,ptr_RTTI_Type.name) -end -ptr_RTTI_Hierarchy={} -ptr_RTTI_Hierarchy.sig={off=0,rtype=DWORD} -ptr_RTTI_Hierarchy.attributes={off=4,rtype=DWORD} -ptr_RTTI_Hierarchy.numBaseClasses={off=8,rtype=DWORD} -ptr_RTTI_Hierarchy.ptrBases={off=12,rtype=DWORD} - -ptr_RTTI_BaseClass={} -ptr_RTTI_BaseClass.typePointer={off=0,rtype=DWORD} -ptr_RTTI_BaseClass.numContained={off=4,rtype=DWORD} ---todo PMD ---todo flags \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/xml_struct.lua b/plugins/Dfusion/luafiles/xml_struct.lua deleted file mode 100644 index 8d0801be3..000000000 --- a/plugins/Dfusion/luafiles/xml_struct.lua +++ /dev/null @@ -1,151 +0,0 @@ -if types ~= nil then - return -end -dofile("dfusion/xml_types.lua") - -function parseTree(t) - for k,v in ipairs(t) do - if v.xarg~=nil and v.xarg["type-name"]~=nil and v.label=="ld:global-type" then - local name=v.xarg["type-name"]; - if(types[name]==nil) then - - --for kk,vv in pairs(v.xarg) do - -- print("\t"..kk.." "..tostring(vv)) - --end - types[name]=makeType(v) - --print("found "..name.." or type:"..v.xarg.meta or v.xarg.base) - - else - types[name]=makeType(v,types[name]) - end - end - end -end -function parseTreeGlobals(t) - local glob={} - --print("Parsing global-objects") - for k,v in ipairs(t) do - if v.xarg~=nil and v.label=="ld:global-object" then - local name=v.xarg["name"]; - --print("Parsing:"..name) - local subitem=v[1] - if subitem==nil then - error("Global-object subitem is nil:"..name) - end - local ret=makeType(subitem) - if ret==nil then - error("Make global returned nil!") - end - glob[name]=ret - end - end - --print("Printing globals:") - --for k,v in pairs(glob) do - -- print(k) - --end - return glob -end -function findAndParse(tname) - --if types[tname]==nil then types[tname]={} end - -- [=[ - for k,v in ipairs(main_tree) do - local name=v.xarg["type-name"]; - if v.xarg~=nil and v.xarg["type-name"]~=nil and v.label=="ld:global-type" then - - if(name ==tname) then - --print("Parsing:"..name) - --for kk,vv in pairs(v.xarg) do - -- print("\t"..kk.." "..tostring(vv)) - --end - types[name]=makeType(v,types[name]) - end - --print("found "..name.." or type:"..v.xarg.meta or v.xarg.base) - end - end - --]=] -end -df={} -df.types=rawget(df,"types") or {} --temporary measure for debug -local df_meta={} -function df_meta:__index(key) - local addr=VersionInfo.getAddress(key) - local vartype=rawget(df,"types")[key]; - if addr==0 then - error("No such global address exist") - elseif vartype==nil then - error("No such global type exist") - else - return type_read(vartype,addr) - end -end -function df_meta:__newindex(key,val) - local addr=VersionInfo.getAddress(key) - local vartype=rawget(df,"types")[key]; - if addr==0 then - error("No such global address exist") - elseif vartype==nil then - error("No such global type exist") - else - return type_write(vartype,addr,val) - end -end -setmetatable(df,df_meta) --------------------------------- -types=types or {} -dofile("dfusion/patterns/xml_angavrilov.lua") --- [=[ -main_tree=parseXmlFile("dfusion/patterns/supplementary.xml")[1] -parseTree(main_tree) -main_tree=parseXmlFile("dfusion/patterns/codegen.out.xml")[1] -parseTree(main_tree) -rawset(df,"types",parseTreeGlobals(main_tree)) ---]=] ---[=[labels={} -for k,v in ipairs(t) do - labels[v.label]=labels[v.label] or {meta={}} - if v.label=="ld:global-type" and v.xarg~=nil and v.xarg.meta ~=nil then - labels[v.label].meta[v.xarg.meta]=1 - end -end -for k,v in pairs(labels) do - print(k) - if v.meta~=nil then - for kk,vv in pairs(v.meta) do - print("=="..kk) - end - end -end--]=] -function addressOf(var,key) - if key== nil then - local addr=rawget(var,"ptr") - return addr - else - local meta=getmetatable(var) - if meta== nil then - error("Failed to get address, no metatable") - end - if meta.__address == nil then - error("Failed to get address, no __address function") - end - return meta.__address(var,key) - end -end -function printGlobals() - print("Globals:") - for k,v in pairs(rawget(df,"types")) do - print(k) - end -end -function printFields(object) - local tbl - if getmetatable(object)==xtypes["struct-type"].wrap then - tbl=rawget(object,"mtype") - elseif getmetatable(object)==xtypes["struct-type"] then - tbl=object - else - error("Not an class_type or a class_object") - end - for k,v in pairs(tbl.types) do - print(string.format("%s %x",k,v[2])) - end -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/xml_types.lua b/plugins/Dfusion/luafiles/xml_types.lua deleted file mode 100644 index cced4be2c..000000000 --- a/plugins/Dfusion/luafiles/xml_types.lua +++ /dev/null @@ -1,734 +0,0 @@ --- otherwise you just maintain alignment granularity in addition to size for all fields, --- round up current offset to field alignment, --- assign structs the max alignment of any field, and round up struct size to its alignment -function type_read(valtype,address) - if valtype.issimple then - --print("Simple read:"..tostring(valtype.ctype)) - return engine.peek(address,valtype.ctype) - else - return valtype:makewrap(address) - end -end -function type_write(valtype,address,val) - if valtype.issimple then - engine.poke(address,valtype.ctype,val) - else - engine.poke(address,DWORD,rawget(val,"ptr")) - end -end -function first_of_type(node,labelname) - for k,v in ipairs(node) do - if type(v)=="table" and v.label==labelname then - return v - end - end -end -xtypes={} -- list of all types prototypes (e.g. enum-type -> announcement_type) --- type must have size, new and makewrap (that makes a wrapper around ptr) -if WINDOWS then - dofile("dfusion/xml_types_windows.lua") -elseif LINUX then - dofile("dfusion/xml_types_linux.lua") -end - -function padAddress(curoff,typetoadd) --return new offset to place things... Maybe linux is different? - --windows -> sizeof(x)==alignof(x) - --[=[ - - if sizetoadd>8 then sizetoadd=8 end - if(math.mod(curoff,sizetoadd)==0) then - return curoff - else - return curoff+(sizetoadd-math.mod(curoff,sizetoadd)) - end - --]=] - if typetoadd==nil or typetoadd.__align==nil then - return curoff - else - - if(math.mod(curoff,typetoadd.__align)==0) then - return curoff - else - if PRINT_PADS then - print("padding off:"..curoff.." with align:"..typetoadd.__align.." pad="..(typetoadd.__align-math.mod(curoff,typetoadd.__align))) - end - return curoff+(typetoadd.__align-math.mod(curoff,typetoadd.__align)) - end - end -end - - -local sarr={} -sarr.__index=sarr -function sarr.new(node,obj) - local o=obj or {} - setmetatable(o,sarr) - --print("Making array.") - o.count=tonumber(node.xarg.count) - --print("got count:"..o.count) - o.item_type=makeType(first_of_type(node,"ld:item")) - o.__align=o.item_type.__align or 4 - o.size=o.count*o.item_type.size - --print("got subtypesize:"..o.item_type.size) - return o -end -function sarr:makewrap(address) - local o={} - o.mtype=self - o.ptr=address - setmetatable(o,self.wrap) - return o -end -sarr.wrap={} -function sarr.wrap:__index(key) - if key=="size" then - return rawget(self,"mtype").count - end - local num=tonumber(key) - local mtype=rawget(self,"mtype") - if num~=nil and num"..v.xarg.meta.." ttype:"..v.label) - local ttype=makeType(v) - if ttype.size==0 then error("Field with 0 size! type="..node.xarg["type-name"].."."..name) end - if ttype.size-math.ceil(ttype.size) ~= 0 then error("Field with real offset! type="..node.xarg["type-name"].."."..name) end - --for k,v in pairs(ttype) do - -- print(k..tostring(v)) - --end - - local off=padAddress(o.size,ttype) - --[=[if PRINT_PADS then - - if ttype.__align then - print(name.." "..ttype.__align .. " off:"..off.." "..math.mod(off,ttype.__align)) - - end - if off~=o.size then - print("var:"..name.." size:"..ttype.size) - end - end]=] - --print("var:"..name.." ->"..tostring(off).. " :"..ttype.size) - if isunion then - if ttype.size > o.size then - o.size=ttype.size - end - o.types[name]={ttype,0} - else - o.size=off - o.types[name]={ttype,o.size}--+o.baseoffset - o.size=o.size+ttype.size - end - if firsttype== nil then - firsttype=o.types[name][1] - end - - end - end - if isunion then - o.__align=0 - for k,v in pairs(o.types) do - if v[1].__align~= nil and v[1].__align>o.__align then - o.__align=v[1].__align - end - end - else - if o.base[1]~= nil then - o.__align=o.base[1].__align - elseif firsttype~= nil then - o.__align=firsttype.__align - --if o.__align~=nil then - --print("\t\t setting align to:"..(o.__align or "")) - --else - --o.__align=4 - --print("\t\t NIL ALIGNMENT!") - --end - end - end - - return o -end - -type_class.wrap={} -function type_class.wrap:__address(key) - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - if mytype.types[key] ~= nil then - return myptr+mytype.types[key][2] - else - error("No such field exists") - end -end -function type_class.wrap:__index(key) - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - if mytype.types[key] ~= nil then - return type_read(mytype.types[key][1],myptr+mytype.types[key][2]) - else - error("No such field exists") - end -end -function type_class.wrap:__newindex(key,value) - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - if mytype.types[key] ~= nil then - return type_write(mytype.types[key][1],myptr+mytype.types[key][2],value) - else - error("No such field exists") - end -end -function type_class:makewrap(ptr) - local o={} - o.ptr=ptr - o.mtype=self - setmetatable(o,self.wrap) - return o -end -xtypes["struct-type"]=type_class -xtypes["class-type"]=type_class -local type_pointer={} -type_pointer.__index=type_pointer -function type_pointer.new(node,obj) - local o=obj or {} - setmetatable(o,type_pointer) - local subnode=first_of_type(node,"ld:item") - if subnode~=nil then - o.ptype=makeType(subnode,nil,true) - end - - o.size=4 - o.__align=4 - return o -end -type_pointer.wrap={} -type_pointer.wrap.__index=type_pointer.wrap -function type_pointer.wrap:tonumber() - local myptr=rawget(self,"ptr") - return engine.peekd(myptr)--type_read(DWORD,myptr) -end -function type_pointer.wrap:__setup(trg) - if trg~= nil then - self:fromnumber(trg) - else - self:fromnumber(0) - end -end -function type_pointer.wrap:fromnumber(num) - local myptr=rawget(self,"ptr") - return engine.poked(myptr,num)--type_write(DWORD,myptr,num) -end -function type_pointer.wrap:deref() - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - return type_read(mytype.ptype,engine.peekd(myptr)) -end -function type_pointer.wrap:setref(val) - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - return type_write(mytype.ptype,engine.peekd(myptr),val) -end -function type_pointer.wrap:newref(val) - local myptr=rawget(self,"ptr") - local mytype=rawget(self,"mtype") - local ptr=engine.alloc(mytype.ptype.size) - self:fromnumber(ptr) - return ptr -end -function type_pointer:makewrap(ptr) - local o={} - o.ptr=ptr - o.mtype=self - setmetatable(o,self.wrap) - return o -end -xtypes["pointer"]=type_pointer --------------------------------------------- ---stl-vector (beginptr,endptr,allocptr) ---df-flagarray (ptr,size) -xtypes.containers=xtypes.containers or {} -local dfarr={} -dfarr.__index=dfarr -function dfarr.new(node,obj) - local o=obj or {} - setmetatable(o,dfarr) - o.size=8 - o.__align=4 - o.item_type=makeType(first_of_type(node,"ld:item")) - return o -end -function dfarr:makewrap(address) - local o={} - o.mtype=self - o.ptr=address - setmetatable(o,self.wrap) - return o -end -dfarr.wrap={} -function dfarr.wrap:__setup(size) - local mtype=rawget(self,"mtype") - engine.pokew(rawget(self,"ptr")+4,size) - local newptr=engine.alloc(size*mtype.item_type.size) - engine.poked(rawget(self,"ptr"),newptr) -end -function dfarr.wrap:__index(key) - local num=tonumber(key) - local mtype=rawget(self,"mtype") - local size=engine.peekw(rawget(self,"ptr")+4) - if key=="size" then - return size - end - local item_start=engine.peekd(rawget(self,"ptr")) - if num~=nil and num"..tostring(v)) - end - error("Node parser not found: "..label) - end - --[=[ - if getSimpleType(node)~=nil then - return getSimpleType(node) - end - print("Trying to make:"..node.xarg.meta) - if xtypes[node.xarg.meta]~=nil then - return xtypes[node.xarg.meta].new(node,obj) - end - - if node.xarg.meta=="global" then - --print(node.xarg["type-name"]) - - if types[node.xarg["type-name"]]== nil then - error("type:"..node.xarg["type-name"].." should already be ready") - end - return types[node.xarg["type-name"]] - end - ]=] - --[=[for k,v in pairs(node) do - print(k.."=>"..tostring(v)) - if type(v)=="table" then - for kk,vv in pairs(v) do - print("\t"..kk.."=>"..tostring(vv)) - end - end - end]=] - -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/xml_types_windows.lua b/plugins/Dfusion/luafiles/xml_types_windows.lua deleted file mode 100644 index 90bc80312..000000000 --- a/plugins/Dfusion/luafiles/xml_types_windows.lua +++ /dev/null @@ -1,159 +0,0 @@ -xtypes.containers=xtypes.containers or {} -local stl_vec={} ---[=[ - (make-instance 'pointer :name $start) - (make-instance 'pointer :name $end) - (make-instance 'pointer :name $block-end) - (make-instance 'padding :name $pad :size 4 :alignment 4) ---]=] -stl_vec.__index=stl_vec - -function stl_vec.new(node,obj) - local o=obj or {} - - o.size=16 - o.__align=4 - local titem=first_of_type(node,"ld:item") - if titem~=nil then - o.item_type=makeType(titem) - else - o.item_type=getSimpleType("uint32_t") - end - setmetatable(o,stl_vec) - return o -end -function stl_vec:makewrap(address) - local o=obj or {} - o.mtype=self - o.ptr=address - setmetatable(o,self.wrap) - return o -end -stl_vec.wrap={} -function stl_vec.wrap:__index(key) - local num=tonumber(key) - local mtype=rawget(self,"mtype") - local ptr=rawget(self,"ptr") - local p_begin=engine.peek(ptr,DWORD) - local p_end=engine.peek(ptr+4,DWORD) - local size=(p_end-p_begin)/mtype.item_type.size - if key=="size" then - return size - end - - --allocend=type_read(ptr+8,DWORD) - if num~=nil and num Date: Sat, 15 Sep 2012 18:05:53 +0300 Subject: [PATCH 014/472] Removed unused triggers folder --- plugins/Dfusion/luafiles/triggers/compile.bat | 1 - .../Dfusion/luafiles/triggers/functions.lua | 20 ---- .../luafiles/triggers/functions_menu.lua | 12 -- plugins/Dfusion/luafiles/triggers/plugin.lua | 107 ------------------ .../Dfusion/luafiles/triggers/triggers.asm | 68 ----------- plugins/Dfusion/luafiles/triggers/triggers.o | Bin 720 -> 0 bytes .../luafiles/triggers/universalfunc.asm | 14 --- 7 files changed, 222 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/triggers/compile.bat delete mode 100644 plugins/Dfusion/luafiles/triggers/functions.lua delete mode 100644 plugins/Dfusion/luafiles/triggers/functions_menu.lua delete mode 100644 plugins/Dfusion/luafiles/triggers/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/triggers/triggers.asm delete mode 100644 plugins/Dfusion/luafiles/triggers/triggers.o delete mode 100644 plugins/Dfusion/luafiles/triggers/universalfunc.asm diff --git a/plugins/Dfusion/luafiles/triggers/compile.bat b/plugins/Dfusion/luafiles/triggers/compile.bat deleted file mode 100644 index 3b0fec1a2..000000000 --- a/plugins/Dfusion/luafiles/triggers/compile.bat +++ /dev/null @@ -1 +0,0 @@ -as -anl --32 -o triggers.o triggers.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/triggers/functions.lua b/plugins/Dfusion/luafiles/triggers/functions.lua deleted file mode 100644 index e6c3a62c9..000000000 --- a/plugins/Dfusion/luafiles/triggers/functions.lua +++ /dev/null @@ -1,20 +0,0 @@ -function func.Find_Print() - pos=offsets.find(offsets.base(),0x73,0x02,0x8b,0xce,0x53,0x6a,0x01,0x6a,0x06,CALL) -- a hack for now... - return engine.peekd(pos+10)+pos+14-offsets.base() -end -function func.PrintMessage(msg,color1,color2) - func.f_print_pos= func.f_print_pos or func.Find_Print() - print(string.format("Print @:%x",func.f_print_pos)) - debuger.suspend() - d=NewCallTable() -- make a call table - t=Allocate(string.len(msg)) - engine.pokestr(t,msg) - --print(string.format("Message location:%x",t)) - d["ECX"]=t --set ecx to message location - d["STACK5"]=color1 -- push to stack color1 - d["STACK4"]=color2 -- push to stack color2 - d["STACK3"]=0 -- this is usually 0 maybe a struct pointing to location of this message? - PushFunction(func.f_print_pos+offsets.base(),d) -- prep to call function - -- was 0x27F030 - debuger.resume() -end diff --git a/plugins/Dfusion/luafiles/triggers/functions_menu.lua b/plugins/Dfusion/luafiles/triggers/functions_menu.lua deleted file mode 100644 index 3256c6117..000000000 --- a/plugins/Dfusion/luafiles/triggers/functions_menu.lua +++ /dev/null @@ -1,12 +0,0 @@ -func={} -dofile("dfusion/triggers/functions.lua") -func.menu=MakeMenu() -function func.PrintMessage_() - print("Type a message:") - msg=io.stdin:read() - func.PrintMessage(msg,6,1) -end -if not(FILE) then -- if not in script mode - func.menu:add("Print message",func.PrintMessage_) - func.menu:display() -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/triggers/plugin.lua b/plugins/Dfusion/luafiles/triggers/plugin.lua deleted file mode 100644 index e8f31eac1..000000000 --- a/plugins/Dfusion/luafiles/triggers/plugin.lua +++ /dev/null @@ -1,107 +0,0 @@ -if FILE then - return -end -callinfo={} -callinfo.regs={} -callinfo.regs["EAX"]=0 -callinfo.regs["EBX"]=1 -callinfo.regs["ECX"]=2 -callinfo.regs["EDX"]=3 -callinfo.regs["ESI"]=4 -callinfo.regs["EDI"]=5 -callinfo.regs["STACK1"]=6 -callinfo.regs["STACK2"]=7 -callinfo.regs["STACK3"]=8 -callinfo.regs["STACK4"]=9 -callinfo.regs["STACK5"]=10 - -mypos=engine.getmod("triggers_main") - -function GetCount() - return engine.peek(0,triggers.count) -end -function SetCount(val) - engine.poke(0,triggers.count,val) -end -function NewCallTable(tbl) - ret=tbl or {} - for k,v in pairs(callinfo.regs) do - ret[k]=0 - end - return ret -end -function PushFunction(off,data) - local i=GetCount() - engine.poked(triggers.table.off+i*44,off) -- add function to table - engine.poked(triggers.data.off+0,data["EAX"]) --set register data... - engine.poked(triggers.data.off+4,data["EBX"]) - engine.poked(triggers.data.off+8,data["ECX"]) - engine.poked(triggers.data.off+12,data["EDX"]) - engine.poked(triggers.data.off+16,data["ESI"]) - engine.poked(triggers.data.off+20,data["EDI"]) - engine.poked(triggers.data.off+24,data["STACK1"]) - engine.poked(triggers.data.off+28,data["STACK2"]) - engine.poked(triggers.data.off+32,data["STACK3"]) - engine.poked(triggers.data.off+36,data["STACK4"]) - engine.poked(triggers.data.off+40,data["STACK5"]) - SetCount(i+1) -end -function loadTriggers() - if triggers then return end - triggers={} - p=engine.getmod("triggerdata") - triggers.count={off=engine.peekd(p),rtype=DWORD} - triggers.table={off=engine.peekd(p+4),rtype=DWORD} - triggers.ret={off=engine.peekd(p+8),rtype=DWORD} - triggers.data={off=engine.peekd(p+12),rtype=DWORD} -end -if mypos then - loadTriggers() - dofile("dfusion/triggers/functions_menu.lua") - --return -else - triggers={} - - off=0x56D345+offsets.base() - print(string.format("Function start %x",off)) - ModData=engine.installMod("dfusion/triggers/triggers.o","triggers_main") - print("installed") - modpos=ModData.pos - modsize=ModData.size - fdata=engine.newmod("function_body",256) - - - engine.poked(modpos+engine.FindMarker(ModData,"trigercount"),modpos+modsize) -- count of functions - engine.poked(modpos+engine.FindMarker(ModData,"f_loc"),modpos+modsize+4) -- function table start - engine.poked(modpos+engine.FindMarker(ModData,"f_data"),fdata) -- function data start - - engine.poked(modpos+engine.FindMarker(ModData,"saveplace31"),modpos+modsize+260) -- save function loc - engine.poked(modpos+engine.FindMarker(ModData,"saveplace32"),modpos+modsize+260) -- save function loc - engine.poked(modpos+engine.FindMarker(ModData,"saveplace33"),modpos+modsize+260) -- save function loc - engine.poked(modpos+engine.FindMarker(ModData,"saveplace"),modpos+modsize+256) -- save function loc - engine.poked(modpos+engine.FindMarker(ModData,"trigcount2"),modpos+modsize) -- count of functions (for zeroing) - engine.poked(modpos+engine.FindMarker(ModData,"saveplace2"),modpos+modsize+256) -- save function loc - engine.poked(modpos+engine.FindMarker(ModData,"results"),modpos+modsize+256) --overwrite function call with results - - triggers.count={off=modpos+modsize,rtype=DWORD} - triggers.table={off=modpos+modsize+4,rtype=DWORD} - triggers.ret={off=modpos+modsize+256,rtype=DWORD} - triggers.data={off=fdata,rtype=DWORD} - pp=Allocate(4*4) - engine.poked(pp,triggers.count.off) - engine.poked(pp+4,triggers.table.off) - engine.poked(pp+8,triggers.ret.off) - engine.poked(pp+12,triggers.data.off) - engine.newmod("triggerdata",0,pp) - function pokeCall(off) - engine.pokeb(off,0xe8) - --b=engine.peekb(off+1) - engine.poked(off+1,modpos-off-5) - --engine.pokeb(off+5,b) - end - print(string.format("Mod @:%x",modpos)) - dat=engine.peekarb(off,5) - engine.pokearb(modpos,dat,5) - pokeCall(off) -end - diff --git a/plugins/Dfusion/luafiles/triggers/triggers.asm b/plugins/Dfusion/luafiles/triggers/triggers.asm deleted file mode 100644 index 58b9e5f61..000000000 --- a/plugins/Dfusion/luafiles/triggers/triggers.asm +++ /dev/null @@ -1,68 +0,0 @@ -.intel_syntax -nop #5 nops for instruction thats replaced by call -nop -nop -nop -nop -pushad -pushfd -saveplace31: -mov [0xDEADBEEF], esp -trigercount: -mov eax, [0xDEADBEEF] #mov count of triggers. -f_loc: -mov esi, 0xdeadbeef #mov location of functions. -f_data: -mov ebx, 0xDEADBEEF #mov a start of function data -test eax,eax -jz lend -lstart: -dec eax -push ebx -push eax - -mov eax,[esi+eax*4] -saveplace: -mov [0xDEADBEEF],eax #save function for later -pop eax -push eax -mov edx,44 -mul edx -add eax,ebx -#stack preparation -mov ebx,[eax+24] -push ebx -mov ebx,[eax+28] -push ebx -mov ebx,[eax+32] -push ebx -mov ebx,[eax+36] -push ebx -mov ebx,[eax+40] -push ebx -mov ebx,[eax+4] -mov ecx,[eax+8] -mov edx,[eax+12] -mov esi,[eax+16] -mov edi,[eax+20] -mov eax,[eax] -saveplace2: -call [0xdeadbeef] #same save loc -results: -mov [0xDEADBEEF],eax #get result -saveplace33: -mov esp, [0xDEADBEEF] -add esp, -8 -pop eax -pop ebx -cmp eax,0 -jnz lstart -lend: -xor eax,eax -trigcount2: -mov dword ptr [0xDEADBEEF], eax # zero triggers -saveplace32: -mov esp, [0xDEADBEEF] -popfd -popad -ret diff --git a/plugins/Dfusion/luafiles/triggers/triggers.o b/plugins/Dfusion/luafiles/triggers/triggers.o deleted file mode 100644 index 5a47daa69bb4ac4f86773a23f66d5a73c8b6d7b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 720 zcmeZaWM%*X5k?>evsfARN>VFIz-$Q70i=46_#mbNgF%8`N@7VOnm9Vwfx+N_UQ%%} zx;Q$&0jO>Q2qetuRDHj1?Y)H{Vjqau4I)|(l!SQ%2XwQvEe3HT0(R*DjrjhE@kV!q zL~wV63=k^-u?i4t05MCq2S;}RPj`VpcZEnd!+%kbF0j^au<^}Dendn!|6nLxWq1Hd zcy8k12@~|vGILU)4*d6@fgznSEioG=faGu%Ae$YCfkDT>1{N0plDu${wD_F-WFSun zNdyvP;&73i)VvfRhoKB1Qe2W)1Pp!!xCoHJAOpl|C?X(UG+d-8wYW5=q?iE|9|-pt z0(ogDBDO$Y1&W9VkXH*AL5_QnIn0c0KoS)o$2${HiW#I34IsxkD1~8D69N=3PAp3; n$Vp62H8x}@DauSwElSQW%_~8OBUnZVrm- Date: Sun, 16 Sep 2012 13:20:55 +0300 Subject: [PATCH 015/472] More useless stuff removed --- plugins/Dfusion/luafiles/items/plugin.lua | 212 ---------------------- plugins/Dfusion/luafiles/tools/init.lua | 2 + 2 files changed, 2 insertions(+), 212 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/items/plugin.lua diff --git a/plugins/Dfusion/luafiles/items/plugin.lua b/plugins/Dfusion/luafiles/items/plugin.lua deleted file mode 100644 index 8b92785bd..000000000 --- a/plugins/Dfusion/luafiles/items/plugin.lua +++ /dev/null @@ -1,212 +0,0 @@ -items={} --> first lets make a menu table -items.menu=MakeMenu() - - -function items.dest() - myoff=offsets.getEx("Items") -- first find out where "item vector" is - vector=engine.peek(myoff,ptr_vector) -- get list of items - for i=0,vector:size()-1 do --look at each item - flg=engine.peek(vector:getval(i),ptr_item.flags) - flg:set(17,1) - engine.poke(vector:getval(i),ptr_item.flags,flg) - end -end -function items.eggs() - myoff=offsets.getEx("Items") -- first find out where "item vector" is - vector=engine.peek(myoff,ptr_vector) -- get list of items - for i=0,vector:size()-1 do --look at each item - rti=engine.peek(vector:getval(i),ptr_item.RTI) - if ptr_item.getname(nil,rti)=="item_eggst" then - egg=engine.peek(vector:getval(i),ptr_subitems["item_eggst"]) - egg.isfertile=1 - egg.hatchtime=0xffffff - --egg.race=123 -- change race for fun times - engine.poke(vector:getval(i),ptr_subitems["item_eggst"],egg) - end - end -end -function editFlags(offset) - while true do - flags=engine.peek(offset,ptr_item.flags) - for i=0,8*8-1 do - if flags:get(i) then - print(i.." is true") - else - print(i.." is false") - end - end - print(" enter number to switch flag or not a number to quit:") - q=tonumber(io.stdin:read()) - if q==nil then return end - flags:flip(q) - engine.poke(offset,ptr_item.flags,flags) - end -end -function editCovering(offset) - off=engine.peek(offset,ptr_item.ptr_covering) - if off == 0 then - print("No coverings found.") - end - vec=engine.peek(off,ptr_vector) - print("Covering list:") - for i=0,vec:size()-1 do - cov=engine.peek(vec:getval(i),ptr_cover) - print(string.format("%d. mat=%d submat=%d state=%d",i,cov.mat,cov.submat,cov.state)) - end - print("To edit type number:") - q=tonumber(io.stdin:read()) - if q==nil then return end - if q>=vec:size() or q<0 then return end - off=vec:getval(q) - cov=engine.peek(off,ptr_cover) - print("Enter mat:") - q=tonumber(io.stdin:read()) - if q==nil then q=0xffff end - print("Enter submat:") - v=tonumber(io.stdin:read()) - if v==nil then v=0xffff end - print("Enter state:") - y=tonumber(io.stdin:read()) - if y==nil then y=0 end - cov.mat=q - cov.submat=v - cov.state=y - engine.poke(off,ptr_cover,cov) -end -function editMaterial(offset) - print("Mat id 0 to 18 is in normal materials (inorganic, amber etc...) after that creature mat (with submat2 being race)") - print("from 219 with submat2=0xffffffff (type not a number) it reads legends id, from 419 submat2 means plant id") - print("Probably submat is not used :? ") - mat=engine.peek(offset,ptr_item.mat) - submat=engine.peek(offset,ptr_item.submat) - submat2=engine.peek(offset,ptr_item.submat2) - lid=engine.peek(offset,ptr_item.legendid) - print(string.format("Now is mat=%d, submat=%d submat2=%d legend id=%d",mat,submat,submat2,lid)) - print("Enter mat:") - q=tonumber(io.stdin:read()) - if q==nil then return end - print("Enter submat:") - v=tonumber(io.stdin:read()) - if v==nil then v=0xffff end - print("Enter submat2:") - z=tonumber(io.stdin:read()) - if z==nil then z=0xffffffff end - print("Enter legendid:") - y=tonumber(io.stdin:read()) - if y==nil then y=0xffffffff end - engine.poke(offset,ptr_item.mat,q) - engine.poke(offset,ptr_item.submat,v) - engine.poke(offset,ptr_item.legendid,y) - engine.poke(offset,ptr_item.submat2,z) - print("Done") -end -function items.select() - myoff=offsets.getEx("Items") - vector=engine.peek(myoff,ptr_vector) - tx,ty,tz=getxyz() - T={} - for i=0,vector:size()-1 do --this finds all item offsets that are on pointer - itoff=vector:getval(i) - x=engine.peek(itoff,ptr_item.x) - y=engine.peek(itoff,ptr_item.y) - z=engine.peek(itoff,ptr_item.z) - if x==tx and y==ty and z==tz then - table.insert(T,itoff) - end - end - print("Items under cursor:") - i=1 - for _,v in pairs(T) do - RTI=engine.peek(v,ptr_item.RTI) - print(i..". "..ptr_item.getname(nil,RTI)) - i=i+1 - end - print("Type number to edit or 'q' to exit") - while true do - q=io.stdin:read() - if q=='q' then return end - if tonumber(q) ~=nil and tonumber(q) Date: Sun, 23 Sep 2012 23:22:14 +0300 Subject: [PATCH 016/472] gm-editor fixes and improvements --- scripts/gui/gm-editor.lua | 341 +++++++++++++++++++------------------- 1 file changed, 172 insertions(+), 169 deletions(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index d95cb652b..44345b3e8 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -1,5 +1,4 @@ -- Interface powered item editor. --- TODO use this: MechanismList = defclass(MechanismList, guidm.MenuOverlay) local gui = require 'gui' local dialog = require 'gui.dialogs' @@ -40,177 +39,181 @@ end local MODE_BROWSE=0 local MODE_EDIT=1 -local item_screen={ +GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) +GmEditorUi.ATTRS={ frame_style = gui.GREY_LINE_FRAME, frame_title = "GameMaster's editor", - stack={}, - item_count=0, - mode=MODE_BROWSE, - - keys={}, - - insertNew=function(self,typename) - local tp=typename - if typename== nil then - dialog.showInputPrompt("Class type","Input class type\n:",COLOR_WHITE,"",dfhack.curry(self.insertNew,self)) - return - end - local ntype=df[tp] - if ntype== nil then - dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) - return - end - - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - local thing=ntype:new() - dfhack.call_with_finalizer(1,false,df.delete,thing,trg.target.insert,trg.target,'#',thing) - - end - end, - deleteSelected=function(self) - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="container" then - trg.target:erase(trg.keys[trg.selected]) - end - end, - currentTarget=function(self) - return self.stack[#self.stack] - end, - changeSelected = function (self,delta) - local trg=self:currentTarget() - if trg.item_count <= 1 then return end - trg.selected = 1 + (trg.selected + delta - 1) % trg.item_count - end, - editSelected = function(self) - local trg=self:currentTarget() - if trg.target and trg.target._kind and trg.target._kind=="bitfield" then - trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] - else - --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") - local trg_type=type(trg.target[trg.keys[trg.selected]]) - if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected - self.mode=MODE_EDIT - self.input=tostring(trg.target[trg.keys[trg.selected]]) - elseif trg_type=='boolean' then - trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] - elseif trg_type=='userdata' then - self:pushTarget(trg.target[trg.keys[trg.selected]]) - --local screen = mkinstance(gui.FramedScreen,item_screen):init(trg.target[trg.keys[trg.selected]]) -- does not work - --screen:show() - else - print("Unknow type:"..trg_type) - print("Subtype:"..tostring(trg.target[trg.keys[trg.selected]]._kind)) - end - end - end, - cancelEdit = function(self) - self.mode=MODE_BROWSE - self.input="" - end, - commitEdit = function(self) - local trg=self:currentTarget() - self.mode=MODE_BROWSE - if type(trg.target[trg.keys[trg.selected]])=='number' then - trg.target[trg.keys[trg.selected]]=tonumber(self.input) - elseif type(trg.target[trg.keys[trg.selected]])=='string' then - trg.target[trg.keys[trg.selected]]=self.input - end - end, - onRenderBody = function(self, dc) - local trg=self:currentTarget() - dc:seek(2,1):string(tostring(trg.target), COLOR_RED) - local offset=2 - local page_offset=0 - local current_item=1 - local t_col - if math.floor(trg.selected / (self.frame_height-offset-2)) >0 then - page_offset=math.floor(trg.selected / (self.frame_height-offset-2))*(self.frame_height-offset-2)-1 - end - for k,v in pairs(trg.target) do - - if current_item==trg.selected then - t_col=COLOR_LIGHTGREEN - else - t_col=COLOR_GRAY - end - - if current_item-page_offset > 0 then - local y_pos=current_item-page_offset+offset - dc:seek(2,y_pos):string(tostring(k),t_col) - - if self.mode==MODE_EDIT and current_item==trg.selected then - dc:seek(20,y_pos):string(self.input..'_',COLOR_GREEN) - else - dc:seek(20,y_pos):string(tostring(v),t_col) - end - end - current_item=current_item+1 - end - end, - - onInput = function(self,keys) - if self.mode==MODE_BROWSE then - if keys.LEAVESCREEN then - self:popTarget() - elseif keys.CURSOR_UP then - self:changeSelected(-1) - elseif keys.CURSOR_DOWN then - self:changeSelected(1) - elseif keys.CURSOR_UP_FAST then - self:changeSelected(-10) - elseif keys.CURSOR_DOWN_FAST then - self:changeSelected(10) - elseif keys.SELECT then - self:editSelected() - elseif keys.CUSTOM_ALT_E then - --self:specialEditor() - local screen = mkinstance(TextInputDialog):init("Input new coordinates") - screen:show() - elseif keys.CUSTOM_ALT_I then --insert - self:insertNew() - elseif keys.CUSTOM_ALT_D then --delete - self:deleteSelected() - end - elseif self.mode==MODE_EDIT then - if keys.LEAVESCREEN then - self:cancelEdit() - elseif keys.SELECT then - self:commitEdit() - elseif keys._STRING then - if keys._STRING==0 then - self.input=string.sub(self.input,1,-2) - else - self.input=self.input.. string.char(keys._STRING) - end - end - end - end, - pushTarget=function(self,target_to_push) - local new_tbl={} - new_tbl.target=target_to_push - new_tbl.keys={} - new_tbl.selected=1 - for k,v in pairs(target_to_push) do - table.insert(new_tbl.keys,k) - end - new_tbl.item_count=#new_tbl.keys - table.insert(self.stack,new_tbl) - end, - popTarget=function(self) - table.remove(self.stack) --removes last element - if #self.stack==0 then - self:dismiss() - end - end, - init = function(self,item_to_edit) - self:pushTarget(item_to_edit) - self.frame_width,self.frame_height=dfhack.screen.getWindowSize() - return self - end } +function GmEditorUi:init(args) + self.stack={} + self.item_count=0 + self.mode=MODE_BROWSE + self.keys={} + self:pushTarget(args.target) + + return self +end +function GmEditorUi:insertNew(typename) + local tp=typename + if typename== nil then + dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",dfhack.curry(self.insertNew,self)) + return + end + local ntype=df[tp] + if ntype== nil then + dialog.showMessage("Error!","Type '"..tp.." not found",COLOR_RED) + return + end + + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="container" then + local thing=ntype:new() + dfhack.call_with_finalizer(1,false,df.delete,thing,trg.target.insert,trg.target,'#',thing) + + end +end +function GmEditorUi:deleteSelected() + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="container" then + trg.target:erase(trg.keys[trg.selected]) + end +end +function GmEditorUi:currentTarget() + return self.stack[#self.stack] +end +function GmEditorUi:changeSelected(delta) + local trg=self:currentTarget() + if trg.item_count <= 1 then return end + trg.selected = 1 + (trg.selected + delta - 1) % trg.item_count +end +function GmEditorUi:editSelected() + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="bitfield" then + trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] + else + --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") + local trg_type=type(trg.target[trg.keys[trg.selected]]) + if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected + self.mode=MODE_EDIT + self.input=tostring(trg.target[trg.keys[trg.selected]]) + elseif trg_type=='boolean' then + trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] + elseif trg_type=='userdata' then + self:pushTarget(trg.target[trg.keys[trg.selected]]) + --local screen = mkinstance(gui.FramedScreen,GmEditorUi):init(trg.target[trg.keys[trg.selected]]) -- does not work + --screen:show() + else + print("Unknow type:"..trg_type) + print("Subtype:"..tostring(trg.target[trg.keys[trg.selected]]._kind)) + end + end +end +function GmEditorUi:cancelEdit() + self.mode=MODE_BROWSE + self.input="" +end +function GmEditorUi:commitEdit() + local trg=self:currentTarget() + self.mode=MODE_BROWSE + if type(trg.target[trg.keys[trg.selected]])=='number' then + trg.target[trg.keys[trg.selected]]=tonumber(self.input) + elseif type(trg.target[trg.keys[trg.selected]])=='string' then + trg.target[trg.keys[trg.selected]]=self.input + end +end +function GmEditorUi:onRenderBody( dc) + local trg=self:currentTarget() + dc:seek(2,1):string(tostring(trg.target), COLOR_RED) + local offset=2 + local page_offset=0 + local current_item=1 + local t_col + local width,height=self:getWindowSize() + local window_height=height-offset-2 + local cursor_window=math.floor(trg.selected / window_height) + if cursor_window>0 then + page_offset=cursor_window*window_height-1 + end + for k,v in pairs(trg.target) do + + if current_item==trg.selected then + t_col=COLOR_LIGHTGREEN + else + t_col=COLOR_GRAY + end + + if current_item-page_offset > 0 then + local y_pos=current_item-page_offset+offset + dc:seek(2,y_pos):string(tostring(k),t_col) + + if self.mode==MODE_EDIT and current_item==trg.selected then + dc:seek(20,y_pos):string(self.input..'_',COLOR_GREEN) + else + dc:seek(20,y_pos):string(tostring(v),t_col) + end + if y_pos+3>height then + break + end + end + current_item=current_item+1 + + end +end + function GmEditorUi:onInput(keys) + if self.mode==MODE_BROWSE then + if keys.LEAVESCREEN then + self:popTarget() + elseif keys.CURSOR_UP then + self:changeSelected(-1) + elseif keys.CURSOR_DOWN then + self:changeSelected(1) + elseif keys.CURSOR_UP_FAST then + self:changeSelected(-10) + elseif keys.CURSOR_DOWN_FAST then + self:changeSelected(10) + elseif keys.SELECT then + self:editSelected() + elseif keys.CUSTOM_ALT_E then + --self:specialEditor() + local screen = mkinstance(TextInputDialog):init("Input new coordinates") + screen:show() + elseif keys.CUSTOM_ALT_I then --insert + self:insertNew() + elseif keys.CUSTOM_ALT_D then --delete + self:deleteSelected() + end + elseif self.mode==MODE_EDIT then + if keys.LEAVESCREEN then + self:cancelEdit() + elseif keys.SELECT then + self:commitEdit() + elseif keys._STRING then + if keys._STRING==0 then + self.input=string.sub(self.input,1,-2) + else + self.input=self.input.. string.char(keys._STRING) + end + end + end +end +function GmEditorUi:pushTarget(target_to_push) + local new_tbl={} + new_tbl.target=target_to_push + new_tbl.keys={} + new_tbl.selected=1 + for k,v in pairs(target_to_push) do + table.insert(new_tbl.keys,k) + end + new_tbl.item_count=#new_tbl.keys + table.insert(self.stack,new_tbl) +end +function GmEditorUi:popTarget() + table.remove(self.stack) --removes last element + if #self.stack==0 then + self:dismiss() + end +end +local screen = GmEditorUi{target=my_trg} - -local screen = mkinstance(gui.FramedScreen,item_screen):init(my_trg) screen:show() \ No newline at end of file From 39df1e0eceef0e6eafcb47f6274b26557f02ef61 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 23 Sep 2012 23:23:12 +0300 Subject: [PATCH 017/472] Removed unused stuff from editor --- scripts/gui/gm-editor.lua | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 44345b3e8..a86a9f4a8 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -25,18 +25,6 @@ else qerror("No valid target found") end -TextInputDialog = defclass(TextInputDialog, gui.FramedScreen) - -function TextInputDialog:init(prompt) - self.frame_style=GREY_LINE_FRAME - self.frame_title=prompt - self.input="" - return self -end -function TextInputDialog:onRenderBody(dc) - dc:seek(1,1):string(self.input, COLOR_WHITE):newline() -end - local MODE_BROWSE=0 local MODE_EDIT=1 GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) @@ -175,8 +163,6 @@ end self:editSelected() elseif keys.CUSTOM_ALT_E then --self:specialEditor() - local screen = mkinstance(TextInputDialog):init("Input new coordinates") - screen:show() elseif keys.CUSTOM_ALT_I then --insert self:insertNew() elseif keys.CUSTOM_ALT_D then --delete From 28354715fff082194499fabe7a02a37b72a53d71 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 23 Sep 2012 23:45:19 +0300 Subject: [PATCH 018/472] Editor with dialog mode (no without switching from/to console to edit anything!) --- scripts/gui/gm-editor.lua | 67 +++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 24 deletions(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index a86a9f4a8..309663bdf 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -1,30 +1,34 @@ -- Interface powered item editor. local gui = require 'gui' local dialog = require 'gui.dialogs' +local args={...} +function getTargetFromScreens() + local my_trg + if dfhack.gui.getCurFocus() == 'item' then + my_trg=dfhack.gui.getCurViewscreen().item + elseif dfhack.gui.getCurFocus() == 'joblist' then + local t_screen=dfhack.gui.getCurViewscreen() + my_trg=t_screen.jobs[t_screen.cursor_pos] + elseif dfhack.gui.getCurFocus() == 'createquota' then + local t_screen=dfhack.gui.getCurViewscreen() + my_trg=t_screen.orders[t_screen.sel_idx] + elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then + local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor] + my_trg=t_look.flow -local my_trg -if dfhack.gui.getCurFocus() == 'item' then - my_trg=dfhack.gui.getCurViewscreen().item -elseif dfhack.gui.getCurFocus() == 'joblist' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.jobs[t_screen.cursor_pos] -elseif dfhack.gui.getCurFocus() == 'createquota' then - local t_screen=dfhack.gui.getCurViewscreen() - my_trg=t_screen.orders[t_screen.sel_idx] -elseif dfhack.gui.getCurFocus() == 'dwarfmode/LookAround/Flow' then - local t_look=df.global.ui_look_list.items[df.global.ui_look_cursor] - my_trg=t_look.flow - -elseif dfhack.gui.getSelectedUnit(true) then - my_trg=dfhack.gui.getSelectedUnit(true) -elseif dfhack.gui.getSelectedItem(true) then - my_trg=dfhack.gui.getSelectedItem(true) -elseif dfhack.gui.getSelectedJob(true) then - my_trg=dfhack.gui.getSelectedJob(true) -else - qerror("No valid target found") + elseif dfhack.gui.getSelectedUnit(true) then + my_trg=dfhack.gui.getSelectedUnit(true) + elseif dfhack.gui.getSelectedItem(true) then + my_trg=dfhack.gui.getSelectedItem(true) + elseif dfhack.gui.getSelectedJob(true) then + my_trg=dfhack.gui.getSelectedJob(true) + else + qerror("No valid target found") + end + return my_trg end + local MODE_BROWSE=0 local MODE_EDIT=1 GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) @@ -199,7 +203,22 @@ function GmEditorUi:popTarget() self:dismiss() end end +function show_editor(trg) + local screen = GmEditorUi{target=trg} + screen:show() +end +if #args~=0 then + if args[1]=="dialog" then + function thunk(entry) + local t=load("return "..entry)() + show_editor(t) + end + dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) + else + local t=load("return "..args[1])() + show_editor(t) + end +else + show_editor(getTargetFromScreens()) +end -local screen = GmEditorUi{target=my_trg} - -screen:show() \ No newline at end of file From bd2f3a9998c72d85d663da44fbdb30702b3e0c6d Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 25 Sep 2012 00:24:37 +0300 Subject: [PATCH 019/472] Moved lua out of dfusion. Now lua is a script. supports --file (or -f) flag, usage: lua or lua --file or just "lua" for interactive interpreter. --- plugins/Dfusion/dfusion.cpp | 62 ------------------------------------- scripts/lua.lua | 10 ++++++ 2 files changed, 10 insertions(+), 62 deletions(-) create mode 100644 scripts/lua.lua diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 78c3fa8d1..6c698f58a 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -33,8 +33,6 @@ DFHACK_PLUGIN("dfusion") command_result dfusion (color_ostream &out, std::vector ¶meters); command_result dfuse (color_ostream &out, std::vector ¶meters); -command_result lua_run (color_ostream &out, std::vector ¶meters); -command_result lua_run_file (color_ostream &out, std::vector ¶meters); DFhackCExport const char * plugin_name ( void ) { @@ -65,8 +63,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ' to run instead.",lua_run,true)); - commands.push_back(PluginCommand("runlua", "Run non-interactive interpreter. Use 'runlua ' to run .",lua_run_file,false)); mymutex=new tthread::mutex; return CR_OK; } @@ -107,64 +103,6 @@ DFhackCExport command_result plugin_onupdate_DISABLED ( Core * c ) mymutex->unlock(); return CR_OK; } -command_result lua_run_file (color_ostream &out, std::vector ¶meters) -{ - if(parameters.size()==0) - { - out.printerr("runlua without file to run!"); - return CR_FAILURE; - } - return lua_run(out,parameters); -} -command_result lua_run (color_ostream &out, std::vector ¶meters) -{ - if (!parameters.empty()) - { - if (parameters[0] == "--core-context") - { - Lua::InterpreterLoop(out, Lua::Core::State, "core lua"); - return CR_OK; - } - else if (parameters[0] == "--core-reload") - { - CoreSuspender suspend; - - for (size_t i = 1; i < parameters.size(); i++) - { - lua_getglobal(Lua::Core::State, "reload"); - lua_pushstring(Lua::Core::State, parameters[i].c_str()); - Lua::SafeCall(out, Lua::Core::State, 1, 0); - } - - return CR_OK; - } - } - - mymutex->lock(); - lua::state s=lua::glua::Get(); - - if(parameters.size()>0) - { - try{ - s.loadfile(parameters[0]); //load file - for(size_t i=1;iunlock(); - return CR_OK; -} void RunDfusion(color_ostream &out, std::vector ¶meters) { mymutex->lock(); diff --git a/scripts/lua.lua b/scripts/lua.lua new file mode 100644 index 000000000..c2033b7e6 --- /dev/null +++ b/scripts/lua.lua @@ -0,0 +1,10 @@ +local args={...} +if args[1]=="--file" or args[1]=="-f" then + local f=loadfile (args[2]) + dfhack.pcall(f,table.unpack(args,3)) +elseif args[1]~=nil then + local f=load(args[1],'=(lua command)', 't',) + dfhack.pcall(f,table.unpack(args,2)) +else + dfhack.interpreter("lua","lua.history") +end \ No newline at end of file From cc5df57e53bf89b8cce51353c0a8b4f048d23679 Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 25 Sep 2012 10:24:45 +0300 Subject: [PATCH 020/472] Little error fixed in lua script --- scripts/lua.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lua.lua b/scripts/lua.lua index c2033b7e6..497498e86 100644 --- a/scripts/lua.lua +++ b/scripts/lua.lua @@ -3,7 +3,7 @@ if args[1]=="--file" or args[1]=="-f" then local f=loadfile (args[2]) dfhack.pcall(f,table.unpack(args,3)) elseif args[1]~=nil then - local f=load(args[1],'=(lua command)', 't',) + local f=load(args[1],'=(lua command)', 't') dfhack.pcall(f,table.unpack(args,2)) else dfhack.interpreter("lua","lua.history") From 0bee8c360e1ad76ea68352e5d1806f79f910cd66 Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 25 Sep 2012 10:25:47 +0300 Subject: [PATCH 021/472] Reaction hooks experimentation. --- plugins/CMakeLists.txt | 1 + plugins/lua/reactionhooks.lua | 13 ++ plugins/reactionhooks.cpp | 319 ++++++++++++++++++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 plugins/lua/reactionhooks.lua create mode 100644 plugins/reactionhooks.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 0b0ad0461..4d4f7493f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -123,6 +123,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(reactionhooks reactionhooks.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(add-spatter add-spatter.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) diff --git a/plugins/lua/reactionhooks.lua b/plugins/lua/reactionhooks.lua new file mode 100644 index 000000000..5f3622e2f --- /dev/null +++ b/plugins/lua/reactionhooks.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.reactionhooks') + +--[[ + + Native events: + + * onReactionComplete(burrow) + +--]] + +rawset_default(_ENV, dfhack.reactionhooks) + +return _ENV \ No newline at end of file diff --git a/plugins/reactionhooks.cpp b/plugins/reactionhooks.cpp new file mode 100644 index 000000000..4041b99a5 --- /dev/null +++ b/plugins/reactionhooks.cpp @@ -0,0 +1,319 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/item_liquid_miscst.h" +#include "df/item_constructed.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/job_item_ref.h" +#include "df/ui.h" +#include "df/report.h" +#include "df/reaction.h" +#include "df/reaction_reagent_itemst.h" +#include "df/reaction_product_itemst.h" +#include "df/matter_state.h" +#include "df/contaminant.h" + +#include "MiscUtils.h" +#include "LuaTools.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::world; +using df::global::ui; + +typedef df::reaction_product_itemst item_product; + +DFHACK_PLUGIN("reactionhooks"); + +struct ReagentSource { + int idx; + df::reaction_reagent *reagent; + + ReagentSource() : idx(-1), reagent(NULL) {} +}; + +struct MaterialSource : ReagentSource { + bool product; + std::string product_name; + + int mat_type, mat_index; + + MaterialSource() : product(false), mat_type(-1), mat_index(-1) {} +}; + +struct ProductInfo { + df::reaction *react; + item_product *product; + + MaterialSource material; + + bool isValid() { + return (material.mat_type >= 0 || material.reagent); + } +}; + +struct ReactionInfo { + df::reaction *react; + + std::vector products; +}; + +static std::map reactions; +static std::map products; + +static ReactionInfo *find_reaction(const std::string &name) +{ + auto it = reactions.find(name); + return (it != reactions.end()) ? &it->second : NULL; +} + +static bool is_lua_hook(const std::string &name) +{ + return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0; +} + +static void find_material(int *type, int *index, df::item *input, MaterialSource &mat) +{ + if (input && mat.reagent) + { + MaterialInfo info(input); + + if (mat.product) + { + if (!info.findProduct(info, mat.product_name)) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + out.printerr("Cannot find product '%s'\n", mat.product_name.c_str()); + } + } + + *type = info.type; + *index = info.index; + } + else + { + *type = mat.mat_type; + *index = mat.mat_index; + } +} + + +/* + * Hooks + */ + +typedef std::map > item_table; + +static void index_items(item_table &table, df::job *job, ReactionInfo *info) +{ + for (int i = job->items.size()-1; i >= 0; i--) + { + auto iref = job->items[i]; + if (iref->job_item_idx < 0) continue; + auto iitem = job->job_items[iref->job_item_idx]; + + if (iitem->contains.empty()) + { + table[iitem->reagent_index].push_back(iref->item); + } + else + { + std::vector contents; + Items::getContainedItems(iref->item, &contents); + + for (int j = contents.size()-1; j >= 0; j--) + { + for (int k = iitem->contains.size()-1; k >= 0; k--) + { + int ridx = iitem->contains[k]; + auto reag = info->react->reagents[ridx]; + + if (reag->matchesChild(contents[j], info->react, iitem->reaction_id)) + table[ridx].push_back(contents[j]); + } + } + } + } +} + +df::item* find_item(ReagentSource &info, item_table &table) +{ + if (!info.reagent) + return NULL; + if (table[info.idx].empty()) + return NULL; + return table[info.idx].back(); +} + + + +df::item* find_item( + ReagentSource &info, + std::vector *in_reag, + std::vector *in_items +) { + if (!info.reagent) + return NULL; + for (int i = in_items->size(); i >= 0; i--) + if ((*in_reag)[i] == info.reagent) + return (*in_items)[i]; + return NULL; +} + +static void handle_reaction_done(color_ostream &out, df::unit *unit, std::vector *in_items, std::vector *out_items,bool *call_native){}; + +DEFINE_LUA_EVENT_4(onReactionComplete, handle_reaction_done, df::unit *, std::vector *,std::vector *,bool *); + + +DFHACK_PLUGIN_LUA_EVENTS { + DFHACK_LUA_EVENT(onReactionComplete), + DFHACK_LUA_END +}; + +struct product_hook : item_product { + typedef item_product interpose_base; + + DEFINE_VMETHOD_INTERPOSE( + void, produce, + (df::unit *unit, std::vector *out_items, + std::vector *in_reag, + std::vector *in_items, + int32_t quantity, int16_t skill, + df::historical_entity *entity, df::world_site *site) + ) { + if (auto product = products[this]) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + bool call_native=true; + onReactionComplete(out,unit,in_items,out_items,&call_native); + if(!call_native) + return; + } + + INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce); + + + + + +/* + * Scan raws for matching reactions. + */ + + +static void parse_product( + color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod + ) { + info.react = react; + info.product = prod; + info.material.mat_type = prod->mat_type; + info.material.mat_index = prod->mat_index; +} + +static bool find_reactions(color_ostream &out) +{ + reactions.clear(); + + auto &rlist = world->raws.reactions; + + for (size_t i = 0; i < rlist.size(); i++) + { + if (!is_lua_hook(rlist[i]->code)) + continue; + reactions[rlist[i]->code].react = rlist[i]; + } + + for (auto it = reactions.begin(); it != reactions.end(); ++it) + { + auto &prod = it->second.react->products; + auto &out_prod = it->second.products; + + for (size_t i = 0; i < prod.size(); i++) + { + auto itprod = strict_virtual_cast(prod[i]); + if (!itprod) continue; + + out_prod.push_back(ProductInfo()); + parse_product(out, out_prod.back(), it->second.react, itprod); + } + + for (size_t i = 0; i < prod.size(); i++) + { + if (out_prod[i].isValid()) + products[out_prod[i].product] = &out_prod[i]; + } + } + + return !products.empty(); +} + +static void enable_hooks(bool enable) +{ + INTERPOSE_HOOK(product_hook, produce).apply(enable); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_WORLD_LOADED: + if (find_reactions(out)) + { + out.print("Detected reaction hooks - enabling plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + break; + case SC_WORLD_UNLOADED: + enable_hooks(false); + reactions.clear(); + products.clear(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (Core::getInstance().isWorldLoaded()) + plugin_onstatechange(out, SC_WORLD_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + enable_hooks(false); + return CR_OK; +} From ddc83a0a72edb7c23abe8f08daa4750ead3dc5ea Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 25 Sep 2012 11:30:38 +0300 Subject: [PATCH 022/472] Another dfusion nuking (not much left :) ) --- plugins/Dfusion/dfusion.cpp | 11 +- plugins/Dfusion/include/functioncall.h | 26 --- plugins/Dfusion/include/lua_FunctionCall.h | 14 -- plugins/Dfusion/include/lua_Misc.h | 1 - plugins/Dfusion/include/lua_Offsets.h | 13 -- plugins/Dfusion/include/lua_VersionInfo.h | 12 -- plugins/Dfusion/src/functioncall.cpp | 121 ------------- plugins/Dfusion/src/lua_FunctionCall.cpp | 39 ----- plugins/Dfusion/src/lua_Misc.cpp | 99 +---------- plugins/Dfusion/src/lua_Offsets.cpp | 190 --------------------- plugins/Dfusion/src/lua_VersionInfo.cpp | 115 ------------- 11 files changed, 5 insertions(+), 636 deletions(-) delete mode 100644 plugins/Dfusion/include/functioncall.h delete mode 100644 plugins/Dfusion/include/lua_FunctionCall.h delete mode 100644 plugins/Dfusion/include/lua_Offsets.h delete mode 100644 plugins/Dfusion/include/lua_VersionInfo.h delete mode 100644 plugins/Dfusion/src/functioncall.cpp delete mode 100644 plugins/Dfusion/src/lua_FunctionCall.cpp delete mode 100644 plugins/Dfusion/src/lua_Offsets.cpp delete mode 100644 plugins/Dfusion/src/lua_VersionInfo.cpp diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 6c698f58a..0f49e860d 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -14,10 +14,8 @@ #include "lua_Process.h" #include "lua_Hexsearch.h" #include "lua_Misc.h" -#include "lua_VersionInfo.h" -#include "functioncall.h" -#include "lua_FunctionCall.h" -#include "lua_Offsets.h" + + #include "DataDefs.h" #include "LuaTools.h" @@ -43,15 +41,12 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector -using std::vector; -using std::size_t; -class FunctionCaller -{ -public: - enum callconv - { - STD_CALL, //__stdcall - all in stack - FAST_CALL, //__fastcall - as much in registers as fits - THIS_CALL, //__thiscall - eax ptr to this, rest in stack - CDECL_CALL //__cdecl - same as stdcall but no stack realign - }; - - FunctionCaller(size_t base):base_(base){}; - - int CallFunction(size_t func_ptr,callconv conv,const vector &arguments); - -private: - int CallF(size_t count,callconv conv,void* f,const vector &arguments); - size_t base_; -}; - -#endif //FUNCTIONCALL__H \ No newline at end of file diff --git a/plugins/Dfusion/include/lua_FunctionCall.h b/plugins/Dfusion/include/lua_FunctionCall.h deleted file mode 100644 index bc1af5685..000000000 --- a/plugins/Dfusion/include/lua_FunctionCall.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef LUA_FUNCTIONCALL__H -#define LUA_FUNCTIONCALL__H - -#include "luamain.h" -#include "functioncall.h" - -namespace lua -{ - - -void RegisterFunctionCall(lua::state &st); - -} -#endif \ No newline at end of file diff --git a/plugins/Dfusion/include/lua_Misc.h b/plugins/Dfusion/include/lua_Misc.h index b39d60214..ff4611456 100644 --- a/plugins/Dfusion/include/lua_Misc.h +++ b/plugins/Dfusion/include/lua_Misc.h @@ -7,7 +7,6 @@ #include #include "luamain.h" #include "OutFile.h" -#include "functioncall.h" #include "LuaTools.h" namespace lua diff --git a/plugins/Dfusion/include/lua_Offsets.h b/plugins/Dfusion/include/lua_Offsets.h deleted file mode 100644 index 8d6a94b95..000000000 --- a/plugins/Dfusion/include/lua_Offsets.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef LUA_OFFSETS_H -#define LUA_OFFSETS_H -#include "luamain.h" - -namespace lua -{ - -void RegisterEngine(lua::state &st); - -} - - -#endif \ No newline at end of file diff --git a/plugins/Dfusion/include/lua_VersionInfo.h b/plugins/Dfusion/include/lua_VersionInfo.h deleted file mode 100644 index a4d7ed658..000000000 --- a/plugins/Dfusion/include/lua_VersionInfo.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef LUA_VERSIONINFO_H -#define LUA_VERSIONINFO_H -#include "Core.h" -#include -#include "luamain.h" -namespace lua -{ - -void RegisterVersionInfo(lua::state &st); - -} -#endif diff --git a/plugins/Dfusion/src/functioncall.cpp b/plugins/Dfusion/src/functioncall.cpp deleted file mode 100644 index b6c38c928..000000000 --- a/plugins/Dfusion/src/functioncall.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include "functioncall.h" - -#ifdef LINUX_BUILD -#define __F_TDEF(CONV,CONV_,tag) typedef int(__attribute__ ( (CONV_) ) *F_TYPE##CONV##tag) -#else -#define __F_TDEF(CONV,CONV_,tag) typedef int(__ ## CONV_ *F_TYPE##CONV##tag) -#endif - -#define __F_T(CONV,tag) F_TYPE##CONV##tag -#define __F_TYPEDEFS(CONV,CONV_) __F_TDEF(CONV,CONV_,1)(int);\ - __F_TDEF(CONV,CONV_,2)(int,int);\ - __F_TDEF(CONV,CONV_,3)(int,int,int);\ - __F_TDEF(CONV,CONV_,4)(int,int,int,int);\ - __F_TDEF(CONV,CONV_,5)(int,int,int,int,int);\ - __F_TDEF(CONV,CONV_,6)(int,int,int,int,int,int);\ - __F_TDEF(CONV,CONV_,7)(int,int,int,int,int,int,int) - - -#define __FCALL(CONV,CONV_) if(conv==CONV)\ - { \ - if(count==1)\ - ret= (reinterpret_cast<__F_T(CONV,1)>(f))\ - (arguments[0]);\ - else if(count==2)\ - ret= (reinterpret_cast<__F_T(CONV,2)>(f))\ - (arguments[0],arguments[1]);\ - else if(count==3)\ - ret= (reinterpret_cast<__F_T(CONV,3)>(f))\ - (arguments[0],arguments[1],arguments[2]);\ - else if(count==4)\ - ret= (reinterpret_cast<__F_T(CONV,4)>(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3]);\ - else if(count==5)\ - ret= (reinterpret_cast<__F_T(CONV,5)>(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4]);\ - else if(count==6)\ - ret= (reinterpret_cast<__F_T(CONV,6)>(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]);\ - else if(count==7)\ - ret= (reinterpret_cast<__F_T(CONV,7)>(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6]);\ - } - -#define __FCALLEX(CONV,CONV_) if(conv==CONV)\ - { if(count==1) {__F_T(CONV,1) tmp_F=reinterpret_cast<__F_T(CONV,1)>(f); return tmp_F(arguments[0]);}\ - else if(count==2){__F_T(CONV,2) tmp_F=reinterpret_cast<__F_T(CONV,2)>(f); return tmp_F(arguments[0],arguments[1]);}\ - else if(count==3){__F_T(CONV,3) tmp_F=reinterpret_cast<__F_T(CONV,3)>(f); return tmp_F(arguments[0],arguments[1],arguments[2]);}\ - else if(count==4){__F_T(CONV,4) tmp_F=reinterpret_cast<__F_T(CONV,4)>(f); return tmp_F(arguments[0],arguments[1],arguments[2],arguments[3]);}\ - else if(count==5){__F_T(CONV,5) tmp_F=reinterpret_cast<__F_T(CONV,5)>(f); return tmp_F(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4]);}\ - else if(count==6){__F_T(CONV,6) tmp_F=reinterpret_cast<__F_T(CONV,6)>(f); return tmp_F(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]);}\ - else if(count==7){__F_T(CONV,7) tmp_F=reinterpret_cast<__F_T(CONV,7)>(f); return tmp_F(arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6]);}\ - } - /*else if(count==8)\ - ret= (reinterpret_cast<__F_T(CONV,8)>(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7]);\ - else if(count==9)\ - ret= (reinterpret_cast(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8]);\ - else if(count==10)\ - ret= (reinterpret_cast(f))\ - (arguments[0],arguments[1],arguments[2],arguments[3],arguments[4],arguments[5],arguments[6],arguments[7],arguments[8],arguments[9]);}*/ - - -/*int FunctionCaller::CallF(size_t count,callconv conv,void* f,const vector &arguments)//more complex but not more error safe -{ - __F_TYPEDEFS(STD_CALL,__stdcall); - __F_TYPEDEFS(FAST_CALL,__fastcall); - __F_TYPEDEFS(THIS_CALL,__thiscall); - __F_TYPEDEFS(CDECL_CALL,__cdecl); - { - __FCALLEX(STD_CALL,__stdcall); - __FCALLEX(FAST_CALL,__fastcall); - __FCALLEX(THIS_CALL,__thiscall); - __FCALLEX(CDECL_CALL,__cdecl); - } -}*/ -int FunctionCaller::CallFunction(size_t func_ptr,callconv conv,const vector &arguments) -{ - //nasty nasty code... -#ifdef LINUX_BUILD //quick fix - if(conv==THIS_CALL) - conv=STD_CALL; -#endif - void* f= reinterpret_cast(func_ptr+base_); - size_t count=arguments.size(); - if(count==0) - return (reinterpret_cast(f))(); //does not matter how we call it... - int ret=0; - //typedefs - __F_TYPEDEFS(STD_CALL,stdcall); - __F_TYPEDEFS(FAST_CALL,fastcall); - __F_TYPEDEFS(THIS_CALL,thiscall); - __F_TYPEDEFS(CDECL_CALL,cdecl); - //calls - __FCALL(STD_CALL,stdcall); - __FCALL(FAST_CALL,fastcall); - __FCALL(THIS_CALL,thiscall); - __FCALL(CDECL_CALL,cdecl); - return -1; //incorect type. Should probably throw... - //return CallF(count,conv,f,arguments); - /*//testing part{ worked some time ago..., put where DFHack::Core is accesible - c->Suspend(); - FunctionCaller caller(c->p->getBase()); - std::vector args; - args.push_back((size_t)"Hello world"); - args.push_back(4); - args.push_back(4); - args.push_back(0); - dfprint mprint=(dfprint)(0x27F030+c->p->getBase()); - mprint("Hello world",4,4,0); - //caller.CallFunction((0x27F030),FunctionCaller::THIS_CALL,args); - c->Resume(); - return CR_OK; - //}end testing*/ -} -#undef __FCALL -#undef __FCALLEX -#undef __F_TYPEDEFS -#undef __F_T diff --git a/plugins/Dfusion/src/lua_FunctionCall.cpp b/plugins/Dfusion/src/lua_FunctionCall.cpp deleted file mode 100644 index a537edbb5..000000000 --- a/plugins/Dfusion/src/lua_FunctionCall.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include "lua_FunctionCall.h" -using namespace lua; -int lua_FunctionCall(lua_State *L) -{ - lua::state st(L); - FunctionCaller cl(0); - size_t ptr=st.as(1); - int callconv=st.as(2); - vector arguments; - for(int i=3;i(i)); - int ret=cl.CallFunction(ptr,(FunctionCaller::callconv)callconv,arguments); - st.push(ret); - return 1;// dunno if len is needed... -} - -const luaL_Reg lua_functioncall_func[]= -{ - {"call",lua_FunctionCall}, - {NULL,NULL} -}; - -#define __ADDCONST(name) st.push(::FunctionCaller:: name); st.setglobal(#name) -void lua::RegisterFunctionCall(lua::state &st) -{ - st.getglobal("FunctionCall"); - if(st.is()) - { - st.pop(); - st.newtable(); - } - __ADDCONST(STD_CALL); - __ADDCONST(FAST_CALL); - __ADDCONST(THIS_CALL); - __ADDCONST(CDECL_CALL); - lua::RegFunctionsLocal(st, lua_functioncall_func); - st.setglobal("FunctionCall"); -} -#undef __ADDCONST \ No newline at end of file diff --git a/plugins/Dfusion/src/lua_Misc.cpp b/plugins/Dfusion/src/lua_Misc.cpp index b58efc7ac..6a06781fd 100644 --- a/plugins/Dfusion/src/lua_Misc.cpp +++ b/plugins/Dfusion/src/lua_Misc.cpp @@ -129,111 +129,16 @@ static int GetMod(lua_State *L) st.push(pos); return 1; } -static int lua_malloc(lua_State *L) -{ - lua::state st(L); - size_t size=st.as(1); - size_t pos=reinterpret_cast(malloc(size)); - st.push(pos); - return 1; -} -static int lua_malloc_free(lua_State *L) -{ - lua::state st(L); - size_t ptr=st.as(1); - free(reinterpret_cast(ptr)); - return 0; -} -#ifdef LINUX_BUILD -static size_t __attribute__((stdcall)) PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t ecx,uint32_t edx,uint32_t edi,uint32_t esi,uint32_t esp,uint32_t ebp) -#else -static size_t __stdcall PushValue(size_t ret,uint32_t eax,uint32_t ebx,uint32_t ecx,uint32_t edx,uint32_t edi,uint32_t esi,uint32_t esp,uint32_t ebp) -#endif -{ - lua::state st=lua::glua::Get(); - st.getglobal("OnFunction"); - if(st.is()) - return 0; - st.newtable(); - st.push(eax); - st.setfield("eax"); - st.push(ebx); - st.setfield("ebx"); - st.push(ecx); - st.setfield("ecx"); - st.push(edx); - st.setfield("edx"); - st.push(edi); - st.setfield("edi"); - st.push(esi); - st.setfield("esi"); - st.push(esp+12); - st.setfield("esp"); - st.push(ebp); - st.setfield("ebp"); - st.push(ret); - st.setfield("ret"); - DFHack::Lua::SafeCall(DFHack::Core::getInstance().getConsole(),st,1,1); - return st.as(); -} -static int Get_PushValue(lua_State *L) -{ - lua::state st(L); - st.push((uint32_t)&PushValue); - return 1; -} -static int Call_Df(lua_State *L) -{ - lua::state st(L); - FunctionCaller f(0); - std::vector args; - size_t ptr; - FunctionCaller::callconv conv; - ptr=st.as(1); - conv=(FunctionCaller::callconv)st.as(2); - for(size_t j=3;j<=st.gettop();j++) - args.push_back(st.as(j)); - st.push(f.CallFunction(ptr,conv,args)); - return 1; -} -static int Suspend_Df(lua_State *L) -{ - lua::state st(L); - DFHack::Core::getInstance().Suspend(); - return 0; -} -static int Resume_Df(lua_State *L) -{ - lua::state st(L); - DFHack::Core::getInstance().Resume(); - return 0; -} -static int Cast(lua_State *L) -{ - lua::state st(L); - if(DFHack::Lua::IsDFObject(st,1)!=DFHack::Lua::OBJ_TYPE) - st.error("First argument must be df type!"); - if(!st.is(2)) //todo maybe lightuserdata? - st.error("Second argument must be pointer as a number!"); - st.getfield("_identity",1); - DFHack::Lua::PushDFObject(st,(DFHack::type_identity*)lua_touserdata(st,-1),(void*)st.as(2)); - return 1; -} + + const luaL_Reg lua_misc_func[]= { - {"alloc",lua_malloc}, - {"free",lua_malloc_free}, {"loadmod",LoadMod}, {"getmod",GetMod}, {"loadobj",LoadObj}, {"loadobjsymbols",LoadObjSymbols}, {"findmarker",FindMarker}, {"newmod",NewMod}, - {"getpushvalue",Get_PushValue}, - {"calldf",Call_Df}, - {"suspend",Suspend_Df}, - {"resume",Resume_Df}, - {"cast",Cast}, {NULL,NULL} }; void lua::RegisterMisc(lua::state &st) diff --git a/plugins/Dfusion/src/lua_Offsets.cpp b/plugins/Dfusion/src/lua_Offsets.cpp deleted file mode 100644 index 4d66e67e7..000000000 --- a/plugins/Dfusion/src/lua_Offsets.cpp +++ /dev/null @@ -1,190 +0,0 @@ -#include "lua_Offsets.h" -#include -#include -//TODO make a seperate module with peeks/pokes and page permisions (linux/windows spec) -//TODO maybe remove alltogether- use DFHack::Process instead? -template -T engine_peek(size_t offset) -{ - return *(reinterpret_cast(offset)); -} -template -void engine_poke(size_t offset,T val) -{ - *(reinterpret_cast(offset))=val; -} - -void peekarb(size_t offset, void *mem,size_t size) -{ - memcpy(mem,(void*)offset,size); -} -void peekstr(size_t offset, char* buf, size_t maxsize) -{ - strncpy(buf,(char*)offset,maxsize); -} -void pokearb(size_t offset, void *mem,size_t size) -{ - memcpy((void*)offset,mem,size); -} -void pokestr(size_t offset, char* buf, size_t maxsize) -{ - strncpy((char*)offset,buf,maxsize); -} - -//// lua stuff here -static int lua_peekb(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekw(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekd(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekq(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekfloat(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekdouble(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_peekarb(lua_State *L) -{ - lua::state st(L); - size_t size=st.as(2); - void *p=st.newuserdata(size); - peekarb(st.as(1),p,size); - return 1; -} -static int lua_peekstr(lua_State *L) -{ - lua::state st(L); - char *buf; - buf=new char[256]; - peekstr(st.as(1),buf,256); - std::string tstr(buf); - st.push(tstr); - delete [] buf; - return 1; -} -static int lua_peekstr2(lua_State *L) -{ - lua::state st(L); - st.push(engine_peek(st.as(1))); - return 1; -} -static int lua_pokeb(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_pokew(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_poked(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_pokeq(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_pokefloat(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_pokedouble(lua_State *L) -{ - lua::state st(L); - engine_poke(st.as(1),st.as(2)); - return 0; -} -static int lua_pokearb(lua_State *L) -{ - lua::state st(L); - void *p=(void *)lua_touserdata(L, 2);//st.as(2); - size_t size=st.as(3); - pokearb(st.as(1),p,size); - return 0; -} -static int lua_pokestr(lua_State *L) -{ - lua::state st(L); - std::string trg=st.as(2); - pokestr(st.as(1),(char*)trg.c_str(),trg.size()); - return 0; -} -static int lua_pokestr2(lua_State *L) -{ - lua::state st(L); - std::string trg=st.as(2); - engine_poke(st.as(1),trg); - return 0; -} -const luaL_Reg lua_engine_func[]= -{ - {"peekb",lua_peekb}, - {"peekw",lua_peekw}, - {"peekd",lua_peekd}, - {"peekq",lua_peekq}, - {"peekfloat",lua_peekfloat}, - {"peekdouble",lua_peekdouble}, - - {"peekarb",lua_peekarb}, - {"peekstr",lua_peekstr}, - {"peekstr2",lua_peekstr2}, - - {"pokeb",lua_pokeb}, - {"pokew",lua_pokew}, - {"poked",lua_poked}, - {"pokeq",lua_pokeq}, - {"pokefloat",lua_pokefloat}, - {"pokedouble",lua_pokedouble}, - - {"pokearb",lua_pokearb}, - {"pokestr",lua_pokestr}, - {"pokestr2",lua_pokestr2}, - {NULL,NULL} -}; - -void lua::RegisterEngine(lua::state &st) -{ - st.getglobal("engine"); - if(st.is()) - { - st.pop(); - st.newtable(); - } - lua::RegFunctionsLocal(st,lua_engine_func); - st.setglobal("engine"); -} diff --git a/plugins/Dfusion/src/lua_VersionInfo.cpp b/plugins/Dfusion/src/lua_VersionInfo.cpp deleted file mode 100644 index 9f5cd17e7..000000000 --- a/plugins/Dfusion/src/lua_VersionInfo.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include "lua_VersionInfo.h" - -#define VI_FUNC(name) int lua_VI_ ## name (lua_State *L){\ - lua::state s(L);\ - DFHack::VersionInfo* vif=DFHack::Core::getInstance().vinfo; -#define END_VI_FUNC } - -VI_FUNC(getBase) -//int lua_VI_getBase(lua_State *L) - s.push(vif->getBase()); - return 1; -END_VI_FUNC - -VI_FUNC(setBase) - uint32_t val=s.as(1); - vif->setBase(val); - return 0; -END_VI_FUNC -VI_FUNC(rebaseTo) - uint32_t val=s.as(1); - vif->rebaseTo(val); - return 0; -END_VI_FUNC -VI_FUNC(addMD5) - std::string val=s.as(1); - vif->addMD5(val); - return 0; -END_VI_FUNC -VI_FUNC(hasMD5) - std::string val=s.as(1); - s.push(vif->hasMD5(val)); - return 1; -END_VI_FUNC - -VI_FUNC(addPE) - uint32_t val=s.as(1); - vif->addPE(val); - return 0; -END_VI_FUNC - -VI_FUNC(hasPE) - uint32_t val=s.as(1); - s.push(vif->hasPE(val)); - return 1; -END_VI_FUNC - -VI_FUNC(setVersion) - std::string val=s.as(1); - vif->setVersion(val); - return 0; -END_VI_FUNC - -VI_FUNC(getVersion) - s.push(vif->getVersion()); - return 1; -END_VI_FUNC - -VI_FUNC(setAddress) - std::string key=s.as(1); - uint32_t val=s.as(2); - vif->setAddress(key,val); - return 0; -END_VI_FUNC - -VI_FUNC(getAddress) - std::string key=s.as(1); - - s.push(vif->getAddress(key)); - return 1; -END_VI_FUNC - -VI_FUNC(setOS) - unsigned os=s.as(1); - vif->setOS((DFHack::OSType)os); - return 0; -END_VI_FUNC - -VI_FUNC(getOS) - s.push(vif->getOS()); - return 1; -END_VI_FUNC -#undef VI_FUNC -#undef END_VI_FUNC -#define VI_FUNC(name) {#name,lua_VI_ ## name} -const luaL_Reg lua_vinfo_func[]= -{ - VI_FUNC(getBase), - VI_FUNC(setBase), - VI_FUNC(rebaseTo), - VI_FUNC(addMD5), - VI_FUNC(hasMD5), - VI_FUNC(addPE), - VI_FUNC(hasPE), - VI_FUNC(setVersion), - VI_FUNC(getVersion), - VI_FUNC(setAddress), - VI_FUNC(getAddress), - VI_FUNC(setOS), - VI_FUNC(getOS), - {NULL,NULL} -}; -#undef VI_FUNC -void lua::RegisterVersionInfo(lua::state &st) -{ - - st.getglobal("VersionInfo"); - if(st.is()) - { - st.pop(); - st.newtable(); - } - - lua::RegFunctionsLocal(st, lua_vinfo_func); - st.setglobal("VersionInfo"); -} From fbc2d85609808b69f27f5653d8898eabfbfce242 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 1 Oct 2012 11:14:00 +0200 Subject: [PATCH 023/472] ruby: rename building_isitemfree to item_isfree, add trader flag check --- plugins/ruby/building.rb | 12 +----------- plugins/ruby/item.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 59f715515..08c12a841 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -352,23 +352,13 @@ module DFHack job end - # check item flags to see if it is suitable for use as a building material - def building_isitemfree(i) - !i.flags.in_job and - !i.flags.in_inventory and - !i.flags.removed and - !i.flags.in_building and - !i.flags.owned and - !i.flags.forbid - end - # exemple usage def buildbed(pos=cursor) raise 'where to ?' if pos.x < 0 item = world.items.all.find { |i| i.kind_of?(ItemBedst) and - building_isitemfree(i) + item_isfree(i) } raise 'no free bed, build more !' if not item diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index fc840f7ad..9a254d9e8 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -47,5 +47,17 @@ module DFHack raise "what what?" end end + + # check item flags to see if it is suitable for use as a job input material + def item_isfree(i) + !i.flags.trader and + !i.flags.in_job and + !i.flags.in_inventory and + !i.flags.removed and + !i.flags.in_building and + !i.flags.owned and + !i.flags.forbid + end + end end From 150704f1af024143575b92429b432ae0c22aea08 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 1 Oct 2012 16:05:07 +0200 Subject: [PATCH 024/472] ruby: raise when using unknown symbols in viewscreen.feed --- plugins/ruby/ruby-autogen-defs.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 64c08facb..4470c8022 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -785,6 +785,7 @@ module DFHack def isset(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_isset(@_memaddr, key) end alias is_set? isset @@ -792,12 +793,14 @@ module DFHack def set(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_set(@_memaddr, key) end def delete(key) raise unless @_memaddr key = @_enum.int(key) if _enum + raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_deletekey(@_memaddr, key) end From 43532e4871255d7bdb3696ce32ebe0f776844c3e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 1 Oct 2012 17:48:47 -0500 Subject: [PATCH 025/472] Progress commit of a rewrite of autolabor to draw from the jobs list and other world information. A lot still to do on this. DO NOT MERGE INTO THE MAIN BRANCH. This code is incomplete and only vaguely tested. --- plugins/autolabor.cpp | 1276 ++++++++++++++++++----------------------- 1 file changed, 560 insertions(+), 716 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c39b126c9..7c984d35f 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -36,6 +36,14 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include + #include @@ -504,17 +512,25 @@ static const int responsibility_penalties[] = { struct dwarf_info_t { - int highest_skill; - int total_skill; - int mastery_penalty; - int assigned_jobs; + df::unit* dwarf; dwarf_state state; - bool has_exclusive_labor; - int noble_penalty; // penalty for assignment due to noble status - bool medical; // this dwarf has medical responsibility - bool trader; // this dwarf has trade responsibility - bool diplomacy; // this dwarf meets with diplomats - int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none) + + bool clear_all; + + bool has_axe; + bool has_pick; + bool has_crossbow; + + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + + void set_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } }; static bool isOptionEnabled(unsigned flag) @@ -706,211 +722,570 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// sorting objects -struct dwarfinfo_sorter + +static df::building* get_building_from_job(df::job* j) { - dwarfinfo_sorter(std::vector & info):dwarf_info(info){}; - bool operator() (int i,int j) - { - if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE) - return true; - if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE) - return false; - return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty; - }; - std::vector & dwarf_info; -}; -struct laborinfo_sorter + for (auto r = j->references.begin(); r != j->references.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +static df::job_skill workshop_build_labor[] = { - bool operator() (int i,int j) - { - return labor_infos[i].mode() < labor_infos[j].mode(); - }; + /* Carpenters */ df::job_skill::CARPENTRY, + /* Farmers */ df::job_skill::PROCESSPLANTS, + /* Masons */ df::job_skill::MASONRY, + /* Craftsdwarfs */ df::job_skill::STONECRAFT, + /* Jewelers */ df::job_skill::CUTGEM, + /* MetalsmithsForge */ df::job_skill::METALCRAFT, + /* MagmaForge */ df::job_skill::METALCRAFT, + /* Bowyers */ df::job_skill::BOWYER, + /* Mechanics */ df::job_skill::MECHANICS, + /* Siege */ df::job_skill::SIEGECRAFT, + /* Butchers */ df::job_skill::BUTCHER, + /* Leatherworks */ df::job_skill::LEATHERWORK, + /* Tanners */ df::job_skill::TANNER, + /* Clothiers */ df::job_skill::CLOTHESMAKING, + /* Fishery */ df::job_skill::FISH, + /* Still */ df::job_skill::BREWING, + /* Loom */ df::job_skill::WEAVING, + /* Quern */ df::job_skill::MILLING, + /* Kennels */ df::job_skill::ANIMALTRAIN, + /* Kitchen */ df::job_skill::COOK, + /* Ashery */ df::job_skill::LYE_MAKING, + /* Dyers */ df::job_skill::DYER, + /* Millstone */ df::job_skill::MILLING, + /* Custom */ (df::job_skill) -1, + /* Tool */ (df::job_skill) -1 }; -struct values_sorter +static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labor &labor) { - values_sorter(std::vector & values):values(values){}; - bool operator() (int i,int j) - { - return values[i] > values[j]; - }; - std::vector & values; -}; + switch (j->job_type) + { + case df::job_type::ConstructBuilding: + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + skill = workshop_build_labor[ws->type]; + break; + } + } + break; + case df::job_type::CustomReaction: + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + skill = (*r)->skill; + break; + } + } + break; + default: + skill = ENUM_ATTR(job_type, skill, j->job_type); + } + + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + + if (labor == df::unit_labor::NONE) + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == -1) + { + } -static void assign_labor(unit_labor::unit_labor labor, - int n_dwarfs, - std::vector& dwarf_info, - bool trader_requested, - std::vector& dwarfs, - bool has_butchers, - bool has_fishery, - color_ostream& out) -{ - df::job_skill skill = labor_to_skill[labor]; +} - if (labor_infos[labor].mode() != AUTOMATIC) - return; +class AutoLaborManager { + color_ostream& out; + +public: + AutoLaborManager(color_ostream& o) : out(o) + { + } + + ~AutoLaborManager() + { + for (std::vector::iterator i = dwarf_info.begin(); + i != dwarf_info.end(); i++) + delete (*i); + } + + dwarf_info_t* add_dwarf(df::unit* u) + { + dwarf_info_t* dwarf = new dwarf_info_t(u); + dwarf_info.push_back(dwarf); + return dwarf; + } + + +private: + bool has_butchers; + bool has_fishery; + bool trader_requested; + + int dig_count; + int tree_count; + int plant_count; + int detail_count; + int pick_count; + int axe_count; + + std::vector jobs; + std::vector dwarf_info; + std::deque idle_dwarfs; + +private: + void scan_buildings() + { + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + df::building *build = *b; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + } + + void count_map_designations() + { + dig_count = 0; + tree_count = 0; + plant_count = 0; + detail_count = 0; + + for (int i = 0; i < world->map.map_blocks.size(); ++i) + { + df::map_block* bl = world->map.map_blocks[i]; + + if (!bl->flags.bits.designated) + continue; + + if (print_debug) + out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); + + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; + if (dig != df::enums::tile_dig_designation::No) + { + df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); + switch (tts) + { + case df::enums::tiletype_shape::TREE: + tree_count++; break; + case df::enums::tiletype_shape::SHRUB: + plant_count++; break; + default: + dig_count++; break; + } + } + if (bl->designation[x][y].bits.smooth != 0) + detail_count++; + } + } + + if (print_debug) + out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); + + } + + void count_tools() + { + pick_count = 0; + axe_count = 0; + + df::item_flags bad_flags; + bad_flags.whole = 0; - int best_dwarf = 0; - int best_value = -10000; +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact1); +#undef F - std::vector values(n_dwarfs); - std::vector candidates; - std::map dwarf_skill; - std::vector previously_enabled(n_dwarfs); + for (int i = 0; i < world->items.all.size(); ++i) + { + df::item* item = world->items.all[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!item->isWeapon()) + continue; + + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + if (weaponsk == df::job_skill::AXE) + axe_count++; + else if (weaponsk == df::job_skill::MINING) + pick_count++; + } + + if (print_debug) + out.print("Axes = %d, picks = %d\n", axe_count, pick_count); + + } + + void collect_job_list() + { + std::vector repeating_jobs; + + + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) + { + df::job* j = jll->item; + if (!j) + continue; + + if (j->flags.bits.suspend) + continue; + + int worker = -1; + + for (int r = 0; r < j->references.size(); ++r) + if (j->references[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->references[r]))->unit_id; + + if (worker != -1) + continue; + + if (j->flags.bits.repeat) + repeating_jobs.push_back(j); + else + jobs.push_back(j); + } + + if (print_debug) + out.print("%d repeating jobs, %d nonrepeating jobs\n", repeating_jobs.size(), jobs.size()); + + for (int i = 0; i < repeating_jobs.size(); i++) + jobs.push_back(repeating_jobs[i]); + + } + + void collect_dwarf_list() + { + + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + df::unit* cre = *u; + + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + + dwarf_info_t* dwarf = add_dwarf(cre); + + df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + if (trader_requested) + dwarf->clear_all = true; + } + + } + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarf->dwarf; + bool p2 = act->person2 == dwarf->dwarf; + + if (p1 || p2) + { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + + // Find the activity state for each dwarf-> + + bool is_on_break = false; + dwarf_state state = OTHER; + + for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarf->dwarf->profession == profession::BABY || + dwarf->dwarf->profession == profession::CHILD || + dwarf->dwarf->profession == profession::DRUNK) + { + state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + state = MILITARY; + else if (dwarf->dwarf->job.current_job == NULL) + { + if (is_on_break) + state = OTHER; + else if (dwarf->dwarf->specific_refs.size() > 0) + state = OTHER; + else + state = IDLE; + } + else + { + int job = dwarf->dwarf->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + state = dwarf_states[job]; + else + { + out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + state = OTHER; + } + } + + dwarf->state = state; + + if (print_debug) + out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_axe = 1; + if (state != IDLE) + axe_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_pick = 1; + if (state != IDLE) + pick_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_crossbow = 1; + if (print_debug) + out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); + } + } + } + + // clear labors if currently idle + + if (state == IDLE || dwarf->clear_all) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + dwarf->dwarf->status.labors[labor] = false; + } + } + + if (state == IDLE && !dwarf->clear_all) + idle_dwarfs.push_back(dwarf); + + } + + } + } + + void assign_one_dwarf (df::job_skill skill, df::unit_labor labor) + { + if (labor == df::unit_labor::NONE) + return; + + std::deque::iterator bestdwarf = idle_dwarfs.begin(); + + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; + + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } + + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); + + idle_dwarfs.erase(bestdwarf); + } - auto mode = labor_infos[labor].mode(); +public: + void process() + { + // scan for specific buildings of interest - // Find candidate dwarfs, and calculate a preference value for each dwarf - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if (dwarf_info[dwarf].state == CHILD) - continue; - if (dwarf_info[dwarf].state == MILITARY) - continue; - if (dwarf_info[dwarf].trader && trader_requested) - continue; - if (dwarf_info[dwarf].diplomacy) - continue; + scan_buildings(); - if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor) - continue; + // count number of squares designated for dig, wood cutting, detailing, and plant harvesting - int value = dwarf_info[dwarf].mastery_penalty; + count_map_designations(); - if (skill != job_skill::NONE) - { - int skill_level = 0; - int skill_experience = 0; - - for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) - { - if ((*s)->id == skill) - { - skill_level = (*s)->rating; - skill_experience = (*s)->experience; - break; - } - } - - dwarf_skill[dwarf] = skill_level; - - value += skill_level * 100; - value += skill_experience / 20; - if (skill_level > 0 || skill_experience > 0) - value += 200; - if (skill_level >= 15) - value += 1000 * (skill_level - 14); - } - else - { - dwarf_skill[dwarf] = 0; - } + // count number of picks and axes available for use - if (dwarfs[dwarf]->status.labors[labor]) - { - value += 5; - if (labor_infos[labor].is_exclusive) - value += 350; - } + count_tools(); - // bias by happiness + // collect current job list - value += dwarfs[dwarf]->status.happiness; + collect_job_list(); - values[dwarf] = value; + // collect list of dwarfs - candidates.push_back(dwarf); + collect_dwarf_list(); - } + // assign jobs requiring skill to idle dwarfs - // Sort candidates by preference value - values_sorter ivs(values); - std::sort(candidates.begin(), candidates.end(), ivs); + int jobs_to_assign = jobs.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); - // Disable the labor on everyone - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if (dwarf_info[dwarf].state == CHILD) - continue; + if (print_debug) + out.print ("Assign idle (skilled): %d\n", jobs_to_assign); - previously_enabled[dwarf] = dwarfs[dwarf]->status.labors[labor]; - dwarfs[dwarf]->status.labors[labor] = false; - } + std::vector jobs2; - int min_dwarfs = labor_infos[labor].minimum_dwarfs(); - int max_dwarfs = labor_infos[labor].maximum_dwarfs(); - - // Special - don't assign hunt without a butchers, or fish without a fishery - if (unit_labor::HUNT == labor && !has_butchers) - min_dwarfs = max_dwarfs = 0; - if (unit_labor::FISH == labor && !has_fishery) - min_dwarfs = max_dwarfs = 0; - - bool want_idle_dwarf = true; - if (state_count[IDLE] < 2) - want_idle_dwarf = false; - - /* - * Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in - * order of preference, and then assign additional dwarfs that meet any of these conditions: - * - The dwarf is idle and there are no idle dwarves assigned to this labor - * - The dwarf has nonzero skill associated with the labor - * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. - * We stop assigning dwarfs when we reach the maximum allowed. - * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs - * (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted. - * Military and children/nobles will not have labors assigned. - * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. - */ - for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) - { - int dwarf = candidates[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - - bool preferred_dwarf = false; - if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) - preferred_dwarf = true; - if (dwarf_skill[dwarf] > 0) - preferred_dwarf = true; - if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) - preferred_dwarf = true; - if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) - preferred_dwarf = true; - if (dwarf_info[dwarf].trader && trader_requested) - continue; - if (dwarf_info[dwarf].diplomacy) - continue; + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job* j = jobs[i]; - if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) - continue; + df::job_skill skill; + df::unit_labor labor; - if (!dwarfs[dwarf]->status.labors[labor]) - dwarf_info[dwarf].assigned_jobs++; + find_job_skill_labor(j, skill, labor); - dwarfs[dwarf]->status.labors[labor] = true; + if(print_debug) + out.print("Job skill = %d labor = %d\n", skill, labor); - if (labor_infos[labor].is_exclusive) - { - dwarf_info[dwarf].has_exclusive_labor = true; - // all the exclusive labors require equipment so this should force the dorf to reequip if needed - dwarfs[dwarf]->military.pickup_flags.bits.update = 1; - } + if (labor == -1) + out.print("Invalid labor for job (%d)\n", j->job_type); - if (print_debug) - out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); + if (skill == df::job_skill::NONE) + jobs2.push_back(j); + else + assign_one_dwarf(skill, labor); + } - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - labor_infos[labor].active_dwarfs++; + // assign mining jobs to idle dwarfs - if (dwarf_info[dwarf].state == IDLE) - want_idle_dwarf = false; - } -} + int dig_max = dig_count; + if (dig_max > pick_count) + dig_max = pick_count; + + jobs_to_assign = dig_max - jobs2.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); + + if (print_debug) + out.print ("Assign idle (mining): %d\n", jobs_to_assign); + + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job_skill skill = df::job_skill::MINING; + df::unit_labor labor = df::unit_labor::MINE; + + assign_one_dwarf(skill, labor); + } + + // now assign unskilled jobs to idle dwarfs + + jobs_to_assign = jobs2.size(); + if (jobs_to_assign > idle_dwarfs.size()) + jobs_to_assign = idle_dwarfs.size(); + + if (print_debug) + out.print ("Assign idle (unskilled): %d\n", jobs_to_assign); + + for (int i = 0; i < jobs_to_assign; ++i) + { + df::job* j = jobs2[i]; + + df::job_skill skill; + df::unit_labor labor; + + find_job_skill_labor(j, skill, labor); + assign_one_dwarf(skill, labor); + } + + print_debug = 0; + + } + +}; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) @@ -944,339 +1319,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; - uint32_t race = ui->race_id; - uint32_t civ = ui->civ_id; - - std::vector dwarfs; - - bool has_butchers = false; - bool has_fishery = false; - bool trader_requested = false; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (building_type::Workshop == type) - { - df::workshop_type subType = (df::workshop_type)build->getSubtype(); - if (workshop_type::Butchers == subType) - has_butchers = true; - if (workshop_type::Fishery == subType) - has_fishery = true; - } - else if (building_type::TradeDepot == type) - { - df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; - if (print_debug) - { - if (trader_requested) - out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); - else - out.print("Trade depot found but trader is not requested.\n"); - } - } - } - - for (int i = 0; i < world->units.active.size(); ++i) - { - df::unit* cre = world->units.active[i]; - if (Units::isCitizen(cre)) - { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - dwarfs.push_back(cre); - } - } - - int n_dwarfs = dwarfs.size(); - - if (n_dwarfs == 0) - return CR_OK; - - std::vector dwarf_info(n_dwarfs); - - // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - dwarf_info[dwarf].single_labor = -1; - - if (dwarfs[dwarf]->status.souls.size() <= 0) - continue; - - // compute noble penalty - - int noble_penalty = 0; - - df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) - { - df::histfig_entity_link* hfelink = hf->entity_links.at(i); - if (hfelink->getType() == df::histfig_entity_link_type::POSITION) - { - df::histfig_entity_link_positionst *epos = - (df::histfig_entity_link_positionst*) hfelink; - df::historical_entity* entity = df::historical_entity::find(epos->entity_id); - if (!entity) - continue; - df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); - if (!assignment) - continue; - df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); - if (!position) - continue; - - for (int n = 0; n < 25; n++) - if (position->responsibilities[n]) - noble_penalty += responsibility_penalties[n]; - - if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT]) - dwarf_info[dwarf].medical = true; - - if (position->responsibilities[df::entity_position_responsibility::TRADE]) - dwarf_info[dwarf].trader = true; - - } - - } - - dwarf_info[dwarf].noble_penalty = noble_penalty; - - // identify dwarfs who are needed for meetings and mark them for exclusion - - for (int i = 0; i < ui->activities.size(); ++i) - { - df::activity_info *act = ui->activities[i]; - if (!act) continue; - bool p1 = act->person1 == dwarfs[dwarf]; - bool p2 = act->person2 == dwarfs[dwarf]; - - if (p1 || p2) - { - dwarf_info[dwarf].diplomacy = true; - if (print_debug) - out.print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", dwarf, dwarfs[dwarf]->name.first_name.c_str()); - break; - } - } - - for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) - { - df::job_skill skill = (*s)->id; - - df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); - - int skill_level = (*s)->rating; - int skill_experience = (*s)->experience; - - // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) - - if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical) - continue; - - if (dwarf_info[dwarf].highest_skill < skill_level) - dwarf_info[dwarf].highest_skill = skill_level; - dwarf_info[dwarf].total_skill += skill_level; - } - } - - // Calculate a base penalty for using each dwarf for a task he isn't good at. - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; - dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; - dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) - dwarf_info[dwarf].mastery_penalty -= 100; - } - } - - // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but - // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. - - state_count.clear(); - state_count.resize(NUM_STATE); - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - bool is_on_break = false; - - for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - - if (dwarfs[dwarf]->profession == profession::BABY || - dwarfs[dwarf]->profession == profession::CHILD || - dwarfs[dwarf]->profession == profession::DRUNK) - { - dwarf_info[dwarf].state = CHILD; - } - else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) - dwarf_info[dwarf].state = MILITARY; - else if (dwarfs[dwarf]->job.current_job == NULL) - { - if (is_on_break) - dwarf_info[dwarf].state = OTHER; - else if (dwarfs[dwarf]->specific_refs.size() > 0) - dwarf_info[dwarf].state = OTHER; - else - dwarf_info[dwarf].state = IDLE; - } - else - { - int job = dwarfs[dwarf]->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - dwarf_info[dwarf].state = dwarf_states[job]; - else - { - out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); - dwarf_info[dwarf].state = OTHER; - } - } - - state_count[dwarf_info[dwarf].state]++; - - if (print_debug) - out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); - } - - std::vector labors; - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - labor_infos[labor].active_dwarfs = 0; - - labors.push_back(labor); - } - laborinfo_sorter lasorter; - std::sort(labors.begin(), labors.end(), lasorter); - - // Handle DISABLED skills (just bookkeeping) - for (auto lp = labors.begin(); lp != labors.end(); ++lp) - { - auto labor = *lp; - - if (labor_infos[labor].mode() != DISABLE) - continue; - - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if ((dwarf_info[dwarf].trader && trader_requested) || - dwarf_info[dwarf].diplomacy) - { - dwarfs[dwarf]->status.labors[labor] = false; - } - - if (dwarfs[dwarf]->status.labors[labor]) - { - if (labor_infos[labor].is_exclusive) - dwarf_info[dwarf].has_exclusive_labor = true; - - dwarf_info[dwarf].assigned_jobs++; - } - } - } - - // Handle all skills except those marked HAULERS - - for (auto lp = labors.begin(); lp != labors.end(); ++lp) - { - auto labor = *lp; - - assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); - } - - // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps - // make sure that hauling jobs are handled quickly rather than building up. - - int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100; + AutoLaborManager alm(out); - if (num_haulers < 1) - num_haulers = 1; + alm.process(); - std::vector hauler_ids; - for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) - { - if ((dwarf_info[dwarf].trader && trader_requested) || - dwarf_info[dwarf].diplomacy) - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - if (labor_infos[labor].mode() != HAULERS) - continue; - dwarfs[dwarf]->status.labors[labor] = false; - } - continue; - } - - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - hauler_ids.push_back(dwarf); - } - dwarfinfo_sorter sorter(dwarf_info); - // Idle dwarves come first, then we sort from least-skilled to most-skilled. - std::sort(hauler_ids.begin(), hauler_ids.end(), sorter); - - // don't set any haulers if everyone is off drinking or something - if (hauler_ids.size() == 0) { - num_haulers = 0; - } - - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - if (labor_infos[labor].mode() != HAULERS) - continue; - - for (int i = 0; i < num_haulers; i++) - { - assert(i < hauler_ids.size()); - - int dwarf = hauler_ids[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - dwarfs[dwarf]->status.labors[labor] = true; - dwarf_info[dwarf].assigned_jobs++; - - if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) - labor_infos[labor].active_dwarfs++; - - if (print_debug) - out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); - } - - for (int i = num_haulers; i < hauler_ids.size(); i++) - { - assert(i < hauler_ids.size()); - - int dwarf = hauler_ids[i]; - - assert(dwarf >= 0); - assert(dwarf < n_dwarfs); - - dwarfs[dwarf]->status.labors[labor] = false; - } - } - - print_debug = 0; return CR_OK; } @@ -1469,205 +1515,3 @@ command_result autolabor (color_ostream &out, std::vector & parame } } -struct StockpileInfo { - df::building_stockpilest* sp; - int size; - int free; - int x1, x2, y1, y2, z; - -public: - StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) - { - MapExtras::MapCache mc; - - z = sp_->z; - x1 = sp_->room.x; - x2 = sp_->room.x + sp_->room.width; - y1 = sp_->room.y; - y2 = sp_->room.y + sp_->room.height; - int e = 0; - size = 0; - free = 0; - for (int y = y1; y < y2; y++) - for (int x = x1; x < x2; x++) - if (sp_->room.extents[e++] == 1) - { - size++; - DFCoord cursor (x,y,z); - uint32_t blockX = x / 16; - uint32_t tileX = x % 16; - uint32_t blockY = y / 16; - uint32_t tileY = y % 16; - MapExtras::Block * b = mc.BlockAt(cursor/16); - if(b && b->is_valid()) - { - auto &block = *b->getRaw(); - df::tile_occupancy &occ = block.occupancy[tileX][tileY]; - if (!occ.bits.item) - free++; - } - } - } - - bool isFull() { return free == 0; } - - bool canHold(df::item *i) - { - return false; - } - - bool inStockpile(df::item *i) - { - df::item *container = Items::getContainer(i); - if (container) - return inStockpile(container); - - if (i->pos.z != z) return false; - if (i->pos.x < x1 || i->pos.x >= x2 || - i->pos.y < y1 || i->pos.y >= y2) return false; - int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; - return sp->room.extents[e] == 1; - } - - int getId() { return sp->id; } -}; - -static int stockcheck(color_ostream &out, vector & parameters) -{ - int count = 0; - - std::vector stockpiles; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (building_type::Stockpile == type) - { - df::building_stockpilest *sp = virtual_cast(build); - StockpileInfo *spi = new StockpileInfo(sp); - stockpiles.push_back(spi); - } - - } - - std::vector &items = world->items.other[items_other_id::ANY_FREE]; - - // Precompute a bitmask with the bad flags - df::item_flags bad_flags; - bad_flags.whole = 0; - -#define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); - F(spider_web); F(owned); F(in_job); -#undef F - - for (size_t i = 0; i < items.size(); i++) - { - df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG - - df::item_type typ = item->getType(); - if (typ != item_type::MEAT && - typ != item_type::FISH && - typ != item_type::FISH_RAW && - typ != item_type::PLANT && - typ != item_type::CHEESE && - typ != item_type::FOOD && - typ != item_type::EGG) - continue; - - df::item *container = 0; - df::unit *holder = 0; - df::building *building = 0; - - for (size_t i = 0; i < item->itemrefs.size(); i++) - { - df::general_ref *ref = item->itemrefs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - container = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - - df::item *nextcontainer = container; - df::item *lastcontainer = 0; - - while(nextcontainer) { - df::item *thiscontainer = nextcontainer; - nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++) - { - df::general_ref *ref = thiscontainer->itemrefs[i]; - - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - lastcontainer = nextcontainer = ref->getItem(); - break; - - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; - - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; - - default: - break; - } - } - } - - if (holder) - continue; // carried items do not rot as far as i know - - if (building) { - df::building_type btype = building->getType(); - if (btype == building_type::TradeDepot || - btype == building_type::Wagon) - continue; // items in trade depot or the embark wagon do not rot - - if (typ == item_type::EGG && btype ==building_type::NestBox) - continue; // eggs in nest box do not rot - } - - int canHoldCount = 0; - StockpileInfo *current = 0; - - for (int idx = 0; idx < stockpiles.size(); idx++) - { - StockpileInfo *spi = stockpiles[idx]; - if (spi->canHold(item)) canHoldCount++; - if (spi->inStockpile(item)) current=spi; - } - - if (current) - continue; - - count++; - - } - - return count; -} From 83117fca40ded212ca0f419718f2a7ab2861c961 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 1 Oct 2012 17:57:38 -0500 Subject: [PATCH 026/472] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 2f76de54d..d9c765cce 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2f76de54dacd32af567b177adfb9d037fdf62d9b +Subproject commit d9c765ccea1d98ac08d33316dbb12f62df271690 From 7440e80e6c6af23284403db3af103a5f35358f0f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 2 Oct 2012 13:49:31 +0400 Subject: [PATCH 027/472] 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 028/472] 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 029/472] 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 030/472] 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 031/472] 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 032/472] 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 033/472] 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 034/472] 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 035/472] 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 036/472] 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 037/472] 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 cbbb164dad740cdaa08fde9dec763b7259676996 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 5 Oct 2012 20:44:29 +0200 Subject: [PATCH 038/472] ruby: add MapTile distance_to and spiral_search --- plugins/ruby/map.rb | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 371614704..7c616d722 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -240,5 +240,35 @@ module DFHack def spawn_magma(quantity=7) spawn_liquid(quantity, true) end + + # yield a serie of tiles until the block returns true, returns the matching tile + # the yielded tiles form a (squared) spiral centered here in the current zlevel + # eg for radius 4, yields (-4, -4), (-4, -3), .., (-4, 3), + # (-4, 4), (-3, 4), .., (4, 4), .., (4, -4), .., (-3, -4) + # then move on to radius 5 + def spiral_search(maxradius=100, minradius=0, step=1) + if minradius == 0 + return self if yield self + minradius += step + end + + sides = [[0, 1], [1, 0], [0, -1], [-1, 0]] + (minradius..maxradius).step(step) { |r| + sides.length.times { |s| + dxr, dyr = sides[(s-1) % sides.length] + dx, dy = sides[s] + (-r...r).step(step) { |v| + t = offset(dxr*r + dx*v, dyr*r + dy*v) + return t if t and yield t + } + } + } + nil + end + + # returns dx^2+dy^2+dz^2 + def distance_to(ot) + (x-ot.x)**2 + (y-ot.y)**2 + (z-ot.z)**2 + end end end From 5396a67465a374260f81baef03c03381d8a09059 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 6 Oct 2012 12:40:46 +0400 Subject: [PATCH 039/472] 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 040/472] 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 041/472] 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 042/472] 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 923ea3f4b0a2c435399dd967d4736e2ec626344f Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 7 Oct 2012 20:44:18 +0300 Subject: [PATCH 043/472] Reactionhooks more usefull and gm-editor minor tweaks (e.g. search in containers) --- library/include/LuaTools.h | 15 +++++++++++++++ plugins/reactionhooks.cpp | 9 ++++++--- scripts/gui/gm-editor.lua | 21 +++++++++++++++++++++ 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 3330e23e7..e956a65d5 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -472,3 +472,18 @@ namespace DFHack {namespace Lua { name##_event.invoke(out, 5); \ } \ } + +#define DEFINE_LUA_EVENT_6(name, handler, arg_type1, arg_type2, arg_type3, arg_type4, arg_type5,arg_type6) \ + static DFHack::Lua::Notification name##_event(df::wrap_function(handler, true)); \ + void name(color_ostream &out, arg_type1 arg1, arg_type2 arg2, arg_type3 arg3, arg_type4 arg4,arg_type5 arg5, arg_type6 arg6) { \ + handler(out, arg1, arg2, arg3, arg4, arg5, arg6); \ + if (auto state = name##_event.state_if_count()) { \ + DFHack::Lua::Push(state, arg1); \ + DFHack::Lua::Push(state, arg2); \ + DFHack::Lua::Push(state, arg3); \ + DFHack::Lua::Push(state, arg4); \ + DFHack::Lua::Push(state, arg5); \ + DFHack::Lua::Push(state, arg6); \ + name##_event.invoke(out, 6); \ + } \ +} \ No newline at end of file diff --git a/plugins/reactionhooks.cpp b/plugins/reactionhooks.cpp index 4041b99a5..d70fb9ea1 100644 --- a/plugins/reactionhooks.cpp +++ b/plugins/reactionhooks.cpp @@ -184,9 +184,10 @@ df::item* find_item( return NULL; } -static void handle_reaction_done(color_ostream &out, df::unit *unit, std::vector *in_items, std::vector *out_items,bool *call_native){}; +static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag + , std::vector *out_items,bool *call_native){}; -DEFINE_LUA_EVENT_4(onReactionComplete, handle_reaction_done, df::unit *, std::vector *,std::vector *,bool *); +DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); DFHACK_PLUGIN_LUA_EVENTS { @@ -207,9 +208,11 @@ struct product_hook : item_product { ) { if (auto product = products[this]) { + df::reaction* this_reaction=product->react; + CoreSuspendClaimer suspend; color_ostream_proxy out(Core::getInstance().getConsole()); bool call_native=true; - onReactionComplete(out,unit,in_items,out_items,&call_native); + onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native); if(!call_native) return; } diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 309663bdf..6999f1d8c 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -45,6 +45,25 @@ function GmEditorUi:init(args) return self end +function GmEditorUi:find(test) + local trg=self:currentTarget() + if trg.target and trg.target._kind and trg.target._kind=="container" then + if test== nil then + dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self)) + return + end + local e,what=load("return function(k,v) return "..test.." end") + if e==nil then + dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED) + end + for k,v in pairs(trg.target) do + if e()(k,v)==true then + self:pushTarget(v) + return + end + end + end +end function GmEditorUi:insertNew(typename) local tp=typename if typename== nil then @@ -165,6 +184,8 @@ end self:changeSelected(10) elseif keys.SELECT then self:editSelected() + elseif keys.CUSTOM_ALT_F then + self:find() elseif keys.CUSTOM_ALT_E then --self:specialEditor() elseif keys.CUSTOM_ALT_I then --insert From 49476818c466118508d2fe37bd8820a766e5a6c5 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 7 Oct 2012 20:45:14 +0300 Subject: [PATCH 044/472] Dfusion rebuild start (lua script side plugins) --- library/xml | 2 +- plugins/Dfusion/CMakeLists.txt | 2 +- plugins/Dfusion/dfusion.cpp | 148 ++++++--------------- plugins/Dfusion/luafiles/common.lua | 6 +- plugins/Dfusion/luafiles/embark/a.out | 0 plugins/Dfusion/luafiles/embark/embark.asm | 6 +- plugins/Dfusion/luafiles/embark/embark.o | Bin 348 -> 369 bytes plugins/Dfusion/src/OutFile.cpp | 18 +-- 8 files changed, 57 insertions(+), 125 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/embark/a.out diff --git a/library/xml b/library/xml index a914f3b75..8a78bfa21 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29 +Subproject commit 8a78bfa218817765b0a80431e0cf25435ffb2179 diff --git a/plugins/Dfusion/CMakeLists.txt b/plugins/Dfusion/CMakeLists.txt index 65587201b..51f2e3bee 100644 --- a/plugins/Dfusion/CMakeLists.txt +++ b/plugins/Dfusion/CMakeLists.txt @@ -1,5 +1,5 @@ include_directories(include) -include_directories("${dfhack_SOURCE_DIR}/library/depends/tthread") + FILE(GLOB DFUSION_CPPS src/*.c*) set( DFUSION_CPPS_ALL diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 0f49e860d..daacc0b26 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -6,16 +6,11 @@ #include #include - -#include "tinythread.h" - - #include "luamain.h" #include "lua_Process.h" #include "lua_Hexsearch.h" #include "lua_Misc.h" - #include "DataDefs.h" #include "LuaTools.h" @@ -23,112 +18,51 @@ using std::vector; using std::string; using namespace DFHack; -static tthread::mutex* mymutex=0; -static tthread::thread* thread_dfusion=0; -uint64_t timeLast=0; -DFHACK_PLUGIN("dfusion") -command_result dfusion (color_ostream &out, std::vector ¶meters); -command_result dfuse (color_ostream &out, std::vector ¶meters); +DFHACK_PLUGIN("dfusion") -DFhackCExport const char * plugin_name ( void ) +static int loadObjectFile(lua_State* L) { - return "dfusion"; + std::string path; + + path=luaL_checkstring(L,1); + + OutFile::File f(path); + lua_newtable(L); + int table_pos=lua_gettop(L); + size_t size=f.GetTextSize(); + Lua::Push(L,size); + lua_setfield(L,table_pos,"data_size"); + char* buf=new char[size]; + f.GetText(buf); + + //Lua::PushDFObject(L,DFHack::,buf); + //Lua::Push(L,buf); + lua_pushlightuserdata(L,buf); + lua_setfield(L,table_pos,"data"); + OutFile::vSymbol& symbols=f.GetSymbols(); + lua_newtable(L); + for(size_t i=0;i &commands) +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(loadObjectFile), + DFHACK_LUA_END +}; +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - lua::state st=lua::glua::Get(); - - //maybe remake it to run automatically - Lua::Open(out, st); - - lua::RegisterProcess(st); - lua::RegisterHexsearch(st); - lua::RegisterMisc(st); - - #ifdef LINUX_BUILD - st.push(1); - st.setglobal("LINUX"); - #else - st.push(1); - st.setglobal("WINDOWS"); - #endif - - commands.push_back(PluginCommand("dfusion","Run dfusion system (interactive i.e. can input further commands).",dfusion,true)); - commands.push_back(PluginCommand("dfuse","Init dfusion system (not interactive).",dfuse,false)); - mymutex=new tthread::mutex; return CR_OK; -} - -DFhackCExport command_result plugin_shutdown ( Core * c ) -{ - -// shutdown stuff - if(thread_dfusion) - delete thread_dfusion; - delete mymutex; - return CR_OK; -} - -DFhackCExport command_result plugin_onupdate_DISABLED ( Core * c ) -{ - uint64_t time2 = GetTimeMs64(); - uint64_t delta = time2-timeLast; - if(delta<100) - return CR_OK; - timeLast = time2; - mymutex->lock(); - lua::state s=lua::glua::Get(); - s.getglobal("OnTick"); - if(s.is()) - { - try{ - s.pcall(); - } - catch(lua::exception &e) - { - c->getConsole().printerr("Error OnTick:%s\n",e.what()); - c->getConsole().printerr("%s\n",lua::DebugDump(lua::glua::Get()).c_str()); - c->getConsole().msleep(1000); - } - } - s.settop(0); - mymutex->unlock(); - return CR_OK; -} -void RunDfusion(color_ostream &out, std::vector ¶meters) -{ - mymutex->lock(); - lua::state s=lua::glua::Get(); - try{ - s.loadfile("dfusion/init.lua"); //load script - for(size_t i=0;iunlock(); -} -command_result dfuse(color_ostream &out, std::vector ¶meters) -{ - lua::state s=lua::glua::Get(); - s.push(1); - s.setglobal("INIT"); - RunDfusion(out,parameters); - return CR_OK; -} -command_result dfusion (color_ostream &out, std::vector ¶meters) -{ - lua::state s=lua::glua::Get(); - s.push(); - s.setglobal("INIT"); - RunDfusion(out,parameters); - return CR_OK; -} +} \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 7e621c4ea..a6781b385 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -541,8 +541,4 @@ function Allocate(size) curptr=curptr+size engine.poked(ptr,curptr) return curptr-size+ptr -end -dofile("dfusion/patterns.lua") -dofile("dfusion/patterns2.lua") -dofile("dfusion/itempatterns.lua") -dofile("dfusion/buildingpatterns.lua") \ No newline at end of file +end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/embark/a.out b/plugins/Dfusion/luafiles/embark/a.out deleted file mode 100644 index e69de29bb..000000000 diff --git a/plugins/Dfusion/luafiles/embark/embark.asm b/plugins/Dfusion/luafiles/embark/embark.asm index 644459ce5..d2fa91081 100644 --- a/plugins/Dfusion/luafiles/embark/embark.asm +++ b/plugins/Dfusion/luafiles/embark/embark.asm @@ -1,7 +1,7 @@ .intel_syntax -mov eax , [esp+0x1C] -caste: +mov eax , [esp+0x1C] # loop counter +mark_caste: movsx ecx, word ptr[eax*2+0xdeadbeef] -race: +mark_race: movzx eax,word ptr [eax*2+0xDEADBEEF] ret diff --git a/plugins/Dfusion/luafiles/embark/embark.o b/plugins/Dfusion/luafiles/embark/embark.o index 1c9c837c840eead962d5364f774aa47869b08db2..87f5bbd68f5c8d0aaf6f81485f653a440145a952 100644 GIT binary patch delta 59 ycmcb^^pR=832_D>V1Z&rAk796VBnv4zfw{X$jD7B%8pM?EG|ifFpCnCQyBnH+YLDY delta 38 ocmey!bcboe3Gw8_;*wMb1_l-&W&~n3hN8seRECN7D_KB70MFV8=l}o! diff --git a/plugins/Dfusion/src/OutFile.cpp b/plugins/Dfusion/src/OutFile.cpp index 2d8399b03..5617175f8 100644 --- a/plugins/Dfusion/src/OutFile.cpp +++ b/plugins/Dfusion/src/OutFile.cpp @@ -1,19 +1,21 @@ #include "OutFile.h" +#include using namespace OutFile; File::File(std::string path) { //mystream.exceptions ( std::fstream::eofbit | std::fstream::failbit | std::fstream::badbit ); mystream.open(path.c_str(),std::fstream::binary|std::ios::in|std::ios::out); - mystream.read((char*)&myhead,sizeof(myhead)); - for(unsigned i=0;i Date: Sun, 7 Oct 2012 17:35:41 -0500 Subject: [PATCH 045/472] 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 046/472] 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 047/472] 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 048/472] 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 5cf42fd6f8b61efe302738d20bb1585b2704a5f3 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 14:57:45 +0200 Subject: [PATCH 049/472] ruby: add Matinfo#=== --- plugins/ruby/material.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/ruby/material.rb b/plugins/ruby/material.rb index 4a92118d6..ca0a64779 100644 --- a/plugins/ruby/material.rb +++ b/plugins/ruby/material.rb @@ -189,6 +189,10 @@ module DFHack end def to_s ; token ; end + + def ===(other) + other.mat_index == mat_index and other.mat_type == mat_type + end end class << self From 7c969f774f5ea86aaf9251c02e187014c73986c6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 10 Oct 2012 18:22:01 +0400 Subject: [PATCH 050/472] 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 051/472] 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] From 46a24a83fb660d46aed0a8304fb68d00e675c785 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 18:01:57 +0200 Subject: [PATCH 052/472] ruby: fix item_find(:selected) in item details screen --- plugins/ruby/item.rb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 9a254d9e8..469ec7449 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -6,8 +6,12 @@ module DFHack if what == :selected case curview._rtti_classname when :viewscreen_itemst - ref = curview.entry_ref[curview.cursor_pos] - ref.item_tg if ref.kind_of?(GeneralRefItem) + if ref = curview.entry_ref[curview.cursor_pos] + ref.item_tg if ref.kind_of?(GeneralRefItem) + else + # not a container + curview.item + end when :viewscreen_storesst # z/stocks if curview.in_group_mode == 0 and curview.in_right_list == 1 curview.items[curview.item_cursor] From e3e01107b91c9b22524c1c0d1e93d3932b53f491 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 18:56:23 +0200 Subject: [PATCH 053/472] fix deathcause for new df-structures --- scripts/deathcause.rb | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index 178ebbc87..0ed54d81a 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -1,15 +1,13 @@ # show death cause of a creature -def display_event(e) - p e if $DEBUG - - str = "#{e.victim_tg.name} died in year #{e.year}" - str << " of #{e.death_cause}" if false - str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_tg.name}" if e.slayer != -1 +def display_death_event(e) + str = "The #{e.victim_hf_tg.race_tg.name[0]} #{e.victim_hf_tg.name} died in year #{e.year}" + str << " (cause: #{e.death_cause.to_s.downcase})," + str << " killed by the #{e.slayer_race_tg.name[0]} #{e.slayer_hf_tg.name}" if e.slayer_hf != -1 str << " using a #{df.world.raws.itemdefs.weapons[e.weapon.item_subtype].name}" if e.weapon.item_type == :WEAPON str << ", shot by a #{df.world.raws.itemdefs.weapons[e.weapon.bow_item_subtype].name}" if e.weapon.bow_item_type == :WEAPON - puts str + '.' + puts str.chomp(',') + '.' end item = df.item_find(:selected) @@ -21,14 +19,15 @@ end if !item or !item.kind_of?(DFHack::ItemBodyComponent) puts "Please select a corpse in the loo'k' menu" else - hfig = item.hist_figure_id - if hfig == -1 - puts "Not a historical figure, cannot find info" + hf = item.hist_figure_id + if hf == -1 + # TODO try to retrieve info from the unit (u = item.unit_tg) + puts "Not a historical figure, cannot death find info" else events = df.world.history.events (0...events.length).reverse_each { |i| - if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim == hfig - display_event(events[i]) + if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim_hf == hf + display_death_event(events[i]) break end } From b3b93f818d097f88cb2517df4de5fb64b1ac3d69 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 19:25:30 +0200 Subject: [PATCH 054/472] slayrace: show number of creatures per race --- scripts/slayrace.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index b1fc24e35..749d0189b 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -33,12 +33,14 @@ slayit = lambda { |u| end } -all_races = df.world.units.active.map { |u| - u.race_tg.creature_id if checkunit[u] -}.compact.uniq.sort +all_races = Hash.new(0) + +df.world.units.active.map { |u| + all_races[u.race_tg.creature_id] += 1 if checkunit[u] +} if !race - puts all_races + all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } elsif race == 'him' if him = df.unit_find slayit[him] @@ -46,7 +48,7 @@ elsif race == 'him' puts "Choose target" end else - raw_race = df.match_rawname(race, all_races) + raw_race = df.match_rawname(race, all_races.keys) raise 'invalid race' if not raw_race race_nr = df.world.raws.creatures.all.index { |cr| cr.creature_id == raw_race } From b5f5d1f85b56fe0d7bd804fe881ce8a50310de10 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 10 Oct 2012 19:42:36 +0200 Subject: [PATCH 055/472] removebadthoughts: add --dry-run option --- NEWS | 1 + Readme.rst | 16 +++++++------ scripts/removebadthoughts.rb | 44 ++++++++++++++++++++++++------------ 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/NEWS b/NEWS index 65339ad64..192b93aed 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,7 @@ DFHack future Nothing yet! + - removebadthoughts: add --dry-run option DFHack v0.34.11-r2 diff --git a/Readme.rst b/Readme.rst index b5c0f335d..7ce15015a 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1632,16 +1632,18 @@ 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 -clear all your fort's units minds. +The script can target a single creature, when used with the ``him`` argument, +or the whole fort population, with ``all``. + +To show every bad thought present without actually removing them, run the +script with the ``-n`` or ``--dry-run`` argument. This can give a quick +hint on what bothers your dwarves the most. Individual dwarf happiness may not increase right after this command is run, but in the short term your dwarves will get much more joyful. -The thoughts are set to be very old, and the game will remove them soon when -you unpause. -With the optional ``-v`` parameter, the script will dump the negative thoughts -it removed. +Internals: the thoughts are set to be very old, so that the game remove them +quickly after you unpause. slayrace @@ -1650,7 +1652,7 @@ Kills any unit of a given race. With no argument, lists the available races. -With the special argument 'him', targets only the selected creature. +With the special argument ``him``, targets only the selected creature. Any non-dead non-caged unit of the specified race gets its ``blood_count`` set to 0, which means immediate death at the next game tick. For creatures diff --git a/scripts/removebadthoughts.rb b/scripts/removebadthoughts.rb index 99b742643..ff98f8f65 100644 --- a/scripts/removebadthoughts.rb +++ b/scripts/removebadthoughts.rb @@ -1,27 +1,43 @@ # remove bad thoughts for the selected unit or the whole fort -# with removebadthoughts -v, dump the bad thoughts types we removed -verbose = $script_args.delete('-v') +dry_run = $script_args.delete('--dry-run') || $script_args.delete('-n') -if u = df.unit_find(:selected) - targets = [u] -else - targets = df.unit_citizens -end +$script_args << 'all' if dry_run and $script_args.empty? seenbad = Hash.new(0) -targets.each { |u| +clear_mind = lambda { |u| u.status.recent_events.each { |e| next if DFHack::UnitThoughtType::Value[e.type].to_s[0, 1] != '-' seenbad[e.type] += 1 - e.age = 0x1000_0000 + e.age = 0x1000_0000 unless dry_run } } -if verbose - seenbad.sort_by { |k, v| v }.each { |k, v| puts " #{v} #{k}" } -end +summary = lambda { + seenbad.sort_by { |thought, cnt| cnt }.each { |thought, cnt| + puts " #{thought} #{cnt}" + } + count = seenbad.values.inject(0) { |sum, cnt| sum+cnt } + puts "Removed #{count} bad thought#{'s' if count != 1}." if count > 0 and not dry_run +} -count = seenbad.values.inject(0) { |s, v| s+v } -puts "removed #{count} bad thought#{'s' if count != 1}" +case $script_args[0] +when 'him' + if u = df.unit_find + clear_mind[u] + summary[] + else + puts 'Please select a dwarf ingame' + end + +when 'all' + df.unit_citizens.each { |uu| + clear_mind[uu] + } + summary[] + +else + puts "Usage: removebadthoughts [--dry-run] " + +end From 599577780add8e8c7caaba281b584c50dacdb9a0 Mon Sep 17 00:00:00 2001 From: ab9rf Date: Wed, 10 Oct 2012 23:46:01 -0500 Subject: [PATCH 056/472] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index d9c765cce..a5a8fd689 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d9c765ccea1d98ac08d33316dbb12f62df271690 +Subproject commit a5a8fd68947b60fcb2d1c03b4f05bdf48cfcf7a5 From 010417c8122bdb16cc881ccba6e2d236a8c01491 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 12:36:17 +0400 Subject: [PATCH 057/472] Compute detailed focus string for the hauling menu. --- library/modules/Gui.cpp | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index b211f9f2b..04a453c06 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -85,6 +85,8 @@ using namespace DFHack; #include "df/assign_trade_status.h" #include "df/announcement_flags.h" #include "df/announcements.h" +#include "df/stop_depart_condition.h" +#include "df/route_stockpile_link.h" using namespace df::enums; using df::global::gview; @@ -303,6 +305,45 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) focus += "/List"; break; + case Hauling: + if (ui->hauling.in_assign_vehicle) + { + auto vehicle = vector_get(ui->hauling.vehicles, ui->hauling.cursor_vehicle); + focus += "/AssignVehicle/" + std::string(vehicle ? "Some" : "None"); + } + else + { + int idx = ui->hauling.cursor_top; + auto route = vector_get(ui->hauling.view_routes, idx); + auto stop = vector_get(ui->hauling.view_stops, idx); + std::string tag = stop ? "Stop" : (route ? "Route" : "None"); + + if (ui->hauling.in_name) + focus += "/Rename/" + tag; + else if (ui->hauling.in_stop) + { + int sidx = ui->hauling.cursor_stop; + auto cond = vector_get(ui->hauling.stop_conditions, sidx); + auto link = vector_get(ui->hauling.stop_links, sidx); + + focus += "/DefineStop"; + + if (cond) + focus += "/Cond/" + enum_item_key(cond->mode); + else if (link) + { + focus += "/Link/"; + if (link->mode.bits.give) focus += "Give"; + if (link->mode.bits.take) focus += "Take"; + } + else + focus += "/None"; + } + else + focus += "/Select/" + tag; + } + break; + default: break; } From 5206236b014850518e2de5f47e4534bc1b10bd48 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 15:10:19 +0400 Subject: [PATCH 058/472] Look through missing intermediate bases when interposing subclasses. --- library/VTableInterpose.cpp | 20 ++++++++++++++++++-- library/include/VTableInterpose.h | 2 +- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 141450615..d8a07e830 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -281,9 +281,10 @@ VMethodInterposeLinkBase *VMethodInterposeLinkBase::get_first_interpose(virtual_ return item; } -void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) +bool VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmptr) { auto &children = cur->getChildren(); + bool found = false; for (size_t i = 0; i < children.size(); i++) { @@ -298,17 +299,32 @@ void VMethodInterposeLinkBase::find_child_hosts(virtual_identity *cur, void *vmp continue; child_next.insert(base); + found = true; } - else + else if (child->vtable_ptr) { void *cptr = child->get_vmethod_ptr(vmethod_idx); if (cptr != vmptr) continue; child_hosts.insert(child); + found = true; + find_child_hosts(child, vmptr); } + else + { + // If this vtable is not known, but any of the children + // have the same vmethod, this one definitely does too + if (find_child_hosts(child, vmptr)) + { + child_hosts.insert(child); + found = true; + } + } } + + return found; } void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index b52b32270..bd3c23036 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -159,7 +159,7 @@ namespace DFHack void on_host_delete(virtual_identity *host); VMethodInterposeLinkBase *get_first_interpose(virtual_identity *id); - void find_child_hosts(virtual_identity *cur, void *vmptr); + bool find_child_hosts(virtual_identity *cur, void *vmptr); public: VMethodInterposeLinkBase(virtual_identity *host, int vmethod_idx, void *interpose_method, void *chain_mptr, int priority); ~VMethodInterposeLinkBase(); From 2865e1373aaa366b1121806408726a9b8c087fa6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 17:34:34 +0400 Subject: [PATCH 059/472] Experimental API for associating tile bitmasks with persistent data. Use block_square_event_world_constructionst objects with the same bogus negative id as the matching historical figure object. --- Lua API.html | 27 ++++++++ Lua API.rst | 34 ++++++++++ library/LuaApi.cpp | 52 ++++++++++++++- .../df/custom/tile_bitmask.methods.inc | 6 +- library/include/modules/World.h | 9 +++ library/modules/World.cpp | 65 +++++++++++++++++++ library/xml | 2 +- 7 files changed, 189 insertions(+), 6 deletions(-) diff --git a/Lua API.html b/Lua API.html index 06fa5418e..125687110 100644 --- a/Lua API.html +++ b/Lua API.html @@ -932,6 +932,21 @@ Returns entry, did_create_new

                                              and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.

                                              +

                                              It is also possible to associate one bit per map tile with an entry, +using these two methods:

                                              +
                                                +
                                              • entry:getTilemask(block[, create])

                                                +

                                                Retrieves the tile bitmask associated with this entry in the given map +block. If create is true, an empty mask is created if none exists; +otherwise the function returns nil, which must be assumed to be the same +as an all-zero mask.

                                                +
                                              • +
                                              • entry:deleteTilemask(block)

                                                +

                                                Deletes the associated tile mask from the given map block.

                                                +
                                              • +
                                              +

                                              Note that these masks are only saved in fortress mode, and also that deleting +the persistent entry will NOT delete the associated masks.

                                              Material info lookup

                                              @@ -1286,6 +1301,18 @@ tools like liquids or tiletypes are used. It also cannot possibly take into account anything that depends on the actual units, like burrows, or the presence of invaders.

                                              +
                                            • dfhack.maps.hasTileAssignment(tilemask)

                                              +

                                              Checks if the tile_bitmask object is not nil and contains any set bits; returns true or false.

                                              +
                                            • +
                                            • dfhack.maps.getTileAssignment(tilemask,x,y)

                                              +

                                              Checks if the tile_bitmask object is not nil and has the relevant bit set; returns true or false.

                                              +
                                            • +
                                            • dfhack.maps.setTileAssignment(tilemask,x,y,enable)

                                              +

                                              Sets the relevant bit in the tile_bitmask object to the enable argument.

                                              +
                                            • +
                                            • dfhack.maps.resetTileAssignment(tilemask[,enable])

                                              +

                                              Sets all bits in the mask to the enable argument.

                                              +
                                            • diff --git a/Lua API.rst b/Lua API.rst index fbb4b7d82..7ad6aec23 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -646,6 +646,24 @@ and automatically stored in the save game, these save and retrieval functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. +It is also possible to associate one bit per map tile with an entry, +using these two methods: + +* ``entry:getTilemask(block[, create])`` + + Retrieves the tile bitmask associated with this entry in the given map + block. If ``create`` is *true*, an empty mask is created if none exists; + otherwise the function returns *nil*, which must be assumed to be the same + as an all-zero mask. + +* ``entry:deleteTilemask(block)`` + + Deletes the associated tile mask from the given map block. + +Note that these masks are only saved in fortress mode, and also that deleting +the persistent entry will **NOT** delete the associated masks. + + Material info lookup -------------------- @@ -1079,6 +1097,22 @@ Maps module take into account anything that depends on the actual units, like burrows, or the presence of invaders. +* ``dfhack.maps.hasTileAssignment(tilemask)`` + + Checks if the tile_bitmask object is not *nil* and contains any set bits; returns *true* or *false*. + +* ``dfhack.maps.getTileAssignment(tilemask,x,y)`` + + Checks if the tile_bitmask object is not *nil* and has the relevant bit set; returns *true* or *false*. + +* ``dfhack.maps.setTileAssignment(tilemask,x,y,enable)`` + + Sets the relevant bit in the tile_bitmask object to the *enable* argument. + +* ``dfhack.maps.resetTileAssignment(tilemask[,enable])`` + + Sets all bits in the mask to the *enable* argument. + Burrows module -------------- diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0df96f066..ef571bcb7 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -30,6 +30,7 @@ distribution. #include "MemAccess.h" #include "Core.h" +#include "Error.h" #include "VersionInfo.h" #include "tinythread.h" // must be last due to MS stupidity @@ -311,12 +312,12 @@ static PersistentDataItem get_persistent(lua_State *state) if (lua_istable(state, 1)) { + Lua::StackUnwinder frame(state); + if (!lua_getmetatable(state, 1) || !lua_rawequal(state, -1, lua_upvalueindex(1))) luaL_argerror(state, 1, "invalid table type"); - lua_settop(state, 1); - return persistent_by_struct(state, 1); } else @@ -446,11 +447,38 @@ static int dfhack_persistent_save(lua_State *state) return 2; } +static int dfhack_persistent_getTilemask(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 3); + auto ref = get_persistent(state); + auto block = Lua::CheckDFObject(state, 2); + bool create = lua_toboolean(state, 3); + + Lua::PushDFObject(state, World::getPersistentTilemask(ref, block, create)); + return 1; +} + +static int dfhack_persistent_deleteTilemask(lua_State *state) +{ + CoreSuspender suspend; + + lua_settop(state, 2); + auto ref = get_persistent(state); + auto block = Lua::CheckDFObject(state, 2); + + lua_pushboolean(state, World::deletePersistentTilemask(ref, block)); + return 1; +} + static const luaL_Reg dfhack_persistent_funcs[] = { { "get", dfhack_persistent_get }, { "delete", dfhack_persistent_delete }, { "get_all", dfhack_persistent_get_all }, { "save", dfhack_persistent_save }, + { "getTilemask", dfhack_persistent_getTilemask }, + { "deleteTilemask", dfhack_persistent_deleteTilemask }, { NULL, NULL } }; @@ -937,6 +965,22 @@ static const luaL_Reg dfhack_items_funcs[] = { /***** Maps module *****/ +static bool hasTileAssignment(df::tile_bitmask *bm) { + return bm && bm->has_assignments(); +} +static bool getTileAssignment(df::tile_bitmask *bm, int x, int y) { + return bm && bm->getassignment(x,y); +} +static void setTileAssignment(df::tile_bitmask *bm, int x, int y, bool val) { + CHECK_NULL_POINTER(bm); + bm->setassignment(x,y,val); +} +static void resetTileAssignment(df::tile_bitmask *bm, bool val) { + CHECK_NULL_POINTER(bm); + if (val) bm->set_all(); + else bm->clear(); +} + static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), WRAPM(Maps, enableBlockUpdates), @@ -944,6 +988,10 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, getLocalInitFeature), WRAPM(Maps, canWalkBetween), WRAPM(Maps, spawnFlow), + WRAPN(hasTileAssignment, hasTileAssignment), + WRAPN(getTileAssignment, getTileAssignment), + WRAPN(setTileAssignment, setTileAssignment), + WRAPN(resetTileAssignment, resetTileAssignment), { NULL, NULL } }; diff --git a/library/include/df/custom/tile_bitmask.methods.inc b/library/include/df/custom/tile_bitmask.methods.inc index 18b312a08..e3414e334 100644 --- a/library/include/df/custom/tile_bitmask.methods.inc +++ b/library/include/df/custom/tile_bitmask.methods.inc @@ -16,7 +16,7 @@ inline bool getassignment( const df::coord2d &xy ) } inline bool getassignment( int x, int y ) { - return (bits[y] & (1 << x)); + return (bits[(y&15)] & (1 << (x&15))); } inline void setassignment( const df::coord2d &xy, bool bit ) { @@ -25,9 +25,9 @@ inline void setassignment( const df::coord2d &xy, bool bit ) inline void setassignment( int x, int y, bool bit ) { if(bit) - bits[y] |= (1 << x); + bits[(y&15)] |= (1 << (x&15)); else - bits[y] &= ~(1 << x); + bits[(y&15)] &= ~(1 << (x&15)); } bool has_assignments() { diff --git a/library/include/modules/World.h b/library/include/modules/World.h index f4c31dcf3..a945c4e72 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -37,6 +37,12 @@ distribution. #include "DataDefs.h" +namespace df +{ + struct tile_bitmask; + struct map_block; +} + namespace DFHack { typedef df::game_mode GameMode; @@ -120,6 +126,9 @@ namespace DFHack DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item); DFHACK_EXPORT void ClearPersistentCache(); + + DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false); + DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block); } } #endif diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 65d0c7cb8..f3283c3a3 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -38,13 +38,18 @@ using namespace std; #include "ModuleFactory.h" #include "Core.h" +#include "modules/Maps.h" + #include "MiscUtils.h" #include "DataDefs.h" #include "df/world.h" #include "df/historical_figure.h" +#include "df/map_block.h" +#include "df/block_square_event_world_constructionst.h" using namespace DFHack; +using namespace df::enums; using df::global::world; @@ -312,3 +317,63 @@ bool World::DeletePersistentData(const PersistentDataItem &item) return false; } + +df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create) +{ + if (!block) + return NULL; + + int id = item.raw_id(); + if (id > -100) + return NULL; + + for (size_t i = 0; i < block->block_events.size(); i++) + { + auto ev = block->block_events[i]; + if (ev->getType() != block_square_event_type::world_construction) + continue; + auto wcsev = strict_virtual_cast(ev); + if (!wcsev || wcsev->construction_id != id) + continue; + return &wcsev->tile_bitmask; + } + + if (!create) + return NULL; + + auto ev = df::allocate(); + if (!ev) + return NULL; + + ev->construction_id = id; + ev->tile_bitmask.clear(); + vector_insert_at(block->block_events, 0, (df::block_square_event*)ev); + + return &ev->tile_bitmask; +} + +bool World::deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block) +{ + if (!block) + return false; + int id = item.raw_id(); + if (id > -100) + return false; + + bool found = false; + for (int i = block->block_events.size()-1; i >= 0; i--) + { + auto ev = block->block_events[i]; + if (ev->getType() != block_square_event_type::world_construction) + continue; + auto wcsev = strict_virtual_cast(ev); + if (!wcsev || wcsev->construction_id != id) + continue; + + delete wcsev; + vector_erase_at(block->block_events, i); + found = true; + } + + return found; +} diff --git a/library/xml b/library/xml index 20c6d9c74..aec106cdc 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 20c6d9c743f1c5a20bb288f427b101e9b2a138d7 +Subproject commit aec106cdc32083bbcc6d6dd27d9e069f5525ea92 From 5f9489a8432d638b2ecb2e264c592025a1e1dde6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 11 Oct 2012 19:32:41 +0400 Subject: [PATCH 060/472] Start making a script for viewing and changing minecart Guide paths. --- dfhack.init-example | 3 + scripts/gui/guide-path.lua | 201 +++++++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) create mode 100644 scripts/gui/guide-path.lua diff --git a/dfhack.init-example b/dfhack.init-example index 8505f5acc..3ecbf4342 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -72,6 +72,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine # military weapon auto-select keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons +# minecart Guide path +keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path + ############################ # UI and game logic tweaks # ############################ diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua new file mode 100644 index 000000000..f3e27d917 --- /dev/null +++ b/scripts/gui/guide-path.lua @@ -0,0 +1,201 @@ +-- Show and manipulate the path used by Guide Cart orders. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local tile_attrs = df.tiletype.attrs + +function same_xyz(a,b) + return a and b and a.x == b.x and a.y == b.y and a.z == b.z +end + +GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) + +GuidePathUI.focus_path = 'guide-path' + +GuidePathUI.ATTRS { + route = DEFAULT_NIL, + stop = DEFAULT_NIL, + order = DEFAULT_NIL, +} + +function GuidePathUI:init() + self.saved_mode = df.global.ui.main.mode + + for i=0,#self.route.stops-1 do + if self.route.stops[i] == self.stop then + self.stop_idx = i + break + end + end + + if not self.stop_idx then + error('Could not find stop index') + end + + self.next_stop = self.route.stops[(self.stop_idx+1)%#self.route.stops] +end + +function GuidePathUI:onShow() + -- with cursor, but without those ugly lines from native hauling mode + df.global.ui.main.mode = df.ui_sidebar_mode.Stockpiles +end + +function GuidePathUI:onDestroy() + df.global.ui.main.mode = self.saved_mode +end + +local function getTileType(cursor) + local block = dfhack.maps.getTileBlock(cursor) + if block then + return block.tiletype[cursor.x%16][cursor.y%16] + else + return 0 + end +end + +local function isTrackTile(tile) + return tile_attrs[tile].special == df.tiletype_special.TRACK +end + +local function paintMapTile(dc, vp, cursor, pos, ...) + if not same_xyz(cursor, pos) then + local stile = vp:tileToScreen(pos) + if stile.z == 0 then + dc:seek(stile.x,stile.y):char(...) + end + end +end + +local function get_path_point(gpath,i) + return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) +end + +local function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + +function GuidePathUI:renderPath(cursor) + local gpath = self.order.guide_path + local startp = self.stop.pos + local endp = self.next_stop.pos + local vp = self:getViewport() + local dc = gui.Painter.new(self.df_layout.map) + local visible = blink_visible(500) + + if visible then + paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) + end + + local ok = nil + local pcnt = #gpath.x + if pcnt > 0 then + ok = true + + for i = 0,pcnt-1 do + local pt = get_path_point(gpath, i) + if i == 0 and not same_xyz(startp,pt) then + ok = false + end + if i == pcnt-1 and not same_xyz(endp,pt) then + ok = false + end + local tile = getTileType(pt) + if not isTrackTile(tile) then + ok = false + end + if visible then + local char = '+' + if i < pcnt-1 then + local npt = get_path_point(gpath, i+1) + if npt.x == pt.x+1 then + char = 26 + elseif npt.x == pt.x-1 then + char = 27 + elseif npt.y == pt.y+1 then + char = 25 + elseif npt.y == pt.y-1 then + char = 24 + end + end + local color = COLOR_LIGHTGREEN + if not ok then color = COLOR_LIGHTRED end + paintMapTile(dc, vp, cursor, pt, char, color) + end + end + end + + if blink_visible(120) then + paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) + end + + return ok +end + +function GuidePathUI:onRenderBody(dc) + dc:clear():seek(1,1):pen(COLOR_WHITE):string("Guide Path") + + dc:seek(2,3) + + local cursor = guidm.getCursorPos() + local path_ok = self:renderPath(cursor) + + if path_ok == nil then + dc:string('No saved path', COLOR_DARKGREY) + elseif path_ok then + dc:string('Valid path', COLOR_GREEN) + else + dc:string('Invalid path', COLOR_RED) + end + + dc:newline():newline(1) + dc:key('CUSTOM_Z'):string(": Reset path, ",COLOR_GREY,nil,path_ok~=nil) + dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) + dc:newline(1) + dc:key('CUSTOM_C'):string(": Zoom cur, ") + dc:key('CUSTOM_N'):string(": Zoom next") + + dc:newline():newline(1):string('At cursor:') + dc:newline():newline(2) + + local tile = getTileType(cursor) + if isTrackTile(tile) then + dc:string('Track '..tile_attrs[tile].direction, COLOR_GREEN) + else + dc:string('No track', COLOR_DARKGREY) + end + + dc:newline():newline(1) + dc:key('LEAVESCREEN'):string(": Back") +end + +function GuidePathUI:onInput(keys) + if keys.CUSTOM_C then + self:moveCursorTo(copyall(self.stop.pos)) + elseif keys.CUSTOM_N then + self:moveCursorTo(copyall(self.next_stop.pos)) + elseif keys.CUSTOM_Z then + local gpath = self.order.guide_path + gpath.x:resize(0) + gpath.y:resize(0) + gpath.z:resize(0) + elseif keys.LEAVESCREEN then + self:dismiss() + elseif self:propagateMoveKeys(keys) then + return + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/Hauling/DefineStop/Cond/Guide') then + qerror("This script requires the main dwarfmode view in 'h' mode over a Guide order") +end + +local hauling = df.global.ui.hauling +local route = hauling.view_routes[hauling.cursor_top] +local stop = hauling.view_stops[hauling.cursor_top] +local order = hauling.stop_conditions[hauling.cursor_stop] + +local list = GuidePathUI{ route = route, stop = stop, order = order } +list:show() From 424c37c0ea23cd9fb822ce61a3964e16502478f8 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 11 Oct 2012 17:51:49 +0200 Subject: [PATCH 061/472] ruby: fix codegen to handle composite vtables --- plugins/ruby/codegen.pl | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index a615ab964..8dae55597 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -232,7 +232,11 @@ sub render_global_class { render_struct_fields($type); my $vms = $type->findnodes('child::virtual-methods')->[0]; - render_class_vmethods($vms) if $vms; + if ($vms) + { + my $voff = render_class_vmethods_voff($parent); + render_class_vmethods($vms, $voff); + } }; push @lines_rb, "end\n"; } @@ -368,9 +372,29 @@ sub render_container_reftarget { } } +# return the size of the parent's vtables +sub render_class_vmethods_voff { + my ($name) = @_; + + return 0 if !$name; + + my $type = $global_types{$name}; + my $parent = $type->getAttribute('inherits-from'); + + my $voff = render_class_vmethods_voff($parent); + my $vms = $type->findnodes('child::virtual-methods')->[0]; + + for my $meth ($vms->findnodes('child::vmethod')) + { + $voff += 4 if $meth->getAttribute('is-destructor') and $os eq 'linux'; + $voff += 4; + } + + return $voff; +} + sub render_class_vmethods { - my ($vms) = @_; - my $voff = 0; + my ($vms, $voff) = @_; for my $meth ($vms->findnodes('child::vmethod')) { From 12eeb9e5c0f5b15bd9976e07f4ba09d34774d3f8 Mon Sep 17 00:00:00 2001 From: ab9rf Date: Fri, 12 Oct 2012 00:38:45 -0500 Subject: [PATCH 062/472] Correct wrong variable use in start_autonestbox. --- plugins/zone.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 542ce8a02..2c63b6af7 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3498,11 +3498,11 @@ command_result start_autonestbox(color_ostream &out) { enable_autonestbox = true; - if (!config_autobutcher.isValid()) + if (!config_autonestbox.isValid()) { config_autonestbox = World::AddPersistentData("autonestbox/config"); - if (!config_autobutcher.isValid()) + if (!config_autonestbox.isValid()) { out << "Cannot enable autonestbox without a world!" << endl; return CR_OK; From 0547ee7f831e9e10442ea52bbe0ebfeb344f9f90 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 11:12:31 +0200 Subject: [PATCH 063/472] ruby: add magic "nolock " prefix to run ruby without Suspending main df --- plugins/ruby/ruby.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 7bd6d13e8..13190f70d 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -118,18 +118,8 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } -// send a single ruby line to be evaluated by the ruby thread -DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) +static command_result do_plugin_eval_ruby(color_ostream &out, const char *command) { - // if dlopen failed - if (!r_thread) - return CR_FAILURE; - - // wrap all ruby code inside a suspend block - // if we dont do that and rely on ruby code doing it, we'll deadlock in - // onupdate - CoreSuspender suspend; - command_result ret; // ensure ruby thread is idle @@ -159,6 +149,27 @@ DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *c return ret; } +// send a single ruby line to be evaluated by the ruby thread +DFhackCExport command_result plugin_eval_ruby( color_ostream &out, const char *command) +{ + // if dlopen failed + if (!r_thread) + return CR_FAILURE; + + if (!strncmp(command, "nolock ", 7)) { + // debug only! + // run ruby commands without locking the main thread + // useful when the game is frozen after a segfault + return do_plugin_eval_ruby(out, command+7); + } else { + // wrap all ruby code inside a suspend block + // if we dont do that and rely on ruby code doing it, we'll deadlock in + // onupdate + CoreSuspender suspend; + return do_plugin_eval_ruby(out, command); + } +} + DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (!r_thread) From 1f88c0eeed1999a252b0b3f0dd40252a7b983f99 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 11:42:42 +0200 Subject: [PATCH 064/472] ruby: codegen: avoid bad vector_reftg accessor --- plugins/ruby/codegen.pl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 8dae55597..ff69853af 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -279,7 +279,6 @@ sub render_struct_field_refs { my ($parent, $field, $name) = @_; my $reftg = $field->getAttribute('ref-target'); - render_field_reftarget($parent, $field, $name, $reftg) if ($reftg); my $refto = $field->getAttribute('refers-to'); render_field_refto($parent, $name, $refto) if ($refto); @@ -289,6 +288,8 @@ sub render_struct_field_refs { if ($meta and $meta eq 'container' and $item) { my $itemreftg = $item->getAttribute('ref-target'); render_container_reftarget($parent, $item, $name, $itemreftg) if $itemreftg; + } elsif ($reftg) { + render_field_reftarget($parent, $field, $name, $reftg); } } From c089534f7315190b6df7d6ce6390b99fc3adc6b0 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 12 Oct 2012 13:33:58 +0200 Subject: [PATCH 065/472] ruby: fix assigning value to pointer to number --- plugins/ruby/ruby-autogen-defs.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 4470c8022..d5bcb08f4 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -282,6 +282,10 @@ module DFHack DFHack.memory_read_int32(@_memaddr) & 0xffffffff end + def _setp(v) + DFHack.memory_write_int32(@_memaddr, v) + end + def _get addr = _getp return if addr == 0 @@ -294,7 +298,15 @@ module DFHack case v when Pointer; DFHack.memory_write_int32(@_memaddr, v._getp) when MemStruct; DFHack.memory_write_int32(@_memaddr, v._memaddr) - when Integer; DFHack.memory_write_int32(@_memaddr, v) + when Integer + if @_tg and @_tg.kind_of?(MemHack::Number) + if _getp == 0 + _setp(DFHack.malloc(@_tg._bits/8)) + end + @_tg._at(_getp)._set(v) + else + DFHack.memory_write_int32(@_memaddr, v) + end when nil; DFHack.memory_write_int32(@_memaddr, 0) else _get._set(v) end From 33bd8103de43d1a6cb1d8a0f48819e4d753a94ce Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 15 Oct 2012 15:30:00 +0400 Subject: [PATCH 066/472] Extract an abstract View superclass from Screen to handle widget trees. --- Lua API.rst | 12 ++ library/lua/dfhack.lua | 16 ++ library/lua/gui.lua | 283 ++++++++++++++++++++++++---------- library/lua/gui/dialogs.lua | 2 +- library/lua/gui/dwarfmode.lua | 18 ++- scripts/gui/guide-path.lua | 4 - scripts/gui/hello-world.lua | 7 +- scripts/gui/siege-engine.lua | 6 +- 8 files changed, 249 insertions(+), 99 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 7ad6aec23..bbee8646c 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1727,6 +1727,18 @@ environment by the mandatory init file dfhack.lua: Returns a table with x, y and z as fields. +* ``same_xyz(a,b)`` + + Checks if ``a`` and ``b`` have the same x, y and z fields. + +* ``get_path_xyz(path,i)`` + + Returns ``path.x[i], path.y[i], path.z[i]``. + +* ``pos2xy(obj)``, ``xy2pos(x,y)``, ``same_xy(a,b)``, ``get_path_xy(a,b)`` + + Same as above, but for 2D coordinates. + * ``safe_index(obj,index...)`` Walks a sequence of dereferences, which may be represented by numbers or strings. diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index ce3be5a87..9fc94a025 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -157,6 +157,14 @@ function xyz2pos(x,y,z) end end +function same_xyz(a,b) + return a and b and a.x == b.x and a.y == b.y and a.z == b.z +end + +function get_path_xyz(path,i) + return path.x[i], path.y[i], path.z[i] +end + function pos2xy(pos) if pos then local x = pos.x @@ -174,6 +182,14 @@ function xy2pos(x,y) end end +function same_xy(a,b) + return a and b and a.x == b.x and a.y == b.y +end + +function get_path_xy(path,i) + return path.x[i], path.y[i] +end + function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil diff --git a/library/lua/gui.lua b/library/lua/gui.lua index a5e4f7503..4f98e1bde 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -50,8 +50,8 @@ function mkdims_wh(x1,y1,w,h) end function inset(rect,dx1,dy1,dx2,dy2) return mkdims_xy( - rect.x1+dx1, rect.y1+dy1, - rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1) + rect.x1+dx1, rect.y1+(dy1 or dx1), + rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1 or dx1) ) end function is_in_rect(rect,x,y) @@ -68,45 +68,68 @@ local function to_pen(default, pen, bg, bold) end end ----------------------------- --- Clipped painter object -- ----------------------------- +function getKeyDisplay(code) + if type(code) == 'string' then + code = df.interface_key[code] + end + return dscreen.getKeyDisplay(code) +end -Painter = defclass(Painter, nil) +----------------------------------- +-- Clipped view rectangle object -- +----------------------------------- -function Painter:init(args) - local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) - local crect = args.clip_rect or rect - self:assign{ - x = rect.x1, y = rect.y1, - x1 = rect.x1, clip_x1 = crect.x1, - y1 = rect.y1, clip_y1 = crect.y1, - x2 = rect.x2, clip_x2 = crect.x2, - y2 = rect.y2, clip_y2 = crect.y2, - width = rect.x2-rect.x1+1, - height = rect.y2-rect.y1+1, - cur_pen = to_pen(nil, args.pen or COLOR_GREY) - } +ViewRect = defclass(ViewRect, nil) + +function ViewRect:init(args) + if args.view_rect then + self:assign(args.view_rect) + else + local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local crect = args.clip_rect or rect + self:assign{ + x1 = rect.x1, clip_x1 = crect.x1, + y1 = rect.y1, clip_y1 = crect.y1, + x2 = rect.x2, clip_x2 = crect.x2, + y2 = rect.y2, clip_y2 = crect.y2, + width = rect.x2-rect.x1+1, + height = rect.y2-rect.y1+1, + } + end + if args.clip_view then + local cr = args.clip_view + self:assign{ + clip_x1 = math.max(self.clip_x1, cr.clip_x1), + clip_y1 = math.max(self.clip_y1, cr.clip_y1), + clip_x2 = math.min(self.clip_x2, cr.clip_x2), + clip_y2 = math.min(self.clip_y2, cr.clip_y2), + } + end end -function Painter.new(rect, pen) - return Painter{ rect = rect, pen = pen } +function ViewRect:isDefunct() + return (self.clip_x1 > self.clip_x2 or self.clip_y1 > self.clip_y2) end -function Painter.new_xy(x1,y1,x2,y2,pen) - return Painter{ rect = mkdims_xy(x1,y1,x2,y2), pen = pen } +function ViewRect:inClipGlobalXY(x,y) + return x >= self.clip_x1 and x <= self.clip_x2 + and y >= self.clip_y1 and y <= self.clip_y2 end -function Painter.new_wh(x,y,w,h,pen) - return Painter{ rect = mkdims_wh(x,y,w,h), pen = pen } +function ViewRect:inClipLocalXY(x,y) + return (x+self.x1) >= self.clip_x1 and (x+self.x1) <= self.clip_x2 + and (y+self.y1) >= self.clip_y1 and (y+self.y1) <= self.clip_y2 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 +function ViewRect:localXY(x,y) + return x-self.x1, y-self.y1 end -function Painter:viewport(x,y,w,h) +function ViewRect:globalXY(x,y) + return x+self.x1, y+self.y1 +end + +function ViewRect:viewport(x,y,w,h) if type(x) == 'table' then x,y,w,h = x.x1, x.y1, x.width, x.height end @@ -121,17 +144,57 @@ function Painter:viewport(x,y,w,h) clip_y1 = math.max(self.clip_y1, y1), clip_x2 = math.min(self.clip_x2, x2), clip_y2 = math.min(self.clip_y2, y2), - -- Pen - cur_pen = self.cur_pen } + return mkinstance(ViewRect, vp) +end + +---------------------------- +-- Clipped painter object -- +---------------------------- + +Painter = defclass(Painter, ViewRect) + +function Painter:init(args) + self.x = self.x1 + self.y = self.y1 + self.cur_pen = to_pen(nil, args.pen or COLOR_GREY) +end + +function Painter.new(rect, pen) + return Painter{ rect = rect, pen = pen } +end + +function Painter.new_view(view_rect, pen) + return Painter{ view_rect = view_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:inClipGlobalXY(self.x, self.y) +end + +function Painter:viewport(x,y,w,h) + local vp = ViewRect.viewport(x,y,w,h) + vp.cur_pen = self.cur_pen return mkinstance(Painter, vp):seek(0,0) end -function Painter:localX() +function Painter:cursor() + return self.x - self.x1, self.y - self.y1 +end + +function Painter:cursorX() return self.x - self.x1 end -function Painter:localY() +function Painter:cursorY() return self.y - self.y1 end @@ -219,25 +282,109 @@ function Painter:string(text,pen,...) end function Painter:key(code,pen,bg,...) - if type(code) == 'string' then - code = df.interface_key[code] - end return self:string( - dscreen.getKeyDisplay(code), + getKeyDisplay(code), pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... ) end +-------------------------- +-- Abstract view object -- +-------------------------- + +View = defclass(View) + +View.ATTRS { + active = true, + hidden = false, +} + +function View:init(args) + self.subviews = {} +end + +function View:getWindowSize() + local rect = self.frame_body + return rect.width, rect.height +end + +function View:getMousePos() + local rect = self.frame_body + local x,y = dscreen.getMousePos() + if rect and rect:inClipGlobalXY(x,y) then + return rect:localXY(x,y) + end +end + +function View:computeFrame(parent_rect) + return mkdims_wh(0,0,parent_rect.width,parent_rect.height) +end + +function View:updateLayout(parent_rect) + if not parent_rect then + parent_rect = self.frame_parent_rect + else + self.frame_parent_rect = parent_rect + end + + local frame_rect,body_rect = self:computeFrame(parent_rect) + + self.frame_rect = frame_rect + self.frame_body = parent_rect:viewport(body_rect or frame_rect) + + for _,child in ipairs(self.subviews) do + child:updateLayout(frame_body) + end + + self:invoke_after('postUpdateLayout',frame_body) +end + +function View:renderSubviews(dc) + for _,child in ipairs(self.subviews) do + if not child.hidden then + child:render(dc) + end + end +end + +function View:render(dc) + local sub_dc = Painter{ + view_rect = self.frame_body, + clip_view = dc + } + + self:onRenderBody(sub_dc) + + self:renderSubviews(sub_dc) +end + +function View:onRenderBody(dc) +end + +function View:inputToSubviews(keys) + for _,child in ipairs(self.subviews) do + if child.active and child:onInput(keys) then + return true + end + end + + return false +end + +function View:onInput(keys) + return self:inputToSubviews(keys) +end + ------------------------ -- Base screen object -- ------------------------ -Screen = defclass(Screen) +Screen = defclass(Screen, View) Screen.text_input_mode = false function Screen:postinit() - self:updateLayout() + self:onResize(dscreen.getWindowSize()) end Screen.isDismissed = dscreen.isDismissed @@ -254,14 +401,6 @@ function Screen:invalidate() dscreen.invalidate() end -function Screen:getWindowSize() - return dscreen.getWindowSize() -end - -function Screen:getMousePos() - return dscreen.getMousePos() -end - function Screen:renderParent() if self._native and self._native.parent then self._native.parent:render() @@ -290,7 +429,7 @@ function Screen:onAboutToShow() end function Screen:onShow() - self:updateLayout() + self:onResize(dscreen.getWindowSize()) end function Screen:dismiss() @@ -306,11 +445,11 @@ function Screen:onDestroy() end function Screen:onResize(w,h) - self:updateLayout() + self:updateLayout(ViewRect{ rect = mkdims_wh(0,0,w,h) }) end -function Screen:updateLayout() - self:invoke_after('postUpdateLayout') +function Screen:onRender() + self:render(Painter.new()) end ------------------------ @@ -372,6 +511,7 @@ FramedScreen.ATTRS{ frame_title = DEFAULT_NIL, frame_width = DEFAULT_NIL, frame_height = DEFAULT_NIL, + frame_inset = 0, } local function hint_coord(gap,hint) @@ -388,52 +528,35 @@ function FramedScreen:getWantedFrameSize() return self.frame_width, self.frame_height end -function FramedScreen:updateFrameSize() - local sw, sh = dscreen.getWindowSize() - local iw, ih = sw-2, sh-2 +function FramedScreen:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + local ins = self.frame_inset + local thickness = 2+ins*2 + local iw, ih = sw-thickness, sh-thickness local fw, fh = self:getWantedFrameSize() local width = math.min(fw or iw, iw) local height = math.min(fh or ih, ih) local gw, gh = iw-width, ih-height local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) - self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) self.frame_opaque = (gw == 0 and gh == 0) + return mkdims_wh(x1,y1,width+thickness,height+thickness), + mkdims_wh(x1+1+ins,y1+1+ins,width,height) end -function FramedScreen:postUpdateLayout() - self:updateFrameSize() -end - -function FramedScreen:getWindowSize() - local rect = self.frame_rect - return rect.width, rect.height -end - -function FramedScreen:getMousePos() - local rect = self.frame_rect - local x,y = dscreen.getMousePos() - if is_in_rect(rect,x,y) then - return x-rect.x1, y-rect.y1 - end -end - -function FramedScreen:onRender() +function FramedScreen:render(dc) local rect = self.frame_rect - local x1,y1,x2,y2 = rect.x1-1, rect.y1-1, rect.x2+1, rect.y2+1 + local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 if self.frame_opaque then - dscreen.clear() + dc:clear() else self:renderParent() - dscreen.fillRect(CLEAR_PEN,x1,y1,x2,y2) + dc:fill(rect, CLEAR_PEN) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) - self:onRenderBody(Painter.new(rect)) -end - -function FramedScreen:onRenderBody(dc) + FramedScreen.super.render(self, dc) end return _ENV diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 553bb3a4b..75f7349d1 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -120,7 +120,7 @@ function InputBox:onRenderBody(dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(1,dc:localY(),dc.width-2,dc:localY()) + dc:fill(1,dc:cursorY(),dc.width-2,dc:cursorY()) local cursor = '_' if math.floor(dfhack.getTickCount()/300) % 2 == 0 then diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 125e71220..4056fbfcc 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -253,8 +253,9 @@ end DwarfOverlay = defclass(DwarfOverlay, gui.Screen) -function DwarfOverlay:postUpdateLayout() +function DwarfOverlay:updateLayout(parent_rect) self.df_layout = getPanelLayout() + DwarfOverlay.super.updateLayout(self, parent_rect) end function DwarfOverlay:getViewport(old_vp) @@ -352,12 +353,13 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) -function MenuOverlay:postUpdateLayout() - self.frame_rect = self.df_layout.menu -end +MenuOverlay.ATTRS { + frame_inset = 0 +} -MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize -MenuOverlay.getMousePos = gui.FramedScreen.getMousePos +function MenuOverlay:computeFrame(parent_rect) + return self.df_layout.menu, gui.inset(self.df_layout.menu, self.frame_inset) +end function MenuOverlay:onAboutToShow(below) MenuOverlay.super.onAboutToShow(self,below) @@ -368,7 +370,7 @@ function MenuOverlay:onAboutToShow(below) end end -function MenuOverlay:onRender() +function MenuOverlay:render(dc) self:renderParent() local menu = self.df_layout.menu @@ -379,7 +381,7 @@ function MenuOverlay:onRender() menu.x1+1, menu.y2+1, "DFHack" ) - self:onRenderBody(gui.Painter.new(menu)) + MenuOverlay.super.render(self, dc) end end diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index f3e27d917..4d32a99c2 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -7,10 +7,6 @@ local dlg = require 'gui.dialogs' local tile_attrs = df.tiletype.attrs -function same_xyz(a,b) - return a and b and a.x == b.x and a.y == b.y and a.z == b.z -end - GuidePathUI = defclass(GuidePathUI, guidm.MenuOverlay) GuidePathUI.focus_path = 'guide-path' diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua index c8cd3bd01..122f2cd38 100644 --- a/scripts/gui/hello-world.lua +++ b/scripts/gui/hello-world.lua @@ -7,12 +7,13 @@ local text = 'Woohoo, lua viewscreen :)' local screen = gui.FramedScreen{ frame_style = gui.GREY_LINE_FRAME, frame_title = 'Hello World', - frame_width = #text+6, - frame_height = 3, + frame_width = #text, + frame_height = 1, + frame_inset = 1, } function screen:onRenderBody(dc) - dc:seek(3,1):string(text, COLOR_LIGHTGREEN) + dc:string(text, COLOR_LIGHTGREEN) end function screen:onInput(keys) diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index c518a9cf8..9a5d76066 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -234,12 +234,12 @@ function SiegeEngine:onRenderBody_main(dc) if links then dc:key('CUSTOM_D'):string(": Delete, ") dc:key('CUSTOM_O'):string(": Zoom"):newline() - self:renderStockpiles(dc, links, bottom-2-dc:localY()) + self:renderStockpiles(dc, links, bottom-2-dc:cursorY()) dc:newline():newline() end local prof = self.building:getWorkshopProfile() or {} - dc:seek(1,math.max(dc:localY(),19)) + dc:seek(1,math.max(dc:cursorY(),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('-') @@ -461,7 +461,7 @@ function SiegeEngine:onRenderBody(dc) self.mode.render(dc) - dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) + dc:seek(1, math.max(dc:cursorY(), 21)):pen(COLOR_WHITE) dc:key('LEAVESCREEN'):string(": Back, ") dc:key('CUSTOM_C'):string(": Recenter") end From abfe2754fbf60199f116514a5d0498a8a90769a4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 15 Oct 2012 20:03:18 +0400 Subject: [PATCH 067/472] Start implementing common widgets for lua screens. --- library/lua/gui.lua | 134 +++++++++++++++++++++++++--------- library/lua/gui/dialogs.lua | 43 ++++------- library/lua/gui/dwarfmode.lua | 9 ++- library/lua/gui/widgets.lua | 80 ++++++++++++++++++++ scripts/gui/guide-path.lua | 8 +- scripts/gui/power-meter.lua | 4 + 6 files changed, 209 insertions(+), 69 deletions(-) create mode 100644 library/lua/gui/widgets.lua diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 4f98e1bde..ea2a79da8 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -48,16 +48,74 @@ end function mkdims_wh(x1,y1,w,h) return { x1=x1, y1=y1, x2=x1+w-1, y2=y1+h-1, width=w, height=h } end -function inset(rect,dx1,dy1,dx2,dy2) - return mkdims_xy( - rect.x1+dx1, rect.y1+(dy1 or dx1), - rect.x2-(dx2 or dx1), rect.y2-(dy2 or dy1 or dx1) - ) -end function is_in_rect(rect,x,y) return x and y and x >= rect.x1 and x <= rect.x2 and y >= rect.y1 and y <= rect.y2 end +local function align_coord(gap,align,lv,rv) + if gap <= 0 then + return 0 + end + if not align then + if rv and not lv then + align = 1.0 + elseif lv and not rv then + align = 0.0 + else + align = 0.5 + end + end + return math.floor(gap*align) +end + +function compute_frame_rect(wavail,havail,spec,xgap,ygap) + if not spec then + return mkdims_wh(0,0,wavail,havail) + end + + local sw = wavail - (spec.l or 0) - (spec.r or 0) + local sh = havail - (spec.t or 0) - (spec.b or 0) + local rqw = math.min(sw, (spec.w or sw)+xgap) + local rqh = math.min(sh, (spec.h or sh)+ygap) + local ax = align_coord(sw - rqw, spec.xalign, spec.l, spec.r) + local ay = align_coord(sh - rqh, spec.yalign, spec.t, spec.b) + + local rect = mkdims_wh((spec.l or 0) + ax, (spec.t or 0) + ay, rqw, rqh) + rect.wgap = sw - rqw + rect.hgap = sh - rqh + return rect +end + +local function parse_inset(inset) + local l,r,t,b + if type(inset) == 'table' then + l,r = inset.l or inset.x, inset.r or inset.x + t,b = inset.t or inset.y, inset.b or inset.y + else + l = inset or 0 + t,r,b = l,l,l + end + return l,r,t,b +end + +function inset_frame(rect, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) +end + +function compute_frame_body(wavail, havail, spec, inset, gap) + gap = gap or 0 + local l,t,r,b = parse_inset(inset) + local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) + local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) + return rect, body +end + +function blink_visible(delay) + return math.floor(dfhack.getTickCount()/delay) % 2 == 0 +end + local function to_pen(default, pen, bg, bold) if pen == nil then return default or {} @@ -296,13 +354,35 @@ View = defclass(View) View.ATTRS { active = true, - hidden = false, + visible = true, + view_id = DEFAULT_NIL, } function View:init(args) self.subviews = {} end +function View:addviews(list) + local sv = self.subviews + + for _,obj in ipairs(list) do + table.insert(sv, obj) + + local id = obj.view_id + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + + for _,dir in ipairs(list) do + for id,obj in pairs(dir) do + if id and type(id) ~= 'number' and sv[id] == nil then + sv[id] = obj + end + end + end +end + function View:getWindowSize() local rect = self.frame_body return rect.width, rect.height @@ -320,6 +400,12 @@ function View:computeFrame(parent_rect) return mkdims_wh(0,0,parent_rect.width,parent_rect.height) end +function View:updateSubviewLayout(frame_body) + for _,child in ipairs(self.subviews) do + child:updateLayout(frame_body) + end +end + function View:updateLayout(parent_rect) if not parent_rect then parent_rect = self.frame_parent_rect @@ -332,16 +418,14 @@ function View:updateLayout(parent_rect) self.frame_rect = frame_rect self.frame_body = parent_rect:viewport(body_rect or frame_rect) - for _,child in ipairs(self.subviews) do - child:updateLayout(frame_body) - end + self:updateSubviewLayout(self.frame_body) - self:invoke_after('postUpdateLayout',frame_body) + self:invoke_after('postUpdateLayout', self.frame_body) end function View:renderSubviews(dc) for _,child in ipairs(self.subviews) do - if not child.hidden then + if child.visible then child:render(dc) end end @@ -512,46 +596,28 @@ FramedScreen.ATTRS{ frame_width = DEFAULT_NIL, frame_height = DEFAULT_NIL, frame_inset = 0, + frame_background = CLEAR_PEN, } -local function hint_coord(gap,hint) - if hint and hint > 0 then - return math.min(hint,gap) - elseif hint and hint < 0 then - return math.max(0,gap-hint) - else - return math.floor(gap/2) - end -end - function FramedScreen:getWantedFrameSize() return self.frame_width, self.frame_height end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height - local ins = self.frame_inset - local thickness = 2+ins*2 - local iw, ih = sw-thickness, sh-thickness local fw, fh = self:getWantedFrameSize() - local width = math.min(fw or iw, iw) - local height = math.min(fh or ih, ih) - local gw, gh = iw-width, ih-height - local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) - self.frame_opaque = (gw == 0 and gh == 0) - return mkdims_wh(x1,y1,width+thickness,height+thickness), - mkdims_wh(x1+1+ins,y1+1+ins,width,height) + return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end function FramedScreen:render(dc) local rect = self.frame_rect local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 - if self.frame_opaque then + if rect.wgap <= 0 and rect.hgap <= 0 then dc:clear() else self:renderParent() - dc:fill(rect, CLEAR_PEN) + dc:fill(rect, self.frame_background) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 75f7349d1..10a43c22e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -3,6 +3,7 @@ local _ENV = mkmodule('gui.dialogs') local gui = require('gui') +local widgets = require('gui.widgets') local utils = require('utils') local dscreen = dfhack.screen @@ -101,8 +102,6 @@ InputBox = defclass(InputBox, MessageBox) InputBox.focus_path = 'InputBox' InputBox.ATTRS{ - input = '', - input_pen = DEFAULT_NIL, on_input = DEFAULT_NIL, } @@ -110,46 +109,36 @@ function InputBox:preinit(info) info.on_accept = nil end +function InputBox:init(info) + self:addviews{ + widgets.EditField{ + view_id = 'edit', + text = info.input, + text_pen = info.input_pen, + frame = { l = 1, r = 1, h = 1 }, + } + } +end + function InputBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) + self.subviews.edit.frame.t = mh return mw, mh+2 end -function InputBox:onRenderBody(dc) - InputBox.super.onRenderBody(self, dc) - - dc:newline(1) - dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(1,dc:cursorY(),dc.width-2,dc:cursorY()) - - local cursor = '_' - if math.floor(dfhack.getTickCount()/300) % 2 == 0 then - cursor = ' ' - end - local txt = self.input .. cursor - if #txt > dc.width-2 then - txt = string.char(27)..string.sub(txt, #txt-dc.width+4) - end - dc:string(txt) -end - function InputBox:onInput(keys) if keys.SELECT then self:dismiss() if self.on_input then - self.on_input(self.input) + self.on_input(self.subviews.edit.text) end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys._STRING then - if keys._STRING == 0 then - self.input = string.sub(self.input, 1, #self.input-1) - else - self.input = self.input .. string.char(keys._STRING) - end + else + self:inputToSubviews(keys) end end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 4056fbfcc..0c1b0599c 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -354,11 +354,12 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) MenuOverlay.ATTRS { - frame_inset = 0 + frame_inset = 0, + frame_background = CLEAR_PEN, } function MenuOverlay:computeFrame(parent_rect) - return self.df_layout.menu, gui.inset(self.df_layout.menu, self.frame_inset) + return self.df_layout.menu, gui.inset_frame(self.df_layout.menu, self.frame_inset) end function MenuOverlay:onAboutToShow(below) @@ -381,6 +382,10 @@ function MenuOverlay:render(dc) menu.x1+1, menu.y2+1, "DFHack" ) + if self.frame_background then + dc:fill(menu, self.frame_background) + end + MenuOverlay.super.render(self, dc) end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua new file mode 100644 index 000000000..010ea5510 --- /dev/null +++ b/library/lua/gui/widgets.lua @@ -0,0 +1,80 @@ +-- Simple widgets for screens + +local _ENV = mkmodule('gui.widgets') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +------------ +-- Widget -- +------------ + +Widget = defclass(Widget, gui.View) + +Widget.ATTRS { + frame = DEFAULT_NIL, + frame_inset = DEFAULT_NIL, + frame_background = DEFAULT_NIL, +} + +function Widget:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset) +end + +function Widget:render(dc) + if self.frame_background then + dc:fill(self.frame_rect, self.frame_background) + end + + Widget.super.render(self, dc) +end + +---------------- +-- Edit field -- +---------------- + +EditField = defclass(EditField, Widget) + +EditField.ATTRS{ + text = '', + text_pen = DEFAULT_NIL, + on_char = DEFAULT_NIL, + on_change = DEFAULT_NIL, +} + +function EditField:onRenderBody(dc) + dc:pen(self.text_pen or COLOR_LIGHTCYAN):fill(0,0,dc.width-1,0) + + local cursor = '_' + if not self.active or gui.blink_visible(300) then + cursor = ' ' + end + local txt = self.text .. cursor + if #txt > dc.width then + txt = string.char(27)..string.sub(txt, #txt-dc.width+2) + end + dc:string(txt) +end + +function EditField:onInput(keys) + if keys._STRING then + local old = self.text + if keys._STRING == 0 then + self.text = string.sub(old, 1, #old-1) + else + local cv = string.char(keys._STRING) + if not self.on_char or self.on_char(cv, old) then + self.text = old .. cv + end + end + if self.on_change and self.text ~= old then + self.on_change(self.text, old) + end + return true + end +end + +return _ENV diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index 4d32a99c2..1546150b7 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -69,17 +69,13 @@ local function get_path_point(gpath,i) return xyz2pos(gpath.x[i], gpath.y[i], gpath.z[i]) end -local function blink_visible(delay) - return math.floor(dfhack.getTickCount()/delay) % 2 == 0 -end - function GuidePathUI:renderPath(cursor) local gpath = self.order.guide_path local startp = self.stop.pos local endp = self.next_stop.pos local vp = self:getViewport() local dc = gui.Painter.new(self.df_layout.map) - local visible = blink_visible(500) + local visible = gui.blink_visible(500) if visible then paintMapTile(dc, vp, cursor, endp, '+', COLOR_LIGHTGREEN) @@ -123,7 +119,7 @@ function GuidePathUI:renderPath(cursor) end end - if blink_visible(120) then + if gui.blink_visible(120) then paintMapTile(dc, vp, cursor, startp, 240, COLOR_LIGHTGREEN, COLOR_GREEN) end diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 81b56e29f..ac30c9273 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -12,6 +12,10 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) PowerMeter.focus_path = 'power-meter' +PowerMeter.ATTRS { + frame_background = false +} + function PowerMeter:init() self:assign{ min_power = 0, max_power = -1, invert = false, From d336abfd974c3a721bf71ba4be0156501877365c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 16 Oct 2012 14:18:35 +0400 Subject: [PATCH 068/472] Add label and list widgets, and switch stock dialogs to them. --- library/lua/dfhack.lua | 4 + library/lua/gui.lua | 18 +- library/lua/gui/dialogs.lua | 164 ++++---------- library/lua/gui/widgets.lua | 424 +++++++++++++++++++++++++++++++++++- library/lua/utils.lua | 18 ++ 5 files changed, 502 insertions(+), 126 deletions(-) diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 9fc94a025..3f57e5722 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -125,6 +125,10 @@ end -- Misc functions +NEWLINE = "\n" +COMMA = "," +PERIOD = "." + function printall(table) local ok,f,t,k = pcall(pairs,table) if ok then diff --git a/library/lua/gui.lua b/library/lua/gui.lua index ea2a79da8..125b59544 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -116,7 +116,7 @@ function blink_visible(delay) return math.floor(dfhack.getTickCount()/delay) % 2 == 0 end -local function to_pen(default, pen, bg, bold) +function to_pen(default, pen, bg, bold) if pen == nil then return default or {} elseif type(pen) ~= 'table' then @@ -363,6 +363,8 @@ function View:init(args) end function View:addviews(list) + if not list then return end + local sv = self.subviews for _,obj in ipairs(list) do @@ -413,11 +415,15 @@ function View:updateLayout(parent_rect) self.frame_parent_rect = parent_rect end + self:invoke_before('preUpdateLayout', parent_rect) + local frame_rect,body_rect = self:computeFrame(parent_rect) self.frame_rect = frame_rect self.frame_body = parent_rect:viewport(body_rect or frame_rect) + self:invoke_after('postComputeFrame', self.frame_body) + self:updateSubviewLayout(self.frame_body) self:invoke_after('postUpdateLayout', self.frame_body) @@ -432,6 +438,8 @@ function View:renderSubviews(dc) end function View:render(dc) + self:onRenderFrame(dc, self.frame_rect) + local sub_dc = Painter{ view_rect = self.frame_body, clip_view = dc @@ -442,6 +450,9 @@ function View:render(dc) self:renderSubviews(sub_dc) end +function View:onRenderFrame(dc,rect) +end + function View:onRenderBody(dc) end @@ -609,8 +620,7 @@ function FramedScreen:computeFrame(parent_rect) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end -function FramedScreen:render(dc) - local rect = self.frame_rect +function FramedScreen:onRenderFrame(dc, rect) local x1,y1,x2,y2 = rect.x1, rect.y1, rect.x2, rect.y2 if rect.wgap <= 0 and rect.hgap <= 0 then @@ -621,8 +631,6 @@ function FramedScreen:render(dc) end paint_frame(x1,y1,x2,y2,self.frame_style,self.frame_title) - - FramedScreen.super.render(self, dc) end return _ENV diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 10a43c22e..7d8058a93 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -14,47 +14,35 @@ MessageBox.focus_path = 'MessageBox' MessageBox.ATTRS{ frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, -- new attrs - text = {}, on_accept = DEFAULT_NIL, on_cancel = DEFAULT_NIL, on_close = DEFAULT_NIL, - text_pen = DEFAULT_NIL, } -function MessageBox:preinit(info) - if type(info.text) == 'string' then - info.text = utils.split_string(info.text, "\n") - end +function MessageBox:init(info) + self:addviews{ + widgets.Label{ + view_id = 'label', + text = info.text, + text_pen = info.text_pen, + frame = { l = 0, t = 0 }, + auto_height = true + } + } end function MessageBox:getWantedFrameSize() - local text = self.text - local w = #(self.frame_title or '') + 4 - w = math.max(w, 20) - w = math.max(self.frame_width or w, w) - for _, l in ipairs(text) do - w = math.max(w, #l) - end - local h = #text+1 - if h > 1 then - h = h+1 - end - return w+2, #text+2 + local label = self.subviews.label + local width = math.max(self.frame_width or 0, 20, #(self.frame_title or '') + 4) + return math.max(width, label:getTextWidth()), label:getTextHeight() end -function MessageBox:onRenderBody(dc) - if #self.text > 0 then - dc:newline(1):pen(self.text_pen or COLOR_GREY) - for _, l in ipairs(self.text or {}) do - dc:string(l):newline(1) - end - end - +function MessageBox:onRenderFrame(dc,rect) + MessageBox.super.onRenderFrame(self,dc,rect) if self.on_accept then - 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') + dc:seek(rect.x1+2,rect.y2):key('LEAVESCREEN'):string('/'):key('MENU_CONFIRM') end end @@ -75,6 +63,8 @@ function MessageBox:onInput(keys) if self.on_cancel then self.on_cancel() end + else + self:inputToSubviews(keys) end end @@ -115,14 +105,14 @@ function InputBox:init(info) view_id = 'edit', text = info.input, text_pen = info.input_pen, - frame = { l = 1, r = 1, h = 1 }, + frame = { l = 0, r = 0, h = 1 }, } } end function InputBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) - self.subviews.edit.frame.t = mh + self.subviews.edit.frame.t = mh+1 return mw, mh+2 end @@ -165,107 +155,47 @@ ListBox.ATTRS{ on_select = DEFAULT_NIL } -function InputBox:preinit(info) +function ListBox:preinit(info) info.on_accept = nil end function ListBox:init(info) - self.page_top = 1 -end + local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) + local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) -local function choice_text(entry) - if type(entry)=="table" then - return entry.caption or entry[1] - else - return entry - end + self:addviews{ + widgets.List{ + view_id = 'list', + selected = info.selected, + choices = info.choices, + text_pen = spen, + cursor_pen = cpen, + on_submit = function(sel,obj) + self:dismiss() + if self.on_select then self.on_select(sel, obj) end + local cb = obj.on_select or obj[2] + if cb then cb(obj, sel) end + end, + frame = { l = 0, r = 0 }, + } + } end function ListBox:getWantedFrameSize() - local mw, mh = ListBox.super.getWantedFrameSize(self) - 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:postUpdateLayout() - self.page_size = self.frame_rect.height - #self.text - 3 - self:moveCursor(0) -end - -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 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: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 + local mw, mh = InputBox.super.getWantedFrameSize(self) + local list = self.subviews.list + list.frame.t = mh+1 + return math.max(mw, list:getContentWidth()), mh+1+list:getContentHeight() end function ListBox:onInput(keys) - if keys.SELECT then - self:dismiss() - - local choice=self.choices[self.selection] - if self.on_select then - self.on_select(self.selection, choice) - end - - if choice then - local callback = choice.on_select or choice[2] - if callback then - callback(choice, self.selection) - end - end - elseif keys.LEAVESCREEN then + if keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.STANDARDSCROLL_UP then - self:moveCursor(-1) - elseif keys.STANDARDSCROLL_DOWN then - self:moveCursor(1) - elseif keys.STANDARDSCROLL_PAGEUP then - self:moveCursor(-self.page_size) - elseif keys.STANDARDSCROLL_PAGEDOWN then - self:moveCursor(self.page_size) + else + self:inputToSubviews(keys) end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 010ea5510..f3796d0e3 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -7,6 +7,21 @@ local utils = require('utils') local dscreen = dfhack.screen +local function show_view(view,vis,act) + if view then + view.visible = vis + view.active = act + end +end + +local function getval(obj) + if type(obj) == 'function' then + return obj() + else + return obj + end +end + ------------ -- Widget -- ------------ @@ -24,12 +39,62 @@ function Widget:computeFrame(parent_rect) return gui.compute_frame_body(sw, sh, self.frame, self.frame_inset) end -function Widget:render(dc) +function Widget:onRenderFrame(dc, rect) if self.frame_background then - dc:fill(self.frame_rect, self.frame_background) + dc:fill(rect, self.frame_background) end +end + +----------- +-- Panel -- +----------- + +Panel = defclass(Panel, Widget) - Widget.super.render(self, dc) +Panel.ATTRS { + on_render = DEFAULT_NIL, +} + +function Panel:init(args) + self:addviews(args.subviews) +end + +function Panel:onRenderBody(dc) + if self.on_render then self.on_render(dc) end +end + +----------- +-- Pages -- +----------- + +Pages = defclass(Pages, Panel) + +function Pages:init(args) + for _,v in ipairs(self.subviews) do + show_view(v, false, false) + end + self:setSelected(args.selected or 1) +end + +function Pages:setSelected(idx) + if type(idx) ~= 'number' then + local key = idx + if type(idx) == 'string' then + key = self.subviews[key] + end + idx = utils.linear_index(self.subviews, key) + if not idx then + error('Unknown page: '..key) + end + end + + show_view(self.subviews[self.selected], false, false) + self.selected = math.min(math.max(1, idx), #self.subviews) + show_view(self.subviews[self.selected], true, true) +end + +function Pages:getSelected() + return self.selected, self.subviews[self.selected] end ---------------- @@ -43,6 +108,7 @@ EditField.ATTRS{ text_pen = DEFAULT_NIL, on_char = DEFAULT_NIL, on_change = DEFAULT_NIL, + on_submit = DEFAULT_NIL, } function EditField:onRenderBody(dc) @@ -60,7 +126,10 @@ function EditField:onRenderBody(dc) end function EditField:onInput(keys) - if keys._STRING then + if self.on_submit and keys.SELECT then + self.on_submit(self.text) + return true + elseif keys._STRING then local old = self.text if keys._STRING == 0 then self.text = string.sub(old, 1, #old-1) @@ -77,4 +146,351 @@ function EditField:onInput(keys) end end +----------- +-- Label -- +----------- + +function parse_label_text(obj) + local text = obj.text or {} + if type(text) ~= 'table' then + text = { text } + end + local curline = nil + local out = { } + local active = nil + local idtab = nil + for _,v in ipairs(text) do + local vv + if type(v) == 'string' then + vv = utils.split_string(v, NEWLINE) + else + vv = { v } + end + + for i = 1,#vv do + local cv = vv[i] + if i > 1 then + if not curline then + table.insert(out, {}) + end + curline = nil + end + if cv ~= '' then + if not curline then + curline = {} + table.insert(out, curline) + end + + if type(cv) == 'string' then + table.insert(curline, { text = cv }) + else + table.insert(curline, cv) + + if cv.on_activate then + active = active or {} + table.insert(active, cv) + end + + if cv.id then + idtab = idtab or {} + idtab[cv.id] = cv + end + end + end + end + end + obj.text_lines = out + obj.text_active = active + obj.text_ids = idtab +end + +function render_text(obj,dc,x0,y0,pen,dpen) + local width = 0 + for iline,line in ipairs(obj.text_lines) do + local x = 0 + if dc then + dc:seek(x+x0,y0+iline-1) + end + for _,token in ipairs(line) do + token.line = iline + token.x1 = x + + if token.gap then + x = x + token.gap + if dc then + dc:advance(token.gap) + end + end + + if token.text or token.key then + local text = getval(token.text) or '' + local keypen + + if dc then + if getval(token.disabled) then + dc:pen(getval(token.dpen) or dpen) + keypen = COLOR_GREEN + else + dc:pen(getval(token.pen) or pen) + keypen = COLOR_LIGHTGREEN + end + end + + x = x + #text + + if token.key then + local keystr = gui.getKeyDisplay(token.key) + local sep = token.key_sep or '' + + if sep == '()' then + if dc then + dc:string(text) + dc:string(' ('):string(keystr,keypen):string(')') + end + x = x + 3 + else + if dc then + dc:string(keystr,keypen):string(sep):string(text) + end + x = x + #sep + end + else + if dc then + dc:string(text) + end + end + end + + token.x2 = x + end + width = math.max(width, x) + end + obj.text_width = width +end + +function check_text_keys(self, keys) + if self.text_active then + for _,item in ipairs(self.text_active) do + if item.key and keys[item.key] and not getval(item.disabled) then + item.on_activate() + return true + end + end + end +end + +Label = defclass(Label, Widget) + +Label.ATTRS{ + text_pen = COLOR_WHITE, + text_dpen = COLOR_DARKGREY, + auto_height = true, +} + +function Label:init(args) + self:setText(args.text) +end + +function Label:setText(text) + self.text = text + parse_label_text(self) + + if self.auto_height then + self.frame = self.frame or {} + self.frame.h = self:getTextHeight() + end +end + +function Label:itemById(id) + if self.text_ids then + return self.text_ids[id] + end +end + +function Label:getTextHeight() + return #self.text_lines +end + +function Label:getTextWidth() + render_text(self) + return self.text_width +end + +function Label:onRenderBody(dc) + render_text(self,dc,0,0,self.text_pen,self.text_dpen) +end + +function Label:onInput(keys) + return check_text_keys(self, keys) +end + +---------- +-- List -- +---------- + +List = defclass(List, Widget) + +STANDARDSCROLL = { + STANDARDSCROLL_UP = -1, + STANDARDSCROLL_DOWN = 1, + STANDARDSCROLL_PAGEUP = '-page', + STANDARDSCROLL_PAGEDOWN = '+page', +} + +List.ATTRS{ + text_pen = COLOR_CYAN, + cursor_pen = COLOR_LIGHTCYAN, + cursor_dpen = DEFAULT_NIL, + inactive_pen = DEFAULT_NIL, + on_select = DEFAULT_NIL, + on_submit = DEFAULT_NIL, + row_height = 1, + scroll_keys = STANDARDSCROLL, +} + +function List:init(info) + self.page_top = 1 + self.page_size = 1 + self:setChoices(info.choices, info.selected) +end + +function List:setChoices(choices, selected) + self.choices = choices or {} + + for i,v in ipairs(self.choices) do + if type(v) ~= 'table' then + v = { text = v } + self.choices[i] = v + end + v.text = v.text or v.caption or v[1] + parse_label_text(v) + end + + self:setSelected(selected) +end + +function List:setSelected(selected) + self.selected = selected or self.selected or 1 + self:moveCursor(0, true) + return self.selected +end + +function List:getSelected() + return self.selected, self.choices[self.selected] +end + +function List:getContentWidth() + local width = 0 + for i,v in ipairs(self.choices) do + render_text(v) + local roww = v.text_width + if v.key then + roww = roww + 3 + #gui.getKeyDisplay(v.key) + end + width = math.max(width, roww) + end + return width +end + +function List:getContentHeight() + return #self.choices * self.row_height +end + +function List:postComputeFrame(body) + self.page_size = math.max(1, math.floor(body.height / self.row_height)) + self:moveCursor(0) +end + +function List:moveCursor(delta, force_cb) + local page = math.max(1, self.page_size) + local cnt = #self.choices + local off = self.selected+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 off <= -ds then + off = cnt-1 + else + off = math.max(0, off) + end + end + + self.selected = 1 + off % cnt + self.page_top = 1 + page * math.floor((self.selected-1) / page) + + if (force_cb or delta ~= 0) and self.on_select then + self.on_select(self:getSelected()) + end +end + +function List:onRenderBody(dc) + local choices = self.choices + local top = self.page_top + local iend = math.min(#choices, top+self.page_size-1) + + for i = top,iend do + local obj = choices[i] + local current = (i == self.selected) + local cur_pen = self.text_pen + local cur_dpen = cur_pen + + if current and active then + cur_pen = self.cursor_pen + cur_dpen = self.cursor_dpen or self.text_pen + elseif current then + cur_pen = self.inactive_pen or self.cursor_pen + cur_dpen = self.inactive_pen or self.text_pen + end + + local y = (i - top)*self.row_height + render_text(obj, dc, 0, y, cur_pen, cur_dpen) + + if obj.key then + local keystr = gui.getKeyDisplay(obj.key) + dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) + dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') + end + end +end + +function List:onInput(keys) + if self.on_submit and keys.SELECT then + self.on_submit(self:getSelected()) + return true + else + for k,v in pairs(self.scroll_keys) do + if keys[k] then + if v == '+page' then + v = self.page_size + elseif v == '-page' then + v = -self.page_size + end + + self:moveCursor(v) + return true + end + end + + for i,v in ipairs(self.choices) do + if v.key and keys[v.key] then + self:setSelected(i) + if self.on_submit then + self.on_submit(self:getSelected()) + end + return true + end + end + + local current = self.choices[self.selected] + if current then + return check_text_keys(current, keys) + end + end +end + return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index b46363cd7..2507c9964 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -302,6 +302,24 @@ function sort_vector(vector,field,cmp) return vector end +-- Linear search + +function linear_index(vector,obj) + local min,max + if df.isvalid(vector) then + min,max = 0,#vector-1 + else + min,max = 1,#vector + end + for i=min,max do + if vector[i] == obj then + return i + end + end + return nil +end + + -- Binary search in a vector or lua table function binsearch(vector,key,field,cmp,min,max) if not(min and max) then From 023dc82564f9f896cffadfede284bb01d9fa7800 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 16 Oct 2012 18:33:00 +0400 Subject: [PATCH 069/472] Implement a material selection dialog. --- library/lua/class.lua | 14 +- library/lua/gui.lua | 4 +- library/lua/gui/materials.lua | 311 ++++++++++++++++++++++++++++++++++ library/lua/gui/script.lua | 12 ++ library/lua/gui/widgets.lua | 29 +++- 5 files changed, 361 insertions(+), 9 deletions(-) create mode 100644 library/lua/gui/materials.lua diff --git a/library/lua/class.lua b/library/lua/class.lua index 7b142e499..bcfff13e2 100644 --- a/library/lua/class.lua +++ b/library/lua/class.lua @@ -3,16 +3,16 @@ local _ENV = mkmodule('class') -- Metatable template for a class -class_obj = {} or class_obj +class_obj = class_obj or {} -- Methods shared by all classes -common_methods = {} or common_methods +common_methods = common_methods or {} -- Forbidden names for class fields and methods. reserved_names = { super = true, ATTRS = true } -- Attribute table metatable -attrs_meta = {} or attrs_meta +attrs_meta = attrs_meta or {} -- Create or updates a class; a class has metamethods and thus own metatable. function defclass(class,parent) @@ -133,6 +133,14 @@ function common_methods:callback(method, ...) return dfhack.curry(self[method], self, ...) end +function common_methods:cb_getfield(field) + return function() return self[field] end +end + +function common_methods:cb_setfield(field) + return function(val) self[field] = val end +end + function common_methods:assign(data) for k,v in pairs(data) do self[k] = v diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 125b59544..90eeb823a 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -377,7 +377,7 @@ function View:addviews(list) end for _,dir in ipairs(list) do - for id,obj in pairs(dir) do + for id,obj in pairs(dir.subviews) do if id and type(id) ~= 'number' and sv[id] == nil then sv[id] = obj end @@ -616,7 +616,7 @@ end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height - local fw, fh = self:getWantedFrameSize() + local fw, fh = self:getWantedFrameSize(parent_rect) return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua new file mode 100644 index 000000000..0cf3c21d9 --- /dev/null +++ b/library/lua/gui/materials.lua @@ -0,0 +1,311 @@ +-- Stock dialog for selecting materials + +local _ENV = mkmodule('gui.materials') + +local gui = require('gui') +local widgets = require('gui.widgets') +local utils = require('utils') + +ARROW = string.char(26) + +CREATURE_BASE = 19 +PLANT_BASE = 419 + +MaterialDialog = defclass(MaterialDialog, gui.FramedScreen) + +MaterialDialog.focus_path = 'MaterialDialog' + +MaterialDialog.ATTRS{ + prompt = 'Type or select a material from this list', + frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, + frame_title = 'Select Material', + -- new attrs + on_select = DEFAULT_NIL, + on_cancel = DEFAULT_NIL, + on_close = DEFAULT_NIL, +} + +function MaterialDialog:init(info) + self:addviews{ + widgets.Label{ + text = { + self.prompt, '\n\n', + 'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN } + }, + text_pen = COLOR_WHITE, + frame = { l = 0, t = 0 }, + }, + widgets.Label{ + view_id = 'back', + visible = false, + text = { { key = 'LEAVESCREEN', text = ': Back' } }, + frame = { r = 0, b = 0 }, + auto_width = true, + }, + widgets.List{ + view_id = 'list', + frame = { l = 0, r = 0, t = 6, b = 2 }, + on_submit = self:callback('onSubmitItem'), + }, + widgets.EditField{ + view_id = 'edit', + frame = { l = 2, t = 4 }, + on_change = self:callback('onFilterChoices'), + on_char = self:callback('onFilterChar'), + }, + widgets.Label{ + view_id = 'not_found', + text = 'No matching materials.', + text_pen = COLOR_LIGHTRED, + frame = { l = 2, r = 0, t = 6 }, + }, + widgets.Label{ + text = { { + key = 'SELECT', text = ': Select', + disabled = function() return self.subviews.not_found.visible end + } }, + frame = { l = 0, b = 0 }, + } + } + self:initBuiltinMode() +end + +function MaterialDialog:getWantedFrameSize(rect) + return math.max(40, #self.prompt), math.min(28, rect.height-8) +end + +function MaterialDialog:onDestroy() + if self.on_close then + self.on_close() + end +end + +function MaterialDialog:initBuiltinMode() + local choices = { + { text = ' none', mat_type = -1, mat_index = -1 }, + { text = ARROW..' inorganic', key = 'CUSTOM_SHIFT_I', + cb = self:callback('initInorganicMode') }, + { text = ARROW..' creature', key = 'CUSTOM_SHIFT_C', + cb = self:callback('initCreatureMode')}, + { text = ARROW..' plant', key = 'CUSTOM_SHIFT_P', + cb = self:callback('initPlantMode') }, + } + + self:addMaterials( + choices, + df.global.world.raws.mat_table.builtin, 0, -1, + df.builtin_mats._last_item + ) + + self:pushContext('Any material', choices) +end + +function MaterialDialog:initInorganicMode() + local choices = {} + + self:addMaterials( + choices, + df.global.world.raws.inorganics, 0, nil, + nil, 'material' + ) + + self:pushContext('Inorganic materials', choices) +end + +function MaterialDialog:initCreatureMode() + local choices = {} + + for i,v in ipairs(df.global.world.raws.creatures.all) do + self:addObjectChoice(choices, v, v.name[0], CREATURE_BASE, i) + end + + self:pushContext('Creature materials', choices) +end + +function MaterialDialog:initPlantMode() + local choices = {} + + for i,v in ipairs(df.global.world.raws.plants.all) do + self:addObjectChoice(choices, v, v.name, PLANT_BASE, i) + end + + self:pushContext('Plant materials', choices) +end + +function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) + if #obj.material == 1 then + self:addMaterial(choices, obj.material[0], typ, index, true) + else + table.insert(choices, { + text = ARROW..' '..name, mat_type = typ, mat_index = index, + ctx = name, obj = obj, cb = self:callback('onSelectObj') + }) + end +end + +function MaterialDialog:onSelectObj(item) + local choices = {} + self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) + self:pushContext(item.ctx, choices) +end + +function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) + for i=0,(maxid or #vector-1) do + local mat = vector[i] + if mat and field then + mat = mat[field] + end + if mat then + local typ, idx + if index then + typ, idx = tid+i, index + else + typ, idx = tid, i + end + self:addMaterial(choices, mat, typ, idx) + end + end +end + +function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) + local state = 0 + if mat.heat.melting_point <= 10015 then + state = 1 + end + local name = mat.state_name[state] + name = string.gsub(name, '^frozen ','') + name = string.gsub(name, '^molten ','') + name = string.gsub(name, '^condensed ','') + local key + if pfix and mat.prefix ~= '' then + name = mat.prefix .. ' ' .. name + key = mat.prefix + end + table.insert(choices, { + text = ' '..name, + search_key = key, + material = mat, + mat_type = typ, mat_index = idx + }) +end + +function MaterialDialog:pushContext(name, choices) + if not self.back_stack then + self.back_stack = {} + self.subviews.back.visible = false + else + table.insert(self.back_stack, { + context_str = self.context_str, + all_choices = self.all_choices, + edit_text = self.subviews.edit.text, + choices = self.choices, + selected = self.subviews.list.selected, + }) + self.subviews.back.visible = true + end + + self.context_str = name + self.all_choices = choices + self.subviews.edit.text = '' + self:setChoices(choices, 1) +end + +function MaterialDialog:onGoBack() + local save = table.remove(self.back_stack) + self.subviews.back.visible = (#self.back_stack > 0) + + self.context_str = save.context_str + self.all_choices = save.all_choices + self.subviews.edit.text = save.edit_text + self:setChoices(save.choices, save.selected) +end + +function MaterialDialog:setChoices(choices, pos) + self.choices = choices + self.subviews.list:setChoices(self.choices, pos) + self.subviews.not_found.visible = (#self.choices == 0) +end + +function MaterialDialog:onFilterChoices(text) + local tokens = utils.split_string(text, ' ') + local choices = {} + + for i,v in ipairs(self.all_choices) do + local ok = true + local search_key = v.search_key or v.text + for _,key in ipairs(tokens) do + if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then + ok = false + break + end + end + if ok then + table.insert(choices, v) + end + end + + self:setChoices(choices) +end + +local bad_chars = { + ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, + ['['] = true, [']'] = true, ['('] = true, [')'] = true, +} + +function MaterialDialog:onFilterChar(char, text) + if bad_chars[char] then + return false + end + if char == ' ' then + if #self.choices == 1 then + self.subviews.list:submit() + return false + end + return string.match(text, '%S$') + end + return true +end + +function MaterialDialog:submitMaterial(typ, index) + self:dismiss() + + if self.on_select then + local info = dfhack.matinfo.decode(typ, index) + self.on_select(info, typ, index) + end +end + +function MaterialDialog:onSubmitItem(idx, item) + if item.cb then + item:cb(idx) + else + self:submitMaterial(item.mat_type, item.mat_index) + end +end + +function MaterialDialog:onInput(keys) + if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then + if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then + self:onGoBack() + else + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end + else + self:inputToSubviews(keys) + end +end + +function showMaterialPrompt(title, prompt, on_select, on_cancel) + MaterialDialog{ + frame_title = title, + prompt = prompt, + on_select = on_select, + on_cancel = on_cancel, + }:show() +end + +return _ENV diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua index 021a4fa52..06079c426 100644 --- a/library/lua/gui/script.lua +++ b/library/lua/gui/script.lua @@ -148,4 +148,16 @@ function showListPrompt(title, text, tcolor, choices, min_width) return wait() end +function showMaterialPrompt(title, prompt) + require('gui.materials').MaterialDialog{ + frame_title = title, + prompt = prompt, + on_select = mkresume(true, + on_cancel = mkresume(false), + on_close = qresume(nil) + }:show() + + return wait() +end + return _ENV diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index f3796d0e3..eb2e8d05d 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -242,6 +242,8 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keystr = gui.getKeyDisplay(token.key) local sep = token.key_sep or '' + x = x + #keystr + if sep == '()' then if dc then dc:string(text) @@ -285,6 +287,7 @@ Label.ATTRS{ text_pen = COLOR_WHITE, text_dpen = COLOR_DARKGREY, auto_height = true, + auto_width = false, } function Label:init(args) @@ -301,6 +304,13 @@ function Label:setText(text) end end +function Label:preUpdateLayout() + if self.auto_width then + self.frame = self.frame or {} + self.frame.w = self:getTextWidth() + end +end + function Label:itemById(id) if self.text_ids then return self.text_ids[id] @@ -404,6 +414,13 @@ end function List:moveCursor(delta, force_cb) local page = math.max(1, self.page_size) local cnt = #self.choices + + if cnt < 1 then + self.page_top = 1 + self.selected = 1 + return + end + local off = self.selected+delta-1 local ds = math.abs(delta) @@ -458,9 +475,15 @@ function List:onRenderBody(dc) end end +function List:submit() + if self.on_submit and #self.choices > 0 then + self.on_submit(self:getSelected()) + end +end + function List:onInput(keys) if self.on_submit and keys.SELECT then - self.on_submit(self:getSelected()) + self:submit() return true else for k,v in pairs(self.scroll_keys) do @@ -479,9 +502,7 @@ function List:onInput(keys) for i,v in ipairs(self.choices) do if v.key and keys[v.key] then self:setSelected(i) - if self.on_submit then - self.on_submit(self:getSelected()) - end + self:submit() return true end end From 7aae869d728dad27504518245028b5ec6105f98f Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Tue, 16 Oct 2012 15:58:34 -0400 Subject: [PATCH 070/472] Add OS X build instructions to the Compile file --- Compile.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Compile.rst b/Compile.rst index 8fca0e1f5..a71642060 100644 --- a/Compile.rst +++ b/Compile.rst @@ -63,6 +63,34 @@ extra options. You can also use a cmake-friendly IDE like KDevelop 4 or the cmake-gui program. +======== +Mac OS X +======== + +1. Download and unpack a copy of the latest DF +2. Install Xcode from Mac App Store +3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. +4. Install MacPorts. +5. Install dependencies from MacPorts: + * sudo port install gcc45 +universal cmake +universal git-core +universal (This will take some time—maybe hours, depending on your machine.) + * At some point during this process, it may ask you to install a Java environment; let it do so. +6. Install perl dependencies + 1. sudo cpan (If this is the first time you've run cpan, you will need to go through the setup process. Just stick with the defaults for everything and you'll be fine.) + 2. install XML::LibXML + 3. install XML::LibXSLT +7. Get the dfhack source + 1. git clone https://github.com/danaris/dfhack.git + 2. cd dfhack + 3. git submodule init + 4. git submodule update +8. mkdir build-osx +9. cd build-osx +10. export CC=/opt/local/bin/gcc-mp-4.5 +11. export CXX=/opt/local/bin/g++-mp-4.5 +12. cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory +13. make +14. make install + ======= Windows ======= From 8fd1dd04bb72eecb8056f9b262a10e828c639bdd Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 16 Oct 2012 19:27:48 -0500 Subject: [PATCH 071/472] Display dfhack version number at end of baseline 'help' message. --- library/Core.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/Core.cpp b/library/Core.cpp index a8000070f..7766b3591 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -374,6 +374,8 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve " unload PLUGIN|all - Unload a plugin or all loaded plugins.\n" " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" ); + + con.print("\nDFHack version " DFHACK_VERSION ".\n"); } else if (parts.size() == 1) { From b14e4e97f543b5b2a107ce0a808f507c68716e41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 09:41:48 +0400 Subject: [PATCH 072/472] Natively support entry icons in the List widget. --- library/lua/gui/materials.lua | 17 +++++++++-------- library/lua/gui/widgets.lua | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 0cf3c21d9..17fcf2928 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -46,6 +46,7 @@ function MaterialDialog:init(info) widgets.List{ view_id = 'list', frame = { l = 0, r = 0, t = 6, b = 2 }, + icon_width = 2, on_submit = self:callback('onSubmitItem'), }, widgets.EditField{ @@ -83,12 +84,12 @@ end function MaterialDialog:initBuiltinMode() local choices = { - { text = ' none', mat_type = -1, mat_index = -1 }, - { text = ARROW..' inorganic', key = 'CUSTOM_SHIFT_I', + { text = 'none', mat_type = -1, mat_index = -1 }, + { icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', cb = self:callback('initInorganicMode') }, - { text = ARROW..' creature', key = 'CUSTOM_SHIFT_C', + { icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', cb = self:callback('initCreatureMode')}, - { text = ARROW..' plant', key = 'CUSTOM_SHIFT_P', + { icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', cb = self:callback('initPlantMode') }, } @@ -138,8 +139,8 @@ function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) self:addMaterial(choices, obj.material[0], typ, index, true) else table.insert(choices, { - text = ARROW..' '..name, mat_type = typ, mat_index = index, - ctx = name, obj = obj, cb = self:callback('onSelectObj') + icon = ARROW, text = name, mat_type = typ, mat_index = index, + obj = obj, cb = self:callback('onSelectObj') }) end end @@ -147,7 +148,7 @@ end function MaterialDialog:onSelectObj(item) local choices = {} self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) - self:pushContext(item.ctx, choices) + self:pushContext(item.text, choices) end function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) @@ -183,7 +184,7 @@ function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) key = mat.prefix end table.insert(choices, { - text = ' '..name, + text = name, search_key = key, material = mat, mat_type = typ, mat_index = idx diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index eb2e8d05d..16568aa16 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -222,6 +222,13 @@ function render_text(obj,dc,x0,y0,pen,dpen) end end + if token.tile then + x = x + 1 + if dc then + dc:char(nil, token.tile) + end + end + if token.text or token.key then local text = getval(token.text) or '' local keypen @@ -356,6 +363,7 @@ List.ATTRS{ on_submit = DEFAULT_NIL, row_height = 1, scroll_keys = STANDARDSCROLL, + icon_width = DEFAULT_NIL, } function List:init(info) @@ -399,7 +407,7 @@ function List:getContentWidth() end width = math.max(width, roww) end - return width + return width + (self.icon_width or 0) end function List:getContentHeight() @@ -449,6 +457,7 @@ function List:onRenderBody(dc) local choices = self.choices local top = self.page_top local iend = math.min(#choices, top+self.page_size-1) + local iw = self.icon_width for i = top,iend do local obj = choices[i] @@ -465,7 +474,17 @@ function List:onRenderBody(dc) end local y = (i - top)*self.row_height - render_text(obj, dc, 0, y, cur_pen, cur_dpen) + + if iw and obj.icon then + dc:seek(0, y) + if type(obj.icon) == 'table' then + dc:char(nil,obj.icon) + else + dc:string(obj.icon, obj.icon_pen or cur_pen) + end + end + + render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen) if obj.key then local keystr = gui.getKeyDisplay(obj.key) From ad4f9908fb437a989f80ea6d334e0b44d5372c16 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 10:41:50 +0400 Subject: [PATCH 073/472] Extract a generic filtered list widget from the materials dialog. --- library/lua/gui/dialogs.lua | 18 ++-- library/lua/gui/materials.lua | 81 +++--------------- library/lua/gui/script.lua | 3 +- library/lua/gui/widgets.lua | 149 ++++++++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 78 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 7d8058a93..cb8d66176 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -149,8 +149,8 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' ListBox.ATTRS{ - selection = 1, - choices = {}, + with_filter = false, + cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, on_select = DEFAULT_NIL } @@ -160,11 +160,16 @@ function ListBox:preinit(info) end function ListBox:init(info) - local spen = gui.to_pen(COLOR_CYAN, info.select_pen, nil, false) - local cpen = gui.to_pen(COLOR_LIGHTCYAN, info.cursor_pen or info.select_pen, nil, true) + local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) + local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) + + local list_widget = widgets.List + if self.with_filter then + list_widget = widgets.FilteredList + end self:addviews{ - widgets.List{ + list_widget{ view_id = 'list', selected = info.selected, choices = info.choices, @@ -199,7 +204,7 @@ function ListBox:onInput(keys) end end -function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width) +function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_width, filter) ListBox{ frame_title = title, text = text, @@ -208,6 +213,7 @@ function showListPrompt(title, text, tcolor, choices, on_select, on_cancel, min_ on_select = on_select, on_cancel = on_cancel, frame_width = min_width, + with_filter = filter, }:show() end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 17fcf2928..e8f1e19ca 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -43,28 +43,17 @@ function MaterialDialog:init(info) frame = { r = 0, b = 0 }, auto_width = true, }, - widgets.List{ + widgets.FilteredList{ view_id = 'list', - frame = { l = 0, r = 0, t = 6, b = 2 }, + not_found_label = 'No matching materials', + frame = { l = 0, r = 0, t = 4, b = 2 }, icon_width = 2, on_submit = self:callback('onSubmitItem'), }, - widgets.EditField{ - view_id = 'edit', - frame = { l = 2, t = 4 }, - on_change = self:callback('onFilterChoices'), - on_char = self:callback('onFilterChar'), - }, - widgets.Label{ - view_id = 'not_found', - text = 'No matching materials.', - text_pen = COLOR_LIGHTRED, - frame = { l = 2, r = 0, t = 6 }, - }, widgets.Label{ text = { { key = 'SELECT', text = ': Select', - disabled = function() return self.subviews.not_found.visible end + disabled = function() return not self.subviews.list:canSubmit() end } }, frame = { l = 0, b = 0 }, } @@ -198,18 +187,15 @@ function MaterialDialog:pushContext(name, choices) else table.insert(self.back_stack, { context_str = self.context_str, - all_choices = self.all_choices, - edit_text = self.subviews.edit.text, - choices = self.choices, - selected = self.subviews.list.selected, + all_choices = self.subviews.list:getChoices(), + edit_text = self.subviews.list:getFilter(), + selected = self.subviews.list:getSelected(), }) self.subviews.back.visible = true end self.context_str = name - self.all_choices = choices - self.subviews.edit.text = '' - self:setChoices(choices, 1) + self.subviews.list:setChoices(choices, 1) end function MaterialDialog:onGoBack() @@ -217,55 +203,8 @@ function MaterialDialog:onGoBack() self.subviews.back.visible = (#self.back_stack > 0) self.context_str = save.context_str - self.all_choices = save.all_choices - self.subviews.edit.text = save.edit_text - self:setChoices(save.choices, save.selected) -end - -function MaterialDialog:setChoices(choices, pos) - self.choices = choices - self.subviews.list:setChoices(self.choices, pos) - self.subviews.not_found.visible = (#self.choices == 0) -end - -function MaterialDialog:onFilterChoices(text) - local tokens = utils.split_string(text, ' ') - local choices = {} - - for i,v in ipairs(self.all_choices) do - local ok = true - local search_key = v.search_key or v.text - for _,key in ipairs(tokens) do - if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then - ok = false - break - end - end - if ok then - table.insert(choices, v) - end - end - - self:setChoices(choices) -end - -local bad_chars = { - ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, - ['['] = true, [']'] = true, ['('] = true, [')'] = true, -} - -function MaterialDialog:onFilterChar(char, text) - if bad_chars[char] then - return false - end - if char == ' ' then - if #self.choices == 1 then - self.subviews.list:submit() - return false - end - return string.match(text, '%S$') - end - return true + self.subviews.list:setChoices(save.all_choices) + self.subviews.list:setFilter(save.edit_text, save.selected) end function MaterialDialog:submitMaterial(typ, index) diff --git a/library/lua/gui/script.lua b/library/lua/gui/script.lua index 06079c426..e15f6c1b9 100644 --- a/library/lua/gui/script.lua +++ b/library/lua/gui/script.lua @@ -133,13 +133,14 @@ function showInputPrompt(title, text, tcolor, input, min_width) return wait() end -function showListPrompt(title, text, tcolor, choices, min_width) +function showListPrompt(title, text, tcolor, choices, min_width, filter) dlg.ListBox{ frame_title = title, text = text, text_pen = tcolor, choices = choices, frame_width = min_width, + with_filter = filter, on_select = mkresume(true), on_cancel = mkresume(false), on_close = qresume(nil) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 16568aa16..0a6d99ba6 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -22,6 +22,14 @@ local function getval(obj) end end +local function map_opttab(tab,idx) + if tab then + return tab[idx] + else + return idx + end +end + ------------ -- Widget -- ------------ @@ -393,6 +401,10 @@ function List:setSelected(selected) return self.selected end +function List:getChoices() + return self.choices +end + function List:getSelected() return self.selected, self.choices[self.selected] end @@ -533,4 +545,141 @@ function List:onInput(keys) end end +------------------- +-- Filtered List -- +------------------- + +FilteredList = defclass(FilteredList, Widget) + +function FilteredList:init(info) + self.edit = EditField{ + text_pen = info.cursor_pen, + frame = { l = info.icon_width, t = 0 }, + on_change = self:callback('onFilterChange'), + on_char = self:callback('onFilterChar'), + } + self.list = List{ + frame = { t = 2 }, + text_pen = info.text_pen, + cursor_pen = info.cursor_pen, + inactive_pen = info.inactive_pen, + row_height = info.row_height, + scroll_keys = info.scroll_keys, + icon_width = info.icon_width, + } + if info.on_select then + self.list.on_select = function() + return info.on_select(self:getSelected()) + end + end + if info.on_submit then + self.list.on_submit = function() + return info.on_submit(self:getSelected()) + end + end + self.not_found = Label{ + visible = false, + text = info.not_found_label or 'No matches', + text_pen = COLOR_LIGHTRED, + frame = { l = info.icon_width, t = 2 }, + } + self:addviews{ self.list, self.edit, self.not_found } + self:setChoices(info.choices, info.selected) +end + +function FilteredList:getChoices() + return self.choices +end + +function FilteredList:setChoices(choices, pos) + choices = choices or {} + self.choices = choices + self.edit.text = '' + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:submit() + return self.list:submit() +end + +function FilteredList:canSubmit() + return not self.not_found.visible +end + +function FilteredList:getSelected() + local i,v = self.list:getSelected() + return map_opttab(self.choice_index, i), v +end + +function FilteredList:getContentWidth() + return self.list:getContentWidth() +end + +function FilteredList:getContentHeight() + return self.list:getContentHeight() + 2 +end + +function FilteredList:getFilter() + return self.edit.text, self.list.choices +end + +function FilteredList:setFilter(filter, pos) + local choices = self.choices + local cidx = nil + + filter = filter or '' + self.edit.text = filter + + if filter ~= '' then + local tokens = utils.split_string(filter, ' ') + local ipos = pos + + choices = {} + cidx = {} + pos = nil + + for i,v in ipairs(self.choices) do + local ok = true + local search_key = v.search_key or v.text + for _,key in ipairs(tokens) do + if key ~= '' and not string.match(search_key, '%f[^%s\x00]'..key) then + ok = false + break + end + end + if ok then + table.insert(choices, v) + cidx[#choices] = i + if ipos == i then + pos = #choices + end + end + end + end + + self.choice_index = cidx + self.list:setChoices(choices, pos) + self.not_found.visible = (#choices == 0) +end + +function FilteredList:onFilterChange(text) + self:setFilter(text) +end + +local bad_chars = { + ['%'] = true, ['.'] = true, ['+'] = true, ['*'] = true, + ['['] = true, [']'] = true, ['('] = true, [')'] = true, +} + +function FilteredList:onFilterChar(char, text) + if bad_chars[char] then + return false + end + if char == ' ' then + return string.match(text, '%S$') + end + return true +end + return _ENV From 0046b093f761fb42f03fccb32a56efcf854c2b41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 11:49:11 +0400 Subject: [PATCH 074/472] Link visibility and event handling order. - Hidden widgets don't receive events. - Children handle events in top to bottom order. --- library/lua/gui.lua | 7 +++++-- library/lua/gui/widgets.lua | 11 +++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 90eeb823a..18b0d67d8 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -457,8 +457,11 @@ function View:onRenderBody(dc) end function View:inputToSubviews(keys) - for _,child in ipairs(self.subviews) do - if child.active and child:onInput(keys) then + local children = self.subviews + + for i=#children,1,-1 do + local child = children[i] + if child.visible and child.active and child:onInput(keys) then return true end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0a6d99ba6..05100c00b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -7,10 +7,9 @@ local utils = require('utils') local dscreen = dfhack.screen -local function show_view(view,vis,act) +local function show_view(view,vis) if view then view.visible = vis - view.active = act end end @@ -79,7 +78,7 @@ Pages = defclass(Pages, Panel) function Pages:init(args) for _,v in ipairs(self.subviews) do - show_view(v, false, false) + v.visible = false end self:setSelected(args.selected or 1) end @@ -96,9 +95,9 @@ function Pages:setSelected(idx) end end - show_view(self.subviews[self.selected], false, false) + show_view(self.subviews[self.selected], false) self.selected = math.min(math.max(1, idx), #self.subviews) - show_view(self.subviews[self.selected], true, true) + show_view(self.subviews[self.selected], true) end function Pages:getSelected() @@ -583,7 +582,7 @@ function FilteredList:init(info) text_pen = COLOR_LIGHTRED, frame = { l = info.icon_width, t = 2 }, } - self:addviews{ self.list, self.edit, self.not_found } + self:addviews{ self.edit, self.list, self.not_found } self:setChoices(info.choices, info.selected) end From ffe70e9ee71731bf93f07a67ba70f37e774cec4e Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 17 Oct 2012 14:40:28 +0200 Subject: [PATCH 075/472] ruby: unregister onupdate callback on exception --- plugins/ruby/ruby.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 81b73b7ce..ab095e8d8 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -48,6 +48,7 @@ module DFHack end end rescue + df.onupdate_unregister self puts_err "onupdate cb #$!", $!.backtrace end From 5f4f540bfaf1dcff0c057b043c1c4e23cb7fd335 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 17 Oct 2012 07:57:14 -0500 Subject: [PATCH 076/472] Fix "fastdwarf" command to print syntax where appropriate, silence warning --- plugins/fastdwarf.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/plugins/fastdwarf.cpp b/plugins/fastdwarf.cpp index 28104b909..6292fc85e 100644 --- a/plugins/fastdwarf.cpp +++ b/plugins/fastdwarf.cpp @@ -42,7 +42,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } - 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]; @@ -108,12 +107,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) static command_result fastdwarf (color_ostream &out, vector & parameters) { - if (parameters.size() > 2) { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + if (parameters.size() > 2) + return CR_WRONG_USAGE; - if (parameters.size() <= 2) + if ((parameters.size() == 1) || (parameters.size() == 2)) { if (parameters.size() == 2) { @@ -122,10 +119,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter else if (parameters[1] == "1") enable_teledwarf = true; else - { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + return CR_WRONG_USAGE; } else enable_teledwarf = false; @@ -155,10 +149,7 @@ static command_result fastdwarf (color_ostream &out, vector & parameter } } else - { - out.print("Incorrect usage.\n"); - return CR_FAILURE; - } + return CR_WRONG_USAGE; } out.print("Current state: fast = %d, teleport = %d.\n", From 4f7895f5717f074dd4f3058aa86fbfe88835aa51 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 18:29:15 +0400 Subject: [PATCH 077/472] Support restricting the set of materials presented in the dialog. --- library/lua/gui/dialogs.lua | 1 + library/lua/gui/materials.lua | 103 +++++++++++++++++++++------------- 2 files changed, 66 insertions(+), 38 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index cb8d66176..079760604 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -173,6 +173,7 @@ function ListBox:init(info) view_id = 'list', selected = info.selected, choices = info.choices, + icon_width = info.icon_width, text_pen = spen, cursor_pen = cpen, on_submit = function(sel,obj) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index e8f1e19ca..56b3eee5f 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -21,6 +21,11 @@ MaterialDialog.ATTRS{ frame_inset = 1, frame_title = 'Select Material', -- new attrs + none_caption = 'none', + use_inorganic = true, + use_creature = true, + use_plant = true, + mat_filter = DEFAULT_NIL, on_select = DEFAULT_NIL, on_cancel = DEFAULT_NIL, on_close = DEFAULT_NIL, @@ -73,20 +78,32 @@ end function MaterialDialog:initBuiltinMode() local choices = { - { text = 'none', mat_type = -1, mat_index = -1 }, - { icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', - cb = self:callback('initInorganicMode') }, - { icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', - cb = self:callback('initCreatureMode')}, - { icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', - cb = self:callback('initPlantMode') }, + { text = self.none_caption, mat_type = -1, mat_index = -1 }, } - self:addMaterials( - choices, - df.global.world.raws.mat_table.builtin, 0, -1, - df.builtin_mats._last_item - ) + if self.use_inorganic then + table.insert(choices, { + icon = ARROW, text = 'inorganic', key = 'CUSTOM_SHIFT_I', + cb = self:callback('initInorganicMode') + }) + end + if self.use_creature then + table.insert(choices, { + icon = ARROW, text = 'creature', key = 'CUSTOM_SHIFT_C', + cb = self:callback('initCreatureMode') + }) + end + if self.use_plant then + table.insert(choices, { + icon = ARROW, text = 'plant', key = 'CUSTOM_SHIFT_P', + cb = self:callback('initPlantMode') + }) + end + + local table = df.global.world.raws.mat_table.builtin + for i=0,df.builtin_mats._last_item do + self:addMaterial(choices, table[i], i, -1, false, nil) + end self:pushContext('Any material', choices) end @@ -94,11 +111,9 @@ end function MaterialDialog:initInorganicMode() local choices = {} - self:addMaterials( - choices, - df.global.world.raws.inorganics, 0, nil, - nil, 'material' - ) + for i,mat in ipairs(df.global.world.raws.inorganics) do + self:addMaterial(choices, mat.material, 0, i, false, mat) + end self:pushContext('Inorganic materials', choices) end @@ -124,8 +139,26 @@ function MaterialDialog:initPlantMode() end function MaterialDialog:addObjectChoice(choices, obj, name, typ, index) - if #obj.material == 1 then - self:addMaterial(choices, obj.material[0], typ, index, true) + -- Check if any eligible children + local count = #obj.material + local idx = 0 + + if self.mat_filter then + count = 0 + for i,v in ipairs(obj.material) do + if self.mat_filter(v, obj, typ+i, index) then + count = count + 1 + if count > 1 then break end + idx = i + end + end + end + + -- Create an entry + if count < 1 then + return + elseif count == 1 then + self:addMaterial(choices, obj.material[idx], typ+idx, index, true, obj) else table.insert(choices, { icon = ARROW, text = name, mat_type = typ, mat_index = index, @@ -136,29 +169,19 @@ end function MaterialDialog:onSelectObj(item) local choices = {} - self:addMaterials(choices, item.obj.material, item.mat_type, item.mat_index) + for i,v in ipairs(item.obj.material) do + self:addMaterial(choices, v, item.mat_type+i, item.mat_index, false, item.obj) + end self:pushContext(item.text, choices) end -function MaterialDialog:addMaterials(choices, vector, tid, index, maxid, field) - for i=0,(maxid or #vector-1) do - local mat = vector[i] - if mat and field then - mat = mat[field] - end - if mat then - local typ, idx - if index then - typ, idx = tid+i, index - else - typ, idx = tid, i - end - self:addMaterial(choices, mat, typ, idx) - end +function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix, parent) + -- Check the filter + if self.mat_filter and not self.mat_filter(mat, parent, typ, idx) then + return end -end -function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) + -- Find the material name local state = 0 if mat.heat.melting_point <= 10015 then state = 1 @@ -167,11 +190,14 @@ function MaterialDialog:addMaterial(choices, mat, typ, idx, pfix) name = string.gsub(name, '^frozen ','') name = string.gsub(name, '^molten ','') name = string.gsub(name, '^condensed ','') + + -- Add prefix if requested local key if pfix and mat.prefix ~= '' then name = mat.prefix .. ' ' .. name key = mat.prefix end + table.insert(choices, { text = name, search_key = key, @@ -239,10 +265,11 @@ function MaterialDialog:onInput(keys) end end -function showMaterialPrompt(title, prompt, on_select, on_cancel) +function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) MaterialDialog{ frame_title = title, prompt = prompt, + mat_filter = mat_filter, on_select = on_select, on_cancel = on_cancel, }:show() From 2d4935bc17039c63d9ffc229578f3d397ffc8b80 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 19:16:18 +0400 Subject: [PATCH 078/472] Add lua API functions for verifying item and material vs job_item flags. --- Lua API.rst | 9 +++++++++ NEWS | 3 +++ library/LuaApi.cpp | 2 ++ library/include/modules/Job.h | 4 ++++ library/modules/Job.cpp | 26 ++++++++++++++++++++++++++ scripts/gui/guide-path.lua | 4 ++-- 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index bbee8646c..4d689ee2b 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -863,6 +863,15 @@ Job module if there are any jobs with ``first_id <= id < job_next_id``, a lua list containing them. +* ``dfhack.job.isSuitableItem(job_item, item_type, item_subtype)`` + + Does basic sanity checks to verify if the suggested item type matches + the flags in the job item. + +* ``dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)`` + + Likewise, if replacing material. + Units module ------------ diff --git a/NEWS b/NEWS index 40e9315c5..61e8a5b3e 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,7 @@ DFHack future Internals: - support for displaying active keybindings properly. + - support for reusable widgets in lua screen library. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. - stopped Dwarf Manipulator from unmasking vampires. @@ -9,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New GUI scripts: + - gui/guide-path: displays the cached path for minecart Guide orders. DFHack v0.34.11-r2 diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index ef571bcb7..9f380c353 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -809,6 +809,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,getWorker), WRAPM(Job,checkBuildingsNow), WRAPM(Job,checkDesignationsNow), + WRAPM(Job,isSuitableItem), + WRAPM(Job,isSuitableMaterial), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 490d79a34..853813073 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -32,6 +32,7 @@ distribution. #include "DataDefs.h" #include "df/job_item_ref.h" +#include "df/item_type.h" namespace df { @@ -69,6 +70,9 @@ namespace DFHack DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item, df::job_item_ref::T_role role, int filter_idx = -1, int insert_idx = -1); + + DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); + DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index); } DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index df2009d0a..def3b4192 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -361,3 +361,29 @@ bool DFHack::Job::attachJobItem(df::job *job, df::item *item, return true; } + +bool Job::isSuitableItem(df::job_item *item, df::item_type itype, int isubtype) +{ + CHECK_NULL_POINTER(item); + + if (itype == item_type::NONE) + return true; + + ItemTypeInfo iinfo(itype, isubtype); + MaterialInfo minfo(item); + + return iinfo.isValid() && iinfo.matches(*item, &minfo); +} + +bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) +{ + CHECK_NULL_POINTER(item); + + if (mat_type == -1 && mat_index == -1) + return true; + + ItemTypeInfo iinfo(item); + MaterialInfo minfo(mat_type, mat_index); + + return minfo.isValid() && iinfo.matches(*item, &minfo); +} diff --git a/scripts/gui/guide-path.lua b/scripts/gui/guide-path.lua index 1546150b7..a807e032d 100644 --- a/scripts/gui/guide-path.lua +++ b/scripts/gui/guide-path.lua @@ -143,8 +143,8 @@ function GuidePathUI:onRenderBody(dc) end dc:newline():newline(1) - dc:key('CUSTOM_Z'):string(": Reset path, ",COLOR_GREY,nil,path_ok~=nil) - dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) + dc:key('CUSTOM_Z'):string(": Reset path",COLOR_GREY,nil,path_ok~=nil) + --dc:key('CUSTOM_P'):string(": Find path",COLOR_GREY,nil,false) dc:newline(1) dc:key('CUSTOM_C'):string(": Zoom cur, ") dc:key('CUSTOM_N'):string(": Zoom next") From 1b5a6616e2544674af95f4c842cbb807107d74f3 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 17 Oct 2012 19:33:20 +0300 Subject: [PATCH 079/472] Just companion orders tool --- scripts/gui/companion-order.lua | 398 ++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 scripts/gui/companion-order.lua diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua new file mode 100644 index 000000000..6b6a79aa2 --- /dev/null +++ b/scripts/gui/companion-order.lua @@ -0,0 +1,398 @@ + +local gui = require 'gui' +local dlg = require 'gui.dialogs' + +local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z) +local permited_equips={} + +permited_equips[df.item_backpackst]="UPPERBODY" +permited_equips[df.item_flaskst]="UPPERBODY" +permited_equips[df.item_armorst]="UPPERBODY" +permited_equips[df.item_shoesst]="STANCE" +permited_equips[df.item_glovesst]="GRASP" +permited_equips[df.item_helmst]="HEAD" +permited_equips[df.item_pantsst]="LOWERBODY" +function DoesHaveSubtype(item) + if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) then + return false + end + return true +end +function CheckCursor(p) + if p.x==-30000 then + dlg.showMessage( + 'Companion orders', + 'You must have a cursor on some tile!', COLOR_LIGHTRED + ) + return false + end + return true +end +function GetCaste(race_id,caste_id) + local race=df.creature_raw.find(race_id) + return race.caste[caste_id] +end + +function EnumBodyEquipable(race_id,caste_id) + local caste=GetCaste(race_id,caste_id) + local bps=caste.body_info.body_parts + local ret={} + for k,v in pairs(bps) do + ret[k]={bp=v,layers={[0]={size=0,permit=0},[1]={size=0,permit=0},[2]={size=0,permit=0},[3]={size=0,permit=0} } } + end + return ret +end +function ReadCurrentEquiped(body_equip,unit) + for k,v in pairs(unit.inventory) do + if v.mode==2 then + local bpid=v.body_part_id + if DoesHaveSubtype(v.item) then + local sb=v.item.subtype.props + local trg=body_equip[bpid] + local trg_layer=trg.layers[sb.layer] + + if trg_layer.permit==0 then + trg_layer.permit=sb.layer_permit + else + if trg_layer.permit>sb.layer_permit then + trg_layer.permit=sb.layer_permit + end + end + trg_layer.size=trg_layer.size+sb.layer_size + end + end + end +end +function LayeringPermits(body_part,item) + if not DoesHaveSubtype(item) then + return true + end + local sb=item.subtype.props + local trg_layer=body_part.layers[sb.layer] + if math.min(trg_layer.permit ,sb.layer_permit)0 and #items>0 do + if(dfhack.items.moveToInventory(items[#items],v,1,grasps[#grasps])) then + table.remove(grasps) + end + table.remove(items) + end + local backpack=GetBackpack(v) + if backpack then + while #items>0 do + dfhack.items.moveToContainer(items[#items],backpack) + table.remove(items) + end + end + end + return true +end}, +{name="unequip",f=function (unit_list) + --remove and drop all the stuff (todo maybe a gui too?) + for k,v in pairs(unit_list) do + while #v.inventory ~=0 do + dfhack.items.moveToGround(v.inventory[0].item,v.pos) + end + end + return true +end}, +--[=[ +{name="roam not working :<",f=function (unit_list,pos,dist) --does not work + if not CheckCursor(pos) then + return false + end + dist=dist or 5 + for k,v in pairs(unit_list) do + v.idle_area:assign(pos) + v.idle_area_threshold=dist + end + return true +end}, +--]=] +{name="wait",f=function (unit_list) + for k,v in pairs(unit_list) do + v.relations.group_leader_id=-1 + end + return true +end}, +{name="follow",f=function (unit_list) + local adv=df.global.world.units.active[0] + for k,v in pairs(unit_list) do + v.relations.group_leader_id=adv.id + end + return true +end}, +{name="leave",f=function (unit_list) + local adv=df.global.world.units.active[0] + local t_nem=dfhack.units.getNemesis(adv) + for k,v in pairs(unit_list) do + + v.relations.group_leader_id=-1 + local u_nem=dfhack.units.getNemesis(v) + if u_nem then + u_nem.group_leader_id=-1 + end + if t_nem and u_nem then + for k,v in pairs(t_nem.companions) do + if v==u_nem.id then + t_nem.companions:erase(k) + break + end + end + end + end + return true +end}, +} +local cheats={} +--[[ todo: add cheats...]]-- +function getCompanions(unit) + unit=unit or df.global.world.units.active[0] + local t_nem=dfhack.units.getNemesis(unit) + if t_nem==nil then + qerror("Invalid unit! No nemesis record") + end + local ret={} + for k,v in pairs(t_nem.companions) do + local u=df.nemesis_record.find(v) + if u.unit then + table.insert(ret,u.unit) + end + end + return ret +end + + +CompanionUi=defclass(CompanionUi,gui.FramedScreen) +CompanionUi.ATTRS{ + frame_title = "Companions", +} +function CompanionUi:init(args) + self.unit_list=args.unit_list + self.selected={} + for i=0,26 do + self.selected[i]=true + end +end +function CompanionUi:GetSelectedUnits() + local ret={} + for k,v in pairs(self.unit_list) do + if self.selected[k] then + table.insert(ret,v) + end + end + return ret +end +function CompanionUi:onInput(keys) + + + if keys.LEAVESCREEN then + self:dismiss() + elseif keys._STRING then + local s=keys._STRING + if s==string.byte('*') then + local v=self.selected[1] or false + for i=0,26 do + + self.selected[i]=not v + end + end + if s>=string.byte('a') and s<=string.byte('z') then + local idx=s-string.byte('a')+1 + if self.selected[idx] then + self.selected[idx]=false + else + self.selected[idx]=true + end + end + if s>=string.byte('A') and s<=string.byte('Z') then + local idx=s-string.byte('A')+1 + if orders[idx] and orders[idx].f then + if orders[idx].f(self:GetSelectedUnits(),cursor) then + self:dismiss() + end + end + --do order + end + end +end +function CompanionUi:onRenderBody( dc) + --list widget goes here... + local char_a=string.byte('a')-1 + dc:newline(1):string("*. All") + for k,v in ipairs(self.unit_list) do + if self.selected[k] then + dc:pen(COLOR_GREEN) + else + dc:pen(COLOR_GREY) + end + dc:newline(1):string(string.char(k+char_a)..". "):string(dfhack.TranslateName(v.name)); + end + dc:pen(COLOR_GREY) + local w,h=self:getWindowSize() + local char_A=string.byte('A')-1 + for k,v in ipairs(orders) do + dc:seek(w/2,k):string(string.char(k+char_A)..". "):string(v.name); + end +end +local screen=CompanionUi{unit_list=getCompanions()} +screen:show() \ No newline at end of file From 2bbd00a8ecffa92eb20f50aa8f0f78dfe702ef36 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 20:58:37 +0400 Subject: [PATCH 080/472] Add pairs and ipairs support for objects in df tree. --- library/LuaWrapper.cpp | 132 ++++++++++++++++++++++++++++++++++------- 1 file changed, 109 insertions(+), 23 deletions(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 3da79e932..75e3b697c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1265,6 +1265,51 @@ static void BuildTypeMetatable(lua_State *state, type_identity *type) * Recursive walk of scopes to construct the df... tree. */ +static int wtype_pnext(lua_State *L) +{ + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, lua_upvalueindex(1))) + return 2; + lua_pushnil(L); + return 1; +} + +static int wtype_pairs(lua_State *state) +{ + lua_pushvalue(state, lua_upvalueindex(1)); + lua_pushcclosure(state, wtype_pnext, 1); + lua_pushnil(state); + lua_pushnil(state); + return 3; +} + +static int wtype_inext(lua_State *L) +{ + int i = luaL_checkint(L, 2); + i++; /* next value */ + if (i <= lua_tointeger(L, lua_upvalueindex(2))) + { + lua_pushinteger(L, i); + lua_rawgeti(L, lua_upvalueindex(1), i); + return 2; + } + else + { + lua_pushnil(L); + return 1; + } +} + +static int wtype_ipairs(lua_State *state) +{ + lua_pushvalue(state, lua_upvalueindex(1)); + lua_pushvalue(state, lua_upvalueindex(3)); + lua_pushcclosure(state, wtype_inext, 2); + lua_pushnil(state); + lua_pushvalue(state, lua_upvalueindex(2)); + return 3; +} + static void RenderTypeChildren(lua_State *state, const std::vector &children); void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *name) @@ -1278,7 +1323,7 @@ void LuaWrapper::AssociateId(lua_State *state, int table, int val, const char *n lua_rawset(state, table); } -static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) +static void FillEnumKeys(lua_State *state, int ix_meta, int ftable, enum_identity *eid) { const char *const *keys = eid->getKeys(); @@ -1296,11 +1341,17 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) if (eid->getFirstItem() <= eid->getLastItem()) { + lua_pushvalue(state, base+1); + lua_pushinteger(state, eid->getFirstItem()-1); + lua_pushinteger(state, eid->getLastItem()); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + lua_pushinteger(state, eid->getFirstItem()); - lua_setfield(state, base+1, "_first_item"); + lua_setfield(state, ftable, "_first_item"); lua_pushinteger(state, eid->getLastItem()); - lua_setfield(state, base+1, "_last_item"); + lua_setfield(state, ftable, "_last_item"); } SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); @@ -1321,7 +1372,7 @@ static void FillEnumKeys(lua_State *state, int ftable, enum_identity *eid) lua_setmetatable(state, ftable); } -static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *eid) +static void FillBitfieldKeys(lua_State *state, int ix_meta, int ftable, bitfield_identity *eid) { // Create a new table attached to ftable as __index lua_newtable(state); @@ -1338,11 +1389,17 @@ static void FillBitfieldKeys(lua_State *state, int ftable, bitfield_identity *ei i += bits[i].size-1; } + lua_pushvalue(state, base+1); + lua_pushinteger(state, -1); + lua_pushinteger(state, eid->getNumBits()-1); + lua_pushcclosure(state, wtype_ipairs, 3); + lua_setfield(state, ix_meta, "__ipairs"); + lua_pushinteger(state, 0); - lua_setfield(state, base+1, "_first_item"); + lua_setfield(state, ftable, "_first_item"); lua_pushinteger(state, eid->getNumBits()-1); - lua_setfield(state, base+1, "_last_item"); + lua_setfield(state, ftable, "_last_item"); SaveInTable(state, eid, &DFHACK_ENUM_TABLE_TOKEN); @@ -1355,7 +1412,12 @@ static void RenderType(lua_State *state, compound_identity *node) assert(node->getName()); std::string name = node->getFullName(); - int base = lua_gettop(state); + // Frame: + // base+1 - outer table + // base+2 - metatable of outer table + // base+3 - inner table + // base+4 - pairs table + Lua::StackUnwinder base(state); lua_newtable(state); if (!lua_checkstack(state, 20)) @@ -1365,51 +1427,59 @@ static void RenderType(lua_State *state, compound_identity *node) // metatable lua_newtable(state); + int ix_meta = base+2; lua_dup(state); lua_setmetatable(state, base+1); lua_pushstring(state, name.c_str()); - lua_setfield(state, base+2, "__metatable"); + lua_setfield(state, ix_meta, "__metatable"); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_TYPE_TOSTRING_NAME); - lua_setfield(state, base+2, "__tostring"); + lua_setfield(state, ix_meta, "__tostring"); lua_pushlightuserdata(state, node); - lua_rawsetp(state, base+2, &DFHACK_IDENTITY_FIELD_TOKEN); + lua_rawsetp(state, ix_meta, &DFHACK_IDENTITY_FIELD_TOKEN); // inner table lua_newtable(state); + int ftable = base+3; lua_dup(state); - lua_setfield(state, base+2, "__index"); + lua_setfield(state, ix_meta, "__index"); - int ftable = base+3; + // pairs table + lua_newtable(state); + int ptable = base+4; + + lua_pushvalue(state, ptable); + lua_pushcclosure(state, wtype_pairs, 1); + lua_setfield(state, ix_meta, "__pairs"); switch (node->type()) { case IDTYPE_STRUCT: lua_pushstring(state, "struct-type"); lua_setfield(state, ftable, "_kind"); - IndexStatics(state, base+2, base+3, (struct_identity*)node); + IndexStatics(state, ix_meta, ftable, (struct_identity*)node); break; case IDTYPE_CLASS: lua_pushstring(state, "class-type"); lua_setfield(state, ftable, "_kind"); - IndexStatics(state, base+2, base+3, (struct_identity*)node); + IndexStatics(state, ix_meta, ftable, (struct_identity*)node); break; case IDTYPE_ENUM: lua_pushstring(state, "enum-type"); lua_setfield(state, ftable, "_kind"); - FillEnumKeys(state, ftable, (enum_identity*)node); + FillEnumKeys(state, ix_meta, ftable, (enum_identity*)node); break; case IDTYPE_BITFIELD: lua_pushstring(state, "bitfield-type"); lua_setfield(state, ftable, "_kind"); - FillBitfieldKeys(state, ftable, (bitfield_identity*)node); + FillBitfieldKeys(state, ix_meta, ftable, (bitfield_identity*)node); break; case IDTYPE_GLOBAL: @@ -1425,14 +1495,14 @@ static void RenderType(lua_State *state, compound_identity *node) BuildTypeMetatable(state, node); lua_dup(state); - lua_setmetatable(state, base+3); + lua_setmetatable(state, ftable); lua_getfield(state, -1, "__newindex"); - lua_setfield(state, base+2, "__newindex"); + lua_setfield(state, ix_meta, "__newindex"); lua_getfield(state, -1, "__pairs"); - lua_setfield(state, base+2, "__pairs"); + lua_setfield(state, ix_meta, "__pairs"); - lua_pop(state, 3); + base += 1; return; } @@ -1454,17 +1524,25 @@ static void RenderType(lua_State *state, compound_identity *node) lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_IS_INSTANCE_NAME); lua_setfield(state, ftable, "is_instance"); - lua_pop(state, 2); + base += 1; } static void RenderTypeChildren(lua_State *state, const std::vector &children) { + // fieldtable pairstable | + int base = lua_gettop(state); + for (size_t i = 0; i < children.size(); i++) { RenderType(state, children[i]); lua_pushstring(state, children[i]->getName()); lua_swap(state); - lua_rawset(state, -3); + + // save in both tables + lua_pushvalue(state, -2); + lua_pushvalue(state, -2); + lua_rawset(state, base); + lua_rawset(state, base-1); } } @@ -1524,10 +1602,13 @@ static int DoAttach(lua_State *state) { // Assign df a metatable with read-only contents lua_newtable(state); + lua_newtable(state); // Render the type structure RenderTypeChildren(state, compound_identity::getTopScope()); + lua_swap(state); // -> pairstable fieldtable + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_SIZEOF_NAME); lua_setfield(state, -2, "sizeof"); lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_NEW_NAME); @@ -1558,7 +1639,12 @@ static int DoAttach(lua_State *state) lua_pushcfunction(state, meta_isnull); lua_setfield(state, -2, "isnull"); - freeze_table(state, false, "df"); + freeze_table(state, true, "df"); + + lua_swap(state); + lua_pushcclosure(state, wtype_pairs, 1); + lua_setfield(state, -2, "__pairs"); + lua_pop(state, 1); } return 1; From 1e2570f4cbbf8fe3ab0c3912418482e56f73a6cf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 17 Oct 2012 21:42:06 +0400 Subject: [PATCH 081/472] Add an extremely simple item selection dialog by wrapping ListBox. --- library/lua/gui/dialogs.lua | 2 +- library/lua/gui/materials.lua | 66 +++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 079760604..5811e94e6 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -191,7 +191,7 @@ function ListBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) local list = self.subviews.list list.frame.t = mh+1 - return math.max(mw, list:getContentWidth()), mh+1+list:getContentHeight() + return math.max(mw, list:getContentWidth()), mh+1+math.min(20,list:getContentHeight()) end function ListBox:onInput(keys) diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 56b3eee5f..baf641976 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -4,6 +4,7 @@ local _ENV = mkmodule('gui.materials') local gui = require('gui') local widgets = require('gui.widgets') +local dlg = require('gui.dialogs') local utils = require('utils') ARROW = string.char(26) @@ -275,4 +276,69 @@ function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) }:show() end +local itemdefs = df.global.world.raws.itemdefs +local itemtype_info = { + TRAPPARTS = { name = 'mechanisms' }, + WEAPON = { defs = itemdefs.weapons }, + TRAPCOMP = { defs = itemdefs.trapcomps }, + TOY = { defs = itemdefs.toys }, + TOOL = { defs = itemdefs.tools }, + INSTRUMENT = { defs = itemdefs.instruments }, + ARMOR = { defs = itemdefs.armor }, + AMMO = { defs = itemdefs.ammo }, + SIEGEAMMO = { defs = itemdefs.siege_ammo }, + GLOVES = { defs = itemdefs.gloves }, + SHOES = { defs = itemdefs.shoes }, + SHIELD = { defs = itemdefs.shields }, + HELM = { defs = itemdefs.helms }, + PANTS = { defs = itemdefs.pants }, + FOOD = { defs = itemdefs.food }, +} + +function ItemTypeDialog(args) + args.text = args.prompt or 'Type or select an item type' + args.text_pen = COLOR_WHITE + args.with_filter = true + args.icon_width = 2 + + local choices = { { + icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1 + } } + local filter = args.item_filter + + for itype = 0,df.item_type._last_item do + local key = df.item_type[itype] + local info = itemtype_info[key] + + if not filter or filter(itype,-1) then + local name = key + local icon + if info and info.defs then + name = 'any '..name + icon = '+' + end + if info and info.name then + name = info.name + end + table.insert(choices, { + icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1 + }) + end + + if info and info.defs then + for subtype,def in ipairs(info.defs) do + if not filter or filter(itype,subtype,def) then + table.insert(choices, { + icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype + }) + end + end + end + end + + args.choices = choices + + return dlg.ListBox(args) +end + return _ENV From da92fb9a1c8dfd373ac9c0781729fe722009fdcb Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 17 Oct 2012 21:43:44 +0300 Subject: [PATCH 082/472] Start of dfusion module. Fixed small error in memscan.lua and start of custom embark command. --- library/lua/memscan.lua | 2 +- plugins/Dfusion/luafiles/embark/init.lua | 25 ++++ plugins/lua/dfusion.lua | 163 +++++++++++++++++++++++ 3 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 plugins/lua/dfusion.lua diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index ba3efd708..6796b3563 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -24,7 +24,7 @@ function CheckedArray:__len() return self.count end function CheckedArray:__index(idx) - if type(idx) == number then + if type(idx) == "number" then if idx >= self.count then error('Index out of bounds: '..tostring(idx)) end diff --git a/plugins/Dfusion/luafiles/embark/init.lua b/plugins/Dfusion/luafiles/embark/init.lua index 529c2d1e5..aa8f2822d 100644 --- a/plugins/Dfusion/luafiles/embark/init.lua +++ b/plugins/Dfusion/luafiles/embark/init.lua @@ -1,3 +1,28 @@ +local dfu=require("dfusion") +local ms=require("memscan") + +CustomEmbark=defclass(CustomEmbark,dfu.BinaryPlugin) +CustomEmbark.ATTRS{filename="dfusion/embark/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} +function CustomEmbark:install() + local stoff=dfhack.internal.getAddress('start_dwarf_count') + if stoff==nil then + error("address for start_dwarf_count not found!") + end + local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) + local needle={0x0f,0xb7,0x0d} --movzx,... + add_dword(needle,race_id_offset) -- ...word ptr[] + local mem=ms.get_code_segment() + local trg_offset=mem.uint8_t.find(needle,stoff)--maybe endoff=stoff+bignumber + if trg_offset==nil then + error("address for race_load not found") + end + needle={0x83,0xc8,0xff} -- or eax, 0xFF + local caste_offset=mem.uint8_t.find(needle,trg_offset) + if caste_offset==nil or caste_offset-stoff>1000 then + error("Caste change code not found or found too far!") + end + +end function MakeTable(modpos,modsize,names) count=0 castes={} diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua new file mode 100644 index 000000000..06128d29b --- /dev/null +++ b/plugins/lua/dfusion.lua @@ -0,0 +1,163 @@ +-- Stuff used by dfusion +local _ENV = mkmodule('plugins.dfusion') + +local ms=require("memscan") + +local marker={0xDE,0xAD,0xBE,0xEF} +patches={} +-- A reversable binary patch +BinaryPatch=defclass(BinaryPatch) +BinaryPatch.ATTRS {pre_data=DEFAULT_NIL,data=DEFAULT_NIL,address=DEFAULT_NIL,name=DEFAULT_NIL} +function BinaryPatch:init(args) + self.is_applied=false + if args.pre_data==nil or args.data==nil or args.address==nil or args.name==nil then + error("Invalid parameters to binary patch") + end + if patches[self.name]~=nil then + error("Patch already exist") + end + self.max_val=0 + for k,v in pairs(args.pre_data) do + if type(k)~="number" then + error("non number key in pre_data") + end + if self.max_val Date: Sat, 20 Oct 2012 17:06:33 +0400 Subject: [PATCH 083/472] Add a few utility functions to the lua api. --- Compile.html | 104 ++++++++++++++++++++++++-------- Compile.rst | 49 +++++++++------ Lua API.html | 33 +++++++++- Lua API.rst | 22 ++++++- Readme.html | 17 +++--- library/LuaApi.cpp | 25 ++++++++ library/include/modules/Items.h | 4 ++ library/modules/Items.cpp | 51 ++++++++++++---- library/xml | 2 +- 9 files changed, 244 insertions(+), 63 deletions(-) diff --git a/Compile.html b/Compile.html index 41d409112..e5f6ca679 100644 --- a/Compile.html +++ b/Compile.html @@ -334,20 +334,21 @@ ul.auto-toc {
                                            • Build
                                            • -
                                            • Windows
                                            • +
                                              +

                                              Mac OS X

                                              +
                                                +
                                              1. Download and unpack a copy of the latest DF

                                                +
                                              2. +
                                              3. Install Xcode from Mac App Store

                                                +
                                              4. +
                                              5. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools.

                                                +
                                              6. +
                                              7. Install MacPorts.

                                                +
                                              8. +
                                              9. Install dependencies from MacPorts:

                                                +
                                                  +
                                                • sudo port install gcc45 +universal cmake +universal git-core +universal

                                                  +

                                                  This will take some time—maybe hours, depending on your machine.

                                                  +
                                                • +
                                                • At some point during this process, it may ask you to install a Java environment; let it do so.

                                                  +
                                                • +
                                                +
                                              10. +
                                              11. Install perl dependencies

                                                +
                                                +
                                                  +
                                                1. sudo cpan

                                                  +

                                                  If this is the first time you've run cpan, you will need to go through the setup +process. Just stick with the defaults for everything and you'll be fine.

                                                  +
                                                2. +
                                                3. install XML::LibXML

                                                  +
                                                4. +
                                                5. install XML::LibXSLT

                                                  +
                                                6. +
                                                +
                                                +
                                              12. +
                                              13. Get the dfhack source:

                                                +
                                                +git clone https://github.com/danaris/dfhack.git
                                                +cd dfhack
                                                +git submodule init
                                                +git submodule update
                                                +
                                                +
                                              14. +
                                              15. Build dfhack:

                                                +
                                                +mkdir build-osx
                                                +cd build-osx
                                                +export CC=/opt/local/bin/gcc-mp-4.5
                                                +export CXX=/opt/local/bin/g++-mp-4.5
                                                +cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory
                                                +make
                                                +make install
                                                +
                                                +
                                              16. +
                                              +
                                              -

                                              Windows

                                              +

                                              Windows

                                              On Windows, DFHack replaces the SDL library distributed with DF.

                                              -

                                              How to get the code

                                              +

                                              How to get the code

                                              DFHack doesn't have any kind of system of code snapshots in place, so you will have to get code from the github repository using git. You will need some sort of Windows port of git, or a GUI. Some examples:

                                              @@ -428,7 +484,7 @@ git submodule update

                                              If you want to get really involved with the development, create an account on github, make a clone there and then use that as your remote repository instead. Detailed instructions are beyond the scope of this document. If you need help, join us on IRC (#dfhack channel on freenode).

                                              -

                                              Dependencies

                                              +

                                              Dependencies

                                              First, you need cmake. Get the win32 installer version from the official site: http://www.cmake.org/cmake/resources/software.html

                                              It has the usual installer wizard. Make sure you let it add its binary folder @@ -445,7 +501,7 @@ Grab it from Microsoft's site.

                                              If you already have a different version of perl (for example the one from cygwin), you can run into some trouble. Either remove the other perl install from PATH, or install libxml and libxslt for it instead. Strawberry perl works though and has all the required packages.

                                              -

                                              Build

                                              +

                                              Build

                                              There are several different batch files in the build folder along with a script that's used for picking the DF path.

                                              First, run set_df_path.vbs and point the dialog that pops up at your DF folder that you want to use for development. Next, run one of the scripts with generate prefix. These create the MSVC solution file(s):

                                              @@ -467,7 +523,7 @@ So pick either Release or RelWithDebInfo build and build the INSTALL target.

                                              -

                                              Build types

                                              +

                                              Build types

                                              cmake allows you to pick a build type by changing this variable: CMAKE_BUILD_TYPE

                                              @@ -479,7 +535,7 @@ cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE
                                               'RelWithDebInfo'. 'Debug' is not available on Windows.

                                              -

                                              Using the library as a developer

                                              +

                                              Using the library as a developer

                                              Currently, the most direct way to use the library is to write a plugin that can be loaded by it. All the plugins can be found in the 'plugins' folder. There's no in-depth documentation on how to write one yet, but it should be easy enough to copy one and just follow the pattern.

                                              @@ -497,29 +553,29 @@ The main license is zlib/libpng, some bits are MIT licensed, and some are BSD li

                                              Feel free to add your own extensions and plugins. Contributing back to the dfhack repository is welcome and the right thing to do :)

                                              -

                                              DF data structure definitions

                                              +

                                              DF data structure definitions

                                              DFHack uses information about the game data structures, represented via xml files in the library/xml/ submodule.

                                              Data structure layouts are described in files following the df.*.xml name pattern. This information is transformed by a perl script into C++ headers describing the structures, and associated metadata for the Lua wrapper. These headers and data are then compiled into the DFHack libraries, thus necessitating a compatibility break every time layouts change; in return it significantly boosts the efficiency and capabilities of DFHack code.

                                              Global object addresses are stored in symbols.xml, which is copied to the dfhack release package and loaded as data at runtime.

                                              -

                                              Remote access interface

                                              +

                                              Remote access interface

                                              DFHack supports remote access by exchanging Google protobuf messages via a TCP socket. Both the core and plugins can define remotely accessible methods. The dfhack-run command uses this interface to invoke ordinary console commands.

                                              Currently the supported set of requests is limited, because the developers don't know what exactly is most useful.

                                              Protocol client implementations exist for Java and C#.

                                              -

                                              Contributing to DFHack

                                              +

                                              Contributing to DFHack

                                              Several things should be kept in mind when contributing to DFHack.

                                              -

                                              Coding style

                                              +

                                              Coding style

                                              DFhack uses ANSI formatting and four spaces as indentation. Line endings are UNIX. The files use UTF-8 encoding. Code not following this won't make me happy, because I'll have to fix it. There's a good chance I'll make you fix it ;)

                                              -

                                              How to get new code into DFHack

                                              +

                                              How to get new code into DFHack

                                              You can send patches or make a clone of the github repo and ask me on the IRC channel to pull your code in. I'll review it and see if there are any problems. I'll fix them if they are minor.

                                              @@ -529,7 +585,7 @@ this is also a good place to dump new ideas and/or bugs that need fixing.

                                              -

                                              Memory research

                                              +

                                              Memory research

                                              If you want to do memory research, you'll need some tools and some knowledge. In general, you'll need a good memory viewer and optionally something to look at machine code without getting crazy :)

                                              diff --git a/Compile.rst b/Compile.rst index a71642060..3340fc97e 100644 --- a/Compile.rst +++ b/Compile.rst @@ -72,24 +72,39 @@ Mac OS X 3. Open Xcode, go to Preferences > Downloads, and install the Command Line Tools. 4. Install MacPorts. 5. Install dependencies from MacPorts: - * sudo port install gcc45 +universal cmake +universal git-core +universal (This will take some time—maybe hours, depending on your machine.) - * At some point during this process, it may ask you to install a Java environment; let it do so. + + * ``sudo port install gcc45 +universal cmake +universal git-core +universal`` + + This will take some time—maybe hours, depending on your machine. + + * At some point during this process, it may ask you to install a Java environment; let it do so. + 6. Install perl dependencies - 1. sudo cpan (If this is the first time you've run cpan, you will need to go through the setup process. Just stick with the defaults for everything and you'll be fine.) - 2. install XML::LibXML - 3. install XML::LibXSLT -7. Get the dfhack source - 1. git clone https://github.com/danaris/dfhack.git - 2. cd dfhack - 3. git submodule init - 4. git submodule update -8. mkdir build-osx -9. cd build-osx -10. export CC=/opt/local/bin/gcc-mp-4.5 -11. export CXX=/opt/local/bin/g++-mp-4.5 -12. cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory -13. make -14. make install + + 1. ``sudo cpan`` + + If this is the first time you've run cpan, you will need to go through the setup + process. Just stick with the defaults for everything and you'll be fine. + + 2. ``install XML::LibXML`` + 3. ``install XML::LibXSLT`` + +7. Get the dfhack source:: + + git clone https://github.com/danaris/dfhack.git + cd dfhack + git submodule init + git submodule update + +8. Build dfhack:: + + mkdir build-osx + cd build-osx + export CC=/opt/local/bin/gcc-mp-4.5 + export CXX=/opt/local/bin/g++-mp-4.5 + cmake .. -DCMAKE_BUILD_TYPE:string=Release -DCMAKE_INSTALL_PREFIX=/path/to/DF/directory + make + make install ======= Windows diff --git a/Lua API.html b/Lua API.html index 125687110..9c18b6585 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1110,6 +1110,13 @@ above operations accordingly. If enabled, pauses and zooms to position.

                                              if there are any jobs with first_id <= id < job_next_id, a lua list containing them.

                                              +
                                            • dfhack.job.isSuitableItem(job_item, item_type, item_subtype)

                                              +

                                              Does basic sanity checks to verify if the suggested item type matches +the flags in the job item.

                                              +
                                            • +
                                            • dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)

                                              +

                                              Likewise, if replacing material.

                                              +
                                            • @@ -1250,6 +1257,15 @@ Returns false in case of error.

                                            • dfhack.items.makeProjectile(item)

                                              Turns the item into a projectile, and returns the new object, or nil if impossible.

                                            • +
                                            • dfhack.items.isCasteMaterial(item_type)

                                              +

                                              Returns true if this item type uses a creature/caste pair as its material.

                                              +
                                            • +
                                            • dfhack.items.getSubtypeCount(item_type)

                                              +

                                              Returns the number of raw-defined subtypes of the given item type, or -1 if not applicable.

                                              +
                                            • +
                                            • dfhack.items.getSubtypeDef(item_type, subtype)

                                              +

                                              Returns the raw definition for the given item type and subtype, or nil if invalid.

                                              +
                                            • @@ -1264,7 +1280,7 @@ Returns false in case of error.

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

                                              Returns a map block object for given x,y,z in local block coordinates.

                                            • -
                                            • dfhack.maps.isValidTilePos(coords), or isValidTilePos(x,y,z)``

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

                                              Checks if the given df::coord or x,y,z in local tile coordinates are valid.

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

                                              @@ -1273,6 +1289,12 @@ Returns false in case of error.

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

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

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

                                              +

                                              Returns the tile type at the given coordinates, or nil if invalid.

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

                                              +

                                              Returns designation and occupancy references for the given coordinates, or nil, nil if invalid.

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

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

                                            • @@ -1838,6 +1860,15 @@ coordinates), returns nil.

                                            • xyz2pos(x,y,z)

                                              Returns a table with x, y and z as fields.

                                            • +
                                            • same_xyz(a,b)

                                              +

                                              Checks if a and b have the same x, y and z fields.

                                              +
                                            • +
                                            • get_path_xyz(path,i)

                                              +

                                              Returns path.x[i], path.y[i], path.z[i].

                                              +
                                            • +
                                            • pos2xy(obj), xy2pos(x,y), same_xy(a,b), get_path_xy(a,b)

                                              +

                                              Same as above, but for 2D coordinates.

                                              +
                                            • safe_index(obj,index...)

                                              Walks a sequence of dereferences, which may be represented by numbers or strings. Returns nil if any of obj or indices is nil, or a numeric index is out of array bounds.

                                              diff --git a/Lua API.rst b/Lua API.rst index 4d689ee2b..185672816 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1043,6 +1043,18 @@ Items module Turns the item into a projectile, and returns the new object, or *nil* if impossible. +* ``dfhack.items.isCasteMaterial(item_type)`` + + Returns *true* if this item type uses a creature/caste pair as its material. + +* ``dfhack.items.getSubtypeCount(item_type)`` + + Returns the number of raw-defined subtypes of the given item type, or *-1* if not applicable. + +* ``dfhack.items.getSubtypeDef(item_type, subtype)`` + + Returns the raw definition for the given item type and subtype, or *nil* if invalid. + Maps module ----------- @@ -1059,7 +1071,7 @@ Maps module Returns a map block object for given x,y,z in local block coordinates. -* ``dfhack.maps.isValidTilePos(coords)``, or isValidTilePos(x,y,z)`` +* ``dfhack.maps.isValidTilePos(coords)``, or ``isValidTilePos(x,y,z)`` Checks if the given df::coord or x,y,z in local tile coordinates are valid. @@ -1071,6 +1083,14 @@ Maps module Like ``getTileBlock``, but if the block is not allocated, try creating it. +* ``dfhack.maps.getTileType(coords)``, or ``getTileType(x,y,z)`` + + Returns the tile type at the given coordinates, or *nil* if invalid. + +* ``dfhack.maps.getTileFlags(coords)``, or ``getTileFlags(x,y,z)`` + + Returns designation and occupancy references for the given coordinates, or *nil, nil* if invalid. + * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` Returns the biome info struct for the given global map region. diff --git a/Readme.html b/Readme.html index c1d2d0d3c..6df1afcad 100644 --- a/Readme.html +++ b/Readme.html @@ -2494,20 +2494,21 @@ growcrops plump 40

                                              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 -clear all your fort's units minds.

                                              +

                                              The script can target a single creature, when used with the him argument, +or the whole fort population, with all.

                                              +

                                              To show every bad thought present without actually removing them, run the +script with the -n or --dry-run argument. This can give a quick +hint on what bothers your dwarves the most.

                                              Individual dwarf happiness may not increase right after this command is run, -but in the short term your dwarves will get much more joyful. -The thoughts are set to be very old, and the game will remove them soon when -you unpause.

                                              -

                                              With the optional -v parameter, the script will dump the negative thoughts -it removed.

                                              +but in the short term your dwarves will get much more joyful.

                                              +

                                              Internals: the thoughts are set to be very old, so that the game remove them +quickly after you unpause.

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

                                              +

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

                                              Any non-dead non-caged unit of the specified race gets its blood_count set to 0, which means immediate death at the next game tick. For creatures such as vampires, also set animal.vanish_countdown to 2.

                                              diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 9f380c353..aa6c93c32 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -82,6 +82,7 @@ distribution. #include "df/flow_info.h" #include "df/unit_misc_trait.h" #include "df/proj_itemst.h" +#include "df/itemdef.h" #include #include @@ -937,6 +938,9 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, setOwner), WRAPM(Items, getContainer), WRAPM(Items, getDescription), + WRAPM(Items, isCasteMaterial), + WRAPM(Items, getSubtypeCount), + WRAPM(Items, getSubtypeDef), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToBuilding, items_moveToBuilding), @@ -1018,6 +1022,25 @@ static int maps_ensureTileBlock(lua_State *L) return 1; } +static int maps_getTileType(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + auto ptype = Maps::getTileType(pos); + if (ptype) + lua_pushinteger(L, *ptype); + else + lua_pushnil(L); + return 1; +} + +static int maps_getTileFlags(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Maps::getTileDesignation(pos)); + Lua::PushDFObject(L, Maps::getTileOccupancy(pos)); + return 2; +} + static int maps_getRegionBiome(lua_State *L) { auto pos = CheckCoordXY(L, 1, true); @@ -1035,6 +1058,8 @@ static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "getTileBlock", maps_getTileBlock }, { "ensureTileBlock", maps_ensureTileBlock }, + { "getTileType", maps_getTileType }, + { "getTileFlags", maps_getTileFlags }, { "getRegionBiome", maps_getRegionBiome }, { "getTileBiomeRgn", maps_getTileBiomeRgn }, { NULL, NULL } diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 367ce9b41..92329980a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -123,6 +123,10 @@ struct dfh_item namespace Items { +DFHACK_EXPORT bool isCasteMaterial(df::item_type itype); +DFHACK_EXPORT int getSubtypeCount(df::item_type itype); +DFHACK_EXPORT df::itemdef *getSubtypeDef(df::item_type itype, int subtype); + /// Look for a particular item by ID DFHACK_EXPORT df::item * findItemByID(int32_t id); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 1118afcfe..4e75c8a6b 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -109,30 +109,47 @@ using df::global::proj_next_id; ITEM(PANTS, pants, itemdef_pantsst) \ ITEM(FOOD, food, itemdef_foodst) -bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) +int Items::getSubtypeCount(df::item_type itype) { using namespace df::enums::item_type; - type = type_; - subtype = subtype_; - custom = NULL; - df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; - switch (type_) { - case NONE: - return false; + switch (itype) { +#define ITEM(type,vec,tclass) \ + case type: \ + return defs.vec.size(); +ITEMDEF_VECTORS +#undef ITEM + + default: + return -1; + } +} + +df::itemdef *Items::getSubtypeDef(df::item_type itype, int subtype) +{ + using namespace df::enums::item_type; + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + + switch (itype) { #define ITEM(type,vec,tclass) \ case type: \ - custom = vector_get(defs.vec, subtype); \ - break; + return vector_get(defs.vec, subtype); ITEMDEF_VECTORS #undef ITEM default: - break; + return NULL; } +} + +bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) +{ + type = type_; + subtype = subtype_; + custom = Items::getSubtypeDef(type_, subtype_); return isValid(); } @@ -171,6 +188,10 @@ ITEMDEF_VECTORS break; } + const char *name = ENUM_ATTR(item_type, caption, type); + if (name) + return name; + return toLower(ENUM_KEY_STR(item_type, type)); } @@ -219,6 +240,11 @@ ITEMDEF_VECTORS return (subtype >= 0); } +bool Items::isCasteMaterial(df::item_type itype) +{ + return ENUM_ATTR(item_type, is_caste_mat, itype); +} + bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) { using namespace df::enums::item_type; @@ -226,6 +252,9 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) if (!isValid()) return mat ? mat->matches(item) : false; + if (Items::isCasteMaterial(type) && mat && !mat->isNone()) + return false; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; diff --git a/library/xml b/library/xml index aec106cdc..0ed2d1dc5 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit aec106cdc32083bbcc6d6dd27d9e069f5525ea92 +Subproject commit 0ed2d1dc5547737fbb838568fc66671098f2c11a From 5388ad475f85b162f4570bf1fbf410b620e5b553 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 20:14:50 +0400 Subject: [PATCH 084/472] Fix a bug in lua wrapper caused by the recent pairs() addition. It inadvertently removed all functions like df.new from the df table. --- library/LuaWrapper.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 75e3b697c..1ce405c29 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -1641,10 +1641,13 @@ static int DoAttach(lua_State *state) freeze_table(state, true, "df"); - lua_swap(state); + // pairstable dftable dfmeta + + lua_pushvalue(state, -3); lua_pushcclosure(state, wtype_pairs, 1); lua_setfield(state, -2, "__pairs"); lua_pop(state, 1); + lua_remove(state, -2); } return 1; From 795961bfc4c1148a13483d739bda8fdf6923fb41 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 20:31:45 +0400 Subject: [PATCH 085/472] Fix the treatment of non_economic in Materials/Items modules. non_economic == !(is boulder && is inorganic && is economic) --- library/modules/Items.cpp | 14 ++++++++++---- library/modules/Materials.cpp | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 4e75c8a6b..7af8c549b 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -256,11 +256,11 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) return false; df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; - df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; + df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; - ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = 0; + ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = xmask2.whole = 0; ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; if (mat) { @@ -281,12 +281,16 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) RQ(1,not_bin); RQ(1,lye_bearing); RQ(2,dye); RQ(2,dyeable); RQ(2,dyed); RQ(2,glass_making); RQ(2,screw); - RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,non_economic); + RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,totemable); RQ(2,plaster_containing); RQ(2,body_part); RQ(2,lye_milk_free); RQ(2,blunt); RQ(2,unengraved); RQ(2,hair_wool); RQ(3,any_raw_material); RQ(3,non_pressed); RQ(3,food_storage); + // only checked if boulder + + xmask2.bits.non_economic = true; + // Compute the ok mask OK(1,solid); @@ -306,7 +310,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) case BOULDER: OK(1,sharpenable); - OK(2,non_economic); + xmask2.bits.non_economic = false; case BAR: OK(3,any_raw_material); case BLOCKS: @@ -432,6 +436,8 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) #undef OK #undef RQ + mask2.whole &= ~xmask2.whole; + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && bits_match(item.flags2.whole, ok2.whole, mask2.whole) && bits_match(item.flags3.whole, ok3.whole, mask3.whole) && diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index fbc441493..a94d49181 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -497,7 +497,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags2 &ok, df::job_item_flags2 &ma TEST(fire_safe, material->heat.melting_point > 11000); TEST(magma_safe, material->heat.melting_point > 12000); TEST(deep_material, FLAG(inorganic, inorganic_flags::SPECIAL)); - TEST(non_economic, inorganic && !(ui && ui->economic_stone[index])); + TEST(non_economic, !inorganic || !(ui && vector_get(ui->economic_stone, index))); TEST(plant, plant); TEST(silk, MAT_FLAG(SILK)); From 0c9f1e0af498678656e759725c25ee44ec17a31d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:01:22 +0400 Subject: [PATCH 086/472] Check the item type against job_item_vector_id when matching to jobs. This will prevent setting an invalid item type via the job command. --- library/include/modules/Items.h | 4 +++- library/modules/Items.cpp | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 92329980a..f9871669a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -40,6 +40,7 @@ distribution. #include "df/building_actual.h" #include "df/body_part_raw.h" #include "df/unit_inventory_item.h" +#include "df/job_item_vector_id.h" namespace df { @@ -86,7 +87,8 @@ namespace DFHack bool find(const std::string &token); - bool matches(const df::job_item &item, MaterialInfo *mat = NULL); + bool matches(df::job_item_vector_id vec_id); + bool matches(const df::job_item &item, MaterialInfo *mat = NULL, bool skip_vector = false); }; inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 7af8c549b..0a78c727f 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -245,7 +245,28 @@ bool Items::isCasteMaterial(df::item_type itype) return ENUM_ATTR(item_type, is_caste_mat, itype); } -bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) +bool ItemTypeInfo::matches(df::job_item_vector_id vec_id) +{ + auto other_id = ENUM_ATTR(job_item_vector_id, other, vec_id); + + auto explicit_item = ENUM_ATTR(items_other_id, item, other_id); + if (explicit_item != item_type::NONE && type != explicit_item) + return false; + + auto generic_item = ENUM_ATTR(items_other_id, generic_item, other_id); + if (generic_item.size > 0) + { + for (size_t i = 0; i < generic_item.size; i++) + if (generic_item.items[i] == type) + return true; + + return false; + } + + return true; +} + +bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool skip_vector) { using namespace df::enums::item_type; @@ -255,6 +276,9 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) if (Items::isCasteMaterial(type) && mat && !mat->isNone()) return false; + if (!skip_vector && !matches(item.vector_id)) + return false; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; From fbba4caab25b703a22157053f45ac627e64121b8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:16:00 +0400 Subject: [PATCH 087/472] Suppress checking the cookable material flag if the item is a container. --- library/modules/Items.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 0a78c727f..cf99d9426 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -279,11 +279,11 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski if (!skip_vector && !matches(item.vector_id)) return false; - df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1, xmask1; df::job_item_flags2 ok2, mask2, item_ok2, item_mask2, xmask2; df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; - ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; + ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = xmask1.whole = 0; ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = xmask2.whole = 0; ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; @@ -362,11 +362,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski case CAGE: OK(1,milk); OK(1,milkable); + xmask1.bits.cookable = true; break; case BUCKET: case FLASK: OK(1,milk); + xmask1.bits.cookable = true; break; case TOOL: @@ -374,6 +376,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski OK(1,milk); OK(2,lye_milk_free); OK(2,blunt); + xmask1.bits.cookable = true; if (VIRTUAL_CAST_VAR(def, df::itemdef_toolst, custom)) { df::tool_uses key(tool_uses::FOOD_STORAGE); @@ -389,11 +392,13 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski OK(1,milk); OK(2,lye_milk_free); OK(3,food_storage); + xmask1.bits.cookable = true; break; case BOX: OK(1,bag); OK(1,sand_bearing); OK(1,milk); OK(2,dye); OK(2,plaster_containing); + xmask1.bits.cookable = true; break; case BIN: @@ -460,6 +465,7 @@ bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat, bool ski #undef OK #undef RQ + mask1.whole &= ~xmask1.whole; mask2.whole &= ~xmask2.whole; return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && From dee0c97584607d39d0a263a0fdd9024786f0c6e3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 20 Oct 2012 21:57:36 +0400 Subject: [PATCH 088/472] Add a gui script for viewing and changing job_item properties. --- NEWS | 1 + dfhack.init-example | 3 + library/lua/gui/materials.lua | 45 +++---- library/lua/gui/widgets.lua | 16 ++- library/xml | 2 +- scripts/gui/workshop-job.lua | 237 ++++++++++++++++++++++++++++++++++ 6 files changed, 271 insertions(+), 33 deletions(-) create mode 100644 scripts/gui/workshop-job.lua diff --git a/NEWS b/NEWS index 61e8a5b3e..5567258bc 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. + - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. DFHack v0.34.11-r2 diff --git a/dfhack.init-example b/dfhack.init-example index 3ecbf4342..239d68a0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -75,6 +75,9 @@ keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons # minecart Guide path keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path +# workshop job details +# keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job + ############################ # UI and game logic tweaks # ############################ diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index baf641976..871c60014 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -238,8 +238,7 @@ function MaterialDialog:submitMaterial(typ, index) self:dismiss() if self.on_select then - local info = dfhack.matinfo.decode(typ, index) - self.on_select(info, typ, index) + self.on_select(typ, index) end end @@ -276,25 +275,6 @@ function showMaterialPrompt(title, prompt, on_select, on_cancel, mat_filter) }:show() end -local itemdefs = df.global.world.raws.itemdefs -local itemtype_info = { - TRAPPARTS = { name = 'mechanisms' }, - WEAPON = { defs = itemdefs.weapons }, - TRAPCOMP = { defs = itemdefs.trapcomps }, - TOY = { defs = itemdefs.toys }, - TOOL = { defs = itemdefs.tools }, - INSTRUMENT = { defs = itemdefs.instruments }, - ARMOR = { defs = itemdefs.armor }, - AMMO = { defs = itemdefs.ammo }, - SIEGEAMMO = { defs = itemdefs.siege_ammo }, - GLOVES = { defs = itemdefs.gloves }, - SHOES = { defs = itemdefs.shoes }, - SHIELD = { defs = itemdefs.shields }, - HELM = { defs = itemdefs.helms }, - PANTS = { defs = itemdefs.pants }, - FOOD = { defs = itemdefs.food }, -} - function ItemTypeDialog(args) args.text = args.prompt or 'Type or select an item type' args.text_pen = COLOR_WHITE @@ -307,26 +287,24 @@ function ItemTypeDialog(args) local filter = args.item_filter for itype = 0,df.item_type._last_item do - local key = df.item_type[itype] - local info = itemtype_info[key] + local attrs = df.item_type.attrs[itype] + local defcnt = dfhack.items.getSubtypeCount(itype) if not filter or filter(itype,-1) then - local name = key + local name = attrs.caption or df.item_type[itype] local icon - if info and info.defs then + if defcnt >= 0 then name = 'any '..name icon = '+' end - if info and info.name then - name = info.name - end table.insert(choices, { icon = icon, text = string.lower(name), item_type = itype, item_subtype = -1 }) end - if info and info.defs then - for subtype,def in ipairs(info.defs) do + if defcnt > 0 then + for subtype = 0,defcnt-1 do + local def = dfhack.items.getSubtypeDef(itype, subtype) if not filter or filter(itype,subtype,def) then table.insert(choices, { icon = '\x1e', text = ' '..def.name, item_type = itype, item_subtype = subtype @@ -338,6 +316,13 @@ function ItemTypeDialog(args) args.choices = choices + if args.on_select then + local cb = args.on_select + args.on_select = function(idx, obj) + return cb(obj.item_type, obj.item_subtype) + end + end + return dlg.ListBox(args) end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 05100c00b..e6a9a4d72 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -211,6 +211,11 @@ function parse_label_text(obj) obj.text_ids = idtab end +local function is_disabled(token) + return (token.disabled ~= nil and getval(token.disabled)) or + (token.enabled ~= nil and not getval(token.enabled)) +end + function render_text(obj,dc,x0,y0,pen,dpen) local width = 0 for iline,line in ipairs(obj.text_lines) do @@ -241,7 +246,7 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keypen if dc then - if getval(token.disabled) then + if is_disabled(token) then dc:pen(getval(token.dpen) or dpen) keypen = COLOR_GREEN else @@ -287,7 +292,7 @@ end function check_text_keys(self, keys) if self.text_active then for _,item in ipairs(self.text_active) do - if item.key and keys[item.key] and not getval(item.disabled) then + if item.key and keys[item.key] and not is_disabled(item) then item.on_activate() return true end @@ -361,6 +366,13 @@ STANDARDSCROLL = { STANDARDSCROLL_PAGEDOWN = '+page', } +SECONDSCROLL = { + SECONDSCROLL_UP = -1, + SECONDSCROLL_DOWN = 1, + SECONDSCROLL_PAGEUP = '-page', + SECONDSCROLL_PAGEDOWN = '+page', +} + List.ATTRS{ text_pen = COLOR_CYAN, cursor_pen = COLOR_LIGHTCYAN, diff --git a/library/xml b/library/xml index 0ed2d1dc5..b9b2e8c6d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 0ed2d1dc5547737fbb838568fc66671098f2c11a +Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua new file mode 100644 index 000000000..973d5cc17 --- /dev/null +++ b/scripts/gui/workshop-job.lua @@ -0,0 +1,237 @@ +-- Show and modify properties of jobs in a workshop. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local guimat = require 'gui.materials' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +JobDetails = defclass(JobDetails, guidm.MenuOverlay) + +JobDetails.focus_path = 'workshop-job' + +JobDetails.ATTRS { + job = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function JobDetails:init(args) + local status = { text = 'No worker', pen = COLOR_DARKGREY } + local worker = dfhack.job.getWorker(self.job) + if self.job.flags.suspend then + status = { text = 'Suspended', pen = COLOR_RED } + elseif worker then + status = { text = dfhack.TranslateName(dfhack.unit.getVisibleName(worker)), pen = COLOR_GREEN } + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + { text = df.job_type.attrs[self.job.job_type].caption }, NEWLINE, NEWLINE, + ' ', status + } + }, + widgets.Label{ + frame = { l = 0, t = 4 }, + text = { + { key = 'CUSTOM_I', text = ': Input item, ', + enabled = self:callback('canChangeIType'), + on_activate = self:callback('onChangeIType') }, + { key = 'CUSTOM_M', text = ': Material', + enabled = self:callback('canChangeMat'), + on_activate = self:callback('onChangeMat') } + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 6, b = 2 }, + row_height = 4, + scroll_keys = widgets.SECONDSCROLL, + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } + + self:initListChoices() +end + +function describe_item_type(iobj) + local itemline = 'any item' + if iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +function is_caste_mat(iobj) + if iobj.item_type >= 0 then + return df.item_type.attrs[iobj.item_type].is_caste_mat + end + return false +end + +function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'material not applicable' + elseif iobj.mat_type >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +function list_flags(list, bitfield) + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end +end + +function JobDetails:initListChoices() + local items = {} + for i,ref in ipairs(self.job.items) do + local idx = ref.job_item_idx + if idx >= 0 then + items[idx] = (items[idx] or 0) + 1 + end + end + + local choices = {} + for i,iobj in ipairs(self.job.job_items) do + local head = 'Item '..(i+1)..': '..(items[idx] or 0)..' of '..iobj.quantity + if iobj.min_dimension > 0 then + head = head .. '(size '..iobj.min_dimension..')' + end + + local line1 = {} + local reaction = df.reaction.find(iobj.reaction_id) + if reaction and #iobj.contains > 0 then + for _,ri in ipairs(iobj.contains) do + table.insert(line1, 'has '..utils.call_with_string( + reaction.reagents[ri],'getDescription',iobj.reaction_id + )) + end + end + if iobj.metal_ore >= 0 then + local ore = dfhack.matinfo.decode(0, iobj.metal_ore) + if ore then + table.insert(line1, 'ore of '..ore:toString()) + end + end + if iobj.has_material_reaction_product ~= '' then + table.insert(line1, 'product '..iobj.has_material_reaction_product) + end + if iobj.reaction_class ~= '' then + table.insert(line1, 'class '..iobj.reaction_class) + end + if iobj.has_tool_use >= 0 then + table.insert(line1, 'has use '..df.tool_uses[iobj.has_tool_use]) + end + list_flags(line1, iobj.flags1) + list_flags(line1, iobj.flags2) + list_flags(line1, iobj.flags3) + if #line1 == 0 then + table.insert(line1, 'no flags') + end + + table.insert(choices, { + index = i, + iobj = iobj, + text = { + head, NEWLINE, + ' ', { text = curry(describe_item_type, iobj) }, NEWLINE, + ' ', { text = curry(describe_material, iobj) }, NEWLINE, + ' ', table.concat(line1, ', '), NEWLINE + } + }) + end + + self.subviews.list:setChoices(choices) +end + +function JobDetails:canChangeIType() + local idx, obj = self.subviews.list:getSelected() + return obj ~= nil +end + +function JobDetails:onChangeIType() + local idx, obj = self.subviews.list:getSelected() + guimat.ItemTypeDialog{ + prompt = 'Please select a new item type for input '..idx, + none_caption = 'any item', + item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), + on_select = function(item_type, item_subtype) + obj.iobj.item_type = item_type + obj.iobj.item_subtype = item_subtype + end + }:show() +end + +function JobDetails:canChangeMat() + local idx, obj = self.subviews.list:getSelected() + return obj ~= nil and not is_caste_mat(obj.iobj) +end + +function JobDetails:onChangeMat() + local idx, obj = self.subviews.list:getSelected() + + if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then + dlg.showMessage( + 'Bug Alert', + { 'Please set a specific item type first.\n\n', + 'Otherwise the material will be matched\n', + 'incorrectly due to a limitation in DF code.' }, + COLOR_YELLOW + ) + return + end + + guimat.MaterialDialog{ + prompt = 'Please select a new material for input '..idx, + none_caption = 'any material', + mat_filter = function(mat,parent,mat_type,mat_index) + return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) + end, + on_select = function(mat_type, mat_index) + if idx == 1 + and self.job.mat_type == obj.iobj.mat_type + and self.job.mat_index == obj.iobj.mat_index + and self.job.job_type ~= df.job_type.PrepareMeal + then + self.job.mat_type = mat_type + self.job.mat_index = mat_index + end + + obj.iobj.mat_type = mat_type + obj.iobj.mat_index = mat_index + end + }:show() +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") +end + +local dlg = JobDetails{ job = dfhack.gui.getSelectedJob() } +dlg:show() From 1f7a01d6854ff528f7ffadb4fa31c2495ab83c9c Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 20 Oct 2012 22:35:39 +0200 Subject: [PATCH 089/472] follow rename unit.military.squad_index to squad_id in df-structures --- library/RemoteTools.cpp | 4 ++-- plugins/lua/sort/units.lua | 6 +++--- plugins/ruby/unit.rb | 2 +- plugins/tweak.cpp | 2 +- scripts/siren.lua | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index b371d60fa..718d1b181 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -292,9 +292,9 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, if (!unit->custom_profession.empty()) info->set_custom_profession(unit->custom_profession); - if (unit->military.squad_index >= 0) + if (unit->military.squad_id >= 0) { - info->set_squad_id(unit->military.squad_index); + info->set_squad_id(unit->military.squad_id); info->set_squad_position(unit->military.squad_position); } } diff --git a/plugins/lua/sort/units.lua b/plugins/lua/sort/units.lua index d8ae83a38..c124e52d9 100644 --- a/plugins/lua/sort/units.lua +++ b/plugins/lua/sort/units.lua @@ -93,7 +93,7 @@ orders.race = { orders.squad = { key = function(unit) - local sidx = unit.military.squad_index + local sidx = unit.military.squad_id if sidx >= 0 then return sidx end @@ -102,7 +102,7 @@ orders.squad = { orders.squad_position = { key = function(unit) - local sidx = unit.military.squad_index + local sidx = unit.military.squad_id if sidx >= 0 then return sidx * 1000 + unit.military.squad_position end @@ -115,4 +115,4 @@ orders.happiness = { end } -return _ENV \ No newline at end of file +return _ENV diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 139a375b2..4fbf75d8d 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -92,7 +92,7 @@ module DFHack # filter 'attend meeting' not u.specific_refs.find { |s| s.type == :ACTIVITY } and # filter soldiers (TODO check schedule) - u.military.squad_index == -1 and + u.military.squad_id == -1 and # filter 'on break' not u.status.misc_traits.find { |t| t.id == :OnBreak } end diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 8488ba3e8..b4af2a4ba 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -640,7 +640,7 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { for (int y = y1, i = i1; i <= i2; i++, y++) { auto unit = vector_get(positions.candidates, i); - if (!unit || unit->military.squad_index < 0) + if (!unit || unit->military.squad_id < 0) continue; for (int x = x1; x <= x2; x++) diff --git a/scripts/siren.lua b/scripts/siren.lua index 30d3aa07f..4fc574ed0 100644 --- a/scripts/siren.lua +++ b/scripts/siren.lua @@ -89,7 +89,7 @@ end for _,v in ipairs(df.global.world.units.active) do local x,y,z = dfhack.units.getPosition(v) if x and dfhack.units.isCitizen(v) and is_in_burrows(xyz2pos(x,y,z)) then - if not in_siege and v.military.squad_index < 0 then + if not in_siege and v.military.squad_id < 0 then add_thought(v, df.unit_thought_type.LackProtection) end wake_unit(v) From 86ec66c0fb69face59182192766b2280dfea5238 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 21 Oct 2012 13:42:55 +0300 Subject: [PATCH 090/472] More work on dfusion. Embark anywhere script separated. --- plugins/Dfusion/dfusion.cpp | 20 ++++ plugins/Dfusion/luafiles/embark/init.lua | 137 ++++++++--------------- plugins/devel/CMakeLists.txt | 1 - plugins/lua/dfusion.lua | 113 ++++++++++++------- scripts/dfusion.lua | 2 + scripts/embark.lua | 53 +++++++++ 6 files changed, 196 insertions(+), 130 deletions(-) create mode 100644 scripts/dfusion.lua create mode 100644 scripts/embark.lua diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index daacc0b26..4507f9a15 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -58,8 +58,28 @@ static int loadObjectFile(lua_State* L) lua_setfield(L,table_pos,"symbols"); return 1; } +static int markAsExecutable(lua_State* L) +{ + unsigned addr=luaL_checkunsigned(L,1); + std::vector ranges; + DFHack::Core::getInstance().p->getMemRanges(ranges); + for(size_t i=0;isetPermisions(ranges[i],newperm); + return 0; + } + } + lua_pushlstring(L,"Memory range not found",23); + lua_error(L); + return 0; +} DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(loadObjectFile), + DFHACK_LUA_COMMAND(markAsExecutable), DFHACK_LUA_END }; DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) diff --git a/plugins/Dfusion/luafiles/embark/init.lua b/plugins/Dfusion/luafiles/embark/init.lua index aa8f2822d..76be00c72 100644 --- a/plugins/Dfusion/luafiles/embark/init.lua +++ b/plugins/Dfusion/luafiles/embark/init.lua @@ -1,108 +1,69 @@ -local dfu=require("dfusion") +local dfu=require("plugins.dfusion") local ms=require("memscan") - +local MAX_RACES=100 CustomEmbark=defclass(CustomEmbark,dfu.BinaryPlugin) CustomEmbark.ATTRS{filename="dfusion/embark/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} function CustomEmbark:install() local stoff=dfhack.internal.getAddress('start_dwarf_count') + if #self.race_caste_data<7 then + error("caste and race count must be bigger than 6") + end + if #self.race_caste_data>MAX_RACES then + error("caste and race count must be less then "..MAX_RACES) + end if stoff==nil then error("address for start_dwarf_count not found!") end local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) - local needle={0x0f,0xb7,0x0d} --movzx,... - add_dword(needle,race_id_offset) -- ...word ptr[] + print(string.format("start=%08x",stoff)) + local needle={0x0f,0xb7,0x0d} --movzx eax,dword ptr [race_id] + local tmp_table=dfu.dwordToTable(race_id_offset) + for k,v in ipairs(tmp_table) do + table.insert(needle,v) + end + local mem=ms.get_code_segment() - local trg_offset=mem.uint8_t.find(needle,stoff)--maybe endoff=stoff+bignumber + print(mem.uint8_t:addr2idx(stoff)) + print(mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff))) + local _,trg_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff),nil)--maybe endoff=stoff+bignumber if trg_offset==nil then error("address for race_load not found") end + local call_data={0x90,0x90} + local _,data_offset=df.sizeof(self.data) + dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) + self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} needle={0x83,0xc8,0xff} -- or eax, 0xFF - local caste_offset=mem.uint8_t.find(needle,trg_offset) + local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) if caste_offset==nil or caste_offset-stoff>1000 then error("Caste change code not found or found too far!") end + self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} + self.disable_castes:apply() + self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} + self.dwarfcount:apply() + local caste_array=self:allocate("caste_array","uint16_t",#self.race_caste_data) + local race_array=self:allocate("race_array","uint16_t",#self.race_caste_data) + for k,v in ipairs(self.race_caste_data) do + caste_array[k-1]=v[2] + race_array[k-1]=v[1] + end + local race_array_off,caste_array_off + local _ + _,race_array_off=df.sizeof(race_array) + _,caste_array_off=df.sizeof(caste_array) + self:set_marker_dword("race",caste_array_off) --hehe... mixed them up i guess... + self:set_marker_dword("caste",race_array_off) + + self:move_to_df() + self.call_patch:apply() + self.installed=true end -function MakeTable(modpos,modsize,names) - count=0 - castes={} - --print("Making table") - for _,line in pairs(names) do - --print("Line:"..line) - tpos=string.find(line,":") - if tpos~=nil then - --print("Was line:"..line) - table.insert(castes,tonumber(string.sub(line,tpos+1))) - line=string.sub(line,1,tpos-1) - --print("IS line:"..line) - else - table.insert(castes,-1) - end - if RaceTable[line] == nil then - error("Failure, "..line.." not found!") - end - --print("adding:"..line.." id:"..RaceTable[line]) - engine.pokew(modpos+modsize+count*2,RaceTable[line]) -- add race - count = count + 1 - end - i=0 - for _,caste in pairs(castes) do - - engine.pokew(modpos+modsize+count*2+i*2,caste) -- add caste - i= i+1 - end - - engine.poked(stoff,count) - return count -end -function embark(names) - RaceTable=RaceTable or BuildNameTable() - mypos=engine.getmod('Embark') - stoff=VersionInfo.getAddress('start_dwarf_count') - if mypos then --if mod already loaded - print("Mod already loaded @:"..mypos.." just updating") - modpos=mypos - _,modsize=engine.loadobj('dfusion/embark/embark.o') - - count=MakeTable(modpos,modsize,names) --just remake tables - else - - _,tofind=df.sizeof(df.global.ui:_field("race_id")) - - loc=offsets.find(stoff,0x0f,0xb7,0x0d,DWORD_,tofind) --MOVZX ECX,WORD PTR[] - - print(string.format("found:%x",loc)) - if((loc~=0)and(loc-stoff<1000)) then - loc2=offsets.find(loc,0x83,0xc8,0xff) -- or eax, ffffff (for caste) - if loc2== 0 then - error ("Location for caste nulling not found!") - end - engine.pokeb(loc2,0x90) - engine.pokeb(loc2+1,0x90) - engine.pokeb(loc2+2,0x90) - ModData=engine.installMod("dfusion/embark/embark.o","Embark",256) - modpos=ModData.pos - modsize=ModData.size - local castepos=modpos+engine.FindMarker(ModData,"caste") - local racepos=modpos+engine.FindMarker(ModData,"race") - count=MakeTable(modpos,modsize,names) - engine.poked(castepos,modpos+modsize) --fix array start for race - engine.poked(racepos,modpos+modsize+count*2) --fix array start for caste - print("sucess loading mod @:"..modpos) - -- build race vector after module. - - - --finaly poke in the call! - engine.pokeb(loc,0x90) - engine.pokeb(loc+1,0x90) - engine.pokeb(loc+2,0xe8) - engine.poked(loc+3,modpos-loc-7) - --engine.pokeb(loc+5,0x90) - - SetExecute(modpos) - else - error("did not find patch location, failing...") - end - - end +function CustomEmbark:uninstall() + if self.installed then + self.call_patch:remove() + self.disable_castes:remove() + self.dwarfcount:remove() + end end \ No newline at end of file diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index f126ae53b..134d5cb67 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,7 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) -DFHACK_PLUGIN(steam-engine steam-engine.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua index 06128d29b..46f28f78e 100644 --- a/plugins/lua/dfusion.lua +++ b/plugins/lua/dfusion.lua @@ -4,8 +4,23 @@ local _ENV = mkmodule('plugins.dfusion') local ms=require("memscan") local marker={0xDE,0xAD,0xBE,0xEF} -patches={} +--utility functions +function dwordToTable(dword) + local b={bit32.extract(dword,0,8),bit32.extract(dword,8,8),bit32.extract(dword,16,8),bit32.extract(dword,24,8)} + return b +end +function concatTables(t1,t2) + for k,v in pairs(t2) do + table.insert(t1,v) + end +end +function makeCall(from,to) + local ret={0xe8} + concatTables(ret,dwordToTable(to-from-5)) + return ret +end -- A reversable binary patch +patches={} BinaryPatch=defclass(BinaryPatch) BinaryPatch.ATTRS {pre_data=DEFAULT_NIL,data=DEFAULT_NIL,address=DEFAULT_NIL,name=DEFAULT_NIL} function BinaryPatch:init(args) @@ -16,19 +31,8 @@ function BinaryPatch:init(args) if patches[self.name]~=nil then error("Patch already exist") end - self.max_val=0 - for k,v in pairs(args.pre_data) do - if type(k)~="number" then - error("non number key in pre_data") - end - if self.max_val Date: Sun, 21 Oct 2012 13:46:12 +0300 Subject: [PATCH 091/472] Small error fix --- plugins/lua/dfusion.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua index 46f28f78e..ac9a20868 100644 --- a/plugins/lua/dfusion.lua +++ b/plugins/lua/dfusion.lua @@ -52,7 +52,7 @@ function BinaryPatch:test() end function BinaryPatch:apply() if not self:test() then - error(string.format("pre-data for binary patch does not match expected") + error(string.format("pre-data for binary patch does not match expected")) end local post_buf=df.new('uint8_t',#self.pre_data) From 46938625fdedbb23bf7b7ebd57bc77c21758dcf2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 21 Oct 2012 21:45:51 +0400 Subject: [PATCH 092/472] Pass the hotkey keys to dwarfmode from overlays, and fix gui/workshop-job. --- dfhack.init-example | 2 +- library/lua/gui/dwarfmode.lua | 48 +++++++++++++++++++----- scripts/gui/workshop-job.lua | 69 ++++++++++++++++++++++++----------- 3 files changed, 87 insertions(+), 32 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index 239d68a0f..27801b73d 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -76,7 +76,7 @@ keybinding add Ctrl-W@layer_military/Equip/Customize/View gui/choose-weapons keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details -# keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job +keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job ############################ # UI and game logic tweaks # diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 0c1b0599c..cef52ae24 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -238,6 +238,19 @@ local function get_movement_delta(key, delta, big_step) end end +HOTKEY_KEYS = {} + +for i,v in ipairs(df.global.ui.main.hotkeys) do + HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v +end + +local function get_hotkey_target(key) + local hk = HOTKEY_KEYS[key] + if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then + return xyz2pos(hk.x, hk.y, hk.z) + end +end + function Viewport:scrollByKey(key) local dx, dy, dz = get_movement_delta(key, 10, 20) if dx then @@ -247,7 +260,12 @@ function Viewport:scrollByKey(key) self.z + dz ) else - return self + local hk_pos = get_hotkey_target(key) + if hk_pos then + return self:centerOn(hk_pos) + else + return self + end end end @@ -286,9 +304,11 @@ function DwarfOverlay:selectBuilding(building,cursor,viewport,gap) end function DwarfOverlay:propagateMoveKeys(keys) - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then - self:sendInputToParent(code) + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then + if not HOTKEY_KEYS[code] or get_hotkey_target(code) then + self:sendInputToParent(code) + end return code end end @@ -305,8 +325,8 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) return 'A_MOVE_SAME_SQUARE' end - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] or HOTKEY_KEYS[code] then local vp = self:getViewport():scrollByKey(code) if (cursor and not no_clip_cursor) or no_clip_cursor == false then vp = vp:reveal(anchor,4,20,4,true) @@ -329,16 +349,26 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor) return 'A_MOVE_SAME_SQUARE' end - for code,_ in pairs(MOVEMENT_KEYS) do - if keys[code] then + for code,_ in pairs(keys) do + if MOVEMENT_KEYS[code] then local dx, dy, dz = get_movement_delta(code, 1, 10) local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) if dfhack.maps.isValidTilePos(ncur) then setCursorPos(ncur) self:getViewport():reveal(ncur,4,10,6,true):set() - return code end + + return code + elseif HOTKEY_KEYS[code] then + local pos = get_hotkey_target(code) + + if pos and dfhack.maps.isValidTilePos(pos) then + setCursorPos(pos) + self:getViewport():centerOn(pos):set() + end + + return code end end end diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua index 973d5cc17..74e33595f 100644 --- a/scripts/gui/workshop-job.lua +++ b/scripts/gui/workshop-job.lua @@ -18,12 +18,14 @@ JobDetails.ATTRS { } function JobDetails:init(args) + self.building = dfhack.job.getHolder(self.job) + local status = { text = 'No worker', pen = COLOR_DARKGREY } local worker = dfhack.job.getWorker(self.job) if self.job.flags.suspend then status = { text = 'Suspended', pen = COLOR_RED } elseif worker then - status = { text = dfhack.TranslateName(dfhack.unit.getVisibleName(worker)), pen = COLOR_GREEN } + status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } end self:addviews{ @@ -63,6 +65,14 @@ function JobDetails:init(args) self:initListChoices() end +function JobDetails:onGetSelectedBuilding() + return self.building +end + +function JobDetails:onGetSelectedJob() + return self.job +end + function describe_item_type(iobj) local itemline = 'any item' if iobj.item_type >= 0 then @@ -79,10 +89,7 @@ function describe_item_type(iobj) end function is_caste_mat(iobj) - if iobj.item_type >= 0 then - return df.item_type.attrs[iobj.item_type].is_caste_mat - end - return false + return dfhack.items.isCasteMaterial(iobj.item_type) end function describe_material(iobj) @@ -119,7 +126,7 @@ function JobDetails:initListChoices() local choices = {} for i,iobj in ipairs(self.job.job_items) do - local head = 'Item '..(i+1)..': '..(items[idx] or 0)..' of '..iobj.quantity + local head = 'Item '..(i+1)..': '..(items[i] or 0)..' of '..iobj.quantity if iobj.min_dimension > 0 then head = head .. '(size '..iobj.min_dimension..')' end @@ -175,16 +182,22 @@ function JobDetails:canChangeIType() return obj ~= nil end +function JobDetails:setItemType(obj, item_type, item_subtype) + obj.iobj.item_type = item_type + obj.iobj.item_subtype = item_subtype + + if is_caste_mat(obj.iobj) then + self:setMaterial(obj, -1, -1) + end +end + function JobDetails:onChangeIType() local idx, obj = self.subviews.list:getSelected() guimat.ItemTypeDialog{ prompt = 'Please select a new item type for input '..idx, none_caption = 'any item', item_filter = curry(dfhack.job.isSuitableItem, obj.iobj), - on_select = function(item_type, item_subtype) - obj.iobj.item_type = item_type - obj.iobj.item_subtype = item_subtype - end + on_select = self:callback('setItemType', obj) }:show() end @@ -193,6 +206,20 @@ function JobDetails:canChangeMat() return obj ~= nil and not is_caste_mat(obj.iobj) end +function JobDetails:setMaterial(obj, mat_type, mat_index) + if obj.index == 0 + and self.job.mat_type == obj.iobj.mat_type + and self.job.mat_index == obj.iobj.mat_index + and self.job.job_type ~= df.job_type.PrepareMeal + then + self.job.mat_type = mat_type + self.job.mat_index = mat_index + end + + obj.iobj.mat_type = mat_type + obj.iobj.mat_index = mat_index +end + function JobDetails:onChangeMat() local idx, obj = self.subviews.list:getSelected() @@ -213,20 +240,18 @@ function JobDetails:onChangeMat() mat_filter = function(mat,parent,mat_type,mat_index) return dfhack.job.isSuitableMaterial(obj.iobj, mat_type, mat_index) end, - on_select = function(mat_type, mat_index) - if idx == 1 - and self.job.mat_type == obj.iobj.mat_type - and self.job.mat_index == obj.iobj.mat_index - and self.job.job_type ~= df.job_type.PrepareMeal - then - self.job.mat_type = mat_type - self.job.mat_index = mat_index - end + on_select = self:callback('setMaterial', obj) + }:show() +end - obj.iobj.mat_type = mat_type - obj.iobj.mat_index = mat_index +function JobDetails:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() end - }:show() + else + JobDetails.super.onInput(self, keys) + end end if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then From dfa3a520fd2e7243413d5dc38d253fd17e072c1e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 21 Oct 2012 16:34:13 -0500 Subject: [PATCH 093/472] sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b9b2e8c6d..e06438924 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db +Subproject commit e06438924929a8ecab751c0c233dad5767e91f7e From cfbdf47f6e75048193f906ad41218a436ce1b892 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 22 Oct 2012 19:37:12 +0200 Subject: [PATCH 094/472] follow rename Carried->Hauled in df-structures unit inventory mode --- library/include/modules/Items.h | 2 +- plugins/advtools.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index f9871669a..217a0f28a 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -161,7 +161,7 @@ DFHACK_EXPORT bool moveToGround(MapExtras::MapCache &mc, df::item *item, df::coo DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df::item *container); DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, - df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); + df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Hauled, int body_part = -1); /// Makes the item removed and marked for garbage collection DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false); diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index d6224f5c2..59b88087b 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -321,7 +321,7 @@ std::string getUnitNameProfession(df::unit *unit) } enum InventoryMode { - INV_CARRIED, + INV_HAULED, INV_WEAPON, INV_WORN, INV_IN_CONTAINER @@ -355,8 +355,8 @@ void listUnitInventory(std::vector *list, df::unit *unit) InventoryMode mode; switch (item->mode) { - case df::unit_inventory_item::Carried: - mode = INV_CARRIED; + case df::unit_inventory_item::Hauled: + mode = INV_HAULED; break; case df::unit_inventory_item::Weapon: mode = INV_WEAPON; From 27c7dfde4f715e42c9bec87f88c07c0d696f6c43 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 23 Oct 2012 12:14:21 -0500 Subject: [PATCH 095/472] ANY_FREE -> IN_PLAY, to match terminology used in DF's error messages --- library/modules/Buildings.cpp | 2 +- plugins/autolabor.cpp | 2 +- plugins/siege-engine.cpp | 2 +- plugins/workflow.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 89106ca87..6421114a1 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -762,7 +762,7 @@ static void markBuildingTiles(df::building *bld, bool remove) static void linkRooms(df::building *bld) { - auto &vec = world->buildings.other[buildings_other_id::ANY_FREE]; + auto &vec = world->buildings.other[buildings_other_id::IN_PLAY]; bool changed = false; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 42ccae4c2..44984cfdb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1547,7 +1547,7 @@ static int stockcheck(color_ostream &out, vector & parameters) } - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; // Precompute a bitmask with the bad flags df::item_flags bad_flags; diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 324c924c4..5c7b8833a 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -493,7 +493,7 @@ static int setAmmoItem(lua_State *L) if (!entry.isValid()) return 0; - engine->ammo_vector_id = job_item_vector_id::ANY_FREE; + engine->ammo_vector_id = job_item_vector_id::IN_PLAY; engine->ammo_item_type = item_type; FOR_ENUM_ITEMS(job_item_vector_id, id) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2720baa83..57020b161 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1133,7 +1133,7 @@ static void map_job_items(color_ostream &out) bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; for (size_t i = 0; i < items.size(); i++) { From 09f8e8e4192d7fe8a61a39c49749a3820f47ed37 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 23 Oct 2012 21:42:03 +0400 Subject: [PATCH 096/472] Start working on gui for workflow. --- NEWS | 1 + dfhack.init-example | 3 + library/LuaApi.cpp | 13 ++- library/include/LuaTools.h | 2 + plugins/lua/workflow.lua | 14 +++ plugins/workflow.cpp | 195 ++++++++++++++++++++++++++++++----- scripts/gui/workflow.lua | 202 +++++++++++++++++++++++++++++++++++++ 7 files changed, 401 insertions(+), 29 deletions(-) create mode 100644 plugins/lua/workflow.lua create mode 100644 scripts/gui/workflow.lua diff --git a/NEWS b/NEWS index 5567258bc..b4712c22d 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ DFHack future New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. + - gui/workflow: a front-end for the workflow plugin. DFHack v0.34.11-r2 diff --git a/dfhack.init-example b/dfhack.init-example index 27801b73d..d4c7dc233 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -78,6 +78,9 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path # workshop job details keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job +# workflow front-end +keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow + ############################ # UI and game logic tweaks # ############################ diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index aa6c93c32..593ac3d8d 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -500,7 +500,9 @@ static void OpenPersistent(lua_State *state) * Material info lookup * ************************/ -static void push_matinfo(lua_State *state, MaterialInfo &info) +static int DFHACK_MATINFO_TOKEN = 0; + +void Lua::Push(lua_State *state, MaterialInfo &info) { if (!info.isValid()) { @@ -509,7 +511,7 @@ static void push_matinfo(lua_State *state, MaterialInfo &info) } lua_newtable(state); - lua_pushvalue(state, lua_upvalueindex(1)); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN); lua_setmetatable(state, -2); lua_pushinteger(state, info.type); @@ -564,7 +566,7 @@ static int dfhack_matinfo_find(lua_State *state) info.find(tokens); } - push_matinfo(state, info); + Lua::Push(state, info); return 1; } @@ -632,7 +634,7 @@ static int dfhack_matinfo_decode(lua_State *state) { MaterialInfo info; decode_matinfo(state, &info, true); - push_matinfo(state, info); + Lua::Push(state, info); return 1; } @@ -711,6 +713,9 @@ static void OpenMatinfo(lua_State *state) { luaL_getsubtable(state, lua_gettop(state), "matinfo"); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_MATINFO_TOKEN); + lua_dup(state); luaL_setfuncs(state, dfhack_matinfo_funcs, 1); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index fddbdbc26..ec4917972 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -36,6 +36,7 @@ distribution. namespace DFHack { class function_identity_base; + struct MaterialInfo; namespace Units { struct NoblePosition; @@ -283,6 +284,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void Push(lua_State *state, df::coord obj); DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); void Push(lua_State *state, const Units::NoblePosition &pos); + DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); template inline void Push(lua_State *state, T *ptr) { PushDFObject(state, ptr); } diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua new file mode 100644 index 000000000..748484052 --- /dev/null +++ b/plugins/lua/workflow.lua @@ -0,0 +1,14 @@ +local _ENV = mkmodule('plugins.workflow') + +--[[ + + Native functions: + + * isEnabled() + * setEnabled(enable) + * listConstraints([job]) -> {...} + * setConstraint(token, by_count, goal[, gap]) -> {...} + +--]] + +return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 2720baa83..af35c6533 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -4,6 +4,9 @@ #include "PluginManager.h" #include "MiscUtils.h" +#include "LuaTools.h" +#include "DataFuncs.h" + #include "modules/Materials.h" #include "modules/Items.h" #include "modules/Gui.h" @@ -743,6 +746,20 @@ static void delete_constraint(ItemConstraint *cv) delete cv; } +static bool deleteConstraint(std::string name) +{ + for (size_t i = 0; i < constraints.size(); i++) + { + if (constraints[i]->config.val() != name) + continue; + + delete_constraint(constraints[i]); + return true; + } + + return false; +} + /****************************** * JOB-CONSTRAINT MAPPING * ******************************/ @@ -1347,6 +1364,153 @@ static void process_constraints(color_ostream &out) update_jobs_by_constraints(out); } +static void update_data_structures(color_ostream &out) +{ + if (enabled) { + check_lost_jobs(out, 0); + recover_jobs(out); + update_job_data(out); + map_job_constraints(out); + map_job_items(out); + } +} + +/************* + * LUA API * + *************/ + +static bool isEnabled() { return enabled; } + +static void setEnabled(color_ostream &out, bool enable) +{ + if (enable && !enabled) + { + enable_plugin(out); + } + else if (!enable && enabled) + { + enabled = false; + setOptionEnabled(CF_ENABLED, false); + stop_protect(out); + } +} + +static void push_constraint(lua_State *L, ItemConstraint *cv) +{ + lua_newtable(L); + int ctable = lua_gettop(L); + + Lua::SetField(L, cv->config.entry_id(), ctable, "id"); + Lua::SetField(L, cv->config.val(), ctable, "token"); + + // Constraint key + + Lua::SetField(L, cv->item.type, ctable, "item_type"); + Lua::SetField(L, cv->item.subtype, ctable, "item_subtype"); + + Lua::SetField(L, cv->is_craft, ctable, "is_craft"); + + Lua::PushDFObject(L, &cv->mat_mask); + lua_setfield(L, -2, "mat_mask"); + + Lua::SetField(L, cv->material.type, ctable, "mat_type"); + Lua::SetField(L, cv->material.index, ctable, "mat_index"); + + Lua::SetField(L, (int)cv->min_quality, ctable, "min_quality"); + + // Constraint value + + Lua::SetField(L, cv->goalByCount(), ctable, "goal_by_count"); + Lua::SetField(L, cv->goalCount(), ctable, "goal_value"); + Lua::SetField(L, cv->goalGap(), ctable, "goal_gap"); + + Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); + Lua::SetField(L, cv->item_count, ctable, "cur_count"); + Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); + + // Current state value + + if (cv->request_resume) + Lua::SetField(L, "resume", ctable, "request"); + else if (cv->request_suspend) + Lua::SetField(L, "suspend", ctable, "request"); + + lua_newtable(L); + + for (size_t i = 0, j = 0; i < cv->jobs.size(); i++) + { + if (!cv->jobs[i]->isLive()) continue; + Lua::PushDFObject(L, cv->jobs[i]->actual_job); + lua_rawseti(L, -2, ++j); + } + + lua_setfield(L, ctable, "jobs"); +} + +static int listConstraints(lua_State *L) +{ + auto job = Lua::CheckDFObject(L, 1); + ProtectedJob *pj = NULL; + if (job) + pj = get_known(job->id); + + if (!enabled || (job && !pj)) + { + lua_pushnil(L); + return 1; + } + + color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); + + lua_newtable(L); + + auto &vec = (pj ? pj->constraints : constraints); + + for (size_t i = 0; i < vec.size(); i++) + { + push_constraint(L, vec[i]); + lua_rawseti(L, -2, i+1); + } + + return 1; +} + +static int setConstraint(lua_State *L) +{ + auto token = luaL_checkstring(L, 1); + bool by_count = lua_toboolean(L, 2); + int count = luaL_checkint(L, 3); + int gap = luaL_optint(L, 4, -1); + + color_ostream &out = *Lua::GetOutput(L); + + ItemConstraint *icv = get_constraint(out, token); + if (!icv) + luaL_error(L, "invalid constraint: %s", token); + + icv->setGoalByCount(by_count); + icv->setGoalCount(count); + icv->setGoalGap(gap); + + process_constraints(out); + push_constraint(L, icv); + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(isEnabled), + DFHACK_LUA_FUNCTION(setEnabled), + DFHACK_LUA_FUNCTION(deleteConstraint), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(listConstraints), + DFHACK_LUA_COMMAND(setConstraint), + DFHACK_LUA_END +}; + /****************************** * PRINTING AND THE COMMAND * ******************************/ @@ -1490,13 +1654,7 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet return CR_FAILURE; } - if (enabled) { - check_lost_jobs(out, 0); - recover_jobs(out); - update_job_data(out); - map_job_constraints(out); - map_job_items(out); - } + update_data_structures(out); df::building *workshop = NULL; //FIXME: unused variable! @@ -1514,18 +1672,11 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet if (cmd == "enable" || cmd == "disable") { bool enable = (cmd == "enable"); - if (enable && !enabled) - { - enable_plugin(out); - } - else if (!enable && parameters.size() == 1) + if (enable) + setEnabled(out, true); + else if (parameters.size() == 1) { - if (enabled) - { - enabled = false; - setOptionEnabled(CF_ENABLED, false); - stop_protect(out); - } + setEnabled(out, false); out << "The plugin is disabled." << endl; return CR_OK; @@ -1643,14 +1794,8 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet if (parameters.size() != 2) return CR_WRONG_USAGE; - for (size_t i = 0; i < constraints.size(); i++) - { - if (constraints[i]->config.val() != parameters[1]) - continue; - - delete_constraint(constraints[i]); + if (deleteConstraint(parameters[1])) return CR_OK; - } out.printerr("Constraint not found: %s\n", parameters[1].c_str()); return CR_FAILURE; diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua new file mode 100644 index 000000000..17413d46e --- /dev/null +++ b/scripts/gui/workflow.lua @@ -0,0 +1,202 @@ +-- A GUI front-end for the workflow plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local guimat = require 'gui.materials' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +local workflow = require 'plugins.workflow' + +function check_enabled(cb,...) + if workflow.isEnabled() then + return cb(...) + else + dlg.showYesNoPrompt( + 'Enable Plugin', + { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE + 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, + COLOR_YELLOW, + curry(function(...) + workflow.setEnabled(true) + return cb(...) + end,...) + ) + end +end + +JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) + +JobConstraints.focus_path = 'workflow-job' + +JobConstraints.ATTRS { + job = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function JobConstraints:init(args) + self.building = dfhack.job.getHolder(self.job) + + local status = { text = 'No worker', pen = COLOR_DARKGREY } + local worker = dfhack.job.getWorker(self.job) + if self.job.flags.suspend then + status = { text = 'Suspended', pen = COLOR_RED } + elseif worker then + status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + 'Workflow Constraints' + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 2, b = 2 }, + row_height = 4, + scroll_keys = widgets.SECONDSCROLL, + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } + + self:initListChoices(args.clist) +end + +function JobConstraints:onGetSelectedBuilding() + return self.building +end + +function JobConstraints:onGetSelectedJob() + return self.job +end + +function describe_item_type(iobj) + local itemline = 'any item' + if iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +function is_caste_mat(iobj) + return dfhack.items.isCasteMaterial(iobj.item_type) +end + +function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'material not applicable' + elseif iobj.mat_type >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +function list_flags(list, bitfield) + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end +end + +function JobConstraints:initListChoices(clist) + clist = clist or workflow.listConstraints(self.job) + + local choices = {} + + for i,cons in ipairs(clist) do + local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value + local curval + if cons.goal_by_count then + goal = goal .. ' stacks' + curval = cons.cur_count + else + goal = goal .. ' items' + curval = cons.cur_amount + end + local order_pen = COLOR_GREY + if cons.request == 'resume' then + order_pen = COLOR_GREEN + elseif cons.request == 'suspend' then + order_pen = COLOR_RED + end + local itemstr + if cons.is_craft then + itemstr = 'any craft' + else + itemstr = describe_item_type(cons) + end + if cons.min_quality > 0 then + itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' + end + local matstr = describe_material(cons) + local matflagstr = '' + local matflags = {} + list_flags(matflags, cons.mat_mask) + if #matflags > 0 then + matflagstr = 'class: '..table.concat(matflags, ', ') + end + + table.insert(choices, { + text = { + goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, + ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr + }, + obj = cons + }) + end + + self.subviews.list:setChoices(choices) +end + +function JobConstraints:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() + end + else + JobConstraints.super.onInput(self, keys) + end +end + +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") +end + +check_enabled(function() + local job = dfhack.gui.getSelectedJob() + if not job.flags['repeat'] then + dlg.showMessage('Not Supported', 'Workflow only tracks repeat jobs.', COLOR_LIGHTRED) + return + end + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() +end) + From cd852e22404223c66ade5af0e973431a637ffe0d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 17:51:47 +0400 Subject: [PATCH 097/472] Fix one more place that was using ANY_FREE. --- plugins/devel/stockcheck.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 8ace55cdc..452a637fc 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -135,7 +135,7 @@ static command_result stockcheck(color_ostream &out, vector & parameter } - std::vector &items = world->items.other[items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::IN_PLAY]; // Precompute a bitmask with the bad flags df::item_flags bad_flags; From 616c57257daaf2e420d452c6997d9897dca5fac9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 19:25:06 +0400 Subject: [PATCH 098/472] Support changing the constraint ranges from the workflow gui script. TODO: implement creating completely new constraints. --- library/lua/gui/widgets.lua | 39 ++++++----- plugins/workflow.cpp | 5 +- scripts/gui/workflow.lua | 132 +++++++++++++++++++++++++++++++++++- 3 files changed, 156 insertions(+), 20 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e6a9a4d72..0ec92030b 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -216,7 +216,7 @@ local function is_disabled(token) (token.enabled ~= nil and not getval(token.enabled)) end -function render_text(obj,dc,x0,y0,pen,dpen) +function render_text(obj,dc,x0,y0,pen,dpen,disabled) local width = 0 for iline,line in ipairs(obj.text_lines) do local x = 0 @@ -246,7 +246,7 @@ function render_text(obj,dc,x0,y0,pen,dpen) local keypen if dc then - if is_disabled(token) then + if disabled or is_disabled(token) then dc:pen(getval(token.dpen) or dpen) keypen = COLOR_GREEN else @@ -305,6 +305,8 @@ Label = defclass(Label, Widget) Label.ATTRS{ text_pen = COLOR_WHITE, text_dpen = COLOR_DARKGREY, + disabled = DEFAULT_NIL, + enabled = DEFAULT_NIL, auto_height = true, auto_width = false, } @@ -346,11 +348,13 @@ function Label:getTextWidth() end function Label:onRenderBody(dc) - render_text(self,dc,0,0,self.text_pen,self.text_dpen) + render_text(self,dc,0,0,self.text_pen,self.text_dpen,is_disabled(self)) end function Label:onInput(keys) - return check_text_keys(self, keys) + if not is_disabled(self) then + return check_text_keys(self, keys) + end end ---------- @@ -376,7 +380,6 @@ SECONDSCROLL = { List.ATTRS{ text_pen = COLOR_CYAN, cursor_pen = COLOR_LIGHTCYAN, - cursor_dpen = DEFAULT_NIL, inactive_pen = DEFAULT_NIL, on_select = DEFAULT_NIL, on_submit = DEFAULT_NIL, @@ -417,7 +420,9 @@ function List:getChoices() end function List:getSelected() - return self.selected, self.choices[self.selected] + if #self.choices > 0 then + return self.selected, self.choices[self.selected] + end end function List:getContentWidth() @@ -485,15 +490,11 @@ function List:onRenderBody(dc) for i = top,iend do local obj = choices[i] local current = (i == self.selected) - local cur_pen = self.text_pen - local cur_dpen = cur_pen + local cur_pen = self.cursor_pen + local cur_dpen = self.text_pen - if current and active then - cur_pen = self.cursor_pen - cur_dpen = self.cursor_dpen or self.text_pen - elseif current then + if not self.active then cur_pen = self.inactive_pen or self.cursor_pen - cur_dpen = self.inactive_pen or self.text_pen end local y = (i - top)*self.row_height @@ -503,11 +504,15 @@ function List:onRenderBody(dc) if type(obj.icon) == 'table' then dc:char(nil,obj.icon) else - dc:string(obj.icon, obj.icon_pen or cur_pen) + if current then + dc:string(obj.icon, obj.icon_pen or cur_pen) + else + dc:string(obj.icon, obj.icon_pen or cur_dpen) + end end end - render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen) + render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) if obj.key then local keystr = gui.getKeyDisplay(obj.key) @@ -620,7 +625,9 @@ end function FilteredList:getSelected() local i,v = self.list:getSelected() - return map_opttab(self.choice_index, i), v + if i then + return map_opttab(self.choice_index, i), v + end end function FilteredList:getContentWidth() diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index b58a0763c..29538b6c9 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -306,8 +306,9 @@ public: void setGoalCount(int v) { config.ival(0) = v; } int goalGap() { - int gcnt = std::max(1, goalCount()/2); - return std::min(gcnt, config.ival(1) <= 0 ? 5 : config.ival(1)); + int cval = (config.ival(1) <= 0) ? 5 : config.ival(1); + int cmax = std::max(goalCount()-5, goalCount()/2); + return std::max(1, std::min(cmax, cval)); } void setGoalGap(int v) { config.ival(1) = v; } diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 17413d46e..c5d28cb98 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -36,6 +36,8 @@ JobConstraints.ATTRS { frame_background = COLOR_BLACK, } +local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } + function JobConstraints:init(args) self.building = dfhack.job.getHolder(self.job) @@ -56,13 +58,54 @@ function JobConstraints:init(args) }, widgets.List{ view_id = 'list', - frame = { t = 2, b = 2 }, + frame = { t = 2, b = 6 }, row_height = 4, scroll_keys = widgets.SECONDSCROLL, }, + widgets.Label{ + frame = { l = 0, b = 3 }, + enabled = self:callback('isAnySelected'), + text = { + { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + text = function() + local cons = self:getCurConstraint() or null_cons + if cons.goal_by_count then + return ': Count stacks ' + else + return ': Count items ' + end + end, + on_activate = self:callback('onChangeUnit') }, + { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + on_activate = self:callback('onEditRange') }, + NEWLINE, ' ', + { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_gap', 5) }, + { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_gap', -1) }, + { text = function() + local cons = self:getCurConstraint() or null_cons + return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) + end }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_value', -1) }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { text = function() + local cons = self:getCurConstraint() or null_cons + return string.format(': Max %-4d', cons.goal_value) + end }, + } + }, widgets.Label{ frame = { l = 0, b = 0 }, text = { + { key = 'CUSTOM_N', text = ': New limit, ', + on_activate = self:callback('onNewConstraint') }, + { key = 'CUSTOM_X', text = ': Delete', + enabled = self:callback('isAnySelected'), + on_activate = self:callback('onDeleteConstraint') }, + NEWLINE, NEWLINE, { key = 'LEAVESCREEN', text = ': Back', on_activate = self:callback('dismiss') } } @@ -157,7 +200,14 @@ function JobConstraints:initListChoices(clist) local matflags = {} list_flags(matflags, cons.mat_mask) if #matflags > 0 then - matflagstr = 'class: '..table.concat(matflags, ', ') + matflags[1] = 'any '..matflags[1] + if matstr == 'any material' then + matstr = table.concat(matflags, ', ') + matflags = {} + end + end + if #matflags > 0 then + matflagstr = table.concat(matflags, ', ') end table.insert(choices, { @@ -172,6 +222,84 @@ function JobConstraints:initListChoices(clist) self.subviews.list:setChoices(choices) end +function JobConstraints:isAnySelected() + return self.subviews.list:getSelected() ~= nil +end + +function JobConstraints:getCurConstraint() + local i,v = self.subviews.list:getSelected() + if v then return v.obj end +end + +function JobConstraints:getCurUnit() + local cons = self:getCurConstraint() + if cons and cons.goal_by_count then + return 'stacks' + else + return 'items' + end +end + +function JobConstraints:saveConstraint(cons) + workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices() +end + +function JobConstraints:onChangeUnit() + local cons = self:getCurConstraint() + cons.goal_by_count = not cons.goal_by_count + self:saveConstraint(cons) +end + +function JobConstraints:onEditRange() + local cons = self:getCurConstraint() + dlg.showInputPrompt( + 'Input Range', + 'Enter the new constraint range:', + COLOR_WHITE, + (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, + function(text) + local maxv = string.match(text, '^%s*(%d+)%s*$') + if maxv then + cons.goal_value = maxv + return self:saveConstraint(cons) + end + local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') + if minv and maxv and minv ~= maxv then + cons.goal_value = math.max(minv,maxv) + cons.goal_gap = math.abs(maxv-minv) + return self:saveConstraint(cons) + end + dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) + end + ) +end + +function JobConstraints:onIncRange(field, delta) + local cons = self:getCurConstraint() + if not cons.goal_by_count then + delta = delta * 5 + end + cons[field] = math.max(1, cons[field] + delta) + self:saveConstraint(cons) +end + +function JobConstraints:onNewConstraint() +end + +function JobConstraints:onDeleteConstraint() + local cons = self:getCurConstraint() + dlg.showYesNoPrompt( + 'Delete Constraint', + 'Really delete the current constraint?', + COLOR_YELLOW, + function() + workflow.deleteConstraint(cons.token) + self:initListChoices() + end + ) +end + function JobConstraints:onInput(keys) if self:propagateMoveKeys(keys) then if df.global.world.selected_building ~= self.building then From 59ec9b304e70d756cefe7f90eff986e9a1c44d06 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 21:49:30 +0400 Subject: [PATCH 099/472] Implement adding workflow constraints from gui/workflow. --- library/lua/utils.lua | 28 +++- library/modules/Materials.cpp | 2 +- plugins/lua/workflow.lua | 261 ++++++++++++++++++++++++++++++++++ plugins/workflow.cpp | 9 +- scripts/gui/workflow.lua | 110 +++++++++++--- 5 files changed, 384 insertions(+), 26 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 2507c9964..8070b85de 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -283,6 +283,16 @@ function clone_with_default(obj,default,force) return rv end +function parse_bitfield_int(value, type_ref) + local res = {} + for i,v in ipairs(type_ref) do + if bit32.extract(value, i) ~= 0 then + res[v] = true + end + end + return res +end + -- Sort a vector or lua table function sort_vector(vector,field,cmp) local fcmp = compare_field(field,cmp) @@ -304,16 +314,26 @@ end -- Linear search -function linear_index(vector,obj) +function linear_index(vector,key,field) local min,max if df.isvalid(vector) then min,max = 0,#vector-1 else min,max = 1,#vector end - for i=min,max do - if vector[i] == obj then - return i + if field then + for i=min,max do + local obj = vector[i] + if obj[field] == key then + return i, obj + end + end + else + for i=min,max do + local obj = vector[i] + if obj == key then + return i, obj + end end end return nil diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index a94d49181..454fdf66d 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -312,7 +312,7 @@ std::string MaterialInfo::getToken() else if (index == 1) return "COAL:CHARCOAL"; } - return material->id; + return material->id + ":NONE"; case Inorganic: return "INORGANIC:" + inorganic->id; case Creature: diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 748484052..598bf8841 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -1,5 +1,7 @@ local _ENV = mkmodule('plugins.workflow') +local utils = require 'utils' + --[[ Native functions: @@ -8,7 +10,266 @@ local _ENV = mkmodule('plugins.workflow') * setEnabled(enable) * listConstraints([job]) -> {...} * setConstraint(token, by_count, goal[, gap]) -> {...} + * deleteConstraint(token) -> true/false --]] +local reaction_id_cache = nil + +if dfhack.is_core_context then + dfhack.onStateChange[_ENV] = function(code) + if code == SC_MAP_LOADED then + reaction_id_cache = nil + end + end +end + +local function get_reaction(name) + if not reaction_id_cache then + reaction_id_cache = {} + for i,v in ipairs(df.global.world.raws.reactions) do + reaction_id_cache[v.code] = i + end + end + local id = reaction_id_cache[name] or -1 + return id, df.reaction.find(id) +end + +local job_outputs = {} + +function job_outputs.CustomReaction(callback, job) + local rid, r = get_reaction(job.reaction_name) + + for i,prod in ipairs(r.products) do + if df.reaction_product_itemst:is_instance(prod) then + local mat_type, mat_index = prod.mat_type, prod.mat_index + local mat_mask + + local get_mat_prod = prod.flags.GET_MATERIAL_PRODUCT + if get_mat_prod or prod.flags.GET_MATERIAL_SAME then + local reagent_code = prod.get_material.reagent_code + local reagent_idx, src = utils.linear_index(r.reagents, reagent_code, 'code') + if not reagent_idx then goto continue end + + local item_idx, jitem = utils.linear_index(job.job_items, reagent_idx, 'reagent_index') + if jitem then + mat_type, mat_index = jitem.mat_type, jitem.mat_index + else + if not df.reaction_reagent_itemst:is_instance(src) then goto continue end + mat_type, mat_index = src.mat_type, src.mat_index + end + + if get_mat_prod then + local mat = dfhack.matinfo.decode(mat_type, mat_index) + + mat_type, mat_index = -1, -1 + + if mat then + local p_code = prod.get_material.product_code + local rp = mat.material.reaction_product + local idx = utils.linear_index(rp.id, p_code) + if not idx then + goto continue + end + mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx] + else + if code == "SOAP_MAT" then + mat_mask = { soap = true } + end + end + end + end + + callback{ + is_craft = prod.flags.CRAFTS, + item_type = prod.item_type, item_subtype = prod.item_subtype, + mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask + } + end + ::continue:: + end +end + +local function guess_job_material(job) + if job.job_type == df.job_type.PrepareMeal then + return -1, -1, nil + end + + local mat_type, mat_index = job.mat_type, job.mat_index + local mask_whole = job.material_category.whole + local mat_mask + + local jmat = df.job_type.attrs[job.job_type].material + if jmat then + mat_type, mat_index = df.builtin_mats[jmat] or -1, -1 + if mat_type < 0 and df.job_material_category[jmat] then + mat_mask = { [jmat] = true } + end + end + + if not mat_mask and mask_whole ~= 0 then + mat_mask = utils.parse_bitfield_int(mask_whole, df.job_material_category) + if mat_mask.wood2 then + mat_mask.wood = true + mat_mask.wood2 = nil + end + end + + if mat_type < 0 and #job.job_items > 0 then + local item0 = job.job_items[0] + if #job.job_items == 1 or item0.item_type == df.item_type.PLANT then + mat_type, mat_index = item0.mat_type, item0.mat_index + + if item0.item_type == df.item_type.WOOD then + mat_mask = mat_mask or {} + mat_mask.wood = true + end + end + end + + return mat_type, mat_index, mat_mask +end + +function default_output(callback, job, mat_type, mat_index, mat_mask) + local itype = df.job_type.attrs[job.job_type].item + if itype >= 0 then + local subtype = nil + if df.item_type.attrs[itype].is_rawable then + subtype = job.item_subtype + end + callback{ + item_type = itype, item_subtype = subtype, + mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask + } + end +end + +function job_outputs.SmeltOre(callback, job) + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.inorganic then + for i,v in ipairs(mat.inorganic.metal_ore.mat_index) do + callback{ item_type = df.item_type.BAR, mat_type = 0, mat_index = v } + end + else + callback{ item_type = df.item_type.BAR, mat_type = 0 } + end +end + +function job_outputs.ExtractMetalStrands(callback, job) + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.inorganic then + for i,v in ipairs(mat.inorganic.thread_metal.mat_index) do + callback{ item_type = df.item_type.THREAD, mat_type = 0, mat_index = v } + end + else + callback{ item_type = df.item_type.THREAD, mat_type = 0 } + end +end + +function job_outputs.PrepareMeal(callback, job) + if job.mat_type ~= -1 then + for i,v in ipairs(df.global.world.raws.itemdefs.food) do + if v.level == job.mat_type then + callback{ item_type = df.item_type.FOOD, item_subtype = i } + end + end + else + callback{ item_type = df.item_type.FOOD } + end +end + +function job_outputs.MakeCrafts(callback, job) + local mat_type, mat_index, mat_mask = guess_job_material(job) + callback{ is_craft = true, mat_type = mat_type, mat_index = mat_index, mat_mask = mat_mask } +end + +local plant_products = { + BrewDrink = 'DRINK', + MillPlants = 'MILL', + ProcessPlants = 'THREAD', + ProcessPlantsBag = 'LEAVES', + ProcessPlantsBarrel = 'EXTRACT_BARREL', + ProcessPlantsVial = 'EXTRACT_VIAL', + ExtractFromPlants = 'EXTRACT_STILL_VIAL', +} + +for job,flag in pairs(plant_products) do + local ttag = 'type_'..string.lower(flag) + local itag = 'idx_'..string.lower(flag) + job_outputs[job] = function(callback, job) + local mat_type, mat_index = -1, -1 + local mat = dfhack.matinfo.decode(job.job_items[0]) + if mat and mat.plant and mat.plant.flags[flag] then + mat_type = mat.plant.material_defs[ttag] + mat_index = mat.plant.material_defs[itag] + end + default_output(callback, job, mat_type, mat_index) + end +end + +local function enum_job_outputs(callback, job) + local handler = job_outputs[df.job_type[job.job_type]] + if handler then + handler(callback, job) + else + default_output(callback, job, guess_job_material(job)) + end +end + +function listJobOutputs(job) + local res = {} + enum_job_outputs(curry(table.insert, res), job) + return res +end + +function constraintToToken(cspec) + local token + if cspec.is_craft then + token = 'CRAFTS' + else + token = df.item_type[cspec.item_type] or error('invalid item type: '..cspec.item_type) + if cspec.item_subtype and cspec.item_subtype >= 0 then + local def = dfhack.items.getSubtypeDef(cspec.item_type, cspec.item_subtype) + if def then + token = token..':'..def.id + else + error('invalid subtype '..cspec.item_subtype..' of '..token) + end + end + end + local mask_part + if cspec.mat_mask then + local lst = {} + for n,v in pairs(cspec.mat_mask) do + if v then table.insert(lst,n) end + end + mask_part = table.concat(lst, ',') + end + local mat_part + if cspec.mat_type and cspec.mat_type >= 0 then + local mat = dfhack.matinfo.decode(cspec.mat_type, cspec.mat_index or -1) + if mat then + mat_part = mat:getToken() + else + error('invalid material: '..cspec.mat_type..':'..(cspec.mat_index or -1)) + end + end + local qpart + if cspec.quality and cspec.quality > 0 then + qpart = df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality) + end + + if mask_part or mat_part or qpart then + token = token .. '/' .. (mask_part or '') + if mat_part or qpart then + token = token .. '/' .. (mat_part or '') + if qpart then + token = token .. '/' .. (qpart or '') + end + end + end + + return token +end + return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 29538b6c9..a6bb3a41a 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -323,6 +323,7 @@ public: void init(const std::string &str) { config.val() = str; + config.ival(0) = 10; config.ival(2) = 0; } @@ -1481,7 +1482,7 @@ static int setConstraint(lua_State *L) { auto token = luaL_checkstring(L, 1); bool by_count = lua_toboolean(L, 2); - int count = luaL_checkint(L, 3); + int count = luaL_optint(L, 3, -1); int gap = luaL_optint(L, 4, -1); color_ostream &out = *Lua::GetOutput(L); @@ -1491,8 +1492,10 @@ static int setConstraint(lua_State *L) luaL_error(L, "invalid constraint: %s", token); icv->setGoalByCount(by_count); - icv->setGoalCount(count); - icv->setGoalGap(gap); + if (!lua_isnil(L, 3)) + icv->setGoalCount(count); + if (!lua_isnil(L, 4)) + icv->setGoalGap(gap); process_constraints(out); push_constraint(L, icv); diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index c5d28cb98..9dfb7dd76 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -125,10 +125,13 @@ end function describe_item_type(iobj) local itemline = 'any item' - if iobj.item_type >= 0 then + if iobj.is_craft then + itemline = 'any craft' + elseif iobj.item_type >= 0 then itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local def = dfhack.items.getSubtypeDef(iobj.item_type, iobj.item_subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, iobj.item_subtype) + local subtype = iobj.item_subtype or -1 + local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) if def then itemline = def.name elseif count >= 0 then @@ -139,14 +142,14 @@ function describe_item_type(iobj) end function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type) + return dfhack.items.isCasteMaterial(iobj.item_type or -1) end function describe_material(iobj) local matline = 'any material' if is_caste_mat(iobj) then - matline = 'material not applicable' - elseif iobj.mat_type >= 0 then + matline = 'no material' + elseif (iobj.mat_type or -1) >= 0 then local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) if info then matline = info:toString() @@ -157,12 +160,16 @@ function describe_material(iobj) return matline end -function list_flags(list, bitfield) - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) +function list_flags(bitfield) + local list = {} + if bitfield then + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end end end + return list end function JobConstraints:initListChoices(clist) @@ -186,19 +193,13 @@ function JobConstraints:initListChoices(clist) elseif cons.request == 'suspend' then order_pen = COLOR_RED end - local itemstr - if cons.is_craft then - itemstr = 'any craft' - else - itemstr = describe_item_type(cons) - end + local itemstr = describe_item_type(cons) if cons.min_quality > 0 then itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' end local matstr = describe_material(cons) local matflagstr = '' - local matflags = {} - list_flags(matflags, cons.mat_mask) + local matflags = list_flags(cons.mat_mask) if #matflags > 0 then matflags[1] = 'any '..matflags[1] if matstr == 'any material' then @@ -284,7 +285,80 @@ function JobConstraints:onIncRange(field, delta) self:saveConstraint(cons) end +function make_constraint_variants(outputs) + local variants = {} + local known = {} + local function register(cons) + cons.token = workflow.constraintToToken(cons) + if not known[cons.token] then + known[cons.token] = true + table.insert(variants, cons) + end + end + + local generic = {} + local anymat = {} + for i,cons in ipairs(outputs) do + local mask = cons.mat_mask + if (cons.mat_type or -1) >= 0 then + cons.mat_mask = nil + end + register(cons) + if mask then + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = mask + }) + end + table.insert(anymat, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft + }) + end + for i,cons in ipairs(generic) do register(cons) end + for i,cons in ipairs(anymat) do register(cons) end + + return variants +end + function JobConstraints:onNewConstraint() + local outputs = workflow.listJobOutputs(self.job) + if #outputs == 0 then + dlg.showMessage('Unsupported', 'Workflow cannot guess the outputs of this job.', COLOR_LIGHTRED) + return + end + + local variants = make_constraint_variants(outputs) + + local choices = {} + for i,cons in ipairs(variants) do + local itemstr = describe_item_type(cons) + local matstr = describe_material(cons) + local matflags = list_flags(cons.mat_mask or {}) + if #matflags > 0 then + local fstr = table.concat(matflags, '/') + if matstr == 'any material' then + matstr = 'any '..fstr + else + matstr = 'any '..fstr..' '..matstr + end + end + + table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) + end + + dlg.showListPrompt( + 'Job Outputs', + 'Select one of the job outputs:', + COLOR_WHITE, + choices, + function(idx,item) + self:saveConstraint(item.obj) + end + ) end function JobConstraints:onDeleteConstraint() From a7f1ceb75d79ab1fc2d2a28c44961f7d8767a9c9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 24 Oct 2012 23:01:31 +0400 Subject: [PATCH 100/472] Link lua to workflow. --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..2f663f805 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -100,7 +100,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(stockpiles stockpiles.cpp) DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) - DFHACK_PLUGIN(workflow workflow.cpp) + DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(showmood showmood.cpp) DFHACK_PLUGIN(fixveins fixveins.cpp) DFHACK_PLUGIN(fixpositions fixpositions.cpp) From 2d83b4fa39f58a52104206e4ce732dc094abca5c Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 24 Oct 2012 14:07:46 -0500 Subject: [PATCH 101/472] We don't need docutils --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 14436dcff..41c38bd44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -145,7 +145,7 @@ include_directories(depends/clsocket/src) add_subdirectory(depends) -find_package(Docutils) +#find_package(Docutils) #set (RST_FILES #"Readme" From a1dd31aab32bdecfb360cf8bb40e5c7c836a6deb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:09:39 +0400 Subject: [PATCH 102/472] Tweak the mechanics for showing and rendering lua screens. - Place new screens below any dismissed ones on top. - When asked to render a dismissed lua screen, call render() on its parent to avoid producing a black screen frame when unlucky. --- library/lua/gui.lua | 9 +++++---- library/lua/gui/dwarfmode.lua | 6 ++---- library/modules/Screen.cpp | 7 ++++++- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 18b0d67d8..15d03742f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -513,17 +513,18 @@ function Screen:sendInputToParent(...) end end -function Screen:show(below) +function Screen:show(parent) if self._native then error("This screen is already on display") end - self:onAboutToShow(below) - if not dscreen.show(self, below) then + parent = parent or dfhack.gui.getCurViewscreen(true) + self:onAboutToShow(parent) + if not dscreen.show(self, parent.child) then error('Could not show screen') end end -function Screen:onAboutToShow() +function Screen:onAboutToShow(parent) end function Screen:onShow() diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index cef52ae24..8c537639b 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -373,10 +373,8 @@ function DwarfOverlay:simulateCursorMovement(keys, anchor) end end -function DwarfOverlay:onAboutToShow(below) - local screen = dfhack.gui.getCurViewscreen() - if below then screen = below.parent end - if not df.viewscreen_dwarfmodest:is_instance(screen) then +function DwarfOverlay:onAboutToShow(parent) + if not df.viewscreen_dwarfmodest:is_instance(parent) then error("This screen requires the main dwarfmode view") end end diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 29f718266..d89b3688d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -639,7 +639,12 @@ dfhack_lua_viewscreen::~dfhack_lua_viewscreen() void dfhack_lua_viewscreen::render() { - if (Screen::isDismissed(this)) return; + if (Screen::isDismissed(this)) + { + if (parent) + parent->render(); + return; + } dfhack_viewscreen::render(); From 8eebfa007c60f210a365c91af029152a4a0c689d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:15:18 +0400 Subject: [PATCH 103/472] Tweak the workflow gui script to make the UI operate smoother. --- library/lua/utils.lua | 17 +++++ plugins/lua/workflow.lua | 51 ++++++++++++-- plugins/workflow.cpp | 21 +++--- scripts/gui/workflow.lua | 125 +++++++++++------------------------ scripts/gui/workshop-job.lua | 38 ++++++++--- 5 files changed, 144 insertions(+), 108 deletions(-) diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 8070b85de..e7267038c 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -283,7 +283,11 @@ function clone_with_default(obj,default,force) return rv end +-- Parse an integer value into a bitfield table function parse_bitfield_int(value, type_ref) + if value == 0 then + return nil + end local res = {} for i,v in ipairs(type_ref) do if bit32.extract(value, i) ~= 0 then @@ -293,6 +297,19 @@ function parse_bitfield_int(value, type_ref) return res end +-- List the enabled flag names in the bitfield table +function list_bitfield_flags(bitfield, list) + list = list or {} + if bitfield then + for name,val in pairs(bitfield) do + if val then + table.insert(list, name) + end + end + end + return list +end + -- Sort a vector or lua table function sort_vector(vector,field,cmp) local fcmp = compare_field(field,cmp) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 598bf8841..391045a96 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -9,7 +9,7 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) * listConstraints([job]) -> {...} - * setConstraint(token, by_count, goal[, gap]) -> {...} + * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false --]] @@ -40,6 +40,10 @@ local job_outputs = {} function job_outputs.CustomReaction(callback, job) local rid, r = get_reaction(job.reaction_name) + if not r then + return + end + for i,prod in ipairs(r.products) do if df.reaction_product_itemst:is_instance(prod) then local mat_type, mat_index = prod.mat_type, prod.mat_index @@ -239,11 +243,7 @@ function constraintToToken(cspec) end local mask_part if cspec.mat_mask then - local lst = {} - for n,v in pairs(cspec.mat_mask) do - if v then table.insert(lst,n) end - end - mask_part = table.concat(lst, ',') + mask_part = table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',') end local mat_part if cspec.mat_type and cspec.mat_type >= 0 then @@ -272,4 +272,43 @@ function constraintToToken(cspec) return token end +function listWeakenedConstraints(outputs) + local variants = {} + local known = {} + local function register(cons) + cons.token = constraintToToken(cons) + if not known[cons.token] then + known[cons.token] = true + table.insert(variants, cons) + end + end + + local generic = {} + local anymat = {} + for i,cons in ipairs(outputs) do + local mask = cons.mat_mask + if (cons.mat_type or -1) >= 0 then + cons.mat_mask = nil + end + register(cons) + if mask then + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = mask + }) + end + table.insert(anymat, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft + }) + end + for i,cons in ipairs(generic) do register(cons) end + for i,cons in ipairs(anymat) do register(cons) end + + return variants +end + return _ENV diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index a6bb3a41a..19709b68c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1452,19 +1452,23 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) static int listConstraints(lua_State *L) { auto job = Lua::CheckDFObject(L, 1); - ProtectedJob *pj = NULL; - if (job) - pj = get_known(job->id); - if (!enabled || (job && !pj)) - { - lua_pushnil(L); + lua_pushnil(L); + + if (!enabled || (job && !isSupportedJob(job))) return 1; - } color_ostream &out = *Lua::GetOutput(L); update_data_structures(out); + ProtectedJob *pj = NULL; + if (job) + { + pj = get_known(job->id); + if (!pj) + return 1; + } + lua_newtable(L); auto &vec = (pj ? pj->constraints : constraints); @@ -1491,7 +1495,8 @@ static int setConstraint(lua_State *L) if (!icv) luaL_error(L, "invalid constraint: %s", token); - icv->setGoalByCount(by_count); + if (!lua_isnil(L, 2)) + icv->setGoalByCount(by_count); if (!lua_isnil(L, 3)) icv->setGoalCount(count); if (!lua_isnil(L, 4)) diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 9dfb7dd76..b0a01a37c 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -26,6 +26,23 @@ function check_enabled(cb,...) end end +function check_repeat(job, cb) + if job.flags['repeat'] then + return cb(job) + else + dlg.showYesNoPrompt( + 'Not Repeat Job', + { 'Workflow only tracks repeating jobs.', NEWLINE, NEWLINE, + 'Press ', { key = 'MENU_CONFIRM' }, ' to make this one repeat.' }, + COLOR_YELLOW, + function() + job.flags['repeat'] = true + return cb(job) + end + ) + end +end + JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) JobConstraints.focus_path = 'workflow-job' @@ -41,14 +58,6 @@ local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } function JobConstraints:init(args) self.building = dfhack.job.getHolder(self.job) - local status = { text = 'No worker', pen = COLOR_DARKGREY } - local worker = dfhack.job.getWorker(self.job) - if self.job.flags.suspend then - status = { text = 'Suspended', pen = COLOR_RED } - elseif worker then - status = { text = dfhack.TranslateName(dfhack.units.getVisibleName(worker)), pen = COLOR_GREEN } - end - self:addviews{ widgets.Label{ frame = { l = 0, t = 0 }, @@ -160,19 +169,7 @@ function describe_material(iobj) return matline end -function list_flags(bitfield) - local list = {} - if bitfield then - for name,val in pairs(bitfield) do - if val then - table.insert(list, name) - end - end - end - return list -end - -function JobConstraints:initListChoices(clist) +function JobConstraints:initListChoices(clist, sel_token) clist = clist or workflow.listConstraints(self.job) local choices = {} @@ -199,7 +196,7 @@ function JobConstraints:initListChoices(clist) end local matstr = describe_material(cons) local matflagstr = '' - local matflags = list_flags(cons.mat_mask) + local matflags = utils.list_bitfield_flags(cons.mat_mask) if #matflags > 0 then matflags[1] = 'any '..matflags[1] if matstr == 'any material' then @@ -216,11 +213,17 @@ function JobConstraints:initListChoices(clist) goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr }, + token = cons.token, obj = cons }) end - self.subviews.list:setChoices(choices) + local selidx = nil + if sel_token then + selidx = utils.linear_index(choices, sel_token, 'token') + end + + self.subviews.list:setChoices(choices, selidx) end function JobConstraints:isAnySelected() @@ -232,18 +235,9 @@ function JobConstraints:getCurConstraint() if v then return v.obj end end -function JobConstraints:getCurUnit() - local cons = self:getCurConstraint() - if cons and cons.goal_by_count then - return 'stacks' - else - return 'items' - end -end - function JobConstraints:saveConstraint(cons) - workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) - self:initListChoices() + local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices(nil, out.token) end function JobConstraints:onChangeUnit() @@ -285,45 +279,6 @@ function JobConstraints:onIncRange(field, delta) self:saveConstraint(cons) end -function make_constraint_variants(outputs) - local variants = {} - local known = {} - local function register(cons) - cons.token = workflow.constraintToToken(cons) - if not known[cons.token] then - known[cons.token] = true - table.insert(variants, cons) - end - end - - local generic = {} - local anymat = {} - for i,cons in ipairs(outputs) do - local mask = cons.mat_mask - if (cons.mat_type or -1) >= 0 then - cons.mat_mask = nil - end - register(cons) - if mask then - table.insert(generic, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft, - mat_mask = mask - }) - end - table.insert(anymat, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft - }) - end - for i,cons in ipairs(generic) do register(cons) end - for i,cons in ipairs(anymat) do register(cons) end - - return variants -end - function JobConstraints:onNewConstraint() local outputs = workflow.listJobOutputs(self.job) if #outputs == 0 then @@ -331,13 +286,13 @@ function JobConstraints:onNewConstraint() return end - local variants = make_constraint_variants(outputs) + local variants = workflow.listWeakenedConstraints(outputs) local choices = {} for i,cons in ipairs(variants) do local itemstr = describe_item_type(cons) local matstr = describe_material(cons) - local matflags = list_flags(cons.mat_mask or {}) + local matflags = utils.list_bitfield_flags(cons.mat_mask) if #matflags > 0 then local fstr = table.concat(matflags, '/') if matstr == 'any material' then @@ -352,7 +307,7 @@ function JobConstraints:onNewConstraint() dlg.showListPrompt( 'Job Outputs', - 'Select one of the job outputs:', + 'Select one of the possible outputs:', COLOR_WHITE, choices, function(idx,item) @@ -390,15 +345,13 @@ end check_enabled(function() local job = dfhack.gui.getSelectedJob() - if not job.flags['repeat'] then - dlg.showMessage('Not Supported', 'Workflow only tracks repeat jobs.', COLOR_LIGHTRED) - return - end - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() + check_repeat(job, function(job) + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() + end) end) diff --git a/scripts/gui/workshop-job.lua b/scripts/gui/workshop-job.lua index 74e33595f..c4e203614 100644 --- a/scripts/gui/workshop-job.lua +++ b/scripts/gui/workshop-job.lua @@ -220,18 +220,40 @@ function JobDetails:setMaterial(obj, mat_type, mat_index) obj.iobj.mat_index = mat_index end +function JobDetails:findUnambiguousItem(iobj) + local count = 0 + local itype + + for i = 0,df.item_type._last_item do + if dfhack.job.isSuitableItem(iobj, i, -1) then + count = count + 1 + if count > 1 then return nil end + itype = i + end + end + + return itype +end + function JobDetails:onChangeMat() local idx, obj = self.subviews.list:getSelected() if obj.iobj.item_type == -1 and obj.iobj.mat_type == -1 then - dlg.showMessage( - 'Bug Alert', - { 'Please set a specific item type first.\n\n', - 'Otherwise the material will be matched\n', - 'incorrectly due to a limitation in DF code.' }, - COLOR_YELLOW - ) - return + -- If the job allows only one specific item type, use it + local vitype = self:findUnambiguousItem(obj.iobj) + + if vitype then + obj.iobj.item_type = vitype + else + dlg.showMessage( + 'Bug Alert', + { 'Please set a specific item type first.\n\n', + 'Otherwise the material will be matched\n', + 'incorrectly due to a limitation in DF code.' }, + COLOR_YELLOW + ) + return + end end guimat.MaterialDialog{ From 79d2cb1a5cef1c94137264b4f0de135bd0f4c841 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 12:44:23 +0400 Subject: [PATCH 104/472] Remove the C++ version of the job output deduction code and switch to lua. --- dfhack.init-example | 2 +- plugins/lua/workflow.lua | 18 +++- plugins/workflow.cpp | 191 ++++++--------------------------------- 3 files changed, 44 insertions(+), 167 deletions(-) diff --git a/dfhack.init-example b/dfhack.init-example index d4c7dc233..2cdb114be 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -79,7 +79,7 @@ keybinding add Alt-P@dwarfmode/Hauling/DefineStop/Cond/Guide gui/guide-path keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end -keybinding add Ctrl-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow ############################ # UI and game logic tweaks # diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 391045a96..4c011b24c 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -64,12 +64,12 @@ function job_outputs.CustomReaction(callback, job) end if get_mat_prod then + local p_code = prod.get_material.product_code local mat = dfhack.matinfo.decode(mat_type, mat_index) mat_type, mat_index = -1, -1 if mat then - local p_code = prod.get_material.product_code local rp = mat.material.reaction_product local idx = utils.linear_index(rp.id, p_code) if not idx then @@ -77,7 +77,7 @@ function job_outputs.CustomReaction(callback, job) end mat_type, mat_index = rp.material.mat_type[idx], rp.material.mat_index[idx] else - if code == "SOAP_MAT" then + if p_code == "SOAP_MAT" then mat_mask = { soap = true } end end @@ -106,7 +106,7 @@ local function guess_job_material(job) local jmat = df.job_type.attrs[job.job_type].material if jmat then mat_type, mat_index = df.builtin_mats[jmat] or -1, -1 - if mat_type < 0 and df.job_material_category[jmat] then + if mat_type < 0 and df.dfhack_material_category[jmat] then mat_mask = { [jmat] = true } end end @@ -220,6 +220,18 @@ local function enum_job_outputs(callback, job) end end +function doEnumJobOutputs(native_cb, job) + local function cb(info) + native_cb( + info.item_type, info.item_subtype, + info.mat_mask, info.mat_type, info.mat_index, + info.is_craft + ) + end + + enum_job_outputs(cb, job) +end + function listJobOutputs(job) local res = {} enum_job_outputs(curry(table.insert, res), job) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 19709b68c..8280adb54 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -379,13 +379,6 @@ static bool isSupportedJob(df::job *job) job->job_type == job_type::CollectSand); } -static void enumLiveJobs(std::map &rv) -{ - df::job_list_link *p = world->job_list.next; - for (; p; p = p->next) - rv[p->item->id] = p->item; -} - static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -828,71 +821,6 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i } } -static void compute_custom_job(ProtectedJob *pj, df::job *job) -{ - if (pj->reaction_id < 0) - pj->reaction_id = linear_index(df::reaction::get_vector(), - &df::reaction::code, job->reaction_name); - - df::reaction *r = df::reaction::find(pj->reaction_id); - if (!r) - return; - - for (size_t i = 0; i < r->products.size(); i++) - { - using namespace df::enums::reaction_product_item_flags; - - VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); - if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS))) - continue; - - MaterialInfo mat(prod); - df::dfhack_material_category mat_mask(0); - - bool get_mat_prod = prod->flags.is_set(GET_MATERIAL_PRODUCT); - if (get_mat_prod || prod->flags.is_set(GET_MATERIAL_SAME)) - { - int reagent_idx = linear_index(r->reagents, &df::reaction_reagent::code, - prod->get_material.reagent_code); - if (reagent_idx < 0) - continue; - - int item_idx = linear_index(job->job_items, &df::job_item::reagent_index, reagent_idx); - if (item_idx >= 0) - mat.decode(job->job_items[item_idx]); - else - { - VIRTUAL_CAST_VAR(src, df::reaction_reagent_itemst, r->reagents[reagent_idx]); - if (!src) - continue; - mat.decode(src); - } - - if (get_mat_prod) - { - std::string code = prod->get_material.product_code; - - if (mat.isValid()) - { - int idx = linear_index(mat.material->reaction_product.id, code); - if (idx < 0) - continue; - - mat.decode(mat.material->reaction_product.material, idx); - } - else - { - if (code == "SOAP_MAT") - mat_mask.bits.soap = true; - } - } - } - - link_job_constraint(pj, prod->item_type, prod->item_subtype, - mat_mask, mat.type, mat.index, prod->flags.is_set(CRAFTS)); - } -} - static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_material_category &mat_mask) { using namespace df::enums::job_type; @@ -934,100 +862,24 @@ static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_mater } } -static void compute_job_outputs(color_ostream &out, ProtectedJob *pj) +static int cbEnumJobOutputs(lua_State *L) { - using namespace df::enums::job_type; - - // Custom reactions handled in another function - df::job *job = pj->job_copy; - - if (job->job_type == CustomReaction) - { - compute_custom_job(pj, job); - return; - } - - // Item type & subtype - df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); - int16_t isubtype = job->item_subtype; - - if (itype == item_type::NONE && job->job_type != MakeCrafts) - return; - - // Item material & material category - MaterialInfo mat; - df::dfhack_material_category mat_mask; - guess_job_material(job, mat, mat_mask); - - // Job-specific code - switch (job->job_type) - { - case SmeltOre: - if (mat.inorganic) - { - std::vector &ores = mat.inorganic->metal_ore.mat_index; - for (size_t i = 0; i < ores.size(); i++) - link_job_constraint(pj, item_type::BAR, -1, 0, 0, ores[i]); - } - return; + auto pj = (ProtectedJob*)lua_touserdata(L, lua_upvalueindex(1)); - case ExtractMetalStrands: - if (mat.inorganic) - { - std::vector &threads = mat.inorganic->thread_metal.mat_index; - for (size_t i = 0; i < threads.size(); i++) - link_job_constraint(pj, item_type::THREAD, -1, 0, 0, threads[i]); - } - return; + lua_settop(L, 6); - case PrepareMeal: - if (job->mat_type != -1) - { - std::vector &food = df::itemdef_foodst::get_vector(); - for (size_t i = 0; i < food.size(); i++) - if (food[i]->level == job->mat_type) - link_job_constraint(pj, item_type::FOOD, i, 0, -1, -1); - return; - } - break; - - case MakeCrafts: - link_job_constraint(pj, item_type::NONE, -1, mat_mask, mat.type, mat.index, true); - return; - -#define PLANT_PROCESS_MAT(flag, tag) \ - if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \ - mat.decode(mat.plant->material_defs.type_##tag, \ - mat.plant->material_defs.idx_##tag); \ - else mat.decode(-1); - case BrewDrink: - PLANT_PROCESS_MAT(DRINK, drink); - break; - case MillPlants: - PLANT_PROCESS_MAT(MILL, mill); - break; - case ProcessPlants: - PLANT_PROCESS_MAT(THREAD, thread); - break; - case ProcessPlantsBag: - PLANT_PROCESS_MAT(LEAVES, leaves); - break; - case ProcessPlantsBarrel: - PLANT_PROCESS_MAT(EXTRACT_BARREL, extract_barrel); - break; - case ProcessPlantsVial: - PLANT_PROCESS_MAT(EXTRACT_VIAL, extract_vial); - break; - case ExtractFromPlants: - PLANT_PROCESS_MAT(EXTRACT_STILL_VIAL, extract_still_vial); - break; -#undef PLANT_PROCESS_MAT + df::dfhack_material_category mat_mask(0); + if (!lua_isnil(L, 3)) + Lua::CheckDFAssign(L, &mat_mask, 3); - default: - break; - } + link_job_constraint( + pj, + (df::item_type)luaL_optint(L, 1, -1), luaL_optint(L, 2, -1), + mat_mask, luaL_optint(L, 4, -1), luaL_optint(L, 5, -1), + lua_toboolean(L, 6) + ); - link_job_constraint(pj, itype, isubtype, mat_mask, mat.type, mat.index); + return 0; } static void map_job_constraints(color_ostream &out) @@ -1040,19 +892,32 @@ static void map_job_constraints(color_ostream &out) constraints[i]->is_active = false; } + auto L = Lua::Core::State; + Lua::StackUnwinder frame(L); + + bool ok = Lua::PushModulePublic(out, L, "plugins.workflow", "doEnumJobOutputs"); + if (!ok) + out.printerr("The workflow lua module is not available.\n"); + for (TKnownJobs::const_iterator it = known_jobs.begin(); it != known_jobs.end(); ++it) { ProtectedJob *pj = it->second; pj->constraints.clear(); - if (!pj->isLive()) + if (!ok || !pj->isLive()) continue; if (!melt_active && pj->actual_job->job_type == job_type::MeltMetalObject) melt_active = pj->isResumed(); - compute_job_outputs(out, pj); + // Call the lua module + lua_pushvalue(L, -1); + lua_pushlightuserdata(L, pj); + lua_pushcclosure(L, cbEnumJobOutputs, 1); + Lua::PushDFObject(L, pj->job_copy); + + Lua::SafeCall(out, L, 2, 0); } } From 1f994295b87c26a841a9074cd8451994c0ba6e38 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 25 Oct 2012 13:20:41 +0400 Subject: [PATCH 105/472] Consider assigned vehicles in use, and tweak text color rendering. --- library/lua/gui/widgets.lua | 5 +++-- plugins/workflow.cpp | 11 +++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 0ec92030b..e526408fb 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -246,11 +246,12 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) local keypen if dc then + local tpen = getval(token.pen) if disabled or is_disabled(token) then - dc:pen(getval(token.dpen) or dpen) + dc:pen(getval(token.dpen) or tpen or dpen) keypen = COLOR_GREEN else - dc:pen(getval(token.pen) or pen) + dc:pen(tpen or pen) keypen = COLOR_LIGHTGREEN end end diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 8280adb54..ca8388413 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -40,6 +40,7 @@ #include "df/plant_raw.h" #include "df/inorganic_raw.h" #include "df/builtin_mats.h" +#include "df/vehicle.h" using std::vector; using std::string; @@ -994,6 +995,15 @@ static bool itemInRealJob(df::item *item) != job_type_class::Hauling; } +static bool isRouteVehicle(df::item *item) +{ + int id = item->getVehicleID(); + if (id < 0) return false; + + auto vehicle = df::vehicle::find(id); + return vehicle && vehicle->route_id >= 0; +} + static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1103,6 +1113,7 @@ static void map_job_items(color_ostream &out) item->flags.bits.owned || item->flags.bits.in_chest || item->isAssignedToStockpile() || + isRouteVehicle(item) || itemInRealJob(item) || itemBusy(item)) { From 1b9d11090ff4e3d9a0e6df01ec9560c82a45a182 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 25 Oct 2012 13:44:01 +0200 Subject: [PATCH 106/472] ruby: ANY_FREE/IN_PLAY --- plugins/ruby/building.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index 08c12a841..e863ec5d5 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -227,7 +227,7 @@ module DFHack # link bld into other rooms if it is inside their extents or vice versa def building_linkrooms(bld) - world.buildings.other[:ANY_FREE].each { |ob| + world.buildings.other[:IN_PLAY].each { |ob| next if ob.z != bld.z if bld.is_room and bld.room.extents next if ob.is_room or ob.x1 < bld.room.x or ob.x1 >= bld.room.x+bld.room.width or ob.y1 < bld.room.y or ob.y1 >= bld.room.y+bld.room.height From e23052a570cf8a090a5c99b0f0c4c114c4db7620 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 11:52:41 +0400 Subject: [PATCH 107/472] Fix an error in gui/workflow when enabling, and tweak color. --- NEWS | 3 +++ scripts/gui/workflow.lua | 23 ++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/NEWS b/NEWS index b4712c22d..fc9266401 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ DFHack future - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workflow: a front-end for the workflow plugin. + Workflow plugin: + - properly considers minecarts assigned to routes busy. + - code for deducing job outputs rewritten in lua for flexibility. DFHack v0.34.11-r2 diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index b0a01a37c..8dc958062 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -9,26 +9,26 @@ local dlg = require 'gui.dialogs' local workflow = require 'plugins.workflow' -function check_enabled(cb,...) +function check_enabled(cb) if workflow.isEnabled() then - return cb(...) + return cb() else dlg.showYesNoPrompt( 'Enable Plugin', - { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE + { 'The workflow plugin is not enabled currently.', NEWLINE, NEWLINE, 'Press ', { key = 'MENU_CONFIRM' }, ' to enable it.' }, COLOR_YELLOW, - curry(function(...) + function() workflow.setEnabled(true) - return cb(...) - end,...) + return cb() + end ) end end function check_repeat(job, cb) if job.flags['repeat'] then - return cb(job) + return cb() else dlg.showYesNoPrompt( 'Not Repeat Job', @@ -37,7 +37,7 @@ function check_repeat(job, cb) COLOR_YELLOW, function() job.flags['repeat'] = true - return cb(job) + return cb() end ) end @@ -188,7 +188,7 @@ function JobConstraints:initListChoices(clist, sel_token) if cons.request == 'resume' then order_pen = COLOR_GREEN elseif cons.request == 'suspend' then - order_pen = COLOR_RED + order_pen = COLOR_BLUE end local itemstr = describe_item_type(cons) if cons.min_quality > 0 then @@ -343,9 +343,10 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Wor qerror("This script requires a workshop job selected in the 'q' mode") end +local job = dfhack.gui.getSelectedJob() + check_enabled(function() - local job = dfhack.gui.getSelectedJob() - check_repeat(job, function(job) + check_repeat(job, function() local clist = workflow.listConstraints(job) if not clist then dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) From b976e01b8cd8a931fb2d8889473946d8a23af0c9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 20:29:21 +0400 Subject: [PATCH 108/472] Follow the change in the base-type of job_skill. --- library/modules/Units.cpp | 3 +-- library/xml | 2 +- plugins/add-spatter.cpp | 4 ++-- plugins/manipulator.cpp | 8 ++++---- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 6d71f5a15..7a0a7549b 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -889,8 +889,7 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust // Retrieve skill from unit soul: - df::enum_field key(skill_id); - auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id); if (skill) { diff --git a/library/xml b/library/xml index b9b2e8c6d..ee38c5446 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db +Subproject commit ee38c5446e527edcdc9d8c3e23a4ae138d213546 diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index 425ffe9d0..ca37c8ee3 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -246,7 +246,7 @@ struct product_hook : improvement_product { (df::unit *unit, std::vector *out_items, std::vector *in_reag, std::vector *in_items, - int32_t quantity, int16_t skill, + int32_t quantity, df::job_skill skill, df::historical_entity *entity, df::world_site *site) ) { if (auto product = products[this]) @@ -279,7 +279,7 @@ struct product_hook : improvement_product { break; } - int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0; + int rating = unit ? Units::getEffectiveSkill(unit, skill) : 0; int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary object->addContaminant( diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b3852437c..b6d30ab1e 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -298,8 +298,8 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) { - df::unit_skill *s1 = binsearch_in_vector>(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); - df::unit_skill *s2 = binsearch_in_vector>(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s1 = binsearch_in_vector(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); + df::unit_skill *s2 = binsearch_in_vector(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); int l1 = s1 ? s1->rating : 0; int l2 = s2 ? s2->rating : 0; int e1 = s1 ? s1->experience : 0; @@ -1030,7 +1030,7 @@ void viewscreen_unitlaborsst::render() fg = 9; if (columns[col_offset].skill != job_skill::NONE) { - df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); + df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { int level = skill->rating; @@ -1086,7 +1086,7 @@ void viewscreen_unitlaborsst::render() } else { - df::unit_skill *skill = binsearch_in_vector>(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); + df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { int level = skill->rating; From c5d8bd9db6f56fb133779425f801107a15314eb9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 23:30:44 +0400 Subject: [PATCH 109/472] Add a tweak to stop stockpiling items on weapon racks and armor stands. --- NEWS | 2 + plugins/tweak.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/NEWS b/NEWS index fc9266401..f7ccc711d 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New tweaks: + - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index b4af2a4ba..b6ef68f2f 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -47,6 +47,15 @@ #include "df/viewscreen_layer_assigntradest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" +#include "df/squad_position.h" +#include "df/item_weaponst.h" +#include "df/item_armorst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shoesst.h" +#include "df/item_glovesst.h" +#include "df/item_shieldst.h" +#include "df/building_armorstandst.h" #include @@ -124,6 +133,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad()); + if (!squad) + return false; + + if (any_position) + { + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) + return true; + } + } + else + { + auto cpos = vector_get(squad->positions, holder->getSpecificPosition()); + if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) + return true; + } + + return false; +} + +static bool is_in_armory(df::item *item, df::building_type btype, bool any_position) +{ + if (item->flags.bits.in_inventory || item->flags.bits.on_ground) + return false; + + auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + if (!holder_ref) + return false; + + auto holder = holder_ref->getBuilding(); + if (!holder || holder->getType() != btype) + return false; + + return belongs_to_position(item, holder, any_position); +} + +struct armory_weapon_hook : df::item_weaponst { + typedef df::item_weaponst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this, building_type::Weaponrack, true)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(armory_weapon_hook, isCollected); + +template struct armory_hook : Item { + typedef Item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this, building_type::Armorstand, false)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); + +/*struct armory_armorstand_hook : df::building_armorstandst { + typedef df::building_armorstandst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, canStoreItem, (df::item *item, bool subtract_jobs)) + { + if (specific_squad >= 0 && specific_position >= 0) + return item->isArmorNotClothing() && belongs_to_position(item, this, false); + + return INTERPOSE_NEXT(canStoreItem)(item, subtract_jobs); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(armory_armorstand_hook, canStoreItem);*/ + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -831,6 +930,17 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } + else if (cmd == "armory") + { + enable_hook(out, INTERPOSE_HOOK(armory_weapon_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + //enable_hook(out, INTERPOSE_HOOK(armory_armorstand_hook, canStoreItem), parameters); + } else return CR_WRONG_USAGE; From 4c2c6a19115e98d22dcddcf640afb2e9100083d3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 26 Oct 2012 23:53:18 +0400 Subject: [PATCH 110/472] Fix handling of Collect Webs jobs in workflow. --- NEWS | 1 + library/xml | 2 +- plugins/workflow.cpp | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f7ccc711d..6b87a52b8 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ DFHack future Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. + - logic fix: collecting webs produces silk, and ungathered webs are not thread. DFHack v0.34.11-r2 diff --git a/library/xml b/library/xml index ee38c5446..4a6903dc9 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ee38c5446e527edcdc9d8c3e23a4ae138d213546 +Subproject commit 4a6903dc9b8d4cd21c5c5d74df4e9f74a4dd58dd diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index ca8388413..d46daed44 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1055,6 +1055,8 @@ static void map_job_items(color_ostream &out) break; case item_type::THREAD: + if (item->flags.bits.spider_web) + continue; if (item->getTotalDimension() < 15000) is_invalid = true; break; From 34f33a8c91b0782f6d8dc43de09a8318141b8dca Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 15:35:11 +0400 Subject: [PATCH 111/472] Fix the error message produced by binpatch when a mismatch is detected. --- library/binpatch.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/library/binpatch.cpp b/library/binpatch.cpp index 815ac5b92..60e2c4ca7 100644 --- a/library/binpatch.cpp +++ b/library/binpatch.cpp @@ -171,8 +171,9 @@ BinaryPatch::State BinaryPatch::checkState(const patch_byte *ptr, size_t len) state |= Applied; else { - cerr << std::hex << bv.offset << ": " << bv.old_val << " " << bv.new_val - << ", but currently " << cv << std::dec << endl; + cerr << std::hex << bv.offset << ": " + << unsigned(bv.old_val) << " " << unsigned(bv.new_val) + << ", but currently " << unsigned(cv) << std::dec << endl; return Conflict; } } From e353f5f03ea9f68be9b5947e29f939dd3e3193f3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 20:16:27 +0400 Subject: [PATCH 112/472] Add a script to complement the weapon rack binary patch. --- NEWS | 1 + dfhack.init-example | 6 ++ library/lua/class.lua | 10 +- library/lua/gui/widgets.lua | 17 +-- scripts/gui/assign-rack.lua | 202 ++++++++++++++++++++++++++++++++++++ 5 files changed, 226 insertions(+), 10 deletions(-) create mode 100644 scripts/gui/assign-rack.lua diff --git a/NEWS b/NEWS index 6b87a52b8..5103cf921 100644 --- a/NEWS +++ b/NEWS @@ -16,6 +16,7 @@ DFHack future - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workflow: a front-end for the workflow plugin. + - gui/assign-rack: works together with a binary patch to fix weapon racks. Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. diff --git a/dfhack.init-example b/dfhack.init-example index 2cdb114be..7dfa86016 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -81,6 +81,9 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +# assign weapon racks to squads so that they can be used +keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack + ############################ # UI and game logic tweaks # ############################ @@ -118,3 +121,6 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned + +# stop squad equpment stored on weapon racks and armor stands from being stockpiled +tweak armory diff --git a/library/lua/class.lua b/library/lua/class.lua index bcfff13e2..e18bad9a9 100644 --- a/library/lua/class.lua +++ b/library/lua/class.lua @@ -65,10 +65,14 @@ end local function apply_attrs(obj, attrs, init_table) for k,v in pairs(attrs) do - if v == DEFAULT_NIL then - v = nil + local init_v = init_table[k] + if init_v ~= nil then + obj[k] = init_v + elseif v == DEFAULT_NIL then + obj[k] = nil + else + obj[k] = v end - obj[k] = init_table[k] or v end end diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e526408fb..e731af068 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -501,14 +501,17 @@ function List:onRenderBody(dc) local y = (i - top)*self.row_height if iw and obj.icon then - dc:seek(0, y) - if type(obj.icon) == 'table' then - dc:char(nil,obj.icon) - else - if current then - dc:string(obj.icon, obj.icon_pen or cur_pen) + local icon = getval(obj.icon) + if icon then + dc:seek(0, y) + if type(icon) == 'table' then + dc:char(nil,icon) else - dc:string(obj.icon, obj.icon_pen or cur_dpen) + if current then + dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) + else + dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen) + end end end end diff --git a/scripts/gui/assign-rack.lua b/scripts/gui/assign-rack.lua new file mode 100644 index 000000000..d358dfff1 --- /dev/null +++ b/scripts/gui/assign-rack.lua @@ -0,0 +1,202 @@ +-- Assign weapon racks to squads. Requires patch from bug 1445. + +--[[ + + Required patches: + + v0.34.11 linux: http://pastebin.com/mt5EUgFZ + v0.34.11 windows: http://pastebin.com/09nRCybe + +]] + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local widgets = require 'gui.widgets' +local dlg = require 'gui.dialogs' + +AssignRack = defclass(AssignRack, guidm.MenuOverlay) + +AssignRack.focus_path = 'assign-rack' + +AssignRack.ATTRS { + building = DEFAULT_NIL, + frame_inset = 1, + frame_background = COLOR_BLACK, +} + +function list_squads(building,squad_table,squad_list) + local sqlist = building:getSquads() + if not sqlist then + return + end + + for i,v in ipairs(sqlist) do + local obj = df.squad.find(v.squad_id) + if obj then + if not squad_table[v.squad_id] then + squad_table[v.squad_id] = { id = v.squad_id, obj = obj } + table.insert(squad_list, squad_table[v.squad_id]) + end + + -- Set specific use flags + for n,ok in pairs(v.mode) do + if ok then + squad_table[v.squad_id][n] = true + end + end + + -- Check if any use is possible + local btype = building:getType() + if btype == df.building_type.Bed then + if v.mode.sleep then + squad_table[v.squad_id].any = true + end + elseif btype == df.building.Weaponrack then + if v.mode.train or v.mode.indiv_eq then + squad_table[v.squad_id].any = true + end + else + if v.mode.indiv_eq then + squad_table[v.squad_id].any = true + end + end + end + end + + for i,v in ipairs(building.parents) do + list_squads(v, squad_table, squad_list) + end +end + +function filter_invalid(list, id) + for i=#list-1,0,-1 do + local bld = df.building.find(list[i]) + if not bld or bld:getSpecificSquad() ~= id then + list:erase(i) + end + end +end + +function AssignRack:init(args) + self.squad_table = {} + self.squad_list = {} + list_squads(self.building, self.squad_table, self.squad_list) + table.sort(self.squad_list, function(a,b) return a.id < b.id end) + + self.choices = {} + for i,v in ipairs(self.squad_list) do + if v.any and (v.train or v.indiv_eq) then + local name = v.obj.alias + if name == '' then + name = dfhack.TranslateName(v.obj.name, true) + end + + filter_invalid(v.obj.rack_combat, v.id) + filter_invalid(v.obj.rack_training, v.id) + + table.insert(self.choices, { + icon = self:callback('isSelected', v), + icon_pen = COLOR_LIGHTGREEN, + obj = v, + text = { + name, NEWLINE, ' ', + { text = function() + return string.format('%d combat, %d training', #v.obj.rack_combat, #v.obj.rack_training) + end } + } + }) + end + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = { + 'Assign Weapon Rack' + } + }, + widgets.List{ + view_id = 'list', + frame = { t = 2, b = 2 }, + icon_width = 2, row_height = 2, + scroll_keys = widgets.SECONDSCROLL, + choices = self.choices, + on_submit = self:callback('onSubmit'), + }, + widgets.Label{ + frame = { l = 0, t = 2 }, + text_pen = COLOR_LIGHTRED, + text = 'No appropriate barracks\n\nNote: weapon racks use the\nIndividual equipment flag', + visible = (#self.choices == 0), + }, + widgets.Label{ + frame = { l = 0, b = 0 }, + text = { + { key = 'LEAVESCREEN', text = ': Back', + on_activate = self:callback('dismiss') } + } + }, + } +end + +function AssignRack:isSelected(info) + if self.building.specific_squad == info.id then + return '\xfb' + else + return nil + end +end + +function AssignRack:onSubmit(idx, choice) + local rid = self.building.id + local curid = self.building.specific_squad + + local cur = df.squad.find(curid) + if cur then + utils.erase_sorted(cur.rack_combat, rid) + utils.erase_sorted(cur.rack_training, rid) + end + + self.building.specific_squad = -1 + df.global.ui.equipment.update.buildings = true + + local new = df.squad.find(choice.obj.id) + if new and choice.obj.id ~= curid then + self.building.specific_squad = choice.obj.id + + if choice.obj.indiv_eq then + utils.insert_sorted(new.rack_combat, rid) + end + if choice.obj.train then + utils.insert_sorted(new.rack_training, rid) + end + end +end + +function AssignRack:onInput(keys) + if self:propagateMoveKeys(keys) then + if df.global.world.selected_building ~= self.building then + self:dismiss() + end + else + AssignRack.super.onInput(self, keys) + end +end + +if dfhack.gui.getCurFocus() ~= 'dwarfmode/QueryBuilding/Some/Weaponrack' then + qerror("This script requires a weapon rack selected in the 'q' mode") +end + +AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show() + +if not already_warned then + already_warned = true + dlg.showMessage( + 'BUG ALERT', + { 'This script requires a binary patch from', NEWLINE, + 'bug 1445 on the tracker. Otherwise the game', NEWLINE, + 'will lose your settings due to a bug.' }, + COLOR_YELLOW + ) +end From 92a32777774e2c96b3ce312056539138544a47b1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 27 Oct 2012 21:58:40 +0400 Subject: [PATCH 113/472] Add a MemoryPatcher class as an optimization of scattered patchMemory. This class can cache the set of memory regions during its lifetime, and make them writable only once. This avoids e.g. re-reading /proc/*/maps once for every modified vtable in interpose code. --- library/Core.cpp | 65 +++++++++++++++++++++++++++++-------- library/VTableInterpose.cpp | 20 ++++++++---- library/include/DataDefs.h | 3 +- library/include/MemAccess.h | 17 ++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 7766b3591..dc620e805 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1615,15 +1615,27 @@ void ClassNameCheck::getKnownClassNames(std::vector &names) names.push_back(*it); } -bool Process::patchMemory(void *target, const void* src, size_t count) +MemoryPatcher::MemoryPatcher(Process *p) : p(p) +{ + if (!p) + p = Core::getInstance().p; +} + +MemoryPatcher::~MemoryPatcher() +{ + close(); +} + +bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) { uint8_t *sptr = (uint8_t*)target; uint8_t *eptr = sptr + count; // Find the valid memory ranges - std::vector ranges; - getMemRanges(ranges); + if (ranges.empty()) + p->getMemRanges(ranges); + // Find the ranges that this area spans unsigned start = 0; while (start < ranges.size() && ranges[start].end <= sptr) start++; @@ -1645,24 +1657,49 @@ bool Process::patchMemory(void *target, const void* src, size_t count) if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) return false; - // Apply writable permissions & update - bool ok = true; + if (!write) + return true; - for (unsigned i = start; i < end && ok; i++) + // Apply writable permissions & update + for (unsigned i = start; i < end; i++) { - t_memrange perms = ranges[i]; + auto &perms = ranges[i]; + if (perms.write && perms.read) + continue; + + save.push_back(perms); perms.write = perms.read = true; - if (!setPermisions(perms, perms)) - ok = false; + if (!p->setPermisions(perms, perms)) + return false; } - if (ok) - memmove(target, src, count); + return true; +} + +bool MemoryPatcher::write(void *target, const void *src, size_t size) +{ + if (!makeWritable(target, size)) + return false; + + memmove(target, src, size); + return true; +} - for (unsigned i = start; i < end && ok; i++) - setPermisions(ranges[i], ranges[i]); +void MemoryPatcher::close() +{ + for (size_t i = 0; i < save.size(); i++) + p->setPermisions(save[i], save[i]); + + save.clear(); + ranges.clear(); +}; + + +bool Process::patchMemory(void *target, const void* src, size_t count) +{ + MemoryPatcher patcher(this); - return ok; + return patcher.write(target, src, count); } /******************************************************************************* diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index d8a07e830..3f9423b45 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -166,12 +166,12 @@ void *virtual_identity::get_vmethod_ptr(int idx) return vtable[idx]; } -bool virtual_identity::set_vmethod_ptr(int idx, void *ptr) +bool virtual_identity::set_vmethod_ptr(MemoryPatcher &patcher, int idx, void *ptr) { assert(idx >= 0); void **vtable = (void**)vtable_ptr; if (!vtable) return NULL; - return Core::getInstance().p->patchMemory(&vtable[idx], &ptr, sizeof(void*)); + return patcher.write(&vtable[idx], &ptr, sizeof(void*)); } /* @@ -344,7 +344,9 @@ void VMethodInterposeLinkBase::on_host_delete(virtual_identity *from) auto last = this; while (last->prev) last = last->prev; - from->set_vmethod_ptr(vmethod_idx, last->saved_chain); + MemoryPatcher patcher; + + from->set_vmethod_ptr(patcher, vmethod_idx, last->saved_chain); // Unlink the chains child_hosts.erase(from); @@ -379,13 +381,15 @@ bool VMethodInterposeLinkBase::apply(bool enable) assert(old_ptr != NULL && (!old_link || old_link->interpose_method == old_ptr)); // Apply the new method ptr + MemoryPatcher patcher; + set_chain(old_ptr); if (next_link) { next_link->set_chain(interpose_method); } - else if (!host->set_vmethod_ptr(vmethod_idx, interpose_method)) + else if (!host->set_vmethod_ptr(patcher, vmethod_idx, interpose_method)) { set_chain(NULL); return false; @@ -459,7 +463,7 @@ bool VMethodInterposeLinkBase::apply(bool enable) { auto nhost = *it; assert(nhost->interpose_list[vmethod_idx] == old_link); - nhost->set_vmethod_ptr(vmethod_idx, interpose_method); + nhost->set_vmethod_ptr(patcher, vmethod_idx, interpose_method); nhost->interpose_list[vmethod_idx] = this; } @@ -496,9 +500,11 @@ void VMethodInterposeLinkBase::remove() } else { + MemoryPatcher patcher; + // Remove from the list in the identity and vtable host->interpose_list[vmethod_idx] = prev; - host->set_vmethod_ptr(vmethod_idx, saved_chain); + host->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); for (auto it = child_next.begin(); it != child_next.end(); ++it) { @@ -515,7 +521,7 @@ void VMethodInterposeLinkBase::remove() auto nhost = *it; assert(nhost->interpose_list[vmethod_idx] == this); nhost->interpose_list[vmethod_idx] = prev; - nhost->set_vmethod_ptr(vmethod_idx, saved_chain); + nhost->set_vmethod_ptr(patcher, vmethod_idx, saved_chain); if (prev) prev->child_hosts.insert(nhost); } diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 6c5ae5e62..32ffabc32 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -294,6 +294,7 @@ namespace DFHack #endif class DFHACK_EXPORT VMethodInterposeLinkBase; + class MemoryPatcher; class DFHACK_EXPORT virtual_identity : public struct_identity { static std::map known; @@ -313,7 +314,7 @@ namespace DFHack bool can_allocate() { return struct_identity::can_allocate() && (vtable_ptr != NULL); } void *get_vmethod_ptr(int index); - bool set_vmethod_ptr(int index, void *ptr); + bool set_vmethod_ptr(MemoryPatcher &patcher, int index, void *ptr); public: virtual_identity(size_t size, TAllocateFn alloc, diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 0bc027c5c..1b8e687b9 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -315,5 +315,22 @@ namespace DFHack // Get list of names given to ClassNameCheck constructors. static void getKnownClassNames(std::vector &names); }; + + class DFHACK_EXPORT MemoryPatcher + { + Process *p; + std::vector ranges, save; + public: + MemoryPatcher(Process *p = NULL); + ~MemoryPatcher(); + + bool verifyAccess(void *target, size_t size, bool write = false); + bool makeWritable(void *target, size_t size) { + return verifyAccess(target, size, true); + } + bool write(void *target, const void *src, size_t size); + + void close(); + }; } #endif From fdaa2a35a1d3f30ba5b58372536334b7bcc52c2f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 09:34:50 +0400 Subject: [PATCH 114/472] Fix name ambiguity in MemoryPatcher constructor. --- library/Core.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/Core.cpp b/library/Core.cpp index dc620e805..9986c3396 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1615,7 +1615,7 @@ void ClassNameCheck::getKnownClassNames(std::vector &names) names.push_back(*it); } -MemoryPatcher::MemoryPatcher(Process *p) : p(p) +MemoryPatcher::MemoryPatcher(Process *p_) : p(p_) { if (!p) p = Core::getInstance().p; From 4aa1999347f85d83dc27d7ef1e6e4cdd6e07561e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 11:50:28 +0400 Subject: [PATCH 115/472] Add a lua api function for patching multiple individual bytes. --- Lua API.html | 9 ++++++ Lua API.rst | 12 +++++++ library/Core.cpp | 5 +-- library/LuaApi.cpp | 76 ++++++++++++++++++++++++++++++++++++++++++++ library/LuaTools.cpp | 5 ++- 5 files changed, 102 insertions(+), 5 deletions(-) diff --git a/Lua API.html b/Lua API.html index 9c18b6585..be52ea985 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1714,6 +1714,15 @@ global environment, persistent between calls to the script.

                                              If destination overlaps a completely invalid memory region, or another error occurs, returns false.

                                              +
                                            • dfhack.internal.patchBytes(write_table[, verify_table])

                                              +

                                              The first argument must be a lua table, which is interpreted as a mapping from +memory addresses to byte values that should be stored there. The second argument +may be a similar table of values that need to be checked before writing anything.

                                              +

                                              The function takes care to either apply all of write_table, or none of it. +An empty write_table with a nonempty verify_table can be used to reasonably +safely check if the memory contains certain values.

                                              +

                                              Returns true if successful, or nil, error_msg, address if not.

                                              +
                                            • dfhack.internal.memmove(dest,src,count)

                                              Wraps the standard memmove function. Accepts both numbers and refs as pointers.

                                            • diff --git a/Lua API.rst b/Lua API.rst index 185672816..8dad3663b 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1581,6 +1581,18 @@ and are only documented here for completeness: If destination overlaps a completely invalid memory region, or another error occurs, returns false. +* ``dfhack.internal.patchBytes(write_table[, verify_table])`` + + The first argument must be a lua table, which is interpreted as a mapping from + memory addresses to byte values that should be stored there. The second argument + may be a similar table of values that need to be checked before writing anything. + + The function takes care to either apply all of ``write_table``, or none of it. + An empty ``write_table`` with a nonempty ``verify_table`` can be used to reasonably + safely check if the memory contains certain values. + + Returns *true* if successful, or *nil, error_msg, address* if not. + * ``dfhack.internal.memmove(dest,src,count)`` Wraps the standard memmove function. Accepts both numbers and refs as pointers. diff --git a/library/Core.cpp b/library/Core.cpp index 9986c3396..111774f26 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -1657,14 +1657,11 @@ bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) if (!ranges[i].valid || !(ranges[i].read || ranges[i].execute) || ranges[i].shared) return false; - if (!write) - return true; - // Apply writable permissions & update for (unsigned i = start; i < end; i++) { auto &perms = ranges[i]; - if (perms.write && perms.read) + if ((perms.write || !write) && perms.read) continue; save.push_back(perms); diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 593ac3d8d..6ca1188d9 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1535,6 +1535,81 @@ static int internal_patchMemory(lua_State *L) return 1; } +static int internal_patchBytes(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TTABLE); + lua_settop(L, 2); + + MemoryPatcher patcher; + + if (!lua_isnil(L, 2)) + { + luaL_checktype(L, 2, LUA_TTABLE); + + lua_pushnil(L); + + while (lua_next(L, 2)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + int isnum; + uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "invalid value in verify table"); + lua_pop(L, 1); + + if (!patcher.verifyAccess(addr, 1, false)) + { + lua_pushnil(L); + lua_pushstring(L, "invalid verify address"); + lua_pushvalue(L, -3); + return 3; + } + + if (*addr != value) + { + lua_pushnil(L); + lua_pushstring(L, "wrong verify value"); + lua_pushvalue(L, -3); + return 3; + } + } + } + + lua_pushnil(L); + + while (lua_next(L, 1)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + int isnum; + uint8_t value = (uint8_t)lua_tounsignedx(L, -1, &isnum); + if (!isnum) + luaL_error(L, "invalid value in write table"); + lua_pop(L, 1); + + if (!patcher.verifyAccess(addr, 1, true)) + { + lua_pushnil(L); + lua_pushstring(L, "invalid write address"); + lua_pushvalue(L, -3); + return 3; + } + } + + lua_pushnil(L); + + while (lua_next(L, 1)) + { + uint8_t *addr = (uint8_t*)checkaddr(L, -2, true); + uint8_t value = (uint8_t)lua_tounsigned(L, -1); + lua_pop(L, 1); + + *addr = value; + } + + lua_pushboolean(L, true); + return 1; +} + static int internal_memmove(lua_State *L) { void *dest = checkaddr(L, 1); @@ -1626,6 +1701,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getVTable", internal_getVTable }, { "getMemRanges", internal_getMemRanges }, { "patchMemory", internal_patchMemory }, + { "patchBytes", internal_patchBytes }, { "memmove", internal_memmove }, { "memcmp", internal_memcmp }, { "memscan", internal_memscan }, diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 6bf218ba0..c052b88aa 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -107,7 +107,8 @@ static void signal_typeid_error(color_ostream *out, lua_State *state, type_identity *type, const char *msg, int val_index, bool perr, bool signal) { - std::string error = stl_sprintf(msg, type->getFullName().c_str()); + std::string typestr = type ? type->getFullName() : "any pointer"; + std::string error = stl_sprintf(msg, typestr.c_str()); if (signal) { @@ -134,6 +135,8 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_ if (lua_isnil(state, val_index)) return NULL; + if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index)) + return NULL; void *rv = get_object_internal(state, type, val_index, exact_type, false); From fa88ee5f171159e0518e73dea2c351a67fec5a08 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 15:37:16 +0400 Subject: [PATCH 116/472] Update the armory tweak to protect other potential squad equipment. Specifically clothing in cabinets and flasks/backpacks/quivers in boxes. --- plugins/tweak.cpp | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index b6ef68f2f..dd504fee3 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -55,6 +55,9 @@ #include "df/item_shoesst.h" #include "df/item_glovesst.h" #include "df/item_shieldst.h" +#include "df/item_flaskst.h" +#include "df/item_backpackst.h" +#include "df/item_quiverst.h" #include "df/building_armorstandst.h" #include @@ -672,7 +675,11 @@ IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); static bool belongs_to_position(df::item *item, df::building *holder, bool any_position) { - auto squad = df::squad::find(holder->getSpecificSquad()); + int sid = holder->getSpecificSquad(); + if (sid < 0) + return false; + + auto squad = df::squad::find(sid); if (!squad) return false; @@ -694,7 +701,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p return false; } -static bool is_in_armory(df::item *item, df::building_type btype, bool any_position) +static bool is_in_armory(df::item *item, bool any_position) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; @@ -704,7 +711,7 @@ static bool is_in_armory(df::item *item, df::building_type btype, bool any_posit return false; auto holder = holder_ref->getBuilding(); - if (!holder || holder->getType() != btype) + if (!holder) return false; return belongs_to_position(item, holder, any_position); @@ -715,7 +722,7 @@ struct armory_weapon_hook : df::item_weaponst { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, building_type::Weaponrack, true)) + if (is_in_armory(this, true)) return false; return INTERPOSE_NEXT(isCollected)(); @@ -729,7 +736,7 @@ template struct armory_hook : Item { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, building_type::Armorstand, false)) + if (is_in_armory(this, false)) return false; return INTERPOSE_NEXT(isCollected)(); @@ -742,20 +749,9 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -/*struct armory_armorstand_hook : df::building_armorstandst { - typedef df::building_armorstandst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, canStoreItem, (df::item *item, bool subtract_jobs)) - { - if (specific_squad >= 0 && specific_position >= 0) - return item->isArmorNotClothing() && belongs_to_position(item, this, false); - - return INTERPOSE_NEXT(canStoreItem)(item, subtract_jobs); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(armory_armorstand_hook, canStoreItem);*/ +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { @@ -939,7 +935,9 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - //enable_hook(out, INTERPOSE_HOOK(armory_armorstand_hook, canStoreItem), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); } else return CR_WRONG_USAGE; From ed4904fb6641e4cd23dba11ad7221554a0bf2826 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 21:13:28 +0400 Subject: [PATCH 117/472] Add a tweak that makes dwarves haul equipment from stockpiles to the armory. --- NEWS | 1 + dfhack.init-example | 1 + library/xml | 2 +- plugins/tweak.cpp | 152 ++++++++++++++++++++++++++++++++++++++------ 4 files changed, 134 insertions(+), 22 deletions(-) diff --git a/NEWS b/NEWS index 5103cf921..c8a0f5770 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option New tweaks: - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. + - tweak stock-armory: makes dwarves actively haul equipment from stockpiles to the armory. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 7dfa86016..2b7ac2cc9 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -124,3 +124,4 @@ tweak military-color-assigned # stop squad equpment stored on weapon racks and armor stands from being stockpiled tweak armory +tweak stock-armory diff --git a/library/xml b/library/xml index 4a6903dc9..4bfc5b197 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4a6903dc9b8d4cd21c5c5d74df4e9f74a4dd58dd +Subproject commit 4bfc5b197de9ca096a11b9c1594e043bd8ae707a diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index dd504fee3..9dc3ae468 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -10,6 +10,7 @@ #include "modules/Screen.h" #include "modules/Units.h" #include "modules/Items.h" +#include "modules/Job.h" #include "MiscUtils.h" @@ -58,7 +59,12 @@ #include "df/item_flaskst.h" #include "df/item_backpackst.h" #include "df/item_quiverst.h" +#include "df/building_weaponrackst.h" #include "df/building_armorstandst.h" +#include "df/building_cabinetst.h" +#include "df/building_boxst.h" +#include "df/job.h" +#include "df/general_ref_building_holderst.h" #include @@ -139,6 +145,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); if (sid < 0) @@ -683,7 +691,9 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p if (!squad) return false; - if (any_position) + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) { @@ -693,7 +703,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p } else { - auto cpos = vector_get(squad->positions, holder->getSpecificPosition()); + auto cpos = vector_get(squad->positions, position); if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) return true; } @@ -701,7 +711,7 @@ static bool belongs_to_position(df::item *item, df::building *holder, bool any_p return false; } -static bool is_in_armory(df::item *item, bool any_position) +static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; @@ -714,35 +724,22 @@ static bool is_in_armory(df::item *item, bool any_position) if (!holder) return false; - return belongs_to_position(item, holder, any_position); + return belongs_to_position(item, holder); } -struct armory_weapon_hook : df::item_weaponst { - typedef df::item_weaponst interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - if (is_in_armory(this, true)) - return false; - - return INTERPOSE_NEXT(isCollected)(); - } -}; - -IMPLEMENT_VMETHOD_INTERPOSE(armory_weapon_hook, isCollected); - template struct armory_hook : Item { typedef Item interpose_base; DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - if (is_in_armory(this, false)) + if (is_in_armory(this)) return false; return INTERPOSE_NEXT(isCollected)(); } }; +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -753,6 +750,112 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +static void check_stock_armory(df::building *target, int squad_idx) +{ + auto squad = df::squad::find(squad_idx); + if (!squad) + return; + + int position = target->getSpecificPosition(); + + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (position >= 0 && position != int(i)) + continue; + + auto pos = squad->positions[i]; + + for (size_t j = 0; j < pos->assigned_items.size(); j++) + { + auto item = df::item::find(pos->assigned_items[j]); + if (!item || item->stockpile_countdown > 0) + continue; + + if (item->flags.bits.in_job || + item->flags.bits.removed || + item->flags.bits.in_building || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + continue; + + auto top = item; + + while (top->flags.bits.in_inventory) + { + auto parent = Items::getContainer(top); + if (!parent) break; + top = parent; + } + + if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) + continue; + + if (is_in_armory(item)) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + continue; + + auto job = new df::job(); + + job->pos = df::coord(target->centerx, target->centery, target->z); + + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + continue; + } + + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + Job::linkIntoWorld(job); + } + } +} + +template struct stock_armory_hook : Building { + typedef Building interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (this->specific_squad >= 0 && DF_GLOBAL_VALUE(cur_year_tick,0) % 50 == 0) + check_stock_armory(this, this->specific_squad); + + INTERPOSE_NEXT(updateAction)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -928,7 +1031,7 @@ static command_result tweak(color_ostream &out, vector ¶meters) } else if (cmd == "armory") { - enable_hook(out, INTERPOSE_HOOK(armory_weapon_hook, isCollected), parameters); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); @@ -939,6 +1042,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); } + else if (cmd == "stock-armory") + { + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); + } else return CR_WRONG_USAGE; From 7219200d173e922d7e2ef42eb6c3fd8ac159be53 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 28 Oct 2012 22:05:00 +0400 Subject: [PATCH 118/472] Split the armory tweak into a separate plugin - it is too big now. --- NEWS | 5 +- dfhack.init-example | 4 - plugins/CMakeLists.txt | 1 + plugins/fix-armory.cpp | 376 +++++++++++++++++++++++++++++++++++++++++ plugins/tweak.cpp | 214 ----------------------- 5 files changed, 379 insertions(+), 221 deletions(-) create mode 100644 plugins/fix-armory.cpp diff --git a/NEWS b/NEWS index c8a0f5770..810f0cd55 100644 --- a/NEWS +++ b/NEWS @@ -10,9 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - New tweaks: - - tweak armory: prevents stockpiling of armor and weapons stored on stands and racks. - - tweak stock-armory: makes dwarves actively haul equipment from stockpiles to the armory. + New commands: + - fix-armory: activates a plugin that makes armor stands and weapon racks be used again. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 2b7ac2cc9..d7f3f5399 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -121,7 +121,3 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned - -# stop squad equpment stored on weapon racks and armor stands from being stockpiled -tweak armory -tweak stock-armory diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2f663f805..de1aa3441 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -124,6 +124,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(add-spatter add-spatter.cpp) + DFHACK_PLUGIN(fix-armory fix-armory.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp new file mode 100644 index 000000000..06adbbe4c --- /dev/null +++ b/plugins/fix-armory.cpp @@ -0,0 +1,376 @@ +// Fixes containers in barracks to actually work as intended. + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "modules/Units.h" +#include "modules/Items.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "MiscUtils.h" + +#include "DataDefs.h" +#include +#include "df/ui.h" +#include "df/world.h" +#include "df/squad.h" +#include "df/unit.h" +#include "df/squad_position.h" +#include "df/item_weaponst.h" +#include "df/item_armorst.h" +#include "df/item_helmst.h" +#include "df/item_pantsst.h" +#include "df/item_shoesst.h" +#include "df/item_glovesst.h" +#include "df/item_shieldst.h" +#include "df/item_flaskst.h" +#include "df/item_backpackst.h" +#include "df/item_quiverst.h" +#include "df/building_weaponrackst.h" +#include "df/building_armorstandst.h" +#include "df/building_cabinetst.h" +#include "df/building_boxst.h" +#include "df/job.h" +#include "df/general_ref_building_holderst.h" +#include "df/barrack_preference_category.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::ui; +using df::global::world; +using df::global::gamemode; +using df::global::ui_build_selector; +using df::global::ui_menu_width; +using df::global::ui_area_map_width; + +using namespace DFHack::Gui; +using Screen::Pen; + +static command_result fix_armory(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("fix-armory"); + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "fix-armory", "Enables or disables the fix-armory plugin.", fix_armory, false, + " fix-armory enable\n" + " Enables the tweaks.\n" + " fix-armory disable\n" + " Disables the tweaks. All equipment will be hauled off to stockpiles.\n" + )); + + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown (color_ostream &out) +{ + return CR_OK; +} + +static bool belongs_to_position(df::item *item, df::building *holder) +{ + int sid = holder->getSpecificSquad(); + if (sid < 0) + return false; + + auto squad = df::squad::find(sid); + if (!squad) + return false; + + int position = holder->getSpecificPosition(); + + if (position == -1 && holder->getType() == building_type::Weaponrack) + { + for (size_t i = 0; i < squad->positions.size(); i++) + { + if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) + return true; + } + } + else + { + auto cpos = vector_get(squad->positions, position); + if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) + return true; + } + + return false; +} + +static bool is_in_armory(df::item *item) +{ + if (item->flags.bits.in_inventory || item->flags.bits.on_ground) + return false; + + auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + if (!holder_ref) + return false; + + auto holder = holder_ref->getBuilding(); + if (!holder) + return false; + + return belongs_to_position(item, holder); +} + +template struct armory_hook : Item { + typedef Item interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) + { + if (is_in_armory(this)) + return false; + + return INTERPOSE_NEXT(isCollected)(); + } +}; + +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); + +static bool can_store_item(df::item *item) +{ + if (!item || item->stockpile_countdown > 0) + return false; + + if (item->flags.bits.in_job || + item->flags.bits.removed || + item->flags.bits.in_building || + item->flags.bits.encased || + item->flags.bits.owned || + item->flags.bits.forbid || + item->flags.bits.on_fire) + return false; + + auto top = item; + + while (top->flags.bits.in_inventory) + { + auto parent = Items::getContainer(top); + if (!parent) break; + top = parent; + } + + if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) + return false; + + if (is_in_armory(item)) + return false; + + return true; +} + +static void try_store_item(std::vector &vec, df::item *item) +{ + for (size_t i = 0; i < vec.size(); i++) + { + auto target = df::building::find(vec[i]); + if (!target) + continue; + + if (!target->canStoreItem(item, true)) + continue; + + auto href = df::allocate(); + if (!href) + return; + + auto job = new df::job(); + + job->pos = df::coord(target->centerx, target->centery, target->z); + + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + continue; + } + + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + Job::linkIntoWorld(job); + return; + } +} + +static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) +{ + for (size_t j = 0; j < items.size(); j++) + { + auto item = df::item::find(items[j]); + + if (!can_store_item(item)) + continue; + + if (item->isWeapon()) + try_store_item(squad->rack_combat, item); + else if (item->isClothing()) + try_store_item(pos->preferences[barrack_preference_category::Cabinet], item); + else if (item->isArmorNotClothing()) + try_store_item(pos->preferences[barrack_preference_category::Armorstand], item); + else + try_store_item(pos->preferences[barrack_preference_category::Box], item); + } +} + +static bool is_enabled = false; + +DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) +{ + if (!is_enabled) + return CR_OK; + + if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) + return CR_OK; + + auto &squads = df::global::world->squads.all; + + for (size_t si = 0; si < squads.size(); si++) + { + auto squad = squads[si]; + + for (size_t i = 0; i < squad->positions.size(); i++) + { + auto pos = squad->positions[i]; + + try_store_item_set(pos->assigned_items, squad, pos); + } + } + + return CR_OK; +} + +static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, bool enable) +{ + if (!hook.apply(enable)) + out.printerr("Could not %s hook.\n", enable?"activate":"deactivate"); +} + +static void enable_hooks(color_ostream &out, bool enable) +{ + is_enabled = enable; + + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); +} + +static void enable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled", NULL); + if (!entry.isValid()) + { + out.printerr("Could not save the status.\n"); + return; + } + + enable_hooks(out, true); +} + +static void disable_plugin(color_ostream &out) +{ + auto entry = World::GetPersistentData("fix-armory/enabled"); + World::DeletePersistentData(entry); + + enable_hooks(out, false); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + if (!gamemode || *gamemode == game_mode::DWARF) + { + bool enable = World::GetPersistentData("fix-armory/enabled").isValid(); + + if (enable) + { + out.print("Enabling the fix-armory plugin.\n"); + enable_hooks(out, true); + } + else + enable_hooks(out, false); + } + break; + case SC_MAP_UNLOADED: + enable_hooks(out, false); + default: + break; + } + + return CR_OK; +} + +static command_result fix_armory(color_ostream &out, vector ¶meters) +{ + CoreSuspender suspend; + + if (parameters.empty()) + return CR_WRONG_USAGE; + + string cmd = parameters[0]; + + if (cmd == "enable") + { + enable_plugin(out); + } + else if (cmd == "disable") + { + disable_plugin(out); + } + else + return CR_WRONG_USAGE; + + return CR_OK; +} diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 9dc3ae468..93e5ab3bc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -49,20 +49,6 @@ #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_layer_militaryst.h" #include "df/squad_position.h" -#include "df/item_weaponst.h" -#include "df/item_armorst.h" -#include "df/item_helmst.h" -#include "df/item_pantsst.h" -#include "df/item_shoesst.h" -#include "df/item_glovesst.h" -#include "df/item_shieldst.h" -#include "df/item_flaskst.h" -#include "df/item_backpackst.h" -#include "df/item_quiverst.h" -#include "df/building_weaponrackst.h" -#include "df/building_armorstandst.h" -#include "df/building_cabinetst.h" -#include "df/building_boxst.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" @@ -142,11 +128,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector getSpecificSquad(); - if (sid < 0) - return false; - - auto squad = df::squad::find(sid); - if (!squad) - return false; - - int position = holder->getSpecificPosition(); - - if (position == -1 && holder->getType() == building_type::Weaponrack) - { - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (binsearch_index(squad->positions[i]->assigned_items, item->id) >= 0) - return true; - } - } - else - { - auto cpos = vector_get(squad->positions, position); - if (cpos && binsearch_index(cpos->assigned_items, item->id) >= 0) - return true; - } - - return false; -} - -static bool is_in_armory(df::item *item) -{ - if (item->flags.bits.in_inventory || item->flags.bits.on_ground) - return false; - - auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); - if (!holder_ref) - return false; - - auto holder = holder_ref->getBuilding(); - if (!holder) - return false; - - return belongs_to_position(item, holder); -} - -template struct armory_hook : Item { - typedef Item interpose_base; - - DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) - { - if (is_in_armory(this)) - return false; - - return INTERPOSE_NEXT(isCollected)(); - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); -template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); - -static void check_stock_armory(df::building *target, int squad_idx) -{ - auto squad = df::squad::find(squad_idx); - if (!squad) - return; - - int position = target->getSpecificPosition(); - - for (size_t i = 0; i < squad->positions.size(); i++) - { - if (position >= 0 && position != int(i)) - continue; - - auto pos = squad->positions[i]; - - for (size_t j = 0; j < pos->assigned_items.size(); j++) - { - auto item = df::item::find(pos->assigned_items[j]); - if (!item || item->stockpile_countdown > 0) - continue; - - if (item->flags.bits.in_job || - item->flags.bits.removed || - item->flags.bits.in_building || - item->flags.bits.encased || - item->flags.bits.owned || - item->flags.bits.forbid || - item->flags.bits.on_fire) - continue; - - auto top = item; - - while (top->flags.bits.in_inventory) - { - auto parent = Items::getContainer(top); - if (!parent) break; - top = parent; - } - - if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) - continue; - - if (is_in_armory(item)) - continue; - - if (!target->canStoreItem(item, true)) - continue; - - auto href = df::allocate(); - if (!href) - continue; - - auto job = new df::job(); - - job->pos = df::coord(target->centerx, target->centery, target->z); - - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreItemInCabinet; - break; - default: - job->job_type = job_type::StoreItemInChest; - break; - } - - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - continue; - } - - href->building_id = target->id; - target->jobs.push_back(job); - job->references.push_back(href); - - Job::linkIntoWorld(job); - } - } -} - -template struct stock_armory_hook : Building { - typedef Building interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) - { - if (this->specific_squad >= 0 && DF_GLOBAL_VALUE(cur_year_tick,0) % 50 == 0) - check_stock_armory(this, this->specific_squad); - - INTERPOSE_NEXT(updateAction)(); - } -}; - -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stock_armory_hook, updateAction); - static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -1029,26 +835,6 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } - else if (cmd == "armory") - { - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), parameters); - } - else if (cmd == "stock-armory") - { - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - enable_hook(out, INTERPOSE_HOOK(stock_armory_hook, updateAction), parameters); - } else return CR_WRONG_USAGE; From abeb9d0f0c9a96606b9f76278dc66b0ed20a7ef7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 29 Oct 2012 20:32:39 +0400 Subject: [PATCH 119/472] Add documentation. --- Readme.html | 370 ++++++++++++++++++++++++++++++++++------------------ Readme.rst | 146 +++++++++++++++++++++ 2 files changed, 392 insertions(+), 124 deletions(-) diff --git a/Readme.html b/Readme.html index 6df1afcad..0fa1d10be 100644 --- a/Readme.html +++ b/Readme.html @@ -434,94 +434,99 @@ access DF memory and allow for easier development of new tools.

                                            • fixmerchants
                                            • fixveins
                                            • tweak
                                            • +
                                            • fix-armory
                                            • -
                                            • Mode switch and reclaim
                                            • @@ -1846,11 +1853,49 @@ to make them stand out more in the list.
                                              +
                                              +

                                              fix-armory

                                              +

                                              Enables a fix for storage of squad equipment in barracks.

                                              +

                                              Specifically, it prevents your haulers from moving that equipment +to stockpiles, and instead queues jobs to store it on weapon racks, +armor stands, and in containers.

                                              +
                                              +

                                              Note

                                              +

                                              In order to actually be used, weapon racks have to be patched and +assigned to a squad. See documentation for gui/assign-rack below.

                                              +

                                              Also, the default capacity of armor stands is way too low, so check out +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 +for a patch addressing that too.

                                              +
                                              +

                                              Note that the buildings in the armory are used as follows:

                                              +
                                                +
                                              • Weapon racks when fixed are used to store any assigned weapons. +Each rack belongs to a specific squad, and can store up to 5 weapons.
                                              • +
                                              • Armor stands belong to specific squad members and are used for +armor and shields. By default one stand can store one item of each +type (hence one boot or gauntlet); if patched, the limit is raised to 2, +which should be sufficient.
                                              • +
                                              • Cabinets are used to store assigned clothing for a specific squad member. +They are never used to store owned clothing.
                                              • +
                                              • Chests (boxes, etc) are used for a flask, backpack or quiver assigned +to the squad member. Due to a bug, food is dropped out of the backpack +when it is stored.
                                              • +
                                              +

                                              Contrary to the common misconception, all these uses are controlled by the +Individual Equipment usage flag; the Squad Equipment mode means nothing.

                                              +
                                              +

                                              Warning

                                              +

                                              Although armor stands, cabinets and chests properly belong only to one +squad member, the owner of the building used to create the barracks will +randomly use any containers inside the room. Thus, it is recommended to +always create the armory from a weapon rack.

                                              +
                                              +
                                              -

                                              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 @@ -1870,7 +1915,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.

                                              @@ -1890,9 +1935,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).

                                              @@ -1905,19 +1950,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:
                                              @@ -1936,7 +1981,7 @@ in a workshop, or the unit/jobs screen.
                                              -

                                              job-material

                                              +

                                              job-material

                                              Alter the material of the selected job.

                                              Invoked as:

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

                                              job-duplicate

                                              +

                                              job-duplicate

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

                                              workflow

                                              +

                                              workflow

                                              Manage control of repeat jobs.

                                              Usage:

                                              @@ -1989,7 +2034,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.

                                              @@ -1998,9 +2043,11 @@ produce that kind of item are automatically suspended and resumed as the item amount goes above or below the limit. The gap specifies how much below the limit the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled.

                                              +

                                              Check out the gui/workflow script below for a simple front-end integrated +in the game UI.

                                              -

                                              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
                                              @@ -2039,15 +2086,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:

                                              @@ -2146,7 +2193,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 @@ -2155,7 +2202,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. @@ -2173,14 +2220,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 @@ -2191,7 +2238,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 @@ -2217,7 +2264,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 @@ -2246,7 +2293,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 @@ -2354,7 +2401,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 @@ -2368,14 +2415,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:

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

                                              -

                                              misery

                                              +

                                              misery

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

                                              Usage:

                                              @@ -2421,7 +2468,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.

                                              @@ -2430,7 +2477,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

                                                @@ -2456,22 +2503,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 @@ -2479,7 +2526,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. @@ -2491,7 +2538,7 @@ growcrops plump 40

                                              -

                                              removebadthoughts

                                              +

                                              removebadthoughts

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

                                              The script can target a single creature, when used with the him argument, @@ -2505,7 +2552,7 @@ but in the short term your dwarves will get much more joyful.

                                              quickly after you unpause.

                                              -

                                              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.

                                              @@ -2531,7 +2578,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.

                                              @@ -2546,7 +2593,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:

                                              @@ -2564,7 +2611,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:

                                              @@ -2574,17 +2621,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.

                                              @@ -2595,7 +2642,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 @@ -2631,13 +2678,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.

                                              @@ -2646,7 +2693,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.

                                                @@ -2662,14 +2709,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 @@ -2678,9 +2725,84 @@ only that entry, and does it even if it is not 'individual choice'.

                                              Rationale: individual choice seems to be unreliable when there is a weapon shortage, and may lead to inappropriate weapons being selected.

                                              +
                                              +

                                              gui/guide-path

                                              +

                                              Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                              +

                                              The script displays the cached path that will be used by the order; the game +computes it when the order is executed for the first time.

                                              +
                                              +
                                              +

                                              gui/workshop-job

                                              +

                                              Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                              +

                                              The script shows a list of the input reagents of the selected job, and allows changing +them like the job item-type and job item-material commands.

                                              +

                                              Specifically, pressing the 'i' key pops up a dialog that lets you select an item +type from a list. Pressing 'm', unless the item type does not allow a material, +lets you choose a material.

                                              +
                                              +

                                              Warning

                                              +

                                              Due to the way input reagent matching works in DF, you must select an item type +if you select a material, or the material will be matched incorrectly in some cases. +If you press 'm' without choosing an item type, the script will auto-choose it +if there is only one valid choice, or pop up an error message box instead of the +material selection dialog.

                                              +
                                              +

                                              Note that both materials and item types presented in the dialogs are filtered +by the job input flags, and even the selected item type for material selection, +or material for item type selection. Many jobs would let you select only one +input item type.

                                              +

                                              For example, if you choose a plant input item type for your prepare meal job, +it will only let you select cookable materials.

                                              +

                                              If you choose a barrel item instead (meaning things stored in barrels, like +drink or milk), it will let you select any material, since in this case the +material is matched against the barrel itself. Then, if you select, say, iron, +and then try to change the input item type, now it won't let you select plant; +you have to unset the material first.

                                              +
                                              +
                                              +

                                              gui/workflow

                                              +

                                              Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                              +

                                              This script provides a simple interface to constraints managed by the workflow +plugin. When active, it displays a list of all constraints applicable to the +current job, and their current status.

                                              +

                                              A constraint specifies a certain range to be compared against either individual +item or whole stack count, an item type and optionally a material. When the +current count is below the lower bound of the range, the job is resumed; if it +is above or equal to the top bound, it will be suspended. Within the range, the +specific constraint has no effect on the job; others may still affect it.

                                              +

                                              Pressing 'c' switches the current constraint between counting stacks or items. +Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting +items and expanding the range each gives a 5x bonus).

                                              +

                                              Pressing 'n' produces a list of possible outputs of this job as guessed by +workflow, and lets you create a new constraint by just choosing one. If you +don't see the choice you want in the list, it likely means you have to adjust +the job material first using job item-material or gui/workshop-job, +as described in workflow documentation above. In this manner, this feature +can be used for troubleshooting jobs that don't match the right constraints.

                                              +
                                              +
                                              +

                                              gui/assign-rack

                                              +

                                              Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                              +

                                              This script is part of a group of related fixes to make the armory storage +work again. The existing issues are:

                                              +
                                                +
                                              • Weapon racks have to each be assigned to a specific squad, like with +beds/boxes/armor stands and individual squad members, but nothing in +the game does this. This issue is what this script addresses.
                                              • +
                                              • Even if assigned by the script, the game will unassign the racks again without a binary patch. +Check the comments for this bug to get it: +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
                                              • +
                                              • Haulers still take equpment stored in the armory away to the stockpiles, +unless the fix-armory plugin above is used.
                                              • +
                                              +

                                              The script interface simply lets you designate one of the squads that +are assigned to the barracks/armory containing the selected stand as +the intended user.

                                              +
                                              -

                                              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.

                                              @@ -2691,20 +2813,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 @@ -2725,7 +2847,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 @@ -2734,11 +2856,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 @@ -2749,7 +2871,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.

                                              @@ -2773,7 +2895,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 @@ -2804,7 +2926,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 @@ -2813,7 +2935,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 @@ -2824,7 +2946,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 39d44d975..ad42035a5 100644 --- a/Readme.rst +++ b/Readme.rst @@ -231,6 +231,8 @@ Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and p * 'fastdwarf 1 1' enables both * 'fastdwarf 0' disables both * 'fastdwarf 1' enables speedydwarf and disables teledwarf + * 'fastdwarf 2 ...' sets a native debug flag in the game memory + that implements an even more aggressive version of speedydwarf. Game interface ============== @@ -1070,6 +1072,51 @@ Subcommands that persist until disabled or DF quit: :military-color-assigned: Color squad candidates already assigned to other squads in brown/green to make them stand out more in the list. +fix-armory +---------- + +Enables a fix for storage of squad equipment in barracks. + +Specifically, it prevents your haulers from moving that equipment +to stockpiles, and instead queues jobs to store it on weapon racks, +armor stands, and in containers. + +.. note:: + + In order to actually be used, weapon racks have to be patched and + assigned to a squad. See documentation for ``gui/assign-rack`` below. + + Also, the default capacity of armor stands is way too low, so check out + http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + for a patch addressing that too. + +Note that the buildings in the armory are used as follows: + +* Weapon racks when fixed are used to store any assigned weapons. + Each rack belongs to a specific squad, and can store up to 5 weapons. + +* Armor stands belong to specific squad members and are used for + armor and shields. By default one stand can store one item of each + type (hence one boot or gauntlet); if patched, the limit is raised to 2, + which should be sufficient. + +* Cabinets are used to store assigned clothing for a specific squad member. + They are **never** used to store owned clothing. + +* Chests (boxes, etc) are used for a flask, backpack or quiver assigned + to the squad member. Due to a bug, food is dropped out of the backpack + when it is stored. + +Contrary to the common misconception, all these uses are controlled by the +*Individual Equipment* usage flag; the Squad Equipment mode means nothing. + +.. warning:: + + Although armor stands, cabinets and chests properly belong only to one + squad member, the owner of the building used to create the barracks will + randomly use any containers inside the room. Thus, it is recommended to + always create the armory from a weapon rack. + Mode switch and reclaim ======================= @@ -1213,6 +1260,9 @@ amount goes above or below the limit. The gap specifies how much below the limit the amount has to drop before jobs are resumed; this is intended to reduce the frequency of jobs being toggled. +Check out the ``gui/workflow`` script below for a simple front-end integrated +in the game UI. + Constraint examples ................... @@ -1896,6 +1946,102 @@ Rationale: individual choice seems to be unreliable when there is a weapon short and may lead to inappropriate weapons being selected. +gui/guide-path +============== + +Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. + +The script displays the cached path that will be used by the order; the game +computes it when the order is executed for the first time. + + +gui/workshop-job +================ + +Bind to a key, and activate with a job selected in a workshop in the 'q' mode. + +The script shows a list of the input reagents of the selected job, and allows changing +them like the ``job item-type`` and ``job item-material`` commands. + +Specifically, pressing the 'i' key pops up a dialog that lets you select an item +type from a list. Pressing 'm', unless the item type does not allow a material, +lets you choose a material. + +.. warning:: + + Due to the way input reagent matching works in DF, you must select an item type + if you select a material, or the material will be matched incorrectly in some cases. + If you press 'm' without choosing an item type, the script will auto-choose it + if there is only one valid choice, or pop up an error message box instead of the + material selection dialog. + +Note that both materials and item types presented in the dialogs are filtered +by the job input flags, and even the selected item type for material selection, +or material for item type selection. Many jobs would let you select only one +input item type. + +For example, if you choose a *plant* input item type for your prepare meal job, +it will only let you select cookable materials. + +If you choose a *barrel* item instead (meaning things stored in barrels, like +drink or milk), it will let you select any material, since in this case the +material is matched against the barrel itself. Then, if you select, say, iron, +and then try to change the input item type, now it won't let you select *plant*; +you have to unset the material first. + + +gui/workflow +============ + +Bind to a key, and activate with a job selected in a workshop in the 'q' mode. + +This script provides a simple interface to constraints managed by the workflow +plugin. When active, it displays a list of all constraints applicable to the +current job, and their current status. + +A constraint specifies a certain range to be compared against either individual +*item* or whole *stack* count, an item type and optionally a material. When the +current count is below the lower bound of the range, the job is resumed; if it +is above or equal to the top bound, it will be suspended. Within the range, the +specific constraint has no effect on the job; others may still affect it. + +Pressing 'c' switches the current constraint between counting stacks or items. +Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting +items and expanding the range each gives a 5x bonus). + +Pressing 'n' produces a list of possible outputs of this job as guessed by +workflow, and lets you create a new constraint by just choosing one. If you +don't see the choice you want in the list, it likely means you have to adjust +the job material first using ``job item-material`` or ``gui/workshop-job``, +as described in ``workflow`` documentation above. In this manner, this feature +can be used for troubleshooting jobs that don't match the right constraints. + + +gui/assign-rack +=============== + +Bind to a key, and activate when viewing a weapon rack in the 'q' mode. + +This script is part of a group of related fixes to make the armory storage +work again. The existing issues are: + +* Weapon racks have to each be assigned to a specific squad, like with + beds/boxes/armor stands and individual squad members, but nothing in + the game does this. This issue is what this script addresses. + +* Even if assigned by the script, **the game will unassign the racks again without a binary patch**. + Check the comments for this bug to get it: + http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +* Haulers still take equpment stored in the armory away to the stockpiles, + unless the ``fix-armory`` plugin above is used. + +The script interface simply lets you designate one of the squads that +are assigned to the barracks/armory containing the selected stand as +the intended user. + + ============= Behavior Mods ============= From 7646fa6aa3f04900a85a5c7b65d77d1ef9569c32 Mon Sep 17 00:00:00 2001 From: Mathias Rav Date: Mon, 29 Oct 2012 22:18:50 +0100 Subject: [PATCH 120/472] Add dfstream plugin. Broadcasts the Dwarf Fortress display on TCP port 8008. For use with https://github.com/Mortal/dfstream --- plugins/CMakeLists.txt | 1 + plugins/dfstream.cpp | 413 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 414 insertions(+) create mode 100644 plugins/dfstream.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..f19947325 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) endif() diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp new file mode 100644 index 000000000..0815146b0 --- /dev/null +++ b/plugins/dfstream.cpp @@ -0,0 +1,413 @@ +// TODO: which of these includes are actually needed? +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "df/graphic.h" +#include "df/enabler.h" +#include "df/viewscreen_unitlistst.h" +#include "df/interface_key.h" +#include "df/unit.h" +#include "df/unit_soul.h" +#include "df/unit_skill.h" +#include "df/creature_graphics_role.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::enabler; + +//--- SNIP class definition from g_src ---------------------------------------- +enum zoom_commands { zoom_in, zoom_out, zoom_reset, zoom_fullscreen, zoom_resetgrid }; + +class renderer { + void cleanup_arrays(); +protected: + friend class renderer_decorator; + unsigned char *screen; + long *screentexpos; + char *screentexpos_addcolor; + unsigned char *screentexpos_grayscale; + unsigned char *screentexpos_cf; + unsigned char *screentexpos_cbr; + // For partial printing: + unsigned char *screen_old; + long *screentexpos_old; + char *screentexpos_addcolor_old; + unsigned char *screentexpos_grayscale_old; + unsigned char *screentexpos_cf_old; + unsigned char *screentexpos_cbr_old; + + //void gps_allocate(int x, int y); + //Either screen_to_texid(int x, int y); +public: + //void display(); + virtual void update_tile(int x, int y) = 0; + virtual void update_all() = 0; + virtual void render() = 0; + virtual void set_fullscreen(); // Should read from enabler.is_fullscreen() + virtual void zoom(zoom_commands cmd); + virtual void resize(int w, int h) = 0; + virtual void grid_resize(int w, int h) = 0; + //void swap_arrays(); + renderer() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + virtual ~renderer(); + virtual bool get_mouse_coords(int &x, int &y) = 0; + virtual bool uses_opengl(); +}; +//---- END class definition from g_src ---------------------------------------- + +// The error messages are taken from the clsocket source code +const char * translate_socket_error(CSimpleSocket::CSocketError err) { + switch (err) { + case CSimpleSocket::SocketError: + return "Generic socket error translates to error below."; + case CSimpleSocket::SocketSuccess: + return "No socket error."; + case CSimpleSocket::SocketInvalidSocket: + return "Invalid socket handle."; + case CSimpleSocket::SocketInvalidAddress: + return "Invalid destination address specified."; + case CSimpleSocket::SocketInvalidPort: + return "Invalid destination port specified."; + case CSimpleSocket::SocketConnectionRefused: + return "No server is listening at remote address."; + case CSimpleSocket::SocketTimedout: + return "Timed out while attempting operation."; + case CSimpleSocket::SocketEwouldblock: + return "Operation would block if socket were blocking."; + case CSimpleSocket::SocketNotconnected: + return "Currently not connected."; + case CSimpleSocket::SocketEinprogress: + return "Socket is non-blocking and the connection cannot be completed immediately"; + case CSimpleSocket::SocketInterrupted: + return "Call was interrupted by a signal that was caught before a valid connection arrived."; + case CSimpleSocket::SocketConnectionAborted: + return "The connection has been aborted."; + case CSimpleSocket::SocketProtocolError: + return "Invalid protocol for operation."; + case CSimpleSocket::SocketFirewallError: + return "Firewall rules forbid connection."; + case CSimpleSocket::SocketInvalidSocketBuffer: + return "The receive buffer point outside the process's address space."; + case CSimpleSocket::SocketConnectionReset: + return "Connection was forcibly closed by the remote host."; + case CSimpleSocket::SocketAddressInUse: + return "Address already in use."; + case CSimpleSocket::SocketInvalidPointer: + return "Pointer type supplied as argument is invalid."; + case CSimpleSocket::SocketEunknown: + return "Unknown error please report to mark@carrierlabs.com"; + default: + return "No such CSimpleSocket error"; + } +} + +// Owns the thread that accepts TCP connections and forwards messages to clients; +// has a mutex +class client_pool { + typedef tthread::mutex mutex; + + mutex clients_lock; + std::vector clients; + + // TODO - delete this at some point + tthread::thread * accepter; + + static void accept_clients(void * client_pool_pointer) { + client_pool * p = reinterpret_cast(client_pool_pointer); + CPassiveSocket socket; + socket.Initialize(); + if (socket.Listen((const uint8 *)"0.0.0.0", 8008)) { + std::cout << "Listening on a socket" << std::endl; + } else { + std::cout << "Not listening: " << socket.GetSocketError() << std::endl; + std::cout << translate_socket_error(socket.GetSocketError()) << std::endl; + } + while (true) { + CActiveSocket * client = socket.Accept(); + if (client != 0) { + lock l(*p); + p->clients.push_back(client); + } + } + } + +public: + class lock { + tthread::lock_guard l; + public: + lock(client_pool & p) + : l(p.clients_lock) + { + } + }; + friend class client_pool::lock; + + client_pool() { + accepter = new tthread::thread(accept_clients, this); + } + + // MUST have lock + bool has_clients() { + return !clients.empty(); + } + + // MUST have lock + void add_client(CActiveSocket * sock) { + clients.push_back(sock); + } + + // MUST have lock + void broadcast(const std::string & message) { + unsigned int sz = htonl(message.size()); + for (size_t i = 0; i < clients.size(); ++i) { + clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); + clients[i]->Send((const uint8 *) message.c_str(), message.size()); + } + } +}; + +// A decorator (in the design pattern sense) of the DF renderer class. +// Sends the screen contents to a client_pool. +class renderer_decorator : public renderer { + // the renderer we're decorating + renderer * inner; + + // how many frames have passed since we last sent a frame + int framesNotPrinted; + + // set to false in the destructor + bool * alive; + + // clients to which we send the frame + client_pool clients; + + // The following three methods facilitate copying of state to the inner object + void set_inner_to_null() { + inner->screen = NULL; + inner->screentexpos = NULL; + inner->screentexpos_addcolor = NULL; + inner->screentexpos_grayscale = NULL; + inner->screentexpos_cf = NULL; + inner->screentexpos_cbr = NULL; + inner->screen_old = NULL; + inner->screentexpos_old = NULL; + inner->screentexpos_addcolor_old = NULL; + inner->screentexpos_grayscale_old = NULL; + inner->screentexpos_cf_old = NULL; + inner->screentexpos_cbr_old = NULL; + } + + void copy_from_inner() { + screen = inner->screen; + screentexpos = inner->screentexpos; + screentexpos_addcolor = inner->screentexpos_addcolor; + screentexpos_grayscale = inner->screentexpos_grayscale; + screentexpos_cf = inner->screentexpos_cf; + screentexpos_cbr = inner->screentexpos_cbr; + screen_old = inner->screen_old; + screentexpos_old = inner->screentexpos_old; + screentexpos_addcolor_old = inner->screentexpos_addcolor_old; + screentexpos_grayscale_old = inner->screentexpos_grayscale_old; + screentexpos_cf_old = inner->screentexpos_cf_old; + screentexpos_cbr_old = inner->screentexpos_cbr_old; + } + + void copy_to_inner() { + inner->screen = screen; + inner->screentexpos = screentexpos; + inner->screentexpos_addcolor = screentexpos_addcolor; + inner->screentexpos_grayscale = screentexpos_grayscale; + inner->screentexpos_cf = screentexpos_cf; + inner->screentexpos_cbr = screentexpos_cbr; + inner->screen_old = screen_old; + inner->screentexpos_old = screentexpos_old; + inner->screentexpos_addcolor_old = screentexpos_addcolor_old; + inner->screentexpos_grayscale_old = screentexpos_grayscale_old; + inner->screentexpos_cf_old = screentexpos_cf_old; + inner->screentexpos_cbr_old = screentexpos_cbr_old; + } + +public: + renderer_decorator(renderer * inner, bool * alive) + : inner(inner) + , framesNotPrinted(0) + , alive(alive) + { + copy_from_inner(); + } + virtual void update_tile(int x, int y) { + copy_to_inner(); + inner->update_tile(x, y); + } + virtual void update_all() { + copy_to_inner(); + inner->update_all(); + } + virtual void render() { + copy_to_inner(); + inner->render(); + + ++framesNotPrinted; + int gfps = enabler->calculated_gfps; + if (gfps == 0) gfps = 1; + // send a frame roughly every 128 mibiseconds (1 second = 1024 mibiseconds) + if ((framesNotPrinted * 1024) / gfps <= 128) return; + + client_pool::lock lock(clients); + if (!clients.has_clients()) return; + framesNotPrinted = 0; + std::stringstream frame; + frame << gps->dimx << ' ' << gps->dimy << " 0 0 " << gps->dimx << ' ' << gps->dimy << '\n'; + unsigned char * sc_ = gps->screen; + for (int y = 0; y < gps->dimy; ++y) { + unsigned char * sc = sc_; + for (int x = 0; x < gps->dimx; ++x) { + unsigned char ch = sc[0]; + unsigned char bold = (sc[3] != 0) * 8; + unsigned char translate[] = + { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; + unsigned char fg = translate[(sc[1] + bold) % 16]; + unsigned char bg = translate[sc[2] % 16]*16; + frame.put(ch); + frame.put(fg+bg); + sc += 4*gps->dimy; + } + sc_ += 4; + } + clients.broadcast(frame.str()); + } + virtual void set_fullscreen() { inner->set_fullscreen(); } + virtual void zoom(zoom_commands cmd) { + copy_to_inner(); + inner->zoom(cmd); + } + virtual void resize(int w, int h) { + copy_to_inner(); + inner->resize(w, h); + copy_from_inner(); + } + virtual void grid_resize(int w, int h) { + copy_to_inner(); + inner->grid_resize(w, h); + copy_from_inner(); + } + virtual ~renderer_decorator() { + *alive = false; + if (inner) set_inner_to_null(); + delete inner; + inner = 0; + } + virtual bool get_mouse_coords(int &x, int &y) { return inner->get_mouse_coords(x, y); } + virtual bool uses_opengl() { return inner->uses_opengl(); } + + static renderer_decorator * hook(renderer *& ptr, bool * alive) { + renderer_decorator * r = new renderer_decorator(ptr, alive); + ptr = r; + return r; + } + + static void unhook(renderer *& ptr, renderer_decorator * dec, color_ostream & out) { + dec->copy_to_inner(); + ptr = dec->inner; + dec->inner = 0; + delete dec; + } +}; + +DFHACK_PLUGIN("dfstream"); + +inline renderer *& active_renderer() { + return reinterpret_cast(enabler->renderer); +} + +// This class is a smart pointer around a renderer_decorator. +// It should only be assigned r_d pointers that use the alive-pointer of this +// instance. +// If the r_d has been deleted by an external force, this smart pointer doesn't +// redelete it. +class auto_renderer_decorator { + renderer_decorator * p; +public: + // pass this member to the ctor of renderer_decorator + bool alive; + + auto_renderer_decorator() + : p(0) + { + } + + ~auto_renderer_decorator() { + reset(); + } + + void reset() { + if (*this) { + delete p; + p = 0; + } + } + + operator bool() { + return (p != 0) && alive; + } + + auto_renderer_decorator & operator=(renderer_decorator *p) { + reset(); + this->p = p; + } + + renderer_decorator * get() { + return p; + } + + renderer_decorator * operator->() { + return get(); + } +}; + +auto_renderer_decorator decorator; + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + if (!decorator) { + decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + decorator.reset(); + return CR_OK; +} +// vim:set sw=4 sts=4 et: From 3a6b0357a29b4367798d73b6df6a4de45b140a39 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 29 Oct 2012 16:56:22 -0500 Subject: [PATCH 121/472] Fix compilation on Windows, incorporate latest df-structures data --- plugins/dfstream.cpp | 113 ++++++++++--------------------------------- 1 file changed, 25 insertions(+), 88 deletions(-) diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 0815146b0..a7bc1015e 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -1,90 +1,26 @@ -// TODO: which of these includes are actually needed? #include "Core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "DataDefs.h" #include "df/graphic.h" #include "df/enabler.h" -#include "df/viewscreen_unitlistst.h" -#include "df/interface_key.h" -#include "df/unit.h" -#include "df/unit_soul.h" -#include "df/unit_skill.h" -#include "df/creature_graphics_role.h" -#include "df/creature_raw.h" -#include "df/caste_raw.h" +#include "df/renderer.h" + +#include +#include +#include "PassiveSocket.h" +#include "tinythread.h" using namespace DFHack; using namespace df::enums; +using std::string; +using std::vector; using df::global::gps; using df::global::enabler; -//--- SNIP class definition from g_src ---------------------------------------- -enum zoom_commands { zoom_in, zoom_out, zoom_reset, zoom_fullscreen, zoom_resetgrid }; - -class renderer { - void cleanup_arrays(); -protected: - friend class renderer_decorator; - unsigned char *screen; - long *screentexpos; - char *screentexpos_addcolor; - unsigned char *screentexpos_grayscale; - unsigned char *screentexpos_cf; - unsigned char *screentexpos_cbr; - // For partial printing: - unsigned char *screen_old; - long *screentexpos_old; - char *screentexpos_addcolor_old; - unsigned char *screentexpos_grayscale_old; - unsigned char *screentexpos_cf_old; - unsigned char *screentexpos_cbr_old; - - //void gps_allocate(int x, int y); - //Either screen_to_texid(int x, int y); -public: - //void display(); - virtual void update_tile(int x, int y) = 0; - virtual void update_all() = 0; - virtual void render() = 0; - virtual void set_fullscreen(); // Should read from enabler.is_fullscreen() - virtual void zoom(zoom_commands cmd); - virtual void resize(int w, int h) = 0; - virtual void grid_resize(int w, int h) = 0; - //void swap_arrays(); - renderer() { - screen = NULL; - screentexpos = NULL; - screentexpos_addcolor = NULL; - screentexpos_grayscale = NULL; - screentexpos_cf = NULL; - screentexpos_cbr = NULL; - screen_old = NULL; - screentexpos_old = NULL; - screentexpos_addcolor_old = NULL; - screentexpos_grayscale_old = NULL; - screentexpos_cf_old = NULL; - screentexpos_cbr_old = NULL; - } - virtual ~renderer(); - virtual bool get_mouse_coords(int &x, int &y) = 0; - virtual bool uses_opengl(); -}; -//---- END class definition from g_src ---------------------------------------- - // The error messages are taken from the clsocket source code const char * translate_socket_error(CSimpleSocket::CSocketError err) { switch (err) { @@ -146,7 +82,7 @@ class client_pool { client_pool * p = reinterpret_cast(client_pool_pointer); CPassiveSocket socket; socket.Initialize(); - if (socket.Listen((const uint8 *)"0.0.0.0", 8008)) { + if (socket.Listen((const uint8_t *)"0.0.0.0", 8008)) { std::cout << "Listening on a socket" << std::endl; } else { std::cout << "Not listening: " << socket.GetSocketError() << std::endl; @@ -190,17 +126,17 @@ public: void broadcast(const std::string & message) { unsigned int sz = htonl(message.size()); for (size_t i = 0; i < clients.size(); ++i) { - clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); - clients[i]->Send((const uint8 *) message.c_str(), message.size()); + clients[i]->Send(reinterpret_cast(&sz), sizeof(sz)); + clients[i]->Send((const uint8_t *) message.c_str(), message.size()); } } }; // A decorator (in the design pattern sense) of the DF renderer class. // Sends the screen contents to a client_pool. -class renderer_decorator : public renderer { +class renderer_decorator : public df::renderer { // the renderer we're decorating - renderer * inner; + df::renderer * inner; // how many frames have passed since we last sent a frame int framesNotPrinted; @@ -258,7 +194,7 @@ class renderer_decorator : public renderer { } public: - renderer_decorator(renderer * inner, bool * alive) + renderer_decorator(df::renderer * inner, bool * alive) : inner(inner) , framesNotPrinted(0) , alive(alive) @@ -307,7 +243,7 @@ public: clients.broadcast(frame.str()); } virtual void set_fullscreen() { inner->set_fullscreen(); } - virtual void zoom(zoom_commands cmd) { + virtual void zoom(df::zoom_commands cmd) { copy_to_inner(); inner->zoom(cmd); } @@ -327,16 +263,16 @@ public: delete inner; inner = 0; } - virtual bool get_mouse_coords(int &x, int &y) { return inner->get_mouse_coords(x, y); } + virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } virtual bool uses_opengl() { return inner->uses_opengl(); } - static renderer_decorator * hook(renderer *& ptr, bool * alive) { + static renderer_decorator * hook(df::renderer *& ptr, bool * alive) { renderer_decorator * r = new renderer_decorator(ptr, alive); ptr = r; return r; } - static void unhook(renderer *& ptr, renderer_decorator * dec, color_ostream & out) { + static void unhook(df::renderer *& ptr, renderer_decorator * dec, color_ostream & out) { dec->copy_to_inner(); ptr = dec->inner; dec->inner = 0; @@ -346,8 +282,8 @@ public: DFHACK_PLUGIN("dfstream"); -inline renderer *& active_renderer() { - return reinterpret_cast(enabler->renderer); +inline df::renderer *& active_renderer() { + return enabler->renderer; } // This class is a smart pointer around a renderer_decorator. @@ -384,6 +320,7 @@ public: auto_renderer_decorator & operator=(renderer_decorator *p) { reset(); this->p = p; + return *this; } renderer_decorator * get() { From cd14bdfd43658d6a9e705f33a1efbfbfd9e7f1a8 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 29 Oct 2012 22:29:07 -0500 Subject: [PATCH 122/472] Disable dfstream by default --- plugins/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1d20550ed..65c6e236c 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -128,7 +128,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) - DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) endif() From dc2805b1f32aa26beb521c5e14ae3fee3ff2ccb6 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 10:38:32 +0400 Subject: [PATCH 123/472] Link the renderer vtable from libgraphics on linux. --- library/Core.cpp | 2 +- library/modules/Screen.cpp | 46 +++++++++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/dfstream.cpp | 44 +++++++++++++++++++++++------------- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 111774f26..26c0acbb0 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -869,7 +869,6 @@ bool Core::Init() // Init global object pointers df::global::InitGlobals(); - init_screen_module(this); cerr << "Initializing Console.\n"; // init the console. @@ -895,6 +894,7 @@ bool Core::Init() */ // initialize data defs virtual_identity::Init(this); + init_screen_module(this); // initialize common lua context Lua::Core::Init(con); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index d89b3688d..e8d23261b 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -55,6 +55,7 @@ using namespace DFHack; #include "df/item.h" #include "df/job.h" #include "df/building.h" +#include "df/renderer.h" using namespace df::enums; using df::global::init; @@ -312,6 +313,47 @@ class DFHACK_EXPORT enabler_inputst { public: std::string GetKeyDisplay(int binding); }; + +class DFHACK_EXPORT renderer { + unsigned char *screen; + long *screentexpos; + char *screentexpos_addcolor; + unsigned char *screentexpos_grayscale; + unsigned char *screentexpos_cf; + unsigned char *screentexpos_cbr; + // For partial printing: + unsigned char *screen_old; + long *screentexpos_old; + char *screentexpos_addcolor_old; + unsigned char *screentexpos_grayscale_old; + unsigned char *screentexpos_cf_old; + unsigned char *screentexpos_cbr_old; +public: + virtual void update_tile(int x, int y) {}; + virtual void update_all() {}; + virtual void render() {}; + virtual void set_fullscreen(); + virtual void zoom(df::zoom_commands cmd); + virtual void resize(int w, int h) {}; + virtual void grid_resize(int w, int h) {}; + renderer() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; + } + virtual ~renderer(); + virtual bool get_mouse_coords(int &x, int &y) { return false; } + virtual bool uses_opengl(); +}; #else struct less_sz { bool operator() (const string &a, const string &b) const { @@ -326,7 +368,9 @@ static std::map > *keydisplay = NULL; void init_screen_module(Core *core) { #ifdef _LINUX - core = core; + renderer tmp; + if (!strict_virtual_cast((virtual_ptr)&tmp)) + cerr << "Could not fetch the renderer vtable." << std::endl; #else if (!core->vinfo->getAddress("keydisplay", keydisplay)) keydisplay = NULL; diff --git a/library/xml b/library/xml index 4bfc5b197..378f91d09 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4bfc5b197de9ca096a11b9c1594e043bd8ae707a +Subproject commit 378f91d0964e09e3cfa0ecbea100ef2cc12a59ab diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index a7bc1015e..6144389d7 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -148,19 +148,19 @@ class renderer_decorator : public df::renderer { client_pool clients; // The following three methods facilitate copying of state to the inner object - void set_inner_to_null() { - inner->screen = NULL; - inner->screentexpos = NULL; - inner->screentexpos_addcolor = NULL; - inner->screentexpos_grayscale = NULL; - inner->screentexpos_cf = NULL; - inner->screentexpos_cbr = NULL; - inner->screen_old = NULL; - inner->screentexpos_old = NULL; - inner->screentexpos_addcolor_old = NULL; - inner->screentexpos_grayscale_old = NULL; - inner->screentexpos_cf_old = NULL; - inner->screentexpos_cbr_old = NULL; + void set_to_null() { + screen = NULL; + screentexpos = NULL; + screentexpos_addcolor = NULL; + screentexpos_grayscale = NULL; + screentexpos_cf = NULL; + screentexpos_cbr = NULL; + screen_old = NULL; + screentexpos_old = NULL; + screentexpos_addcolor_old = NULL; + screentexpos_grayscale_old = NULL; + screentexpos_cf_old = NULL; + screentexpos_cbr_old = NULL; } void copy_from_inner() { @@ -259,9 +259,12 @@ public: } virtual ~renderer_decorator() { *alive = false; - if (inner) set_inner_to_null(); - delete inner; - inner = 0; + if (inner) { + copy_to_inner(); + delete inner; + inner = 0; + } + set_to_null(); } virtual bool get_mouse_coords(int *x, int *y) { return inner->get_mouse_coords(x, y); } virtual bool uses_opengl() { return inner->uses_opengl(); } @@ -336,6 +339,11 @@ auto_renderer_decorator decorator; DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { + if (!df::renderer::_identity.can_allocate()) + { + out.printerr("Cannot allocate a renderer\n"); + return CR_OK; + } if (!decorator) { decorator = renderer_decorator::hook(active_renderer(), &decorator.alive); } @@ -344,6 +352,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Tue, 30 Oct 2012 12:40:26 +0400 Subject: [PATCH 124/472] Add a few utility functions to the main library. --- Lua API.html | 6 ++++++ Lua API.rst | 8 ++++++++ library/LuaApi.cpp | 2 ++ library/MiscUtils.cpp | 5 +++++ library/include/MiscUtils.h | 2 ++ library/include/modules/Items.h | 5 +++++ library/modules/Items.cpp | 14 ++++++++++++++ plugins/siege-engine.cpp | 5 ----- 8 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Lua API.html b/Lua API.html index be52ea985..a52347104 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1239,6 +1239,12 @@ Returns false in case of error.

                                            • dfhack.items.getContainedItems(item)

                                              Returns a list of items contained in this one.

                                            • +
                                            • dfhack.items.getHolderBuilding(item)

                                              +

                                              Returns the holder building or nil.

                                              +
                                            • +
                                            • dfhack.items.getHolderUnit(item)

                                              +

                                              Returns the holder unit or nil.

                                              +
                                            • dfhack.items.moveToGround(item,pos)

                                              Move the item to the ground at position. Returns false if impossible.

                                            • diff --git a/Lua API.rst b/Lua API.rst index 8dad3663b..bd712d301 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1019,6 +1019,14 @@ Items module Returns a list of items contained in this one. +* ``dfhack.items.getHolderBuilding(item)`` + + Returns the holder building or *nil*. + +* ``dfhack.items.getHolderUnit(item)`` + + Returns the holder unit or *nil*. + * ``dfhack.items.moveToGround(item,pos)`` Move the item to the ground at position. Returns *false* if impossible. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6ca1188d9..e7424ad50 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -942,6 +942,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getOwner), WRAPM(Items, setOwner), WRAPM(Items, getContainer), + WRAPM(Items, getHolderBuilding), + WRAPM(Items, getHolderUnit), WRAPM(Items, getDescription), WRAPM(Items, isCasteMaterial), WRAPM(Items, getSubtypeCount), diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index a05e7eba5..53c403026 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -148,6 +148,11 @@ bool prefix_matches(const std::string &prefix, const std::string &key, std::stri return false; } +int random_int(int max) +{ + return int(int64_t(rand())*max/(int64_t(RAND_MAX)+1)); +} + #ifdef LINUX_BUILD // Linux uint64_t GetTimeMs64() { diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index 61dd69297..f5b4e8ded 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -331,6 +331,8 @@ inline T clip_range(T a, T1 minv, T2 maxv) { return a; } +DFHACK_EXPORT int random_int(int max); + /** * Returns the amount of milliseconds elapsed since the UNIX epoch. * Works on both windows and linux. diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 217a0f28a..34ca98162 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -151,6 +151,11 @@ DFHACK_EXPORT df::item *getContainer(df::item *item); /// which items does it contain? DFHACK_EXPORT void getContainedItems(df::item *item, /*output*/ std::vector *items); +/// which building holds it? +DFHACK_EXPORT df::building *getHolderBuilding(df::item *item); +/// which unit holds it? +DFHACK_EXPORT df::unit *getHolderUnit(df::item *item); + /// Returns the true position of the item. DFHACK_EXPORT df::coord getPosition(df::item *item); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index cf99d9426..877f8abe0 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -592,6 +592,20 @@ void Items::getContainedItems(df::item *item, std::vector *items) } } +df::building *Items::getHolderBuilding(df::item * item) +{ + auto ref = getGeneralRef(item, general_ref_type::BUILDING_HOLDER); + + return ref ? ref->getBuilding() : NULL; +} + +df::unit *Items::getHolderUnit(df::item * item) +{ + auto ref = getGeneralRef(item, general_ref_type::UNIT_HOLDER); + + return ref ? ref->getUnit() : NULL; +} + df::coord Items::getPosition(df::item *item) { CHECK_NULL_POINTER(item); diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp index 5c7b8833a..4b2060e99 100644 --- a/plugins/siege-engine.cpp +++ b/plugins/siege-engine.cpp @@ -132,11 +132,6 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target) df::building_siegeenginest::Up; } -static int random_int(int val) -{ - return int(int64_t(rand())*val/RAND_MAX); -} - static int point_distance(df::coord speed) { return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); From 4fa826d2b92a8461a422469dcd38cdf50ad97337 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 13:11:57 +0400 Subject: [PATCH 125/472] Block instant creation of store in stockpile jobs when removing uniforms. --- plugins/fix-armory.cpp | 211 +++++++++++++++++++++++++++++++---------- 1 file changed, 163 insertions(+), 48 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 06adbbe4c..6bfdfe421 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -11,6 +11,7 @@ #include "modules/Items.h" #include "modules/Job.h" #include "modules/World.h" +#include "modules/Maps.h" #include "MiscUtils.h" @@ -21,6 +22,7 @@ #include "df/squad.h" #include "df/unit.h" #include "df/squad_position.h" +#include "df/items_other_id.h" #include "df/item_weaponst.h" #include "df/item_armorst.h" #include "df/item_helmst.h" @@ -84,6 +86,32 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +// Check if the item is assigned to any use controlled by the military tab +static bool is_assigned_item(df::item *item) +{ + if (!ui) + return false; + + auto type = item->getType(); + int idx = binsearch_index(ui->equipment.items_assigned[type], item->id); + if (idx < 0) + return false; + + // Exclude weapons used by miners, wood cutters etc + switch (type) { + case item_type::WEAPON: + if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) + return false; + break; + + default: + break; + } + + return true; +} + +// Check if the item is assigned to the squad member who owns this armory building static bool belongs_to_position(df::item *item, df::building *holder) { int sid = holder->getSpecificSquad(); @@ -96,6 +124,7 @@ static bool belongs_to_position(df::item *item, df::building *holder) int position = holder->getSpecificPosition(); + // Weapon racks belong to the whole squad, i.e. can be used by any position if (position == -1 && holder->getType() == building_type::Weaponrack) { for (size_t i = 0; i < squad->positions.size(); i++) @@ -114,19 +143,17 @@ static bool belongs_to_position(df::item *item, df::building *holder) return false; } +// Check if the item is appropriately stored in an armory building static bool is_in_armory(df::item *item) { if (item->flags.bits.in_inventory || item->flags.bits.on_ground) return false; - auto holder_ref = Items::getGeneralRef(item, general_ref_type::BUILDING_HOLDER); - if (!holder_ref) - return false; - - auto holder = holder_ref->getBuilding(); + auto holder = Items::getHolderBuilding(item); if (!holder) return false; + // If indeed in a building, check if it is the right one return belongs_to_position(item, holder); } @@ -135,11 +162,47 @@ template struct armory_hook : Item { DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { + // Normally this vmethod is used to prevent stockpiling of uncollected webs. + // This uses it to also block stockpiling of items in the armory. if (is_in_armory(this)) return false; + // Also never let items in process of being removed from uniform be stockpiled at once + if (this->flags.bits.in_inventory) + { + auto holder = Items::getHolderUnit(this); + + if (holder && binsearch_index(holder->military.uniform_drop, this->id) >= 0) + { + if (is_assigned_item(this)) + return false; + } + } + return INTERPOSE_NEXT(isCollected)(); } + + DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) + { + bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); + + // Prevent instant restockpiling of dropped assigned items. + if (is_assigned_item(this)) + { + // The original vmethod adds the item to this vector to force instant check + auto &ovec = world->items.other[items_other_id::ANY_RECENTLY_DROPPED]; + + // If it is indeed there, remove it + if (erase_from_vector(ovec, &df::item::id, this->id)) + { + // and queue it to be checked normally in 0.5-1 in-game days + this->stockpile_countdown = 12 + random_int(12); + this->stockpile_delay = 0; + } + } + + return rv; + } }; template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); @@ -153,11 +216,20 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollecte template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); + +// Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { - if (!item || item->stockpile_countdown > 0) - return false; - if (item->flags.bits.in_job || item->flags.bits.removed || item->flags.bits.in_building || @@ -185,6 +257,69 @@ static bool can_store_item(df::item *item) return true; } +// Queue a job to store the item in the building, if possible +static bool try_store_item(df::building *target, df::item *item) +{ + // Check if the dwarves can path between the target and the item + df::coord tpos(target->centerx, target->centery, target->z); + df::coord ipos = Items::getPosition(item); + + if (!Maps::canWalkBetween(tpos, ipos)) + return false; + + // Check if the target has enough space left + if (!target->canStoreItem(item, true)) + return false; + + // Create the job + auto href = df::allocate(); + if (!href) + return false; + + auto job = new df::job(); + + job->pos = tpos; + + // Choose the job type - correct matching is needed so that + // later canStoreItem calls would take the job into account. + switch (target->getType()) { + case building_type::Weaponrack: + job->job_type = job_type::StoreWeapon; + // Without this flag dwarves will pick up the item, and + // then dismiss the job and put it back into the stockpile: + job->flags.bits.specific_dropoff = true; + break; + case building_type::Armorstand: + job->job_type = job_type::StoreArmor; + job->flags.bits.specific_dropoff = true; + break; + case building_type::Cabinet: + job->job_type = job_type::StoreItemInCabinet; + break; + default: + job->job_type = job_type::StoreItemInChest; + break; + } + + // job <-> item link + if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) + { + delete job; + delete href; + return false; + } + + // job <-> building link + href->building_id = target->id; + target->jobs.push_back(job); + job->references.push_back(href); + + // add to job list + Job::linkIntoWorld(job); + return true; +} + +// Store the item into the first building in the list that would accept it. static void try_store_item(std::vector &vec, df::item *item) { for (size_t i = 0; i < vec.size(); i++) @@ -193,56 +328,23 @@ static void try_store_item(std::vector &vec, df::item *item) if (!target) continue; - if (!target->canStoreItem(item, true)) - continue; - - auto href = df::allocate(); - if (!href) + if (try_store_item(target, item)) return; - - auto job = new df::job(); - - job->pos = df::coord(target->centerx, target->centery, target->z); - - switch (target->getType()) { - case building_type::Weaponrack: - job->job_type = job_type::StoreWeapon; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Armorstand: - job->job_type = job_type::StoreArmor; - job->flags.bits.specific_dropoff = true; - break; - case building_type::Cabinet: - job->job_type = job_type::StoreItemInCabinet; - break; - default: - job->job_type = job_type::StoreItemInChest; - break; - } - - if (!Job::attachJobItem(job, item, df::job_item_ref::Hauled)) - { - delete job; - delete href; - continue; - } - - href->building_id = target->id; - target->jobs.push_back(job); - job->references.push_back(href); - - Job::linkIntoWorld(job); - return; } } +// Store the items into appropriate armory buildings static void try_store_item_set(std::vector &items, df::squad *squad, df::squad_position *pos) { for (size_t j = 0; j < items.size(); j++) { auto item = df::item::find(items[j]); + // bad id, or cooldown timer still counting + if (!item || item->stockpile_countdown > 0) + continue; + + // not loose if (!can_store_item(item)) continue; @@ -264,9 +366,11 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev if (!is_enabled) return CR_OK; + // Process every 50th frame, sort of like regular stockpiling does if (DF_GLOBAL_VALUE(cur_year_tick,1) % 50 != 0) return CR_OK; + // Loop over squads auto &squads = df::global::world->squads.all; for (size_t si = 0; si < squads.size(); si++) @@ -304,6 +408,17 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); } static void enable_plugin(color_ostream &out) From b46885bb3cdca263d669565954f3d924e32a36e0 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 30 Oct 2012 11:48:28 -0500 Subject: [PATCH 126/472] Fix compile --- plugins/dfstream.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 6144389d7..1ed906881 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -339,7 +339,7 @@ auto_renderer_decorator decorator; DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!df::renderer::_identity.can_allocate()) + if (!df::renderer::_identity.can_instantiate()) { out.printerr("Cannot allocate a renderer\n"); return CR_OK; From c4f544d796a7f3e319303728f54bd5e53e08de99 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 30 Oct 2012 13:50:14 -0500 Subject: [PATCH 127/472] Fix stupid MSVC --- plugins/fix-armory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 6bfdfe421..9162c9456 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -172,7 +172,7 @@ template struct armory_hook : Item { { auto holder = Items::getHolderUnit(this); - if (holder && binsearch_index(holder->military.uniform_drop, this->id) >= 0) + if (holder && ::binsearch_index(holder->military.uniform_drop, this->id) >= 0) { if (is_assigned_item(this)) return false; From 05b73af9bb23ff379dc2904f2d4891e708be09de Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 30 Oct 2012 23:20:34 +0400 Subject: [PATCH 128/472] Extend fix-armory with support for storing ammo in barracks. --- library/xml | 2 +- plugins/fix-armory.cpp | 200 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 196 insertions(+), 6 deletions(-) diff --git a/library/xml b/library/xml index 378f91d09..fcacacce7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 378f91d0964e09e3cfa0ecbea100ef2cc12a59ab +Subproject commit fcacacce7cf09cf70f011fea87b5be416da73457 diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 9162c9456..56374fdad 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -22,6 +22,7 @@ #include "df/squad.h" #include "df/unit.h" #include "df/squad_position.h" +#include "df/squad_ammo_spec.h" #include "df/items_other_id.h" #include "df/item_weaponst.h" #include "df/item_armorst.h" @@ -33,12 +34,15 @@ #include "df/item_flaskst.h" #include "df/item_backpackst.h" #include "df/item_quiverst.h" +#include "df/item_ammost.h" #include "df/building_weaponrackst.h" #include "df/building_armorstandst.h" #include "df/building_cabinetst.h" #include "df/building_boxst.h" +#include "df/building_squad_use.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" +#include "df/general_ref_building_destinationst.h" #include "df/barrack_preference_category.h" #include @@ -111,6 +115,67 @@ static bool is_assigned_item(df::item *item) return true; } +static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) +{ + for (size_t i = 0; i < squad->ammunition.size(); i++) + { + auto spec = squad->ammunition[i]; + bool cs = spec->flags.bits.use_combat; + bool ts = spec->flags.bits.use_training; + + if (((cs || !ts) && combat) || (ts && train)) + { + if (binsearch_index(spec->assigned, item->id) >= 0) + return true; + } + } + + return false; +} + +static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) +{ + auto squads = holder->getSquads(); + + if (squads) + { + bool target = holder->getType() == building_type::ArcheryTarget; + + for (size_t i = 0; i < squads->size(); i++) + { + auto use = (*squads)[i]; + + if (squad_id >= 0 && use->squad_id != squad_id) + continue; + + bool combat = use->mode.bits.squad_eq; + bool train = target && use->mode.bits.train; + + if (combat || train) + { + auto squad = df::squad::find(use->squad_id); + + if (squad && is_squad_ammo(item, squad, combat, train)) + return true; + } + } + } + + for (size_t i = 0; i < holder->parents.size(); i++) + if (can_store_ammo_rec(item, holder->parents[i], squad_id)) + return true; + + return false; +} + +static bool can_store_ammo(df::item *item, df::building *holder) +{ + if (holder->getType() != building_type::Box) + return false; + + return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); +} + // Check if the item is assigned to the squad member who owns this armory building static bool belongs_to_position(df::item *item, df::building *holder) { @@ -154,7 +219,10 @@ static bool is_in_armory(df::item *item) return false; // If indeed in a building, check if it is the right one - return belongs_to_position(item, holder); + if (item->getType() == item_type::AMMO) + return can_store_ammo(item, holder); + else + return belongs_to_position(item, holder); } template struct armory_hook : Item { @@ -196,6 +264,8 @@ template struct armory_hook : Item { if (erase_from_vector(ovec, &df::item::id, this->id)) { // and queue it to be checked normally in 0.5-1 in-game days + // (this is a grace period in case the uniform is dropped just + // for a moment due to a momentary glitch) this->stockpile_countdown = 12 + random_int(12); this->stockpile_delay = 0; } @@ -215,6 +285,7 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollect template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, isCollected); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); @@ -226,10 +297,16 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGro template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); // Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { + // bad id, or cooldown timer still counting + if (!item || item->stockpile_countdown > 0) + return false; + + // bad flags? if (item->flags.bits.in_job || item->flags.bits.removed || item->flags.bits.in_building || @@ -239,6 +316,7 @@ static bool can_store_item(df::item *item) item->flags.bits.on_fire) return false; + // in unit inventory? auto top = item; while (top->flags.bits.in_inventory) @@ -251,6 +329,7 @@ static bool can_store_item(df::item *item) if (Items::getGeneralRef(top, general_ref_type::UNIT_HOLDER)) return false; + // already in armory? if (is_in_armory(item)) return false; @@ -280,6 +359,8 @@ static bool try_store_item(df::building *target, df::item *item) job->pos = tpos; + bool dest = false; + // Choose the job type - correct matching is needed so that // later canStoreItem calls would take the job into account. switch (target->getType()) { @@ -295,9 +376,11 @@ static bool try_store_item(df::building *target, df::item *item) break; case building_type::Cabinet: job->job_type = job_type::StoreItemInCabinet; + dest = true; break; default: job->job_type = job_type::StoreItemInChest; + dest = true; break; } @@ -314,6 +397,17 @@ static bool try_store_item(df::building *target, df::item *item) target->jobs.push_back(job); job->references.push_back(href); + if (dest) + { + auto rdest = df::allocate(); + + if (rdest) + { + rdest->building_id = target->id; + job->references.push_back(rdest); + } + } + // add to job list Job::linkIntoWorld(job); return true; @@ -340,10 +434,6 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df { auto item = df::item::find(items[j]); - // bad id, or cooldown timer still counting - if (!item || item->stockpile_countdown > 0) - continue; - // not loose if (!can_store_item(item)) continue; @@ -359,6 +449,102 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df } } +// Use a data structure sorted by free space, to even out the load +typedef std::map > ammo_box_set; + +static void index_boxes(df::building *root, ammo_box_set &group) +{ + if (root->getType() == building_type::Box) + { + //color_ostream_proxy out(Core::getInstance().getConsole()); + //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + + group[root->getFreeCapacity(true)].insert(root); + } + + for (size_t i = 0; i < root->children.size(); i++) + index_boxes(root->children[i], group); +} + +static bool try_store_ammo(df::item *item, ammo_box_set &group) +{ + int volume = item->getVolume(); + + for (auto it = group.rbegin(); it != group.rend(); ++it) + { + if (it->first < volume) + break; + + for (auto it2 = it->second.begin(); it2 != it->second.end(); ++it2) + { + auto bld = *it2; + + if (try_store_item(bld, item)) + { + it->second.erase(bld); + group[bld->getFreeCapacity(true)].insert(bld); + return true; + } + } + } + + return false; +} + +// Collect chests for ammo storage +static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box_set &combat_set) +{ + for (size_t j = 0; j < squad->rooms.size(); j++) + { + auto room = squad->rooms[j]; + auto bld = df::building::find(room->building_id); + + // Chests in rooms marked for Squad Equipment used for combat ammo + if (room->mode.bits.squad_eq) + index_boxes(bld, combat_set); + + // Chests in archery ranges used for training-only ammo + if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) + index_boxes(bld, train_set); + } +} + +// Store ammo into appropriate chests +static void try_store_ammo(df::squad *squad) +{ + bool indexed = false; + ammo_box_set train_set, combat_set; + + for (size_t i = 0; i < squad->ammunition.size(); i++) + { + auto spec = squad->ammunition[i]; + bool cs = spec->flags.bits.use_combat; + bool ts = spec->flags.bits.use_training; + + for (size_t j = 0; j < spec->assigned.size(); j++) + { + auto item = df::item::find(spec->assigned[j]); + + // not loose + if (!can_store_item(item)) + continue; + + if (!indexed) + { + indexed = true; + index_ammo_boxes(squad, train_set, combat_set); + } + + if (cs && try_store_ammo(item, combat_set)) + continue; + if (ts && try_store_ammo(item, train_set)) + continue; + if (!(ts || cs) && try_store_ammo(item, combat_set)) + continue; + } + } +} + static bool is_enabled = false; DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_event event) @@ -383,6 +569,8 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out, state_change_ev try_store_item_set(pos->assigned_items, squad, pos); } + + try_store_ammo(squad); } return CR_OK; @@ -408,6 +596,7 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, isCollected), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); @@ -419,6 +608,7 @@ static void enable_hooks(color_ostream &out, bool enable) enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); + enable_hook(out, INTERPOSE_HOOK(armory_hook, moveToGround), enable); } static void enable_plugin(color_ostream &out) From 69bdb0f8b8e5e0c1dbfb9a86695000d9e4c1e992 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 31 Oct 2012 11:31:31 +0400 Subject: [PATCH 129/472] Update fix-armory documentation and comments. --- NEWS | 6 +- Readme.html | 26 ++++++-- Readme.rst | 30 +++++++-- plugins/fix-armory.cpp | 147 +++++++++++++++++++++++++++++++++++++---- 4 files changed, 180 insertions(+), 29 deletions(-) diff --git a/NEWS b/NEWS index 810f0cd55..0f9bf4c3d 100644 --- a/NEWS +++ b/NEWS @@ -10,8 +10,6 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - New commands: - - fix-armory: activates a plugin that makes armor stands and weapon racks be used again. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. @@ -21,6 +19,10 @@ DFHack future - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. + New Fix Armory plugin: + Together with a couple of binary patches and the gui/assign-rack script, + this plugin makes weapon racks, armor stands, chests and cabinets in + properly designated barracks be used again for storage of squad equipment. DFHack v0.34.11-r2 diff --git a/Readme.html b/Readme.html index 0fa1d10be..0da481c88 100644 --- a/Readme.html +++ b/Readme.html @@ -1862,14 +1862,15 @@ armor stands, and in containers.

                                              Note

                                              In order to actually be used, weapon racks have to be patched and -assigned to a squad. See documentation for gui/assign-rack below.

                                              +manually assigned to a squad. See documentation for gui/assign-rack +below.

                                              Also, the default capacity of armor stands is way too low, so check out http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 for a patch addressing that too.

                                              Note that the buildings in the armory are used as follows:

                                                -
                                              • Weapon racks when fixed are used to store any assigned weapons. +
                                              • Weapon racks (when patched) are used to store any assigned weapons. Each rack belongs to a specific squad, and can store up to 5 weapons.
                                              • Armor stands belong to specific squad members and are used for armor and shields. By default one stand can store one item of each @@ -1878,11 +1879,9 @@ which should be sufficient.
                                              • Cabinets are used to store assigned clothing for a specific squad member. They are never used to store owned clothing.
                                              • Chests (boxes, etc) are used for a flask, backpack or quiver assigned -to the squad member. Due to a bug, food is dropped out of the backpack -when it is stored.
                                              • +to the squad member. Due to a probable bug, food is dropped out of the +backpack when it is stored.
                                              -

                                              Contrary to the common misconception, all these uses are controlled by the -Individual Equipment usage flag; the Squad Equipment mode means nothing.

                                              Warning

                                              Although armor stands, cabinets and chests properly belong only to one @@ -1890,6 +1889,21 @@ squad member, the owner of the building used to create the barracks will randomly use any containers inside the room. Thus, it is recommended to always create the armory from a weapon rack.

                                              +

                                              Contrary to the common misconception, all these uses are controlled by the +Individual Equipment usage flag. The Squad Equipment flag is actually +intended for ammo, but the game does even less in that area than for armor +and weapons. This plugin implements the following rules almost from scratch:

                                              +
                                                +
                                              • Combat ammo is stored in chests inside rooms with Squad Equipment enabled.
                                              • +
                                              • If a chest is assigned to a squad member due to Individual Equipment also +being set, it is only used for that squad's ammo; otherwise, any squads +with Squad Equipment on the room will use all of the chests at random.
                                              • +
                                              • Training ammo is stored in chests inside archery ranges designated from +archery targets, and controlled by the same Train flag as archery training +itself. This is inspired by some defunct code for weapon racks.
                                              • +
                                              +

                                              There are some minor traces in the game code to suggest that the first of +these rules is intended by Toady; the rest are invented by this plugin.

                                              diff --git a/Readme.rst b/Readme.rst index ad42035a5..0b2fbc378 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1084,7 +1084,8 @@ armor stands, and in containers. .. note:: In order to actually be used, weapon racks have to be patched and - assigned to a squad. See documentation for ``gui/assign-rack`` below. + manually assigned to a squad. See documentation for ``gui/assign-rack`` + below. Also, the default capacity of armor stands is way too low, so check out http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 @@ -1092,7 +1093,7 @@ armor stands, and in containers. Note that the buildings in the armory are used as follows: -* Weapon racks when fixed are used to store any assigned weapons. +* Weapon racks (when patched) are used to store any assigned weapons. Each rack belongs to a specific squad, and can store up to 5 weapons. * Armor stands belong to specific squad members and are used for @@ -1104,11 +1105,8 @@ Note that the buildings in the armory are used as follows: They are **never** used to store owned clothing. * Chests (boxes, etc) are used for a flask, backpack or quiver assigned - to the squad member. Due to a bug, food is dropped out of the backpack - when it is stored. - -Contrary to the common misconception, all these uses are controlled by the -*Individual Equipment* usage flag; the Squad Equipment mode means nothing. + to the squad member. Due to a probable bug, food is dropped out of the + backpack when it is stored. .. warning:: @@ -1117,6 +1115,24 @@ Contrary to the common misconception, all these uses are controlled by the randomly use any containers inside the room. Thus, it is recommended to always create the armory from a weapon rack. +Contrary to the common misconception, all these uses are controlled by the +*Individual Equipment* usage flag. The *Squad Equipment* flag is actually +intended for ammo, but the game does even less in that area than for armor +and weapons. This plugin implements the following rules almost from scratch: + +* Combat ammo is stored in chests inside rooms with Squad Equipment enabled. + +* If a chest is assigned to a squad member due to Individual Equipment also + being set, it is only used for that squad's ammo; otherwise, any squads + with Squad Equipment on the room will use all of the chests at random. + +* Training ammo is stored in chests inside archery ranges designated from + archery targets, and controlled by the same Train flag as archery training + itself. This is inspired by some defunct code for weapon racks. + +There are some minor traces in the game code to suggest that the first of +these rules is intended by Toady; the rest are invented by this plugin. + Mode switch and reclaim ======================= diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 56374fdad..ade9e4252 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -90,6 +90,48 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) return CR_OK; } +/* + * PART 1 - Stop restockpiling of items stored in the armory. + * + * For everything other than ammo this is quite straightforward, + * since the uniform switch code already tries to store items + * in barracks containers, and it is thus known what the intention + * is. Moreover these containers know which squad and member they + * belong to. + * + * For ammo there is no such code (in fact, ammo is never removed + * from a quiver, except when it is dropped itself), so I had to + * apply some improvisation. There is one place where BOX containers + * with Squad Equipment set are used as an anchor location for a + * pathfinding check when assigning ammo, so presumably that's + * the correct place. I however wanted to also differentiate + * training ammo, so came up with the following rules: + * + * 1. Combat ammo and ammo without any allowed use can be stored + * in BOXes marked for Squad Equipment, either directly or via + * containing room. No-allowed-use ammo is assumed to be reserved + * for emergency combat use, or something like that. + * 1a. If assigned to a squad position, that box can be used _only_ + * for ammo assigned to that specific _squad_. Otherwise, if + * multiple squads can use this room, they will store their + * ammo all mixed up. + * 2. Training ammo can be stored in BOXes within archery ranges + * (designated from archery target) that are enabled for Training. + * Train-only ammo in particular can _only_ be stored in such + * boxes. The inspiration for this comes from some broken code + * for weapon racks in Training rooms. + * + * As an additional feature (partially needed due to the constraints + * of working from an external hack), this plugin also blocks instant + * queueing of stockpiling jobs for items blocked on the ground, if + * these items are assigned to any squad. + * + * Since there apparently still are bugs that cause uniform items to be + * momentarily dropped on ground, this delay is set not to the minimally + * necessary 50 ticks, but to 0.5 - 1.0 in-game days, so as to provide a + * grace period during which the items can be instantly picked up again. + */ + // Check if the item is assigned to any use controlled by the military tab static bool is_assigned_item(df::item *item) { @@ -104,6 +146,8 @@ static bool is_assigned_item(df::item *item) // Exclude weapons used by miners, wood cutters etc switch (type) { case item_type::WEAPON: + // the game code also checks this for ammo, funnily enough + // maybe it's not just for weapons?.. if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) return false; break; @@ -115,6 +159,7 @@ static bool is_assigned_item(df::item *item) return true; } +// Check if this ammo item is assigned to this squad with one of the specified uses static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) { for (size_t i = 0; i < squad->ammunition.size(); i++) @@ -123,6 +168,7 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool com bool cs = spec->flags.bits.use_combat; bool ts = spec->flags.bits.use_training; + // no-use ammo assumed to be combat if (((cs || !ts) && combat) || (ts && train)) { if (binsearch_index(spec->assigned, item->id) >= 0) @@ -133,6 +179,7 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool com return false; } +// Recursively check room parents to find out if this ammo item is allowed here static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_id) { auto squads = holder->getSquads(); @@ -145,10 +192,13 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i { auto use = (*squads)[i]; + // For containers assigned to a squad, only consider that squad if (squad_id >= 0 && use->squad_id != squad_id) continue; + // Squad Equipment -> combat bool combat = use->mode.bits.squad_eq; + // Archery target with Train -> training bool train = target && use->mode.bits.train; if (combat || train) @@ -168,11 +218,14 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i return false; } +// Check if the ammo item can be stored in this container static bool can_store_ammo(df::item *item, df::building *holder) { + // Only chests if (holder->getType() != building_type::Box) return false; + // with appropriate flags set return can_store_ammo_rec(item, holder, holder->getSpecificSquad()); } @@ -225,33 +278,71 @@ static bool is_in_armory(df::item *item) return belongs_to_position(item, holder); } +/* + * Hooks used to affect stockpiling code as it runs, and prevent it + * from doing unwanted stuff. + * + * Toady can simply add these checks directly to the stockpiling code; + * we have to abuse some handy item vmethods. + */ + template struct armory_hook : Item { typedef Item interpose_base; + /* + * This vmethod is called by the actual stockpiling code before it + * tries to queue a job, and is normally used to prevent stockpiling + * of uncollected webs. + */ DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { - // Normally this vmethod is used to prevent stockpiling of uncollected webs. - // This uses it to also block stockpiling of items in the armory. + // Block stockpiling of items in the armory. if (is_in_armory(this)) return false; - // Also never let items in process of being removed from uniform be stockpiled at once + /* + * When an item is removed from inventory due to Pickup Equipment + * process, the unit code directly invokes the stockpiling code + * and thus creates the job even before the item is actually dropped + * on the ground. We don't want this at all, especially due to the + * grace period idea. + * + * With access to source, that code can just be changed to simply + * drop the item on ground, without running stockpiling code. + */ if (this->flags.bits.in_inventory) { auto holder = Items::getHolderUnit(this); - if (holder && ::binsearch_index(holder->military.uniform_drop, this->id) >= 0) + // When that call happens, the item is still in inventory + if (holder && is_assigned_item(this)) { - if (is_assigned_item(this)) + // And its ID is is this vector + if (::binsearch_index(holder->military.uniform_drop, this->id) >= 0) return false; } } + // Call the original vmethod return INTERPOSE_NEXT(isCollected)(); } + /* + * This vmethod is used to actually put the item on the ground. + * When it does that, it also adds it to a vector of items to be + * instanly restockpiled by a loop in another part of the code. + * + * We don't want this either, even more than when removing from + * uniform, because this can happen in lots of various situations, + * including deconstructed containers etc, and we want our own + * armory code below to have a chance to look at the item. + * + * The logical place for this code is in the loop that processes + * that vector, but that part is not virtual. + */ DEFINE_VMETHOD_INTERPOSE(bool, moveToGround, (int16_t x, int16_t y, int16_t z)) { + // First, let it do its work bool rv = INTERPOSE_NEXT(moveToGround)(x, y, z); // Prevent instant restockpiling of dropped assigned items. @@ -299,6 +390,15 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToG template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); template<> IMPLEMENT_VMETHOD_INTERPOSE(armory_hook, moveToGround); +/* + * PART 2 - Actively queue jobs to haul assigned items to the armory. + * + * The logical place for this of course is in the same code that decides + * to put stuff in stockpiles, alongside the checks to prevent moving + * stuff away from the armory. We just run it independently every 50 + * simulation frames. + */ + // Check if this item is loose and can be moved to armory static bool can_store_item(df::item *item) { @@ -397,6 +497,8 @@ static bool try_store_item(df::building *target, df::item *item) target->jobs.push_back(job); job->references.push_back(href); + // Two of the jobs need this link to find the job in canStoreItem(). + // They also don't actually need BUILDING_HOLDER, but it doesn't hurt. if (dest) { auto rdest = df::allocate(); @@ -438,6 +540,7 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df if (!can_store_item(item)) continue; + // queue jobs to put it in the appropriate container if (item->isWeapon()) try_store_item(squad->rack_combat, item); else if (item->isClothing()) @@ -449,23 +552,30 @@ static void try_store_item_set(std::vector &items, df::squad *squad, df } } -// Use a data structure sorted by free space, to even out the load +// For storing ammo, use a data structure sorted by free space, to even out the load typedef std::map > ammo_box_set; -static void index_boxes(df::building *root, ammo_box_set &group) +// Enumerate boxes in the room, adding them to the set +static void index_boxes(df::building *root, ammo_box_set &group, int squad_id) { if (root->getType() == building_type::Box) { - //color_ostream_proxy out(Core::getInstance().getConsole()); - //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + int id = root->getSpecificSquad(); - group[root->getFreeCapacity(true)].insert(root); + if (id < 0 || id == squad_id) + { + //color_ostream_proxy out(Core::getInstance().getConsole()); + //out.print("%08x %d\n", unsigned(root), root->getFreeCapacity(true)); + + group[root->getFreeCapacity(true)].insert(root); + } } for (size_t i = 0; i < root->children.size(); i++) - index_boxes(root->children[i], group); + index_boxes(root->children[i], group, squad_id); } +// Loop over the set from most empty to least empty static bool try_store_ammo(df::item *item, ammo_box_set &group) { int volume = item->getVolume(); @@ -501,11 +611,11 @@ static void index_ammo_boxes(df::squad *squad, ammo_box_set &train_set, ammo_box // Chests in rooms marked for Squad Equipment used for combat ammo if (room->mode.bits.squad_eq) - index_boxes(bld, combat_set); + index_boxes(bld, combat_set, squad->id); - // Chests in archery ranges used for training-only ammo + // Chests in archery ranges used for training ammo if (room->mode.bits.train && bld->getType() == building_type::ArcheryTarget) - index_boxes(bld, train_set); + index_boxes(bld, train_set, squad->id); } } @@ -529,16 +639,25 @@ static void try_store_ammo(df::squad *squad) if (!can_store_item(item)) continue; + // compute the maps lazily if (!indexed) { indexed = true; index_ammo_boxes(squad, train_set, combat_set); } + // BUG: if the same container is in both sets, + // when a job is queued, the free space in the other + // set will not be updated, which could lead to uneven + // loading - but not to overflowing the container! + + // As said above, combat goes into Squad Equipment if (cs && try_store_ammo(item, combat_set)) continue; + // Training goes into Archery Range with Train if (ts && try_store_ammo(item, train_set)) continue; + // No use goes into combat if (!(ts || cs) && try_store_ammo(item, combat_set)) continue; } From 0a78064467f75accc112eaf56dffacabc87535b9 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 31 Oct 2012 16:11:06 +0100 Subject: [PATCH 130/472] ruby: raise on invalid enum symbols --- plugins/ruby/ruby-autogen-defs.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index d5bcb08f4..c3203bd52 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -181,7 +181,8 @@ module DFHack @nume ||= const_get(:NUME) end - def self.int(i) + def self.int(i, allow_bad_sym=false) + raise ArgumentError, "invalid enum member #{i} of #{self}" if i.kind_of?(::Symbol) and not allow_bad_sym and not nume.has_key?(i) nume[i] || i end def self.sym(i) @@ -797,7 +798,6 @@ module DFHack def isset(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_isset(@_memaddr, key) end alias is_set? isset @@ -805,14 +805,12 @@ module DFHack def set(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_set(@_memaddr, key) end def delete(key) raise unless @_memaddr key = @_enum.int(key) if _enum - raise "unknown key #{key.inspect}" if key.kind_of?(::Symbol) DFHack.memory_stlset_deletekey(@_memaddr, key) end From 209d593f2173e70085fe287d7635c03d66b94cd2 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 1 Nov 2012 16:00:00 +0200 Subject: [PATCH 131/472] Another day, another commit. --- plugins/Dfusion/luafiles/embark/init.lua | 146 +++++++++++++--------- plugins/lua/dfusion.lua | 19 ++- plugins/lua/dfusion/embark.lua | 148 +++++++++++++++++++++++ plugins/lua/dfusion/embark.o | Bin 0 -> 369 bytes scripts/dfusion.lua | 40 ++++++ 5 files changed, 289 insertions(+), 64 deletions(-) create mode 100644 plugins/lua/dfusion/embark.lua create mode 100644 plugins/lua/dfusion/embark.o diff --git a/plugins/Dfusion/luafiles/embark/init.lua b/plugins/Dfusion/luafiles/embark/init.lua index 76be00c72..b7795d049 100644 --- a/plugins/Dfusion/luafiles/embark/init.lua +++ b/plugins/Dfusion/luafiles/embark/init.lua @@ -2,68 +2,94 @@ local dfu=require("plugins.dfusion") local ms=require("memscan") local MAX_RACES=100 CustomEmbark=defclass(CustomEmbark,dfu.BinaryPlugin) -CustomEmbark.ATTRS{filename="dfusion/embark/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} -function CustomEmbark:install() - local stoff=dfhack.internal.getAddress('start_dwarf_count') - if #self.race_caste_data<7 then - error("caste and race count must be bigger than 6") +local myos=dfhack.getOSType() +if myos=="windows" then + + CustomEmbark.ATTRS{filename="dfusion/embark/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} + CustomEmbark.class_status="valid, not installed" + function CustomEmbark:install() + local stoff=dfhack.internal.getAddress('start_dwarf_count') + + if #self.race_caste_data<7 then + error("caste and race count must be bigger than 6") + end + if #self.race_caste_data>MAX_RACES then + error("caste and race count must be less then "..MAX_RACES) + end + if stoff==nil then + error("address for start_dwarf_count not found!") + end + local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) + print(string.format("start=%08x",stoff)) + local needle={0x0f,0xb7,0x0d} --movzx eax,dword ptr [race_id] + local tmp_table=dfu.dwordToTable(race_id_offset) + for k,v in ipairs(tmp_table) do + table.insert(needle,v) + end + + local mem=ms.get_code_segment() + print(mem.uint8_t:addr2idx(stoff)) + print(mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff))) + local _,trg_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff),nil)--maybe endoff=stoff+bignumber + if trg_offset==nil then + error("address for race_load not found") + end + local call_data={0x90,0x90} + local _,data_offset=df.sizeof(self.data) + dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) + self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} + needle={0x83,0xc8,0xff} -- or eax, 0xFF + local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) + if caste_offset==nil or caste_offset-stoff>1000 then + error("Caste change code not found or found too far!") + end + + self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} + self.disable_castes:apply() + self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} + self.dwarfcount:apply() + local caste_array=self:allocate("caste_array","uint16_t",#self.race_caste_data) + local race_array=self:allocate("race_array","uint16_t",#self.race_caste_data) + self:setEmbarkParty(self.race_caste_data) + for k,v in ipairs(self.race_caste_data) do + caste_array[k-1]=v[2] + race_array[k-1]=v[1] + end + local race_array_off,caste_array_off + local _ + _,race_array_off=df.sizeof(race_array) + _,caste_array_off=df.sizeof(caste_array) + self:set_marker_dword("race",caste_array_off) --hehe... mixed them up i guess... + self:set_marker_dword("caste",race_array_off) + + self:move_to_df() + self.call_patch:apply() + self.installed=true end - if #self.race_caste_data>MAX_RACES then - error("caste and race count must be less then "..MAX_RACES) + function CustomEmbark:setEmbarkParty(racesAndCastes) + self.race_caste_data=racesAndCastes + if self.dwarfcount== nil then + self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} + self.dwarfcount:apply() + else + self.dwarfcount:repatch(dfu.dwordToTable(#self.race_caste_data)) + end + end - if stoff==nil then - error("address for start_dwarf_count not found!") + function CustomEmbark:status() + if self.installed then + return "valid, installed" + else + return "valid, not installed" + end end - local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) - print(string.format("start=%08x",stoff)) - local needle={0x0f,0xb7,0x0d} --movzx eax,dword ptr [race_id] - local tmp_table=dfu.dwordToTable(race_id_offset) - for k,v in ipairs(tmp_table) do - table.insert(needle,v) - end - - local mem=ms.get_code_segment() - print(mem.uint8_t:addr2idx(stoff)) - print(mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff))) - local _,trg_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff),nil)--maybe endoff=stoff+bignumber - if trg_offset==nil then - error("address for race_load not found") - end - local call_data={0x90,0x90} - local _,data_offset=df.sizeof(self.data) - dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) - self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} - needle={0x83,0xc8,0xff} -- or eax, 0xFF - local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) - if caste_offset==nil or caste_offset-stoff>1000 then - error("Caste change code not found or found too far!") - end - - self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} - self.disable_castes:apply() - self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} - self.dwarfcount:apply() - local caste_array=self:allocate("caste_array","uint16_t",#self.race_caste_data) - local race_array=self:allocate("race_array","uint16_t",#self.race_caste_data) - for k,v in ipairs(self.race_caste_data) do - caste_array[k-1]=v[2] - race_array[k-1]=v[1] - end - local race_array_off,caste_array_off - local _ - _,race_array_off=df.sizeof(race_array) - _,caste_array_off=df.sizeof(caste_array) - self:set_marker_dword("race",caste_array_off) --hehe... mixed them up i guess... - self:set_marker_dword("caste",race_array_off) - - self:move_to_df() - self.call_patch:apply() - self.installed=true -end -function CustomEmbark:uninstall() - if self.installed then - self.call_patch:remove() - self.disable_castes:remove() - self.dwarfcount:remove() + function CustomEmbark:uninstall() + if self.installed then + self.call_patch:remove() + self.disable_castes:remove() + self.dwarfcount:remove() + end end +else + CustomEmbark.class_status="invalid, os not supported" end \ No newline at end of file diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua index ac9a20868..053c486cf 100644 --- a/plugins/lua/dfusion.lua +++ b/plugins/lua/dfusion.lua @@ -107,6 +107,7 @@ end -- A binary hack (obj file) loader/manager -- has to have: a way to get offsets for marked areas (for post load modification) or some way to do that pre-load -- page managing (including excecute/write flags for DEP and the like) +-- TODO plugin state enum, a way to modify post install (could include repatching code...) plugins=plugins or {} BinaryPlugin=defclass(BinaryPlugin) BinaryPlugin.ATTRS {filename=DEFAULT_NIL,reloc_table={},name=DEFAULT_NIL} @@ -115,14 +116,21 @@ function BinaryPlugin:init(args) end function BinaryPlugin:postinit(args) if self.name==nil then error("Not a valid plugin name!") end - --if plugins[args.name]==nil then + if plugins[args.name]==nil then plugins[self.name]=self - --else - -- error("Trying to create a same plugin") - --end + else + error("Trying to create a same plugin") + end self.allocated_object={} self:load() end +function BinaryPlugin:get_or_alloc(name,typename,arrsize) + if self.allocated_object[name]~=nil then + return self.allocated_object[name] + else + return self:allocate(name,typename,arrsize) + end +end function BinaryPlugin:allocate(name,typename,arrsize) local trg if df[typename]==nil then @@ -182,6 +190,9 @@ function BinaryPlugin:print_data() end print(out) end +function BinaryPlugin:status() + return "invalid, base class only!" +end function BinaryPlugin:__gc() for k,v in pairs(self.allocated_object) do df.delete(v) diff --git a/plugins/lua/dfusion/embark.lua b/plugins/lua/dfusion/embark.lua new file mode 100644 index 000000000..a674a96d3 --- /dev/null +++ b/plugins/lua/dfusion/embark.lua @@ -0,0 +1,148 @@ +local _ENV = mkmodule('plugins.dfusion.embark') +local dfu=require("plugins.dfusion") +local ms=require("memscan") +local MAX_RACES=100 +CustomEmbark=defclass(CustomEmbark,dfu.BinaryPlugin) +CustomEmbark.name="CustomEmbark" +local myos=dfhack.getOSType() +if myos=="windows" then + CustomEmbark.ATTRS{filename="hack/lua/plugins/dfusion/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} + CustomEmbark.class_status="valid, not installed" + function CustomEmbark:install() + local stoff=dfhack.internal.getAddress('start_dwarf_count') + + if #self.race_caste_data<7 then + error("caste and race count must be bigger than 6") + end + if #self.race_caste_data>MAX_RACES then + error("caste and race count must be less then "..MAX_RACES) + end + if stoff==nil then + error("address for start_dwarf_count not found!") + end + local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) + print(string.format("start=%08x",stoff)) + local needle={0x0f,0xb7,0x0d} --movzx eax,dword ptr [race_id] + local tmp_table=dfu.dwordToTable(race_id_offset) + for k,v in ipairs(tmp_table) do + table.insert(needle,v) + end + + local mem=ms.get_code_segment() + print(mem.uint8_t:addr2idx(stoff)) + print(mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff))) + local _,trg_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff),nil)--maybe endoff=stoff+bignumber + if trg_offset==nil then + error("address for race_load not found") + end + local call_data={0x90,0x90} + local _,data_offset=df.sizeof(self.data) + dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) + self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} + needle={0x83,0xc8,0xff} -- or eax, 0xFF + local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) + if caste_offset==nil or caste_offset-stoff>1000 then + error("Caste change code not found or found too far!") + end + + self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} + self.disable_castes:apply() + + + self:setEmbarkParty(self.race_caste_data) + local caste_array=self:get_or_alloc("caste_array","uint16_t",MAX_RACES) + local race_array=self:get_or_alloc("race_array","uint16_t",MAX_RACES) + + local race_array_off,caste_array_off + local _ + _,race_array_off=df.sizeof(race_array) + _,caste_array_off=df.sizeof(caste_array) + self:set_marker_dword("race",caste_array_off) --hehe... mixed them up i guess... + self:set_marker_dword("caste",race_array_off) + + self:move_to_df() + self.call_patch:apply() + self.installed=true + end + function CustomEmbark:setEmbarkParty(racesAndCastes) + local stoff=dfhack.internal.getAddress('start_dwarf_count') + if #racesAndCastes<7 then + error("caste and race count must be bigger than 6") + end + if #racesAndCastes>MAX_RACES then + error("caste and race count must be less then "..MAX_RACES) + end + + self.race_caste_data=racesAndCastes + if self.dwarfcount== nil then + self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} + self.dwarfcount:apply() + else + self.dwarfcount:repatch(dfu.dwordToTable(#self.race_caste_data)) + end + local caste_array=self:get_or_alloc("caste_array","uint16_t",MAX_RACES) + local race_array=self:get_or_alloc("race_array","uint16_t",MAX_RACES) + for k,v in ipairs(self.race_caste_data) do + caste_array[k-1]=v[2] + race_array[k-1]=v[1] + end + end + function CustomEmbark:status() + if self.installed then + return "valid, installed" + else + return "valid, not installed" + end + end + function CustomEmbark:uninstall() + if self.installed then + self.call_patch:remove() + self.disable_castes:remove() + self.dwarfcount:remove() + end + end + function CustomEmbark:edit() + local data=self.race_caste_data or {} + print(string.format("Old race count:%d",#data)) + local endthis=false + print("current:") + while(not endthis) do + print(" # RaceId Race name Caste num") + for k,v in pairs(data) do + local name=df.creature_raw.find(v[1]).creature_id or "" + print(string.format("%3d. %6d %20s %d",k,v[1],name,v[2])) + end + print("a- add, r-remove, c-cancel, s-save and update") + local choice=io.stdin:read() + if choice=='a' then + print("Enter new race then caste ids:") + local race=tonumber(io.stdin:read()) + local caste=tonumber(io.stdin:read()) + if race and caste then + table.insert(data,{race,caste}) + else + print("input parse error") + end + elseif choice=='r' then + print("enter number to remove:") + local num_rem=tonumber(io.stdin:read()) + if num_rem~=nil then + table.remove(data,num_rem) + end + elseif choice=='c' then + endthis=true + elseif choice=='s' then + endthis=true + if self.installed then + self:setEmbarkParty(data) + else + self.race_caste_data=data + self:install() + end + end + end + end +else + CustomEmbark.class_status="invalid, os not supported" +end +return _ENV \ No newline at end of file diff --git a/plugins/lua/dfusion/embark.o b/plugins/lua/dfusion/embark.o new file mode 100644 index 0000000000000000000000000000000000000000..87f5bbd68f5c8d0aaf6f81485f653a440145a952 GIT binary patch literal 369 zcmeZaWM%+?B|yvtX0bBrm84dbfY}g20!Z~B@j*-l27?5>l*E!mG;wsU1B1Z Date: Fri, 2 Nov 2012 00:28:16 +0200 Subject: [PATCH 132/472] New way of doing things! Now using a class for menus, also no (non script) way to use bin-plugins. --- plugins/Dfusion/luafiles/adv_tools/init.lua | 133 -------------------- plugins/Dfusion/luafiles/common.lua | 32 +---- plugins/lua/dfusion.lua | 35 ++++++ plugins/lua/dfusion/adv_tools.lua | 52 ++++++++ scripts/dfusion.lua | 47 ++----- 5 files changed, 96 insertions(+), 203 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/adv_tools/init.lua create mode 100644 plugins/lua/dfusion/adv_tools.lua diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua deleted file mode 100644 index 566484f01..000000000 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ /dev/null @@ -1,133 +0,0 @@ -adv_tools= {} -adv_tools.menu=MakeMenu() ---TODO make every tool generic (work for both modes) -function adv_tools.reincarnate(swap_soul) --only for adventurer i guess - if swap_soul==nil then - swap_soul=true - end - local adv=df.global.world.units.active[0] - if adv.flags1.dead==false then - error("You are not dead (yet)!") - end - local hist_fig=getNemesis(adv).figure - if hist_fig==nil then - error("No historical figure for adventurer...") - end - local events=df.global.world.history.events - local trg_hist_fig - for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry - if df.history_event_hist_figure_diedst:is_instance(events[i]) then - --print("is instance:"..i) - if events[i].victim==hist_fig.id then - --print("Is same id:"..i) - trg_hist_fig=events[i].slayer - if trg_hist_fig then - trg_hist_fig=df.historical_figure.find(trg_hist_fig) - end - break - end - end - end - if trg_hist_fig ==nil then - qerror("Slayer not found") - end - - local trg_unit=trg_hist_fig.unit_id - if trg_unit==nil then - qerror("Unit id not found!") - end - local trg_unit_final=df.unit.find(trg_unit) - - tools.change_adv(trg_unit_final) - if swap_soul then --actually add a soul... - t_soul=adv.status.current_soul - adv.status.current_soul=df.NULL - adv.status.souls:resize(0) - trg_unit_final.status.current_soul=t_soul - trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul) - end -end -adv_tools.menu:add("Reincarnate",adv_tools.reincarnate) -function adv_tools.ressurect() - - v2=engine.peek(vector:getval(indx),ptr_Creature.hurt1) - for i=0,v2:size()-1 do - v2:setval(i,0) - end - v2=engine.peek(vector:getval(indx),ptr_Creature.hurt2) - v2.type=DWORD - for i=0,v2:size()-1 do - v2:setval(i,0) - end - engine.poke(vector:getval(indx),ptr_Creature.bloodlvl,60000) --give blood - engine.poke(vector:getval(indx),ptr_Creature.bleedlvl,0) --stop some bleeding... - local flg=engine.peek(vector:getval(indx),ptr_Creature.flags) - flg:set(1,false) --ALIVE - flg:set(39,false) -- leave body yet again - flg:set(37,false) -- something todo with wounds- lets you walk again. - flg:set(58,true) -- makes them able to breathe - flg:set(61,true) -- gives them sight - engine.poke(vector:getval(indx),ptr_Creature.flags,flg) -end - -function adv_tools.wagonmode() --by rumrusher - --first three lines same as before (because we will need an offset of creature at location x,y,z) - myoff=offsets.getEx("AdvCreatureVec") - vector=engine.peek(myoff,ptr_vector) - indx=GetCreatureAtPos(getxyz()) - --indx=0 - --print(string.format("%x",vector:getval(indx))) - flg=engine.peek(vector:getval(indx),ptr_Creature.flags) --get flags - flg:set(1,false) - flg:set(74,false) - engine.poke(vector:getval(indx),ptr_Creature.flags,flg) - print("To stay normal press y, else hit Enter turn Wagon mode on.") - r=io.stdin:read() -- repeat for it too work... also creature will be dead. - if r== "y" then - flg=engine.peek(vector:getval(indx),ptr_Creature.flags) - flg:set(1,false) - engine.poke(vector:getval(indx),ptr_Creature.flags,flg) - else - flg=engine.peek(vector:getval(indx),ptr_Creature.flags) - flg:set(1,false) - flg:flip(74) - engine.poke(vector:getval(indx),ptr_Creature.flags,flg) - end -end -function selectall() - local retvec={} --return vector (or a list) - myoff=offsets.getEx("AdvCreatureVec") - vector=engine.peek(myoff,ptr_vector) --standart start - for i=0,vector:size()-1 do --check all creatures - local off - off=vector:getval(i) - local flags=engine.peek(off,ptr_Creature.flags) - if flags:get(1)==true then --if dead ... - table.insert(retvec,off)--... add it to return vector - end - end - return retvec --return the "return vector" :) -end -function adv_tools.hostilate() - vector=engine.peek(offsets.getEx("AdvCreatureVec"),ptr_vector) - id=GetCreatureAtPos(getxyz()) - print(string.format("Vec:%d cr:%d",vector:size(),id)) - off=vector:getval(id) - crciv=engine.peek(vector:getval(id),ptr_Creature.civ) - curciv=engine.peek(vector:getval(0),ptr_Creature.civ) - - if curciv==crciv then - print("Friendly-making enemy") - engine.poke(off,ptr_Creature.civ,-1) - flg=engine.peek(off,ptr_Creature.flags) - flg:set(17,true) - engine.poke(off,ptr_Creature.flags,flg) - else - print("Enemy- making friendly") - engine.poke(off,ptr_Creature.civ,curciv) - flg=engine.peek(off,ptr_Creature.flags) - flg:set(17,false) - flg:set(19,false) - engine.poke(off,ptr_Creature.flags,flg) - end -end diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index a6781b385..bdf3a2bc8 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -242,37 +242,7 @@ function engine.installMod(file,name,bonussize) return T end -it_menu={} -it_menu.__index=it_menu -function it_menu:add(name,func) - table.insert(self.items,{func,name}) -end -function it_menu:display() - print("Select choice (q exits):") - for p,c in pairs(self.items) do - print(string.format("%3d).%s",p,c[2])) - end - local ans - repeat - local r - r=getline("") - if r==nil then return end - if r=='q' then return end - ans=tonumber(r) - - if ans==nil or not(ans<=#self.items and ans>0) then - print("incorrect choice") - end - - until ans~=nil and (ans<=#self.items and ans>0) - self.items[ans][1]() -end -function MakeMenu() - local ret={} - ret.items={} - setmetatable(ret,it_menu) - return ret -end + function PrintPattern(loadedpattern) for k,v in pairs(loadedpattern) do diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua index 053c486cf..c7bd9bef3 100644 --- a/plugins/lua/dfusion.lua +++ b/plugins/lua/dfusion.lua @@ -202,4 +202,39 @@ function BinaryPlugin:__gc() end self.data:delete() end +-- a Menu for some stuff. Maybe add a posibility of it working as a gui, or a gui adaptor? +-- Todo add hints, and parse them to make a "smart" choice of parameters to pass +SimpleMenu=defclass(SimpleMenu) +SimpleMenu.ATTRS{title=DEFAULT_NIL} +function SimpleMenu:init(args) + self.items={} +end +function SimpleMenu:add(name,entry,hints) + table.insert(self.items,{entry,name,hints}) +end +function SimpleMenu:display() + print("Select choice (q exits):") + for p,c in pairs(self.items) do + print(string.format("%3d).%s",p,c[2])) + end + local ans + repeat + local r + r=io.stdin:read() + if r==nil then return end + if r=='q' then return end + ans=tonumber(r) + + if ans==nil or not(ans<=#self.items and ans>0) then + print("Invalid choice.") + end + + until ans~=nil and (ans<=#self.items and ans>0) + if type(self.items[ans][1])=="function" then + self.items[ans][1]() + else + self.items[ans][1]:display() + end +end + return _ENV \ No newline at end of file diff --git a/plugins/lua/dfusion/adv_tools.lua b/plugins/lua/dfusion/adv_tools.lua new file mode 100644 index 000000000..4440a4b2f --- /dev/null +++ b/plugins/lua/dfusion/adv_tools.lua @@ -0,0 +1,52 @@ +local _ENV = mkmodule('plugins.dfusion.adv_tools') +local dfu=require("plugins.dfusion") +menu=dfu.SimpleMenu() +function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess + if swap_soul==nil then + swap_soul=true + end + local adv=trg_unit or df.global.world.units.active[0] + if adv.flags1.dead==false then + qerror("You are not dead (yet)!") + end + local hist_fig=getNemesis(adv).figure + if hist_fig==nil then + qerror("No historical figure for adventurer...") + end + local events=df.global.world.history.events + local trg_hist_fig + for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry + if df.history_event_hist_figure_diedst:is_instance(events[i]) then + --print("is instance:"..i) + if events[i].victim==hist_fig.id then + --print("Is same id:"..i) + trg_hist_fig=events[i].slayer + if trg_hist_fig then + trg_hist_fig=df.historical_figure.find(trg_hist_fig) + end + break + end + end + end + if trg_hist_fig ==nil then + qerror("Slayer not found") + end + + local trg_unit=trg_hist_fig.unit_id + if trg_unit==nil then + qerror("Unit id not found!") + end + local trg_unit_final=df.unit.find(trg_unit) + + tools.change_adv(trg_unit_final) + if swap_soul then --actually add a soul... + t_soul=adv.status.current_soul + adv.status.current_soul=df.NULL + adv.status.souls:resize(0) + trg_unit_final.status.current_soul=t_soul + trg_unit_final.status.souls:insert(#trg_unit_final.status.souls,t_soul) + end +end +menu:add("Reincarnate",Reincarnate,{{df.unit,"optional"}})-- bool, optional + +return _ENV \ No newline at end of file diff --git a/scripts/dfusion.lua b/scripts/dfusion.lua index 63a530454..4fee0438e 100644 --- a/scripts/dfusion.lua +++ b/scripts/dfusion.lua @@ -1,42 +1,11 @@ --- a binary hack/plugin collection for df +-- a collection of misc lua scripts local dfu=require("plugins.dfusion") local myos=dfhack.getOSType() - ---some imports go here -local plugins={ - require("plugins.dfusion.embark").CustomEmbark -} ---show a table of all the statuses -function status(plug) - if dfu.plugins[plug.name]==nil then - return plug.class_status - else - return dfu.plugins[plug.name]:status() - end +args={...} +mainmenu=dfu.SimpleMenu() +function runsave() + print("doing file:"..df.global.world.cur_savegame.save_dir) end -function printPlugs() - local endthis=false - print("current:") - while(not endthis) do - for k,v in pairs(plugins) do - if v then - print(string.format("%2d. %15s-%s",k,v.name,status(v))) - end - end - print("e-edit and load, u-unload,c-cancel and then number to manipulate:") - local choice=io.stdin:read() - local num=tonumber(io.stdin:read()) - if num then - local plg=dfu.plugins[plugins[num].name] or plugins[num]() - if choice=='e' then - plg:edit() - elseif choice=='u' then - plg:uninstall() - elseif choice=='c' then - endthis=true - end - end - end -end - -printPlugs() \ No newline at end of file +mainmenu:add("Run save script",runsave) +mainmenu:add("Adventurer tools",require("plugins.dfusion.adv_tools").menu) +mainmenu:display() \ No newline at end of file From 296d1cf090745c9bb667ddb8d30395ad431d13ef Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 2 Nov 2012 00:50:20 +0200 Subject: [PATCH 133/472] More scripts for dfusion. Only fixes left, and updating bin-plugins (friendship and migrants(??)) --- plugins/lua/dfusion/adv_tools.lua | 68 ++++++++++++- plugins/lua/dfusion/tools.lua | 164 ++++++++++++++++++++++++++++++ scripts/dfusion.lua | 5 +- 3 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 plugins/lua/dfusion/tools.lua diff --git a/plugins/lua/dfusion/adv_tools.lua b/plugins/lua/dfusion/adv_tools.lua index 4440a4b2f..31e83b234 100644 --- a/plugins/lua/dfusion/adv_tools.lua +++ b/plugins/lua/dfusion/adv_tools.lua @@ -48,5 +48,69 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess end end menu:add("Reincarnate",Reincarnate,{{df.unit,"optional"}})-- bool, optional - -return _ENV \ No newline at end of file +function change_adv(unit,nemesis) + if nemesis==nil then + nemesis=true --default value is nemesis switch too. + end + if unit==nil then + unit=getCreatureAtPointer() + end + if unit==nil then + error("Invalid unit!") + end + local other=df.global.world.units.active + local unit_indx + for k,v in pairs(other) do + if v==unit then + unit_indx=k + break + end + end + if unit_indx==nil then + error("Unit not found in array?!") --should not happen + end + other[unit_indx]=other[0] + other[0]=unit + if nemesis then --basicly copied from advtools plugin... + local nem=getNemesis(unit) + local other_nem=getNemesis(other[unit_indx]) + if other_nem then + other_nem.flags[0]=false + other_nem.flags[1]=true + end + if nem then + nem.flags[0]=true + nem.flags[2]=true + for k,v in pairs(df.global.world.nemesis.all) do + if v.id==nem.id then + df.global.ui_advmode.player_id=k + end + end + else + error("Current unit does not have nemesis record, further working not guaranteed") + end + end +end +menu:add("Change adventurer",change_adv) +function log_pos() + local adv=df.global.world.units.active[0] + + local wmap=df.global.world.map + local sub_pos={x=adv.pos.x,y=adv.pos.y,z=adv.pos.z} + local region_pos={x=wmap.region_x,y=wmap.region_y,z=wmap.region_z} + local pos={x=sub_pos.x+region_pos.x*48,y=sub_pos.y+region_pos.y*48,z=sub_pos.z+region_pos.z} + local state + if adv.flags1.dead then + state="dead" + else + state="live n kicking" + end + local message=string.format("%s %s at pos={%d,%d,%d} region={%d,%d,%d}",dfhack.TranslateName(adv.name),state,pos.x,pos.y,pos.z,region_pos.x,region_pos.y,region_pos.z) + print(message) + local path="deaths_"..df.global.world.cur_savegame.save_dir..".txt" + local f=io.open(path,"a") + f:write(message) + f:close() +end +menu:add("Log adventurers position",log_pos) +return _ENV diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua new file mode 100644 index 000000000..9ddfb8e45 --- /dev/null +++ b/plugins/lua/dfusion/tools.lua @@ -0,0 +1,164 @@ +local _ENV = mkmodule('plugins.dfusion.tools') +local dfu=require("plugins.dfusion") +local ms=require "memscan" +menu=dfu.SimpleMenu() +function setrace(name) --TODO FIX + RaceTable=BuildNameTable() + print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) + local id + if name == nil then + print("Type new race's token name in full caps (q to quit):") + repeat + entry=getline() + if entry=="q" then + return + end + id=RaceTable[entry] + until id~=nil + else + id=RaceTable[name] + if id==nil then + error("Name not found!") + end + end + df.global.ui.race_id=id +end +menu:add("Set current race",setrace) +function GiveSentience(names) --TODO FIX + RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again + if names ==nil then + ids={} + print("Type race's token name in full caps to give sentience to:") + repeat + entry=getline() + id=RaceTable[entry] + until id~=nil + table.insert(ids,id) + else + ids={} + for _,name in pairs(names) do + id=RaceTable[name] + table.insert(ids,id) + end + end + for _,id in pairs(ids) do + local races=df.global.world.raws.creatures.all + + local castes=races[id].caste + print(string.format("Caste count:%i",castes.size)) + for i =0,#castes-1 do + + print("Caste name:"..castes[i].caste_id.."...") + + local flags=castes[i].flags + --print(string.format("%x",flagoffset)) + if flags.CAN_SPEAK then + print("\tis sentient.") + else + print("\tnon sentient. Allocating IQ...") + flags.CAN_SPEAK=true + end + end + end +end +menu:add("Give Sentience",GiveSentience) +function MakeFollow(unit,trgunit) + if unit == nil then + unit=dfhack.gui.getSelectedUnit() + end + if unit== nil then + error("Invalid creature") + end + if trgunit==nil then + trgunit=df.global.world.units.active[0] + end + unit.relations.group_leader_id=trgunit.id + local u_nem=getNemesis(unit) + local t_nem=getNemesis(trgunit) + if u_nem then + u_nem.group_leader_id=t_nem.id + end + if t_nem and u_nem then + t_nem.companions:insert(#t_nem.companions,u_nem.id) + end +end +menu:add("Make creature follow",MakeFollow) +function project(unit,trg) --TODO add to menu? + if unit==nil then + unit=getCreatureAtPointer() + end + + if unit==nil then + error("Failed to project unit. Unit not selected/valid") + end + -- todo: add projectile to world, point to unit, add flag to unit, add gen-ref to projectile. + local p=df.proj_unitst:new() + local startpos={x=unit.pos.x,y=unit.pos.y,z=unit.pos.z} + p.origin_pos=startpos + p.target_pos=trg + p.cur_pos=startpos + p.prev_pos=startpos + p.unit=unit + --- wtf stuff + p.unk14=100 + p.unk16=-1 + p.unk23=-1 + p.fall_delay=5 + p.fall_counter=5 + p.collided=true + -- end wtf + local citem=df.global.world.proj_list + local maxid=1 + local newlink=df.proj_list_link:new() + newlink.item=p + while citem.item~= nil do + if citem.item.id>maxid then maxid=citem.item.id end + if citem.next ~= nil then + citem=citem.next + else + break + end + end + p.id=maxid+1 + newlink.prev=citem + citem.next=newlink + local proj_ref=df.general_ref_projectile:new() + proj_ref.projectile_id=p.id + unit.refs:insert(#unit.refs,proj_ref) + unit.flags1.projectile=true +end +function empregnate(unit) + if unit==nil then + unit=getSelectedUnit() + end + + if unit==nil then + unit=getCreatureAtPos(getxyz()) + end + + if unit==nil then + error("Failed to empregnate. Unit not selected/valid") + end + if unit.curse then + unit.curse.add_tags2.STERILE=false + end + local genes = unit.appearance.genes + if unit.relations.pregnancy_ptr == nil then + print("creating preg ptr.") + if false then + print(string.format("%x %x",df.sizeof(unit.relations:_field("pregnancy_ptr")))) + return + end + unit.relations.pregnancy_ptr = { new = true, assign = genes } + end + local ngenes = unit.relations.pregnancy_ptr + if #ngenes.appearance ~= #genes.appearance or #ngenes.colors ~= #genes.colors then + print("Array sizes incorrect, fixing.") + ngenes:assign(genes); + end + print("Setting preg timer.") + unit.relations.pregnancy_timer=10 + unit.relations.pregnancy_mystery=1 +end +menu:add("Empregnate",empregnate) +return _ENV \ No newline at end of file diff --git a/scripts/dfusion.lua b/scripts/dfusion.lua index 4fee0438e..79f9fd953 100644 --- a/scripts/dfusion.lua +++ b/scripts/dfusion.lua @@ -4,8 +4,11 @@ local myos=dfhack.getOSType() args={...} mainmenu=dfu.SimpleMenu() function runsave() - print("doing file:"..df.global.world.cur_savegame.save_dir) + local path=string.format("data/save/%s/dfhack.lua",df.global.world.cur_savegame.save_dir) + print("doing file:"..path) + loadfile(path)() end mainmenu:add("Run save script",runsave) mainmenu:add("Adventurer tools",require("plugins.dfusion.adv_tools").menu) +mainmenu:add("Misc tools",require("plugins.dfusion.tools").menu) mainmenu:display() \ No newline at end of file From 5295be5fdbe4ef7e40e174ddfab86253d6d154bd Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 2 Nov 2012 20:28:08 +0200 Subject: [PATCH 134/472] More work done. Only bin-plugs left (and docs) --- plugins/lua/dfusion/adv_tools.lua | 2 +- plugins/lua/dfusion/tools.lua | 41 +++++++++++++++++++++++-------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/plugins/lua/dfusion/adv_tools.lua b/plugins/lua/dfusion/adv_tools.lua index 31e83b234..6e95d2117 100644 --- a/plugins/lua/dfusion/adv_tools.lua +++ b/plugins/lua/dfusion/adv_tools.lua @@ -87,7 +87,7 @@ function change_adv(unit,nemesis) end end else - error("Current unit does not have nemesis record, further working not guaranteed") + qerror("Current unit does not have nemesis record, further working not guaranteed") end end end diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index 9ddfb8e45..e577a9418 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -2,14 +2,31 @@ local _ENV = mkmodule('plugins.dfusion.tools') local dfu=require("plugins.dfusion") local ms=require "memscan" menu=dfu.SimpleMenu() -function setrace(name) --TODO FIX - RaceTable=BuildNameTable() - print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) +RaceNames={} +function build_race_names() + if #RaceNames~=0 then + return RaceNames + else + for k,v in pairs(df.global.world.raws.creatures.all) do + RaceNames[v.creature_id]=k + end + dfhack.onStateChange.invalidate_races=function(change_id) --todo does this work? + if change_id==SC_WORLD_UNLOADED then + dfhack.onStateChange.invalidate_races=nil + RaceNames={} + end + end + return RaceNames + end +end +function setrace(name) + local RaceTable=build_race_names() + print("Your current race is:"..df.global.world.raws.creatures.all[df.global.ui.race_id].creature_id) local id if name == nil then print("Type new race's token name in full caps (q to quit):") repeat - entry=getline() + local entry=io.stdin:read() if entry=="q" then return end @@ -24,16 +41,20 @@ function setrace(name) --TODO FIX df.global.ui.race_id=id end menu:add("Set current race",setrace) -function GiveSentience(names) --TODO FIX - RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again +function GiveSentience(names) + local RaceTable=build_race_names() --slow.If loaded don't load again + local id,ids if names ==nil then ids={} print("Type race's token name in full caps to give sentience to:") repeat - entry=getline() + id=io.stdin:read() id=RaceTable[entry] - until id~=nil - table.insert(ids,id) + if id~=nil then + table.insert(ids,id) + end + until id==nil + else ids={} for _,name in pairs(names) do @@ -45,7 +66,7 @@ function GiveSentience(names) --TODO FIX local races=df.global.world.raws.creatures.all local castes=races[id].caste - print(string.format("Caste count:%i",castes.size)) + print(string.format("Caste count:%i",#castes)) for i =0,#castes-1 do print("Caste name:"..castes[i].caste_id.."...") From 86e4a42bdda70a47338969aba2b8ade42800294f Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 2 Nov 2012 20:59:05 +0200 Subject: [PATCH 135/472] Small fix due to vmethod change --- plugins/reactionhooks.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/reactionhooks.cpp b/plugins/reactionhooks.cpp index d70fb9ea1..ba0f8742d 100644 --- a/plugins/reactionhooks.cpp +++ b/plugins/reactionhooks.cpp @@ -203,7 +203,7 @@ struct product_hook : item_product { (df::unit *unit, std::vector *out_items, std::vector *in_reag, std::vector *in_items, - int32_t quantity, int16_t skill, + int32_t quantity, df::job_skill skill, df::historical_entity *entity, df::world_site *site) ) { if (auto product = products[this]) From e887c60e93c819dc3b803457dbb6b3c3c7300731 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 2 Nov 2012 21:00:35 +0200 Subject: [PATCH 136/472] Removed unused buffers. --- plugins/devel/memview.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 757b475dd..29b8403f8 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -168,8 +168,6 @@ command_result memview (color_ostream &out, vector & parameters) else memdata.refresh=0; - - uint8_t *buf,*lbuf; memdata.buf=new uint8_t[memdata.len]; memdata.lbuf=new uint8_t[memdata.len]; Core::getInstance().p->getMemRanges(memdata.ranges); From 3257eb80a1104599b0ae57ccf3a09b38343242d9 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 2 Nov 2012 16:28:48 -0500 Subject: [PATCH 137/472] Add checks to avoid crashing if we encounter a soulless unit --- plugins/manipulator.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index b6d30ab1e..1bc0195b6 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -298,6 +298,10 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) { + if (!d1->unit->status.current_soul) + return !descending; + if (!d2->unit->status.current_soul) + return descending; df::unit_skill *s1 = binsearch_in_vector(d1->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); df::unit_skill *s2 = binsearch_in_vector(d2->unit->status.current_soul->skills, &df::unit_skill::id, sort_skill); int l1 = s1 ? s1->rating : 0; @@ -1030,7 +1034,9 @@ void viewscreen_unitlaborsst::render() fg = 9; if (columns[col_offset].skill != job_skill::NONE) { - df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); + df::unit_skill *skill = NULL; + if (unit->status.current_soul) + skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[col_offset].skill); if ((skill != NULL) && (skill->rating || skill->experience)) { int level = skill->rating; @@ -1086,7 +1092,9 @@ void viewscreen_unitlaborsst::render() } else { - df::unit_skill *skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); + df::unit_skill *skill = NULL; + if (unit->status.current_soul) + skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, columns[sel_column].skill); if (skill) { int level = skill->rating; From 8bccfb1e9a197084ff645b05ee877209a2475556 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 14:31:07 +0400 Subject: [PATCH 138/472] Fix gcc compiler errors and warnings in search, reindent plugin_init, etc. --- NEWS | 3 +++ plugins/search.cpp | 53 +++++++++++++++++++++++++++++----------------- 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/NEWS b/NEWS index 0f9bf4c3d..d5080b05a 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,9 @@ DFHack future Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in properly designated barracks be used again for storage of squad equipment. + New Search plugin by falconne: + Adds an incremental search function to the Stocks, Trading and Unit List screens. + DFHack v0.34.11-r2 diff --git a/plugins/search.cpp b/plugins/search.cpp index fdc788955..e19c2e3c0 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -206,12 +206,12 @@ protected: bool list_has_been_sorted = (sort_list1->size() == reference_list.size() && *sort_list1 != reference_list); - for (int i = 0; i < saved_indexes.size(); i++) + for (size_t i = 0; i < saved_indexes.size(); i++) { int adjusted_item_index = i; if (list_has_been_sorted) { - for (int j = 0; j < sort_list1->size(); j++) + for (size_t j = 0; j < sort_list1->size(); j++) { if ((*sort_list1)[j] == reference_list[i]) { @@ -278,7 +278,7 @@ protected: } string search_string_l = toLower(search_string); - for (int i = 0; i < saved_list1.size(); i++ ) + for (size_t i = 0; i < saved_list1.size(); i++ ) { T element = saved_list1[i]; string desc = toLower(get_element_description(element)); @@ -440,8 +440,8 @@ private: typedef search_hook stocks_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // END: Stocks screen search @@ -502,8 +502,8 @@ private: }; typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); -IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search @@ -552,8 +552,8 @@ public: }; typedef search_hook trade_search_merc_hook; -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); class trade_search_fort : public trade_search_base @@ -576,8 +576,8 @@ public: }; typedef search_hook trade_search_fort_hook; -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // // END: Trade screen search @@ -589,10 +589,15 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() - || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() - || !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() - || !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || !INTERPOSE_HOOK(stocks_search_hook, render).apply()) + if (!gps || + !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || + !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || + !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || + !INTERPOSE_HOOK(trade_search_merc_hook, render).apply() || + !INTERPOSE_HOOK(trade_search_fort_hook, feed).apply() || + !INTERPOSE_HOOK(trade_search_fort_hook, render).apply() || + !INTERPOSE_HOOK(stocks_search_hook, feed).apply() || + !INTERPOSE_HOOK(stocks_search_hook, render).apply()) out.printerr("Could not insert Search hooks!\n"); return CR_OK; @@ -613,9 +618,17 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { - unitlist_search_hook::module.reset_all(); - trade_search_merc_hook::module.reset_all(); - trade_search_fort_hook::module.reset_all(); - stocks_search_hook::module.reset_all(); + switch (event) { + case SC_VIEWSCREEN_CHANGED: + unitlist_search_hook::module.reset_all(); + trade_search_merc_hook::module.reset_all(); + trade_search_fort_hook::module.reset_all(); + stocks_search_hook::module.reset_all(); + break; + + default: + break; + } + return CR_OK; -} \ No newline at end of file +} From 9bf24bde10154e335dfe96dfb1cb202dd9027cc1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 15:34:04 +0400 Subject: [PATCH 139/472] More various updates for search. - Keep the search state as long as the screen is alive. - Properly forget saved state when clearing search. - Fix the start column in render for stocks screen. - Allow search by profession in all Units pages. - Dismiss search when trying to trade. --- plugins/search.cpp | 102 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index e19c2e3c0..cc3f29c12 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -10,6 +10,7 @@ #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" +#include "df/interfacest.h" using std::set; using std::vector; @@ -19,6 +20,7 @@ using namespace DFHack; using namespace df::enums; using df::global::gps; +using df::global::gview; /* Search Plugin @@ -39,6 +41,14 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) x += text.length(); } +static bool is_live_screen(const df::viewscreen *screen) +{ + for (df::viewscreen *cur = &gview->view; cur; cur = cur->child) + if (cur == screen) + return true; + return false; +} + // // START: Base Search functionality // @@ -60,6 +70,15 @@ public: track_secondary_values = false; } + bool reset_on_change() + { + if (valid && is_live_screen(viewscreen)) + return false; + + reset_all(); + return true; + } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { @@ -245,6 +264,9 @@ protected: update_secondary_values(); *sort_list2 = saved_list2; } + + saved_list1.clear(); + saved_list2.clear(); } store_reference_values(); search_string = ""; @@ -346,7 +368,12 @@ struct search_hook : T DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - module.init(this); + if (!module.init(this)) + { + INTERPOSE_NEXT(feed)(input); + return; + } + if (!module.process_input(input)) { INTERPOSE_NEXT(feed)(input); @@ -357,9 +384,10 @@ struct search_hook : T DEFINE_VMETHOD_INTERPOSE(void, render, ()) { - module.init(this); + bool ok = module.init(this); INTERPOSE_NEXT(render)(); - module.render(); + if (ok) + module.render(); } }; @@ -382,10 +410,10 @@ public: virtual void render() const { if (!viewscreen->in_group_mode) - print_search_option(1); + print_search_option(2); else { - int x = 1; + int x = 2; OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); } } @@ -402,13 +430,18 @@ public: search_parent::do_post_update_check(); } - virtual void init(df::viewscreen_storesst *screen) + bool init(df::viewscreen_storesst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->item_cursor, &screen->items); } + + return true; } @@ -461,21 +494,25 @@ public: print_search_option(28); } - virtual void init(df::viewscreen_unitlistst *screen) + bool init(df::viewscreen_unitlistst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); } + + return true; } private: virtual string get_element_description(df::unit *element) const { string desc = Translation::TranslateName(Units::getVisibleName(element), false); - if (viewscreen->page == 1) - desc += Units::getProfessionName(element); // Check animal type too + desc += ", " + Units::getProfessionName(element); // Check animal type too return desc; } @@ -529,6 +566,29 @@ private: { return Items::getDescription(element, 0, true); } + + virtual bool should_check_input(set *input) + { + if (is_entry_mode()) + return true; + + if (input->count(interface_key::TRADE_TRADE) || + input->count(interface_key::TRADE_OFFER) || + input->count(interface_key::TRADE_SEIZE)) + { + // Block the keys if were searching + if (!search_string.empty()) + input->clear(); + + // Trying to trade, reset search + clear_search(); + reset_all(); + + return false; + } + + return true; + } }; @@ -540,14 +600,19 @@ public: print_search_option(2, 26); } - virtual void init(df::viewscreen_tradegoodsst *screen) + bool init(df::viewscreen_tradegoodsst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); track_secondary_values = true; } + + return true; } }; @@ -564,14 +629,19 @@ public: print_search_option(42, 26); } - virtual void init(df::viewscreen_tradegoodsst *screen) + bool init(df::viewscreen_tradegoodsst *screen) { + if (screen != viewscreen && !reset_on_change()) + return false; + if (!valid) { viewscreen = screen; search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); track_secondary_values = true; } + + return true; } }; @@ -589,7 +659,7 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || + if (!gps || !gview || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_hook, render).apply() || !INTERPOSE_HOOK(trade_search_merc_hook, feed).apply() || @@ -620,10 +690,10 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch { switch (event) { case SC_VIEWSCREEN_CHANGED: - unitlist_search_hook::module.reset_all(); - trade_search_merc_hook::module.reset_all(); - trade_search_fort_hook::module.reset_all(); - stocks_search_hook::module.reset_all(); + unitlist_search_hook::module.reset_on_change(); + trade_search_merc_hook::module.reset_on_change(); + trade_search_fort_hook::module.reset_on_change(); + stocks_search_hook::module.reset_on_change(); break; default: From d6f1bb93b5397338d9c6761ed9ce4d3a6d01e999 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 15:49:34 +0400 Subject: [PATCH 140/472] Add documentation for the search plugin. --- Readme.html | 104 +++++++++++++++++++++++++++++++--------------------- Readme.rst | 25 +++++++++++++ 2 files changed, 87 insertions(+), 42 deletions(-) diff --git a/Readme.html b/Readme.html index 0da481c88..cd073459c 100644 --- a/Readme.html +++ b/Readme.html @@ -500,33 +500,34 @@ access DF memory and allow for easier development of new tools.

                                            • In-game interface tools
                                            • -
                                            • Behavior Mods
                                            • +
                                              -

                                              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.

                                              @@ -2707,7 +2727,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.

                                                @@ -2723,14 +2743,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 @@ -2740,13 +2760,13 @@ only that entry, and does it even if it is not 'individual choice'.

                                              and may lead to inappropriate weapons being selected.

                                              -

                                              gui/guide-path

                                              +

                                              gui/guide-path

                                              Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                              The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

                                              -

                                              gui/workshop-job

                                              +

                                              gui/workshop-job

                                              Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                              The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

                                              @@ -2774,7 +2794,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                              -

                                              gui/workflow

                                              +

                                              gui/workflow

                                              Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                              This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2796,7 +2816,7 @@ as described in workflow documentation above. can be used for troubleshooting jobs that don't match the right constraints.

                                              -

                                              gui/assign-rack

                                              +

                                              gui/assign-rack

                                              Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                              This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

                                              @@ -2816,7 +2836,7 @@ the intended user.

                                              -

                                              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.

                                              @@ -2827,20 +2847,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 @@ -2861,7 +2881,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 @@ -2870,11 +2890,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 @@ -2885,7 +2905,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.

                                              @@ -2909,7 +2929,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 @@ -2940,7 +2960,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 @@ -2949,7 +2969,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 @@ -2960,7 +2980,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 0b2fbc378..d9021c7cb 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1898,6 +1898,31 @@ Pressing ESC normally returns to the unit screen, but Shift-ESC would exit directly to the main dwarf mode screen. +Search +====== + +The search plugin adds search to the Stocks, Trading and Unit List screens. + +Searching works the same way as the search option in "Move to Depot" does. +You will see the Search option displayed on screen with a hotkey (usually 's'). +Pressing it lets you start typing a query and the relevant list will start +filtering automatically. + +Pressing ENTER, ESC or the arrow keys will return you to browsing the now +filtered list, which still functions as normal. You can clear the filter +by either going back into search mode and backspacing to delete it, or +pressing the "shifted" version of the search hotkey while browsing the +list (e.g. if the hotkey is 's', then hitting 'shift-s' will clear any +filter). + +Leaving any screen automatically clears the filter. + +In the Trade screen, the actual trade will always only act on items that +are actually visible in the list; the same effect applies to the Trade +Value numbers displayed by the screen. Because of this, pressing the 't' +key while search is active clears the search instead of executing the trade. + + gui/liquids =========== From 959831685506a3c92f8af0f6678afa91c95c4aa8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 3 Nov 2012 20:06:33 +0400 Subject: [PATCH 141/472] Add a native pen object for lua with a more checked behavior. --- Lua API.html | 82 +++++--- Lua API.rst | 76 +++++-- library/LuaApi.cpp | 349 ++++++++++++++++++++++++++++--- library/include/LuaTools.h | 6 + library/include/modules/Screen.h | 2 + library/lua/gui.lua | 62 +++--- library/lua/gui/dialogs.lua | 4 +- 7 files changed, 466 insertions(+), 115 deletions(-) diff --git a/Lua API.html b/Lua API.html index a52347104..5dff4b3e4 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1555,37 +1555,12 @@ be feasibly used in the core context.

                                              Checks if [GRAPHICS:YES] was specified in init.

                                            • dfhack.screen.paintTile(pen,x,y[,char,tile])

                                              -

                                              Paints a tile using given parameters. Pen is a table with following possible fields:

                                              -
                                              -
                                              ch
                                              -

                                              Provides the ordinary tile character, as either a 1-character string or a number. -Can be overridden with the char function parameter.

                                              -
                                              -
                                              fg
                                              -

                                              Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).

                                              -
                                              -
                                              bg
                                              -

                                              Background color for the ordinary tile. Defaults to COLOR_BLACK (0).

                                              -
                                              -
                                              bold
                                              -

                                              Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. -Otherwise should be true/false.

                                              -
                                              -
                                              tile
                                              -

                                              Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.

                                              -
                                              -
                                              tile_color = true
                                              -

                                              Specifies that the tile should be shaded with fg/bg.

                                              -
                                              -
                                              tile_fg, tile_bg
                                              -

                                              If specified, overrides tile_color and supplies shading colors directly.

                                              -
                                              -
                                              +

                                              Paints a tile using given parameters. See below for a description of pen.

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

                                            • dfhack.screen.readTile(x,y)

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

                                              +Returns a pen object, or nil if invalid or TrueType.

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

                                              Paints the string starting at x,y. Uses the string characters @@ -1610,6 +1585,59 @@ The values can then be used for the tile field of pen structur functions in this section, this may be used at any time.

                                            • +

                                              The "pen" argument used by functions above may be represented by +a table with the following possible fields:

                                              +
                                              +
                                              +
                                              ch
                                              +
                                              Provides the ordinary tile character, as either a 1-character string or a number. +Can be overridden with the char function parameter.
                                              +
                                              fg
                                              +
                                              Foreground color for the ordinary tile. Defaults to COLOR_GREY (7).
                                              +
                                              bg
                                              +
                                              Background color for the ordinary tile. Defaults to COLOR_BLACK (0).
                                              +
                                              bold
                                              +
                                              Bright/bold text flag. If nil, computed based on (fg & 8); fg is masked to 3 bits. +Otherwise should be true/false.
                                              +
                                              tile
                                              +
                                              Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt.
                                              +
                                              tile_color = true
                                              +
                                              Specifies that the tile should be shaded with fg/bg.
                                              +
                                              tile_fg, tile_bg
                                              +
                                              If specified, overrides tile_color and supplies shading colors directly.
                                              +
                                              +
                                              +

                                              Alternatively, it may be a pre-parsed native object with the following API:

                                              +
                                                +
                                              • dfhack.pen.make(base[,pen_or_fg,bg,bold])

                                                +

                                                Creates a new pre-parsed pen by combining its arguments according to the +following rules:

                                                +
                                                  +
                                                1. The base argument may be a pen object, a pen table as specified above, +or a single color value. In the single value case, it is split into +fg and bold properties, and others are initialized to 0. +This argument will be converted to a pre-parsed object and returned +if there are no other arguments.
                                                2. +
                                                3. If the pen_or_fg argument is specified as a table or object, it +completely replaces the base, and is returned instead of it.
                                                4. +
                                                5. Otherwise, the non-nil subset of the optional arguments is used +to update the fg, bg and bold properties of the base. +If the bold flag is nil, but pen_or_fg is a number, bold +is deduced from it like in the simple base case.
                                                6. +
                                                +

                                                This function always returns a new pre-parsed pen, or nil.

                                                +
                                              • +
                                              • dfhack.pen.parse(base[,pen_or_fg,bg,bold])

                                                +

                                                Exactly like the above function, but returns base or pen_or_fg +directly if they are already a pre-parsed native object.

                                                +
                                              • +
                                              • pen.property, pen.property = value, pairs(pen)

                                                +

                                                Pre-parsed pens support reading and setting their properties, +but don't behave exactly like a simple table would; for instance, +assigning to pen.tile_color also resets pen.tile_fg and +pen.tile_bg to nil.

                                                +
                                              • +

                                              In order to actually be able to paint to the screen, it is necessary to create and register a viewscreen (basically a modal dialog) with the game.

                                              diff --git a/Lua API.rst b/Lua API.rst index bd712d301..ec48938db 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1403,31 +1403,14 @@ Basic painting functions: * ``dfhack.screen.paintTile(pen,x,y[,char,tile])`` - Paints a tile using given parameters. Pen is a table with following possible fields: - - ``ch`` - Provides the ordinary tile character, as either a 1-character string or a number. - Can be overridden with the ``char`` function parameter. - ``fg`` - Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). - ``bg`` - Background color for the ordinary tile. Defaults to COLOR_BLACK (0). - ``bold`` - Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. - Otherwise should be *true/false*. - ``tile`` - Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt. - ``tile_color = true`` - Specifies that the tile should be shaded with *fg/bg*. - ``tile_fg, tile_bg`` - If specified, overrides *tile_color* and supplies shading colors directly. + Paints a tile using given parameters. See below for a description of pen. Returns *false* if coordinates out of bounds, or other error. * ``dfhack.screen.readTile(x,y)`` Retrieves the contents of the specified tile from the screen buffers. - Returns a pen, or *nil* if invalid or TrueType. + Returns a pen object, or *nil* if invalid or TrueType. * ``dfhack.screen.paintString(pen,x,y,text)`` @@ -1458,6 +1441,61 @@ Basic painting functions: Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time. +The "pen" argument used by functions above may be represented by +a table with the following possible fields: + + ``ch`` + Provides the ordinary tile character, as either a 1-character string or a number. + Can be overridden with the ``char`` function parameter. + ``fg`` + Foreground color for the ordinary tile. Defaults to COLOR_GREY (7). + ``bg`` + Background color for the ordinary tile. Defaults to COLOR_BLACK (0). + ``bold`` + Bright/bold text flag. If *nil*, computed based on (fg & 8); fg is masked to 3 bits. + Otherwise should be *true/false*. + ``tile`` + Graphical tile id. Ignored unless [GRAPHICS:YES] was in init.txt. + ``tile_color = true`` + Specifies that the tile should be shaded with *fg/bg*. + ``tile_fg, tile_bg`` + If specified, overrides *tile_color* and supplies shading colors directly. + +Alternatively, it may be a pre-parsed native object with the following API: + +* ``dfhack.pen.make(base[,pen_or_fg,bg,bold])`` + + Creates a new pre-parsed pen by combining its arguments according to the + following rules: + + 1. The ``base`` argument may be a pen object, a pen table as specified above, + or a single color value. In the single value case, it is split into + ``fg`` and ``bold`` properties, and others are initialized to 0. + This argument will be converted to a pre-parsed object and returned + if there are no other arguments. + + 2. If the ``pen_or_fg`` argument is specified as a table or object, it + completely replaces the base, and is returned instead of it. + + 3. Otherwise, the non-nil subset of the optional arguments is used + to update the ``fg``, ``bg`` and ``bold`` properties of the base. + If the ``bold`` flag is *nil*, but *pen_or_fg* is a number, ``bold`` + is deduced from it like in the simple base case. + + This function always returns a new pre-parsed pen, or *nil*. + +* ``dfhack.pen.parse(base[,pen_or_fg,bg,bold])`` + + Exactly like the above function, but returns ``base`` or ``pen_or_fg`` + directly if they are already a pre-parsed native object. + +* ``pen.property``, ``pen.property = value``, ``pairs(pen)`` + + Pre-parsed pens support reading and setting their properties, + but don't behave exactly like a simple table would; for instance, + assigning to ``pen.tile_color`` also resets ``pen.tile_fg`` and + ``pen.tile_bg`` to *nil*. + In order to actually be able to paint to the screen, it is necessary to create and register a viewscreen (basically a modal dialog) with the game. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index e7424ad50..0151ed404 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -725,6 +725,316 @@ static void OpenMatinfo(lua_State *state) lua_pop(state, 1); } +/************** + * Pen object * + **************/ + +static int DFHACK_PEN_TOKEN = 0; + +void Lua::Push(lua_State *L, const Screen::Pen &info) +{ + if (!info.valid()) + { + lua_pushnil(L); + return; + } + + void *pdata = lua_newuserdata(L, sizeof(Pen)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + lua_setmetatable(L, -2); + + new (pdata) Pen(info); +} + +static Pen *check_pen_native(lua_State *L, int index) +{ + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + if (!lua_getmetatable(L, index) || !lua_rawequal(L, -1, -2)) + luaL_argerror(L, index, "not a pen object"); + + lua_pop(L, 2); + + return (Pen*)lua_touserdata(L, index); +} + +void Lua::CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil, bool allow_color) +{ + index = lua_absindex(L, index); + + luaL_checkany(L, index); + + if (lua_isnil(L, index)) + { + if (!allow_nil) + luaL_argerror(L, index, "nil pen not allowed"); + + *pen = Pen(0,0,0,-1); + } + else if (lua_isuserdata(L, index)) + { + *pen = *check_pen_native(L, index); + } + else if (allow_color && lua_isnumber(L, index)) + { + *pen = Pen(0, lua_tointeger(L, index)&15, 0); + } + else + { + luaL_checktype(L, index, LUA_TTABLE); + decode_pen(L, *pen, index); + } +} + +static int adjust_pen(lua_State *L, bool no_copy) +{ + lua_settop(L, 4); + + Pen pen; + int iidx = 1; + Lua::CheckPen(L, &pen, 1, true, true); + + if (!lua_isnil(L, 2) || !lua_isnil(L, 3) || !lua_isnil(L, 4)) + { + if (lua_isnumber(L, 2) || lua_isnil(L, 2)) + { + if (!pen.valid()) + pen = Pen(); + + iidx = -1; + + pen.fg = luaL_optint(L, 2, pen.fg) & 15; + pen.bg = luaL_optint(L, 3, pen.bg); + + if (!lua_isnil(L, 4)) + pen.bold = lua_toboolean(L, 4); + else if (!lua_isnil(L, 2)) + { + pen.bold = !!(pen.fg & 8); + pen.fg &= 7; + } + } + else + { + iidx = 2; + Lua::CheckPen(L, &pen, 2, false, false); + } + } + + if (no_copy && iidx > 0 && lua_isuserdata(L, iidx)) + lua_pushvalue(L, iidx); + else + Lua::Push(L, pen); + + return 1; +} + +static int dfhack_pen_parse(lua_State *L) +{ + return adjust_pen(L, true); +} + +static int dfhack_pen_make(lua_State *L) +{ + return adjust_pen(L, false); +} + +static void make_pen_table(lua_State *L, Pen &pen) +{ + if (!pen.valid()) + luaL_error(L, "invalid pen state"); + else + { + lua_newtable(L); + lua_pushinteger(L, (unsigned char)pen.ch); lua_setfield(L, -2, "ch"); + lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); + lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); + lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); + + if (pen.tile) + { + lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); + } + + switch (pen.tile_mode) { + case Pen::CharColor: + lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); + break; + case Pen::TileColor: + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); + break; + default: + lua_pushboolean(L, false); lua_setfield(L, -2, "tile_color"); + break; + } + } +} + +static void get_pen_mirror(lua_State *L, int idx) +{ + lua_getuservalue(L, idx); + + if (lua_isnil(L, -1)) + { + lua_pop(L, 1); + + Pen pen; + Lua::CheckPen(L, &pen, idx, false, false); + make_pen_table(L, pen); + + lua_dup(L); + lua_setuservalue(L, idx); + } +} + +static int dfhack_pen_index(lua_State *L) +{ + lua_settop(L, 2); + luaL_checktype(L, 1, LUA_TUSERDATA); + + // check metatable + if (!lua_getmetatable(L, 1)) + luaL_argerror(L, 1, "must be a pen"); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + if (!lua_isnil(L, -1)) + return 1; + + // otherwise read from the mirror table, creating it if necessary + lua_settop(L, 2); + get_pen_mirror(L, 1); + lua_pushvalue(L, 2); + lua_rawget(L, -2); + return 1; +} + +static int pen_pnext(lua_State *L) +{ + lua_settop(L, 2); /* create a 2nd argument if there isn't one */ + if (lua_next(L, lua_upvalueindex(1))) + return 2; + lua_pushnil(L); + return 1; +} + +static int dfhack_pen_pairs(lua_State *L) +{ + luaL_checktype(L, 1, LUA_TUSERDATA); + get_pen_mirror(L, 1); + lua_pushcclosure(L, pen_pnext, 1); + lua_pushnil(L); + lua_pushnil(L); + return 3; +} + +const char *const pen_fields[] = { + "ch", "fg", "bold", "bg", "tile", "tile_color", "tile_fg", "tile_bg", NULL +}; + +static int dfhack_pen_newindex(lua_State *L) +{ + lua_settop(L, 3); + luaL_checktype(L, 1, LUA_TUSERDATA); + int id = luaL_checkoption(L, 2, NULL, pen_fields); + int arg = 0; + Pen &pen = *check_pen_native(L, 1); + bool wipe_tile = false, wipe_tc = false; + + switch (id) { + case 0: + if (lua_type(L, 3) != LUA_TNUMBER) + arg = (unsigned char)*luaL_checkstring(L, 3); + else + arg = luaL_checkint(L, 3); + pen.ch = arg; + lua_pushinteger(L, (unsigned char)pen.ch); + break; + case 1: + pen.fg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.fg); + break; + case 2: + pen.bold = lua_toboolean(L, 3); + lua_pushboolean(L, pen.bold); + break; + case 3: + pen.bg = luaL_checkint(L, 3) & 15; + lua_pushinteger(L, pen.bg); + break; + case 4: + arg = lua_isnil(L, 3) ? 0 : luaL_checkint(L, 3); + if (arg < 0) + luaL_argerror(L, 3, "invalid tile index"); + pen.tile = arg; + if (pen.tile) + lua_pushinteger(L, pen.tile); + else + lua_pushnil(L); + break; + case 5: + wipe_tile = (pen.tile_mode == Pen::TileColor); + pen.tile_mode = lua_toboolean(L, 3) ? Pen::CharColor : Pen::AsIs; + lua_pushboolean(L, pen.tile_mode == Pen::CharColor); + break; + case 6: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_bg = 0; } + pen.tile_fg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_fg); + break; + case 7: + if (pen.tile_mode != Pen::TileColor) { wipe_tc = true; pen.tile_fg = 7; } + pen.tile_bg = luaL_checkint(L, 3) & 15; + pen.tile_mode = Pen::TileColor; + lua_pushinteger(L, pen.tile_bg); + break; + } + + lua_getuservalue(L, 1); + + if (!lua_isnil(L, -1)) + { + lua_remove(L, 3); + lua_insert(L, 2); + lua_rawset(L, 2); + + if (wipe_tc) { + lua_pushnil(L); lua_setfield(L, 2, "tile_color"); + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, 2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, 2, "tile_bg"); + } + if (wipe_tile) { + lua_pushnil(L); lua_setfield(L, 2, "tile_fg"); + lua_pushnil(L); lua_setfield(L, 2, "tile_bg"); + } + } + + return 0; +} + +static const luaL_Reg dfhack_pen_funcs[] = { + { "parse", dfhack_pen_parse }, + { "make", dfhack_pen_make }, + { "__index", dfhack_pen_index }, + { "__pairs", dfhack_pen_pairs }, + { "__newindex", dfhack_pen_newindex }, + { NULL, NULL } +}; + +static void OpenPen(lua_State *state) +{ + luaL_getsubtable(state, lua_gettop(state), "pen"); + + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_PEN_TOKEN); + + luaL_setfuncs(state, dfhack_pen_funcs, 0); + + lua_pop(state, 1); +} + /************************ * Wrappers for C++ API * ************************/ @@ -1251,7 +1561,7 @@ static int screen_getWindowSize(lua_State *L) static int screen_paintTile(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); if (lua_gettop(L) >= 4 && !lua_isnil(L, 4)) @@ -1272,44 +1582,14 @@ static int screen_readTile(lua_State *L) int x = luaL_checkint(L, 1); int y = luaL_checkint(L, 2); Pen pen = Screen::readTile(x, y); - - if (!pen.valid()) - { - lua_pushnil(L); - } - else - { - lua_newtable(L); - lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch"); - lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); - lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); - lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); - - if (pen.tile) - { - lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); - - switch (pen.tile_mode) { - case Pen::CharColor: - lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); - break; - case Pen::TileColor: - lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); - lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); - break; - default: - break; - } - } - } - + Lua::Push(L, pen); return 1; } static int screen_paintString(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); const char *text = luaL_checkstring(L, 4); @@ -1320,7 +1600,7 @@ static int screen_paintString(lua_State *L) static int screen_fillRect(lua_State *L) { Pen pen; - decode_pen(L, pen, 1); + Lua::CheckPen(L, &pen, 1); int x1 = luaL_checkint(L, 2); int y1 = luaL_checkint(L, 3); int x2 = luaL_checkint(L, 4); @@ -1720,6 +2000,7 @@ void OpenDFHackApi(lua_State *state) { OpenPersistent(state); OpenMatinfo(state); + OpenPen(state); LuaWrapper::SetFunctionWrappers(state, dfhack_module); OpenModule(state, "gui", dfhack_gui_module); diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index ec4917972..655d069d3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -41,6 +41,9 @@ namespace DFHack { namespace Units { struct NoblePosition; } + namespace Screen { + struct Pen; + }; } namespace DFHack {namespace Lua { @@ -285,6 +288,7 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void Push(lua_State *state, df::coord2d obj); void Push(lua_State *state, const Units::NoblePosition &pos); DFHACK_EXPORT void Push(lua_State *state, MaterialInfo &info); + DFHACK_EXPORT void Push(lua_State *state, const Screen::Pen &info); template inline void Push(lua_State *state, T *ptr) { PushDFObject(state, ptr); } @@ -315,6 +319,8 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT int PushPosXYZ(lua_State *state, df::coord pos); DFHACK_EXPORT int PushPosXY(lua_State *state, df::coord2d pos); + DFHACK_EXPORT void CheckPen(lua_State *L, Screen::Pen *pen, int index, bool allow_nil = false, bool allow_color = true); + DFHACK_EXPORT bool IsCoreContext(lua_State *state); namespace Event { diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index ccd7f2f8d..d8b5774e9 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -76,6 +76,8 @@ namespace DFHack bool valid() const { return tile >= 0; } bool empty() const { return ch == 0 && tile == 0; } + // NOTE: LuaApi.cpp assumes this struct is plain data and has empty destructor + Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 15d03742f..cfb058f9d 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -6,7 +6,9 @@ local dscreen = dfhack.screen USE_GRAPHICS = dscreen.inGraphicsMode() -CLEAR_PEN = {ch=32,fg=0,bg=0} +local to_pen = dfhack.pen.parse + +CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} function simulateInput(screen,...) local keys = {} @@ -116,16 +118,6 @@ function blink_visible(delay) return math.floor(dfhack.getTickCount()/delay) % 2 == 0 end -function to_pen(default, pen, bg, bold) - if pen == nil then - return default or {} - elseif type(pen) ~= 'table' then - return {fg=pen,bg=bg,bold=bold} - else - return pen - end -end - function getKeyDisplay(code) if type(code) == 'string' then code = df.interface_key[code] @@ -215,7 +207,8 @@ Painter = defclass(Painter, ViewRect) function Painter:init(args) self.x = self.x1 self.y = self.y1 - self.cur_pen = to_pen(nil, args.pen or COLOR_GREY) + self.cur_pen = to_pen(args.pen or COLOR_GREY) + self.cur_key_pen = to_pen(args.key_pen or COLOR_LIGHTGREEN) end function Painter.new(rect, pen) @@ -241,6 +234,7 @@ end function Painter:viewport(x,y,w,h) local vp = ViewRect.viewport(x,y,w,h) vp.cur_pen = self.cur_pen + vp.cur_key_pen = self.cur_key_pen return mkinstance(Painter, vp):seek(0,0) end @@ -280,10 +274,12 @@ function Painter:pen(pen,...) end function Painter:color(fg,bold,bg) - self.cur_pen = copyall(self.cur_pen) - self.cur_pen.fg = fg - self.cur_pen.bold = bold - if bg then self.cur_pen.bg = bg end + self.cur_pen = to_pen(self.cur_pen, fg, bg, bold) + return self +end + +function Painter:key_pen(pen,...) + self.cur_key_pen = to_pen(self.cur_key_pen, pen, ...) return self end @@ -339,10 +335,10 @@ function Painter:string(text,pen,...) return self:advance(#text, nil) end -function Painter:key(code,pen,bg,...) +function Painter:key(code,pen,...) return self:string( getKeyDisplay(code), - pen or COLOR_LIGHTGREEN, bg or self.cur_pen.bg, ... + to_pen(self.cur_key_pen, pen, ...) ) end @@ -557,28 +553,28 @@ end -- Plain grey-colored frame. GREY_FRAME = { - frame_pen = { ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_WHITE }, - signature_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, + frame_pen = to_pen{ ch = ' ', fg = COLOR_BLACK, bg = COLOR_GREY }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_WHITE }, + signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, } -- The usual boundary used by the DF screens. Often has fancy pattern in tilesets. BOUNDARY_FRAME = { - frame_pen = { ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, - signature_pen = { fg = COLOR_BLACK, bg = COLOR_DARKGREY }, + frame_pen = to_pen{ ch = 0xDB, fg = COLOR_DARKGREY, bg = COLOR_BLACK }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_DARKGREY }, } GREY_LINE_FRAME = { - frame_pen = { ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, - h_frame_pen = { ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, - v_frame_pen = { ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, - lt_frame_pen = { ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, - lb_frame_pen = { ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, - rt_frame_pen = { ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, - rb_frame_pen = { ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, - title_pen = { fg = COLOR_BLACK, bg = COLOR_GREY }, - signature_pen = { fg = COLOR_DARKGREY, bg = COLOR_BLACK }, + frame_pen = to_pen{ ch = 206, fg = COLOR_GREY, bg = COLOR_BLACK }, + h_frame_pen = to_pen{ ch = 205, fg = COLOR_GREY, bg = COLOR_BLACK }, + v_frame_pen = to_pen{ ch = 186, fg = COLOR_GREY, bg = COLOR_BLACK }, + lt_frame_pen = to_pen{ ch = 201, fg = COLOR_GREY, bg = COLOR_BLACK }, + lb_frame_pen = to_pen{ ch = 200, fg = COLOR_GREY, bg = COLOR_BLACK }, + rt_frame_pen = to_pen{ ch = 187, fg = COLOR_GREY, bg = COLOR_BLACK }, + rb_frame_pen = to_pen{ ch = 188, fg = COLOR_GREY, bg = COLOR_BLACK }, + title_pen = to_pen{ fg = COLOR_BLACK, bg = COLOR_GREY }, + signature_pen = to_pen{ fg = COLOR_DARKGREY, bg = COLOR_BLACK }, } function paint_frame(x1,y1,x2,y2,style,title) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 5811e94e6..0a79b4c3e 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -160,8 +160,8 @@ function ListBox:preinit(info) end function ListBox:init(info) - local spen = gui.to_pen(COLOR_CYAN, self.select_pen, nil, false) - local cpen = gui.to_pen(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) + local spen = dfhack.pen.parse(COLOR_CYAN, self.select_pen, nil, false) + local cpen = dfhack.pen.parse(COLOR_LIGHTCYAN, self.cursor_pen or self.select_pen, nil, true) local list_widget = widgets.List if self.with_filter then From bd8c59462c897207a6f386a73d93bb0f7fe33182 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 17:06:32 +0400 Subject: [PATCH 142/472] Add documentation for the core lua gui library stuff. --- Lua API.html | 833 +++++++++++++++++++++++++++++++++++- Lua API.rst | 782 ++++++++++++++++++++++++++++++++- library/lua/gui/widgets.lua | 5 +- library/lua/utils.lua | 1 + 4 files changed, 1603 insertions(+), 18 deletions(-) diff --git a/Lua API.html b/Lua API.html index 5dff4b3e4..226afa98a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -377,12 +377,34 @@ ul.auto-toc {
                                            • class
                                            • -
                                            • Plugins
                                            • The current version of DFHack has extensive support for @@ -1584,6 +1606,10 @@ The values can then be used for the tile field of pen structur

                                              Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time.

                                              +
                                            • dfhack.screen.getKeyDisplay(key)

                                              +

                                              Returns the string that should be used to represent the given +logical keybinding on the screen in texts like "press Key to ...".

                                              +
                                            • The "pen" argument used by functions above may be represented by a table with the following possible fields:

                                              @@ -1889,6 +1915,19 @@ SC_MAP_UNLOADED, SC_VIEWSCREEN_CHANGED, SC_CORE_INITIALIZED

                                            • Functions already described above

                                              safecall, qerror, mkmodule, reload

                                            • +
                                            • Miscellaneous constants

                                              +
                                            • +++ + + + + + + +
                                              NEWLINE, COMMA, PERIOD:
                                               evaluate to the relevant character strings.
                                              DEFAULT_NIL:is an unspecified unique token used by the class module below.
                                              +
                                            • printall(obj)

                                              If the argument is a lua table or DF object reference, prints all fields.

                                            • @@ -1988,11 +2027,25 @@ are converted to 1-based lua sequences.

                                              as a guide to which values should be skipped as uninteresting. The force argument makes it always return a non-nil value.

                                              +
                                            • utils.parse_bitfield_int(value, type_ref)

                                              +

                                              Given an int value, and a bitfield type in the df tree, +it returns a lua table mapping the enabled bit keys to true, +unless value is 0, in which case it returns nil.

                                              +
                                            • +
                                            • utils.list_bitfield_flags(bitfield[, list])

                                              +

                                              Adds all enabled bitfield keys to list or a newly-allocated +empty sequence, and returns it. The bitfield argument may +be nil.

                                              +
                                            • utils.sort_vector(vector,field,cmpfun)

                                              Sorts a native vector or lua sequence using the comparator function. If field is not nil, applies the comparator to the field instead of the whole object.

                                            • +
                                            • utils.linear_index(vector,key[,field])

                                              +

                                              Searches for key in the vector, and returns index, found_value, +or nil if none found.

                                              +
                                            • utils.binsearch(vector,key,field,cmpfun,min,max)

                                              Does a binary search in a native vector or lua sequence for key, using cmpfun and field like sort_vector. @@ -2021,6 +2074,19 @@ utils.insert_or_update(soul.skills, {new=true, id=..., rating=...}, 'id')

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

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

                                            • +
                                            • utils.call_with_string(obj,methodname,...)

                                              +

                                              Allocates a temporary string object, calls obj:method(tmp,...), and +returns the value written into the temporary after deleting it.

                                              +
                                            • +
                                            • utils.getBuildingName(building)

                                              +

                                              Returns the string description of the given building.

                                              +
                                            • +
                                            • utils.getBuildingCenter(building)

                                              +

                                              Returns an x/y/z table pointing at the building center.

                                              +
                                            • +
                                            • utils.split_string(string, delimiter)

                                              +

                                              Splits the string by the given delimiter, and returns a sequence of results.

                                              +
                                            • 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. @@ -2072,19 +2138,31 @@ calling superclass methods.

                                              from fields in the table used as the constructor argument. If omitted, they are initialized with the default values specified in this declaration.

                                              If the default value should be nil, use ATTRS { foo = DEFAULT_NIL }.

                                              +

                                              Declaring an attribute is mostly the same as defining your init method like this:

                                              +
                                              +function Class.init(args)
                                              +    self.attr1 = args.attr1 or default1
                                              +    self.attr2 = args.attr2 or default2
                                              +    ...
                                              +end
                                              +
                                              +

                                              The main difference is that attributes are processed as a separate +initialization step, before any init methods are called. They +also make the directy relation between instance fields and constructor +arguments more explicit.

                                            • new_obj = Class{ foo = arg, bar = arg, ... }

                                              Calling the class as a function creates and initializes a new instance. Initialization happens in this order:

                                              1. An empty instance table is created, and its metatable set.
                                              2. -
                                              3. The preinit method is called via invoke_before (see below) -with the table used as argument to the class. This method is intended +
                                              4. The preinit methods are called via invoke_before (see below) +with the table used as argument to the class. These methods are intended for validating and tweaking that argument table.
                                              5. Declared ATTRS are initialized from the argument table or their default values.
                                              6. -
                                              7. The init method is called via invoke_after with the argument table. +
                                              8. The init methods are called via invoke_after with the argument table. This is the main constructor method.
                                              9. -
                                              10. The postinit method is called via invoke_after with the argument table. +
                                              11. The postinit methods are called via invoke_after with the argument table. Place code that should be called after the object is fully constructed here.
                                            • @@ -2099,6 +2177,12 @@ Place code that should be called after the object is fully constructed here. +
                                            • instance:cb_getfield(field_name)

                                              +

                                              Returns a closure that returns the specified field of the object when called.

                                              +
                                            • +
                                            • instance:cb_setfield(field_name)

                                              +

                                              Returns a closure that sets the specified field to its argument when called.

                                              +
                                            • instance:invoke_before(method_name, args...)

                                              Navigates the inheritance chain of the instance starting from the most specific class, and invokes the specified method with the arguments if it is defined in @@ -2123,15 +2207,740 @@ library itself uses them for constructors.

                                              To avoid confusion, these methods cannot be redefined.

                                            • +
                                              +

                                              In-game UI Library

                                              +

                                              A number of lua modules with names starting with gui are dedicated +to wrapping the natives of the dfhack.screen module in a way that +is easy to use. This allows relatively easily and naturally creating +dialogs that integrate in the main game UI window.

                                              +

                                              These modules make extensive use of the class module, and define +things ranging from the basic Painter, View and Screen +classes, to fully functional predefined dialogs.

                                              +
                                              +

                                              gui

                                              +

                                              This module defines the most important classes and functions for +implementing interfaces. This documents those of them that are +considered stable.

                                              +
                                              +

                                              Misc

                                              +
                                                +
                                              • USE_GRAPHICS

                                                +

                                                Contains the value of dfhack.screen.inGraphicsMode(), which cannot be +changed without restarting the game and thus is constant during the session.

                                                +
                                              • +
                                              • CLEAR_PEN

                                                +

                                                The black pen used to clear the screen.

                                                +
                                              • +
                                              • simulateInput(screen, keys...)

                                                +

                                                This function wraps an undocumented native function that passes a set of +keycodes to a screen, and is the official way to do that.

                                                +

                                                Every argument after the initial screen may be nil, a numeric keycode, +a string keycode, a sequence of numeric or string keycodes, or a mapping +of keycodes to true or false. For instance, it is possible to use the +table passed as argument to onInput.

                                                +
                                              • +
                                              • mkdims_xy(x1,y1,x2,y2)

                                                +

                                                Returns a table containing the arguments as fields, and also width and +height that contains the rectangle dimensions.

                                                +
                                              • +
                                              • mkdims_wh(x1,y1,width,height)

                                                +

                                                Returns the same kind of table as mkdims_xy, only this time it computes +x2 and y2.

                                                +
                                              • +
                                              • is_in_rect(rect,x,y)

                                                +

                                                Checks if the given point is within a rectangle, represented by a table produced +by one of the mkdims functions.

                                                +
                                              • +
                                              • blink_visible(delay)

                                                +

                                                Returns true or false, with the value switching to the opposite every delay +msec. This is intended for rendering blinking interface objects.

                                                +
                                              • +
                                              • getKeyDisplay(keycode)

                                                +

                                                Wraps dfhack.screen.getKeyDisplay in order to allow using strings for the keycode argument.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              ViewRect class

                                              +

                                              This class represents an on-screen rectangle with an associated independent +clip area rectangle. It is the base of the Painter class, and is used by +Views to track their client area.

                                              +
                                                +
                                              • ViewRect{ rect = ..., clip_rect = ..., view_rect = ..., clip_view = ... }

                                                +

                                                The constructor has the following arguments:

                                                + +++ + + + + + + + + + +
                                                rect:The mkdims rectangle in screen coordinates of the logical viewport. +Defaults to the whole screen.
                                                clip_rect:The clip rectangle in screen coordinates. Defaults to rect.
                                                view_rect:A ViewRect object to copy from; overrides both rect and clip_rect.
                                                clip_view:A ViewRect object to intersect the specified clip area with.
                                                +
                                              • +
                                              • rect:isDefunct()

                                                +

                                                Returns true if the clip area is empty, i.e. no painting is possible.

                                                +
                                              • +
                                              • rect:inClipGlobalXY(x,y)

                                                +

                                                Checks if these global coordinates are within the clip rectangle.

                                                +
                                              • +
                                              • rect:inClipLocalXY(x,y)

                                                +

                                                Checks if these coordinates (specified relative to x1,y1) are within the clip rectangle.

                                                +
                                              • +
                                              • rect:localXY(x,y)

                                                +

                                                Converts a pair of global coordinates to local; returns x_local,y_local.

                                                +
                                              • +
                                              • rect:globalXY(x,y)

                                                +

                                                Converts a pair of local coordinates to global; returns x_global,y_global.

                                                +
                                              • +
                                              • rect:viewport(x,y,w,h) or rect:viewport(subrect)

                                                +

                                                Returns a ViewRect representing a sub-rectangle of the current one. +The arguments are specified in local coordinates; the subrect +argument must be a mkdims table. The returned object consists of +the exact specified rectangle, and a clip area produced by intersecting +it with the clip area of the original object.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              Painter class

                                              +

                                              The painting natives in dfhack.screen apply to the whole screen, are +completely stateless and don't implement clipping.

                                              +

                                              The Painter class inherits from ViewRect to provide clipping and local +coordinates, and tracks current cursor position and current pen.

                                              +
                                                +
                                              • Painter{ ..., pen = ..., key_pen = ... }

                                                +

                                                In addition to ViewRect arguments, Painter accepts a suggestion of +the initial value for the main pen, and the keybinding pen. They +default to COLOR_GREY and COLOR_LIGHTGREEN otherwise.

                                                +

                                                There are also some convenience functions that wrap this constructor:

                                                +
                                                  +
                                                • Painter.new(rect,pen)
                                                • +
                                                • Painter.new_view(view_rect,pen)
                                                • +
                                                • Painter.new_xy(x1,y1,x2,y2,pen)
                                                • +
                                                • Painter.new_wh(x1,y1,width,height,pen)
                                                • +
                                                +
                                              • +
                                              • painter:isValidPos()

                                                +

                                                Checks if the current cursor position is within the clip area.

                                                +
                                              • +
                                              • painter:viewport(x,y,w,h)

                                                +

                                                Like the superclass method, but returns a Painter object.

                                                +
                                              • +
                                              • painter:cursor()

                                                +

                                                Returns the current cursor x,y in local coordinates.

                                                +
                                              • +
                                              • painter:seek(x,y)

                                                +

                                                Sets the current cursor position, and returns self. +Either of the arguments may be nil to keep the current value.

                                                +
                                              • +
                                              • painter:advance(dx,dy)

                                                +

                                                Adds the given offsets to the cursor position, and returns self. +Either of the arguments may be nil to keep the current value.

                                                +
                                              • +
                                              • painter:newline([dx])

                                                +

                                                Advances the cursor to the start of the next line plus the given x offset, and returns self.

                                                +
                                              • +
                                              • painter:pen(...)

                                                +

                                                Sets the current pen to dfhack.pen.parse(old_pen,...), and returns self.

                                                +
                                              • +
                                              • painter:key_pen(...)

                                                +

                                                Sets the current keybinding pen to dfhack.pen.parse(old_pen,...), and returns self.

                                                +
                                              • +
                                              • painter:clear()

                                                +

                                                Fills the whole clip rectangle with CLEAR_PEN, and returns self.

                                                +
                                              • +
                                              • painter:fill(x1,y1,x2,y2[,...]) or painter:fill(rect[,...])

                                                +

                                                Fills the specified local coordinate rectangle with dfhack.pen.parse(cur_pen,...), +and returns self.

                                                +
                                              • +
                                              • painter:char([char[, ...]])

                                                +

                                                Paints one character using char and dfhack.pen.parse(cur_pen,...); returns self. +The char argument, if not nil, is used to override the ch property of the pen.

                                                +
                                              • +
                                              • painter:tile([char, tile[, ...]])

                                                +

                                                Like above, but also allows overriding the tile property on ad-hoc basis.

                                                +
                                              • +
                                              • painter:string(text[, ...])

                                                +

                                                Paints the string with dfhack.pen.parse(cur_pen,...); returns self.

                                                +
                                              • +
                                              • painter:key(keycode[, ...])

                                                +

                                                Paints the description of the keycode using dfhack.pen.parse(cur_key_pen,...); returns self.

                                                +
                                              • +
                                              +

                                              As noted above, all painting methods return self, in order to allow chaining them like this:

                                              +
                                              +painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...
                                              +
                                              +
                                              +
                                              +

                                              View class

                                              +

                                              This class is the common abstract base of both the stand-alone screens +and common widgets to be used inside them. It defines the basic layout, +rendering and event handling framework.

                                              +

                                              The class defines the following attributes:

                                              + +++ + + + + + + + +
                                              visible:Specifies that the view should be painted.
                                              active:Specifies that the view should receive events, if also visible.
                                              view_id:Specifies an identifier to easily identify the view among subviews. +This is reserved for implementation of top-level views, and should +not be used by widgets for their internal subviews.
                                              +

                                              It also always has the following fields:

                                              + +++ + + + +
                                              subviews:Contains a table of all subviews. The sequence part of the +table is used for iteration. In addition, subviews are also +indexed under their view_id, if any; see addviews() below.
                                              +

                                              These fields are computed by the layout process:

                                              + +++ + + + + + + + + +
                                              frame_parent_rect:
                                               The ViewRect represeting the client area of the parent view.
                                              frame_rect:The mkdims rect of the outer frame in parent-local coordinates.
                                              frame_body:The ViewRect representing the body part of the View's own frame.
                                              +

                                              The class has the following methods:

                                              +
                                                +
                                              • view:addviews(list)

                                                +

                                                Adds the views in the list to the subviews sequence. If any of the views +in the list have view_id attributes that don't conflict with existing keys +in subviews, also stores them under the string keys. Finally, copies any +non-conflicting string keys from the subviews tables of the listed views.

                                                +

                                                Thus, doing something like this:

                                                +
                                                +self:addviews{
                                                +    Panel{
                                                +        view_id = 'panel',
                                                +        subviews = {
                                                +            Label{ view_id = 'label' }
                                                +        }
                                                +    }
                                                +}
                                                +
                                                +

                                                Would make the label accessible as both self.subviews.label and +self.subviews.panel.subviews.label.

                                                +
                                              • +
                                              • view:getWindowSize()

                                                +

                                                Returns the dimensions of the frame_body rectangle.

                                                +
                                              • +
                                              • view:getMousePos()

                                                +

                                                Returns the mouse x,y in coordinates local to the frame_body +rectangle if it is within its clip area, or nothing otherwise.

                                                +
                                              • +
                                              • view:updateLayout([parent_rect])

                                                +

                                                Recomputes layout of the view and its subviews. If no argument is +given, re-uses the previous parent rect. The process goes as follows:

                                                +
                                                  +
                                                1. Calls preUpdateLayout(parent_rect) via invoke_before.
                                                2. +
                                                3. Uses computeFrame(parent_rect) to compute the desired frame.
                                                4. +
                                                5. Calls postComputeFrame(frame_body) via invoke_after.
                                                6. +
                                                7. Calls updateSubviewLayout(frame_body) to update children.
                                                8. +
                                                9. Calls postUpdateLayout(frame_body) via invoke_after.
                                                10. +
                                                +
                                              • +
                                              • view:computeFrame(parent_rect) (for overriding)

                                                +

                                                Called by updateLayout in order to compute the frame rectangle(s). +Should return the mkdims rectangle for the outer frame, and optionally +also for the body frame. If only one rectangle is returned, it is used +for both frames, and the margin becomes zero.

                                                +
                                              • +
                                              • view:updateSubviewLayout(frame_body)

                                                +

                                                Calls updateLayout on all children.

                                                +
                                              • +
                                              • view:render(painter)

                                                +

                                                Given the parent's painter, renders the view via the following process:

                                                +
                                                  +
                                                1. Calls onRenderFrame(painter, frame_rect) to paint the outer frame.
                                                2. +
                                                3. Creates a new painter using the frame_body rect.
                                                4. +
                                                5. Calls onRenderBody(new_painter) to paint the client area.
                                                6. +
                                                7. Calls renderSubviews(new_painter) to paint visible children.
                                                8. +
                                                +
                                              • +
                                              • view:renderSubviews(painter)

                                                +

                                                Calls render on all visible subviews in the order they +appear in the subviews sequence.

                                                +
                                              • +
                                              • view:onRenderFrame(painter, rect) (for overriding)

                                                +

                                                Called by render to paint the outer frame; by default does nothing.

                                                +
                                              • +
                                              • view:onRenderBody(painter) (for overriding)

                                                +

                                                Called by render to paint the client area; by default does nothing.

                                                +
                                              • +
                                              • view:onInput(keys) (for overriding)

                                                +

                                                Override this to handle events. By default directly calls inputToSubviews. +Return a true value from this method to signal that the event has been handled +and should not be passed on to more views.

                                                +
                                              • +
                                              • view:inputToSubviews(keys)

                                                +

                                                Calls onInput on all visible active subviews, iterating the subviews +sequence in reverse order, so that topmost subviews get events first. +Returns true if any of the subviews handled the event.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              Screen class

                                              +

                                              This is a View subclass intended for use as a stand-alone dialog or screen. +It adds the following methods:

                                              +
                                                +
                                              • screen:isShown()

                                                +

                                                Returns true if the screen is currently in the game engine's display stack.

                                                +
                                              • +
                                              • screen:isDismissed()

                                                +

                                                Returns true if the screen is dismissed.

                                                +
                                              • +
                                              • screen:isActive()

                                                +

                                                Returns true if the screen is shown and not dismissed.

                                                +
                                              • +
                                              • screen:invalidate()

                                                +

                                                Requests a repaint. Note that currently using it is not necessary, because +repaints are constantly requested automatically, due to issues with native +screens happening otherwise.

                                                +
                                              • +
                                              • screen:renderParent()

                                                +

                                                Asks the parent native screen to render itself, or clears the screen if impossible.

                                                +
                                              • +
                                              • screen:sendInputToParent(...)

                                                +

                                                Uses simulateInput to send keypresses to the native parent screen.

                                                +
                                              • +
                                              • screen:show([parent])

                                                +

                                                Adds the screen to the display stack with the given screen as the parent; +if parent is not specified, places this one one topmost. Before calling +dfhack.screen.show, calls self:onAboutToShow(parent).

                                                +
                                              • +
                                              • screen:onAboutToShow(parent) (for overriding)

                                                +

                                                Called when dfhack.screen.show is about to be called.

                                                +
                                              • +
                                              • screen:onShow()

                                                +

                                                Called by dfhack.screen.show once the screen is successfully shown.

                                                +
                                              • +
                                              • screen:dismiss()

                                                +

                                                Dismisses the screen. A dismissed screen does not receive any more +events or paint requests, but may remain in the display stack for +a short time until the game removes it.

                                                +
                                              • +
                                              • screen:onDismiss() (for overriding)

                                                +

                                                Called by dfhack.screen.dismiss().

                                                +
                                              • +
                                              • screen:onDestroy() (for overriding)

                                                +

                                                Called by the native code when the screen is fully destroyed and removed +from the display stack. Place code that absolutely must be called whenever +the screen is removed by any means here.

                                                +
                                              • +
                                              • screen:onResize, screen:onRender

                                                +

                                                Defined as callbacks for native code.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              FramedScreen class

                                              +

                                              A Screen subclass that paints a visible frame around its body. +Most dialogs should inherit from this class.

                                              +

                                              A framed screen has the following attributes:

                                              + +++ + + + + + + + + + + + + + + +
                                              frame_style:A table that defines a set of pens to draw various parts of the frame.
                                              frame_title:A string to display in the middle of the top of the frame.
                                              frame_width:Desired width of the client area. If nil, the screen will occupy the whole width.
                                              frame_height:Likewise, for height.
                                              frame_inset:The gap between the frame and the client area. Defaults to 0.
                                              frame_background:
                                               The pen to fill in the frame with. Defaults to CLEAR_PEN.
                                              +

                                              There are the following predefined frame style tables:

                                              +
                                                +
                                              • GREY_FRAME

                                                +

                                                A plain grey-colored frame.

                                                +
                                              • +
                                              • BOUNDARY_FRAME

                                                +

                                                The same frame as used by the usual full-screen DF views, like dwarfmode.

                                                +
                                              • +
                                              • GREY_LINE_FRAME

                                                +

                                                A frame consisting of grey lines, similar to the one used by titan announcements.

                                                +
                                              • +
                                              +
                                              +
                                              +
                                              +

                                              gui.widgets

                                              +

                                              This module implements some basic widgets based on the View infrastructure.

                                              +
                                              +

                                              Widget class

                                              +

                                              Base of all the widgets. Inherits from View and has the following attributes:

                                              +
                                                +
                                              • frame = {...}

                                                +

                                                Specifies the constraints on the outer frame of the widget. +If omitted, the widget will occupy the whole parent rectangle.

                                                +

                                                The frame is specified as a table with the following possible fields:

                                                + +++ + + + + + + + + + + + + + + + + + +
                                                l:gap between the left edges of the frame and the parent.
                                                t:gap between the top edges of the frame and the parent.
                                                r:gap between the right edges of the frame and the parent.
                                                b:gap between the bottom edges of the frame and the parent.
                                                w:maximum width of the frame.
                                                h:maximum heigth of the frame.
                                                xalign:X alignment of the frame.
                                                yalign:Y alignment of the frame.
                                                +

                                                First the l,t,r,b fields restrict the available area for +placing the frame. If w and h are not specified or +larger then the computed area, it becomes the frame. Otherwise +the smaller frame is placed within the are based on the +xalign/yalign fields. If the align hints are omitted, they +are assumed to be 0, 1, or 0.5 based on which of the l/r/t/b +fields are set.

                                                +
                                              • +
                                              • frame_inset = {...}

                                                +

                                                Specifies the gap between the outer frame, and the client area. +The attribute may be a simple integer value to specify a uniform +inset, or a table with the following fields:

                                                + +++ + + + + + + + + + + + + + +
                                                l:left margin.
                                                t:top margin.
                                                r:right margin.
                                                b:bottom margin.
                                                x:left/right margin, if l and/or r are omitted.
                                                y:top/bottom margin, if t and/or b are omitted.
                                                +
                                              • +
                                              • frame_background = pen

                                                +

                                                The pen to fill the outer frame with. Defaults to no fill.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              Panel class

                                              +

                                              Inherits from Widget, and intended for grouping a number of subviews.

                                              +

                                              Has attributes:

                                              +
                                                +
                                              • subviews = {}

                                                +

                                                Used to initialize the subview list in the constructor.

                                                +
                                              • +
                                              • on_render = function(painter)

                                                +

                                                Called from onRenderBody.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              Pages class

                                              +

                                              Subclass of Panel; keeps exactly one child visible.

                                              +
                                                +
                                              • Pages{ ..., selected = ... }

                                                +

                                                Specifies which child to select initially; defaults to the first one.

                                                +
                                              • +
                                              • pages:getSelected()

                                                +

                                                Returns the selected index, child.

                                                +
                                              • +
                                              • pages:setSelected(index)

                                                +

                                                Selects the specified child, hiding the previous selected one. +It is permitted to use the subview object, or its view_id as index.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              EditField class

                                              +

                                              Subclass of Widget; implements a simple edit field.

                                              +

                                              Attributes:

                                              + +++ + + + + + + + + + + + +
                                              text:The current contents of the field.
                                              text_pen:The pen to draw the text with.
                                              on_char:Input validation callback; used as on_char(new_char,text). +If it returns false, the character is ignored.
                                              on_change:Change notification callback; used as on_change(new_text,old_text).
                                              on_submit:Enter key callback; if set the field will handle the key and call on_submit(text).
                                              +
                                              +
                                              +

                                              Label class

                                              +

                                              This Widget subclass implements flowing semi-static text.

                                              +

                                              It has the following attributes:

                                              + +++ + + + + + + + + + + + + + +
                                              text_pen:Specifies the pen for active text.
                                              text_dpen:Specifies the pen for disabled text.
                                              disabled:Boolean or a callback; if true, the label is disabled.
                                              enabled:Boolean or a callback; if false, the label is disabled.
                                              auto_height:Sets self.frame.h from the text height.
                                              auto_width:Sets self.frame.w from the text width.
                                              +

                                              The text itself is represented as a complex structure, and passed +to the object via the text argument of the constructor, or via +the setText method, as one of:

                                              +
                                                +
                                              • A simple string, possibly containing newlines.
                                              • +
                                              • A sequence of tokens.
                                              • +
                                              +

                                              Every token in the sequence in turn may be either a string, possibly +containing newlines, or a table with the following possible fields:

                                              +
                                                +
                                              • token.text = ...

                                                +

                                                Specifies the main text content of a token, and may be a string, or +a callback returning a string.

                                                +
                                              • +
                                              • token.gap = ...

                                                +

                                                Specifies the number of character positions to advance on the line +before rendering the token.

                                                +
                                              • +
                                              • token.tile = pen

                                                +

                                                Specifies a pen to paint as one tile before the main part of the token.

                                                +
                                              • +
                                              • token.key = '...'

                                                +

                                                Specifies the keycode associated with the token. The string description +of the key binding is added to the text content of the token.

                                                +
                                              • +
                                              • token.key_sep = '...'

                                                +

                                                Specifies the separator to place between the keybinding label produced +by token.key, and the main text of the token. If the separator is +'()', the token is formatted as text..' ('..binding..')'. Otherwise +it is simply binding..sep..text.

                                                +
                                              • +
                                              • token.enabled, token.disabled

                                                +

                                                Same as the attributes of the label itself, but applies only to the token.

                                                +
                                              • +
                                              • token.pen, token.dpen

                                                +

                                                Specify the pen and disabled pen to be used for the token's text. +The field may be either the pen itself, or a callback that returns it.

                                                +
                                              • +
                                              • token.on_activate

                                                +

                                                If this field is not nil, and token.key is set, the token will actually +respond to that key binding unless disabled, and call this callback. Eventually +this may be extended with mouse click support.

                                                +
                                              • +
                                              • token.id

                                                +

                                                Specifies a unique identifier for the token.

                                                +
                                              • +
                                              • token.line, token.x1, token.x2

                                                +

                                                Reserved for internal use.

                                                +
                                              • +
                                              +

                                              The Label widget implements the following methods:

                                              +
                                                +
                                              • label:setText(new_text)

                                                +

                                                Replaces the text currently contained in the widget.

                                                +
                                              • +
                                              • label:itemById(id)

                                                +

                                                Finds a token by its id field.

                                                +
                                              • +
                                              • label:getTextHeight()

                                                +

                                                Computes the height of the text.

                                                +
                                              • +
                                              • label:getTextWidth()

                                                +

                                                Computes the width of the text.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              List class

                                              +

                                              The List widget implements a simple list with paging.

                                              +

                                              It has the following attributes:

                                              + +++ + + + + + + + + + + + + + + + + + + + +
                                              text_pen:Specifies the pen for deselected list entries.
                                              cursor_pen:Specifies the pen for the selected entry.
                                              inactive_pen:If specified, used for the cursor when the widget is not active.
                                              icon_pen:Default pen for icons.
                                              on_select:Selection change callback; called as on_select(index,choice).
                                              on_submit:Enter key callback; if specified, the list reacts to the key +and calls it as on_submit(index,choice).
                                              row_height:Height of every row in text lines.
                                              icon_width:If not nil, the specified number of character columns +are reserved to the left of the list item for the icons.
                                              scroll_keys:Specifies which keys the list should react to as a table.
                                              +

                                              Every list item may be specified either as a string, or as a lua table +with the following fields:

                                              + +++ + + + + + + + + + + + + + +
                                              text:Specifies the label text in the same format as the Label text.
                                              caption, [1]:Deprecated legacy aliases for text.
                                              text_*:Reserved for internal use.
                                              key:Specifies a keybinding that acts as a shortcut for the specified item.
                                              icon:Specifies an icon string, or a pen to paint a single character. May be a callback.
                                              icon_pen:When the icon is a string, used to paint it.
                                              +

                                              The list supports the following methods:

                                              +
                                                +
                                              • List{ ..., choices = ..., selected = ... }

                                                +

                                                Same as calling setChoices after construction.

                                                +
                                              • +
                                              • list:setChoices(choices[, selected])

                                                +

                                                Replaces the list of choices, possibly also setting the currently selected index.

                                                +
                                              • +
                                              • list:setSelected(selected)

                                                +

                                                Sets the currently selected index. Returns the index after validation.

                                                +
                                              • +
                                              • list:getChoices()

                                                +

                                                Returns the list of choices.

                                                +
                                              • +
                                              • list:getSelected()

                                                +

                                                Returns the selected index, choice, or nothing if the list is empty.

                                                +
                                              • +
                                              • list:getContentWidth()

                                                +

                                                Returns the minimal width to draw all choices without clipping.

                                                +
                                              • +
                                              • list:getContentHeight()

                                                +

                                                Returns the minimal width to draw all choices without scrolling.

                                                +
                                              • +
                                              • list:submit()

                                                +

                                                Call the on_submit callback, as if the Enter key was handled.

                                                +
                                              • +
                                              +
                                              +
                                              +

                                              FilteredList class

                                              +

                                              This widget combines List, EditField and Label into a combo-box like +construction that allows filtering the list by subwords of its items.

                                              +

                                              In addition to passing through all attributes supported by List, it +supports:

                                              + +++ + + + + + + +
                                              edit_pen:If specified, used instead of cursor_pen for the edit field.
                                              not_found_label:
                                               Specifies the text of the label shown when no items match the filter.
                                              +

                                              The list choices may include the following attributes:

                                              + +++ + + + +
                                              search_key:If specified, used instead of text to match against the filter.
                                              +

                                              The widget implements:

                                              +
                                                +
                                              • list:setChoices(choices[, selected])

                                                +

                                                Resets the filter, and passes through to the inner list.

                                                +
                                              • +
                                              • list:getChoices()

                                                +

                                                Returns the list of all choices.

                                                +
                                              • +
                                              • list:getFilter()

                                                +

                                                Returns the current filter string, and the filtered list of choices.

                                                +
                                              • +
                                              • list:setFilter(filter[,pos])

                                                +

                                                Sets the new filter string, filters the list, and selects the item at +index pos in the unfiltered list if possible.

                                                +
                                              • +
                                              • list:canSubmit()

                                                +

                                                Checks if there are currently any choices in the filtered list.

                                                +
                                              • +
                                              • list:getSelected(), list:getContentWidth(), list:getContentHeight(), list:submit()

                                                +

                                                Same as with an ordinary list.

                                                +
                                              • +
                                              +
                                              +
                                              +
                                              -

                                              Plugins

                                              +

                                              Plugins

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

                                              The following plugins have lua support.

                                              -

                                              burrows

                                              +

                                              burrows

                                              Implements extended burrow manipulations.

                                              Events:

                                                @@ -2169,13 +2978,13 @@ set is the same as used by the command line.

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

                                              -

                                              sort

                                              +

                                              sort

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

                                              -

                                              Scripts

                                              +

                                              Scripts

                                              Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans diff --git a/Lua API.rst b/Lua API.rst index ec48938db..d06e3d2e6 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1441,6 +1441,11 @@ Basic painting functions: Requests repaint of the screen by setting a flag. Unlike other functions in this section, this may be used at any time. +* ``dfhack.screen.getKeyDisplay(key)`` + + Returns the string that should be used to represent the given + logical keybinding on the screen in texts like "press Key to ...". + The "pen" argument used by functions above may be represented by a table with the following possible fields: @@ -1796,6 +1801,11 @@ environment by the mandatory init file dfhack.lua: safecall, qerror, mkmodule, reload +* Miscellaneous constants + + :NEWLINE, COMMA, PERIOD: evaluate to the relevant character strings. + :DEFAULT_NIL: is an unspecified unique token used by the class module below. + * ``printall(obj)`` If the argument is a lua table or DF object reference, prints all fields. @@ -1906,12 +1916,29 @@ utils as a guide to which values should be skipped as uninteresting. The ``force`` argument makes it always return a non-*nil* value. +* ``utils.parse_bitfield_int(value, type_ref)`` + + Given an int ``value``, and a bitfield type in the ``df`` tree, + it returns a lua table mapping the enabled bit keys to *true*, + unless value is 0, in which case it returns *nil*. + +* ``utils.list_bitfield_flags(bitfield[, list])`` + + Adds all enabled bitfield keys to ``list`` or a newly-allocated + empty sequence, and returns it. The ``bitfield`` argument may + be *nil*. + * ``utils.sort_vector(vector,field,cmpfun)`` Sorts a native vector or lua sequence using the comparator function. If ``field`` is not *nil*, applies the comparator to the field instead of the whole object. +* ``utils.linear_index(vector,key[,field])`` + + Searches for ``key`` in the vector, and returns *index, found_value*, + or *nil* if none found. + * ``utils.binsearch(vector,key,field,cmpfun,min,max)`` Does a binary search in a native vector or lua sequence for @@ -1947,6 +1974,23 @@ utils Exactly like ``erase_sorted_key``, but if field is specified, takes the key from ``item[field]``. +* ``utils.call_with_string(obj,methodname,...)`` + + Allocates a temporary string object, calls ``obj:method(tmp,...)``, and + returns the value written into the temporary after deleting it. + +* ``utils.getBuildingName(building)`` + + Returns the string description of the given building. + +* ``utils.getBuildingCenter(building)`` + + Returns an x/y/z table pointing at the building center. + +* ``utils.split_string(string, delimiter)`` + + Splits the string by the given delimiter, and returns a sequence of results. + * ``utils.prompt_yes_no(prompt, default)`` Presents a yes/no prompt to the user. If ``default`` is not *nil*, @@ -2006,19 +2050,32 @@ Implements a trivial single-inheritance class system. If the default value should be *nil*, use ``ATTRS { foo = DEFAULT_NIL }``. + Declaring an attribute is mostly the same as defining your ``init`` method like this:: + + function Class.init(args) + self.attr1 = args.attr1 or default1 + self.attr2 = args.attr2 or default2 + ... + end + + The main difference is that attributes are processed as a separate + initialization step, before any ``init`` methods are called. They + also make the directy relation between instance fields and constructor + arguments more explicit. + * ``new_obj = Class{ foo = arg, bar = arg, ... }`` Calling the class as a function creates and initializes a new instance. Initialization happens in this order: 1. An empty instance table is created, and its metatable set. - 2. The ``preinit`` method is called via ``invoke_before`` (see below) - with the table used as argument to the class. This method is intended + 2. The ``preinit`` methods are called via ``invoke_before`` (see below) + with the table used as argument to the class. These methods are intended for validating and tweaking that argument table. 3. Declared ATTRS are initialized from the argument table or their default values. - 4. The ``init`` method is called via ``invoke_after`` with the argument table. + 4. The ``init`` methods are called via ``invoke_after`` with the argument table. This is the main constructor method. - 5. The ``postinit`` method is called via ``invoke_after`` with the argument table. + 5. The ``postinit`` methods are called via ``invoke_after`` with the argument table. Place code that should be called after the object is fully constructed here. Predefined instance methods: @@ -2033,6 +2090,14 @@ Predefined instance methods: properly passing in self, and optionally a number of initial arguments too. The arguments given to the closure are appended to these. +* ``instance:cb_getfield(field_name)`` + + Returns a closure that returns the specified field of the object when called. + +* ``instance:cb_setfield(field_name)`` + + Returns a closure that sets the specified field to its argument when called. + * ``instance:invoke_before(method_name, args...)`` Navigates the inheritance chain of the instance starting from the most specific @@ -2057,6 +2122,715 @@ Predefined instance methods: To avoid confusion, these methods cannot be redefined. +================== +In-game UI Library +================== + +A number of lua modules with names starting with ``gui`` are dedicated +to wrapping the natives of the ``dfhack.screen`` module in a way that +is easy to use. This allows relatively easily and naturally creating +dialogs that integrate in the main game UI window. + +These modules make extensive use of the ``class`` module, and define +things ranging from the basic ``Painter``, ``View`` and ``Screen`` +classes, to fully functional predefined dialogs. + +gui +=== + +This module defines the most important classes and functions for +implementing interfaces. This documents those of them that are +considered stable. + + +Misc +---- + +* ``USE_GRAPHICS`` + + Contains the value of ``dfhack.screen.inGraphicsMode()``, which cannot be + changed without restarting the game and thus is constant during the session. + +* ``CLEAR_PEN`` + + The black pen used to clear the screen. + +* ``simulateInput(screen, keys...)`` + + This function wraps an undocumented native function that passes a set of + keycodes to a screen, and is the official way to do that. + + Every argument after the initial screen may be *nil*, a numeric keycode, + a string keycode, a sequence of numeric or string keycodes, or a mapping + of keycodes to *true* or *false*. For instance, it is possible to use the + table passed as argument to ``onInput``. + +* ``mkdims_xy(x1,y1,x2,y2)`` + + Returns a table containing the arguments as fields, and also ``width`` and + ``height`` that contains the rectangle dimensions. + +* ``mkdims_wh(x1,y1,width,height)`` + + Returns the same kind of table as ``mkdims_xy``, only this time it computes + ``x2`` and ``y2``. + +* ``is_in_rect(rect,x,y)`` + + Checks if the given point is within a rectangle, represented by a table produced + by one of the ``mkdims`` functions. + +* ``blink_visible(delay)`` + + Returns *true* or *false*, with the value switching to the opposite every ``delay`` + msec. This is intended for rendering blinking interface objects. + +* ``getKeyDisplay(keycode)`` + + Wraps ``dfhack.screen.getKeyDisplay`` in order to allow using strings for the keycode argument. + + +ViewRect class +-------------- + +This class represents an on-screen rectangle with an associated independent +clip area rectangle. It is the base of the ``Painter`` class, and is used by +``Views`` to track their client area. + +* ``ViewRect{ rect = ..., clip_rect = ..., view_rect = ..., clip_view = ... }`` + + The constructor has the following arguments: + + :rect: The ``mkdims`` rectangle in screen coordinates of the logical viewport. + Defaults to the whole screen. + :clip_rect: The clip rectangle in screen coordinates. Defaults to ``rect``. + :view_rect: A ViewRect object to copy from; overrides both ``rect`` and ``clip_rect``. + :clip_view: A ViewRect object to intersect the specified clip area with. + +* ``rect:isDefunct()`` + + Returns *true* if the clip area is empty, i.e. no painting is possible. + +* ``rect:inClipGlobalXY(x,y)`` + + Checks if these global coordinates are within the clip rectangle. + +* ``rect:inClipLocalXY(x,y)`` + + Checks if these coordinates (specified relative to ``x1,y1``) are within the clip rectangle. + +* ``rect:localXY(x,y)`` + + Converts a pair of global coordinates to local; returns *x_local,y_local*. + +* ``rect:globalXY(x,y)`` + + Converts a pair of local coordinates to global; returns *x_global,y_global*. + +* ``rect:viewport(x,y,w,h)`` or ``rect:viewport(subrect)`` + + Returns a ViewRect representing a sub-rectangle of the current one. + The arguments are specified in local coordinates; the ``subrect`` + argument must be a ``mkdims`` table. The returned object consists of + the exact specified rectangle, and a clip area produced by intersecting + it with the clip area of the original object. + + +Painter class +------------- + +The painting natives in ``dfhack.screen`` apply to the whole screen, are +completely stateless and don't implement clipping. + +The Painter class inherits from ViewRect to provide clipping and local +coordinates, and tracks current cursor position and current pen. + +* ``Painter{ ..., pen = ..., key_pen = ... }`` + + In addition to ViewRect arguments, Painter accepts a suggestion of + the initial value for the main pen, and the keybinding pen. They + default to COLOR_GREY and COLOR_LIGHTGREEN otherwise. + + There are also some convenience functions that wrap this constructor: + + - ``Painter.new(rect,pen)`` + - ``Painter.new_view(view_rect,pen)`` + - ``Painter.new_xy(x1,y1,x2,y2,pen)`` + - ``Painter.new_wh(x1,y1,width,height,pen)`` + +* ``painter:isValidPos()`` + + Checks if the current cursor position is within the clip area. + +* ``painter:viewport(x,y,w,h)`` + + Like the superclass method, but returns a Painter object. + +* ``painter:cursor()`` + + Returns the current cursor *x,y* in local coordinates. + +* ``painter:seek(x,y)`` + + Sets the current cursor position, and returns *self*. + Either of the arguments may be *nil* to keep the current value. + +* ``painter:advance(dx,dy)`` + + Adds the given offsets to the cursor position, and returns *self*. + Either of the arguments may be *nil* to keep the current value. + +* ``painter:newline([dx])`` + + Advances the cursor to the start of the next line plus the given x offset, and returns *self*. + +* ``painter:pen(...)`` + + Sets the current pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. + +* ``painter:key_pen(...)`` + + Sets the current keybinding pen to ``dfhack.pen.parse(old_pen,...)``, and returns *self*. + +* ``painter:clear()`` + + Fills the whole clip rectangle with ``CLEAR_PEN``, and returns *self*. + +* ``painter:fill(x1,y1,x2,y2[,...])`` or ``painter:fill(rect[,...])`` + + Fills the specified local coordinate rectangle with ``dfhack.pen.parse(cur_pen,...)``, + and returns *self*. + +* ``painter:char([char[, ...]])`` + + Paints one character using ``char`` and ``dfhack.pen.parse(cur_pen,...)``; returns *self*. + The ``char`` argument, if not nil, is used to override the ``ch`` property of the pen. + +* ``painter:tile([char, tile[, ...]])`` + + Like above, but also allows overriding the ``tile`` property on ad-hoc basis. + +* ``painter:string(text[, ...])`` + + Paints the string with ``dfhack.pen.parse(cur_pen,...)``; returns *self*. + +* ``painter:key(keycode[, ...])`` + + Paints the description of the keycode using ``dfhack.pen.parse(cur_key_pen,...)``; returns *self*. + +As noted above, all painting methods return *self*, in order to allow chaining them like this:: + + painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')... + + +View class +---------- + +This class is the common abstract base of both the stand-alone screens +and common widgets to be used inside them. It defines the basic layout, +rendering and event handling framework. + +The class defines the following attributes: + +:visible: Specifies that the view should be painted. +:active: Specifies that the view should receive events, if also visible. +:view_id: Specifies an identifier to easily identify the view among subviews. + This is reserved for implementation of top-level views, and should + not be used by widgets for their internal subviews. + +It also always has the following fields: + +:subviews: Contains a table of all subviews. The sequence part of the + table is used for iteration. In addition, subviews are also + indexed under their *view_id*, if any; see ``addviews()`` below. + +These fields are computed by the layout process: + +:frame_parent_rect: The ViewRect represeting the client area of the parent view. +:frame_rect: The ``mkdims`` rect of the outer frame in parent-local coordinates. +:frame_body: The ViewRect representing the body part of the View's own frame. + +The class has the following methods: + +* ``view:addviews(list)`` + + Adds the views in the list to the ``subviews`` sequence. If any of the views + in the list have ``view_id`` attributes that don't conflict with existing keys + in ``subviews``, also stores them under the string keys. Finally, copies any + non-conflicting string keys from the ``subviews`` tables of the listed views. + + Thus, doing something like this:: + + self:addviews{ + Panel{ + view_id = 'panel', + subviews = { + Label{ view_id = 'label' } + } + } + } + + Would make the label accessible as both ``self.subviews.label`` and + ``self.subviews.panel.subviews.label``. + +* ``view:getWindowSize()`` + + Returns the dimensions of the ``frame_body`` rectangle. + +* ``view:getMousePos()`` + + Returns the mouse *x,y* in coordinates local to the ``frame_body`` + rectangle if it is within its clip area, or nothing otherwise. + +* ``view:updateLayout([parent_rect])`` + + Recomputes layout of the view and its subviews. If no argument is + given, re-uses the previous parent rect. The process goes as follows: + + 1. Calls ``preUpdateLayout(parent_rect)`` via ``invoke_before``. + 2. Uses ``computeFrame(parent_rect)`` to compute the desired frame. + 3. Calls ``postComputeFrame(frame_body)`` via ``invoke_after``. + 4. Calls ``updateSubviewLayout(frame_body)`` to update children. + 5. Calls ``postUpdateLayout(frame_body)`` via ``invoke_after``. + +* ``view:computeFrame(parent_rect)`` *(for overriding)* + + Called by ``updateLayout`` in order to compute the frame rectangle(s). + Should return the ``mkdims`` rectangle for the outer frame, and optionally + also for the body frame. If only one rectangle is returned, it is used + for both frames, and the margin becomes zero. + +* ``view:updateSubviewLayout(frame_body)`` + + Calls ``updateLayout`` on all children. + +* ``view:render(painter)`` + + Given the parent's painter, renders the view via the following process: + + 1. Calls ``onRenderFrame(painter, frame_rect)`` to paint the outer frame. + 2. Creates a new painter using the ``frame_body`` rect. + 3. Calls ``onRenderBody(new_painter)`` to paint the client area. + 4. Calls ``renderSubviews(new_painter)`` to paint visible children. + +* ``view:renderSubviews(painter)`` + + Calls ``render`` on all ``visible`` subviews in the order they + appear in the ``subviews`` sequence. + +* ``view:onRenderFrame(painter, rect)`` *(for overriding)* + + Called by ``render`` to paint the outer frame; by default does nothing. + +* ``view:onRenderBody(painter)`` *(for overriding)* + + Called by ``render`` to paint the client area; by default does nothing. + +* ``view:onInput(keys)`` *(for overriding)* + + Override this to handle events. By default directly calls ``inputToSubviews``. + Return a true value from this method to signal that the event has been handled + and should not be passed on to more views. + +* ``view:inputToSubviews(keys)`` + + Calls ``onInput`` on all visible active subviews, iterating the ``subviews`` + sequence in *reverse order*, so that topmost subviews get events first. + Returns *true* if any of the subviews handled the event. + + +Screen class +------------ + +This is a View subclass intended for use as a stand-alone dialog or screen. +It adds the following methods: + +* ``screen:isShown()`` + + Returns *true* if the screen is currently in the game engine's display stack. + +* ``screen:isDismissed()`` + + Returns *true* if the screen is dismissed. + +* ``screen:isActive()`` + + Returns *true* if the screen is shown and not dismissed. + +* ``screen:invalidate()`` + + Requests a repaint. Note that currently using it is not necessary, because + repaints are constantly requested automatically, due to issues with native + screens happening otherwise. + +* ``screen:renderParent()`` + + Asks the parent native screen to render itself, or clears the screen if impossible. + +* ``screen:sendInputToParent(...)`` + + Uses ``simulateInput`` to send keypresses to the native parent screen. + +* ``screen:show([parent])`` + + Adds the screen to the display stack with the given screen as the parent; + if parent is not specified, places this one one topmost. Before calling + ``dfhack.screen.show``, calls ``self:onAboutToShow(parent)``. + +* ``screen:onAboutToShow(parent)`` *(for overriding)* + + Called when ``dfhack.screen.show`` is about to be called. + +* ``screen:onShow()`` + + Called by ``dfhack.screen.show`` once the screen is successfully shown. + +* ``screen:dismiss()`` + + Dismisses the screen. A dismissed screen does not receive any more + events or paint requests, but may remain in the display stack for + a short time until the game removes it. + +* ``screen:onDismiss()`` *(for overriding)* + + Called by ``dfhack.screen.dismiss()``. + +* ``screen:onDestroy()`` *(for overriding)* + + Called by the native code when the screen is fully destroyed and removed + from the display stack. Place code that absolutely must be called whenever + the screen is removed by any means here. + +* ``screen:onResize``, ``screen:onRender`` + + Defined as callbacks for native code. + + +FramedScreen class +------------------ + +A Screen subclass that paints a visible frame around its body. +Most dialogs should inherit from this class. + +A framed screen has the following attributes: + +:frame_style: A table that defines a set of pens to draw various parts of the frame. +:frame_title: A string to display in the middle of the top of the frame. +:frame_width: Desired width of the client area. If *nil*, the screen will occupy the whole width. +:frame_height: Likewise, for height. +:frame_inset: The gap between the frame and the client area. Defaults to 0. +:frame_background: The pen to fill in the frame with. Defaults to CLEAR_PEN. + +There are the following predefined frame style tables: + +* ``GREY_FRAME`` + + A plain grey-colored frame. + +* ``BOUNDARY_FRAME`` + + The same frame as used by the usual full-screen DF views, like dwarfmode. + +* ``GREY_LINE_FRAME`` + + A frame consisting of grey lines, similar to the one used by titan announcements. + + +gui.widgets +=========== + +This module implements some basic widgets based on the View infrastructure. + +Widget class +------------ + +Base of all the widgets. Inherits from View and has the following attributes: + +* ``frame = {...}`` + + Specifies the constraints on the outer frame of the widget. + If omitted, the widget will occupy the whole parent rectangle. + + The frame is specified as a table with the following possible fields: + + :l: gap between the left edges of the frame and the parent. + :t: gap between the top edges of the frame and the parent. + :r: gap between the right edges of the frame and the parent. + :b: gap between the bottom edges of the frame and the parent. + :w: maximum width of the frame. + :h: maximum heigth of the frame. + :xalign: X alignment of the frame. + :yalign: Y alignment of the frame. + + First the ``l,t,r,b`` fields restrict the available area for + placing the frame. If ``w`` and ``h`` are not specified or + larger then the computed area, it becomes the frame. Otherwise + the smaller frame is placed within the are based on the + ``xalign/yalign`` fields. If the align hints are omitted, they + are assumed to be 0, 1, or 0.5 based on which of the ``l/r/t/b`` + fields are set. + +* ``frame_inset = {...}`` + + Specifies the gap between the outer frame, and the client area. + The attribute may be a simple integer value to specify a uniform + inset, or a table with the following fields: + + :l: left margin. + :t: top margin. + :r: right margin. + :b: bottom margin. + :x: left/right margin, if ``l`` and/or ``r`` are omitted. + :y: top/bottom margin, if ``t`` and/or ``b`` are omitted. + +* ``frame_background = pen`` + + The pen to fill the outer frame with. Defaults to no fill. + +Panel class +----------- + +Inherits from Widget, and intended for grouping a number of subviews. + +Has attributes: + +* ``subviews = {}`` + + Used to initialize the subview list in the constructor. + +* ``on_render = function(painter)`` + + Called from ``onRenderBody``. + +Pages class +----------- + +Subclass of Panel; keeps exactly one child visible. + +* ``Pages{ ..., selected = ... }`` + + Specifies which child to select initially; defaults to the first one. + +* ``pages:getSelected()`` + + Returns the selected *index, child*. + +* ``pages:setSelected(index)`` + + Selects the specified child, hiding the previous selected one. + It is permitted to use the subview object, or its ``view_id`` as index. + +EditField class +--------------- + +Subclass of Widget; implements a simple edit field. + +Attributes: + +:text: The current contents of the field. +:text_pen: The pen to draw the text with. +:on_char: Input validation callback; used as ``on_char(new_char,text)``. + If it returns false, the character is ignored. +:on_change: Change notification callback; used as ``on_change(new_text,old_text)``. +:on_submit: Enter key callback; if set the field will handle the key and call ``on_submit(text)``. + +Label class +----------- + +This Widget subclass implements flowing semi-static text. + +It has the following attributes: + +:text_pen: Specifies the pen for active text. +:text_dpen: Specifies the pen for disabled text. +:disabled: Boolean or a callback; if true, the label is disabled. +:enabled: Boolean or a callback; if false, the label is disabled. +:auto_height: Sets self.frame.h from the text height. +:auto_width: Sets self.frame.w from the text width. + +The text itself is represented as a complex structure, and passed +to the object via the ``text`` argument of the constructor, or via +the ``setText`` method, as one of: + +* A simple string, possibly containing newlines. +* A sequence of tokens. + +Every token in the sequence in turn may be either a string, possibly +containing newlines, or a table with the following possible fields: + +* ``token.text = ...`` + + Specifies the main text content of a token, and may be a string, or + a callback returning a string. + +* ``token.gap = ...`` + + Specifies the number of character positions to advance on the line + before rendering the token. + +* ``token.tile = pen`` + + Specifies a pen to paint as one tile before the main part of the token. + +* ``token.key = '...'`` + + Specifies the keycode associated with the token. The string description + of the key binding is added to the text content of the token. + +* ``token.key_sep = '...'`` + + Specifies the separator to place between the keybinding label produced + by ``token.key``, and the main text of the token. If the separator is + '()', the token is formatted as ``text..' ('..binding..')'``. Otherwise + it is simply ``binding..sep..text``. + +* ``token.enabled``, ``token.disabled`` + + Same as the attributes of the label itself, but applies only to the token. + +* ``token.pen``, ``token.dpen`` + + Specify the pen and disabled pen to be used for the token's text. + The field may be either the pen itself, or a callback that returns it. + +* ``token.on_activate`` + + If this field is not nil, and ``token.key`` is set, the token will actually + respond to that key binding unless disabled, and call this callback. Eventually + this may be extended with mouse click support. + +* ``token.id`` + + Specifies a unique identifier for the token. + +* ``token.line``, ``token.x1``, ``token.x2`` + + Reserved for internal use. + +The Label widget implements the following methods: + +* ``label:setText(new_text)`` + + Replaces the text currently contained in the widget. + +* ``label:itemById(id)`` + + Finds a token by its ``id`` field. + +* ``label:getTextHeight()`` + + Computes the height of the text. + +* ``label:getTextWidth()`` + + Computes the width of the text. + +List class +---------- + +The List widget implements a simple list with paging. + +It has the following attributes: + +:text_pen: Specifies the pen for deselected list entries. +:cursor_pen: Specifies the pen for the selected entry. +:inactive_pen: If specified, used for the cursor when the widget is not active. +:icon_pen: Default pen for icons. +:on_select: Selection change callback; called as ``on_select(index,choice)``. +:on_submit: Enter key callback; if specified, the list reacts to the key + and calls it as ``on_submit(index,choice)``. +:row_height: Height of every row in text lines. +:icon_width: If not *nil*, the specified number of character columns + are reserved to the left of the list item for the icons. +:scroll_keys: Specifies which keys the list should react to as a table. + +Every list item may be specified either as a string, or as a lua table +with the following fields: + +:text: Specifies the label text in the same format as the Label text. +:caption, [1]: Deprecated legacy aliases for **text**. +:text_*: Reserved for internal use. +:key: Specifies a keybinding that acts as a shortcut for the specified item. +:icon: Specifies an icon string, or a pen to paint a single character. May be a callback. +:icon_pen: When the icon is a string, used to paint it. + +The list supports the following methods: + +* ``List{ ..., choices = ..., selected = ... }`` + + Same as calling ``setChoices`` after construction. + +* ``list:setChoices(choices[, selected])`` + + Replaces the list of choices, possibly also setting the currently selected index. + +* ``list:setSelected(selected)`` + + Sets the currently selected index. Returns the index after validation. + +* ``list:getChoices()`` + + Returns the list of choices. + +* ``list:getSelected()`` + + Returns the selected *index, choice*, or nothing if the list is empty. + +* ``list:getContentWidth()`` + + Returns the minimal width to draw all choices without clipping. + +* ``list:getContentHeight()`` + + Returns the minimal width to draw all choices without scrolling. + +* ``list:submit()`` + + Call the ``on_submit`` callback, as if the Enter key was handled. + +FilteredList class +------------------ + +This widget combines List, EditField and Label into a combo-box like +construction that allows filtering the list by subwords of its items. + +In addition to passing through all attributes supported by List, it +supports: + +:edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. +:not_found_label: Specifies the text of the label shown when no items match the filter. + +The list choices may include the following attributes: + +:search_key: If specified, used instead of **text** to match against the filter. + +The widget implements: + +* ``list:setChoices(choices[, selected])`` + + Resets the filter, and passes through to the inner list. + +* ``list:getChoices()`` + + Returns the list of *all* choices. + +* ``list:getFilter()`` + + Returns the current filter string, and the *filtered* list of choices. + +* ``list:setFilter(filter[,pos])`` + + Sets the new filter string, filters the list, and selects the item at + index ``pos`` in the *unfiltered* list if possible. + +* ``list:canSubmit()`` + + Checks if there are currently any choices in the filtered list. + +* ``list:getSelected()``, ``list:getContentWidth()``, ``list:getContentHeight()``, ``list:submit()`` + + Same as with an ordinary list. + ======= Plugins diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index e731af068..ad408a2ea 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -504,7 +504,7 @@ function List:onRenderBody(dc) local icon = getval(obj.icon) if icon then dc:seek(0, y) - if type(icon) == 'table' then + if type(icon) ~= 'string' then dc:char(nil,icon) else if current then @@ -573,7 +573,7 @@ FilteredList = defclass(FilteredList, Widget) function FilteredList:init(info) self.edit = EditField{ - text_pen = info.cursor_pen, + text_pen = info.edit_pen or info.cursor_pen, frame = { l = info.icon_width, t = 0 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), @@ -583,6 +583,7 @@ function FilteredList:init(info) text_pen = info.text_pen, cursor_pen = info.cursor_pen, inactive_pen = info.inactive_pen, + icon_pen = info.icon_pen, row_height = info.row_height, scroll_keys = info.scroll_keys, icon_width = info.icon_width, diff --git a/library/lua/utils.lua b/library/lua/utils.lua index e7267038c..6d6b4de5c 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -457,6 +457,7 @@ function getBuildingCenter(building) return xyz2pos(building.centerx, building.centery, building.z) end +-- Split the string by the given delimiter function split_string(self, delimiter) local result = { } local from = 1 From edf80ff748ad8193015a98936e5ac86263bf9ac7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 18:03:02 +0400 Subject: [PATCH 143/472] Show the sex of the unit in the bottom line of Dwarf Manipulator. --- plugins/manipulator.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 1bc0195b6..79999d468 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1067,18 +1067,22 @@ void viewscreen_unitlaborsst::render() if (cur != NULL) { df::unit *unit = cur->unit; - int x = 1; - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->transname); + int x = 1, y = 3 + num_rows + 2; + Screen::Pen white_pen(' ', 15, 0); + + Screen::paintString(white_pen, x, y, (cur->unit && cur->unit->sex) ? "\x0b" : "\x0c"); + x += 2; + Screen::paintString(white_pen, x, y, cur->transname); x += cur->transname.length(); if (cur->transname.length()) { - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ", "); + Screen::paintString(white_pen, x, y, ", "); x += 2; } - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->profession); + Screen::paintString(white_pen, x, y, cur->profession); x += cur->profession.length(); - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ": "); + Screen::paintString(white_pen, x, y, ": "); x += 2; string str; From 20e98d49263c2e1d719a57fc3c138dfafb32fd8f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 4 Nov 2012 20:51:13 +0400 Subject: [PATCH 144/472] Add a script for viewing and poking at local populations. --- scripts/region-pops.lua | 141 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 scripts/region-pops.lua diff --git a/scripts/region-pops.lua b/scripts/region-pops.lua new file mode 100644 index 000000000..779167134 --- /dev/null +++ b/scripts/region-pops.lua @@ -0,0 +1,141 @@ +-- Shows populations of animals in the region, and allows tweaking them. + +local utils = require 'utils' + +local function sort_keys(tab) + local kt = {} + for k,v in pairs(tab) do table.insert(kt,k) end + table.sort(kt) + return ipairs(kt) +end + +local is_plant_map = { + Animal = false, Vermin = false, VerminInnumerable = false, + ColonyInsect = false, Tree = true, Grass = true, Bush = true +} + +function enum_populations() + local stat_table = { + plants = {}, + creatures = {}, + any = {} + } + + for i,v in ipairs(df.global.world.populations) do + local typeid = df.world_population_type[v.type] + local is_plant = is_plant_map[typeid] + local id, obj, otable, idtoken + + if is_plant then + id = v.plant + obj = df.plant_raw.find(id) + otable = stat_table.plants + idtoken = obj.id + else + id = v.race + obj = df.creature_raw.find(id) + otable = stat_table.creatures + idtoken = obj.creature_id + end + + local entry = otable[idtoken] + if not entry then + entry = { + obj = obj, token = idtoken, id = id, records = {}, + count = 0, known_count = 0, + known = false, infinite = false + } + otable[idtoken] = entry + stat_table.any[idtoken] = entry + end + + table.insert(entry.records, v) + entry.known = entry.known or v.known + + if v.quantity < 10000001 then + entry.count = entry.count + v.quantity + if v.known then + entry.known_count = entry.known_count + v.quantity + end + else + entry.infinite = true + end + end + + return stat_table +end + +function list_poptable(entries, all, pattern) + for _,k in sort_keys(entries) do + local entry = entries[k] + if (all or entry.known) and (not pattern or string.match(k,pattern)) then + local count = entry.known_count + if all then + count = entry.count + end + if entry.infinite then + count = 'innumerable' + end + print(string.format('%-40s %s', entry.token, count)) + end + end +end + +function list_populations(stat_table, all, pattern) + print('Plants:') + list_poptable(stat_table.plants, true, pattern) + print('\nCreatures and vermin:') + list_poptable(stat_table.creatures, all, pattern) +end + + +function boost_population(entry, factor, boost_count) + for _,v in ipairs(entry.records) do + if v.quantity < 10000001 then + boost_count = boost_count + 1 + v.quantity = math.floor(v.quantity * factor) + end + end + return boost_count +end + +local args = {...} +local pops = enum_populations() + +if args[1] == 'list' or args[1] == 'list-all' then + list_populations(pops, args[1] == 'list-all', args[2]) +elseif args[1] == 'boost' or args[1] == 'boost-all' then + local factor = tonumber(args[3]) + if not factor or factor < 0 then + qerror('Invalid boost factor.') + end + + local count = 0 + + if args[1] == 'boost' then + local entry = pops.any[args[2]] or qerror('Unknown population token.') + count = boost_population(entry, factor, count) + else + for k,entry in pairs(pops.any) do + if string.match(k, args[2]) then + count = boost_population(entry, factor, count) + end + end + end + + print('Updated '..count..' populations.') +else + print([[ +Usage: + region-pops list [pattern] + Lists encountered populations of the region, possibly restricted by pattern. + region-pops list-all [pattern] + Lists all populations of the region. + region-pops boost + Multiply all populations of TOKEN by factor. + If the factor is greater than one, increases the + population, otherwise decreases it. + region-pops boost-all + Same as above, but match using a pattern acceptable to list. +]]) +end From ff982dcf738ba766270663d8fc751c55c1fe8699 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 7 Nov 2012 13:31:36 +0400 Subject: [PATCH 145/472] Fix representation and parsing of built-in materials. The trick is to support both FOO and FOO:NONE for all of them, including INORGANIC[:NONE]. Otherwise the workflow gui scripts have problems. --- NEWS | 2 ++ dfhack.init-example | 2 +- library/modules/Materials.cpp | 6 ++++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index d5080b05a..f2237cab9 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,8 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + New scripts: + - region-pops: displays animal populations of the region and allows tweaking them. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index d7f3f5399..20048e39e 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -18,7 +18,7 @@ keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave # gui/rename script keybinding add Ctrl-Shift-N gui/rename -keybinding add Alt-Shift-P "gui/rename unit-profession" +keybinding add Ctrl-Shift-T "gui/rename unit-profession" ############################## # Generic adv mode bindings # diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 454fdf66d..7c06aeb4c 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -190,6 +190,8 @@ bool MaterialInfo::find(const std::vector &items) } else if (items.size() == 2) { + if (items[1] == "NONE" && findBuiltin(items[0])) + return true; if (findPlant(items[0], items[1])) return true; if (findCreature(items[0], items[1])) @@ -210,7 +212,7 @@ bool MaterialInfo::findBuiltin(const std::string &token) } df::world_raws &raws = world->raws; - for (int i = 1; i < NUM_BUILTIN; i++) + for (int i = 0; i < NUM_BUILTIN; i++) { auto obj = raws.mat_table.builtin[i]; if (obj && obj->id == token) @@ -312,7 +314,7 @@ std::string MaterialInfo::getToken() else if (index == 1) return "COAL:CHARCOAL"; } - return material->id + ":NONE"; + return material->id; case Inorganic: return "INORGANIC:" + inorganic->id; case Creature: From f6b6d730a08802deb9733e0119b5b8b24097bd0f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 7 Nov 2012 22:49:40 +0400 Subject: [PATCH 146/472] Fix fix-armory constantly trying to store ammo already stored in chest. --- plugins/fix-armory.cpp | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index ade9e4252..b937d40e8 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -160,7 +160,7 @@ static bool is_assigned_item(df::item *item) } // Check if this ammo item is assigned to this squad with one of the specified uses -static bool is_squad_ammo(df::item *item, df::squad *squad, bool train, bool combat) +static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool train) { for (size_t i = 0; i < squad->ammunition.size(); i++) { @@ -186,8 +186,6 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i if (squads) { - bool target = holder->getType() == building_type::ArcheryTarget; - for (size_t i = 0; i < squads->size(); i++) { auto use = (*squads)[i]; @@ -198,8 +196,7 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i // Squad Equipment -> combat bool combat = use->mode.bits.squad_eq; - // Archery target with Train -> training - bool train = target && use->mode.bits.train; + bool train = false; if (combat || train) { @@ -210,6 +207,41 @@ static bool can_store_ammo_rec(df::item *item, df::building *holder, int squad_i } } } + // Ugh, archery targets don't actually have a squad use vector + else if (holder->getType() == building_type::ArcheryTarget) + { + auto &squads = df::global::world->squads.all; + + for (size_t si = 0; si < squads.size(); si++) + { + auto squad = squads[si]; + + // For containers assigned to a squad, only consider that squad + if (squad_id >= 0 && squad->id != squad_id) + continue; + + for (size_t j = 0; j < squad->rooms.size(); j++) + { + auto use = squad->rooms[j]; + + if (use->building_id != holder->id) + continue; + + // Squad Equipment -> combat + bool combat = use->mode.bits.squad_eq; + // Archery target with Train -> training + bool train = use->mode.bits.train; + + if (combat || train) + { + if (is_squad_ammo(item, squad, combat, train)) + return true; + } + + break; + } + } + } for (size_t i = 0; i < holder->parents.size(); i++) if (can_store_ammo_rec(item, holder->parents[i], squad_id)) From 0c70a448d0f88823dd6ac236e8d123fa79da966f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Wed, 7 Nov 2012 23:06:02 +0100 Subject: [PATCH 147/472] Update submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index fcacacce7..4ab899319 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit fcacacce7cf09cf70f011fea87b5be416da73457 +Subproject commit 4ab899319014d950214714a48cd3049a4beb5eb5 diff --git a/plugins/stonesense b/plugins/stonesense index 75df76626..cb97cf308 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 75df766263b23182820a1e07b330e64f87d5c9b7 +Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd From a7bf526f41216f6e43c942c21907dbd46fe8f44f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 8 Nov 2012 21:27:56 +0400 Subject: [PATCH 148/472] Make workflow consider squad-assigned items busy. --- NEWS | 1 + plugins/workflow.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index f2237cab9..32181f16a 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ DFHack future - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. + - items assigned to squads are considered busy, even if not in inventory. New Fix Armory plugin: Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index d46daed44..5347d4671 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1004,6 +1004,12 @@ static bool isRouteVehicle(df::item *item) return vehicle && vehicle->route_id >= 0; } +static bool isAssignedSquad(df::item *item) +{ + auto &vec = ui->equipment.items_assigned[item->getType()]; + return binsearch_index(vec, &df::item::id, item->id) >= 0; +} + static void map_job_items(color_ostream &out) { for (size_t i = 0; i < constraints.size(); i++) @@ -1117,8 +1123,10 @@ static void map_job_items(color_ostream &out) item->isAssignedToStockpile() || isRouteVehicle(item) || itemInRealJob(item) || - itemBusy(item)) + itemBusy(item) || + isAssignedSquad(item)) { + is_invalid = true; cv->item_inuse++; } else From eb936c4ce07bb16684de0157a2ab5bbdf74fc4ca Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 17:06:54 +0400 Subject: [PATCH 149/472] Support milking and shearing in workflow. --- NEWS | 1 + library/modules/Job.cpp | 2 +- library/modules/Materials.cpp | 2 ++ library/xml | 2 +- plugins/lua/workflow.lua | 23 +++++++++++++++++------ plugins/workflow.cpp | 4 +++- scripts/gui/workflow.lua | 2 +- 7 files changed, 26 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index 32181f16a..51321be95 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,7 @@ DFHack future - code for deducing job outputs rewritten in lua for flexibility. - logic fix: collecting webs produces silk, and ungathered webs are not thread. - items assigned to squads are considered busy, even if not in inventory. + - shearing and milking jobs are supported, but only with generic MILK or YARN outputs. New Fix Armory plugin: Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index def3b4192..757000885 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -75,7 +75,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->references[i]; - if (virtual_cast(ref)) + if (virtual_cast(ref)) vector_erase_at(pnew->references, i); else pnew->references[i] = ref->clone(); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 7c06aeb4c..4da484ade 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -425,6 +425,8 @@ bool MaterialInfo::matches(const df::dfhack_material_category &cat) TEST(glass, IS_GLASS); if (cat.bits.clay && linear_index(material->reaction_product.id, std::string("FIRED_MAT")) >= 0) return true; + if (cat.bits.milk && linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0) + return true; return false; } diff --git a/library/xml b/library/xml index 4ab899319..02e0e0d7b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4ab899319014d950214714a48cd3049a4beb5eb5 +Subproject commit 02e0e0d7b9a7ef708a621ef5511a24bf8657b4a2 diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 4c011b24c..c3dbe20d9 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -301,15 +301,26 @@ function listWeakenedConstraints(outputs) local mask = cons.mat_mask if (cons.mat_type or -1) >= 0 then cons.mat_mask = nil + local info = dfhack.matinfo.decode(cons) + if info then + for i,flag in ipairs(df.dfhack_material_category) do + if flag and flag ~= 'wood2' and info:matches{[flag]=true} then + mask = mask or {} + mask[flag] = true + end + end + end end register(cons) if mask then - table.insert(generic, { - item_type = cons.item_type, - item_subtype = cons.item_subtype, - is_craft = cons.is_craft, - mat_mask = mask - }) + for k,v in pairs(mask) do + table.insert(generic, { + item_type = cons.item_type, + item_subtype = cons.item_subtype, + is_craft = cons.is_craft, + mat_mask = { [k] = v } + }) + end end table.insert(anymat, { item_type = cons.item_type, diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 5347d4671..c89d87333 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -377,7 +377,9 @@ static bool isSupportedJob(df::job *job) Job::getHolder(job) && (!job->job_items.empty() || job->job_type == job_type::CollectClay || - job->job_type == job_type::CollectSand); + job->job_type == job_type::CollectSand || + job->job_type == job_type::MilkCreature || + job->job_type == job_type::ShearCreature); } static bool isOptionEnabled(unsigned flag) diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 8dc958062..366e3ec91 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -306,7 +306,7 @@ function JobConstraints:onNewConstraint() end dlg.showListPrompt( - 'Job Outputs', + 'New limit', 'Select one of the possible outputs:', COLOR_WHITE, choices, From 56ef33ea0e5c6236239b4af43f00cb182c181987 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 17:33:05 +0400 Subject: [PATCH 150/472] Support building steam engines on top of brooks without any down stairs. --- library/include/TileTypes.h | 6 ++++++ library/xml | 2 +- plugins/steam-engine.cpp | 8 +++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index d21fb3c17..48c10146a 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -203,6 +203,12 @@ namespace DFHack return ENUM_ATTR(tiletype_shape, passable_flow, tileShape(tiletype)); } + inline + bool FlowPassableDown(df::tiletype tiletype) + { + return ENUM_ATTR(tiletype_shape, passable_flow_down, tileShape(tiletype)); + } + inline bool isWalkable(df::tiletype tiletype) { diff --git a/library/xml b/library/xml index 02e0e0d7b..4b2124957 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 02e0e0d7b9a7ef708a621ef5511a24bf8657b4a2 +Subproject commit 4b2124957e282683480eaf05922e63c353364ec1 diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index d884191e5..60f38ef83 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -320,7 +320,7 @@ struct workshop_hook : df::building_workshopst { for (int y = y1; y <= y2; y++) { auto ptile = Maps::getTileType(x,y,z); - if (!ptile || !LowPassable(*ptile)) + if (!ptile || !FlowPassableDown(*ptile)) continue; auto pltile = Maps::getTileType(x,y,z-1); @@ -891,7 +891,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_hook, feed); * Scan raws for matching workshop buildings. */ -static bool find_engines() +static bool find_engines(color_ostream &out) { engines.clear(); @@ -943,6 +943,8 @@ static bool find_engines() if (!ws.gear_tiles.empty()) engines.push_back(ws); + else + out.printerr("%s has no gear tiles - ignoring.\n", wslist[i]->code.c_str()); } return !engines.empty(); @@ -973,7 +975,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan { switch (event) { case SC_WORLD_LOADED: - if (find_engines()) + if (find_engines(out)) { out.print("Detected steam engine workshops - enabling plugin.\n"); enable_hooks(true); From f86371cfc3114dc08963cb3d0a521ca3660ac5ad Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 10 Nov 2012 18:06:41 +0400 Subject: [PATCH 151/472] Try blocking any use of stockpiles for squad stuff in fix-armory. --- plugins/fix-armory.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index b937d40e8..99e5fd500 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -132,6 +132,9 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) * grace period during which the items can be instantly picked up again. */ +// Completely block the use of stockpiles +#define NO_STOCKPILES + // Check if the item is assigned to any use controlled by the military tab static bool is_assigned_item(df::item *item) { @@ -143,19 +146,6 @@ static bool is_assigned_item(df::item *item) if (idx < 0) return false; - // Exclude weapons used by miners, wood cutters etc - switch (type) { - case item_type::WEAPON: - // the game code also checks this for ammo, funnily enough - // maybe it's not just for weapons?.. - if (binsearch_index(ui->equipment.work_weapons, item->id) >= 0) - return false; - break; - - default: - break; - } - return true; } @@ -328,6 +318,16 @@ template struct armory_hook : Item { */ DEFINE_VMETHOD_INTERPOSE(bool, isCollected, ()) { +#ifdef NO_STOCKPILES + /* + * Completely block any items assigned to a squad from being stored + * in stockpiles. The reason is that I still observe haulers running + * around with bins to pick them up for some reason. There could be + * some unaccounted race conditions involved. + */ + if (is_assigned_item(this)) + return false; +#else // Block stockpiling of items in the armory. if (is_in_armory(this)) return false; @@ -354,6 +354,7 @@ template struct armory_hook : Item { return false; } } +#endif // Call the original vmethod return INTERPOSE_NEXT(isCollected)(); From f1d4eac70016ced41cb5e383e65763187a84b191 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Nov 2012 11:58:43 +0200 Subject: [PATCH 152/472] Pre-class remove --- .../luafiles/friendship/friendship.asm | 10 +- .../Dfusion/luafiles/friendship/friendship.o | Bin 722 -> 854 bytes plugins/lua/dfusion/embark.lua | 2 + plugins/lua/dfusion/friendship.lua | 111 ++++++++++++++++++ plugins/lua/dfusion/friendship.o | Bin 0 -> 854 bytes plugins/lua/dfusion/srcs/compile.bat | 1 + plugins/lua/dfusion/srcs/embark.asm | 7 ++ plugins/lua/dfusion/srcs/friendship.asm | 106 +++++++++++++++++ 8 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 plugins/lua/dfusion/friendship.lua create mode 100644 plugins/lua/dfusion/friendship.o create mode 100644 plugins/lua/dfusion/srcs/compile.bat create mode 100644 plugins/lua/dfusion/srcs/embark.asm create mode 100644 plugins/lua/dfusion/srcs/friendship.asm diff --git a/plugins/Dfusion/luafiles/friendship/friendship.asm b/plugins/Dfusion/luafiles/friendship/friendship.asm index e56fe7f49..b649e38de 100644 --- a/plugins/Dfusion/luafiles/friendship/friendship.asm +++ b/plugins/Dfusion/luafiles/friendship/friendship.asm @@ -66,8 +66,10 @@ mov eax, [edi+0x8c] #jmp compare compare: push ecx +mark_racepointer: mov ebx,0xDEADBEEF #write a pointer to the list of allowed races -mov ecx,2000 #write a number of allowed races +mark_racecount: +mov ecx,0xDEADBEEF #write a number of allowed races loop1: cmp word[ebx+ecx*2],ax jz endok @@ -92,11 +94,13 @@ endfinal: pop ebx pop eax -mov [0xFEEDBEEF],eax #write a pointer to safe location (usually after this) +mark_safeloc1: +mov [0xDEADBEEF],eax #write a pointer to safe location (usually after this) pop eax pushfd inc eax #skip one instruction popfd push eax -mov eax,[0xFEEDBEEF] #write a pointer to safe location (same as above) +mark_safeloc2: +mov eax,[0xDEADBEEF] #write a pointer to safe location (same as above) ret diff --git a/plugins/Dfusion/luafiles/friendship/friendship.o b/plugins/Dfusion/luafiles/friendship/friendship.o index f956de3e0a6fce74f63feb2c41a1d07b7a441ec4..c801562dbc1bc9ab11c74929ef422aff7629dcb5 100644 GIT binary patch delta 177 zcmcb_dX0_Ohmn~91VR`Y7$i6H3Ny02-?#SOWEDnbp0)QP<~Ym^SO^vfX1pm4QpWMtiq_w^Y&lF9EZ693*Ya1`)_hEMAX_RACES then + error("race count must be less then "..MAX_RACES) + end + local rarr=self:allocate("race_array",'uint16_t',MAX_RACES) + local _,rarr_offset=df.sizeof(rarr) + self:set_marker_dword("racepointer",rarr_offset) + self:set_races(rarr) + self:set_marker_dword("racecount",#self.race_data) + local safe_loc=self:allocate("safe_loc",'uint32_t',1) + local _1,safe_loc_offset=df.sizeof(safe_loc) + self:set_marker_dword("safeloc1",safe_loc_offset) + self:set_marker_dword("safeloc2",safe_loc_offset) + local addr=self:move_to_df() + self:patchCalls(addr) + self.installed=true + end +return _ENV \ No newline at end of file diff --git a/plugins/lua/dfusion/friendship.o b/plugins/lua/dfusion/friendship.o new file mode 100644 index 0000000000000000000000000000000000000000..c801562dbc1bc9ab11c74929ef422aff7629dcb5 GIT binary patch literal 854 zcmeZaWM%+?5JmI3a0tG+rTticRPp<-2Hyv+Iu@eM4F|GH&Y3hXYaMV#5$~(mYI_Z_29q%3=HXvX^Gh|0jPLZZb3;>4g&)x zkPi%c1~!JG)O2T%WIVEn3rHj%S;Pq>l8z!00OXY;i?{}RGBDJmh`0bn+L1*ZfgFZl zxJX)JCeSqu4C~<{$@#ejiAAXly>JnbJPQ!_p@@hAd6VHHIr;eohCtp7xJYVVN`5ww zw-Q+-Ei*4MXB%7u$Y4+b;vFa=MnK*nxCnBp1cewgBPd;>0^}6Q1e9V1DMSOvsS%X+ uv8izYisvR4WycpKCZ`tUXXcfp79j+Z^GoweAl%}_wA7sZWJ4655d#3LE~Ebd literal 0 HcmV?d00001 diff --git a/plugins/lua/dfusion/srcs/compile.bat b/plugins/lua/dfusion/srcs/compile.bat new file mode 100644 index 000000000..e084949f4 --- /dev/null +++ b/plugins/lua/dfusion/srcs/compile.bat @@ -0,0 +1 @@ +as -anl --32 -o friendship.o friendship.asm \ No newline at end of file diff --git a/plugins/lua/dfusion/srcs/embark.asm b/plugins/lua/dfusion/srcs/embark.asm new file mode 100644 index 000000000..d2fa91081 --- /dev/null +++ b/plugins/lua/dfusion/srcs/embark.asm @@ -0,0 +1,7 @@ +.intel_syntax +mov eax , [esp+0x1C] # loop counter +mark_caste: +movsx ecx, word ptr[eax*2+0xdeadbeef] +mark_race: +movzx eax,word ptr [eax*2+0xDEADBEEF] +ret diff --git a/plugins/lua/dfusion/srcs/friendship.asm b/plugins/lua/dfusion/srcs/friendship.asm new file mode 100644 index 000000000..b649e38de --- /dev/null +++ b/plugins/lua/dfusion/srcs/friendship.asm @@ -0,0 +1,106 @@ +.intel_syntax +push eax +mov eax,[esp+0x04] +push ebx +pushfd +mov eax,[eax] # get a byte after the call this procedure to analyze what register holds cr ptr +jmptbl: +cmp al,0x81 +jz regC +cmp al,0x82 +jz regD +cmp al,0x83 +jz regB +cmp al,0x85 +jz regBP +cmp al,0x86 +jz regESI +cmp al,0x87 +jz regEDI +cmp al,0x88 +jz regA +cmp al,0x8A +jz regD +cmp al,0x8B +jz regB +cmp al,0x8D +jz regBP +cmp al,0x8E +jz regESI +cmp al,0x8F +jz regEDI +cmp al,0x90 +jz regA +cmp al,0x91 +jz regC +cmp al,0x93 +jz regB +cmp al,0x95 +jz regBP +cmp al,0x96 +jz regESI +cmp al,0x97 +jz regEDI +jmp fail +regA: +mov eax, [esp+0x8] +mov eax, [eax+0x8c] +jmp compare +regC: +mov eax, [ecx+0x8c] +jmp compare +regB: +mov eax, [ebx+0x8c] +jmp compare +regD: +mov eax, [edx+0x8c] +jmp compare +regBP: +mov eax, [ebp+0x8c] +jmp compare +regESI: +mov eax, [esi+0x8c] +jmp compare +regEDI: +mov eax, [edi+0x8c] +#jmp compare +compare: +push ecx +mark_racepointer: +mov ebx,0xDEADBEEF #write a pointer to the list of allowed races +mark_racecount: +mov ecx,0xDEADBEEF #write a number of allowed races +loop1: +cmp word[ebx+ecx*2],ax +jz endok +dec ecx +cmp ecx ,-1 +jnz loop1 +pop ecx +popfd +jmp fail +endok: +pop ecx +popfd +cmp eax,eax +jmp endfinal +fail: + +xor ebx,ebx +xor eax,eax +inc eax +cmp eax,ebx +endfinal: + +pop ebx +pop eax +mark_safeloc1: +mov [0xDEADBEEF],eax #write a pointer to safe location (usually after this) +pop eax +pushfd +inc eax #skip one instruction +popfd +push eax +mark_safeloc2: +mov eax,[0xDEADBEEF] #write a pointer to safe location (same as above) +ret From 33f674eee2ce40b5528b5ef6b4f4681867299829 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Nov 2012 12:33:54 +0200 Subject: [PATCH 153/472] Removed dfusion lua files. Updated plugins. --- plugins/Dfusion/CMakeLists.txt | 3 - plugins/Dfusion/luafiles/adv_tools/plugin.lua | 3 - plugins/Dfusion/luafiles/common.lua | 514 ------------------ plugins/Dfusion/luafiles/embark/build.bat | 1 - plugins/Dfusion/luafiles/embark/embark.asm | 7 - plugins/Dfusion/luafiles/embark/embark.o | Bin 369 -> 0 bytes plugins/Dfusion/luafiles/embark/init.lua | 95 ---- plugins/Dfusion/luafiles/embark/plugin.lua | 5 - plugins/Dfusion/luafiles/embark/races.txt | 9 - .../Dfusion/luafiles/friendship/compile.bat | 1 - .../luafiles/friendship/friendship.asm | 106 ---- .../Dfusion/luafiles/friendship/friendship.o | Bin 854 -> 0 bytes plugins/Dfusion/luafiles/friendship/init.lua | 45 -- .../Dfusion/luafiles/friendship/install.lua | 35 -- plugins/Dfusion/luafiles/friendship/patch.lua | 57 -- .../Dfusion/luafiles/friendship/plugin.lua | 18 - plugins/Dfusion/luafiles/friendship/races.txt | 8 - .../luafiles/friendship_civ/compile.bat | 1 - .../luafiles/friendship_civ/friendship_c.asm | 41 -- .../luafiles/friendship_civ/friendship_c.o | Bin 462 -> 0 bytes .../Dfusion/luafiles/friendship_civ/init.lua | 89 --- .../luafiles/friendship_civ/plugin.lua | 57 -- plugins/Dfusion/luafiles/init.lua | 92 ---- plugins/Dfusion/luafiles/migrants/compile.bat | 1 - plugins/Dfusion/luafiles/migrants/init.lua | 62 --- .../Dfusion/luafiles/migrants/migrants.asm | 20 - plugins/Dfusion/luafiles/migrants/migrants.o | Bin 336 -> 0 bytes plugins/Dfusion/luafiles/migrants/plugin.lua | 5 - plugins/Dfusion/luafiles/migrants/races.txt | 29 - plugins/Dfusion/luafiles/offsets/plugin.lua | 2 - plugins/Dfusion/luafiles/offsets_misc.lua | 48 -- .../Dfusion/luafiles/simple_embark/plugin.lua | 23 - plugins/Dfusion/luafiles/tools/init.lua | 511 ----------------- plugins/Dfusion/luafiles/tools/plugin.lua | 8 - plugins/Dfusion/luafiles/utils.lua | 1 - plugins/lua/dfusion/embark.lua | 92 ++-- plugins/lua/dfusion/friendship.lua | 5 +- 37 files changed, 36 insertions(+), 1958 deletions(-) delete mode 100644 plugins/Dfusion/luafiles/adv_tools/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/common.lua delete mode 100644 plugins/Dfusion/luafiles/embark/build.bat delete mode 100644 plugins/Dfusion/luafiles/embark/embark.asm delete mode 100644 plugins/Dfusion/luafiles/embark/embark.o delete mode 100644 plugins/Dfusion/luafiles/embark/init.lua delete mode 100644 plugins/Dfusion/luafiles/embark/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/embark/races.txt delete mode 100644 plugins/Dfusion/luafiles/friendship/compile.bat delete mode 100644 plugins/Dfusion/luafiles/friendship/friendship.asm delete mode 100644 plugins/Dfusion/luafiles/friendship/friendship.o delete mode 100644 plugins/Dfusion/luafiles/friendship/init.lua delete mode 100644 plugins/Dfusion/luafiles/friendship/install.lua delete mode 100644 plugins/Dfusion/luafiles/friendship/patch.lua delete mode 100644 plugins/Dfusion/luafiles/friendship/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/friendship/races.txt delete mode 100644 plugins/Dfusion/luafiles/friendship_civ/compile.bat delete mode 100644 plugins/Dfusion/luafiles/friendship_civ/friendship_c.asm delete mode 100644 plugins/Dfusion/luafiles/friendship_civ/friendship_c.o delete mode 100644 plugins/Dfusion/luafiles/friendship_civ/init.lua delete mode 100644 plugins/Dfusion/luafiles/friendship_civ/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/init.lua delete mode 100644 plugins/Dfusion/luafiles/migrants/compile.bat delete mode 100644 plugins/Dfusion/luafiles/migrants/init.lua delete mode 100644 plugins/Dfusion/luafiles/migrants/migrants.asm delete mode 100644 plugins/Dfusion/luafiles/migrants/migrants.o delete mode 100644 plugins/Dfusion/luafiles/migrants/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/migrants/races.txt delete mode 100644 plugins/Dfusion/luafiles/offsets/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/offsets_misc.lua delete mode 100644 plugins/Dfusion/luafiles/simple_embark/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/tools/init.lua delete mode 100644 plugins/Dfusion/luafiles/tools/plugin.lua delete mode 100644 plugins/Dfusion/luafiles/utils.lua diff --git a/plugins/Dfusion/CMakeLists.txt b/plugins/Dfusion/CMakeLists.txt index 51f2e3bee..6cfe9eb40 100644 --- a/plugins/Dfusion/CMakeLists.txt +++ b/plugins/Dfusion/CMakeLists.txt @@ -9,6 +9,3 @@ set( FILE(GLOB DFUSION_HS include/*) SET_SOURCE_FILES_PROPERTIES( ${DFUSION_HS} PROPERTIES HEADER_FILE_ONLY TRUE ) DFHACK_PLUGIN(dfusion ${DFUSION_CPPS_ALL} ${DFUSION_HS} LINK_LIBRARIES lua dfhack-tinythread) - -# installs into DF root -install(DIRECTORY luafiles/ DESTINATION dfusion) \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/adv_tools/plugin.lua b/plugins/Dfusion/luafiles/adv_tools/plugin.lua deleted file mode 100644 index cdd81e06f..000000000 --- a/plugins/Dfusion/luafiles/adv_tools/plugin.lua +++ /dev/null @@ -1,3 +0,0 @@ -if not(FILE) then - adv_tools.menu:display() -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua deleted file mode 100644 index bdf3a2bc8..000000000 --- a/plugins/Dfusion/luafiles/common.lua +++ /dev/null @@ -1,514 +0,0 @@ -dofile("dfusion/offsets_misc.lua") -STD_STRING=0 -DWORD=1 -WORD=2 -BYTE=3 -QWORD=4 -DOUBLE=5 -FLOAT=6 - -getline=function (inp) -return dfhack.lineedit(inp or "") -end -io.stdin=nil - -function printd(...) - if DEBUG then - print(...) - end -end -function GetTextRegion() - if __TEXT ~=nil then --Chache this, not going to change. - return __TEXT - end - - ranges__=Process.getMemRanges() - --print("Ranges:"..#ranges__) - for k,v in pairs(ranges__) do - for k2,v2 in pairs(v) do - --print(string.format("%d %s->%s",k,tostring(k2),tostring(v2))) - end - --local num - --flgs="" - --if(v["read"])then flgs=flgs..'r' end - --if(v["write"])then flgs=flgs..'w' end - --if(v["execute"]) then flgs=flgs..'e' end - --if num>=100 then - --print(string.format("%d %x->%x %s %s",k,v["start"],v["end"],v.name or "",flgs)) - --end - local pos=string.find(v.name,"Dwarf Fortress.exe") or string.find(v.name,"libs/Dwarf_Fortress") - if(pos~=nil) and v["execute"] then - __TEXT=v; - return v; - end - end - error(".Text region not found!") -end -function UpdateRanges() - ranges__=Process.getMemRanges() -end -function GetRegionIn(pos) - ranges__=ranges__ or Process.getMemRanges() - for k,v in pairs(ranges__) do - if pos>=v.start and pos%s",k,tostring(k2),tostring(v2))) - --end - --local num - --num=0 - --if(v["read"])then num=num+1 end - --if(v["write"])then num=num+10 end - --if(v["execute"]) then num=num+100 end - --print(string.format("%d %x->%x %s %x",k,v["start"],v["end"],v.name,pos)) - if pos>=v.start then --This is a hack to counter .text region suddenly shrinking. - if cr~=nil then - if v.start < cr.start then -- find region that start is closest - cr=v - end - else - cr=v - end - end - end - return cr -end -function ValidOffset(pos) - ranges__=ranges__ or Process.getMemRanges() - return GetRegionIn(pos)~=nil -end -function unlockDF() - local reg=GetTextRegion() - reg["write"]=true - Process.setPermisions(reg,reg) -end -function lockDF() - local reg=GetTextRegion() - reg["write"]=false - Process.setPermisions(reg,reg) -end -function SetExecute(pos) - UpdateRanges() - local reg=GetRegionIn(pos) - reg.execute=true - reg["write"]=true - Process.setPermisions(reg,reg) -- TODO maybe make a page with only execute permisions or sth -end --- engine bindings -engine=engine or {} ---[=[ use default peek/pokes for now -engine.peekd=Process.readDWord -engine.poked=Process.writeDWord -engine.peekb=Process.readByte -engine.pokeb=Process.writeByte -engine.peekw=Process.readWord -engine.pokew=Process.writeWord -engine.peekstr_stl=Process.readSTLString -engine.pokestr_stl=Process.writeSTLString -engine.peekstr=Process.readCString ---engine.pokestr=Process.readCString -engine.peekarb=Process.read -engine.pokearb=Process.write ---]=] - -function engine.peek(offset,rtype) - if type(rtype)=="table" then - if rtype.off ==nil then - return engine.peekpattern(offset,rtype) - else - return engine.peek(rtype.off+offset,rtype.rtype) - end - end - if rtype==STD_STRING then - return engine.peekstr2(offset) - elseif rtype==DWORD then - return engine.peekd(offset) - elseif rtype==WORD then - return engine.peekw(offset) - elseif rtype==BYTE then - return engine.peekb(offset) - elseif rtype==QWORD then - return engine.peekq(offset) - elseif rtype==FLOAT then - return engine.peekfloat(offset) - elseif rtype==DOUBLE then - return engine.peekdouble(offset) - else - error("Invalid peek type") - return - end -end -function engine.poke(offset,rtype,val) - if type(rtype)=="table" then - if rtype.off ==nil then - return engine.pokepattern(offset,rtype,val) - else - return engine.poke(rtype.off+offset,rtype.rtype,val) - end - end - if rtype==STD_STRING then - return engine.pokestr2(offset,val) - elseif rtype==DWORD then - return engine.poked(offset,val) - elseif rtype==WORD then - return engine.pokew(offset,val) - elseif rtype==BYTE then - return engine.pokeb(offset,val) - elseif rtype==QWORD then - return engine.pokeq(offset,val) - elseif rtype==FLOAT then - return engine.pokefloat(offset,val) - elseif rtype==DOUBLE then - return engine.pokedouble(offset,val) - else - error("Invalid poke type:"..tostring(rtype)) - return - end -end -function engine.sizeof(rtype) - if rtype==STD_STRING then - error("String has no constant size") - return - elseif rtype==DWORD then - return 4; - elseif rtype==WORD then - return 2; - elseif rtype==BYTE then - return 1; - else - error("Invalid sizeof type") - return - end -end -function engine.peekpattern(offset,pattern) - local ret={} - for k,v in pairs(pattern) do - --print("k:"..k.." v:"..type(v)) - if type(v)=="table" then - ret[k]=engine.peek(offset+v.off,v.rtype) - --print(k.." peeked:"..offset+v.off) - else - ret[k]=v - end - end - ret.__offset=offset - return ret -end -function engine.pokepattern(offset,pattern,val) - for k,v in pairs(pattern) do - --print("k:"..k.." v:"..type(v)) - if type(v)=="table" then - engine.poke(offset+v.off,v.rtype,val[k]) - end - end -end - -function engine.LoadModData(file) - local T2={} - T2.symbols={} - T2.data,T2.size=engine.loadobj(file) - data,modsize=engine.loadobj(file) - local T=engine.loadobjsymbols(file) - for k,v in pairs(T) do - - if v.pos~=0 then - T2.symbols[v.name]=v.pos - end - end - return T2 -end -function engine.FindMarkerCall(moddata,name) - if moddata.symbols[name] ~=nil then - return moddata.symbols[name]+1 - end -end -function engine.FindMarker(moddata,name) - if moddata.symbols[name] ~=nil then - return engine.findmarker(0xDEADBEEF,moddata.data,moddata.size,moddata.symbols[name]) - end -end -function engine.installMod(file,name,bonussize) - local T=engine.LoadModData(file) - local modpos,modsize=engine.loadmod(file,name,bonussize) - T.pos=modpos - return T -end - - - -function PrintPattern(loadedpattern) - for k,v in pairs(loadedpattern) do - if type(v)== "string" then - print(k.." "..v) - else - print(string.format("%s %d inhex:%x",k,v,v)) - end - end -end - -function printPattern(pattern) - local i=0; - local names={} - names[STD_STRING]="std_string (STD_STRING)" - names[DWORD]= "Double word (DWORD)" - names[WORD]= "Word (WORD)" - names[STD_STRING]="Byte (BYTE)" - ret={} - for k,v in pairs(pattern) do - if type(v)=="table" and v.off~=nil then - - if names[v.rtype]~=nil then - lname=names[v.rtype] - else - if type(v.rtype)=="table" then - lname="Table (prob subpattern)" - else - lname="Other" - end - end - print(string.format("%d. %s is %s with offset %x",i,k,lname,v.off)) - table.insert(ret,k) - else - print(string.format("%d. %s",i,k)) - end - i=i+1 - end - return ret; -end -function editPattern(offset,pattern,name) - if type(pattern[name].rtype)=="table" then - if pattern[name].rtype.setval~=nil then - print(string.format("%x",offset+pattern[name].off)) - local t=engine.peek(offset+pattern[name].off,pattern[name].rtype) - print("Value is now:"..t:getval()) - print("Enter new value:") - val=io.stdin:read() - t:setval(val) - else - ModPattern(offset+pattern[name].off,pattern[name].rtype) - end - return - end - val=engine.peek(offset,pattern[name]) - print("Value is now:"..val) - print("Enter new value:") - if pattern[name].rtype==STD_STRING then - val=io.stdin:read() - else - val=tonumber(io.stdin:read()) - end - engine.poke(offset,pattern[name],val) -end -function ModPattern(itemoffset,pattern) - print("Select what to edit:") - nm=printPattern(pattern) - q=tonumber(io.stdin:read()) - if q~=nil and q<#nm then - editPattern(itemoffset,pattern,nm[q+1]) - end -end - -function findVectors() - if __VECTORS ~=nil then --chache - return __VECTORS - end - local text=GetTextRegion() - local h=hexsearch(text.start,text["end"],0x8b,ANYBYTE,ANYDWORD,0x8b,ANYBYTE,ANYDWORD,0x2b) - local pos=h:findall() - local T={} - for k,v in pairs(pos) do - local loc1,loc2 - loc1=engine.peekd(v+2) - loc2=engine.peekd(v+8) - --print(string.format("%x - %x=%x",loc1,loc2,loc1-loc2)) - if(loc1-loc2==4) then - if T[loc1-4]~=nil then - T[loc1-4]=T[loc1-4]+1 - else - T[loc1-4]=1 - end - end - end - __VECTORS=T - return T -end - -function GetRaceToken(p) --actually gets token... - local vec=df.global.world.raws.creatures.all - return vec[p].creature_id -end -function BuildNameTable() - local rtbl={} - local vec=df.global.world.raws.creatures.all - --print(string.format("Vector start:%x",vec.st)) - --print(string.format("Vector end:%x",vec.en)) - --print("Creature count:"..vec.size) - for k=0,#vec-1 do - local name=vec[k].creature_id - --print(k.." "..tostring(name)) - rtbl[name]=k - end - return rtbl; -end -function BuildMaterialTable() - local rtbl={} - local vec=engine.peek(offsets.getEx('Materials'),ptr_vector) - --print(string.format("Vector start:%x",vec.st)) - --print(string.format("Vector end:%x",vec.en)) - --local i=0 - for p=0,vec:size()-1 do - local off=vec:getval(p) - --print("First member:"..off) - local name=engine.peek(off,ptt_dfstring) - --print("Loading:"..p.."="..name:getval()) - rtbl[name:getval()]=p - --i=i+1 - --if i>100 then - -- io.stdin:read() - -- i=0 - --end - end - return rtbl; -end -function BuildWordTables() - local names={} - local rnames={} - local vector=engine.peek(offsets.getEx('WordVec'),ptr_vector) -for i =0,vector:size()-1 do - local off=vector:getval(i) - local n=engine.peekstr(off) - names[i]=n - rnames[n]=i -end - return names,rnames -end -function ParseScript(file) - - io.input(file) - f="" - first=0 - nobraces=0 - function updFunction() - if f~="" then - first=0 - if nobraces==0 then - f=f.."}" - end - nobraces=0 - print("Doing:"..f) - assert(loadstring(f))() - - f="" - end - end - while true do - - local line = io.read("*line") - if line == nil then break end - if string.sub(line,1,2)==">>" then - updFunction() - if string.find(line,"%b()") then - f=string.sub(line,3) - nobraces=1 - else - f=string.sub(line,3).."{" - end - --print(string.sub(line,3)..) - else - if first~=0 then - f=f.."," - else - first=1 - end - f=f..string.format('%q',line) - - end - - end - updFunction() -end -function ParseNames(path) - local ret={} - local ff=io.open(path) - for n in ff:lines() do - table.insert(ret,n) - end - return ret -end -function getSelectedUnit() - if df.global.ui.main.mode~=23 then - return nil - end - local unit_indx=df.global.ui_selected_unit - if unit_indx<#df.global.world.units.active-1 then - return df.global.world.units.active[unit_indx] - else - return nil - end -end -function getxyz() -- this will return pointers x,y and z coordinates. - local x=df.global.cursor.x - local y=df.global.cursor.y - local z=df.global.cursor.z - return x,y,z -- return the coords -end -function getCreatureAtPos(x,y,z) -- gets the creature index @ x,y,z coord - --local x,y,z=getxyz() --get 'X' coords - local vector=df.global.world.units.all -- load all creatures - for i = 0, #vector-1 do -- look into all creatures offsets - local curpos=vector[i].pos --get its coordinates - local cx=curpos.x - local cy=curpos.y - local cz=curpos.z - if cx==x and cy==y and cz==z then --compare them - return vector[i] --return index - end - end - --print("Creature not found!") - return nil - -end -function getCreatureAtPointer() - return getCreatureAtPos(getxyz()) -end -function getCreature() - local unit=getSelectedUnit() - if unit==nil then - unit=getCreatureAtPointer() - end - --any other selection methods... - return unit -end -function getNemesisId(unit) - for k,v in pairs(unit.refs) do - if df.general_ref_is_nemesisst:is_instance(v) then - return v.nemesis_id - end - end -end -function getNemesis(unit) - local id=getNemesisId(unit) - if id then - return df.nemesis_record.find(id) - end -end -function Allocate(size) - local ptr=engine.getmod('General_Space') - if ptr==nil then - ptr=engine.newmod("General_Space",4096) -- some time later maybe make some more space - engine.poked(ptr,4) - end - - local curptr=engine.peekd(ptr) - curptr=curptr+size - engine.poked(ptr,curptr) - return curptr-size+ptr -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/embark/build.bat b/plugins/Dfusion/luafiles/embark/build.bat deleted file mode 100644 index 51eeb8140..000000000 --- a/plugins/Dfusion/luafiles/embark/build.bat +++ /dev/null @@ -1 +0,0 @@ -as -a --32 -o embark.o embark.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/embark/embark.asm b/plugins/Dfusion/luafiles/embark/embark.asm deleted file mode 100644 index d2fa91081..000000000 --- a/plugins/Dfusion/luafiles/embark/embark.asm +++ /dev/null @@ -1,7 +0,0 @@ -.intel_syntax -mov eax , [esp+0x1C] # loop counter -mark_caste: -movsx ecx, word ptr[eax*2+0xdeadbeef] -mark_race: -movzx eax,word ptr [eax*2+0xDEADBEEF] -ret diff --git a/plugins/Dfusion/luafiles/embark/embark.o b/plugins/Dfusion/luafiles/embark/embark.o deleted file mode 100644 index 87f5bbd68f5c8d0aaf6f81485f653a440145a952..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 369 zcmeZaWM%+?B|yvtX0bBrm84dbfY}g20!Z~B@j*-l27?5>l*E!mG;wsU1B1ZMAX_RACES then - error("caste and race count must be less then "..MAX_RACES) - end - if stoff==nil then - error("address for start_dwarf_count not found!") - end - local _,race_id_offset=df.sizeof(df.global.ui:_field("race_id")) - print(string.format("start=%08x",stoff)) - local needle={0x0f,0xb7,0x0d} --movzx eax,dword ptr [race_id] - local tmp_table=dfu.dwordToTable(race_id_offset) - for k,v in ipairs(tmp_table) do - table.insert(needle,v) - end - - local mem=ms.get_code_segment() - print(mem.uint8_t:addr2idx(stoff)) - print(mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff))) - local _,trg_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(stoff),nil)--maybe endoff=stoff+bignumber - if trg_offset==nil then - error("address for race_load not found") - end - local call_data={0x90,0x90} - local _,data_offset=df.sizeof(self.data) - dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) - self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} - needle={0x83,0xc8,0xff} -- or eax, 0xFF - local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) - if caste_offset==nil or caste_offset-stoff>1000 then - error("Caste change code not found or found too far!") - end - - self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} - self.disable_castes:apply() - self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} - self.dwarfcount:apply() - local caste_array=self:allocate("caste_array","uint16_t",#self.race_caste_data) - local race_array=self:allocate("race_array","uint16_t",#self.race_caste_data) - self:setEmbarkParty(self.race_caste_data) - for k,v in ipairs(self.race_caste_data) do - caste_array[k-1]=v[2] - race_array[k-1]=v[1] - end - local race_array_off,caste_array_off - local _ - _,race_array_off=df.sizeof(race_array) - _,caste_array_off=df.sizeof(caste_array) - self:set_marker_dword("race",caste_array_off) --hehe... mixed them up i guess... - self:set_marker_dword("caste",race_array_off) - - self:move_to_df() - self.call_patch:apply() - self.installed=true - end - function CustomEmbark:setEmbarkParty(racesAndCastes) - self.race_caste_data=racesAndCastes - if self.dwarfcount== nil then - self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} - self.dwarfcount:apply() - else - self.dwarfcount:repatch(dfu.dwordToTable(#self.race_caste_data)) - end - - end - function CustomEmbark:status() - if self.installed then - return "valid, installed" - else - return "valid, not installed" - end - end - function CustomEmbark:uninstall() - if self.installed then - self.call_patch:remove() - self.disable_castes:remove() - self.dwarfcount:remove() - end - end -else - CustomEmbark.class_status="invalid, os not supported" -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/embark/plugin.lua b/plugins/Dfusion/luafiles/embark/plugin.lua deleted file mode 100644 index 039000656..000000000 --- a/plugins/Dfusion/luafiles/embark/plugin.lua +++ /dev/null @@ -1,5 +0,0 @@ - -if not(FILE)then - names=ParseNames("dfusion/embark/races.txt")--io.open("plugins/embark/races.txt"):lines() - embark(names) -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/embark/races.txt b/plugins/Dfusion/luafiles/embark/races.txt deleted file mode 100644 index 376928866..000000000 --- a/plugins/Dfusion/luafiles/embark/races.txt +++ /dev/null @@ -1,9 +0,0 @@ -ANT_MAN:0 -ANT_MAN:0 -ANT_MAN:0 -ANT_MAN:1 -ANT_MAN:1 -ANT_MAN:0 -ANT_MAN:0 -ANT_MAN:2 -ANT_MAN:3 \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship/compile.bat b/plugins/Dfusion/luafiles/friendship/compile.bat deleted file mode 100644 index e084949f4..000000000 --- a/plugins/Dfusion/luafiles/friendship/compile.bat +++ /dev/null @@ -1 +0,0 @@ -as -anl --32 -o friendship.o friendship.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship/friendship.asm b/plugins/Dfusion/luafiles/friendship/friendship.asm deleted file mode 100644 index b649e38de..000000000 --- a/plugins/Dfusion/luafiles/friendship/friendship.asm +++ /dev/null @@ -1,106 +0,0 @@ -.intel_syntax -push eax -mov eax,[esp+0x04] -push ebx -pushfd -mov eax,[eax] # get a byte after the call this procedure to analyze what register holds cr ptr -jmptbl: -cmp al,0x81 -jz regC -cmp al,0x82 -jz regD -cmp al,0x83 -jz regB -cmp al,0x85 -jz regBP -cmp al,0x86 -jz regESI -cmp al,0x87 -jz regEDI -cmp al,0x88 -jz regA -cmp al,0x8A -jz regD -cmp al,0x8B -jz regB -cmp al,0x8D -jz regBP -cmp al,0x8E -jz regESI -cmp al,0x8F -jz regEDI -cmp al,0x90 -jz regA -cmp al,0x91 -jz regC -cmp al,0x93 -jz regB -cmp al,0x95 -jz regBP -cmp al,0x96 -jz regESI -cmp al,0x97 -jz regEDI -jmp fail -regA: -mov eax, [esp+0x8] -mov eax, [eax+0x8c] -jmp compare -regC: -mov eax, [ecx+0x8c] -jmp compare -regB: -mov eax, [ebx+0x8c] -jmp compare -regD: -mov eax, [edx+0x8c] -jmp compare -regBP: -mov eax, [ebp+0x8c] -jmp compare -regESI: -mov eax, [esi+0x8c] -jmp compare -regEDI: -mov eax, [edi+0x8c] -#jmp compare -compare: -push ecx -mark_racepointer: -mov ebx,0xDEADBEEF #write a pointer to the list of allowed races -mark_racecount: -mov ecx,0xDEADBEEF #write a number of allowed races -loop1: -cmp word[ebx+ecx*2],ax -jz endok -dec ecx -cmp ecx ,-1 -jnz loop1 -pop ecx -popfd -jmp fail -endok: -pop ecx -popfd -cmp eax,eax -jmp endfinal -fail: - -xor ebx,ebx -xor eax,eax -inc eax -cmp eax,ebx -endfinal: - -pop ebx -pop eax -mark_safeloc1: -mov [0xDEADBEEF],eax #write a pointer to safe location (usually after this) -pop eax -pushfd -inc eax #skip one instruction -popfd -push eax -mark_safeloc2: -mov eax,[0xDEADBEEF] #write a pointer to safe location (same as above) -ret diff --git a/plugins/Dfusion/luafiles/friendship/friendship.o b/plugins/Dfusion/luafiles/friendship/friendship.o deleted file mode 100644 index c801562dbc1bc9ab11c74929ef422aff7629dcb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 854 zcmeZaWM%+?5JmI3a0tG+rTticRPp<-2Hyv+Iu@eM4F|GH&Y3hXYaMV#5$~(mYI_Z_29q%3=HXvX^Gh|0jPLZZb3;>4g&)x zkPi%c1~!JG)O2T%WIVEn3rHj%S;Pq>l8z!00OXY;i?{}RGBDJmh`0bn+L1*ZfgFZl zxJX)JCeSqu4C~<{$@#ejiAAXly>JnbJPQ!_p@@hAd6VHHIr;eohCtp7xJYVVN`5ww zw-Q+-Ei*4MXB%7u$Y4+b;vFa=MnK*nxCnBp1cewgBPd;>0^}6Q1e9V1DMSOvsS%X+ uv8izYisvR4WycpKCZ`tUXXcfp79j+Z^GoweAl%}_wA7sZWJ4655d#3LE~Ebd diff --git a/plugins/Dfusion/luafiles/friendship/init.lua b/plugins/Dfusion/luafiles/friendship/init.lua deleted file mode 100644 index 0fc3219e4..000000000 --- a/plugins/Dfusion/luafiles/friendship/init.lua +++ /dev/null @@ -1,45 +0,0 @@ -function analyzeF(off) - pos=offsets.find(off,0x39,ANYBYTE,0x8c,00,00,00) - print(string.format("Compare at:%x",pos)) - if pos ==0 then - return 0 - end - if(pos-off>0x100) then - print(string.format("Distance to cmp:%x",pos-off)) - pos =offsets.find(off,CALL) - print(string.format("Distance to call:%x",pos-off)) - return 0 - --return analyzeF(pos) - else - return pos - end -end -function minEx(list) - local imin=list[1] - for _,v in ipairs(list) do - if imin> v and v~=0 then - imin=v - end - end - return imin -end -function signDword(dw) - if(dw>0xFFFFFFFF) then - return dw-0xFFFFFFFF - end - return dw -end ---[[ - Warning: not all mov's are acounted for. Found one: mov EAX,WORD PTR[EBP+1EF4] WTF?? - Two more compares are missing. There are calls instead (same function) -]]-- - -friendship_in={} -dofile("dfusion/friendship/install.lua") -dofile("dfusion/friendship/patch.lua") - -function friendship(names) - friendship_in.install(names) - friendship_in.patch() -end - diff --git a/plugins/Dfusion/luafiles/friendship/install.lua b/plugins/Dfusion/luafiles/friendship/install.lua deleted file mode 100644 index 251d2fb3a..000000000 --- a/plugins/Dfusion/luafiles/friendship/install.lua +++ /dev/null @@ -1,35 +0,0 @@ - -function friendship_in.install(names) -RaceTable=RaceTable or BuildNameTable() -mypos=engine.getmod("Friendship") -if mypos then - modpos=mypos - _,modsize=engine.loadobj("dfusion/friendship/friendship.o") - _=nil -else - modpos,modsize=engine.loadmod("dfusion/friendship/friendship.o","Friendship",1024) - print(string.format("Loaded module @:%x",modpos)) -end -count=0 -for _,v in pairs(names) do - if RaceTable[v] == nil then - --print("Failure, "..v.." not found!") - error("Failure, "..v.." not found!") - --break --maybe some indication of failure? and cleanup? - end - engine.pokew(modpos+modsize+count*2+4+2,RaceTable[v]) -- for some reason it compiled strangely - -- cmp word[ebx+ecx*2],ax -> cmp word[ebx+ecx*2+2],ax - count = count + 1 -end -engine.poked(modpos+0x8f,modpos+modsize+4) -- set ptr to creatures -engine.poked(modpos+0x94,count) -- set count of creatures -engine.poked(modpos+0xb9,modpos+modsize) -- set safe location -engine.poked(modpos+0xc3,modpos+modsize) -- set safe location -SetExecute(modpos) -end -function pokeCall(off) - engine.pokeb(off,0xe8) - b=engine.peekb(off+1) - engine.poked(off+1,modpos-off-5) - engine.pokeb(off+5,b) -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship/patch.lua b/plugins/Dfusion/luafiles/friendship/patch.lua deleted file mode 100644 index 247766db1..000000000 --- a/plugins/Dfusion/luafiles/friendship/patch.lua +++ /dev/null @@ -1,57 +0,0 @@ -function friendship_in.patch() - UpdateRanges() - pos=GetTextRegion().start - local _,crace=df.sizeof(df.global.ui:_field("race_id")) - hits={} - i=1 - repeat - --todo make something better/smarter... - pos1=offsets.find(pos+7,0x0f,0xBF,ANYBYTE,DWORD_,crace) -- movsx - pos2=offsets.find(pos+7,0x66,0xa1,DWORD_,crace) -- mov ax,[ptr] - pos3=offsets.find(pos+7,0xa1,DWORD_,crace) -- mov eax,[ptr] - pos4=offsets.find(pos+7,0x66,0x8b,ANYBYTE,DWORD_,crace) -- mov ANYREG,[ptr] - --pos5=offsets.find(pos+7,0x66,0x8b,0x15,DWORD_,crace) -- mov dx,[ptr] - pos=minEx{pos1,pos2,pos3,pos4} - if pos ~=0 then - hits[i]=pos - i=i+1 - print(string.format("Found at %x",pos)) - end - until pos==0 - print("=======================================") - for _,p in pairs(hits) do - myp=p - repeat - - --print(string.format("Analyzing %x...",p)) - --TODO read offset from memory.xml - pos1=offsets.find(myp,0x39,ANYBYTE,0x8c,00,00,00) -- compare [reg+08c] (creature race) with any reg - pos2=offsets.find(myp,0x3b,ANYBYTE,0x8c,00,00,00) -- compare any reg with [reg+08c] (creature race) - pos=minEx{pos1,pos2} - if pos ~=0 then - - if(pos-p>250) then - --this here does not work yet... - --[[pos =offsets.find(p,CALL) - print(string.format("Distance to call:%x",pos-p)) - print(string.format("Call: %x",signDword(engine.peekd(pos+1)+pos))) - pos=analyzeF(signDword(signDword(engine.peekd(pos+1)+pos))) - - print(string.format("Cmp @:%x",pos))]]-- - print(string.format("skipping %x... Cmp too far away (dist=%i)",p,pos-p)) - else - --print(string.format("Found at %x, simple compare",pos)) - --print(string.format("Distance =%x",pos-p)) - --patch compares - - pokeCall(pos) - end - else - break - end - myp=myp+pos+6 - if myp-p >250 then break end - until false - - end -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship/plugin.lua b/plugins/Dfusion/luafiles/friendship/plugin.lua deleted file mode 100644 index 695ddf05e..000000000 --- a/plugins/Dfusion/luafiles/friendship/plugin.lua +++ /dev/null @@ -1,18 +0,0 @@ -if not(FILE) then - --sanity test - --print("race num:"..engine.peekw(offsets.getEx("CurrentRace"))) - --print(string.format("%x vs %x",offsets.getEx("CurrentRace"),VersionInfo.getGroup("Creatures"):getAddress("current_race"))) - print("Race num:"..df.global.ui.race_id) - print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) - print("If this is wrong please type 'q'") - if(getline()=='q') then - return - end - -end - -if not(FILE) then - names=ParseNames("dfusion/friendship/races.txt")--io.open("plugins/friendship/races.txt"):lines() - friendship_in.install(names) - friendship_in.patch() -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship/races.txt b/plugins/Dfusion/luafiles/friendship/races.txt deleted file mode 100644 index e4ed7c5a6..000000000 --- a/plugins/Dfusion/luafiles/friendship/races.txt +++ /dev/null @@ -1,8 +0,0 @@ -DWARF -GOBLIN -ELF -HUMAN -KOBOLD -GREMLIN -TIGERMAN -ANT_MAN \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship_civ/compile.bat b/plugins/Dfusion/luafiles/friendship_civ/compile.bat deleted file mode 100644 index 64d4d4440..000000000 --- a/plugins/Dfusion/luafiles/friendship_civ/compile.bat +++ /dev/null @@ -1 +0,0 @@ -as -anl --32 -o friendship_c.o friendship_c.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship_civ/friendship_c.asm b/plugins/Dfusion/luafiles/friendship_civ/friendship_c.asm deleted file mode 100644 index b2b49ee78..000000000 --- a/plugins/Dfusion/luafiles/friendship_civ/friendship_c.asm +++ /dev/null @@ -1,41 +0,0 @@ -.intel_syntax -eaxpart: -push eax -push ecx -jmp compare -ecxpart: -push eax -push ecx -mov eax,ecx - -compare: -push ebx -mov ebx,0xDEADBEEF #write a pointer to the list of allowed civs -mov ecx,2000 #write a number of allowed civs -loop1: -cmp [ebx+ecx*4],eax -jnz endok -dec ecx -cmp ecx ,-1 -jnz loop1 - -pop ebx - -jmp fail - -endok: -pop ebx - -cmp eax,eax -jmp endfinal -fail: - -xor ecx,ecx -xor eax,eax -inc eax -cmp eax,ebx -endfinal: - -pop ecx -pop eax -ret \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/friendship_civ/friendship_c.o b/plugins/Dfusion/luafiles/friendship_civ/friendship_c.o deleted file mode 100644 index d85134682ac1e464b944b3e3403babe96a43a8b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 462 zcmeZaWM%+?JwVJ4X0bBrm84dbfY}hj07&&9@j*-l27?5>l*E!mG;wsU1B1Z1OTR|HW{tKy@S5H5q~QSv z%NvmqhxO7jb5fxO{rk_rkj|Kvm<QhwOUcg$@)Y4BX^ELRKn{Z%Tm+~lEi*4MM;9)F9Ficrm>EG~ ai3*TIkqIcp3{r>&kV6m@U)a=uTn_-GKtCM- diff --git a/plugins/Dfusion/luafiles/friendship_civ/init.lua b/plugins/Dfusion/luafiles/friendship_civ/init.lua deleted file mode 100644 index 5390829de..000000000 --- a/plugins/Dfusion/luafiles/friendship_civ/init.lua +++ /dev/null @@ -1,89 +0,0 @@ -friendship_civ={} -function friendship_civ.init() - friendship_civ.count=0x0f - friendship_civ.firsttime=true - local mypos=engine.getmod("Friendship_civ") - local modpos=0 - if mypos then - modpos=mypos - _,modsize=engine.loadobj("dfusion/friendship_civ/friendship_c.o") - _=nil - friendship_civ.firsttime=false - else - modpos,modsize=engine.loadmod("dfusion/friendship_civ/friendship_c.o","Friendship_civ",1024) - print(string.format("Loaded module @:%x",modpos)) - end - friendship_civ.modpos=modpos - friendship_civ.modsize=modsize -end -function friendship_civ.install(civs) - friendship_civ.init() - local count=0 - for _,v in pairs(civs) do - engine.poked(friendship_civ.modpos+friendship_civ.modsize+count*4,v) -- for some reason it compiled strangely - -- cmp word[ebx+ecx*2],ax -> cmp word[ebx+ecx*2+2],ax - count = count + 1 - end - engine.poked(friendship_civ.modpos+0x0a,friendship_civ.modpos+friendship_civ.modsize) -- set ptr to civ ids - engine.poked(friendship_civ.modpos+friendship_civ.count,count-1) -- set count of civs - SetExecute(friendship_civ.modpos) - - if(friendship_civ.firsttime) then - friendship_civ.patch() - end -end -function friendship_civ.getcivs() - if(friendship_civ.firsttime==nil)then - return nil - end - friendship_civ.init() - local count=engine.peekd(friendship_civ.modpos+friendship_civ.count)+1 - local ret={} - for i=0, count-1 do - table.insert(ret,engine.peekd(friendship_civ.modpos+friendship_civ.modsize+i*4)) - end - return ret -end -function friendship_civ.addciv(civ) --if called with nil add current civ :) - if civ==nil then - local cciv=engine.peekd(VersionInfo.getGroup("Creatures"):getAddress("current_civ")) - friendship_civ.install({cciv}) - return - end - local oldcivs=friendship_civ.getcivs() - oldcivs=oldcivs or {} - if type(civ)=="table" then - for k,v in ipairs(civ) do - table.insert(oldcivs,v) - end - else - table.insert(oldcivs,civ) - end - friendship_civ.install(oldcivs) -end -function friendship_civ.patch_call(off,iseax) - local calltrg=friendship_civ.modpos - if not iseax then - calltrg=calltrg+4 - end - engine.pokeb(off,0xe8) --this is a call - engine.poked(off+1,calltrg-off-5) --offset to call to (relative) - engine.pokeb(off+5,0x90) --nop -end -function friendship_civ.patch() - --UpdateRanges() - local civloc= VersionInfo.getGroup("Creatures"):getAddress("current_civ") - local pos1=offsets.findall(0,0x3B,0x05,DWORD_,civloc) --eax - for k,v in pairs(pos1) do print(string.format("%d %x",k,v)) end - local pos2=offsets.findall(0,0x3B,0x0D,DWORD_,civloc) --ecx - for k,v in pairs(pos2) do print(string.format("%d %x",k,v)) end - - for k,v in pairs(pos1) do - print(string.format("Patching eax compare %d: %x",k,v)) - friendship_civ.patch_call(v,true) - end - for k,v in pairs(pos2) do - print(string.format("Patching ecx compare %d: %x",k,v)) - friendship_civ.patch_call(v,false) - end -end diff --git a/plugins/Dfusion/luafiles/friendship_civ/plugin.lua b/plugins/Dfusion/luafiles/friendship_civ/plugin.lua deleted file mode 100644 index 12f334fcf..000000000 --- a/plugins/Dfusion/luafiles/friendship_civ/plugin.lua +++ /dev/null @@ -1,57 +0,0 @@ -fc_ui={} -fc_ui.menu=MakeMenu() -function fc_ui.get() - local mycivs=friendship_civ.getcivs() - if mycivs~= nil then - print(" Currently friendly civs:") - for k,v in pairs(mycivs) do - print(string.format("%d. %d",k,v)) - end - else - print(" Plugin no yet activated.") - end -end -function fc_ui.add() - print("Type in civ id to add (leave empty to add current, q cancels):") - local r - while r==nil and r~='q' do - r=io.stdin:read() - if r=="" then - r=nil - break - end - if r~='q' then r=tonumber(r) else - return - end - end - friendship_civ.addciv(r) -end -function fc_ui.remove() - local mycivs=friendship_civ.getcivs() - if mycivs~= nil then - print(" Currently friendly civs:") - for k,v in pairs(mycivs) do - print(string.format("%d. %d",k,v)) - end - else - print(" Plugin no yet activated, nothing to remove.") - return - end - print("Type in civ id to remove( q cancels):") - local r - while r==nil and r~='q' do - r=io.stdin:read() - if r~='q' then - r=tonumber(r) - if r>#mycivs then r=nil end - else - return - end - end - table.remove(mycivs,r) - friendship_civ.install(mycivs) -end -fc_ui.menu:add("Add civ",fc_ui.add) -fc_ui.menu:add("Get civs",fc_ui.get) -fc_ui.menu:add("Remove civ",fc_ui.remove) -fc_ui.menu:display() \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/init.lua b/plugins/Dfusion/luafiles/init.lua deleted file mode 100644 index c8eebcd23..000000000 --- a/plugins/Dfusion/luafiles/init.lua +++ /dev/null @@ -1,92 +0,0 @@ -function dofile(filename) --safer dofile, with traceback (very usefull) - f,perr=loadfile(filename) - if f~=nil then - return safecall(f) - else - print(perr) - end -end -function dofile_silent(filename) --safer dofile, with traceback, no file not found error - f,perr=loadfile(filename) - if f~=nil then - return safecall(f) - else - if(string.sub(perr,1,11)~="cannot open") then --ugly hack - print(perr) - end - end -end -function loadall(t1) --loads all non interactive plugin parts, so that later they could be used - for k,v in pairs(t1) do - dofile_silent("dfusion/"..v[1].."/init.lua") - end -end -function mainmenu(t1) - while true do - print("No. Name Desc") - for k,v in pairs(t1) do - print(string.format("%3d %15s %s",k,v[1],v[2])) - end - local q=dfhack.lineedit("Select plugin to run (q to quit):") - if q=='q' then return end - q=tonumber(q) - if q~=nil then - if q>=1 and q<=#t1 then - if t1[q][3]==nil then - dofile("dfusion/"..t1[q][1].."/plugin.lua") - else - t1[q][3]() - end - end - end - end -end -function RunSaved() - print("Locating saves...") - local str=df.global.world.cur_savegame.save_dir - print("Current region:"..str) - str="data/save/"..str.."/dfusion/init.lua" - print("Trying to run:"..str) - dofile_silent(str) -end -dofile("dfusion/common.lua") -dofile("dfusion/utils.lua") -dofile("dfusion/offsets_misc.lua") -dofile("dfusion/editor.lua") - -unlockDF() -plugins={} -table.insert(plugins,{"simple_embark","A simple embark dwarf count editor"}) -table.insert(plugins,{"tools","some misc tools"}) -table.insert(plugins,{"embark","Multi race embark"}) -table.insert(plugins,{"friendship","Multi race fort enabler"}) ---[=[table.insert(plugins,{"items","A collection of item hacking tools"}) -table.insert(plugins,{"offsets","Find all offsets"}) - -table.insert(plugins,{"friendship_civ","Multi civ fort enabler"}) - - -table.insert(plugins,{"triggers","a function calling plug (discontinued...)"}) -table.insert(plugins,{"migrants","multi race imigrations"}) ---]=] ---table.insert(plugins,{"onfunction","run lua on some df function"}) ---table.insert(plugins,{"editor","edit internals of df",EditDF}) -table.insert(plugins,{"saves","run current worlds's init.lua",RunSaved}) -table.insert(plugins,{"adv_tools","some tools for (mainly) adventurer hacking"}) -loadall(plugins) -dofile_silent("dfusion/initcustom.lua") - -local args={...} - - -local f,err=load(table.concat(args,' ')) -if f then - f() -else - dfhack.printerr(err) -end - -if not INIT then -mainmenu(plugins) -end - diff --git a/plugins/Dfusion/luafiles/migrants/compile.bat b/plugins/Dfusion/luafiles/migrants/compile.bat deleted file mode 100644 index 4d226851d..000000000 --- a/plugins/Dfusion/luafiles/migrants/compile.bat +++ /dev/null @@ -1 +0,0 @@ -as -anl --32 -o migrants.o migrants.asm \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/migrants/init.lua b/plugins/Dfusion/luafiles/migrants/init.lua deleted file mode 100644 index 6fb315923..000000000 --- a/plugins/Dfusion/luafiles/migrants/init.lua +++ /dev/null @@ -1,62 +0,0 @@ ---install part -function migrants(names) -RaceTable=RaceTable or BuildNameTable() -mypos=engine.getmod("Migrants") -if mypos then - print("Migrant mod is running already @:"..mypos) - modpos=mypos - _,modsize=engine.loadobj("dfusion/migrants/migrants.o") - count=0 - for _,v in pairs(names) do - if RaceTable[v] == nil then - print("Failure, "..v.." not found!") - break --maybe some indication of failure? and cleanup? - end - engine.pokew(modpos+modsize+count*2+4,RaceTable[v]) - count = count + 1 - end - seedpos=modpos+modsize - engine.poked(seedpos,math.random(1234567)) -- seed the generator :) - engine.poked(modpos+0x1c,count) --max size for div - -else - modpos,modsize=engine.loadmod("dfusion/migrants/migrants.o","Migrants",400) - print(string.format("Loaded module @:%x",modpos)) - count=0 - for _,v in pairs(names) do - if RaceTable[v] == nil then - print("Failure, "..v.." not found!") - break --maybe some indication of failure? and cleanup? - end - engine.pokew(modpos+modsize+count*2+4,RaceTable[v]) - - count = count + 1 - end - - seedpos=modpos+modsize - engine.poked(modpos+0x04,seedpos) - engine.poked(modpos+0x15,seedpos) - - engine.poked(seedpos,math.random(1234567)) -- seed the generator :) - engine.poked(modpos+0x1c,count) --max size for div - - engine.poked(modpos+0x26,seedpos+4) --start of array - - --patch part - --pos=62873C+DF - -- pattern: A1,DWORD_,"CURRENTRACE",56,89,ANYBYTE,ANYBYTE,34,e8 - _,raceoff=df.sizeof(df.global.ui:_field('race_id')) - pos=offsets.find(offsets.base(),0xa1,DWORD_,raceoff,0x56,0x89,ANYBYTE,ANYBYTE,0x34,0xe8) - function pokeCall(off) - engine.pokeb(off,0xe8) - engine.poked(off+1,modpos-off-5) - end - if pos~=0 then - print(string.format("Found @:%x",pos)) - pokeCall(pos) - else - print("Not found patch location!!!") - end - end - -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/migrants/migrants.asm b/plugins/Dfusion/luafiles/migrants/migrants.asm deleted file mode 100644 index 646497665..000000000 --- a/plugins/Dfusion/luafiles/migrants/migrants.asm +++ /dev/null @@ -1,20 +0,0 @@ -.intel_syntax -pushfd -push ebx -push edx -mov eax,[0xdeadbeef] # get old seed -mov ebx,1103515245 -#mul 1103515245 -mul ebx -add eax,12345 -mov [0xdeadbeef],eax #put seed back...thus generation rnd is complete - -xor edx,edx -mov ebx,2000 #put size of array here -div ebx #why oh why there is no div const? compiler prob makes some xor/add magic -movzx eax,word ptr[0xdeadbeef+edx*2] -pop edx -pop ebx - -popfd -ret diff --git a/plugins/Dfusion/luafiles/migrants/migrants.o b/plugins/Dfusion/luafiles/migrants/migrants.o deleted file mode 100644 index 30b5b14f03cd8e110b91123e0631f8e0b3378ed0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 336 zcmeZaWM%+?JwVI>X0bBrm84dbfY}hj07&&9@j*-l27?5>l*E!mG;wsU1B1Zh*T58Y}8WPdP$)nF)LK=uPD#F#;bp#c^k F9{~H9FI)fs diff --git a/plugins/Dfusion/luafiles/migrants/plugin.lua b/plugins/Dfusion/luafiles/migrants/plugin.lua deleted file mode 100644 index 651e4900c..000000000 --- a/plugins/Dfusion/luafiles/migrants/plugin.lua +++ /dev/null @@ -1,5 +0,0 @@ - -if not(FILE) then - names=ParseNames("dfusion/migrants/races.txt")--io.open("plugins/migrants/races.txt"):lines() - migrants(names) -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/migrants/races.txt b/plugins/Dfusion/luafiles/migrants/races.txt deleted file mode 100644 index 9a3fa6cf6..000000000 --- a/plugins/Dfusion/luafiles/migrants/races.txt +++ /dev/null @@ -1,29 +0,0 @@ -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -ELF -HUMAN -DWARF -GREMLIN -KOBOLD -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -DWARF -ELF -HUMAN -DWARF -GREMLIN -KOBOLD -DEMON_13 \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/offsets/plugin.lua b/plugins/Dfusion/luafiles/offsets/plugin.lua deleted file mode 100644 index b9e2fc441..000000000 --- a/plugins/Dfusion/luafiles/offsets/plugin.lua +++ /dev/null @@ -1,2 +0,0 @@ -offsets.searchoffsets() -offsets.save() \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/offsets_misc.lua b/plugins/Dfusion/luafiles/offsets_misc.lua deleted file mode 100644 index 8bca50000..000000000 --- a/plugins/Dfusion/luafiles/offsets_misc.lua +++ /dev/null @@ -1,48 +0,0 @@ -offsets=offsets or {} -function offsets.find(startoffset,...) - -- [=[ - if startoffset== 0 then - local text=GetTextRegion() - --print("searching in:"..text.name) - startoffset=text.start - endadr=text["end"] - else - local reg=GetRegionIn(startoffset) - --print("searching in:"..reg.name) - if reg==nil then - print(string.format("Warning: memory range for search @:%x not found!",startoffset)) - return 0 - end - endadr=reg["end"] - end - --]=] - --print(string.format("Searching (%x->%x)",startoffset,endadr)) - local h=hexsearch(startoffset,endadr,...) - local pos=h:find() - h=nil - return pos -end -function offsets.findall(startoffset,...) - local endadr; - if startoffset== 0 then - local text=GetTextRegion() - --print("searching in:"..text.name) - startoffset=text.start - endadr=text["end"] - else - local reg=GetRegionIn(startoffset) - --print("searching in:"..reg.name) - endadr=reg["end"] - end - local h=hexsearch(startoffset,endadr,...) - local pos=h:findall() - h=nil - return pos -end -function offsets.base() - return Process.getBase() -end -function offsets.getvectors() - return findVectors() -end -ADDRESS=ANYDWORD \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/simple_embark/plugin.lua b/plugins/Dfusion/luafiles/simple_embark/plugin.lua deleted file mode 100644 index abc3530c2..000000000 --- a/plugins/Dfusion/luafiles/simple_embark/plugin.lua +++ /dev/null @@ -1,23 +0,0 @@ -function simple_embark(num) - local stoff=dfhack.internal.getAddress('start_dwarf_count') - print("Starting dwarves found:"..df.reinterpret_cast('int32_t', stoff).value) - local tmp_val=df.new('int32_t') - local size,pos=tmp_val:sizeof() - tmp_val.value=num - local ret=dfhack.internal.patchMemory(stoff,tmp_val,size) - if ret then - print("Success!") - else - qerror("Failed to patch in number") - end -end -if not(FILE) then -print("Type in new amount (more than 6, less than 15000):") - repeat - ans=tonumber(io.read()) - if ans==nil or not(ans<=15000 and ans>0) then - print("incorrect choice") - end - until ans~=nil and (ans<=15000 and ans>0) - simple_embark(ans) -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua deleted file mode 100644 index a8c787ffb..000000000 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ /dev/null @@ -1,511 +0,0 @@ -local ms=require "memscan" -tools={} -tools.menu=MakeMenu() -function tools.setrace(name) - RaceTable=BuildNameTable() - print("Your current race is:"..GetRaceToken(df.global.ui.race_id)) - local id - if name == nil then - print("Type new race's token name in full caps (q to quit):") - repeat - entry=getline() - if entry=="q" then - return - end - id=RaceTable[entry] - until id~=nil - else - id=RaceTable[name] - if id==nil then - error("Name not found!") - end - end - df.global.ui.race_id=id -end -tools.menu:add("Set current race",tools.setrace) -function tools.GiveSentience(names) - RaceTable=RaceTable or BuildNameTable() --slow.If loaded don't load again - if names ==nil then - ids={} - print("Type race's token name in full caps to give sentience to:") - repeat - entry=getline() - id=RaceTable[entry] - until id~=nil - table.insert(ids,id) - else - ids={} - for _,name in pairs(names) do - id=RaceTable[name] - table.insert(ids,id) - end - end - for _,id in pairs(ids) do - local races=df.global.world.raws.creatures.all - - local castes=races[id].caste - print(string.format("Caste count:%i",castes.size)) - for i =0,#castes-1 do - - print("Caste name:"..castes[i].caste_id.."...") - - local flags=castes[i].flags - --print(string.format("%x",flagoffset)) - if flags.CAN_SPEAK then - print("\tis sentient.") - else - print("\tnon sentient. Allocating IQ...") - flags.CAN_SPEAK=true - end - end - end -end -tools.menu:add("Give Sentience",tools.GiveSentience) -function tools.embark() --windows only? - local seg=ms.get_code_segment() - local idx,off - - idx,off=seg.uint8_t:find_one{0x66, 0x83, 0x7F ,0x1A ,0xFF,0x74,0x04} - if idx then - local tmp_val=df.new('uint8_t',2) - tmp_val[0]=0x90 - tmp_val[1]=0x90 - local size,pos=tmp_val:sizeof() - local ret=dfhack.internal.patchMemory(off+5,pos,size*2) - if ret then - print("Found and patched:",off+5) - else - print("Patching failed at:",off+5) - end - tmp_val:delete() - else - qerror("Offset for embark patch not found!") - end -end -if WINDOWS then -tools.menu:add("Embark anywhere",tools.embark) -end -function tools.getCreatureId(vector) --redo it to getcreature by name/id or something - tnames={} - rnames={} - --[[print("vector1 size:"..vector:size()) - print("vector2 size:"..vector2:size())]]-- - for i=0,vector:size()-1 do - --print(string.format("%x",vector:getval(i))) - - local name=engine.peek(vector:getval(i),ptt_dfstring):getval() - local lid= tools.getlegendsid(vector:getval(i)) - if lid ~=0 then - print(i..")*Creature Name:"..name.." race="..engine.peekw(vector:getval(i)+ptr_Creature.race.off).." legendid="..lid) - else - print(i..") Creature Name:"..name.." race="..engine.peekw(vector:getval(i)+ptr_Creature.race.off)) - end - if name ~="" and name~=nil then - tnames[i]=name - rnames[name]=i - end - end - print("=====================================") - print("type in name or number:") - r=getline() - if tonumber(r) ==nil then - indx=rnames[r] - if indx==nil then return end - else - r=tonumber(r) - if rLOVE), q to quit:") - names={} - repeat - w=getline(); - - if rwords[w]~=nil then - table.insert(names,w) - print("--added--") - end - - until w=='q' - end - - tnames={} - for _,v in pairs(names) do - if rwords[v] ~=nil then - table.insert(tnames,rwords[v]) --get word numbers - end - end - - local offsites=engine.peekd(offsets.getEx("SiteData"))+0x120 - snames={" pfort"," dfort"," cave","mohall","forest","hamlet","imploc"," lair"," fort"," camp"} - vector=engine.peek(offsites,ptr_vector) - print("Number of sites:"..vector:size()) - print("List of hits:") - for i =0,vector:size()-1 do - off=vector:getval(i) - - good=true - r="" - hits=0 - sname=engine.peek(off,ptr_site.name) - for k=0,6 do - vnum=sname[k]--engine.peekd(off+0x38+k*4) - tgood=false - - if vnum~=0xFFFFFFFF then - --print(string.format("%x",vnum)) - if names[vnum]~=nil then - r=r..names[vnum].." " - end - for _,v in pairs(tnames) do - if vnum==v then - tgood=true - --print("Match") - hits=hits+1 - break - end - end - if not tgood then - good=false - end - end - end - - if(good) and (hits>0)then - --if true then - --print("=====================") - typ=engine.peek(off,ptr_site.type)--engine.peekw(off+0x78) - flg=engine.peekd(engine.peek(off,ptr_site.flagptr)) - --flg=engine.peekd(off+224) - --flg2=engine.peekw(off) - --tv=engine.peek(off+0x84,ptr_vector) - --tv2=engine.peek(off+0xA4,ptr_vector) - - print(string.format("%d)%s off=%x type=%s\t flags=%x",i,r,off,snames[typ+1],flg)) - - if i%100==99 then - r=getline() - end - - end - end - print("Type which to change (q cancels):") - repeat - r=getline() - n=tonumber(r) - if(r=='q') then return end - until n~=nil - return vector:getval(n) -end -function tools.changesite(names) - off=tools.getsite(names) - snames={"Mountain halls (yours)","Dark fort","Cave","Mountain hall (NPC)","Forest retreat","Hamlet","Important location","Lair","Fort","Camp"} - - print("Type in the site type (q cancels):") - for k,v in pairs(snames) do - print((k-1).."->"..v) - end - repeat - r=getline() - n2=tonumber(r) - if(r=='q') then return end - until n2~=nil - --off=vector:getval(n) - print(string.format("%x->%d",off,n2)) - engine.poke(off,ptr_site.type,n2) -end -function tools.project(unit,trg) - if unit==nil then - unit=getCreatureAtPointer() - end - - if unit==nil then - error("Failed to project unit. Unit not selected/valid") - end - -- todo: add projectile to world, point to unit, add flag to unit, add gen-ref to projectile. - local p=df.proj_unitst:new() - local startpos={x=unit.pos.x,y=unit.pos.y,z=unit.pos.z} - p.origin_pos=startpos - p.target_pos=trg - p.cur_pos=startpos - p.prev_pos=startpos - p.unit=unit - --- wtf stuff - p.unk14=100 - p.unk16=-1 - p.unk23=-1 - p.fall_delay=5 - p.fall_counter=5 - p.collided=true - -- end wtf - local citem=df.global.world.proj_list - local maxid=1 - local newlink=df.proj_list_link:new() - newlink.item=p - while citem.item~= nil do - if citem.item.id>maxid then maxid=citem.item.id end - if citem.next ~= nil then - citem=citem.next - else - break - end - end - p.id=maxid+1 - newlink.prev=citem - citem.next=newlink - - local proj_ref=df.general_ref_projectile:new() - proj_ref.projectile_id=p.id - unit.refs:insert(#unit.refs,proj_ref) - unit.flags1.projectile=true -end -function tools.empregnate(unit) - if unit==nil then - unit=getSelectedUnit() - end - - if unit==nil then - unit=getCreatureAtPos(getxyz()) - end - - if unit==nil then - error("Failed to empregnate. Unit not selected/valid") - end - if unit.curse then - unit.curse.add_tags2.STERILE=false - end - local genes = unit.appearance.genes - if unit.relations.pregnancy_ptr == nil then - print("creating preg ptr.") - if false then - print(string.format("%x %x",df.sizeof(unit.relations:_field("pregnancy_ptr")))) - return - end - unit.relations.pregnancy_ptr = { new = true, assign = genes } - end - local ngenes = unit.relations.pregnancy_ptr - if #ngenes.appearance ~= #genes.appearance or #ngenes.colors ~= #genes.colors then - print("Array sizes incorrect, fixing.") - ngenes:assign(genes); - end - print("Setting preg timer.") - unit.relations.pregnancy_timer=10 - unit.relations.pregnancy_mystery=1 -end -tools.menu:add("Empregnate",tools.empregnate) -function tools.changeflags(names) - myflag_pattern=ptt_dfflag.new(3*8) - off=tools.getsite(names) - offflgs=engine.peek(off,ptr_site.flagptr) - q='' - print(string.format("Site offset %x flags offset %x",off,offflgs)) - repeat - print("flags:") - - --off=vector:getval(n) - flg=engine.peek(offflgs,myflag_pattern) - r="" - for i=0,3*8-1 do - if flg:get(i)==1 then - r=r.."x" - else - r=r.."o" - end - if i%8==7 then - print(i-7 .."->"..r) - r="" - end - end - print("Type number to flip, or 'q' to quit.") - q=getline() - n2=tonumber(q) - if n2~=nil then - - flg:flip(n2) - engine.poke(offflgs,myflag_pattern,flg) - end - until q=='q' -end -function tools.hostilate() - vector=engine.peek(offsets.getEx("CreatureVec"),ptr_vector) - id=engine.peekd(offsets.getEx("CreaturePtr")) - print(string.format("Vec:%d cr:%d",vector:size(),id)) - off=vector:getval(id) - crciv=engine.peek(off,ptr_Creature.civ) - print("Creatures civ:"..crciv) - curciv=engine.peekd(offsets.getEx("CurrentRace")-12) - print("My civ:"..curciv) - if curciv==crciv then - print("Friendly-making enemy") - engine.poke(off,ptr_Creature.civ,-1) - flg=engine.peek(off,ptr_Creature.flags) - flg:set(17,0) - print("flag 51:"..tostring(flg:get(51))) - engine.poke(off,ptr_Creature.flags,flg) - else - print("Enemy- making friendly") - engine.poke(off,ptr_Creature.civ,curciv) - flg=engine.peek(off,ptr_Creature.flags) - flg:set(17,1) - flg:set(19,0) - engine.poke(off,ptr_Creature.flags,flg) - end -end -function tools.mouseBlock() - local xs,ys,zs - xs,ys,zs=getxyz() - xs=math.floor(xs/16) - ys=math.floor(ys/16) - print("Mouse block is:"..xs.." "..ys.." "..zs) -end -function tools.fixwarp() - local mapoffset=offsets.getEx("WorldData")--0x131C128+offsets.base() - local x=engine.peek(mapoffset+24,DWORD) - local y=engine.peek(mapoffset+28,DWORD) - local z=engine.peek(mapoffset+32,DWORD) - --vec=engine.peek(mapoffset,ptr_vector) - - print("Blocks loaded:"..x.." "..y.." "..z) - print("Select type:") - print("1. All (SLOW)") - print("2. range (x0 x1 y0 y1 z0 z1)") - print("3. One block around pointer") - print("anything else- quit") - q=getline() - n2=tonumber(q) - if n2==nil then return end - if n2>3 or n2<1 then return end - local xs,xe,ys,ye,zs,ze - if n2==1 then - xs=0 - xe=x-1 - ys=0 - ye=y-1 - zs=0 - ze=z-1 - elseif n2==2 then - print("enter x0:") - xs=tonumber(getline()) - print("enter x1:") - xe=tonumber(getline()) - print("enter y0:") - ys=tonumber(getline()) - print("enter y1:") - ye=tonumber(getline()) - print("enter z0:") - zs=tonumber(getline()) - print("enter z1:") - ze=tonumber(getline()) - function clamp(t,vmin,vmax) - if t> vmax then return vmax end - if t< vmin then return vmin end - return t - end - xs=clamp(xs,0,x-1) - ys=clamp(ys,0,y-1) - zs=clamp(zs,0,z-1) - xe=clamp(xe,xs,x-1) - ye=clamp(ye,ys,y-1) - ze=clamp(ze,zs,z-1) - else - xs,ys,zs=getxyz() - xs=math.floor(xs/16) - ys=math.floor(ys/16) - xe=xs - ye=ys - ze=zs - end - local xblocks=engine.peek(mapoffset,DWORD) - local flg=bit.bnot(bit.lshift(1,3)) - for xx=xs,xe do - local yblocks=engine.peek(xblocks+xx*4,DWORD) - for yy=ys,ye do - local zblocks=engine.peek(yblocks+yy*4,DWORD) - for zz=zs,ze do - local myblock=engine.peek(zblocks+zz*4,DWORD) - if myblock~=0 then - for i=0,255 do - local ff=engine.peek(myblock+0x67c+i*4,DWORD) - ff=bit.band(ff,flg) --set 14 flag to 1 - engine.poke(myblock+0x67c+i*4,DWORD,ff) - end - end - end - print("Blocks done:"..xx.." "..yy) - end - end -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/tools/plugin.lua b/plugins/Dfusion/luafiles/tools/plugin.lua deleted file mode 100644 index 8eaefd44d..000000000 --- a/plugins/Dfusion/luafiles/tools/plugin.lua +++ /dev/null @@ -1,8 +0,0 @@ -if not(FILE) then - --tools.menu:add("Change site type",tools.changesite) - --tools.menu:add("Change site flags",tools.changeflags) - --tools.menu:add("Hostilate creature",tools.hostilate) - --tools.menu:add("Print current mouse block",tools.mouseBlock) - - tools.menu:display() -end \ No newline at end of file diff --git a/plugins/Dfusion/luafiles/utils.lua b/plugins/Dfusion/luafiles/utils.lua deleted file mode 100644 index d481f5cfc..000000000 --- a/plugins/Dfusion/luafiles/utils.lua +++ /dev/null @@ -1 +0,0 @@ -function findVectorsSized(size) local ret={} local text=GetTextRegion() for k,v in pairs(offsets.getvectors()) do if GetRegionIn2(k)~=nil then --if v>4 then local tv=engine.peek(k,ptr_vector) if tv:size() == size then print(string.format("%x is size %d",k,size)) table.insert(ret,k) end end end return ret end function findMaterial(mattype,matname) --currently only stones local tbl=BuildMaterialTable() return tbl[matname] end function iter(tbl) if getmetatable(tbl) ~=nil then if getmetatable(tbl).__next~= nil then return getmetatable(tbl).__next,tbl else return getmetatable(tbl).__pairs(tbl) or pairs(tbl) end else return pairs(tbl) end end \ No newline at end of file diff --git a/plugins/lua/dfusion/embark.lua b/plugins/lua/dfusion/embark.lua index 578c0b903..6e5df4eef 100644 --- a/plugins/lua/dfusion/embark.lua +++ b/plugins/lua/dfusion/embark.lua @@ -3,22 +3,34 @@ local dfu=require("plugins.dfusion") local ms=require("memscan") local MAX_RACES=100 CustomEmbark=defclass(CustomEmbark,dfu.BinaryPlugin) -CustomEmbark.name="CustomEmbark" - -print(t) local myos=dfhack.getOSType() if myos=="windows" then CustomEmbark.ATTRS{filename="hack/lua/plugins/dfusion/embark.o",name="CustomEmbark",race_caste_data=DEFAULT_NIL} - CustomEmbark.class_status="valid, not installed" - function CustomEmbark:install() - local stoff=dfhack.internal.getAddress('start_dwarf_count') - - if #self.race_caste_data<7 then + function CustomEmbark:parseRaces(races) + if #races<7 then error("caste and race count must be bigger than 6") end - if #self.race_caste_data>MAX_RACES then + if #races>MAX_RACES then error("caste and race count must be less then "..MAX_RACES) end + local n_to_id=require("plugins.dfusion.tools").build_race_names() + + local ids={} + for k,v in pairs(races) do + local race=v[1] or v + ids[k]={} + ids[k][1]=n_to_id[race] + if ids[k][1]==nil then qerror(race.." not found!") end + ids[k][2]=v[2] or -1 + end + self.race_caste_data=ids + end + function CustomEmbark:install(race_caste_data) + local stoff=dfhack.internal.getAddress('start_dwarf_count') + if race_caste_data~=nil then + self:parseRaces(race_caste_data) + end + if stoff==nil then error("address for start_dwarf_count not found!") end @@ -40,14 +52,14 @@ if myos=="windows" then local call_data={0x90,0x90} local _,data_offset=df.sizeof(self.data) dfu.concatTables(call_data,dfu.makeCall(trg_offset+2,data_offset)) - self.call_patch=dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} + self.call_patch=self.call_patch or dfu.BinaryPatch{pre_data=needle,data=call_data,address=trg_offset,name="custom_embark_call_patch"} needle={0x83,0xc8,0xff} -- or eax, 0xFF local _,caste_offset=mem.uint8_t:find(needle,mem.uint8_t:addr2idx(trg_offset),nil) if caste_offset==nil or caste_offset-stoff>1000 then error("Caste change code not found or found too far!") end - self.disable_castes=dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} + self.disable_castes=self.disable_castes or dfu.BinaryPatch{pre_data={0x83,0xc8,0xff},data={0x90,0x90,0x90},address=caste_offset,name="custom_embark_caste_disable"} self.disable_castes:apply() @@ -66,16 +78,11 @@ if myos=="windows" then self.call_patch:apply() self.installed=true end + function CustomEmbark:setEmbarkParty(racesAndCastes) local stoff=dfhack.internal.getAddress('start_dwarf_count') - if #racesAndCastes<7 then - error("caste and race count must be bigger than 6") - end - if #racesAndCastes>MAX_RACES then - error("caste and race count must be less then "..MAX_RACES) - end - self.race_caste_data=racesAndCastes + if self.dwarfcount== nil then self.dwarfcount=dfu.BinaryPatch{pre_data=dfu.dwordToTable(7),data=dfu.dwordToTable(#self.race_caste_data),address=stoff,name="custom_embark_embarkcount"} self.dwarfcount:apply() @@ -84,8 +91,9 @@ if myos=="windows" then end local caste_array=self:get_or_alloc("caste_array","uint16_t",MAX_RACES) local race_array=self:get_or_alloc("race_array","uint16_t",MAX_RACES) + for k,v in ipairs(self.race_caste_data) do - caste_array[k-1]=v[2] + caste_array[k-1]=v[2] or -1 race_array[k-1]=v[1] end end @@ -103,48 +111,14 @@ if myos=="windows" then self.dwarfcount:remove() end end - function CustomEmbark:edit() - local data=self.race_caste_data or {} - print(string.format("Old race count:%d",#data)) - local endthis=false - print("current:") - while(not endthis) do - print(" # RaceId Race name Caste num") - for k,v in pairs(data) do - local name=df.creature_raw.find(v[1]).creature_id or "" - print(string.format("%3d. %6d %20s %d",k,v[1],name,v[2])) - end - print("a- add, r-remove, c-cancel, s-save and update") - local choice=io.stdin:read() - if choice=='a' then - print("Enter new race then caste ids:") - local race=tonumber(io.stdin:read()) - local caste=tonumber(io.stdin:read()) - if race and caste then - table.insert(data,{race,caste}) - else - print("input parse error") - end - elseif choice=='r' then - print("enter number to remove:") - local num_rem=tonumber(io.stdin:read()) - if num_rem~=nil then - table.remove(data,num_rem) - end - elseif choice=='c' then - endthis=true - elseif choice=='s' then - endthis=true - if self.installed then - self:setEmbarkParty(data) - else - self.race_caste_data=data - self:install() - end - end + function CustomEmbark:unload() + self:uninstall() + if Embark~=nil then + Embark=nil end end + Embark=Embark or CustomEmbark() else - CustomEmbark.class_status="invalid, os not supported" + CustomEmbark.status=function() return"invalid, os not supported" end end return _ENV \ No newline at end of file diff --git a/plugins/lua/dfusion/friendship.lua b/plugins/lua/dfusion/friendship.lua index 70a1ceea4..93a370c86 100644 --- a/plugins/lua/dfusion/friendship.lua +++ b/plugins/lua/dfusion/friendship.lua @@ -35,7 +35,7 @@ function FriendshipRainbow:find_all() dfu.concatTables(locations,self:find_one(code,{0x0f,0xbf,reg},crace)) --movsx reg,[ptr] dfu.concatTables(locations,self:find_one(code,{0x66,0x8b,reg},crace)) --mov reg,[ptr] end - printall(locations) + return self:filter_locations(code,locations) end function FriendshipRainbow:filter_locations(codesg,locations) @@ -107,5 +107,6 @@ function FriendshipRainbow:install(races) local addr=self:move_to_df() self:patchCalls(addr) self.installed=true - end +end +Friendship=Friendship or FriendshipRainbow() return _ENV \ No newline at end of file From 856c9ebd4b7bfb09a57576cb73763c1ead00baa1 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Nov 2012 12:39:49 +0200 Subject: [PATCH 154/472] Added save specific scripts to lua interpreter script, also better error reporting. --- scripts/lua.lua | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/lua.lua b/scripts/lua.lua index 497498e86..556962347 100644 --- a/scripts/lua.lua +++ b/scripts/lua.lua @@ -1,9 +1,26 @@ local args={...} if args[1]=="--file" or args[1]=="-f" then - local f=loadfile (args[2]) + local f,err=loadfile (args[2]) + if f==nil then + qerror(err) + end + dfhack.pcall(f,table.unpack(args,3)) +elseif args[1]=="--save" or args[1]=="-s" then + if df.global.world.cur_savegame.save_dir=="" then + qerror("Savefile not loaded") + end + local fname=args[2] or "dfhack.lua" + fname=string.format("data/save/%s/%s",df.global.world.cur_savegame.save_dir,fname) + local f,err=loadfile (fname) + if f==nil then + qerror(err) + end dfhack.pcall(f,table.unpack(args,3)) elseif args[1]~=nil then - local f=load(args[1],'=(lua command)', 't') + local f,err=load(args[1],'=(lua command)', 't') + if f==nil then + qerror(err) + end dfhack.pcall(f,table.unpack(args,2)) else dfhack.interpreter("lua","lua.history") From d5c31942b50accc840026182100c48928d76641f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 11 Nov 2012 10:58:05 +0400 Subject: [PATCH 155/472] Add a way to only count locally-made items in workflow. --- Readme.html | 661 ++++++++++++++++++++++----------------- Readme.rst | 82 ++++- dfhack.init-example | 23 +- plugins/lua/workflow.lua | 11 +- plugins/workflow.cpp | 95 ++++-- scripts/gui/workflow.lua | 11 +- 6 files changed, 550 insertions(+), 333 deletions(-) diff --git a/Readme.html b/Readme.html index cd073459c..16f3aed5a 100644 --- a/Readme.html +++ b/Readme.html @@ -337,197 +337,201 @@ access DF memory and allow for easier development of new tools.

                                            • Introduction
                                            • Getting DFHack
                                            • Compatibility
                                            • -
                                            • Installation/Removal
                                            • -
                                            • Using DFHack
                                            • -

                                              Using DFHack

                                              +

                                              Using DFHack

                                              DFHack basically extends what DF can do with something similar to the drop-down console found in Quake engine games. On Windows, this is a separate command line window. On linux, the terminal used to launch the dfhack script is taken over @@ -595,7 +619,7 @@ 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

                                              +

                                              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:

                                              @@ -624,7 +648,7 @@ system console:

                                              -

                                              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. @@ -633,13 +657,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.

                                              @@ -684,7 +708,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.

                                              @@ -706,13 +730,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.

                                              @@ -723,12 +747,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.

                                                @@ -745,29 +769,29 @@ that implements an even more aggressive version of speedydwarf.
                                              -

                                              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:

                                              @@ -801,9 +825,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.

                                              @@ -816,7 +840,7 @@ properly.

                                              -

                                              advtools

                                              +

                                              advtools

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

                                              Usage:

                                              @@ -839,9 +863,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 @@ -916,7 +940,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.

                                              @@ -929,7 +953,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 @@ -969,7 +993,7 @@ crafters/haulers.

                                              -

                                              colonies

                                              +

                                              colonies

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

                                              Options:

                                              @@ -984,12 +1008,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 @@ -1014,7 +1038,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 @@ -1027,13 +1051,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 @@ -1094,27 +1118,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:

                                              @@ -1134,20 +1158,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:

                                              @@ -1168,9 +1192,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. @@ -1225,17 +1249,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:

                                              @@ -1254,7 +1278,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.

                                              @@ -1279,7 +1303,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 @@ -1288,34 +1312,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:

                                              @@ -1363,17 +1387,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 @@ -1381,11 +1405,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.

                                              @@ -1448,7 +1472,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:

                                              @@ -1511,7 +1535,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:

                                            @@ -1539,7 +1563,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:

                                            @@ -1578,7 +1602,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:

                                            @@ -1602,7 +1626,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.

                                            @@ -1629,9 +1653,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.

                                            @@ -1665,12 +1689,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. @@ -1697,17 +1721,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:

                                            @@ -1741,13 +1765,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 @@ -1756,19 +1780,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:

                                            @@ -1855,7 +1879,7 @@ to make them stand out more in the list.
                                            -

                                            fix-armory

                                            +

                                            fix-armory

                                            Enables a fix for storage of squad equipment in barracks.

                                            Specifically, it prevents your haulers from moving that equipment to stockpiles, and instead queues jobs to store it on weapon racks, @@ -1908,9 +1932,9 @@ these rules is intended by Toady; the rest are invented by this plugin.

                                            -

                                            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 @@ -1930,7 +1954,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.

                                            @@ -1950,9 +1974,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).

                                            @@ -1965,19 +1989,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:
                                            @@ -1996,7 +2020,7 @@ in a workshop, or the unit/jobs screen.
                                            -

                                            job-material

                                            +

                                            job-material

                                            Alter the material of the selected job.

                                            Invoked as:

                                            @@ -2014,7 +2038,7 @@ over the first available choice with the matching material.
                                          • -

                                            job-duplicate

                                            +

                                            job-duplicate

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

                                            workflow

                                            +

                                            workflow

                                            Manage control of repeat jobs.

                                            Usage:

                                            @@ -2042,14 +2066,22 @@ Otherwise, enables or disables any of the following options:

                                            List workflow-controlled jobs (if in a workshop, filtered by it).
                                            workflow list
                                            List active constraints, and their job counts.
                                            -
                                            workflow count <constraint-spec> <cnt-limit> [cnt-gap], workflow amount <constraint-spec> <cnt-limit> [cnt-gap]
                                            -
                                            Set a constraint. The first form counts each stack as only 1 item.
                                            +
                                            workflow list-commands
                                            +
                                            List active constraints as workflow commands that re-create them; +this list can be copied to a file, and then reloaded using the +script built-in command.
                                            +
                                            workflow count <constraint-spec> <cnt-limit> [cnt-gap]
                                            +
                                            Set a constraint, counting every stack as 1 item.
                                            +
                                            workflow amount <constraint-spec> <cnt-limit> [cnt-gap]
                                            +
                                            Set a constraint, counting all items within stacks.
                                            workflow unlimit <constraint-spec>
                                            Delete a constraint.
                                            +
                                            workflow unlimit-all
                                            +
                                            Delete all constraints.
                                            -

                                            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.

                                            @@ -2061,8 +2093,37 @@ the frequency of jobs being toggled.

                                            Check out the gui/workflow script below for a simple front-end integrated in the game UI.

                                            +
                                            +

                                            Constraint format

                                            +

                                            The contstraint spec consists of 4 parts, separated with '/' characters:

                                            +
                                            +ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
                                            +
                                            +

                                            The first part is mandatory and specifies the item type and subtype, +using the raw tokens for items, in the same syntax you would e.g. use +for a custom reaction input. See this list for more info: http://dwarffortresswiki.org/index.php/Item_token

                                            +

                                            The subsequent parts are optional:

                                            +
                                              +
                                            • A generic material spec constrains the item material to one of +the hard-coded generic classes, which currently include:

                                              +
                                              +PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN
                                              +METAL STONE SAND GLASS CLAY MILK
                                              +
                                              +
                                            • +
                                            • A specific material spec chooses the material exactly, using the +raw syntax for reaction input materials, e.g. INORGANIC:IRON, +although for convenience it also allows just IRON, or ACACIA:WOOD etc. +See this page for more details on the unabbreviated raw syntax:

                                              +

                                              http://dwarffortresswiki.org/index.php/Material_token

                                              +
                                            • +
                                            • A comma-separated list of miscellaneous flags, which currently can +be used to ignore imported items or items below a certain quality.

                                              +
                                            • +
                                            +
                                            -

                                            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
                                            @@ -2084,6 +2145,10 @@ workflow count BOX/CLOTH,SILK,YARN 30
                                             workflow count BAR//COAL 20
                                             workflow count BAR//COPPER 30
                                             
                                            +

                                            Produce 15-20 gold crafts.

                                            +
                                            +workflow count CRAFTS//GOLD 20
                                            +

                                            Collect 15-20 sand bags and clay boulders.

                                             workflow count POWDER_MISC/SAND 20
                                            @@ -2091,25 +2156,31 @@ workflow count BOULDER/CLAY 20
                                             

                                            Make sure there are always 80-100 units of dimple dye.

                                            -  workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
                                            -
                                            -In order for this to work, you have to set the material of the PLANT input
                                            +workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20
                                            +
                                            +
                                            +

                                            Note

                                            +

                                            In order for this to work, you have to set the material of the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' -command. +command. Otherwise the plugin won't be able to deduce the output material.

                                            +
                                            +

                                            Maintain 10-100 locally-made crafts of exceptional quality.

                                            +
                                            +workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
                                             
                                            -

                                            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:

                                            @@ -2208,7 +2279,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 @@ -2217,7 +2288,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. @@ -2235,14 +2306,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 @@ -2253,7 +2324,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 @@ -2279,7 +2350,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 @@ -2308,7 +2379,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 @@ -2416,7 +2487,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 @@ -2430,14 +2501,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:

                                            @@ -2459,7 +2530,7 @@ twice.

                                            -

                                            misery

                                            +

                                            misery

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

                                            Usage:

                                            @@ -2483,7 +2554,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.

                                            @@ -2492,7 +2563,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

                                              @@ -2518,22 +2589,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 @@ -2541,7 +2612,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. @@ -2553,7 +2624,7 @@ growcrops plump 40

                                            -

                                            removebadthoughts

                                            +

                                            removebadthoughts

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

                                            The script can target a single creature, when used with the him argument, @@ -2567,7 +2638,7 @@ but in the short term your dwarves will get much more joyful.

                                            quickly after you unpause.

                                            -

                                            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.

                                            @@ -2593,7 +2664,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.

                                            @@ -2608,7 +2679,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:

                                            @@ -2626,7 +2697,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:

                                            @@ -2636,17 +2707,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.

                                            @@ -2657,7 +2728,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 @@ -2693,7 +2764,7 @@ 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.

                                            @@ -2727,7 +2798,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.

                                              @@ -2743,14 +2814,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 @@ -2760,13 +2831,13 @@ only that entry, and does it even if it is not 'individual choice'.

                                            and may lead to inappropriate weapons being selected.

                                            -

                                            gui/guide-path

                                            +

                                            gui/guide-path

                                            Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                            The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

                                            -

                                            gui/workshop-job

                                            +

                                            gui/workshop-job

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

                                            @@ -2794,7 +2865,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                            -

                                            gui/workflow

                                            +

                                            gui/workflow

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2816,7 +2887,7 @@ as described in workflow documentation above. can be used for troubleshooting jobs that don't match the right constraints.

                                            -

                                            gui/assign-rack

                                            +

                                            gui/assign-rack

                                            Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                            This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

                                            @@ -2836,7 +2907,7 @@ the intended user.

                                            -

                                            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.

                                            @@ -2847,20 +2918,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 @@ -2881,7 +2952,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 @@ -2890,11 +2961,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 @@ -2905,7 +2976,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.

                                            @@ -2929,7 +3000,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 @@ -2960,7 +3031,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 @@ -2969,7 +3040,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 @@ -2980,7 +3051,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 d9021c7cb..7fbc42528 100644 --- a/Readme.rst +++ b/Readme.rst @@ -58,9 +58,35 @@ The stonesense plugin might require some additional libraries on Linux. If any of the plugins or dfhack itself refuses to load, check the stderr.log file created in your DF folder. +Getting started +=============== + +If DFHack is installed correctly, it will automatically pop up a console +window once DF is started as usual on windows. Linux and Mac OS X require +running the dfhack script from the terminal, and will use that terminal for +the console. + +**NOTE**: The dfhack-run executable is there for calling DFHack commands in +an already running DF+DFHack instance from external OS scripts and programs, +and is *not* the way how you use DFHack normally. + +DFHack has a lot of features, which can be accessed by typing commands in the +console, or by mapping them to keyboard shortcuts. Most of the newer and more +user-friendly tools are designed to be at least partially used via the latter +way. + +In order to set keybindings, you have to create a text configuration file +called ``dfhack.init``; the installation comes with an example version called +``dfhack.init-example``, which is fully functional, covers all of the recent +features and can be simply renamed to ``dfhack.init``. You are encouraged to look +through it to learn which features it makes available under which key combinations. + +For more information, refer to the rest of this document. + ============ Using DFHack ============ + DFHack basically extends what DF can do with something similar to the drop-down console found in Quake engine games. On Windows, this is a separate command line window. On linux, the terminal used to launch the dfhack script is taken over @@ -1258,10 +1284,18 @@ Usage: List workflow-controlled jobs (if in a workshop, filtered by it). ``workflow list`` List active constraints, and their job counts. - ``workflow count [cnt-gap], workflow amount [cnt-gap]`` - Set a constraint. The first form counts each stack as only 1 item. + ``workflow list-commands`` + List active constraints as workflow commands that re-create them; + this list can be copied to a file, and then reloaded using the + ``script`` built-in command. + ``workflow count [cnt-gap]`` + Set a constraint, counting every stack as 1 item. + ``workflow amount [cnt-gap]`` + Set a constraint, counting all items within stacks. ``workflow unlimit `` Delete a constraint. + ``workflow unlimit-all`` + Delete all constraints. Function ........ @@ -1279,6 +1313,34 @@ the frequency of jobs being toggled. Check out the ``gui/workflow`` script below for a simple front-end integrated in the game UI. +Constraint format +................. + +The contstraint spec consists of 4 parts, separated with '/' characters:: + + ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,] + +The first part is mandatory and specifies the item type and subtype, +using the raw tokens for items, in the same syntax you would e.g. use +for a custom reaction input. See this list for more info: http://dwarffortresswiki.org/index.php/Item_token + +The subsequent parts are optional: + +- A generic material spec constrains the item material to one of + the hard-coded generic classes, which currently include:: + + PLANT WOOD CLOTH SILK LEATHER BONE SHELL SOAP TOOTH HORN PEARL YARN + METAL STONE SAND GLASS CLAY MILK + +- A specific material spec chooses the material exactly, using the + raw syntax for reaction input materials, e.g. INORGANIC:IRON, + although for convenience it also allows just IRON, or ACACIA:WOOD etc. + See this page for more details on the unabbreviated raw syntax: + + http://dwarffortresswiki.org/index.php/Material_token + +- A comma-separated list of miscellaneous flags, which currently can + be used to ignore imported items or items below a certain quality. Constraint examples ................... @@ -1304,10 +1366,15 @@ Make sure there are always 25-30 empty bins/barrels/bags. Make sure there are always 15-20 coal and 25-30 copper bars. :: - + workflow count BAR//COAL 20 workflow count BAR//COPPER 30 +Produce 15-20 gold crafts. +:: + + workflow count CRAFTS//GOLD 20 + Collect 15-20 sand bags and clay boulders. :: @@ -1319,9 +1386,16 @@ Make sure there are always 80-100 units of dimple dye. workflow amount POWDER_MISC//MUSHROOM_CUP_DIMPLE:MILL 100 20 +.. note:: + In order for this to work, you have to set the material of the PLANT input on the Mill Plants job to MUSHROOM_CUP_DIMPLE using the 'job item-material' - command. + command. Otherwise the plugin won't be able to deduce the output material. + +Maintain 10-100 locally-made crafts of exceptional quality. +:: + + workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90 Fortress activity management diff --git a/dfhack.init-example b/dfhack.init-example index 20048e39e..729747714 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -2,21 +2,32 @@ # Generic dwarfmode bindings # ############################## +# toggle the display of water level as 1-7 tiles keybinding add Ctrl-W twaterlvl # with cursor: + +# designate the whole vein for digging keybinding add Ctrl-V digv keybinding add Ctrl-Shift-V "digv x" + +# clean the selected tile of blood etc keybinding add Ctrl-C spotclean + +# destroy items designated for dump in the selected tile keybinding add Ctrl-Shift-K autodump-destroy-here -# any item: +# with an item selected: + +# destroy the selected item keybinding add Ctrl-K autodump-destroy-item +# scripts: + # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave -# gui/rename script +# gui/rename script - rename units and buildings keybinding add Ctrl-Shift-N gui/rename keybinding add Ctrl-Shift-T "gui/rename unit-profession" @@ -31,10 +42,10 @@ keybinding add Ctrl-Shift-B "adv-bodyswap force" # Context-specific bindings # ############################# -# q->stockpile; p +# q->stockpile; p - copy & paste stockpiles keybinding add Alt-P copystock -# q->workshop +# q->workshop - duplicate the selected job keybinding add Ctrl-D job-duplicate # materials: q->workshop; b->select items @@ -48,7 +59,7 @@ keybinding add Shift-O "job-material OBSIDIAN" keybinding add Shift-T "job-material ORTHOCLASE" keybinding add Shift-G "job-material GLASS_GREEN" -# sort units and items +# sort units and items in the on-screen list keybinding add Alt-Shift-N "sort-units name" "sort-items description" keybinding add Alt-Shift-R "sort-units arrival" keybinding add Alt-Shift-T "sort-units profession" "sort-items type material" @@ -60,7 +71,7 @@ keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms # browse rooms of same owner keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list -# interface for the liquids plugin +# interface for the liquids plugin - spawn water/magma/obsidian keybinding add Alt-L@dwarfmode/LookAround gui/liquids # machine power sensitive pressure plate construction diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index c3dbe20d9..e3fb7b32e 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -266,9 +266,16 @@ function constraintToToken(cspec) error('invalid material: '..cspec.mat_type..':'..(cspec.mat_index or -1)) end end - local qpart + local qlist = {} + if cspec.is_local then + table.insert(qlist, "LOCAL") + end if cspec.quality and cspec.quality > 0 then - qpart = df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality) + table.insert(qlist, df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality)) + end + local qpart + if #qlist > 0 then + qpart = table.concat(qlist, ',') end if mask_part or mat_part or qpart then diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index c89d87333..04e3c13b0 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -100,6 +100,19 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector ]\n" + " The first part is mandatory and specifies the item type and subtype,\n" + " using the raw tokens for items, in the same syntax you would e.g. use\n" + " for a custom reaction input. The subsequent parts are optional:\n" + " - A generic material spec constrains the item material to one of\n" + " the hard-coded generic classes, like WOOD, METAL, YARN or MILK.\n" + " - A specific material spec chooses the material exactly, using the\n" + " raw syntax for reaction input materials, e.g. INORGANIC:IRON,\n" + " although for convenience it also allows just IRON, or ACACIA:WOOD.\n" + " - A comma-separated list of miscellaneous flags, which currently can\n" + " be used to ignore imported items or items below a certain quality.\n" "Constraint examples:\n" " workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100\n" " workflow amount AMMO:ITEM_AMMO_BOLTS/WOOD,BONE 200 50\n" @@ -124,6 +137,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector , bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; + // Fixed key parsed into fields bool is_craft; ItemTypeInfo item; MaterialInfo material; df::dfhack_material_category mat_mask; + item_quality::item_quality min_quality; + bool is_local; + + // Tracking data int weight; std::vector jobs; - item_quality::item_quality min_quality; - int item_amount, item_count, item_inuse; bool request_suspend, request_resume; @@ -299,8 +317,9 @@ struct ItemConstraint { public: ItemConstraint() - : is_craft(false), weight(0), min_quality(item_quality::Ordinary),item_amount(0), - item_count(0), item_inuse(0), is_active(false), cant_resume_reported(false) + : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), + weight(0), item_amount(0), item_count(0), item_inuse(0), + is_active(false), cant_resume_reported(false) {} int goalCount() { return config.ival(0); } @@ -673,7 +692,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (mat_mask.whole != 0) weight += 100; - + MaterialInfo material; std::string matstr = vector_get(tokens,2); if (!matstr.empty() && (!material.find(matstr) || !material.isValid())) { @@ -681,21 +700,6 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return NULL; } - item_quality::item_quality minqual = item_quality::Ordinary; - std::string qualstr = vector_get(tokens, 3); - if(!qualstr.empty()) { - if(qualstr == "ordinary") minqual = item_quality::Ordinary; - else if(qualstr == "wellcrafted") minqual = item_quality::WellCrafted; - else if(qualstr == "finelycrafted") minqual = item_quality::FinelyCrafted; - else if(qualstr == "superior") minqual = item_quality::Superior; - else if(qualstr == "exceptional") minqual = item_quality::Exceptional; - else if(qualstr == "masterful") minqual = item_quality::Masterful; - else { - out.printerr("Cannot find quality: %s\nKnown qualities: ordinary, wellcrafted, finelycrafted, superior, exceptional, masterful\n", qualstr.c_str()); - return NULL; - } - } - if (material.type >= 0) weight += (material.index >= 0 ? 5000 : 1000); @@ -704,13 +708,52 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return NULL; } + item_quality::item_quality minqual = item_quality::Ordinary; + bool is_local = false; + std::string qualstr = vector_get(tokens, 3); + + if(!qualstr.empty()) + { + std::vector qtokens; + split_string(&qtokens, qualstr, ","); + + for (size_t i = 0; i < qtokens.size(); i++) + { + auto token = toLower(qtokens[i]); + + if (token == "local") + is_local = true; + else + { + bool found = false; + FOR_ENUM_ITEMS(item_quality, qv) + { + if (toLower(ENUM_KEY_STR(item_quality, qv)) != token) + continue; + minqual = qv; + found = true; + } + + if (!found) + { + out.printerr("Cannot parse token: %s\n", token.c_str()); + return NULL; + } + } + } + } + + if (is_local || minqual > item_quality::Ordinary) + weight += 10; + for (size_t i = 0; i < constraints.size(); i++) { ItemConstraint *ct = constraints[i]; if (ct->is_craft == is_craft && ct->item == item && ct->material == material && ct->mat_mask.whole == mat_mask.whole && - ct->min_quality == minqual) + ct->min_quality == minqual && + ct->is_local == is_local) return ct; } @@ -720,6 +763,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->material = material; nct->mat_mask = mat_mask; nct->min_quality = minqual; + nct->is_local = is_local; nct->weight = weight; if (cfg) @@ -1099,9 +1143,11 @@ static void map_job_items(color_ostream &out) (cv->item.subtype != -1 && cv->item.subtype != isubtype)) continue; } - if(item->getQuality() < cv->min_quality) { - continue; - } + + if (cv->is_local && item->flags.bits.foreign) + continue; + if (item->getQuality() < cv->min_quality) + continue; TMaterialCache::iterator it = cv->material_cache.find(matkey); @@ -1307,6 +1353,7 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) Lua::SetField(L, cv->material.index, ctable, "mat_index"); Lua::SetField(L, (int)cv->min_quality, ctable, "min_quality"); + Lua::SetField(L, (bool)cv->is_local, ctable, "is_local"); // Constraint value diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 366e3ec91..84540b5ca 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -191,8 +191,15 @@ function JobConstraints:initListChoices(clist, sel_token) order_pen = COLOR_BLUE end local itemstr = describe_item_type(cons) - if cons.min_quality > 0 then - itemstr = itemstr .. ' ('..df.item_quality[cons.min_quality]..')' + if cons.min_quality > 0 or cons.is_local then + local lst = {} + if cons.is_local then + table.insert(lst, 'local') + end + if cons.min_quality > 0 then + table.insert(lst, string.lower(df.item_quality[cons.min_quality])) + end + itemstr = itemstr .. ' ('..table.concat(lst,',')..')' end local matstr = describe_material(cons) local matflagstr = '' From f657c20a1da4b5d6ce0ae95d60c5f2921c68ebf9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 11 Nov 2012 15:49:01 +0400 Subject: [PATCH 156/472] Add an internal API for converting between file and memory offsets. --- Lua API.html | 8 +++++++ Lua API.rst | 10 +++++++++ library/LuaApi.cpp | 15 +++++++++++++ library/Process-darwin.cpp | 9 ++++++-- library/Process-linux.cpp | 9 ++++++-- library/Process-windows.cpp | 39 +++++++++++++++++++++++++++++++--- library/VersionInfoFactory.cpp | 4 ++-- library/include/MemAccess.h | 4 +++- 8 files changed, 88 insertions(+), 10 deletions(-) diff --git a/Lua API.html b/Lua API.html index 226afa98a..d2e0da1ef 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1763,9 +1763,17 @@ global environment, persistent between calls to the script.

                                          • dfhack.internal.getVTable(name)

                                            Returns the pre-extracted vtable address name, or nil.

                                          • +
                                          • dfhack.internal.getImageBase()

                                            +

                                            Returns the mmap base of the executable.

                                            +
                                          • dfhack.internal.getRebaseDelta()

                                            Returns the ASLR rebase offset of the DF executable.

                                          • +
                                          • dfhack.internal.adjustOffset(offset[,to_file])

                                            +

                                            Returns the re-aligned offset, or nil if invalid. +If to_file is true, the offset is adjusted from memory to file. +This function returns the original value everywhere except windows.

                                            +
                                          • dfhack.internal.getMemRanges()

                                            Returns a sequence of tables describing virtual memory ranges of the process.

                                          • diff --git a/Lua API.rst b/Lua API.rst index d06e3d2e6..f89750e88 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1618,10 +1618,20 @@ and are only documented here for completeness: Returns the pre-extracted vtable address ``name``, or *nil*. +* ``dfhack.internal.getImageBase()`` + + Returns the mmap base of the executable. + * ``dfhack.internal.getRebaseDelta()`` Returns the ASLR rebase offset of the DF executable. +* ``dfhack.internal.adjustOffset(offset[,to_file])`` + + Returns the re-aligned offset, or *nil* if invalid. + If ``to_file`` is true, the offset is adjusted from memory to file. + This function returns the original value everywhere except windows. + * ``dfhack.internal.getMemRanges()`` Returns a sequence of tables describing virtual memory ranges of the process. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0151ed404..3862530dd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1718,9 +1718,11 @@ static void *checkaddr(lua_State *L, int idx, bool allow_null = false) return rv; } +static uint32_t getImageBase() { return Core::getInstance().p->getBase(); } static int getRebaseDelta() { return Core::getInstance().vinfo->getRebaseDelta(); } static const LuaWrapper::FunctionReg dfhack_internal_module[] = { + WRAP(getImageBase), WRAP(getRebaseDelta), { NULL, NULL } }; @@ -1774,6 +1776,18 @@ static int internal_getVTable(lua_State *L) return 1; } +static int internal_adjustOffset(lua_State *L) +{ + lua_settop(L, 2); + int off = luaL_checkint(L, 1); + int rv = Core::getInstance().p->adjustOffset(off, lua_toboolean(L, 2)); + if (rv >= 0) + lua_pushinteger(L, rv); + else + lua_pushnil(L); + return 1; +} + static int internal_getMemRanges(lua_State *L) { std::vector ranges; @@ -1981,6 +1995,7 @@ static const luaL_Reg dfhack_internal_funcs[] = { { "getAddress", internal_getAddress }, { "setAddress", internal_setAddress }, { "getVTable", internal_getVTable }, + { "adjustOffset", internal_adjustOffset }, { "getMemRanges", internal_getMemRanges }, { "patchMemory", internal_patchMemory }, { "patchBytes", internal_patchBytes }, diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index c5e4e4b85..d081c8c5c 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -220,9 +220,14 @@ void Process::getMemRanges( vector & ranges ) }*/ } -uint32_t Process::getBase() +uintptr_t Process::getBase() { - return 0; + return 0x1000000; +} + +int Process::adjustOffset(int offset, bool /*to_file*/) +{ + return offset; } static int getdir (string dir, vector &files) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index f88279b3f..046b7696d 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -155,9 +155,14 @@ void Process::getMemRanges( vector & ranges ) fclose(mapFile); } -uint32_t Process::getBase() +uintptr_t Process::getBase() { - return 0; + return 0x8048000; +} + +int Process::adjustOffset(int offset, bool /*to_file*/) +{ + return offset; } static int getdir (string dir, vector &files) diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 966468fb5..6f79236f9 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -160,7 +160,7 @@ Process::Process(VersionInfoFactory * factory) identified = true; // give the process a data model and memory layout fixed for the base of first module my_descriptor = new VersionInfo(*vinfo); - my_descriptor->rebaseTo((uint32_t)d->base); + my_descriptor->rebaseTo(getBase()); for(size_t i = 0; i < threads_ids.size();i++) { HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, (DWORD) threads_ids[i]); @@ -394,13 +394,46 @@ void Process::getMemRanges( vector & ranges ) } } -uint32_t Process::getBase() +uintptr_t Process::getBase() { if(d) - return (uint32_t) d->base; + return (uintptr_t) d->base; return 0x400000; } +int Process::adjustOffset(int offset, bool to_file) +{ + if (!d) + return -1; + + for(int i = 0; i < d->pe_header.FileHeader.NumberOfSections; i++) + { + auto §ion = d->sections[i]; + + if (to_file) + { + unsigned delta = offset - section.VirtualAddress; + if (delta >= section.Misc.VirtualSize) + continue; + if (!section.PointerToRawData || delta >= section.SizeOfRawData) + return -1; + return (int)(section.PointerToRawData + delta); + } + else + { + unsigned delta = offset - section.PointerToRawData; + if (!section.PointerToRawData || delta >= section.SizeOfRawData) + continue; + if (delta >= section.Misc.VirtualSize) + return -1; + return (int)(section.VirtualAddress + delta); + } + } + + return -1; +} + + string Process::doReadClassName (void * vptr) { char * rtti = readPtr((char *)vptr - 0x4); diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index e8c0561cd..7142233d8 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -103,13 +103,13 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem) { mem->setOS(OS_LINUX); // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x0); + mem->setBase(0x8048000); } else if(os == "darwin") { mem->setOS(OS_APPLE); // this is wrong... I'm not going to do base image relocation on linux though. - mem->setBase(0x0); + mem->setBase(0x1000000); } else { diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 1b8e687b9..22f15eecf 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -275,11 +275,13 @@ namespace DFHack { return my_descriptor; }; - uint32_t getBase(); + uintptr_t getBase(); /// get the DF Process ID int getPID(); /// get the DF Process FilePath std::string getPath(); + /// Adjust between in-memory and in-file image offset + int adjustOffset(int offset, bool to_file = false); /// millisecond tick count, exactly as DF uses uint32_t getTickCount(); From 012d22fa4f8c6562ed9fc9e11b6059942fa5e8c8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 11 Nov 2012 17:24:13 +0400 Subject: [PATCH 157/472] Add a script for manipulating binary patches at runtime, and some patches. --- NEWS | 1 + Readme.html | 151 ++++++++++-------- Readme.rst | 11 ++ dfhack.init-example | 23 +++ library/CMakeLists.txt | 4 + patches/v0.34.11 SDL/armorstand-capacity.dif | 142 ++++++++++++++++ patches/v0.34.11 SDL/custom-reagent-size.dif | 91 +++++++++++ patches/v0.34.11 SDL/deconstruct-heapfall.dif | 61 +++++++ patches/v0.34.11 SDL/deconstruct-teleport.dif | 104 ++++++++++++ .../v0.34.11 SDL/hospital-overstocking.dif | 62 +++++++ patches/v0.34.11 SDL/training-ammo.dif | 83 ++++++++++ patches/v0.34.11 SDL/weaponrack-unassign.dif | 61 +++++++ .../v0.34.11 linux/armorstand-capacity.dif | 147 +++++++++++++++++ .../v0.34.11 linux/custom-reagent-size.dif | 40 +++++ .../v0.34.11 linux/deconstruct-heapfall.dif | 83 ++++++++++ .../v0.34.11 linux/deconstruct-teleport.dif | 139 ++++++++++++++++ .../v0.34.11 linux/hospital-overstocking.dif | 60 +++++++ patches/v0.34.11 linux/training-ammo.dif | 85 ++++++++++ .../v0.34.11 linux/weaponrack-unassign.dif | 45 ++++++ scripts/binpatch.lua | 117 ++++++++++++++ 20 files changed, 1440 insertions(+), 70 deletions(-) create mode 100644 patches/v0.34.11 SDL/armorstand-capacity.dif create mode 100644 patches/v0.34.11 SDL/custom-reagent-size.dif create mode 100644 patches/v0.34.11 SDL/deconstruct-heapfall.dif create mode 100644 patches/v0.34.11 SDL/deconstruct-teleport.dif create mode 100644 patches/v0.34.11 SDL/hospital-overstocking.dif create mode 100644 patches/v0.34.11 SDL/training-ammo.dif create mode 100644 patches/v0.34.11 SDL/weaponrack-unassign.dif create mode 100644 patches/v0.34.11 linux/armorstand-capacity.dif create mode 100644 patches/v0.34.11 linux/custom-reagent-size.dif create mode 100644 patches/v0.34.11 linux/deconstruct-heapfall.dif create mode 100644 patches/v0.34.11 linux/deconstruct-teleport.dif create mode 100644 patches/v0.34.11 linux/hospital-overstocking.dif create mode 100644 patches/v0.34.11 linux/training-ammo.dif create mode 100644 patches/v0.34.11 linux/weaponrack-unassign.dif create mode 100644 scripts/binpatch.lua diff --git a/NEWS b/NEWS index 51321be95..494a6c680 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ DFHack future - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option New scripts: + - binpatch: the same as the stand-alone binpatch.exe, but works at runtime. - region-pops: displays animal populations of the region and allows tweaking them. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. diff --git a/Readme.html b/Readme.html index 16f3aed5a..737963372 100644 --- a/Readme.html +++ b/Readme.html @@ -489,49 +489,50 @@ access DF memory and allow for easier development of new tools.

                                          • Scripts
                                          • -
                                          • In-game interface tools
                                          • +
                                            +

                                            binpatch

                                            +

                                            Checks, applies or removes binary patches directly in memory at runtime:

                                            +
                                            +binpatch check/apply/remove <patchname>
                                            +
                                            +

                                            If the name of the patch has no extension or directory separators, the +script uses hack/patches/<df-version>/<name>.dif, thus auto-selecting +the version appropriate for the currently loaded executable.

                                            +
                                            -

                                            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 @@ -2612,7 +2623,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. @@ -2624,7 +2635,7 @@ growcrops plump 40

                                            -

                                            removebadthoughts

                                            +

                                            removebadthoughts

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

                                            The script can target a single creature, when used with the him argument, @@ -2638,7 +2649,7 @@ but in the short term your dwarves will get much more joyful.

                                            quickly after you unpause.

                                            -

                                            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.

                                            @@ -2664,7 +2675,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.

                                            @@ -2679,7 +2690,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:

                                            @@ -2697,7 +2708,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:

                                            @@ -2707,17 +2718,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.

                                            @@ -2728,7 +2739,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 @@ -2764,7 +2775,7 @@ 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.

                                            @@ -2798,7 +2809,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.

                                              @@ -2814,14 +2825,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 @@ -2831,13 +2842,13 @@ only that entry, and does it even if it is not 'individual choice'.

                                            and may lead to inappropriate weapons being selected.

                                            -

                                            gui/guide-path

                                            +

                                            gui/guide-path

                                            Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                            The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

                                            -

                                            gui/workshop-job

                                            +

                                            gui/workshop-job

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

                                            @@ -2865,7 +2876,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                            -

                                            gui/workflow

                                            +

                                            gui/workflow

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2887,7 +2898,7 @@ as described in workflow documentation above. can be used for troubleshooting jobs that don't match the right constraints.

                                            -

                                            gui/assign-rack

                                            +

                                            gui/assign-rack

                                            Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                            This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

                                            @@ -2907,7 +2918,7 @@ the intended user.

                                            -

                                            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.

                                            @@ -2918,20 +2929,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 @@ -2952,7 +2963,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 @@ -2961,11 +2972,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 @@ -2976,7 +2987,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.

                                            @@ -3000,7 +3011,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 @@ -3031,7 +3042,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 @@ -3040,7 +3051,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 @@ -3051,7 +3062,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 7fbc42528..16665ab98 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1761,6 +1761,17 @@ gui/* Scripts that implement dialogs inserted into the main game window are put in this directory. +binpatch +======== + +Checks, applies or removes binary patches directly in memory at runtime:: + + binpatch check/apply/remove + +If the name of the patch has no extension or directory separators, the +script uses ``hack/patches//.dif``, thus auto-selecting +the version appropriate for the currently loaded executable. + quicksave ========= diff --git a/dfhack.init-example b/dfhack.init-example index 729747714..885849c33 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -132,3 +132,26 @@ tweak fast-trade tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned + +####################################################### +# Apply binary patches at runtime # +# # +# Commented out by default; enable the ones you want. # +####################################################### + +# Bug 5994 - items teleported when removing a construction +#binpatch apply deconstruct-teleport +#binpatch apply deconstruct-heapfall + +# Bug 4406 - hospital overstocking on all items +#binpatch apply hospital-overstocking + +# Bug 808 - custom reactions completely using up all of their reagents +#binpatch apply custom-reagent-size + +# Bug 4530 - marksdwarves not training when quiver full of combat-only ammo +#binpatch apply training-ammo + +# Bug 1445 - weapon racks broken, armor stand capacity too low +#binpatch apply weaponrack-unassign +#binpatch apply armorstand-capacity diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6f33d5c8a..b141e9fa5 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -346,6 +346,10 @@ install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts PATTERN "*.rb" ) +install(DIRECTORY ${dfhack_SOURCE_DIR}/patches + DESTINATION ${DFHACK_DATA_DESTINATION} + FILES_MATCHING PATTERN "*.dif") + # Unused for so long that it's not even relevant now... if(BUILD_DEVEL) if(WIN32) diff --git a/patches/v0.34.11 SDL/armorstand-capacity.dif b/patches/v0.34.11 SDL/armorstand-capacity.dif new file mode 100644 index 000000000..e7d69a8c2 --- /dev/null +++ b/patches/v0.34.11 SDL/armorstand-capacity.dif @@ -0,0 +1,142 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +0x2ac6b +CC CC CC CC CC +66 39 E8 EB 53 + +.text:0042B86B loc_42B86B: +.text:0042B86B cmp ax, bp +.text:0042B86E jmp short loc_42B8C3 + +0x2ac7b +CC CC CC CC CC +E9 96 A2 00 00 + +.text:0042B87B loc_42B87B: +.text:0042B87B jmp loc_435B16 + +0x2acc3 +CC CC CC CC CC CC CC CC CC CC CC CC CC +75 0A 66 FF 4C 24 16 79 03 58 EB AC C3 + +.text:0042B8C3 loc_42B8C3: +.text:0042B8C3 jnz short locret_42B8CF +.text:0042B8C5 dec word ptr [esp+16h] ; 4+8+8+2 +.text:0042B8CA jns short locret_42B8CF +.text:0042B8CC pop eax +.text:0042B8CD jmp short loc_42B87B +.text:0042B8CF locret_42B8CF: +.text:0042B8CF retn + +0x2b2a1 +CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC +66 C7 44 24 0E 01 00 8B 90 44 01 00 00 C3 CC + +.text:0042BEA1 loc_42BEA1: +.text:0042BEA1 mov word ptr [esp+0Eh], 1 ; 4+8+2 +.text:0042BEA8 mov edx, [eax+144h] +.text:0042BEAE retn + +0x34d91 +8B 90 44 01 00 00 +E8 0B 65 FF FF 90 + +<<<< +.text:00435991 mov edx, [eax+144h] +==== +.text:00435991 call loc_42BEA1 +.text:00435996 nop +>>>> + +0x34e53 +0F 84 BD 00 00 00 +E8 6B 5E FF FF 90 + +<<<< +.text:00435A53 jz loc_435B16 +==== +.text:00435A53 call loc_42B8C3 +.text:00435A58 nop +>>>> + +0x34ef3 +66 3B C5 74 1E +E8 73 5D FF FF + +<<<< +.text:00435AF3 cmp ax, bp +.text:00435AF6 jz short loc_435B16 +==== +.text:00435AF3 call loc_42B86B +>>>> + + +basically: + ++ int allowed_count = 1; // to mean 2 + ... +- if (type(item) == new_type) ++ if (type(item) == new_type && --allowed_count < 0) + return false; + +to allow up to two items of the same type at the same time + + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +0002AC6B: CC 66 +0002AC6C: CC 39 +0002AC6D: CC E8 +0002AC6E: CC EB +0002AC6F: CC 53 +0002AC7B: CC E9 +0002AC7C: CC 96 +0002AC7D: CC A2 +0002AC7E: CC 00 +0002AC7F: CC 00 +0002ACC3: CC 75 +0002ACC4: CC 0A +0002ACC5: CC 66 +0002ACC6: CC FF +0002ACC7: CC 4C +0002ACC8: CC 24 +0002ACC9: CC 16 +0002ACCA: CC 79 +0002ACCB: CC 03 +0002ACCC: CC 58 +0002ACCD: CC EB +0002ACCE: CC AC +0002ACCF: CC C3 +0002B2A1: CC 66 +0002B2A2: CC C7 +0002B2A3: CC 44 +0002B2A4: CC 24 +0002B2A5: CC 0E +0002B2A6: CC 01 +0002B2A7: CC 00 +0002B2A8: CC 8B +0002B2A9: CC 90 +0002B2AA: CC 44 +0002B2AB: CC 01 +0002B2AC: CC 00 +0002B2AD: CC 00 +0002B2AE: CC C3 +00034D91: 8B E8 +00034D92: 90 0B +00034D93: 44 65 +00034D94: 01 FF +00034D95: 00 FF +00034D96: 00 90 +00034E53: 0F E8 +00034E54: 84 6B +00034E55: BD 5E +00034E56: 00 FF +00034E57: 00 FF +00034E58: 00 90 +00034EF3: 66 E8 +00034EF4: 3B 73 +00034EF5: C5 5D +00034EF6: 74 FF +00034EF7: 1E FF diff --git a/patches/v0.34.11 SDL/custom-reagent-size.dif b/patches/v0.34.11 SDL/custom-reagent-size.dif new file mode 100644 index 000000000..2227a48d0 --- /dev/null +++ b/patches/v0.34.11 SDL/custom-reagent-size.dif @@ -0,0 +1,91 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=808 + +Original code: + +.text:00916BCE mov edi, ebp +.text:00916BD0 call eax +.text:00916BD2 test eax, eax +.text:00916BD4 jnz short loc_916C1C + +.text:00916C0A mov edi, ebp + +.text:00916C14 mov edi, ebp + +Patch: + +0x2ac34: +CC CC CC CC CC CC CC CC CC CC CC CC +8B 7C 24 78 8B 3C B7 FF D0 EB 25 CC + +.text:0042B834 loc_42B834: +.text:0042B834 mov edi, [esp+78h] +.text:0042B838 mov edi, [edi+esi*4] +.text:0042B83B call eax +.text:0042B83D jmp short unk_42B864 + +0x2ac64 +CC CC CC CC CC CC CC CC CC CC CC CC +85 C0 E9 69 B3 4E 00 CC CC CC CC CC + +.text:0042B864 loc_42B864: +.text:0042B864 test eax, eax +.text:0042B866 jmp loc_916BD4 + +0x515fce +8B FD FF D0 85 C0 +E9 61 4C B1 FF 90 + +.text:00916BCE jmp loc_42B834 +.text:00916BD3 nop +.text:00916BD4 loc_916BD4: + +0x51600a +8B FD +90 90 + +.text:00916C0A nop +.text:00916C0B nop + +0x516014 +8B FD +90 90 + +.text:00916C14 nop +.text:00916C15 nop + + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +0002AC34: CC 8B +0002AC35: CC 7C +0002AC36: CC 24 +0002AC37: CC 78 +0002AC38: CC 8B +0002AC39: CC 3C +0002AC3A: CC B7 +0002AC3B: CC FF +0002AC3C: CC D0 +0002AC3D: CC EB +0002AC3E: CC 25 +0002AC64: CC 85 +0002AC65: CC C0 +0002AC66: CC E9 +0002AC67: CC 69 +0002AC68: CC B3 +0002AC69: CC 4E +0002AC6A: CC 00 +00515FCE: 8B E9 +00515FCF: FD 61 +00515FD0: FF 4C +00515FD1: D0 B1 +00515FD2: 85 FF +00515FD3: C0 90 +0051600A: 8B 90 +0051600B: FD 90 +00516014: 8B 90 +00516015: FD 90 diff --git a/patches/v0.34.11 SDL/deconstruct-heapfall.dif b/patches/v0.34.11 SDL/deconstruct-heapfall.dif new file mode 100644 index 000000000..05c0047d3 --- /dev/null +++ b/patches/v0.34.11 SDL/deconstruct-heapfall.dif @@ -0,0 +1,61 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994 + +Original code: + +.text:008629BD mov edi, [eax+38h] +.text:008629C0 mov eax, [eax+3Ch] +.text:008629C3 mov [esp+1Ch], eax +.text:008629C7 cmp edi, eax +.text:008629C9 jnb short loc_862A22 +.text:008629CB jmp short loc_8629D0 +.text:008629CD lea ecx, [ecx+0] +... +.text:00862A19 add edi, 4 +.text:00862A1C cmp edi, [esp+1Ch] +.text:00862A20 jb short loc_8629D0 + +Patch: + +0x461dbd +8B 78 38 8B 40 3C 89 44 24 1C 3B F8 +8B 78 3C 8B 40 38 89 44 24 1C 39 F8 + +.text:008629BD mov edi, [eax+3Ch] +.text:008629C0 mov eax, [eax+38h] +.text:008629C3 mov [esp+1Ch], eax +.text:008629C7 cmp eax, edi + +0x461dcb +EB 03 8D 49 00 +83 EF 04 90 90 + +.text:008629CB sub edi, 4 +.text:008629CE nop +.text:008629CF nop + +0x461e19 +83 C7 04 3B 7C 24 1C 72 AE +83 EF 04 3B 7C 24 1C 73 AE + +.text:00862A19 sub edi, 4 +.text:00862A1C cmp edi, [esp+1Ch] +.text:00862A20 jnb short loc_8629D0 + + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +00461DBF: 38 3C +00461DC2: 3C 38 +00461DC7: 3B 39 +00461DCB: EB 83 +00461DCC: 03 EF +00461DCD: 8D 04 +00461DCE: 49 90 +00461DCF: 00 90 +00461E1A: C7 EF +00461E20: 72 73 diff --git a/patches/v0.34.11 SDL/deconstruct-teleport.dif b/patches/v0.34.11 SDL/deconstruct-teleport.dif new file mode 100644 index 000000000..c6037e2c6 --- /dev/null +++ b/patches/v0.34.11 SDL/deconstruct-teleport.dif @@ -0,0 +1,104 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994 + +0x461de2 +F6 46 0C 01 74 31 +E9 0A 8E BC FF 90 + +.text:008629E2 jmp near ptr loc_42B7F1 ; << CAVE +.text:008629E7 nop +.text:008629E8 loc_8629E8: + +0x2abf1 +CC CC CC CC CC CC CC CC CC CC CC CC CC CC CC +8B 4C 24 2C F6 46 0C 01 75 08 E9 19 72 43 00 + +.text:0042B7F1 loc_42B7F1: +.text:0042B7F1 mov ecx, [esp+2Ch] ; job +.text:0042B7F5 test byte ptr [esi+0Ch], 1 +.text:0042B7F9 jnz short near ptr loc_42B803 +.text:0042B7FB coord_test_jfail: +.text:0042B7FB jmp loc_862A19 + +0x2ac03 +CC CC CC CC CC CC CC CC CC CC CC CC CC +8B 41 10 3B 46 04 75 F0 EB 06 CC CC CC + +.text:0042B803 loc_42B803: +.text:0042B803 mov eax, [ecx+10h] ; job->pos.(x,y) +.text:0042B806 cmp eax, [esi+4] ; item->pos.(x,y) +.text:0042B809 jnz short coord_test_jfail +.text:0042B80B jmp short near ptr loc_42B813 + +0x2ac13 +CC CC CC CC CC CC CC CC CC CC CC CC CC +66 8B 41 14 66 3B 46 08 75 DE EB 06 CC + +text:0042B813 loc_42B813: +.text:0042B813 mov ax, [ecx+14h] ; job->pos.z +.text:0042B817 cmp ax, [esi+8] ; item->pos.z +.text:0042B81B jnz short coord_test_jfail +.text:0042B81D jmp short near ptr loc_42B825 + +0x2ac25 +CC CC CC CC CC CC CC CC CC CC CC +E9 BE 71 43 00 CC CC CC CC CC CC + +.text:0042B825 loc_42B825: +.text:0042B825 jmp loc_8629E8 + + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +0002ABF1: CC 8B +0002ABF2: CC 4C +0002ABF3: CC 24 +0002ABF4: CC 2C +0002ABF5: CC F6 +0002ABF6: CC 46 +0002ABF7: CC 0C +0002ABF8: CC 01 +0002ABF9: CC 75 +0002ABFA: CC 08 +0002ABFB: CC E9 +0002ABFC: CC 19 +0002ABFD: CC 72 +0002ABFE: CC 43 +0002ABFF: CC 00 +0002AC03: CC 8B +0002AC04: CC 41 +0002AC05: CC 10 +0002AC06: CC 3B +0002AC07: CC 46 +0002AC08: CC 04 +0002AC09: CC 75 +0002AC0A: CC F0 +0002AC0B: CC EB +0002AC0C: CC 06 +0002AC13: CC 66 +0002AC14: CC 8B +0002AC15: CC 41 +0002AC16: CC 14 +0002AC17: CC 66 +0002AC18: CC 3B +0002AC19: CC 46 +0002AC1A: CC 08 +0002AC1B: CC 75 +0002AC1C: CC DE +0002AC1D: CC EB +0002AC1E: CC 06 +0002AC25: CC E9 +0002AC26: CC BE +0002AC27: CC 71 +0002AC28: CC 43 +0002AC29: CC 00 +00461DE2: F6 E9 +00461DE3: 46 0A +00461DE4: 0C 8E +00461DE5: 01 BC +00461DE6: 74 FF +00461DE7: 31 90 diff --git a/patches/v0.34.11 SDL/hospital-overstocking.dif b/patches/v0.34.11 SDL/hospital-overstocking.dif new file mode 100644 index 000000000..2bc305e60 --- /dev/null +++ b/patches/v0.34.11 SDL/hospital-overstocking.dif @@ -0,0 +1,62 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=4406 + +1. Include store in hospital jobs when recomputing counters + +0x68a63 +0F 85 58 01 00 00 +90 90 90 90 90 90 + +<<<< +.text:00469663 jnz loc_4697C1 +==== +.text:00469663 nop +.text:00469664 nop +.text:00469665 nop +.text:00469666 nop +.text:00469667 nop +.text:00469668 nop +>>>> + +- if (job->getBuildingRef(BUILDING_DESTINATION) != this) continue; ++ // NOP + +This reference points to the containers, not the hospital civzone. +Since fixing this properly is too hard for a patch, just remove the +check. Most people have only one hospital anyway, and it is better +to err on the side of caution here. + + +2. Make the stockpiling code increment the right stock counters + +0x3dcbf9 +8B 0C 90 8B 81 80 00 00 00 +8B 3C 90 8B 87 80 00 00 00 + +<<<< +.text:007DD7F9 mov ecx, [eax+edx*4] +.text:007DD7FC mov eax, [ecx+80h] +==== +.text:007DD7F9 mov edi, [eax+edx*4] +.text:007DD7FC mov eax, [edi+80h] +>>>> + +- id = civzones[i]->children[child_idx[i]]->id ++ cur_civzone = civzones[i] // existing var from previous loop ++ id = cur_civzone->children[child_idx[i]]->id + +The reason being, later code uses that var (at this point containing +useless data) to increment counters and amounts in the hospital. + + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +00068A63: 0F 90 +00068A64: 85 90 +00068A65: 58 90 +00068A66: 01 90 +00068A67: 00 90 +00068A68: 00 90 +003DCBFA: 0C 3C +003DCBFD: 81 87 diff --git a/patches/v0.34.11 SDL/training-ammo.dif b/patches/v0.34.11 SDL/training-ammo.dif new file mode 100644 index 000000000..ea50d4221 --- /dev/null +++ b/patches/v0.34.11 SDL/training-ammo.dif @@ -0,0 +1,83 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=4530 + +0x2ac85 +CC CC CC CC CC CC CC CC CC CC CC +29 44 24 24 8B 9D 28 01 00 00 C3 + +.text:0042B885 loc_42B885: +.text:0042B885 sub [esp+24h], eax +.text:0042B889 mov ebx, [ebp+128h] +.text:0042B88F retn + +0x2ac94 +CC CC CC CC CC CC CC CC CC CC CC CC +89 C1 8B 00 FF 90 34 02 00 00 EB E5 + +.text:0042B894 loc_42B894: +.text:0042B894 mov ecx, eax +.text:0042B896 mov eax, [eax] +.text:0042B898 call dword ptr [eax+234h] +.text:0042B89E jmp short loc_42B885 + +0x6e28ff +29 44 24 20 +90 90 90 90 + +<<<< +.text:00AE34FF sub [esp+20h], eax +==== +.text:00AE34FF nop +.text:00AE3500 nop +.text:00AE3501 nop +.text:00AE3502 nop +>>>> + +0x6e2999 +8B 9D 28 01 00 00 +E8 F6 82 94 FF 90 + +<<<< +.text:00AE3599 mov ebx, [ebp+128h] +==== +.text:00AE3599 call loc_42B894 +.text:00AE359E nop +>>>> + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +0002AC85: CC 29 +0002AC86: CC 44 +0002AC87: CC 24 +0002AC88: CC 24 +0002AC89: CC 8B +0002AC8A: CC 9D +0002AC8B: CC 28 +0002AC8C: CC 01 +0002AC8D: CC 00 +0002AC8E: CC 00 +0002AC8F: CC C3 +0002AC94: CC 89 +0002AC95: CC C1 +0002AC96: CC 8B +0002AC97: CC 00 +0002AC98: CC FF +0002AC99: CC 90 +0002AC9A: CC 34 +0002AC9B: CC 02 +0002AC9C: CC 00 +0002AC9D: CC 00 +0002AC9E: CC EB +0002AC9F: CC E5 +006E28FF: 29 90 +006E2900: 44 90 +006E2901: 24 90 +006E2902: 20 90 +006E2999: 8B E8 +006E299A: 9D F6 +006E299B: 28 82 +006E299C: 01 94 +006E299D: 00 FF +006E299E: 00 90 + diff --git a/patches/v0.34.11 SDL/weaponrack-unassign.dif b/patches/v0.34.11 SDL/weaponrack-unassign.dif new file mode 100644 index 000000000..7760048ba --- /dev/null +++ b/patches/v0.34.11 SDL/weaponrack-unassign.dif @@ -0,0 +1,61 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +0x4c05c4 +8B 8C 24 80 00 00 00 +89 C1 90 90 90 90 90 + +<<<< +.text:008C11C4 mov ecx, [esp+98h+var_18] +==== +.text:008C11C4 mov ecx, eax +.text:008C11C6 nop +.text:008C11C7 nop +.text:008C11C8 nop +.text:008C11C9 nop +.text:008C11CA nop +>>>> + +0x4c06a1 +8B 8C 24 80 00 00 00 +89 C1 90 90 90 90 90 + +<<<< +.text:008C12A1 mov ecx, [esp+98h+var_18] +==== +.text:008C12A1 mov ecx, eax +.text:008C12A3 nop +.text:008C12A4 nop +.text:008C12A5 nop +.text:008C12A6 nop +.text:008C12A7 nop +>>>> + + +basically: + + b_squad_id = building->getSpecificSquad(); +- if (b_squad_id != squad->id || !building->canUse(some_squad_id, 4)) ++ if (b_squad_id != squad->id || !building->canUse(b_squad_id, 4)) + unassign(building); + +the reason being, some_other_squad_id contains irrelevant garbage at this point + + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf Fortress.exe +004C05C4: 8B 89 +004C05C5: 8C C1 +004C05C6: 24 90 +004C05C7: 80 90 +004C05C8: 00 90 +004C05C9: 00 90 +004C05CA: 00 90 +004C06A1: 8B 89 +004C06A2: 8C C1 +004C06A3: 24 90 +004C06A4: 80 90 +004C06A5: 00 90 +004C06A6: 00 90 +004C06A7: 00 90 diff --git a/patches/v0.34.11 linux/armorstand-capacity.dif b/patches/v0.34.11 linux/armorstand-capacity.dif new file mode 100644 index 000000000..36dbcf0a9 --- /dev/null +++ b/patches/v0.34.11 linux/armorstand-capacity.dif @@ -0,0 +1,147 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +0x9461 +90 90 90 90 90 90 90 90 90 90 90 90 90 90 +C7 44 24 18 01 00 00 00 FF A0 44 01 00 00 + +.text:08051461 sub_8051461 proc near +.text:08051461 mov dword ptr [esp+18h], 1 +.text:08051469 jmp dword ptr [eax+144h] +.text:08051469 sub_8051461 endp + +0x9548 +90 90 90 90 90 90 90 90 +FF 4C 24 14 78 08 EB 0B + +.text:08051548 loc_8051548: +.text:08051548 dec dword ptr [esp+14h] +.text:0805154C js short loc_8051556 +.text:0805154E jmp short loc_805155B + +0x9556 +90 90 90 90 90 90 90 90 90 90 +E9 F6 8C 05 00 E9 D0 8D 05 00 + +.text:08051556 loc_8051556: +.text:08051556 jmp loc_80AA251 +.text:0805155B loc_805155B: +.text:0805155B jmp loc_80AA330 + +0x9568 +90 90 90 90 90 90 90 90 +FF 4C 24 14 78 E8 EB 06 + +.text:08051568 loc_8051568: +.text:08051568 dec [esp+14h] +.text:0805156C js short loc_8051556 +.text:0805156E jmp short loc_8051576 + +0x9576 +90 90 90 90 90 +E9 4D 8E 05 00 + +.text:08051576 loc_8051576: +.text:08051576 jmp loc_80AA3C8 + +0x62243 +FF 90 44 01 00 00 +E8 19 72 FA FF 90 + +<<<< +.text:080AA243 call dword ptr [eax+144h] +==== +.text:080AA243 call sub_8051461 +.text:080AA248 nop +>>>> + +0x62369 +E9 E3 FE FF FF +E9 DA 71 FA FF + +<<<< +.text:080AA369 jmp loc_80AA251 +==== +.text:080AA369 jmp loc_8051548 +>>>> + +0x623f6 +E9 56 FE FF FF +E9 6D 71 FA FF + +<<<< +.text:080AA3F6 jmp loc_80AA251 +==== +.text:080AA3F6 jmp loc_8051568 +>>>> + +basically: + ++ int allowed_count = 1; // to mean 2 + ... +- if (type(item) == new_type) ++ if (type(item) == new_type && --allowed_count < 0) + return false; + +to allow up to two items of the same type at the same time + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +00009461: 90 C7 +00009462: 90 44 +00009463: 90 24 +00009464: 90 18 +00009465: 90 01 +00009466: 90 00 +00009467: 90 00 +00009468: 90 00 +00009469: 90 FF +0000946A: 90 A0 +0000946B: 90 44 +0000946C: 90 01 +0000946D: 90 00 +0000946E: 90 00 +00009548: 90 FF +00009549: 90 4C +0000954A: 90 24 +0000954B: 90 14 +0000954C: 90 78 +0000954D: 90 08 +0000954E: 90 EB +0000954F: 90 0B +00009556: 90 E9 +00009557: 90 F6 +00009558: 90 8C +00009559: 90 05 +0000955A: 90 00 +0000955B: 90 E9 +0000955C: 90 D0 +0000955D: 90 8D +0000955E: 90 05 +0000955F: 90 00 +00009568: 90 FF +00009569: 90 4C +0000956A: 90 24 +0000956B: 90 14 +0000956C: 90 78 +0000956D: 90 E8 +0000956E: 90 EB +0000956F: 90 06 +00009576: 90 E9 +00009577: 90 4D +00009578: 90 8E +00009579: 90 05 +0000957A: 90 00 +00062243: FF E8 +00062244: 90 19 +00062245: 44 72 +00062246: 01 FA +00062247: 00 FF +00062248: 00 90 +0006236A: E3 DA +0006236B: FE 71 +0006236C: FF FA +000623F7: 56 6D +000623F8: FE 71 +000623F9: FF FA diff --git a/patches/v0.34.11 linux/custom-reagent-size.dif b/patches/v0.34.11 linux/custom-reagent-size.dif new file mode 100644 index 000000000..d99269db1 --- /dev/null +++ b/patches/v0.34.11 linux/custom-reagent-size.dif @@ -0,0 +1,40 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=808 + +for (i = 0; i < num_items; i++) +{ + ridx = reagent_idx[i]; + sz = reagent_quantity[ridx]; // used quantity + if (sz <= 0) continue; + reag = reagent[ridx]; + if (reag->flags.PRESERVE_REAGENT) continue; + rsz = items[i]->getTotalDimension(); +<<<<<<<< + if (reag->flags3.ANY_RAW_MATERIAL) + rsz *= BASE_SIZE(items[i]->getType()); + if (items[i]->subtractDimension(rsz)) +======== + /* Not in patch, but necessary for full correctness: + if (reag->flags3.ANY_RAW_MATERIAL) + rsz /= BASE_SIZE(items[i]->getType()); + */ + if (reag->flags3.ANY_RAW_MATERIAL) + sz *= BASE_SIZE(items[i]->getType()); + if (items[i]->subtractDimension(sz)) +>>>>>>>> + destroy_item(items[i]); + reagent_quantity[ridx] -= rsz + if (reagent_quantity[ridx] < 0) + reagent_quantity[ridx] = 0; +} + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +0087F7EF: F8 D8 +0087F86F: F8 D8 +0087F9AD: C7 C3 +0087F9ED: C7 C3 diff --git a/patches/v0.34.11 linux/deconstruct-heapfall.dif b/patches/v0.34.11 linux/deconstruct-heapfall.dif new file mode 100644 index 000000000..294118ac5 --- /dev/null +++ b/patches/v0.34.11 linux/deconstruct-heapfall.dif @@ -0,0 +1,83 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994 + +Original code: + +.text:087AC378 cmp edx, eax +.text:087AC37A mov [esp+4Ch], eax +.text:087AC37E jnb loc_87A7034 +.text:087AC384 mov [esp+48h], edx +.text:087AC388 mov [esp+54h], ebx +... +.text:087AC440 add dword ptr [esp+48h], 4 +.text:087AC445 mov ebp, [esp+48h] +.text:087AC449 cmp [esp+4Ch], ebp +.text:087AC44D ja loc_87AC38C + +Patch: + +0x76437a +89 44 24 4C +89 54 24 4C + +.text:087AC37A mov [esp+4Ch], edx + +0x764384 +89 54 24 48 89 5C 24 54 +E8 8A 51 8A FF 90 90 90 + +.text:087AC384 call sub_8051513 +.text:087AC389 nop +.text:087AC38A nop +.text:087AC38B nop + +0x764440 +83 44 24 48 04 8B 6C 24 48 39 6C 24 4C 0F 87 39 FF FF FF +83 6C 24 48 04 8B 6C 24 48 39 6C 24 4C 0F 86 39 FF FF FF + +.text:087AC440 sub dword ptr [esp+48h], 4 +.text:087AC445 mov ebp, [esp+48h] +.text:087AC449 cmp [esp+4Ch], ebp +.text:087AC44D jbe loc_87AC38C + +0x9513 +90 90 90 90 90 90 90 90 90 90 90 90 90 +83 E8 04 89 44 24 4C 89 5C 24 58 C3 90 + +.text:08051513 sub_8051513 proc near +.text:08051513 sub eax, 4 +.text:08051516 mov [esp+4Ch], eax ; 48h +.text:0805151A mov [esp+58h], ebx ; 54h +.text:0805151E retn +.text:0805151E sub_8051513 endp + + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +00009513: 90 83 +00009514: 90 E8 +00009515: 90 04 +00009516: 90 89 +00009517: 90 44 +00009518: 90 24 +00009519: 90 4C +0000951A: 90 89 +0000951B: 90 5C +0000951C: 90 24 +0000951D: 90 58 +0000951E: 90 C3 +0076437B: 44 54 +00764384: 89 E8 +00764385: 54 8A +00764386: 24 51 +00764387: 48 8A +00764388: 89 FF +00764389: 5C 90 +0076438A: 24 90 +0076438B: 54 90 +00764441: 44 6C +0076444E: 87 86 diff --git a/patches/v0.34.11 linux/deconstruct-teleport.dif b/patches/v0.34.11 linux/deconstruct-teleport.dif new file mode 100644 index 000000000..3b3212109 --- /dev/null +++ b/patches/v0.34.11 linux/deconstruct-teleport.dif @@ -0,0 +1,139 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=5994 + +0x7643f8 +F6 46 0C 01 74 42 +E9 B6 50 8A FF 90 + +.text:087AC3F8 jmp loc_80514B3 ; << CAVE +.text:087AC3FD nop +.text:087AC3FE loc_87AC3FE: + +0x94b3 +90 90 90 90 90 90 90 90 90 90 90 90 90 +F6 46 0C 01 75 0A E9 82 AF 75 00 90 90 + +.text:080514B3 loc_80514B3: +.text:080514B3 test byte ptr [esi+0Ch], 1 +.text:080514B7 jnz short loc_80514C3 +.text:080514B9 coord_test_jfail: +.text:080514B9 jmp loc_87AC440 + +0x94c3 +90 90 90 90 90 90 90 90 90 90 90 90 90 +8D 9C 24 60 03 00 00 0F BF 03 EB 07 90 + +.text:080514C3 loc_80514C3: +.text:080514C3 lea ebx, [esp+360h] +.text:080514CA movsx eax, word ptr [ebx] ; job_z +.text:080514CD jmp short loc_80514D6 + +0x94d6 +90 90 90 90 90 90 90 90 90 90 +66 3B 46 08 75 DD EB 05 90 90 + +.text:080514D6 loc_80514D6: +.text:080514D6 cmp ax, [esi+8] ; item->pos.z +.text:080514DA jnz short coord_test_jfail +.text:080514DC jmp short loc_80514E3 + +0x94e3 +90 90 90 90 90 90 90 90 90 90 90 90 90 +0F BF 43 10 66 3B 46 04 75 CC EB 04 90 + +.text:080514E3 loc_80514E3: +.text:080514E3 movsx eax, word ptr [ebx+10h] ; job_x +.text:080514E7 cmp ax, [esi+4] ; item->pos.x +.text:080514EB jnz short coord_test_jfail +.text:080514ED jmp short loc_80514F3 + +0x94f3 +90 90 90 90 90 90 90 90 90 90 90 90 90 +0F BF 43 20 66 3B 46 06 75 BC EB 04 90 + +.text:080514F3 loc_80514F3: +.text:080514F3 movsx eax, word ptr [ebx+20h] ; job_y +.text:080514F7 cmp ax, [esi+6] ; item->pos.y +.text:080514FB jnz short coord_test_jfail +.text:080514FD jmp short loc_8051503 + +0x9503 +90 90 90 90 90 90 90 90 90 90 90 90 90 +E9 F6 AE 75 00 90 90 90 90 90 90 90 90 + +.text:08051503 loc_8051503: +.text:08051503 jmp loc_87AC3FE + + +You can use this script to apply the generated patch below: +http://stalkr.net/files/ida/idadif.py + +----8<---- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +000094B3: 90 F6 +000094B4: 90 46 +000094B5: 90 0C +000094B6: 90 01 +000094B7: 90 75 +000094B8: 90 0A +000094B9: 90 E9 +000094BA: 90 82 +000094BB: 90 AF +000094BC: 90 75 +000094BD: 90 00 +000094C3: 90 8D +000094C4: 90 9C +000094C5: 90 24 +000094C6: 90 60 +000094C7: 90 03 +000094C8: 90 00 +000094C9: 90 00 +000094CA: 90 0F +000094CB: 90 BF +000094CC: 90 03 +000094CD: 90 EB +000094CE: 90 07 +000094D6: 90 66 +000094D7: 90 3B +000094D8: 90 46 +000094D9: 90 08 +000094DA: 90 75 +000094DB: 90 DD +000094DC: 90 EB +000094DD: 90 05 +000094E3: 90 0F +000094E4: 90 BF +000094E5: 90 43 +000094E6: 90 10 +000094E7: 90 66 +000094E8: 90 3B +000094E9: 90 46 +000094EA: 90 04 +000094EB: 90 75 +000094EC: 90 CC +000094ED: 90 EB +000094EE: 90 04 +000094F3: 90 0F +000094F4: 90 BF +000094F5: 90 43 +000094F6: 90 20 +000094F7: 90 66 +000094F8: 90 3B +000094F9: 90 46 +000094FA: 90 06 +000094FB: 90 75 +000094FC: 90 BC +000094FD: 90 EB +000094FE: 90 04 +00009503: 90 E9 +00009504: 90 F6 +00009505: 90 AE +00009506: 90 75 +00009507: 90 00 +007643F8: F6 E9 +007643F9: 46 B6 +007643FA: 0C 50 +007643FB: 01 8A +007643FC: 74 FF +007643FD: 42 90 diff --git a/patches/v0.34.11 linux/hospital-overstocking.dif b/patches/v0.34.11 linux/hospital-overstocking.dif new file mode 100644 index 000000000..73e5d2fac --- /dev/null +++ b/patches/v0.34.11 linux/hospital-overstocking.dif @@ -0,0 +1,60 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=4406 + +1. Include store in hospital jobs when recomputing counters + +0x746d7 +75 D7 +90 90 + +<<<< +.text:080BC6D7 jnz short loc_80BC6B0 +==== +.text:080BC6D7 nop +.text:080BC6D8 nop +>>>> + +- if (job->getBuildingRef(BUILDING_DESTINATION) != this) continue; ++ // NOP + +This reference points to the containers, not the hospital civzone. +Since fixing this properly is too hard for a patch, just remove the +check. Most people have only one hospital anyway, and it is better +to err on the side of caution here. + + +2. Make the stockpiling code increment the right stock counters + +0x67cb0e +0B 04 90 +8B 1C 90 + +0x67cb18 +8B 40 74 +8B 43 74 + +<<<< +.text:086C4B0E mov eax, [eax+edx*4] +.text:086C4B11 mov edx, [esp+ecx*4+39Ch+var_2B4] +.text:086C4B18 mov eax, [eax+74h] +==== +.text:086C4B0E mov ebx, [eax+edx*4] +.text:086C4B11 mov edx, [esp+ecx*4+39Ch+var_2B4] +.text:086C4B18 mov eax, [ebx+74h] +>>>> + +- id = civzones[i]->children[child_idx[i]]->id ++ cur_civzone = civzones[i] // existing var from previous loop ++ id = cur_civzone->children[child_idx[i]]->id + +The reason being, later code uses that var (at this point containing +useless data) to increment counters and amounts in the hospital. + + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +000746D7: 75 90 +000746D8: D7 90 +0067CB0F: 04 1C +0067CB19: 40 43 diff --git a/patches/v0.34.11 linux/training-ammo.dif b/patches/v0.34.11 linux/training-ammo.dif new file mode 100644 index 000000000..9fbcabe78 --- /dev/null +++ b/patches/v0.34.11 linux/training-ammo.dif @@ -0,0 +1,85 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=4530 + +0x9508 + +90 90 90 90 90 90 90 90 +E9 13 76 9C 00 90 90 90 + +.text:08051508 loc_8051508: +.text:08051508 jmp sub_8A18B20 + +0x9523 + +90 90 90 90 90 90 90 90 90 90 90 90 90 +50 8B 03 53 FF 90 34 02 00 00 EB 09 90 + +.text:08051523 sub_8051523: +.text:08051523 push eax +.text:08051524 mov eax, [ebx] +.text:08051526 push ebx +.text:08051527 call dword ptr [eax+234h] +.text:0805152D jmp short loc_8051538 + +0x9538 + +90 90 90 90 90 90 90 90 +29 44 24 64 5B 58 EB C8 + +.text:08051538 loc_8051538: +.text:08051538 sub [esp+64h], eax +.text:0805153C pop ebx +.text:0805153D pop eax +.text:0805153E jmp short loc_8051508 + +0xa5cdd0 + +29 44 24 58 +90 90 90 90 + +.text:08AA4DD0 nop +.text:08AA4DD1 nop +.text:08AA4DD2 nop +.text:08AA4DD3 nop + +0xa5e2c3 + +E8 58 28 F7 FF +E8 5B B2 5A FF + +.text:08AA62C3 call sub_8051523 + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +00009508: 90 E9 +00009509: 90 13 +0000950A: 90 76 +0000950B: 90 9C +0000950C: 90 00 +00009523: 90 50 +00009524: 90 8B +00009525: 90 03 +00009526: 90 53 +00009527: 90 FF +00009529: 90 34 +0000952A: 90 02 +0000952B: 90 00 +0000952C: 90 00 +0000952D: 90 EB +0000952E: 90 09 +00009538: 90 29 +00009539: 90 44 +0000953A: 90 24 +0000953B: 90 64 +0000953C: 90 5B +0000953D: 90 58 +0000953E: 90 EB +0000953F: 90 C8 +00A5CDD0: 29 90 +00A5CDD1: 44 90 +00A5CDD2: 24 90 +00A5CDD3: 58 90 +00A5E2C4: 58 5B +00A5E2C5: 28 B2 +00A5E2C6: F7 5A diff --git a/patches/v0.34.11 linux/weaponrack-unassign.dif b/patches/v0.34.11 linux/weaponrack-unassign.dif new file mode 100644 index 000000000..721f235e8 --- /dev/null +++ b/patches/v0.34.11 linux/weaponrack-unassign.dif @@ -0,0 +1,45 @@ +http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 + +Fix use of uninitialized variables to stop auto-unassigning racks: + +0x7ee948 +8B 7C 24 3C +89 C7 90 90 + +.text:08836948 mov edi, eax +.text:0883694A nop +.text:0883694B nop + + +0x7eea2f +8B 7C 24 3C +89 C7 90 90 + +.text:08836A2F mov edi, eax +.text:08836A31 nop +.text:08836A32 nop + + +basically: + + b_squad_id = building->getSpecificSquad(); +- if (b_squad_id != squad->id || !building->canUse(some_squad_id, 4)) ++ if (b_squad_id != squad->id || !building->canUse(b_squad_id, 4)) + unassign(building); + +the reason being, some_other_squad_id contains irrelevant garbage at this point + + +---8<--- +This difference file is created by The Interactive Disassembler + +Dwarf_Fortress +007EE948: 8B 89 +007EE949: 7C C7 +007EE94A: 24 90 +007EE94B: 3C 90 +007EEA2F: 8B 89 +007EEA30: 7C C7 +007EEA31: 24 90 +007EEA32: 3C 90 + diff --git a/scripts/binpatch.lua b/scripts/binpatch.lua new file mode 100644 index 000000000..f0f14e929 --- /dev/null +++ b/scripts/binpatch.lua @@ -0,0 +1,117 @@ +-- Apply or remove binary patches at runtime. + +local utils = require('utils') + +function load_patch(name) + local filename = name + local auto = false + if not string.match(filename, '[./\\]') then + auto = true + filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif' + end + + local file, err = io.open(filename, 'r') + if not file then + if auto and string.match(err, ': No such file or directory') then + return nil, 'no patch '..name..' for '..dfhack.getDFVersion() + else + return nil, err + end + end + + local old_bytes = {} + local new_bytes = {} + + for line in file:lines() do + if string.match(line, '^%x+:') then + local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$') + if not offset then + file:close() + return nil, 'Could not parse: '..line + end + + offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16) + if oldv > 255 or newv > 255 then + file:close() + return nil, 'Invalid byte values: '..line + end + + old_bytes[offset] = oldv + new_bytes[offset] = newv + end + end + + return { name = name, old_bytes = old_bytes, new_bytes = new_bytes } +end + +function rebase_table(input) + local output = {} + local base = dfhack.internal.getImageBase() + for k,v in pairs(input) do + local offset = dfhack.internal.adjustOffset(k) + if not offset then + return nil, string.format('invalid offset: %x', k) + end + output[base + offset] = v + end + return output +end + +function rebase_patch(patch) + local nold, err = rebase_table(patch.old_bytes) + if not nold then return nil, err end + local nnew, err = rebase_table(patch.new_bytes) + if not nnew then return nil, err end + return { name = patch.name, old_bytes = nold, new_bytes = nnew } +end + +function run_command(cmd,name) + local patch, err = load_patch(name) + if not patch then + dfhack.printerr('Could not load: '..err) + return + end + + local rpatch, err = rebase_patch(patch) + if not rpatch then + dfhack.printerr(name..': '..err) + return + end + + if cmd == 'check' then + local old_ok, err, addr = dfhack.internal.patchBytes({}, rpatch.old_bytes) + if old_ok then + print(name..': patch is not applied.') + elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then + print(name..': patch is applied.') + else + dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + end + elseif cmd == 'apply' then + local ok, err, addr = dfhack.internal.patchBytes(rpatch.new_bytes, rpatch.old_bytes) + if ok then + print(name..': applied the patch.') + elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then + print(name..': patch is already applied.') + else + dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + end + elseif cmd == 'remove' then + local ok, err, addr = dfhack.internal.patchBytes(rpatch.old_bytes, rpatch.new_bytes) + if ok then + print(name..': removed the patch.') + elseif dfhack.internal.patchBytes({}, rpatch.old_bytes) then + print(name..': patch is already removed.') + else + dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + end + else + qerror('Invalid command: '..cmd) + end +end + +local cmd,name = ... +if not cmd or not name then + qerror('Usage: binpatch check/apply/remove ') +end +run_command(cmd, name) From 683da39636c6dd9c9a3d3d51061e166cbfec8ca7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 11 Nov 2012 17:24:25 +0400 Subject: [PATCH 158/472] Fix dfusion build on linux. --- plugins/Dfusion/dfusion.cpp | 2 +- plugins/Dfusion/include/OutFile.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index 4507f9a15..15bcfa7a8 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -41,7 +41,7 @@ static int loadObjectFile(lua_State* L) //Lua::Push(L,buf); lua_pushlightuserdata(L,buf); lua_setfield(L,table_pos,"data"); - OutFile::vSymbol& symbols=f.GetSymbols(); + const OutFile::vSymbol &symbols=f.GetSymbols(); lua_newtable(L); for(size_t i=0;i Date: Sun, 11 Nov 2012 17:19:37 +0200 Subject: [PATCH 159/472] Fixed error in dfusion and added some readme. --- NEWS | 8 +++++++- Readme.rst | 22 +++++++++++++++------- plugins/Dfusion/include/OutFile.h | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index 51321be95..ad340447e 100644 --- a/NEWS +++ b/NEWS @@ -12,11 +12,16 @@ DFHack future - removebadthoughts: add --dry-run option New scripts: - region-pops: displays animal populations of the region and allows tweaking them. + - lua: lua interpreter. + - dfusion: misc scripts with a text based menu. + - embark: lets you embark anywhere. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - gui/workflow: a front-end for the workflow plugin. - gui/assign-rack: works together with a binary patch to fix weapon racks. + - gui/gm-editor: an universal editor for lots of dfhack things. + - gui/companion-order: a adventure mode command interface for your companions. Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. @@ -29,7 +34,8 @@ DFHack future properly designated barracks be used again for storage of squad equipment. New Search plugin by falconne: Adds an incremental search function to the Stocks, Trading and Unit List screens. - + Dfusion plugin: + Reworked to make use of lua modules, now all the scripts can be used from other scripts. DFHack v0.34.11-r2 diff --git a/Readme.rst b/Readme.rst index d9021c7cb..668d8be50 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1611,19 +1611,17 @@ twice. dfusion ------- -This is the DFusion lua plugin system by warmist/darius, running as a DFHack plugin. +This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu: + :Friendship: a binary plugin that allows multi race forts (to use make a script that imports plugins.dfusion.friendship and use Friendship:install{table} table should contain list of race names.) + :Embark: a binary plugin that allows multi race embark (to use make a script that imports plugins.dfusion.embark and use Embark:install{table} table should contain list of race names or list of pairs (race-name, caste_id)). -See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=69682.15 +See the bay12 thread for details: http://www.bay12forums.com/smf/index.php?topic=93317.0 -Confirmed working DFusion plugins: - -:simple_embark: allows changing the number of dwarves available on embark. .. note:: * Some of the DFusion plugins aren't completely ported yet. This can lead to crashes. - * This is currently working only on Windows. - * The game will be suspended while you're using dfusion. Don't panic when it doen't respond. + * The game will be suspended while you're using dfusion. Don't panic when it doesn't respond. misery ------ @@ -1836,6 +1834,16 @@ deathcause Focus a body part ingame, and this script will display the cause of death of the creature. +lua +=== +There are three ways to invoke this command: + 1. without any parameters - starts an interactive lua interpreter + 2. -f "filename" or --file "filename" - loads and runs the file indicated by filename + 3. -s ["filename"] or --save ["filename"] - loads and runs the file indicated by filename from save directory. If filename is not supplied it loads "dfhack.lua" + +embark +====== +Allows to embark anywhere. Currently windows only. ======================= In-game interface tools diff --git a/plugins/Dfusion/include/OutFile.h b/plugins/Dfusion/include/OutFile.h index 665a1e132..2ba9ecc80 100644 --- a/plugins/Dfusion/include/OutFile.h +++ b/plugins/Dfusion/include/OutFile.h @@ -105,7 +105,7 @@ public: void GetText(char *ptr); size_t GetTextSize(); void LoadSymbols(); - vSymbol GetSymbols(){LoadSymbols();return symbols;}; + const vSymbol& GetSymbols(){LoadSymbols();return symbols;}; void PrintSymbols(); void PrintRelocations(); protected: From 3eb852a43b6c1b9076084b4b89d2a78dce76f32b Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Nov 2012 21:18:59 +0200 Subject: [PATCH 160/472] Added cheat commands to companion-order, (including rumrushers) --- plugins/lua/dfusion/tools.lua | 70 ++++++++++++++++++++++++++++++--- scripts/gui/companion-order.lua | 57 +++++++++++++++++++++++++-- 2 files changed, 118 insertions(+), 9 deletions(-) diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index e577a9418..86efe009c 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -150,13 +150,8 @@ function project(unit,trg) --TODO add to menu? end function empregnate(unit) if unit==nil then - unit=getSelectedUnit() - end - - if unit==nil then - unit=getCreatureAtPos(getxyz()) + unit=dfhack.gui.getSelectedUnit() end - if unit==nil then error("Failed to empregnate. Unit not selected/valid") end @@ -182,4 +177,67 @@ function empregnate(unit) unit.relations.pregnancy_mystery=1 end menu:add("Empregnate",empregnate) +function healunit(unit) + if unit==nil then + unit=dfhack.gui.getSelectedUnit() + end + + if unit==nil then + error("Failed to Heal unit. Unit not selected/valid") + end + + unit.body.wounds:resize(0) -- memory leak here :/ + unit.body.blood_count=unit.body.blood_max + --set flags for standing and grasping... + unit.status2.able_stand=4 + unit.status2.able_stand_impair=4 + unit.status2.able_grasp=4 + unit.status2.able_grasp_impair=4 + --should also set temperatures, and flags for breath etc... + unit.flags1.dead=false + unit.flags2.calculated_bodyparts=false + unit.flags2.calculated_nerves=false + unit.flags2.circulatory_spray=false + unit.flags2.vision_good=true + unit.flags2.vision_damaged=false + unit.flags2.vision_missing=false + unit.counters.winded=0 + unit.counters.unconscious=0 + for k,v in pairs(unit.body.components) do + for kk,vv in pairs(v) do + if k == 'body_part_status' then v[kk].whole = 0 else v[kk] = 0 end + end + end +end +menu:add("Heal unit",healunit) +function powerup(unit,labor_rating,military_rating,skills) + if unit==nil then + unit=dfhack.gui.getSelectedUnit() + end + if unit==nil then + error("Failed to power up unit. Unit not selected/valid") + end + + if unit.status.current_soul== nil then + error("Failed to power up unit. Unit has no soul") + end + local utils = require 'utils' + labor_rating = labor_rating or 15 + military_rating = military_rating or 70 + + skill =skill or { 0,2,3,4,5,6,7,8,9,10,11,12,13,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,41,42,43,44,45,46,47,48,49,54,55,57,58,59,60,61,62,63,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,95,96,97,98,99,100,101,102,103,104,105,109,110,111,112,113,114,115 } + local military = { 38,39,41,42,43,44,45,46,54,99,100,101,102,103,104,105 } + + for sk,sv in ipairs(skill) do + local new_rating = labor_rating + for _,v in ipairs(military) do + if v == sv then + local new_rating = military_rating + end + end + utils.insert_or_update(unit.status.current_soul.skills, { new = true, id = sv, rating = new_rating, experience = (new_rating * 500) + (new_rating * (new_rating - 1)) * 50}, 'id') + end + +end +menu:add("Power up",powerup) return _ENV \ No newline at end of file diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua index 6b6a79aa2..6a25787e6 100644 --- a/scripts/gui/companion-order.lua +++ b/scripts/gui/companion-order.lua @@ -1,7 +1,8 @@ local gui = require 'gui' local dlg = require 'gui.dialogs' - +local args={...} +local is_cheat=(#args>0 and args[1]=="-c") local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z) local permited_equips={} @@ -28,6 +29,13 @@ function CheckCursor(p) end return true end +function getxyz() -- this will return pointers x,y and z coordinates. + local x=df.global.cursor.x + local y=df.global.cursor.y + local z=df.global.cursor.z + return x,y,z -- return the coords +end + function GetCaste(race_id,caste_id) local race=df.creature_raw.find(race_id) return race.caste[caste_id] @@ -302,8 +310,39 @@ end}, end return true end}, +{name="Get in",f=function (unit_list,pos) + if not CheckCursor(pos) then + return false + end + adv=df.global.world.units.active[0] + item=getItemsAtPos(getxyz())[1] + print(item.id) + for k,v in pairs(unit_list) do + v.riding_item_id=item.id + local ref=df.general_ref_unit_riderst:new() + ref.unit_id=v.id + item.itemrefs:insert("#",ref) + end + return true +end}, +} +local cheats={ +{name="Patch up",f=function (unit_list) + local dft=require("plugins.dfusion.tools") + for k,v in pairs(unit_list) do + dft.healunit(v) + end + return true +end}, +{name="Power up",f=function (unit_list) + local dft=require("plugins.dfusion.tools") + for k,d in pairs(unit_list) do + dft.powerup(d) + end + return true +end}, + } -local cheats={} --[[ todo: add cheats...]]-- function getCompanions(unit) unit=unit or df.global.world.units.active[0] @@ -371,7 +410,14 @@ function CompanionUi:onInput(keys) self:dismiss() end end - --do order + if is_cheat then + idx=idx-#orders + if cheats[idx] and cheats[idx].f then + if cheats[idx].f(self:GetSelectedUnits(),cursor) then + self:dismiss() + end + end + end end end end @@ -393,6 +439,11 @@ function CompanionUi:onRenderBody( dc) for k,v in ipairs(orders) do dc:seek(w/2,k):string(string.char(k+char_A)..". "):string(v.name); end + if is_cheat then + for k,v in ipairs(cheats) do + dc:seek(w/2,k+#orders):string(string.char(k+#orders+char_A)..". "):string(v.name); + end + end end local screen=CompanionUi{unit_list=getCompanions()} screen:show() \ No newline at end of file From ce8ada4419727d20c710a911fd257970667c91ef Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 11 Nov 2012 22:14:00 +0200 Subject: [PATCH 161/472] More bug fixing --- plugins/lua/dfusion/tools.lua | 4 +-- scripts/gui/companion-order.lua | 55 +++++++++++++++++++++++---------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index 86efe009c..f1fdadf2b 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -94,8 +94,8 @@ function MakeFollow(unit,trgunit) trgunit=df.global.world.units.active[0] end unit.relations.group_leader_id=trgunit.id - local u_nem=getNemesis(unit) - local t_nem=getNemesis(trgunit) + local u_nem=dfhack.units.getNemesis(unit) + local t_nem=dfhack.units.getNemesis(trgunit) if u_nem then u_nem.group_leader_id=t_nem.id end diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua index 6a25787e6..b9e3b8300 100644 --- a/scripts/gui/companion-order.lua +++ b/scripts/gui/companion-order.lua @@ -7,6 +7,7 @@ local cursor=xyz2pos(df.global.cursor.x,df.global.cursor.y,df.global.cursor.z) local permited_equips={} permited_equips[df.item_backpackst]="UPPERBODY" +permited_equips[df.item_quiverst]="UPPERBODY" permited_equips[df.item_flaskst]="UPPERBODY" permited_equips[df.item_armorst]="UPPERBODY" permited_equips[df.item_shoesst]="STANCE" @@ -14,7 +15,7 @@ permited_equips[df.item_glovesst]="GRASP" permited_equips[df.item_helmst]="HEAD" permited_equips[df.item_pantsst]="LOWERBODY" function DoesHaveSubtype(item) - if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) then + if df.item_backpackst:is_instance(item) or df.item_flaskst:is_instance(item) or df.item_quiverst:is_instance(item) then return false end return true @@ -263,6 +264,26 @@ end}, end return true end}, +{name="unwield",f=function (unit_list) + + for k,v in pairs(unit_list) do + local wep_count=0 + for _,it in pairs(v.inventory) do + if it.mode==1 then + wep_count=wep_count+1 + end + end + for i=1,wep_count do + for _,it in pairs(v.inventory) do + if it.mode==1 then + dfhack.items.moveToGround(it.item,v.pos) + break + end + end + end + end + return true +end}, --[=[ {name="roam not working :<",f=function (unit_list,pos,dist) --does not work if not CheckCursor(pos) then @@ -310,21 +331,7 @@ end}, end return true end}, -{name="Get in",f=function (unit_list,pos) - if not CheckCursor(pos) then - return false - end - adv=df.global.world.units.active[0] - item=getItemsAtPos(getxyz())[1] - print(item.id) - for k,v in pairs(unit_list) do - v.riding_item_id=item.id - local ref=df.general_ref_unit_riderst:new() - ref.unit_id=v.id - item.itemrefs:insert("#",ref) - end - return true -end}, + } local cheats={ {name="Patch up",f=function (unit_list) @@ -341,7 +348,21 @@ end}, end return true end}, - +{name="get in",f=function (unit_list,pos) + if not CheckCursor(pos) then + return false + end + adv=df.global.world.units.active[0] + item=getItemsAtPos(getxyz())[1] + print(item.id) + for k,v in pairs(unit_list) do + v.riding_item_id=item.id + local ref=df.general_ref_unit_riderst:new() + ref.unit_id=v.id + item.itemrefs:insert("#",ref) + end + return true +end}, } --[[ todo: add cheats...]]-- function getCompanions(unit) From 6cf85b43185d5cd3bab4de368805cfea7f08b912 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 12 Nov 2012 12:26:31 +0400 Subject: [PATCH 162/472] Abstract the back-end from the binpatch script, and use in gui/assign-rack. --- Readme.html | 142 +++++++++++++++++++++--------------- Readme.rst | 11 ++- library/lua/binpatch.lua | 121 ++++++++++++++++++++++++++++++ scripts/binpatch.lua | 103 ++++---------------------- scripts/gui/assign-rack.lua | 26 +++---- 5 files changed, 239 insertions(+), 164 deletions(-) create mode 100644 library/lua/binpatch.lua diff --git a/Readme.html b/Readme.html index 737963372..b7ca94964 100644 --- a/Readme.html +++ b/Readme.html @@ -501,38 +501,40 @@ access DF memory and allow for easier development of new tools.

                                          • superdwarf
                                          • drainaquifer
                                          • deathcause
                                          • +
                                          • lua
                                          • +
                                          • embark
                                          • -
                                          • In-game interface tools
                                          • +
                                            +
                                            This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
                                            +
                                            - + + +
                                            simple_embark:allows changing the number of dwarves available on embark.
                                            Friendship:a binary plugin that allows multi race forts (to use make a script that imports plugins.dfusion.friendship and use Friendship:install{table} table should contain list of race names.)
                                            Embark:a binary plugin that allows multi race embark (to use make a script that imports plugins.dfusion.embark and use Embark:install{table} table should contain list of race names or list of pairs (race-name, caste_id)).
                                            + + +

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

                                            Note

                                            • Some of the DFusion plugins aren't completely ported yet. This can lead to crashes.
                                            • -
                                            • This is currently working only on Windows.
                                            • -
                                            • The game will be suspended while you're using dfusion. Don't panic when it doen't respond.
                                            • +
                                            • The game will be suspended while you're using dfusion. Don't panic when it doesn't respond.
                                            @@ -2726,9 +2732,25 @@ superdwarf add

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

                                            +
                                            +

                                            lua

                                            +
                                            +
                                            There are three ways to invoke this command:
                                            +
                                              +
                                            1. without any parameters - starts an interactive lua interpreter
                                            2. +
                                            3. -f "filename" or --file "filename" - loads and runs the file indicated by filename
                                            4. +
                                            5. -s ["filename"] or --save ["filename"] - loads and runs the file indicated by filename from save directory. If filename is not supplied it loads "dfhack.lua"
                                            6. +
                                            +
                                            +
                                            +
                                            +
                                            +

                                            embark

                                            +

                                            Allows to embark anywhere. Currently windows only.

                                            +
                                            -

                                            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.

                                            @@ -2739,7 +2761,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 @@ -2775,7 +2797,7 @@ 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.

                                            @@ -2809,7 +2831,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.

                                              @@ -2825,14 +2847,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 @@ -2842,13 +2864,13 @@ only that entry, and does it even if it is not 'individual choice'.

                                            and may lead to inappropriate weapons being selected.

                                            -

                                            gui/guide-path

                                            +

                                            gui/guide-path

                                            Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                            The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

                                            -

                                            gui/workshop-job

                                            +

                                            gui/workshop-job

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

                                            @@ -2876,7 +2898,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                            -

                                            gui/workflow

                                            +

                                            gui/workflow

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2898,7 +2920,7 @@ as described in workflow documentation above. can be used for troubleshooting jobs that don't match the right constraints.

                                            -

                                            gui/assign-rack

                                            +

                                            gui/assign-rack

                                            Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                            This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

                                            @@ -2907,7 +2929,9 @@ work again. The existing issues are:

                                            beds/boxes/armor stands and individual squad members, but nothing in the game does this. This issue is what this script addresses.
                                          • Even if assigned by the script, the game will unassign the racks again without a binary patch. -Check the comments for this bug to get it: +This patch is called weaponrack-unassign, and can be applied via +the binpatch program, or the matching script. See this for more info +about the bug: http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445
                                          • Haulers still take equpment stored in the armory away to the stockpiles, unless the fix-armory plugin above is used.
                                          • @@ -2918,7 +2942,7 @@ the intended user.

                                            -

                                            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.

                                            @@ -2929,20 +2953,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 @@ -2963,7 +2987,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 @@ -2972,11 +2996,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 @@ -2987,7 +3011,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.

                                            @@ -3011,7 +3035,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 @@ -3042,7 +3066,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 @@ -3051,7 +3075,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 @@ -3062,7 +3086,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 66f4017c7..ed2fe3ee8 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1103,7 +1103,7 @@ fix-armory Enables a fix for storage of squad equipment in barracks. -Specifically, it prevents your haulers from moving that equipment +Specifically, it prevents your haulers from moving squad equipment to stockpiles, and instead queues jobs to store it on weapon racks, armor stands, and in containers. @@ -1113,9 +1113,10 @@ armor stands, and in containers. manually assigned to a squad. See documentation for ``gui/assign-rack`` below. - Also, the default capacity of armor stands is way too low, so check out + Also, the default capacity of armor stands is way too low, so you + may want to also apply the ``armorstand-capacity`` patch. Check out http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 - for a patch addressing that too. + for more information about the bugs. Note that the buildings in the armory are used as follows: @@ -2165,7 +2166,9 @@ work again. The existing issues are: the game does this. This issue is what this script addresses. * Even if assigned by the script, **the game will unassign the racks again without a binary patch**. - Check the comments for this bug to get it: + This patch is called ``weaponrack-unassign``, and can be applied via + the binpatch program, or the matching script. See this for more info + about the bug: http://www.bay12games.com/dwarves/mantisbt/view.php?id=1445 * Haulers still take equpment stored in the armory away to the stockpiles, diff --git a/library/lua/binpatch.lua b/library/lua/binpatch.lua new file mode 100644 index 000000000..e957148f7 --- /dev/null +++ b/library/lua/binpatch.lua @@ -0,0 +1,121 @@ +-- Simple binary patch with IDA dif file support. + +local function load_patch(name) + local filename = name + if not string.match(filename, '[./\\]') then + filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif' + end + + local file, err = io.open(filename, 'r') + if not file then + if string.match(err, ': No such file or directory') then + return nil, 'patch not found' + end + end + + local old_bytes = {} + local new_bytes = {} + + for line in file:lines() do + if string.match(line, '^%x+:') then + local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$') + if not offset then + file:close() + return nil, 'could not parse: '..line + end + + offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16) + if oldv > 255 or newv > 255 then + file:close() + return nil, 'invalid byte values: '..line + end + + old_bytes[offset] = oldv + new_bytes[offset] = newv + end + end + + return { name = name, old_bytes = old_bytes, new_bytes = new_bytes } +end + +local function rebase_table(input) + local output = {} + local base = dfhack.internal.getImageBase() + for k,v in pairs(input) do + local offset = dfhack.internal.adjustOffset(k) + if not offset then + return nil, string.format('invalid offset: %x', k) + end + output[base + offset] = v + end + return output +end + +local function rebase_patch(patch) + local nold, err = rebase_table(patch.old_bytes) + if not nold then return nil, err end + local nnew, err = rebase_table(patch.new_bytes) + if not nnew then return nil, err end + return { name = patch.name, old_bytes = nold, new_bytes = nnew } +end + +BinaryPatch = defclass(BinaryPatch) + +BinaryPatch.ATTRS { + name = DEFAULT_NIL, + old_bytes = DEFAULT_NIL, + new_bytes = DEFAULT_NIL, +} + +function load_dif_file(name) + local patch, err = load_patch(name) + if not patch then return nil, err end + + local rpatch, err = rebase_patch(patch) + if not rpatch then return nil, err end + + return BinaryPatch(rpatch) +end + +function BinaryPatch:status() + local old_ok, err, addr = dfhack.internal.patchBytes({}, self.old_bytes) + if old_ok then + return 'removed' + elseif dfhack.internal.patchBytes({}, self.new_bytes) then + return 'applied' + else + return 'conflict', addr + end +end + +function BinaryPatch:isApplied() + return dfhack.internal.patchBytes({}, self.new_bytes) +end + +function BinaryPatch:apply() + local ok, err, addr = dfhack.internal.patchBytes(self.new_bytes, self.old_bytes) + if ok then + return true, 'applied the patch' + elseif dfhack.internal.patchBytes({}, self.new_bytes) then + return true, 'patch is already applied' + else + return false, string.format('conflict at address %x', addr) + end +end + +function BinaryPatch:isRemoved() + return dfhack.internal.patchBytes({}, self.old_bytes) +end + +function BinaryPatch:remove() + local ok, err, addr = dfhack.internal.patchBytes(self.old_bytes, self.new_bytes) + if ok then + return true, 'removed the patch' + elseif dfhack.internal.patchBytes({}, self.old_bytes) then + return true, 'patch is already removed' + else + return false, string.format('conflict at address %x', addr) + end +end + +return _ENV diff --git a/scripts/binpatch.lua b/scripts/binpatch.lua index f0f14e929..b9a4cf0b1 100644 --- a/scripts/binpatch.lua +++ b/scripts/binpatch.lua @@ -1,109 +1,36 @@ -- Apply or remove binary patches at runtime. -local utils = require('utils') - -function load_patch(name) - local filename = name - local auto = false - if not string.match(filename, '[./\\]') then - auto = true - filename = dfhack.getHackPath()..'/patches/'..dfhack.getDFVersion()..'/'..name..'.dif' - end - - local file, err = io.open(filename, 'r') - if not file then - if auto and string.match(err, ': No such file or directory') then - return nil, 'no patch '..name..' for '..dfhack.getDFVersion() - else - return nil, err - end - end - - local old_bytes = {} - local new_bytes = {} - - for line in file:lines() do - if string.match(line, '^%x+:') then - local offset, oldv, newv = string.match(line, '^(%x+):%s*(%x+)%s+(%x+)%s*$') - if not offset then - file:close() - return nil, 'Could not parse: '..line - end - - offset, oldv, newv = tonumber(offset,16), tonumber(oldv,16), tonumber(newv,16) - if oldv > 255 or newv > 255 then - file:close() - return nil, 'Invalid byte values: '..line - end - - old_bytes[offset] = oldv - new_bytes[offset] = newv - end - end - - return { name = name, old_bytes = old_bytes, new_bytes = new_bytes } -end - -function rebase_table(input) - local output = {} - local base = dfhack.internal.getImageBase() - for k,v in pairs(input) do - local offset = dfhack.internal.adjustOffset(k) - if not offset then - return nil, string.format('invalid offset: %x', k) - end - output[base + offset] = v - end - return output -end - -function rebase_patch(patch) - local nold, err = rebase_table(patch.old_bytes) - if not nold then return nil, err end - local nnew, err = rebase_table(patch.new_bytes) - if not nnew then return nil, err end - return { name = patch.name, old_bytes = nold, new_bytes = nnew } -end +local bp = require('binpatch') function run_command(cmd,name) - local patch, err = load_patch(name) - if not patch then - dfhack.printerr('Could not load: '..err) - return - end + local pfix = name..': ' - local rpatch, err = rebase_patch(patch) - if not rpatch then - dfhack.printerr(name..': '..err) + local patch, err = bp.load_dif_file(name) + if not patch then + dfhack.printerr(pfix..err) return end if cmd == 'check' then - local old_ok, err, addr = dfhack.internal.patchBytes({}, rpatch.old_bytes) - if old_ok then - print(name..': patch is not applied.') - elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then - print(name..': patch is applied.') + local status, addr = patch:status() + if status == 'conflict' then + dfhack.printerr(string.format('%sconflict at address %x', pfix, addr)) else - dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + print(pfix..'patch is '..status) end elseif cmd == 'apply' then - local ok, err, addr = dfhack.internal.patchBytes(rpatch.new_bytes, rpatch.old_bytes) + local ok, msg = patch:apply() if ok then - print(name..': applied the patch.') - elseif dfhack.internal.patchBytes({}, rpatch.new_bytes) then - print(name..': patch is already applied.') + print(pfix..msg) else - dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + dfhack.printerr(pfix..msg) end elseif cmd == 'remove' then - local ok, err, addr = dfhack.internal.patchBytes(rpatch.old_bytes, rpatch.new_bytes) + local ok, msg = patch:remove() if ok then - print(name..': removed the patch.') - elseif dfhack.internal.patchBytes({}, rpatch.old_bytes) then - print(name..': patch is already removed.') + print(pfix..msg) else - dfhack.printerr(string.format('%s: conflict at address %x', name, addr)) + dfhack.printerr(pfix..msg) end else qerror('Invalid command: '..cmd) diff --git a/scripts/gui/assign-rack.lua b/scripts/gui/assign-rack.lua index d358dfff1..92535d793 100644 --- a/scripts/gui/assign-rack.lua +++ b/scripts/gui/assign-rack.lua @@ -1,19 +1,13 @@ --- Assign weapon racks to squads. Requires patch from bug 1445. +-- Assign weapon racks to squads. Requires the weaponrack-unassign patch. ---[[ - - Required patches: - - v0.34.11 linux: http://pastebin.com/mt5EUgFZ - v0.34.11 windows: http://pastebin.com/09nRCybe - -]] +-- See bug 1445 for more info about the patches. local utils = require 'utils' local gui = require 'gui' local guidm = require 'gui.dwarfmode' local widgets = require 'gui.widgets' local dlg = require 'gui.dialogs' +local bp = require 'binpatch' AssignRack = defclass(AssignRack, guidm.MenuOverlay) @@ -190,12 +184,18 @@ end AssignRack{ building = dfhack.gui.getSelectedBuilding() }:show() -if not already_warned then - already_warned = true +if not already_patched then + local patch = bp.load_dif_file('weaponrack-unassign') + if patch and patch:isApplied() then + already_patched = true + end +end + +if not already_patched then dlg.showMessage( 'BUG ALERT', - { 'This script requires a binary patch from', NEWLINE, - 'bug 1445 on the tracker. Otherwise the game', NEWLINE, + { 'This script requires applying the binary patch', NEWLINE, + 'named weaponrack-unassign. Otherwise the game', NEWLINE, 'will lose your settings due to a bug.' }, COLOR_YELLOW ) From bd75cad508859ecb4f0d35000803de293d62cb3b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 12 Nov 2012 12:48:17 +0400 Subject: [PATCH 163/472] Support ! and ~ prefixes in the lua script, and edit readme. --- Readme.html | 23 +++++++++++++++-------- Readme.rst | 23 +++++++++++++++++++---- scripts/lua.lua | 31 +++++++++++++++++++++++++------ 3 files changed, 59 insertions(+), 18 deletions(-) diff --git a/Readme.html b/Readme.html index b7ca94964..c596371e7 100644 --- a/Readme.html +++ b/Readme.html @@ -2734,15 +2734,22 @@ the creature.

                                            lua

                                            -
                                            -
                                            There are three ways to invoke this command:
                                            -
                                              -
                                            1. without any parameters - starts an interactive lua interpreter
                                            2. -
                                            3. -f "filename" or --file "filename" - loads and runs the file indicated by filename
                                            4. -
                                            5. -s ["filename"] or --save ["filename"] - loads and runs the file indicated by filename from save directory. If filename is not supplied it loads "dfhack.lua"
                                            6. +

                                              There are the following ways to invoke this command:

                                              +
                                                +
                                              1. lua (without any parameters)

                                                +

                                                This starts an interactive lua interpreter.

                                                +
                                              2. +
                                              3. lua -f "filename" or lua --file "filename"

                                                +

                                                This loads and runs the file indicated by filename.

                                                +
                                              4. +
                                              5. lua -s ["filename"] or lua --save ["filename"]

                                                +

                                                This loads and runs the file indicated by filename from the save +directory. If the filename is not supplied, it loads "dfhack.lua".

                                                +
                                              6. +
                                              7. :lua lua statement...

                                                +

                                                Parses and executes the lua statement like the interactive interpreter would.

                                                +
                                              -
                                            -

                                            embark

                                            diff --git a/Readme.rst b/Readme.rst index ed2fe3ee8..009d82d77 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1922,10 +1922,25 @@ the creature. lua === -There are three ways to invoke this command: - 1. without any parameters - starts an interactive lua interpreter - 2. -f "filename" or --file "filename" - loads and runs the file indicated by filename - 3. -s ["filename"] or --save ["filename"] - loads and runs the file indicated by filename from save directory. If filename is not supplied it loads "dfhack.lua" + +There are the following ways to invoke this command: + +1. ``lua`` (without any parameters) + + This starts an interactive lua interpreter. + +2. ``lua -f "filename"`` or ``lua --file "filename"`` + + This loads and runs the file indicated by filename. + +3. ``lua -s ["filename"]`` or ``lua --save ["filename"]`` + + This loads and runs the file indicated by filename from the save + directory. If the filename is not supplied, it loads "dfhack.lua". + +4. ``:lua`` *lua statement...* + + Parses and executes the lua statement like the interactive interpreter would. embark ====== diff --git a/scripts/lua.lua b/scripts/lua.lua index 556962347..9bf6ce793 100644 --- a/scripts/lua.lua +++ b/scripts/lua.lua @@ -1,11 +1,15 @@ +-- Execute lua commands interactively or from files. + local args={...} -if args[1]=="--file" or args[1]=="-f" then +local cmd = args[1] + +if cmd=="--file" or cmd=="-f" then local f,err=loadfile (args[2]) if f==nil then qerror(err) end dfhack.pcall(f,table.unpack(args,3)) -elseif args[1]=="--save" or args[1]=="-s" then +elseif cmd=="--save" or cmd=="-s" then if df.global.world.cur_savegame.save_dir=="" then qerror("Savefile not loaded") end @@ -16,12 +20,27 @@ elseif args[1]=="--save" or args[1]=="-s" then qerror(err) end dfhack.pcall(f,table.unpack(args,3)) -elseif args[1]~=nil then - local f,err=load(args[1],'=(lua command)', 't') +elseif cmd~=nil then + -- Support some of the prefixes allowed by dfhack.interpreter + local prefix + if string.match(cmd, "^[~!]") then + prefix = string.sub(cmd, 1, 1) + cmd = 'return '..string.sub(cmd, 2) + end + + local f,err=load(cmd,'=(lua command)', 't') if f==nil then qerror(err) end - dfhack.pcall(f,table.unpack(args,2)) + + local rv = table.pack(dfhack.safecall(f,table.unpack(args,2))) + + if rv[1] and prefix then + print(table.unpack(rv,2,rv.n)) + if prefix == '~' then + printall(rv[2]) + end + end else dfhack.interpreter("lua","lua.history") -end \ No newline at end of file +end From 766aca4911d72b4b6724c20e37e50fd5280b2e46 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 12 Nov 2012 08:27:58 -0600 Subject: [PATCH 164/472] Rename general_ref vectors for consistency --- library/modules/Buildings.cpp | 4 ++-- library/modules/Items.cpp | 42 +++++++++++++++++------------------ library/modules/Job.cpp | 22 +++++++++--------- library/modules/Units.cpp | 10 ++++----- library/xml | 2 +- plugins/advtools.cpp | 8 +++---- plugins/autodump.cpp | 4 ++-- plugins/autolabor.cpp | 8 +++---- plugins/devel/stockcheck.cpp | 8 +++---- plugins/devel/stripcaged.cpp | 4 ++-- plugins/fix-armory.cpp | 4 ++-- plugins/ruby/building.rb | 4 ++-- plugins/showmood.cpp | 4 ++-- plugins/workflow.cpp | 8 +++---- plugins/zone.cpp | 38 +++++++++++++++---------------- 15 files changed, 85 insertions(+), 85 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 6421114a1..ca6cb2c17 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) @@ -895,7 +895,7 @@ static bool linkForConstruct(df::job* &job, df::building *bld) job = new df::job(); job->job_type = df::job_type::ConstructBuilding; job->pos = df::coord(bld->centerx, bld->centery, bld->z); - job->references.push_back(ref); + job->general_refs.push_back(ref); bld->jobs.push_back(job); diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 877f8abe0..4045e6baa 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) @@ -509,7 +509,7 @@ df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type) { CHECK_NULL_POINTER(item); - return findRef(item->itemrefs, type); + return findRef(item->general_refs, type); } df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type) @@ -530,9 +530,9 @@ bool Items::setOwner(df::item *item, df::unit *unit) { CHECK_NULL_POINTER(item); - for (int i = item->itemrefs.size()-1; i >= 0; i--) + for (int i = item->general_refs.size()-1; i >= 0; i--) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; if (!strict_virtual_cast(ref)) continue; @@ -546,7 +546,7 @@ bool Items::setOwner(df::item *item, df::unit *unit) } delete ref; - vector_erase_at(item->itemrefs, i); + vector_erase_at(item->general_refs, i); } item->flags.bits.owned = false; @@ -561,7 +561,7 @@ bool Items::setOwner(df::item *item, df::unit *unit) ref->unit_id = unit->id; insert_into_vector(unit->owned_items, item->id); - item->itemrefs.push_back(ref); + item->general_refs.push_back(ref); } return true; @@ -580,9 +580,9 @@ void Items::getContainedItems(df::item *item, std::vector *items) items->clear(); - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; if (ref->getType() != general_ref_type::CONTAINS_ITEM) continue; @@ -617,9 +617,9 @@ df::coord Items::getPosition(df::item *item) if (item->flags.bits.in_inventory) { - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; switch (ref->getType()) { @@ -716,9 +716,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) if (item->world_data_id != -1) return false; - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; switch (ref->getType()) { @@ -748,9 +748,9 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) { bool found = false; - for (int i = item->itemrefs.size()-1; i >= 0; i--) + for (int i = item->general_refs.size()-1; i >= 0; i--) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; switch (ref->getType()) { @@ -767,7 +767,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) item2->flags.bits.weight_computed = false; - removeRef(item2->itemrefs, general_ref_type::CONTAINS_ITEM, item->id); + removeRef(item2->general_refs, general_ref_type::CONTAINS_ITEM, item->id); } break; @@ -799,7 +799,7 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) } found = true; - vector_erase_at(item->itemrefs, i); + vector_erase_at(item->general_refs, i); delete ref; } @@ -878,10 +878,10 @@ bool DFHack::Items::moveToContainer(MapExtras::MapCache &mc, df::item *item, df: container->flags.bits.weight_computed = false; ref1->item_id = item->id; - container->itemrefs.push_back(ref1); + container->general_refs.push_back(ref1); ref2->item_id = container->id; - item->itemrefs.push_back(ref2); + item->general_refs.push_back(ref2); return true; } @@ -912,7 +912,7 @@ bool DFHack::Items::moveToBuilding(MapExtras::MapCache &mc, df::item *item, df:: item->flags.bits.in_building=true; ref->building_id=building->id; - item->itemrefs.push_back(ref); + item->general_refs.push_back(ref); auto con=new df::building_actual::T_contained_items; con->item=item; @@ -955,7 +955,7 @@ bool DFHack::Items::moveToInventory( unit->inventory.push_back(newInventoryItem); holderReference->unit_id = unit->id; - item->itemrefs.push_back(holderReference); + item->general_refs.push_back(holderReference); resetUnitInvFlags(unit, newInventoryItem); @@ -1016,7 +1016,7 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item) proj->item = item; ref->projectile_id = proj->id; - item->itemrefs.push_back(ref); + item->general_refs.push_back(ref); linked_list_append(&world->proj_list, proj->link); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 757000885..db0fd73fe 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) @@ -71,14 +71,14 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) pnew->specific_refs.clear(); // Clone refs - for (int i = pnew->references.size()-1; i >= 0; i--) + for (int i = pnew->general_refs.size()-1; i >= 0; i--) { - df::general_ref *ref = pnew->references[i]; + df::general_ref *ref = pnew->general_refs[i]; if (virtual_cast(ref)) - vector_erase_at(pnew->references, i); + vector_erase_at(pnew->general_refs, i); else - pnew->references[i] = ref->clone(); + pnew->general_refs[i] = ref->clone(); } // Clone items @@ -96,8 +96,8 @@ void DFHack::Job::deleteJobStruct(df::job *job) // Only allow free-floating job structs assert(!job->list_link && job->items.empty() && job->specific_refs.empty()); - for (int i = job->references.size()-1; i >= 0; i--) - delete job->references[i]; + for (int i = job->general_refs.size()-1; i >= 0; i--) + delete job->general_refs[i]; for (int i = job->job_items.size()-1; i >= 0; i--) delete job->job_items[i]; @@ -232,9 +232,9 @@ df::building *DFHack::Job::getHolder(df::job *job) { CHECK_NULL_POINTER(job); - for (size_t i = 0; i < job->references.size(); i++) + for (size_t i = 0; i < job->general_refs.size(); i++) { - VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->references[i]); + VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->general_refs[i]); if (ref) return ref->getBuilding(); } @@ -246,9 +246,9 @@ df::unit *DFHack::Job::getWorker(df::job *job) { CHECK_NULL_POINTER(job); - for (size_t i = 0; i < job->references.size(); i++) + for (size_t i = 0; i < job->general_refs.size(); i++) { - VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->references[i]); + VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]); if (ref) return ref->getUnit(); } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 7a0a7549b..aa0459d08 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) @@ -523,9 +523,9 @@ df::item *Units::getContainer(df::unit *unit) { CHECK_NULL_POINTER(unit); - for (size_t i = 0; i < unit->refs.size(); i++) + for (size_t i = 0; i < unit->general_refs.size(); i++) { - df::general_ref *ref = unit->refs[i]; + df::general_ref *ref = unit->general_refs[i]; if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) return ref->getItem(); } @@ -607,9 +607,9 @@ df::nemesis_record *Units::getNemesis(df::unit *unit) if (!unit) return NULL; - for (unsigned i = 0; i < unit->refs.size(); i++) + for (unsigned i = 0; i < unit->general_refs.size(); i++) { - df::nemesis_record *rv = unit->refs[i]->getNemesis(); + df::nemesis_record *rv = unit->general_refs[i]->getNemesis(); if (rv && rv->unit == unit) return rv; } diff --git a/library/xml b/library/xml index 4b2124957..327a9662b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 4b2124957e282683480eaf05922e63c353364ec1 +Subproject commit 327a9662be81627ebcbb3aea11ffbca3e536b7ee diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 59b88087b..5ceea69c8 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -331,7 +331,7 @@ typedef std::pair inv_item; static void listContainerInventory(std::vector *list, df::item *container) { - auto &refs = container->itemrefs; + auto &refs = container->general_refs; for (size_t i = 0; i < refs.size(); i++) { auto ref = refs[i]; @@ -372,9 +372,9 @@ void listUnitInventory(std::vector *list, df::unit *unit) bool isShopItem(df::item *item) { - for (size_t k = 0; k < item->itemrefs.size(); k++) + for (size_t k = 0; k < item->general_refs.size(); k++) { - auto ref = item->itemrefs[k]; + auto ref = item->general_refs[k]; if (virtual_cast(ref)) return true; } @@ -404,7 +404,7 @@ int containsMetalItems(df::item *item, bool all, bool non_trader, bool rec = fal { int cnt = 0; - auto &refs = item->itemrefs; + auto &refs = item->general_refs; for (size_t i = 0; i < refs.size(); i++) { auto ref = refs[i]; diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index cfb73fa8b..5eb25964e 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -277,9 +277,9 @@ command_result df_autodump_destroy_item(color_ostream &out, vector & pa return CR_FAILURE; } - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; if (ref->getType() == general_ref_type::UNIT_HOLDER) { out.printerr("Choosing not to destroy items in unit inventory.\n"); diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 44984cfdb..4ee5c0a54 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1582,9 +1582,9 @@ static int stockcheck(color_ostream &out, vector & parameters) df::unit *holder = 0; df::building *building = 0; - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; switch (ref->getType()) { @@ -1611,9 +1611,9 @@ static int stockcheck(color_ostream &out, vector & parameters) while(nextcontainer) { df::item *thiscontainer = nextcontainer; nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++) + for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) { - df::general_ref *ref = thiscontainer->itemrefs[i]; + df::general_ref *ref = thiscontainer->general_refs[i]; switch (ref->getType()) { diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 452a637fc..57be4b126 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -170,9 +170,9 @@ static command_result stockcheck(color_ostream &out, vector & parameter df::unit *holder = 0; df::building *building = 0; - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; switch (ref->getType()) { @@ -199,9 +199,9 @@ static command_result stockcheck(color_ostream &out, vector & parameter while(nextcontainer) { df::item *thiscontainer = nextcontainer; nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++) + for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) { - df::general_ref *ref = thiscontainer->itemrefs[i]; + df::general_ref *ref = thiscontainer->general_refs[i]; switch (ref->getType()) { diff --git a/plugins/devel/stripcaged.cpp b/plugins/devel/stripcaged.cpp index e3d2a82fc..a7683a9bb 100644 --- a/plugins/devel/stripcaged.cpp +++ b/plugins/devel/stripcaged.cpp @@ -46,9 +46,9 @@ DFHACK_PLUGIN("stripcaged"); bool isContainedInItem(df::unit* unit) { bool contained = false; - for (size_t r=0; r < unit->refs.size(); r++) + for (size_t r=0; r < unit->general_refs.size(); r++) { - df::general_ref * ref = unit->refs[r]; + df::general_ref * ref = unit->general_refs[r]; auto rtype = ref->getType(); if(rtype == df::general_ref_type::CONTAINED_IN_ITEM) { diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 99e5fd500..efa9350ff 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -528,7 +528,7 @@ static bool try_store_item(df::building *target, df::item *item) // job <-> building link href->building_id = target->id; target->jobs.push_back(job); - job->references.push_back(href); + job->general_refs.push_back(href); // Two of the jobs need this link to find the job in canStoreItem(). // They also don't actually need BUILDING_HOLDER, but it doesn't hurt. @@ -539,7 +539,7 @@ static bool try_store_item(df::building *target, df::item *item) if (rdest) { rdest->building_id = target->id; - job->references.push_back(rdest); + job->general_refs.push_back(rdest); } } diff --git a/plugins/ruby/building.rb b/plugins/ruby/building.rb index e863ec5d5..37b91d166 100644 --- a/plugins/ruby/building.rb +++ b/plugins/ruby/building.rb @@ -275,7 +275,7 @@ module DFHack job = Job.cpp_new job.job_type = :ConstructBuilding job.pos = [bld.centerx, bld.centery, bld.z] - job.references << ref + job.general_refs << ref bld.jobs << job job_link job job @@ -346,7 +346,7 @@ module DFHack refbuildingholder = GeneralRefBuildingHolderst.cpp_new job.job_type = :DestroyBuilding refbuildingholder.building_id = bld.id - job.references << refbuildingholder + job.general_refs << refbuildingholder bld.jobs << job job_link job job diff --git a/plugins/showmood.cpp b/plugins/showmood.cpp index 0b3fa8a99..7a3662f82 100644 --- a/plugins/showmood.cpp +++ b/plugins/showmood.cpp @@ -49,9 +49,9 @@ command_result df_showmood (color_ostream &out, vector & parameters) found = true; df::unit *unit = NULL; df::building *building = NULL; - for (size_t i = 0; i < job->references.size(); i++) + for (size_t i = 0; i < job->general_refs.size(); i++) { - df::general_ref *ref = job->references[i]; + df::general_ref *ref = job->general_refs[i]; if (ref->getType() == general_ref_type::UNIT_WORKER) unit = ref->getUnit(); if (ref->getType() == general_ref_type::BUILDING_HOLDER) diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 04e3c13b0..6ba45cfd3 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -974,9 +974,9 @@ static void map_job_constraints(color_ostream &out) static void dryBucket(df::item *item) { - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; if (ref->getType() == general_ref_type::CONTAINS_ITEM) { df::item *obj = ref->getItem(); @@ -996,9 +996,9 @@ static bool itemBusy(df::item *item) { using namespace df::enums::item_type; - for (size_t i = 0; i < item->itemrefs.size(); i++) + for (size_t i = 0; i < item->general_refs.size(); i++) { - df::general_ref *ref = item->itemrefs[i]; + df::general_ref *ref = item->general_refs[i]; if (ref->getType() == general_ref_type::CONTAINS_ITEM) { df::item *obj = ref->getItem(); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 2c63b6af7..15d310dc1 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -753,10 +753,10 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) if(!verbose) return; - //out << "number of refs: " << creature->refs.size() << endl; - for(size_t r = 0; rrefs.size(); r++) + //out << "number of refs: " << creature->general_refs.size() << endl; + for(size_t r = 0; rgeneral_refs.size(); r++) { - df::general_ref* ref = unit->refs.at(r); + df::general_ref* ref = unit->general_refs.at(r); df::general_ref_type refType = ref->getType(); out << " ref#" << r <<" refType#" << refType << " "; //endl; switch(refType) @@ -975,10 +975,10 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() for(size_t i = 0; i < world->units.all.size(); i++) { df::unit * creature = world->units.all[i]; - for(size_t r = 0; rrefs.size(); r++) + for(size_t r = 0; rgeneral_refs.size(); r++) { df::general_ref* ref; - ref = creature->refs.at(r); + ref = creature->general_refs.at(r); if(ref->getType() == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED) { if (strict_virtual_cast(ref)) @@ -1007,9 +1007,9 @@ bool isInBuiltCage(df::unit* unit); bool isAssigned(df::unit* unit) { bool assigned = false; - for (size_t r=0; r < unit->refs.size(); r++) + for (size_t r=0; r < unit->general_refs.size(); r++) { - df::general_ref * ref = unit->refs[r]; + df::general_ref * ref = unit->general_refs[r]; auto rtype = ref->getType(); if( rtype == df::general_ref_type::BUILDING_CIVZONE_ASSIGNED || rtype == df::general_ref_type::BUILDING_CAGED @@ -1029,9 +1029,9 @@ bool isAssigned(df::unit* unit) bool isChained(df::unit* unit) { bool contained = false; - for (size_t r=0; r < unit->refs.size(); r++) + for (size_t r=0; r < unit->general_refs.size(); r++) { - df::general_ref * ref = unit->refs[r]; + df::general_ref * ref = unit->general_refs[r]; auto rtype = ref->getType(); if(rtype == df::general_ref_type::BUILDING_CHAIN) { @@ -1046,9 +1046,9 @@ bool isChained(df::unit* unit) bool isContainedInItem(df::unit* unit) { bool contained = false; - for (size_t r=0; r < unit->refs.size(); r++) + for (size_t r=0; r < unit->general_refs.size(); r++) { - df::general_ref * ref = unit->refs[r]; + df::general_ref * ref = unit->general_refs[r]; auto rtype = ref->getType(); if(rtype == df::general_ref_type::CONTAINED_IN_ITEM) { @@ -1280,14 +1280,14 @@ size_t countFreeEgglayers() bool unassignUnitFromBuilding(df::unit* unit) { bool success = false; - for (std::size_t idx = 0; idx < unit->refs.size(); idx++) + for (std::size_t idx = 0; idx < unit->general_refs.size(); idx++) { - df::general_ref * oldref = unit->refs[idx]; + df::general_ref * oldref = unit->general_refs[idx]; switch(oldref->getType()) { case df::general_ref_type::BUILDING_CIVZONE_ASSIGNED: { - unit->refs.erase(unit->refs.begin() + idx); + unit->general_refs.erase(unit->general_refs.begin() + idx); df::building_civzonest * oldciv = (df::building_civzonest *) oldref->getBuilding(); for(size_t oc=0; ocassigned_creature.size(); oc++) { @@ -1305,7 +1305,7 @@ bool unassignUnitFromBuilding(df::unit* unit) case df::general_ref_type::CONTAINED_IN_ITEM: { // game does not erase the ref until creature gets removed from cage - //unit->refs.erase(unit->refs.begin() + idx); + //unit->general_refs.erase(unit->general_refs.begin() + idx); // walk through buildings, check cages for inhabitants, compare ids for (size_t b=0; b < world->buildings.all.size(); b++) @@ -1335,7 +1335,7 @@ bool unassignUnitFromBuilding(df::unit* unit) case df::general_ref_type::BUILDING_CHAIN: { // try not erasing the ref and see what happens - //unit->refs.erase(unit->refs.begin() + idx); + //unit->general_refs.erase(unit->general_refs.begin() + idx); // probably need to delete chain reference here //success = true; break; @@ -1344,7 +1344,7 @@ bool unassignUnitFromBuilding(df::unit* unit) case df::general_ref_type::BUILDING_CAGED: { // not sure what to do here, doesn't seem to get used by the game - //unit->refs.erase(unit->refs.begin() + idx); + //unit->general_refs.erase(unit->general_refs.begin() + idx); //success = true; break; } @@ -1396,7 +1396,7 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building } ref->building_id = building->id; - unit->refs.push_back(ref); + unit->general_refs.push_back(ref); df::building_civzonest * civz = (df::building_civzonest *) building; civz->assigned_creature.push_back(unit->id); @@ -1437,7 +1437,7 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building } //ref->building_id = building->id; - //unit->refs.push_back(ref); + //unit->general_refs.push_back(ref); df::building_cagest* civz = (df::building_cagest*) building; civz->assigned_creature.push_back(unit->id); From 7a3de785ec534fd14055befb36dc8d29e630e625 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 12 Nov 2012 08:29:44 -0600 Subject: [PATCH 165/472] Missed a few spots --- scripts/growcrops.rb | 4 ++-- scripts/gui/companion-order.lua | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/growcrops.rb b/scripts/growcrops.rb index e3abe54ac..c591dff87 100644 --- a/scripts/growcrops.rb +++ b/scripts/growcrops.rb @@ -17,7 +17,7 @@ end inventory = Hash.new(0) df.world.items.other[:SEEDS].each { |seed| next if not seed.flags.in_building - next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } + next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] inventory[seed.mat_index] += 1 } @@ -40,7 +40,7 @@ else df.world.items.other[:SEEDS].each { |seed| next if seed.mat_index != wantmat next if not seed.flags.in_building - next if not seed.itemrefs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } + next if not seed.general_refs.find { |ref| ref._rtti_classname == :general_ref_building_holderst } next if seed.grow_counter >= @raws_plant_growdur[seed.mat_index] seed.grow_counter = @raws_plant_growdur[seed.mat_index] count += 1 diff --git a/scripts/gui/companion-order.lua b/scripts/gui/companion-order.lua index b9e3b8300..68bc7ab39 100644 --- a/scripts/gui/companion-order.lua +++ b/scripts/gui/companion-order.lua @@ -359,7 +359,7 @@ end}, v.riding_item_id=item.id local ref=df.general_ref_unit_riderst:new() ref.unit_id=v.id - item.itemrefs:insert("#",ref) + item.general_refs:insert("#",ref) end return true end}, From 55fcb7e3cade121bfb5455e7d4416b5e849c123f Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 12 Nov 2012 08:33:05 -0600 Subject: [PATCH 166/472] One more missed --- plugins/lua/dfusion/tools.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index f1fdadf2b..3e24c169d 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -145,7 +145,7 @@ function project(unit,trg) --TODO add to menu? citem.next=newlink local proj_ref=df.general_ref_projectile:new() proj_ref.projectile_id=p.id - unit.refs:insert(#unit.refs,proj_ref) + unit.general_refs:insert(#unit.general_refs,proj_ref) unit.flags1.projectile=true end function empregnate(unit) From a99d47ee7a2ccbd8ebecac5fafb8803510d672fd Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 12 Nov 2012 08:38:29 -0600 Subject: [PATCH 167/472] Remove UTF-8 BOMs added by notepad --- library/modules/Buildings.cpp | 2 +- library/modules/Items.cpp | 2 +- library/modules/Job.cpp | 2 +- library/modules/Units.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index ca6cb2c17..de121bfa8 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 4045e6baa..91448ab3f 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index db0fd73fe..f913a71b6 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index aa0459d08..d22022914 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -1,4 +1,4 @@ -/* +/* https://github.com/peterix/dfhack Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) From b4dcc7e7adf7c6e1deab4a0a77e0697610c8f6f3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 12 Nov 2012 19:17:32 +0400 Subject: [PATCH 168/472] Add more native api functions for finding general and specific refs. --- Lua API.rst | 24 ++++++++++++++++++++++++ library/LuaApi.cpp | 6 ++++++ library/Types.cpp | 18 ++++++++++++++++++ library/include/Types.h | 4 ++++ library/include/modules/Buildings.h | 3 +++ library/include/modules/Job.h | 5 +++++ library/include/modules/Units.h | 3 +++ library/modules/Buildings.cpp | 14 ++++++++++++++ library/modules/Job.cpp | 15 +++++++++++++++ library/modules/Units.cpp | 23 +++++++++++++++-------- 10 files changed, 107 insertions(+), 8 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index f89750e88..126bc7f9d 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -833,6 +833,14 @@ Job module Prints info about the job item. +* ``dfhack.job.getGeneralRef(job, type)`` + + Searches for a general_ref with the given type. + +* ``dfhack.job.getSpecificRef(job, type)`` + + Searches for a specific_ref with the given type. + * ``dfhack.job.getHolder(job)`` Returns the building holding the job. @@ -879,6 +887,14 @@ Units module Returns true *x,y,z* of the unit, or *nil* if invalid; may be not equal to unit.pos if caged. +* ``dfhack.units.getGeneralRef(unit, type)`` + + Searches for a general_ref with the given type. + +* ``dfhack.units.getSpecificRef(unit, type)`` + + Searches for a specific_ref with the given type. + * ``dfhack.units.getContainer(unit)`` Returns the container (cage) item or *nil*. @@ -1198,6 +1214,14 @@ Burrows module Buildings module ---------------- +* ``dfhack.buildings.getGeneralRef(building, type)`` + + Searches for a general_ref with the given type. + +* ``dfhack.buildings.getSpecificRef(building, type)`` + + Searches for a specific_ref with the given type. + * ``dfhack.buildings.setOwner(item,unit)`` Replaces the owner of the building. If unit is *nil*, removes ownership. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 3862530dd..b186316a8 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1121,6 +1121,8 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,cloneJobStruct), WRAPM(Job,printItemDetails), WRAPM(Job,printJobDetails), + WRAPM(Job,getGeneralRef), + WRAPM(Job,getSpecificRef), WRAPM(Job,getHolder), WRAPM(Job,getWorker), WRAPM(Job,checkBuildingsNow), @@ -1157,6 +1159,8 @@ static const luaL_Reg dfhack_job_funcs[] = { /***** Units module *****/ static const LuaWrapper::FunctionReg dfhack_units_module[] = { + WRAPM(Units, getGeneralRef), + WRAPM(Units, getSpecificRef), WRAPM(Units, getContainer), WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), @@ -1427,6 +1431,8 @@ static bool buildings_containsTile(df::building *bld, int x, int y, bool room) { } static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { + WRAPM(Buildings, getGeneralRef), + WRAPM(Buildings, getSpecificRef), WRAPM(Buildings, setOwner), WRAPM(Buildings, allocInstance), WRAPM(Buildings, checkFreeTiles), diff --git a/library/Types.cpp b/library/Types.cpp index 1ba87f839..80d629934 100644 --- a/library/Types.cpp +++ b/library/Types.cpp @@ -100,6 +100,24 @@ bool DFHack::removeRef(std::vector &vec, df::general_ref_type return false; } +df::item *DFHack::findItemRef(std::vector &vec, df::general_ref_type type) +{ + auto ref = findRef(vec, type); + return ref ? ref->getItem() : NULL; +} + +df::building *DFHack::findBuildingRef(std::vector &vec, df::general_ref_type type) +{ + auto ref = findRef(vec, type); + return ref ? ref->getBuilding() : NULL; +} + +df::unit *DFHack::findUnitRef(std::vector &vec, df::general_ref_type type) +{ + auto ref = findRef(vec, type); + return ref ? ref->getUnit() : NULL; +} + df::specific_ref *DFHack::findRef(std::vector &vec, df::specific_ref_type type) { for (int i = vec.size()-1; i >= 0; i--) diff --git a/library/include/Types.h b/library/include/Types.h index 3b9bf00b6..98dbb7e74 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -80,6 +80,10 @@ namespace DFHack DFHACK_EXPORT df::general_ref *findRef(std::vector &vec, df::general_ref_type type); DFHACK_EXPORT bool removeRef(std::vector &vec, df::general_ref_type type, int id); + DFHACK_EXPORT df::item *findItemRef(std::vector &vec, df::general_ref_type type); + DFHACK_EXPORT df::building *findBuildingRef(std::vector &vec, df::general_ref_type type); + DFHACK_EXPORT df::unit *findUnitRef(std::vector &vec, df::general_ref_type type); + DFHACK_EXPORT df::specific_ref *findRef(std::vector &vec, df::specific_ref_type type); DFHACK_EXPORT bool removeRef(std::vector &vec, df::specific_ref_type type, void *ptr); }// namespace DFHack \ No newline at end of file diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 266aadcb8..53852efbb 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -92,6 +92,9 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building); */ DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map & btypes); +DFHACK_EXPORT df::general_ref *getGeneralRef(df::building *building, df::general_ref_type type); +DFHACK_EXPORT df::specific_ref *getSpecificRef(df::building *building, df::specific_ref_type type); + /** * Sets the owner unit for the building. */ diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 853813073..e33e8717c 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -28,6 +28,8 @@ distribution. #include "Export.h" #include "Module.h" +#include "Types.h" + #include #include "DataDefs.h" @@ -55,6 +57,9 @@ namespace DFHack DFHACK_EXPORT void printItemDetails(color_ostream &out, df::job_item *item, int idx); DFHACK_EXPORT void printJobDetails(color_ostream &out, df::job *job); + DFHACK_EXPORT df::general_ref *getGeneralRef(df::job *job, df::general_ref_type type); + DFHACK_EXPORT df::specific_ref *getSpecificRef(df::job *job, df::specific_ref_type type); + DFHACK_EXPORT df::building *getHolder(df::job *job); DFHACK_EXPORT df::unit *getWorker(df::job *job); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index ab1a5f63a..93e11afb1 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -205,6 +205,9 @@ DFHACK_EXPORT void CopyNameTo(df::unit *creature, df::language_name * target); /// Returns the true position of the unit (non-trivial in case of caged). DFHACK_EXPORT df::coord getPosition(df::unit *unit); +DFHACK_EXPORT df::general_ref *getGeneralRef(df::unit *unit, df::general_ref_type type); +DFHACK_EXPORT df::specific_ref *getSpecificRef(df::unit *unit, df::specific_ref_type type); + DFHACK_EXPORT df::item *getContainer(df::unit *unit); DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index de121bfa8..1667a27ac 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -178,6 +178,20 @@ bool Buildings::ReadCustomWorkshopTypes(map & btypes) return true; } +df::general_ref *Buildings::getGeneralRef(df::building *building, df::general_ref_type type) +{ + CHECK_NULL_POINTER(building); + + return findRef(building->general_refs, type); +} + +df::specific_ref *Buildings::getSpecificRef(df::building *building, df::specific_ref_type type) +{ + CHECK_NULL_POINTER(building); + + return findRef(building->specific_refs, type); +} + bool Buildings::setOwner(df::building *bld, df::unit *unit) { CHECK_NULL_POINTER(bld); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index f913a71b6..5a8a8c7a4 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -35,6 +35,7 @@ using namespace std; #include "Error.h" #include "PluginManager.h" #include "MiscUtils.h" +#include "Types.h" #include "modules/Job.h" #include "modules/Materials.h" @@ -228,6 +229,20 @@ void DFHack::Job::printJobDetails(color_ostream &out, df::job *job) printItemDetails(out, job->job_items[i], i); } +df::general_ref *Job::getGeneralRef(df::job *job, df::general_ref_type type) +{ + CHECK_NULL_POINTER(job); + + return findRef(job->general_refs, type); +} + +df::specific_ref *Job::getSpecificRef(df::job *job, df::specific_ref_type type) +{ + CHECK_NULL_POINTER(job); + + return findRef(job->specific_refs, type); +} + df::building *DFHack::Job::getHolder(df::job *job) { CHECK_NULL_POINTER(job); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index d22022914..ddbb8cb79 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -519,18 +519,25 @@ df::coord Units::getPosition(df::unit *unit) return unit->pos; } -df::item *Units::getContainer(df::unit *unit) +df::general_ref *Units::getGeneralRef(df::unit *unit, df::general_ref_type type) { CHECK_NULL_POINTER(unit); - for (size_t i = 0; i < unit->general_refs.size(); i++) - { - df::general_ref *ref = unit->general_refs[i]; - if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getItem(); - } + return findRef(unit->general_refs, type); +} - return NULL; +df::specific_ref *Units::getSpecificRef(df::unit *unit, df::specific_ref_type type) +{ + CHECK_NULL_POINTER(unit); + + return findRef(unit->specific_refs, type); +} + +df::item *Units::getContainer(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + return findItemRef(unit->general_refs, general_ref_type::CONTAINED_IN_ITEM); } static df::assumed_identity *getFigureIdentity(df::historical_figure *figure) From bbe94c006f56c1c5ea181071473ef280d5bb7a93 Mon Sep 17 00:00:00 2001 From: Quietust Date: Mon, 12 Nov 2012 11:54:21 -0600 Subject: [PATCH 169/472] Update for temperaturest --- library/modules/Vegetation.cpp | 10 +++++----- plugins/steam-engine.cpp | 8 ++++---- plugins/tweak.cpp | 20 ++++++++++---------- scripts/fix/stable-temp.lua | 10 +++++----- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp index 1a6779553..f7c4c9b0c 100644 --- a/library/modules/Vegetation.cpp +++ b/library/modules/Vegetation.cpp @@ -71,15 +71,15 @@ bool Vegetation::copyPlant(const int32_t index, t_plant &out) out.material = out.origin->material; out.pos = out.origin->pos; out.grow_counter = out.origin->grow_counter; - out.temperature_1 = out.origin->temperature_1; - out.temperature_2 = out.origin->temperature_2; + out.temperature_1 = out.origin->temperature.whole; + out.temperature_2 = out.origin->temperature.fraction; out.is_burning = out.origin->is_burning; out.hitpoints = out.origin->hitpoints; out.update_order = out.origin->update_order; //out.unk1 = out.origin->anon_1; //out.unk2 = out.origin->anon_2; - //out.temperature_3 = out.origin->temperature_3; - //out.temperature_4 = out.origin->temperature_4; - //out.temperature_5 = out.origin->temperature_5; + //out.temperature_3 = out.origin->temperature_unk; + //out.temperature_4 = out.origin->min_safe_temp; + //out.temperature_5 = out.origin->max_safe_temp; return true; } diff --git a/plugins/steam-engine.cpp b/plugins/steam-engine.cpp index 60f38ef83..a8ba0e214 100644 --- a/plugins/steam-engine.cpp +++ b/plugins/steam-engine.cpp @@ -263,7 +263,7 @@ struct liquid_hook : df::item_liquid_miscst { DEFINE_VMETHOD_INTERPOSE(bool, checkTemperatureDamage, ()) { if (mat_state.whole & BOILING_FLAG) - temperature = std::max(int(temperature), getBoilingPoint()-1); + temperature.whole = std::max(int(temperature.whole), getBoilingPoint()-1); return INTERPOSE_NEXT(checkTemperatureDamage)(); } @@ -371,8 +371,8 @@ struct workshop_hook : df::building_workshopst { // Update flags liquid->flags.bits.in_building = true; liquid->mat_state.whole |= liquid_hook::BOILING_FLAG; - liquid->temperature = liquid->getBoilingPoint()-1; - liquid->temperature_fraction = 0; + liquid->temperature.whole = liquid->getBoilingPoint()-1; + liquid->temperature.fraction = 0; // This affects where the steam appears to come from if (engine->hearth_tile.isValid()) @@ -387,7 +387,7 @@ struct workshop_hook : df::building_workshopst { { liquid->wear = 4; liquid->flags.bits.in_building = false; - liquid->temperature = liquid->getBoilingPoint() + 10; + liquid->temperature.whole = liquid->getBoilingPoint() + 10; return liquid->checkMeltBoil(); } diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 93e5ab3bc..b4a435421 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -291,18 +291,18 @@ struct stable_temp_hook : df::item_actual { DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult)) { - if (temperature != temp) + if (temperature.whole != temp) { // Bug 6012 is caused by fixed-point precision mismatch jitter // when an item is being pushed by two sources at N and N+1. // This check suppresses it altogether. - if (temp == temperature+1 || - (temp == temperature-1 && temperature_fraction == 0)) - temp = temperature; + if (temp == temperature.whole+1 || + (temp == temperature.whole-1 && temperature.fraction == 0)) + temp = temperature.whole; // When SPEC_HEAT is NONE, the original function seems to not // change the temperature, yet return true, which is silly. else if (getSpecHeat() == 60001) - temp = temperature; + temp = temperature.whole; } return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult); @@ -317,10 +317,10 @@ struct stable_temp_hook : df::item_actual { { auto obj = (*contaminants)[i]; - if (abs(obj->temperature - temperature) == 1) + if (abs(obj->temperature.whole - temperature.whole) == 1) { - obj->temperature = temperature; - obj->temperature_fraction = temperature_fraction; + obj->temperature.whole = temperature.whole; + obj->temperature.fraction = temperature.fraction; } } } @@ -355,11 +355,11 @@ struct fast_heat_hook : df::item_actual { (uint16_t temp, bool local, bool contained, bool adjust, int32_t rate_mult) ) { // Some items take ages to cross the last degree, so speed them up - if (map_temp_mult > 0 && temp != temperature && max_heat_ticks > 0) + if (map_temp_mult > 0 && temp != temperature.whole && max_heat_ticks > 0) { int spec = getSpecHeat(); if (spec != 60001) - rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature)); + rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature.whole)); } return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult); diff --git a/scripts/fix/stable-temp.lua b/scripts/fix/stable-temp.lua index 27a88ef7b..63739f8ea 100644 --- a/scripts/fix/stable-temp.lua +++ b/scripts/fix/stable-temp.lua @@ -8,20 +8,20 @@ local count = 0 local types = {} local function update_temp(item,btemp) - if item.temperature ~= btemp then + if item.temperature.whole ~= btemp then count = count + 1 local tid = item:getType() types[tid] = (types[tid] or 0) + 1 end if apply then - item.temperature = btemp - item.temperature_fraction = 0 + item.temperature.whole = btemp + item.temperature.fraction = 0 if item.contaminants then for _,c in ipairs(item.contaminants) do - c.temperature = btemp - c.temperature_fraction = 0 + c.temperature.whole = btemp + c.temperature.fraction = 0 end end end From dd89baf6f88b5e111dbe21b24a2f951f0799a5e2 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 12 Nov 2012 19:18:03 +0100 Subject: [PATCH 170/472] add raw mmap/mprotect access --- library/Process-darwin.cpp | 27 ++++++++++++++++++++++++- library/Process-linux.cpp | 27 ++++++++++++++++++++++++- library/Process-windows.cpp | 39 +++++++++++++++++++++++++++++++++++++ library/include/MemAccess.h | 21 ++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index d081c8c5c..72311e83a 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -304,4 +304,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +} + +int Process::memDealloc(const void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(const void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 046b7696d..c3995a2aa 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -235,4 +235,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +} + +int Process::memDealloc(void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 6f79236f9..cfa0b688d 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -473,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) return result; } + +void* Process::memAlloc(const int length) +{ + void *ret; + // returns 0 on error + ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!ret) + ret = (void*)-1; + return ret; +} + +int Process::memDealloc(const void *ptr, const int length) +{ + // can only free the whole region at once + // vfree returns 0 on error + return !VirtualFree(ptr, 0, MEM_RELEASE) +} + +int Process::memProtect(const void *ptr, const int length, const int prot) +{ + int prot_native = 0; + int old_prot = 0; + + // only support a few constant combinations + if (prot == 0) + prot_native = PAGE_NOACCESS; + else if (prot == Process::MemProt::READ) + prot_native = PAGE_READONLY; + else if (prot == Process::MemProt::READ | Process::MemProt::WRITE) + prot_native = PAGE_READWRITE; + else if (prot == Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXECUTE) + prot_native = PAGE_EXECUTE_READWRITE; + else if (prot == Process::MemProt::READ | Process::MemProt::EXECUTE) + prot_native = PAGE_EXECUTE_READ; + else + return -1; + + return !VirtualProtect(ptr, length, prot_native, &old_prot); +} diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 22f15eecf..31647a25e 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -291,6 +291,27 @@ namespace DFHack /// write a possibly read-only memory area bool patchMemory(void *target, const void* src, size_t count); + + /// allocate new memory pages for code or stuff + /// returns -1 on error (0 is a valid address) + void* memAlloc(const int length); + + /// free memory pages from memAlloc + /// should have length = alloced length for portability + /// returns 0 on success + int memDealloc(void *ptr, const int length); + + /// change memory page permissions + /// prot is a bitwise OR of the MemProt enum + /// returns 0 on success + int memProtect(void *ptr, const int length, const int prot); + + enum MemProt { + READ = 1, + WRITE = 2, + EXEC = 4 + }; + private: VersionInfo * my_descriptor; PlatformSpecific *d; From 72912edf58ea562592f00c8bab308c13529427a2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Nov 2012 18:45:51 +0400 Subject: [PATCH 171/472] Ensure AddPersistentData won't create duplicate ids. If anything messes around with the histfig vector between calls. --- library/modules/World.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index f3283c3a3..9ae4266b2 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -197,11 +197,15 @@ 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 = 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)); + if (!hfvec.empty()) + hfig->id = std::min(hfig->id, hfvec[0]->id-1); + next_persistent_id = hfig->id-1; + hfvec.insert(hfvec.begin(), hfig); persistent_index.insert(T_persistent_item(key, -hfig->id)); From 423c1224248195fa22d72781b7b26fa84e9dd2c7 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 16 Nov 2012 17:39:08 +0100 Subject: [PATCH 172/472] ruby: fix unit_find for advmode --- plugins/ruby/unit.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 4fbf75d8d..4c638b1a9 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -21,7 +21,7 @@ module DFHack when :SelectTrainer v.trainer_unit[v.trainer_cursor] end - else + when :viewscreen_dwarfmodest case ui.main.mode when :ViewUnits # nobody selected => idx == 0 @@ -33,6 +33,15 @@ module DFHack else ui.follow_unit_tg if ui.follow_unit != -1 end + when :viewscreen_dungeonmodest + case ui_advmode.menu + when :Default + world.units.active[0] + else + unit_find(cursor) # XXX + end + when :viewscreen_dungeon_monsterstatusst + curview.unit end elsif what.kind_of?(Integer) # search by id From 342badac982d63b3c600c5307624e3a4402a93f8 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 16 Nov 2012 17:46:41 +0100 Subject: [PATCH 173/472] scripts/superdwarf: advmode support --- NEWS | 1 + scripts/superdwarf.rb | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 48aaf26a2..4ccb1d18d 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + - superdwarf: work in adventure mode too New scripts: - binpatch: the same as the stand-alone binpatch.exe, but works at runtime. - region-pops: displays animal populations of the region and allows tweaking them. diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index 7f5296b74..6277db97f 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,7 +8,12 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - $superdwarf_onupdate ||= df.onupdate_register(1) { + if df.gamemode == :ADVENTURE + onupdate_delay = nil + else + onupdate_delay = 1 + end + $superdwarf_onupdate ||= df.onupdate_register(onupdate_delay) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil From 2401be1b3b4ed423542e232deeafa8406ffc79b8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Nov 2012 22:48:49 +0400 Subject: [PATCH 174/472] Add an api function to retrieve unit skill experience. --- Lua API.html | 21 +++++++++++++++++++++ Lua API.rst | 4 ++++ library/LuaApi.cpp | 1 + library/include/modules/Units.h | 2 ++ library/modules/Units.cpp | 18 ++++++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/Lua API.html b/Lua API.html index d2e0da1ef..04af5d672 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1109,6 +1109,12 @@ above operations accordingly. If enabled, pauses and zooms to position.

                                          • dfhack.job.printItemDetails(jobitem,idx)

                                            Prints info about the job item.

                                          • +
                                          • dfhack.job.getGeneralRef(job, type)

                                            +

                                            Searches for a general_ref with the given type.

                                            +
                                          • +
                                          • dfhack.job.getSpecificRef(job, type)

                                            +

                                            Searches for a specific_ref with the given type.

                                            +
                                          • dfhack.job.getHolder(job)

                                            Returns the building holding the job.

                                          • @@ -1147,6 +1153,12 @@ the flags in the job item.

                                          • dfhack.units.getPosition(unit)

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

                                          • +
                                          • dfhack.units.getGeneralRef(unit, type)

                                            +

                                            Searches for a general_ref with the given type.

                                            +
                                          • +
                                          • dfhack.units.getSpecificRef(unit, type)

                                            +

                                            Searches for a specific_ref with the given type.

                                            +
                                          • dfhack.units.getContainer(unit)

                                            Returns the container (cage) item or nil.

                                          • @@ -1209,6 +1221,9 @@ is true, subtracts the rust penalty.

                                          • dfhack.units.getEffectiveSkill(unit, skill)

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

                                          • +
                                          • dfhack.units.getExperience(unit, skill[, total])

                                            +

                                            Returns the experience value for the given skill. If total is true, adds experience implied by the current rating.

                                            +
                                          • dfhack.units.computeMovementSpeed(unit)

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

                                          • @@ -1403,6 +1418,12 @@ burrows, or the presence of invaders.

                                            Buildings module

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

                                            +

                                            The example config binds building/unit rename to Ctrl-Shift-N, and +unit profession change to Ctrl-Shift-T.

                                            gui/room-list

                                            -

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

                                            +

                                            To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, +either immediately or after opening the assign owner page.

                                            images/room-list.png

                                            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

                                            -

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

                                            +

                                            Bind to a key (the example config uses Ctrl-W), 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 unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites @@ -2882,14 +2897,16 @@ and may lead to inappropriate weapons being selected.

                                            gui/guide-path

                                            -

                                            Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

                                            +

                                            Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with +the cursor over a Guide order.

                                            images/guide-path.png

                                            The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

                                            gui/workshop-job

                                            -

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            +

                                            Bind to a key (the example config uses Alt-A), and activate with a job selected in +a workshop in the 'q' mode.

                                            images/workshop-job.png

                                            The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

                                            @@ -2924,7 +2941,8 @@ you have to unset the material first.

                                            gui/workflow

                                            -

                                            Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

                                            +

                                            Bind to a key (the example config uses Alt-W), and activate with a job selected +in a workshop in the 'q' mode.

                                            images/workflow.png

                                            This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2953,7 +2971,8 @@ suit your need, and set the item count range.

                                            gui/assign-rack

                                            -

                                            Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

                                            +

                                            Bind to a key (the example config uses P), and activate when viewing a weapon +rack in the 'q' mode.

                                            images/assign-rack.png

                                            This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

                                            @@ -3002,7 +3021,8 @@ e.g. like making siegers bring their own, are something only Toady can do.

                                            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.

                                            +script. Bind it to a key (the example config uses Alt-A) and activate after selecting +a siege engine in 'q' mode.

                                            images/siege-engine.png

                                            The main mode displays the current target, selected ammo item type, linked stockpiles and the allowed operator skill range. The map tile color is changed to signify if it can be @@ -3026,7 +3046,8 @@ menu.

                                            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 -key and activate after selecting Pressure Plate in the build menu.

                                            +key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate +in the build menu.

                                            images/power-meter.png

                                            The script follows the general look and feel of the regular pressure plate build configuration page, but configures parameters relevant to the modded power meter building.

                                            diff --git a/Readme.rst b/Readme.rst index 4488490dd..1f5ac08fd 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1077,6 +1077,9 @@ Subcommands that persist until disabled or DF quit: :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. Does NOT fix the problem when soldiers go off-duty (i.e. civilian). :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. + + .. image:: images/tweak-plate.png + :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. In very item-heavy forts with big stockpiles this can improve FPS by 50-100% :fast-heat: Further improves temperature update performance by ensuring that 1 degree @@ -1095,9 +1098,16 @@ Subcommands that persist until disabled or DF quit: :military-stable-assign: Preserve list order and cursor position when assigning to squad, i.e. stop the rightmost list of the Positions page of the military screen from constantly resetting to the top. -:military-color-assigned: Color squad candidates already assigned to other squads in brown/green +:military-color-assigned: Color squad candidates already assigned to other squads in yellow/green to make them stand out more in the list. + .. image:: images/tweak-mil-color.png + +:military-training: Speeds up melee squad training by removing an almost certainly + unintended inverse dependency of training speed on unit count + (i.e. the more units you have, the slower it becomes), and making + the units spar more. + fix-armory ---------- @@ -2039,7 +2049,7 @@ key while search is active clears the search instead of executing the trade. gui/liquids =========== -To use, bind to a key and activate in the 'k' mode. +To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode. .. image:: images/liquids.png @@ -2050,7 +2060,7 @@ to select the target area and apply changes. gui/mechanisms ============== -To use, bind to a key and activate in the 'q' mode. +To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode. .. image:: images/mechanisms.png @@ -2088,12 +2098,15 @@ via a simple dialog in the game ui. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. +The example config binds building/unit rename to Ctrl-Shift-N, and +unit profession change to Ctrl-Shift-T. + gui/room-list ============= -To use, bind to a key and activate in the 'q' mode, either immediately or after opening -the assign owner page. +To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, +either immediately or after opening the assign owner page. .. image:: images/room-list.png @@ -2104,7 +2117,8 @@ list, and allows unassigning them. gui/choose-weapons ================== -Bind to a key, and activate in the Equip->View/Customize page of the military screen. +Bind to a key (the example config uses Ctrl-W), 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 @@ -2118,7 +2132,8 @@ and may lead to inappropriate weapons being selected. gui/guide-path ============== -Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. +Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with +the cursor over a Guide order. .. image:: images/guide-path.png @@ -2129,7 +2144,8 @@ computes it when the order is executed for the first time. gui/workshop-job ================ -Bind to a key, and activate with a job selected in a workshop in the 'q' mode. +Bind to a key (the example config uses Alt-A), and activate with a job selected in +a workshop in the 'q' mode. .. image:: images/workshop-job.png @@ -2176,7 +2192,8 @@ you have to unset the material first. gui/workflow ============ -Bind to a key, and activate with a job selected in a workshop in the 'q' mode. +Bind to a key (the example config uses Alt-W), and activate with a job selected +in a workshop in the 'q' mode. .. image:: images/workflow.png @@ -2217,7 +2234,8 @@ If you don't need advanced settings, you can just press 'y' to confirm creation. gui/assign-rack =============== -Bind to a key, and activate when viewing a weapon rack in the 'q' mode. +Bind to a key (the example config uses P), and activate when viewing a weapon +rack in the 'q' mode. .. image:: images/assign-rack.png @@ -2278,7 +2296,8 @@ 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. +script. Bind it to a key (the example config uses Alt-A) and activate after selecting +a siege engine in 'q' mode. .. image:: images/siege-engine.png @@ -2310,7 +2329,8 @@ The power-meter plugin implements a modified pressure plate that detects power b 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 -key and activate after selecting Pressure Plate in the build menu. +key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate +in the build menu. .. image:: images/power-meter.png diff --git a/images/tweak-mil-color.png b/images/tweak-mil-color.png new file mode 100644 index 0000000000000000000000000000000000000000..b4a012cabf69fe7ab62d0941dcd018f946c86eab GIT binary patch literal 6967 zcmZ{Jby!qgwEhG`4KX04)F2_<(hMUxbP5b1(k0!^AZftRA)$mwhjd8^LwBbjA_5=M zDPF!`-g}?t{&DtN=Y7t*pMCaP>+G}N9jBwELQ4FA7ytl}s-hJ2001x?008phf$xw3 z@F3Bhq1VwcPznzZ2ZV=rc6R>#`}Yn5+%@5Vf8D=-3nJb&-XTCMbrnT01&PMd-3^gD z3he~|kY(Rpy+EI{NDKh*KwnkyiGly(e$KAPG55XxH5yS>2nnf;JGjevo9emWx-KMB z7rd=77%Jz!0DZ5{Yw9^FeEidc4Gj;qqP>0A`}ej{!snc$EoH&>YVCEA__o>kk7boP z$^Npdg54JXb=Li$>sb2jmP#*DI87%#wA`v({JF*j0Y~Tq?Mq^sPVo-l(XtW2Wb>#` z&aT<{+2PrffVL3tl_iz!ZVrw)AMBp2Q5JnQkDkcZ-12zHXOhBYq*tM)V|}B^$JUB>p?j3!rk7B?={=6&r27Ac`d= z!cVj#>+K!>%@T8>F{7rf<}QPsjFIyrjg+zGgA=49h>}wgvlzkRP!glAQgS*>nn!Lm%c0j6dNF`A6Z#$TX zCinqL*wgWz+cA=yYDQ>wXpmz!5&5joMoA?rkKWIH+DVc2=LXkLctXIvm9q~Lf_@AU z*UMdedpB6<_Xw|dw8CTOELZG*QxjW)<_B1F_)@iIRe9bJWb4DTn0`#FC$pSQcs$v1 zM7Uo1ONkre)P}fZVwME3)|$5zww1=tjUOP7{&YF9@E{|k8m-}EK}em%Fgw7)$B$KO<2iy8X6>*nCvYAK;L@F|p z_EC>Tqc*%>3RYS|xbkgXZNN`~Sw5TrFi4|!$imsa4{R=%#n#o8#O1>!XmXBqVT-eiavvu!wP0DC19nfdklgan;~w&Y>&Qk^zrEb%lY=P*GIAh^yxscg zDp~rZs51klV0I-o`wDXRsMZR#>$jipDcoiV(4dJcMUV#Q>TE<7uj(B`WW#JbtrP0|5#`Ej zy3NZFTQ;+tOkr-p$6Sx7@XVm~g!+h!G?v7GjeJxuUr}#PQoi;r;!Ov31yK)yqU!bC zL;8bLc&qF2P<*U-5h_#My3w-zWk8m%vEtybN9qAvrNv;?Arfk&dr6CqbuReB_DZrY zur~uACMVa3wyi;!d6UFia1B8!=EQ#y1=lz*%0Ad@CEs32P3#)l4lY9i?6$?23EzOw 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 Date: Thu, 22 Nov 2012 02:57:55 +0100 Subject: [PATCH 200/472] add scripts/lever, add binary patches section in NEWS file --- NEWS | 9 ++++ scripts/lever.rb | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 scripts/lever.rb diff --git a/NEWS b/NEWS index f14bcb1b4..e12608d34 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ DFHack future - lua: lua interpreter front-end converted to a script from a native command. - dfusion: misc scripts with a text based menu. - embark: lets you embark anywhere. + - lever: list and pull fort levers from the dfhack console. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. @@ -26,6 +27,14 @@ DFHack future - gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/gm-editor: an universal editor for lots of dfhack things. - gui/companion-order: a adventure mode command interface for your companions. + New binary patches: + - armorstand-capacity + - custom-reagent-size + - deconstruct-heapfall + - deconstruct-teleport + - hospital-overstocking + - training-ammo + - weaponrack-unassign Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. diff --git a/scripts/lever.rb b/scripts/lever.rb new file mode 100644 index 000000000..2012f7297 --- /dev/null +++ b/scripts/lever.rb @@ -0,0 +1,109 @@ +# control your levers from the dfhack console + +def lever_pull_job(bld) + ref = DFHack::GeneralRefBuildingHolderst.cpp_new + ref.building_id = bld.id + + job = DFHack::Job.cpp_new + job.job_type = :PullLever + job.pos = [bld.centerx, bld.centery, bld.z] + job.general_refs << ref + bld.jobs << job + df.job_link job +end + +def lever_pull_cheat(bld) + bld.state = (bld.state == 0 ? 1 : 0) + + bld.linked_mechanisms.each { |i| + i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| + tg = r.building_tg + next if tg.gate_flags.closing or tg.gate_flags.opening + r.building_tg.setTriggerState(tg.gate_flags.closed ? 0 : 1) + } + } + + puts lever_descr(bld) +end + +def lever_descr(bld, idx=nil) + ret = [] + + # lever description + descr = '' + descr << "#{idx}: " if idx + descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + + bld.linked_mechanisms.map { |i| + i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) + }.flatten.each { |r| + # linked building description + tg = r.building_tg + state = tg.gate_flags.closed ? 'closed' : 'opened' + state << ', closing' if tg.gate_flags.closing + state << ', opening' if tg.gate_flags.opening + + ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") + + # indent other links + descr = descr.gsub(/./, ' ') + } + + ret << descr if ret.empty? + + ret +end + +def lever_list + @lever_list = [] + df.world.buildings.other[:TRAP].find_all { |bld| + bld.trap_type == :Lever + }.sort_by { |bld| bld.id }.each { |bld| + puts lever_descr(bld, @lever_list.length) + @lever_list << bld.id + } +end + +case $script_args[0] +when 'pull' + cheat = $script_args.delete('--cheat') || $script_args.delete('--now') + + id = $script_args[1].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + if cheat + lever_pull_cheat(bld) + else + lever_pull_job(bld) + end + +when 'list' + lever_list + +when /^\d+$/ + id = $script_args[0].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + puts lever_descr(bld) + +else + puts < Date: Thu, 22 Nov 2012 03:17:41 +0100 Subject: [PATCH 201/472] script/lever: synchronize linked buildings as the game does --- scripts/lever.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/lever.rb b/scripts/lever.rb index 2012f7297..2c7735146 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -13,16 +13,14 @@ def lever_pull_job(bld) end def lever_pull_cheat(bld) - bld.state = (bld.state == 0 ? 1 : 0) - bld.linked_mechanisms.each { |i| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| - tg = r.building_tg - next if tg.gate_flags.closing or tg.gate_flags.opening - r.building_tg.setTriggerState(tg.gate_flags.closed ? 0 : 1) + r.building_tg.setTriggerState(bld.state) } } + bld.state = (bld.state == 0 ? 1 : 0) + puts lever_descr(bld) end From e7905a5cff2b6174d92527ab6bbdd8358b98b877 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 22 Nov 2012 19:38:45 +0400 Subject: [PATCH 202/472] Add docs for the automaterial plugin, and use the new Painter class. --- NEWS | 3 + Readme.html | 123 ++++++++++++++++++++----------- Readme.rst | 39 ++++++++++ images/automaterial-mat.png | Bin 0 -> 5187 bytes images/automaterial-pos.png | Bin 0 -> 3007 bytes library/include/modules/Screen.h | 4 + plugins/automaterial.cpp | 69 ++++++----------- 7 files changed, 147 insertions(+), 91 deletions(-) create mode 100644 images/automaterial-mat.png create mode 100644 images/automaterial-pos.png diff --git a/NEWS b/NEWS index e12608d34..6ba9a769f 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,9 @@ DFHack future properly designated barracks be used again for storage of squad equipment. New Search plugin by falconne: Adds an incremental search function to the Stocks, Trading and Unit List screens. + New AutoMaterial plugin by falconne: + Makes building constructions (walls, floors, fortifications, etc) a little bit easier by + saving you from having to trawl through long lists of materials each time you place one. Dfusion plugin: Reworked to make use of lua modules, now all the scripts can be used from other scripts. diff --git a/Readme.html b/Readme.html index f520e1a82..5b231b429 100644 --- a/Readme.html +++ b/Readme.html @@ -508,33 +508,34 @@ access DF memory and allow for easier development of new tools.

                                          • In-game interface tools
                                          • -
                                          • Behavior Mods
                                          • @@ -2836,15 +2846,42 @@ are actually visible in the list; the same effect applies to the Trade Value numbers displayed by the screen. Because of this, pressing the 't' key while search is active clears the search instead of executing the trade.

                                            +
                                            +

                                            AutoMaterial

                                            +

                                            The automaterial plugin makes building constructions (walls, floors, fortifications, +etc) a little bit easier by saving you from having to trawl through long lists of +materials each time you place one.

                                            +

                                            Firstly, it moves the last used material for a given construction type to the top of +the list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, regardless of +distance (it only does this in "grouped" mode, as individual item lists could be huge). +This should mean you can place most constructions without having to search for your +preferred material type.

                                            +images/automaterial-mat.png +

                                            Pressing 'a' while highlighting any material will enable that material for "auto select" +for this construction type. You can enable multiple materials as autoselect. Now the next +time you place this type of construction, the plugin will automatically choose materials +for you from the kinds you enabled. If there is enough to satisfy the whole placement, +you won't be prompted with the material screen - the construction will be placed and you +will be back in the construction menu as if you did it manually.

                                            +

                                            When choosing the construction placement, you will see a couple of options:

                                            +images/automaterial-pos.png +

                                            Use 'a' here to temporarily disable the material autoselection, e.g. if you need +to go to the material selection screen so you can toggle some materials on or off.

                                            +

                                            The other option (auto type selection, off by default) can be toggled on with 't'. If you +toggle this option on, instead of returning you to the main construction menu after selecting +materials, it returns you back to this screen. If you use this along with several autoselect +enabled materials, you should be able to place complex constructions more conveniently.

                                            +
                                            -

                                            gui/liquids

                                            +

                                            gui/liquids

                                            To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

                                            images/liquids.png

                                            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 (the example config uses Ctrl-M) and activate in the 'q' mode.

                                            images/mechanisms.png

                                            Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -2854,7 +2891,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.

                                              @@ -2877,7 +2914,7 @@ their species string.

                                              unit profession change to Ctrl-Shift-T.

                                            -

                                            gui/room-list

                                            +

                                            gui/room-list

                                            To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

                                            images/room-list.png @@ -2885,7 +2922,7 @@ either immediately or after opening the assign owner page.

                                            list, and allows unassigning them.

                                            -

                                            gui/choose-weapons

                                            +

                                            gui/choose-weapons

                                            Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

                                            Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -2896,7 +2933,7 @@ only that entry, and does it even if it is not 'individual choice'.

                                            and may lead to inappropriate weapons being selected.

                                            -

                                            gui/guide-path

                                            +

                                            gui/guide-path

                                            Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

                                            images/guide-path.png @@ -2904,7 +2941,7 @@ the cursor over a Guide order.

                                            computes it when the order is executed for the first time.

                                            -

                                            gui/workshop-job

                                            +

                                            gui/workshop-job

                                            Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

                                            images/workshop-job.png @@ -2940,7 +2977,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                            -

                                            gui/workflow

                                            +

                                            gui/workflow

                                            Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

                                            images/workflow.png @@ -2970,7 +3007,7 @@ suit your need, and set the item count range.

                                            If you don't need advanced settings, you can just press 'y' to confirm creation.

                                            -

                                            gui/assign-rack

                                            +

                                            gui/assign-rack

                                            Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

                                            images/assign-rack.png @@ -2995,7 +3032,7 @@ of currently assigned racks for every valid squad.

                                            -

                                            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.

                                            @@ -3006,20 +3043,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 (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

                                            @@ -3042,7 +3079,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 @@ -3053,11 +3090,11 @@ 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 @@ -3068,7 +3105,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.

                                            @@ -3092,7 +3129,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 @@ -3123,7 +3160,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 @@ -3132,7 +3169,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 @@ -3143,7 +3180,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 1f5ac08fd..e6554cac0 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1968,6 +1968,9 @@ are mostly implemented by lua scripts. In order to avoid user confusion, as a matter of policy all these tools display the word "DFHack" on the screen somewhere while active. + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. + As an exception, the tweak plugin described above does not follow this guideline because it arguably just fixes small usability bugs in the game UI. @@ -2046,6 +2049,42 @@ Value numbers displayed by the screen. Because of this, pressing the 't' key while search is active clears the search instead of executing the trade. +AutoMaterial +============ + +The automaterial plugin makes building constructions (walls, floors, fortifications, +etc) a little bit easier by saving you from having to trawl through long lists of +materials each time you place one. + +Firstly, it moves the last used material for a given construction type to the top of +the list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, regardless of +distance (it only does this in "grouped" mode, as individual item lists could be huge). +This should mean you can place most constructions without having to search for your +preferred material type. + +.. image:: images/automaterial-mat.png + +Pressing 'a' while highlighting any material will enable that material for "auto select" +for this construction type. You can enable multiple materials as autoselect. Now the next +time you place this type of construction, the plugin will automatically choose materials +for you from the kinds you enabled. If there is enough to satisfy the whole placement, +you won't be prompted with the material screen - the construction will be placed and you +will be back in the construction menu as if you did it manually. + +When choosing the construction placement, you will see a couple of options: + +.. image:: images/automaterial-pos.png + +Use 'a' here to temporarily disable the material autoselection, e.g. if you need +to go to the material selection screen so you can toggle some materials on or off. + +The other option (auto type selection, off by default) can be toggled on with 't'. If you +toggle this option on, instead of returning you to the main construction menu after selecting +materials, it returns you back to this screen. If you use this along with several autoselect +enabled materials, you should be able to place complex constructions more conveniently. + + gui/liquids =========== diff --git a/images/automaterial-mat.png b/images/automaterial-mat.png new file mode 100644 index 0000000000000000000000000000000000000000..5517bbc6d33636b9842da1b712cac161a74a4f39 GIT binary patch literal 5187 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 literal 0 HcmV?d00001 diff --git a/images/automaterial-pos.png b/images/automaterial-pos.png new file mode 100644 index 0000000000000000000000000000000000000000..959573643a8d8a0ba3e9c686dd80e1885f4ad318 GIT binary patch literal 3007 zcma)83pA8l8~(;H?lYt0n&~5)Fw{&JC#h!43?ov;tx?By5JHBCqA}w#-T0Ktp_|0y zc2pB8w;V|$Ni^i{BywDT@bn6B z@94%XxP$Pqv!TkWdGn{>f&5WAGXen6m*KYwaqLrK0sts<+EU5R$NOdm>q7f2RXN_M zO<*2#aaTpt|VB9KDWJ%e$eF^RMY$C z__mg`x092F7Pl+gf2-=Ues};a#Z>IDEDe60c7{cY+EEvG_e4tW z4lT}TB~ECxqJffO6kDF!Xdcx58C&d7+E-Rp8uI6LT&8E|krF}lo^^9R^IP*$`1ASU zuFDZ)EBY6z4MkIz`lAwR>vPnt!LyY}g4*QA9g2!01y6UYvDR?QrXP6P#x=Wj9%)36 zkTTk_P;IX)_4d8c-OyUmRJkHGS_LVIo2!xc!IvPv8mO^?$>%5o6fvjj7%yi6G?8V5 zqPQ74`IcQlk?vE^Rk(OAa;*P|w`ab`8}o|^(@|>F^Y*uD+;(tv5RSi4-<9nDu*i%{ zxrb{c--fzV6>Mhc_&bu-!ylcpdUN_DjB~st0j9wa&6Chf?@JuXtMT|FMa4|uN_W9 zhD2}7hw)un!alO(tBb2Pz2C)Eoj&D~Ro9?(EYo11CqlYu)?hR1LEdZf0MH$u=_i3U z$V`YRRb6XbOfgKE-wU%+iqxOZh%Bi+<= zKU$0IJycjj1lE4pz!_a#dN>Ud@}6n3diLog@C`*Sw}k$2y(dyNE~tqHc-0#67jm#8 z7gSb@&zf*go~*rCe6bkWsmy2_m(|yVu3cLJY?0I_(9+!{BwefUqNLGnX-Sq&1a~XR z5%kwU7*eNHjI6#9lostncL$A&A~G|3T-0LdK{0YUu|7E#U|_=jL-x})HB&K68e>j4 zt1~(p!_QJ^%4z zZ>Roo<@{HhS1u2GWhYoVW5N3+rMC;t06c0jzeW4 zh3n>jRb2~D&gj^hBn@^P)~VKbG5Ljd5EDl6b>Uf2t*;zR90wqhcQ}PRrldW&^uoRl14v=6*ucOEGv8sy=JLd;V8BNXRF_rZK=2Mf5(N)^`yZ`>k7DA>!_6!e%J&THxGdp@ zht=N@b^Ob2s-x{y_dY`Jy8g{O;txoA&P`!X-ld(HP zTjZMRmLH|yr~ummvQ|o;fByQ7HOz(`uV=G1m+y5>0!E2CZA!yh`MNjCg`Wy(fx@sB zN`bs$S4^~Ht%^9LtYet4P5ZU*b47RTl@lY5Br`{gboLiVQjuSqdC|KWL4av%6Z_)r z{jC-O`=c+#SK=}GjW$uKCGsGF{?I*ZcPIl(dX8cQO|LugN|+d#UAHYDd;GpwiprdZ z9cQeXZL1g2Bx~+;i1~zDYYnK=y{RN9uri(U4`wbd-|2YDiu(u^mkV*FKdr;0y4Huvh@`=) zt$XS@hh(Ykt*g35@psiTtE8}+VZa0W#AxfY$QL^c`p)hK!|%og6`GRFx+#|Y2}4f; zzdtu}P$rgSrb=nVjpW-JYJ>H8#~$E690D<|y44?dKd-y?$%|Qd26tT@EtH^I%ii0J1aa7k|;p7bO z7^ys?xaRWhUE5pRTP5l>oI|Qj;yrjiMmK4>G#xF)p1|eHmb*e;9$DDm_a<=;{gf;g zuof;UAhw5Tj=y(tT{5Ek=C7r67m`y)b>lr;HF^!eWt2Yg*q)y_1IGM?j_0E5%ST3a%{IBs3GI7A7n01Pip!cOf2CC@oD6ZuKGh=>zjM#XW{&EzCcmqaWiXR{ zRMQUk&?Mcx+Yt(zC)#eif6m1)X@(&dem&&Z70l$d9?)6i6~w$bu500hA)omoXnfgv zAb-!6r6gfq*4Eo8iEkVU73aptOKrQvLTeuXsL_;%Uzr8{{imB*TaA+|-1eqs-O<{a z=-v%NvEc-5_hw=Aq}uNg&9rpI?_I!p%!i~B7!cHsi53elh{QH#Ms|YuA%RWVktT(l zd&=y?S|*ic^#RlISdDV<2y> +#include #include "DataDefs.h" #include "df/graphic.h" @@ -51,6 +53,8 @@ namespace DFHack { class Core; + typedef std::set interface_key_set; + /** * The Screen module * \ingroup grp_modules diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index ac5a4ae22..9f383b935 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -74,28 +74,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } - -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - -void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) -{ - OutputString(10, x, y, hotkey); - string display(": "); - display.append(text); - OutputString(color, x, y, display, newline, left_margin); -} - - static inline bool in_material_choice_stage() { return Gui::build_selector_hotkey(Core::getTopViewscreen()) && @@ -137,7 +115,7 @@ static inline MaterialDescriptor &get_last_used_material() return last_used_material[ui_build_selector->building_subtype]; } -static void set_last_used_material(MaterialDescriptor &matetial) +static void set_last_used_material(const MaterialDescriptor &matetial) { last_used_material[ui_build_selector->building_subtype] = matetial; } @@ -150,7 +128,7 @@ static MaterialDescriptor &get_last_moved_material() return last_moved_material[ui_build_selector->building_subtype]; } -static void set_last_moved_material(MaterialDescriptor &matetial) +static void set_last_moved_material(const MaterialDescriptor &matetial) { last_moved_material[ui_build_selector->building_subtype] = matetial; } @@ -306,9 +284,9 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest !in_material_choice_stage() && hotkeys.find(last_used_constr_subtype) != hotkeys.end()) { - input->clear(); - input->insert(hotkeys[last_used_constr_subtype]); - this->feed(input); + interface_key_set keys; + keys.insert(hotkeys[last_used_constr_subtype]); + INTERPOSE_NEXT(feed)(&keys); } } @@ -349,47 +327,42 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - - string toggle_string = "Enable"; string title = "Disabled"; if (check_autoselect(material, false)) { - toggle_string = "Disable"; title = "Enabled"; } - OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); - OutputHotkeyString(x, y, toggle_string.c_str(), "a", true, left_margin); + auto dims = Gui::getDwarfmodeViewDims(); + Screen::Painter dc(dims.menu()); + + dc.seek(1,24).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE); + dc.key(interface_key::CUSTOM_A).string(": Autoselect "+title); } } - else if (in_placement_stage() && ui_build_selector->building_subtype != 7) + else if (in_placement_stage() && ui_build_selector->building_subtype < construction_type::TrackN) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; + string autoselect_toggle = (auto_choose_materials) ? "Disable" : "Enable"; + string revert_toggle = (revert_to_last_used_type) ? "Disable" : "Enable"; - string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; - string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; + auto dims = Gui::getDwarfmodeViewDims(); + Screen::Painter dc(dims.menu()); - OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); - OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + dc.seek(1,23).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE); + dc.key(interface_key::CUSTOM_A).string(": "+autoselect_toggle+" Auto Mat-Select").newline(1); + dc.key(interface_key::CUSTOM_T).string(": "+revert_toggle+" Auto Type-Select"); } } }; -color_ostream_proxy console_out(Core::getInstance().getConsole()); - - IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - if (!gps || !INTERPOSE_HOOK(jobutils_hook, feed).apply() || !INTERPOSE_HOOK(jobutils_hook, render).apply()) + if (!gps || !ui_build_selector || + !INTERPOSE_HOOK(jobutils_hook, feed).apply() || + !INTERPOSE_HOOK(jobutils_hook, render).apply()) out.printerr("Could not insert jobutils hooks!\n"); hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL; From 2a0d04804068a3e63866fa128711a0d1c2add8b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 22 Nov 2012 20:08:47 +0400 Subject: [PATCH 203/472] Make tweak stable-cursor interact with the build menu properly. --- NEWS | 1 + plugins/tweak.cpp | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 6ba9a769f..463ebb47a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ DFHack future - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - superdwarf: work in adventure mode too + - tweak stable-cursor: carries cursor location from/to Build menu. New tweaks: - tweak military-training: speed up melee squad training up to 10x (normally 3-5x). New scripts: diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 7143e715e..70d915ffc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -212,15 +212,31 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; + bool check_default() + { + switch (ui->main.mode) { + case ui_sidebar_mode::Default: + return true; + + case ui_sidebar_mode::Build: + return ui_build_selector && + (ui_build_selector->building_type < 0 || + ui_build_selector->stage < 1); + + default: + return false; + } + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default); + bool was_default = check_default(); df::coord view = Gui::getViewportPos(); df::coord cursor = Gui::getCursorPos(); INTERPOSE_NEXT(feed)(input); - bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default); + bool is_default = check_default(); df::coord cur_cursor = Gui::getCursorPos(); if (is_default && !was_default) @@ -241,7 +257,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest tmp.insert(interface_key::CURSOR_UP_Z); INTERPOSE_NEXT(feed)(&tmp); } - else if (cur_cursor.isValid()) + else if (!is_default && cur_cursor.isValid()) { last_cursor = df::coord(); } From e3eb325d3680aa06b5fa0cde68da186345858e34 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 23 Nov 2012 19:18:56 -0600 Subject: [PATCH 204/472] Minimize references to gps->dimx/dimy --- library/modules/Screen.cpp | 45 ++++++++++++++------------ plugins/manipulator.cpp | 66 ++++++++++++++++++++------------------ plugins/search.cpp | 8 +++-- 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index f2d1f2d5d..cd20bc25e 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -110,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y) { if (!gps || !pen.valid()) return false; - int dimx = gps->dimx, dimy = gps->dimy; - if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; + auto dim = getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false; - doSetTile(pen, x*dimy + y); + doSetTile(pen, x*dim.y + y); return true; } @@ -121,11 +121,11 @@ Pen Screen::readTile(int x, int y) { if (!gps) return Pen(0,0,0,-1); - int dimx = gps->dimx, dimy = gps->dimy; - if (x < 0 || x >= dimx || y < 0 || y >= dimy) + auto dim = getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return Pen(0,0,0,-1); - int index = x*dimy + y; + int index = x*dim.y + y; auto screen = gps->screen + index*4; if (screen[3] & 0x80) return Pen(0,0,0,-1); @@ -154,14 +154,15 @@ Pen Screen::readTile(int x, int y) bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) { - if (!gps || y < 0 || y >= gps->dimy) return false; + auto dim = getWindowSize(); + if (!gps || y < 0 || y >= dim.y) return false; Pen tmp(pen); bool ok = false; for (size_t i = -std::min(0,x); i < text.size(); i++) { - if (x + i >= size_t(gps->dimx)) + if (x + i >= size_t(dim.x)) break; tmp.ch = text[i]; @@ -175,17 +176,18 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) { + auto dim = getWindowSize(); if (!gps || !pen.valid()) return false; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; - if (x2 >= gps->dimx) x2 = gps->dimx-1; - if (y2 >= gps->dimy) y2 = gps->dimy-1; + if (x2 >= dim.x) x2 = dim.x-1; + if (y2 >= dim.y) y2 = dim.y-1; if (x1 > x2 || y1 > y2) return false; for (int x = x1; x <= x2; x++) { - int index = x*gps->dimy; + int index = x*dim.y; for (int y = y1; y <= y2; y++) doSetTile(pen, index+y); @@ -198,32 +200,33 @@ bool Screen::drawBorder(const std::string &title) { if (!gps) return false; - int dimx = gps->dimx, dimy = gps->dimy; + auto dim = getWindowSize(); Pen border('\xDB', 8); Pen text(0, 0, 7); Pen signature(0, 0, 8); - for (int x = 0; x < dimx; x++) + for (int x = 0; x < dim.x; x++) { - doSetTile(border, x * dimy + 0); - doSetTile(border, x * dimy + dimy - 1); + doSetTile(border, x * dim.y + 0); + doSetTile(border, x * dim.y + dim.y - 1); } - for (int y = 0; y < dimy; y++) + for (int y = 0; y < dim.y; y++) { - doSetTile(border, 0 * dimy + y); - doSetTile(border, (dimx - 1) * dimy + y); + doSetTile(border, 0 * dim.y + y); + doSetTile(border, (dim.x - 1) * dim.y + y); } - paintString(signature, dimx-8, dimy-1, "DFHack"); + paintString(signature, dim.x-8, dim.y-1, "DFHack"); - return paintString(text, (dimx - title.length()) / 2, 0, title); + return paintString(text, (dim.x - title.length()) / 2, 0, title); } bool Screen::clear() { if (!gps) return false; - return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); + auto dim = getWindowSize(); + return fillRect(Pen(' ',0,0,false), 0, 0, dim.x-1, dim.y-1); } bool Screen::invalidate() diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 79999d468..59b257979 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -456,11 +456,13 @@ void viewscreen_unitlaborsst::refreshNames() void viewscreen_unitlaborsst::calcSize() { - num_rows = gps->dimy - 10; + auto dim = Screen::getWindowSize(); + + num_rows = dim.y - 10; if (num_rows > units.size()) num_rows = units.size(); - int num_columns = gps->dimx - DISP_COLUMN_MAX - 1; + int num_columns = dim.x - DISP_COLUMN_MAX - 1; // min/max width of columns int col_minwidth[DISP_COLUMN_MAX]; @@ -940,10 +942,11 @@ void viewscreen_unitlaborsst::render() dfhack_viewscreen::render(); + auto dim = Screen::getWindowSize(); + Screen::clear(); Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); - Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap."); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession"); @@ -1116,48 +1119,48 @@ void viewscreen_unitlaborsst::render() canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); } - int x = 2; - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT)); - OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); + int x = 2, y = dim.y - 3; + OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT)); + OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, "); - 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, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); + OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, "); - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); - OutputString(15, x, gps->dimy - 3, ": ViewCre, "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); + OutputString(15, x, y, ": ViewCre, "); - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); - OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); + OutputString(15, x, y, ": Zoom-Cre"); - x = 2; - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); - OutputString(15, x, gps->dimy - 2, ": Done, "); + x = 2; y = dim.y - 2; + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); + OutputString(15, x, y, ": Done, "); - 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, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); + OutputString(15, x, y, ": Sort by Skill, "); - 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, Screen::getKeyDisplay(interface_key::CHANGETAB)); - OutputString(15, x, gps->dimy - 2, ") "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); + OutputString(15, x, y, ": Sort by ("); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CHANGETAB)); + OutputString(15, x, y, ") "); switch (altsort) { case ALTSORT_NAME: - OutputString(15, x, gps->dimy - 2, "Name"); + OutputString(15, x, y, "Name"); break; case ALTSORT_PROFESSION: - OutputString(15, x, gps->dimy - 2, "Profession"); + OutputString(15, x, y, "Profession"); break; case ALTSORT_HAPPINESS: - OutputString(15, x, gps->dimy - 2, "Happiness"); + OutputString(15, x, y, "Happiness"); break; case ALTSORT_ARRIVAL: - OutputString(15, x, gps->dimy - 2, "Arrival"); + OutputString(15, x, y, "Arrival"); break; default: - OutputString(15, x, gps->dimy - 2, "Unknown"); + OutputString(15, x, y, "Unknown"); break; } } @@ -1193,9 +1196,10 @@ struct unitlist_hook : df::viewscreen_unitlistst if (units[page].size()) { - int x = 2; - OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); - OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(12, x, y, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); + OutputString(15, x, y, ": Manage labors (DFHack)"); } } }; diff --git a/plugins/search.cpp b/plugins/search.cpp index cc3f29c12..742fa9277 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -329,8 +329,9 @@ protected: // Display hotkey message void print_search_option(int x, int y = -1) const { + auto dim = Screen::getWindowSize(); if (y == -1) - y = gps->dimy - 2; + y = dim.y - 2; OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); @@ -413,8 +414,9 @@ public: print_search_option(2); else { - int x = 2; - OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); } } From 139fd07df3e738fec33842d645fb1ac947679e61 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 23 Nov 2012 19:23:06 -0600 Subject: [PATCH 205/472] missed a spot --- plugins/manipulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 59b257979..e8a91fdb6 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1120,10 +1120,10 @@ void viewscreen_unitlaborsst::render() } int x = 2, y = dim.y - 3; - OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, "); - OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); From 8429f651766566f697f36347faba23a98d987f72 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 22 Nov 2012 16:56:22 +0100 Subject: [PATCH 206/472] add scripts/stripcaged.rb and documentation --- NEWS | 1 + Readme.rst | 45 ++++++++++ scripts/stripcaged.rb | 194 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 scripts/stripcaged.rb diff --git a/NEWS b/NEWS index 463ebb47a..65c647337 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ DFHack future - dfusion: misc scripts with a text based menu. - embark: lets you embark anywhere. - lever: list and pull fort levers from the dfhack console. + - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/Readme.rst b/Readme.rst index e6554cac0..3434a240d 100644 --- a/Readme.rst +++ b/Readme.rst @@ -144,6 +144,16 @@ system console: The patches are expected to be encoded in text format used by IDA. + +Live patching +------------- + +As an alternative, you can use the ``binpatch`` dfhack command to apply/remove +patches live in memory during a DF session. + +In this case, updating symbols.xml is not necessary. + + ============================= Something doesn't work, help! ============================= @@ -1956,6 +1966,41 @@ embark ====== Allows to embark anywhere. Currently windows only. +lever +===== +Allow manipulation of in-game levers from the dfhack console. + +Can list levers, including state and links, with:: + + lever list + +To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``. +This is the same as 'q'uerying the building and queue a 'P'ull request. + +To magically toggle the lever immediately, use:: + + lever pull 42 --now + +stripcaged +========== +For dumping items inside cages. Will mark selected items for dumping, then +a dwarf may come and actually dump it. See also ``autodump``. + +With the ``items`` argument, only dumps items laying in the cage, excluding +stuff worn by caged creatures. ``weapons`` will dump worn weapons, ``armor`` +will dump everything worn by caged creatures (including armor and clothing), +and ``all`` will dump everything, on a creature or not. + +``stripcaged list`` will display on the dfhack console the list of all cages +and their item content. + +Without further arguments, all commands work on all cages and animal traps on +the map. With the ``here`` argument, considers only the in-game selected cage +(or the cage under the game cursor). To target only specific cages, you can +alternatively pass cage IDs as arguments:: + + stripcaged weapons 25321 34228 + ======================= In-game interface tools ======================= diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb new file mode 100644 index 000000000..fa9c49552 --- /dev/null +++ b/scripts/stripcaged.rb @@ -0,0 +1,194 @@ +# mark stuff inside of cages for dumping. + +def plural(nr, name) + # '1 cage' / '4 cages' + "#{nr} #{name}#{'s' if nr > 1}" +end + +def cage_dump_items(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_armor(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Worn + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_weapons(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Weapon + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_all(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + end + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + + +def cage_dump_list(list) + count_total = Hash.new(0) + list.each { |cage| + count = Hash.new(0) + + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + count[ref.item_tg._rtti_classname] += 1 + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + count[it.item._rtti_classname] += 1 + } + # TODO vermin ? + else + puts "unhandled ref #{ref.inspect}" if $DEBUG + end + } + + type = case cage + when DFHack::ItemCagest; 'Cage' + when DFHack::ItemAnimaltrapst; 'Animal trap' + else cage._rtti_classname + end + + puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + + count.each { |k, v| count_total[k] += v } + } + + if list.length > 2 + puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + end +end + + +# handle magic script arguments +here_only = $script_args.delete 'here' +if here_only + it = df.item_find + list = [it] + if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) + list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } + end + puts 'Please select a cage' if list.empty? + +elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first + list = [] + ids.each { |id| + $script_args.delete id + if not it = df.item_find(id.to_i) + puts "Invalid item id #{id}" + elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) + puts "Item ##{id} is not a cage" + list << it + else + list << it + end + } + puts 'Please use a valid cage id' if list.empty? + +else + list = df.world.items.other[:ANY_CAGE_OR_TRAP] +end + + +# act +case $script_args[0] +when 'items' + cage_dump_items(list) if not list.empty? +when 'armor' + cage_dump_armor(list) if not list.empty? +when 'weapons' + cage_dump_weapons(list) if not list.empty? +when 'all' + cage_dump_all(list) if not list.empty? + +when 'list' + cage_dump_list(list) if not list.empty? + +else + puts < Date: Thu, 22 Nov 2012 17:42:10 +0100 Subject: [PATCH 207/472] scripts/lever: show pending jobs --- scripts/lever.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/lever.rb b/scripts/lever.rb index 2c7735146..59196f7d2 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -10,6 +10,8 @@ def lever_pull_job(bld) job.general_refs << ref bld.jobs << job df.job_link job + + puts lever_descr(bld) end def lever_pull_cheat(bld) @@ -31,6 +33,14 @@ def lever_descr(bld, idx=nil) descr = '' descr << "#{idx}: " if idx descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + bld.jobs.each { |j| + if j.job_type == :PullLever + flags = '' + flags << ', repeat' if j.flags.repeat + flags << ', suspended' if j.flags.suspend + descr << " (pull order#{flags})" + end + } bld.linked_mechanisms.map { |i| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) From cb06c896984735c59a72570dd50f5dfc62bc40ab Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 23 Nov 2012 17:20:16 +0100 Subject: [PATCH 208/472] stripcaged: dont list empty cages individually --- scripts/stripcaged.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb index fa9c49552..07694f711 100644 --- a/scripts/stripcaged.rb +++ b/scripts/stripcaged.rb @@ -90,6 +90,7 @@ end def cage_dump_list(list) count_total = Hash.new(0) + empty_cages = 0 list.each { |cage| count = Hash.new(0) @@ -113,13 +114,18 @@ def cage_dump_list(list) else cage._rtti_classname end - puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + if count.empty? + empty_cages += 1 + else + puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + end count.each { |k, v| count_total[k] += v } } if list.length > 2 puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + puts "with #{plural(empty_cages, 'empty cage')}" end end From e73274d281e4f7f8b476a64a4854400dbb40c791 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 24 Nov 2012 16:05:03 +0100 Subject: [PATCH 209/472] ruby: add description field to onupdate_register --- plugins/ruby/README | 4 ++-- plugins/ruby/ruby.rb | 38 ++++++++++++++++++++------------------ scripts/autofarm.rb | 16 ++++++++-------- scripts/autounsuspend.rb | 6 +++--- scripts/magmasource.rb | 2 +- scripts/slayrace.rb | 2 +- scripts/superdwarf.rb | 4 ++-- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/plugins/ruby/README b/plugins/ruby/README index 9246fec88..d35c34bbe 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -125,9 +125,9 @@ DFHack callbacks The plugin interfaces with dfhack 'onupdate' hook. To register ruby code to be run every graphic frame, use: - handle = df.onupdate_register { puts 'i love flooding the console' } + handle = df.onupdate_register('log') { puts 'i love flooding the console' } You can also rate-limit when your callback is called to a number of game ticks: - handle = df.onupdate_register(10) { puts '10 more in-game ticks elapsed' } + handle = df.onupdate_register('myname', 10) { puts '10 more in-game ticks elapsed' } In this case, the callback is called immediately, and then every X in-game ticks (advances only when the game is unpaused). To stop being called, use: diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index ab095e8d8..4fcb5543a 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -24,8 +24,9 @@ end module DFHack class OnupdateCallback - attr_accessor :callback, :timelimit, :minyear, :minyeartick - def initialize(cb, tl, initdelay=0) + attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description + def initialize(descr, cb, tl, initdelay=0) + @description = descr @callback = cb @ticklimit = tl @minyear = (tl ? df.cur_year : 0) @@ -34,22 +35,21 @@ module DFHack # run callback if timedout def check_run(year, yeartick, yearlen) - if !@ticklimit - @callback.call - else - if year > @minyear or (year == @minyear and yeartick >= @minyeartick) - @minyear = year - @minyeartick = yeartick + @ticklimit - if @minyeartick > yearlen - @minyear += 1 - @minyeartick -= yearlen - end - @callback.call + if @ticklimit + return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick) + @minyear = year + @minyeartick = yeartick + @ticklimit + if @minyeartick > yearlen + @minyear += 1 + @minyeartick -= yearlen end end + # t0 = Time.now + @callback.call + # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 rescue df.onupdate_unregister self - puts_err "onupdate cb #$!", $!.backtrace + puts_err "onupdate #@description unregistered: #$!", $!.backtrace end def <=>(o) @@ -61,10 +61,11 @@ module DFHack attr_accessor :onupdate_list, :onstatechange_list # register a callback to be called every gframe or more - # ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 } - def onupdate_register(ticklimit=nil, initialtickdelay=0, &b) + # ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 } + def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b) + raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String) @onupdate_list ||= [] - @onupdate_list << OnupdateCallback.new(b, ticklimit, initialtickdelay) + @onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay) DFHack.onupdate_active = true if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear @@ -73,8 +74,9 @@ module DFHack @onupdate_list.last end - # delete the callback for onupdate ; use the value returned by onupdate_register + # delete the callback for onupdate ; use the value returned by onupdate_register or the description def onupdate_unregister(b) + b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String) @onupdate_list.delete b if @onupdate_list.empty? DFHack.onupdate_active = false diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 098466745..c89cb9ff4 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -5,7 +5,7 @@ class AutoFarm @lastcounts = Hash.new(0) end - def setthreshold (id, v) + def setthreshold(id, v) if df.world.raws.plants.all.find { |r| r.id == id } @thresholds[id] = v.to_i else @@ -13,11 +13,11 @@ class AutoFarm end end - def setdefault (v) + def setdefault(v) @thresholds.default = v.to_i end - def is_plantable (plant) + def is_plantable(plant) season = df.cur_season harvest = df.cur_season_tick + plant.growdur * 10 will_finish = harvest < 10080 @@ -40,7 +40,7 @@ class AutoFarm return plantable end - def set_farms ( plants, farms) + def set_farms( plants, farms) return if farms.length == 0 if plants.length == 0 plants = [-1] @@ -66,7 +66,7 @@ class AutoFarm if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact1 && plantable.has_key? (i.mat_index)) + !i.flags.artifact1 && plantable.has_key?(i.mat_index)) counts[i.mat_index] = counts[i.mat_index] + i.stack_size end } @@ -95,13 +95,13 @@ class AutoFarm end } - set_farms (plants_s, farms_s) - set_farms (plants_u, farms_u) + set_farms(plants_s, farms_s) + set_farms(plants_u, farms_u) end def start - @onupdate = df.onupdate_register (100) { process } + @onupdate = df.onupdate_register('autofarm', 100) { process } @running = true end diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb index 45dd8df4d..c7fe20748 100644 --- a/scripts/autounsuspend.rb +++ b/scripts/autounsuspend.rb @@ -26,7 +26,7 @@ class AutoUnsuspend end def start - @onupdate = df.onupdate_register (5) { process } + @onupdate = df.onupdate_register('autounsuspend', 5) { process } @running = true end @@ -36,7 +36,7 @@ class AutoUnsuspend end def status - stat = @running ? "Running." : "Loaded." + @running ? 'Running.' : 'Stopped.' end end @@ -53,6 +53,6 @@ else if $AutoUnsuspend puts $AutoUnsuspend.status else - puts "AI not started" + puts 'Not loaded.' end end diff --git a/scripts/magmasource.rb b/scripts/magmasource.rb index e97080834..c20199c2a 100644 --- a/scripts/magmasource.rb +++ b/scripts/magmasource.rb @@ -4,7 +4,7 @@ $magma_sources ||= [] case $script_args[0] when 'here' - $magma_onupdate ||= df.onupdate_register(12) { + $magma_onupdate ||= df.onupdate_register('magmasource', 12) { # called every 12 game ticks (100x a dwarf day) if $magma_sources.empty? df.onupdate_unregister($magma_onupdate) diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index 749d0189b..ca50020f7 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -21,7 +21,7 @@ slayit = lambda { |u| else # it's getting hot around here # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register(1) { + ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) { if u.flags1.dead df.onupdate_unregister(ouh) else diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index 6277db97f..eac9802fa 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,12 +8,12 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - if df.gamemode == :ADVENTURE + if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode) onupdate_delay = nil else onupdate_delay = 1 end - $superdwarf_onupdate ||= df.onupdate_register(onupdate_delay) { + $superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil From 4dfe46e26f139167cc51ec689c19b3ba08747c0d Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 24 Nov 2012 16:52:21 +0100 Subject: [PATCH 210/472] manipulator: fix column width calculations for 80x25 window --- plugins/manipulator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index e8a91fdb6..57c9390bb 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -469,10 +469,10 @@ void viewscreen_unitlaborsst::calcSize() int col_maxwidth[DISP_COLUMN_MAX]; col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; - col_minwidth[DISP_COLUMN_NAME] = 0; - col_maxwidth[DISP_COLUMN_NAME] = 0; - col_minwidth[DISP_COLUMN_PROFESSION] = 0; - col_maxwidth[DISP_COLUMN_PROFESSION] = 0; + col_minwidth[DISP_COLUMN_NAME] = 15; + col_maxwidth[DISP_COLUMN_NAME] = 15; // adjusted in the loop below + col_minwidth[DISP_COLUMN_PROFESSION] = 15; + col_maxwidth[DISP_COLUMN_PROFESSION] = 15; // adjusted in the loop below col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; From cdc44b74f2d8d353d0beab7e73bfa8bbf916151c Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 10:36:32 -0600 Subject: [PATCH 211/472] Fix possible crash when using shift+enter on cells that don't have labors --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 57c9390bb..d8b44f657 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -842,7 +842,7 @@ void viewscreen_unitlaborsst::feed(set *events) { df::unit *unit = cur->unit; const SkillColumn &col = columns[input_column]; - bool newstatus = !unit->status.labors[col.labor]; + bool newstatus = (col.labor == unit_labor::NONE) ? true : !unit->status.labors[col.labor]; for (int i = 0; i < NUM_COLUMNS; i++) { if (columns[i].group != col.group) From c58f30ba0095248512c3b1320de6869620794377 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 10:37:22 -0600 Subject: [PATCH 212/472] Use teal background instead of red for no-labor cells --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index d8b44f657..3e1a414ff 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1060,7 +1060,7 @@ void viewscreen_unitlaborsst::render() } } else - bg = 4; + bg = 3; Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row); } } From e9141f34f6dd9354a4eed0498aa72ff944cc1189 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 11:13:54 -0600 Subject: [PATCH 213/472] Adjust minimum widths so they actually work at 80x25 without glitching out --- plugins/manipulator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3e1a414ff..88dc61726 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -469,10 +469,10 @@ void viewscreen_unitlaborsst::calcSize() int col_maxwidth[DISP_COLUMN_MAX]; col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; - col_minwidth[DISP_COLUMN_NAME] = 15; - col_maxwidth[DISP_COLUMN_NAME] = 15; // adjusted in the loop below - col_minwidth[DISP_COLUMN_PROFESSION] = 15; - col_maxwidth[DISP_COLUMN_PROFESSION] = 15; // adjusted in the loop below + col_minwidth[DISP_COLUMN_NAME] = 16; + col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below + col_minwidth[DISP_COLUMN_PROFESSION] = 10; + col_maxwidth[DISP_COLUMN_PROFESSION] = 10; // adjusted in the loop below col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; From f091284a75b52b84dbe602d85345bae9bcfc2422 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 25 Nov 2012 17:29:03 +0100 Subject: [PATCH 214/472] ruby: avoid crash on ArgumentError in onupdate --- plugins/ruby/ruby.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 4fcb5543a..b7f7590e9 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -47,7 +47,7 @@ module DFHack # t0 = Time.now @callback.call # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 - rescue + rescue Exception df.onupdate_unregister self puts_err "onupdate #@description unregistered: #$!", $!.backtrace end From 2bbcfe912ae55023cd68a3ed31d61430e1c26c3d Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 26 Nov 2012 21:04:17 +0200 Subject: [PATCH 215/472] Site spawner added to adv_tools --- plugins/lua/dfusion/adv_tools.lua | 70 +++++++++++++++++++++++++++---- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/plugins/lua/dfusion/adv_tools.lua b/plugins/lua/dfusion/adv_tools.lua index 6e95d2117..95ace5d38 100644 --- a/plugins/lua/dfusion/adv_tools.lua +++ b/plugins/lua/dfusion/adv_tools.lua @@ -1,5 +1,6 @@ local _ENV = mkmodule('plugins.dfusion.adv_tools') local dfu=require("plugins.dfusion") +local tools=require("plugins.dfusion.tools") menu=dfu.SimpleMenu() function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess if swap_soul==nil then @@ -9,7 +10,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess if adv.flags1.dead==false then qerror("You are not dead (yet)!") end - local hist_fig=getNemesis(adv).figure + local hist_fig=dfhack.units.getNemesis(adv).figure if hist_fig==nil then qerror("No historical figure for adventurer...") end @@ -18,9 +19,9 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess for i=#events-1,0,-1 do -- reverse search because almost always it will be last entry if df.history_event_hist_figure_diedst:is_instance(events[i]) then --print("is instance:"..i) - if events[i].victim==hist_fig.id then + if events[i].victim_hf==hist_fig.id then --print("Is same id:"..i) - trg_hist_fig=events[i].slayer + trg_hist_fig=events[i].slayer_hf if trg_hist_fig then trg_hist_fig=df.historical_figure.find(trg_hist_fig) end @@ -38,7 +39,7 @@ function Reincarnate(trg_unit,swap_soul) --only for adventurer i guess end local trg_unit_final=df.unit.find(trg_unit) - tools.change_adv(trg_unit_final) + change_adv(trg_unit_final) if swap_soul then --actually add a soul... t_soul=adv.status.current_soul adv.status.current_soul=df.NULL @@ -53,7 +54,7 @@ function change_adv(unit,nemesis) nemesis=true --default value is nemesis switch too. end if unit==nil then - unit=getCreatureAtPointer() + unit=dfhack.gui.getSelectedUnit()--getCreatureAtPointer() end if unit==nil then error("Invalid unit!") @@ -72,8 +73,8 @@ function change_adv(unit,nemesis) other[unit_indx]=other[0] other[0]=unit if nemesis then --basicly copied from advtools plugin... - local nem=getNemesis(unit) - local other_nem=getNemesis(other[unit_indx]) + local nem=dfhack.units.getNemesis(unit) + local other_nem=dfhack.units.getNemesis(other[unit_indx]) if other_nem then other_nem.flags[0]=false other_nem.flags[1]=true @@ -113,4 +114,59 @@ function log_pos() f:close() end menu:add("Log adventurers position",log_pos) +function addSite(x,y,rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y,civ_id,name,sitetype) + if x==nil or y==nil then + x=df.global.world.map.region_x/16 + y=df.global.world.map.region_y/16 + end + if name==nil then + name=dfhack.lineedit("Site name:")or "Hacked site" + end + if sitetype==nil then + sitetype=tonumber(dfhack.lineedit("Site type (numeric):")) or 7 + end + rgn_max_x=rgn_max_x or df.global.world.map.region_x%16 + rgn_max_y=rgn_max_y or df.global.world.map.region_y%16 + rgn_min_y=rgn_min_y or rgn_max_y + rgn_min_x=rgn_min_x or rgn_max_x + print("Region:",rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y) +--[=[ + global = pos*16 + rgn + BUT + for cities global is usually 17x17, i.e. max size + while rgn designates a small bit in the middle + for stuff like forts that formula holds exactly +]=]-- + local wd=df.global.world.world_data + local nsite=df.world_site:new() + nsite.name.first_name=name + nsite.name.has_name=true + nsite.pos:assign{x=x,y=y} + nsite.rgn_max_x=rgn_max_x + nsite.rgn_min_x=rgn_min_x + nsite.rgn_min_y=rgn_min_y + nsite.rgn_max_y=rgn_max_y + nsite.global_max_x=nsite.pos.x*16+nsite.rgn_max_x + nsite.global_min_x=nsite.pos.x*16+nsite.rgn_min_x + nsite.global_max_y=nsite.pos.y*16+nsite.rgn_max_y + nsite.global_min_y=nsite.pos.y*16+nsite.rgn_min_y + nsite.id=wd.next_site_id + nsite.civ_id=civ_id or -1 + nsite.cur_owner_id=civ_id or -1 + nsite.type=sitetype --lair = 7 + nsite.flags:resize(23) + --nsite.flags[4]=true + --nsite.flags[5]=true + --nsite.flags[6]=true + nsite.index=#wd.sites+1 + wd.sites:insert("#",nsite) + wd.next_site_id=wd.next_site_id+1 + --might not be needed... + --[[local unk130=df.world_site_unk130:new() + unk130.index=#wd.site_unk130+1 + wd.site_unk130:insert("#",unk130) + --wd.next_site_unk136_id=wd.next_site_unk136_id+1--]] + return nsite +end +menu:add("Create site at current location",addSite) return _ENV From 76bb5f0196e5f57be0e7c3bdd4947c8bd6416fc3 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 26 Nov 2012 20:09:56 +0100 Subject: [PATCH 216/472] ruby: items in containers are free --- plugins/ruby/item.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 469ec7449..0d65a707b 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -56,7 +56,7 @@ module DFHack def item_isfree(i) !i.flags.trader and !i.flags.in_job and - !i.flags.in_inventory and + (!i.flags.in_inventory or i.general_refs.grep(GeneralRefContainedInItemst).first) and !i.flags.removed and !i.flags.in_building and !i.flags.owned and From 536fd5546a8420340d0f6ca2fc8e5133bbda447b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 27 Nov 2012 13:56:02 +0400 Subject: [PATCH 217/472] Update manipulator screenshots. --- Readme.html | 639 +++++++++++++++++++++------------------- Readme.rst | 8 +- images/manipulator.png | Bin 7373 -> 9024 bytes images/manipulator2.png | Bin 0 -> 8840 bytes 4 files changed, 348 insertions(+), 299 deletions(-) create mode 100644 images/manipulator2.png diff --git a/Readme.html b/Readme.html index 5b231b429..cdc4dd631 100644 --- a/Readme.html +++ b/Readme.html @@ -342,200 +342,205 @@ access DF memory and allow for easier development of new tools.

                                        • Using DFHack
                                            -
                                          • Patched binaries
                                          • +
                                          • Patched binaries
                                          • -
                                          • Something doesn't work, help!
                                          • -
                                          • The init file
                                          • -
                                          • 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
                                                                • -
                                                                • fix-armory
                                                                • +
                                                                • 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 format
                                                                        • -
                                                                        • 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
                                                                                • -
                                                                                • Search
                                                                                • -
                                                                                • AutoMaterial
                                                                                • -
                                                                                • gui/liquids
                                                                                • -
                                                                                • gui/mechanisms
                                                                                • -
                                                                                • gui/rename
                                                                                • -
                                                                                • gui/room-list
                                                                                • -
                                                                                • gui/choose-weapons
                                                                                • -
                                                                                • gui/guide-path
                                                                                • -
                                                                                • gui/workshop-job
                                                                                • -
                                                                                • gui/workflow
                                                                                • -
                                                                                • gui/assign-rack
                                                                                • +
                                                                                • Scripts
                                                                                • -
                                                                                • Behavior Mods
                                                                                    -
                                                                                  • Siege Engine

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

                                                                                    +
                                                                                    +

                                                                                    Live patching

                                                                                    +

                                                                                    As an alternative, you can use the binpatch dfhack command to apply/remove +patches live in memory during a DF session.

                                                                                    +

                                                                                    In this case, updating symbols.xml is not necessary.

                                                                                    +
                                                                                    -

                                                                                    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. @@ -661,13 +672,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.

                                                                                    @@ -712,7 +723,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.

                                                                                    @@ -734,13 +745,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.

                                                                                    @@ -751,12 +762,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.

                                                                                      @@ -773,29 +784,29 @@ that implements an even more aggressive version of speedydwarf.
                                                                                    -

                                                                                    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:

                                                                                    @@ -829,9 +840,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.

                                                                                    @@ -844,7 +855,7 @@ properly.

                                                                                    -

                                                                                    advtools

                                                                                    +

                                                                                    advtools

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

                                                                                    Usage:

                                                                                    @@ -867,9 +878,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 @@ -944,7 +955,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.

                                                                                    @@ -957,7 +968,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 @@ -997,7 +1008,7 @@ crafters/haulers.

                                                                                    -

                                                                                    colonies

                                                                                    +

                                                                                    colonies

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

                                                                                    Options:

                                                                                    @@ -1012,12 +1023,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 @@ -1042,7 +1053,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 @@ -1055,13 +1066,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 @@ -1122,27 +1133,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:

                                                                                    @@ -1162,20 +1173,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:

                                                                                    @@ -1196,9 +1207,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. @@ -1253,17 +1264,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:

                                                                                    @@ -1282,7 +1293,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.

                                                                                    @@ -1307,7 +1318,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 @@ -1316,34 +1327,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:

                                                                                    @@ -1391,17 +1402,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 @@ -1409,11 +1420,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.

                                                                                    @@ -1476,7 +1487,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:

                                                                                    @@ -1539,7 +1550,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:

                                                                                  @@ -1567,7 +1578,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:

                                                                                  @@ -1606,7 +1617,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:

                                                                                  @@ -1630,7 +1641,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.

                                                                                  @@ -1657,9 +1668,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.

                                                                                  @@ -1693,12 +1704,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. @@ -1725,17 +1736,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:

                                                                                  @@ -1769,13 +1780,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 @@ -1784,19 +1795,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:

                                                                                  @@ -1902,7 +1913,7 @@ the units spar more.

                                                                                  -

                                                                                  fix-armory

                                                                                  +

                                                                                  fix-armory

                                                                                  Enables a fix for storage of squad equipment in barracks.

                                                                                  Specifically, it prevents your haulers from moving squad equipment to stockpiles, and instead queues jobs to store it on weapon racks, @@ -1956,9 +1967,9 @@ these rules is intended by Toady; the rest are invented by this plugin.

                                                                                  -

                                                                                  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 @@ -1978,7 +1989,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.

                                                                                  @@ -1998,9 +2009,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).

                                                                                  @@ -2013,19 +2024,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:
                                                                                  @@ -2044,7 +2055,7 @@ in a workshop, or the unit/jobs screen.
                                                                                  -

                                                                                  job-material

                                                                                  +

                                                                                  job-material

                                                                                  Alter the material of the selected job.

                                                                                  Invoked as:

                                                                                  @@ -2062,7 +2073,7 @@ over the first available choice with the matching material.
                                                                                • -

                                                                                  job-duplicate

                                                                                  +

                                                                                  job-duplicate

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

                                                                                  workflow

                                                                                  +

                                                                                  workflow

                                                                                  Manage control of repeat jobs.

                                                                                  Usage:

                                                                                  @@ -2105,7 +2116,7 @@ this list can be copied to a file, and then reloaded using the
                                                                                  -

                                                                                  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.

                                                                                  @@ -2118,7 +2129,7 @@ the frequency of jobs being toggled.

                                                                                  in the game UI.

                                                                                  -

                                                                                  Constraint format

                                                                                  +

                                                                                  Constraint format

                                                                                  The contstraint spec consists of 4 parts, separated with '/' characters:

                                                                                   ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
                                                                                  @@ -2147,7 +2158,7 @@ be used to ignore imported items or items below a certain quality.

                                                                                -

                                                                                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
                                                                                @@ -2196,15 +2207,15 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
                                                                                 
                                                                                -

                                                                                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:

                                                                                @@ -2303,7 +2314,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 @@ -2312,7 +2323,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. @@ -2330,14 +2341,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 @@ -2348,7 +2359,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 @@ -2374,7 +2385,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 @@ -2403,7 +2414,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 @@ -2511,7 +2522,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 @@ -2525,14 +2536,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, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
                                                                                @@ -2557,7 +2568,7 @@ twice.

                                                                                -

                                                                                misery

                                                                                +

                                                                                misery

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

                                                                                Usage:

                                                                                @@ -2581,7 +2592,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.

                                                                                @@ -2590,7 +2601,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

                                                                                  @@ -2616,12 +2627,12 @@ 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.

                                                                                -

                                                                                binpatch

                                                                                +

                                                                                binpatch

                                                                                Checks, applies or removes binary patches directly in memory at runtime:

                                                                                 binpatch check/apply/remove <patchname>
                                                                                @@ -2631,17 +2642,17 @@ script uses hack/patches/<df-v
                                                                                 the version appropriate for the currently loaded executable.

                                                                                -

                                                                                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 @@ -2649,7 +2660,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. @@ -2661,7 +2672,7 @@ growcrops plump 40

                                                                                -

                                                                                removebadthoughts

                                                                                +

                                                                                removebadthoughts

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

                                                                                The script can target a single creature, when used with the him argument, @@ -2675,7 +2686,7 @@ but in the short term your dwarves will get much more joyful.

                                                                                quickly after you unpause.

                                                                                -

                                                                                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.

                                                                                @@ -2701,7 +2712,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.

                                                                                @@ -2716,7 +2727,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:

                                                                                @@ -2734,7 +2745,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:

                                                                                @@ -2744,16 +2755,16 @@ 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.

                                                                                -

                                                                                lua

                                                                                +

                                                                                lua

                                                                                There are the following ways to invoke this command:

                                                                                1. lua (without any parameters)

                                                                                  @@ -2772,12 +2783,44 @@ directory. If the filename is not supplied, it loads "dfhack.lua".

                                                                                -

                                                                                embark

                                                                                +

                                                                                embark

                                                                                Allows to embark anywhere. Currently windows only.

                                                                                +
                                                                                +

                                                                                lever

                                                                                +

                                                                                Allow manipulation of in-game levers from the dfhack console.

                                                                                +

                                                                                Can list levers, including state and links, with:

                                                                                +
                                                                                +lever list
                                                                                +
                                                                                +

                                                                                To queue a job so that a dwarf will pull the lever 42, use lever pull 42. +This is the same as 'q'uerying the building and queue a 'P'ull request.

                                                                                +

                                                                                To magically toggle the lever immediately, use:

                                                                                +
                                                                                +lever pull 42 --now
                                                                                +
                                                                                +
                                                                                +
                                                                                +

                                                                                stripcaged

                                                                                +

                                                                                For dumping items inside cages. Will mark selected items for dumping, then +a dwarf may come and actually dump it. See also autodump.

                                                                                +

                                                                                With the items argument, only dumps items laying in the cage, excluding +stuff worn by caged creatures. weapons will dump worn weapons, armor +will dump everything worn by caged creatures (including armor and clothing), +and all will dump everything, on a creature or not.

                                                                                +

                                                                                stripcaged list will display on the dfhack console the list of all cages +and their item content.

                                                                                +

                                                                                Without further arguments, all commands work on all cages and animal traps on +the map. With the here argument, considers only the in-game selected cage +(or the cage under the game cursor). To target only specific cages, you can +alternatively pass cage IDs as arguments:

                                                                                +
                                                                                +stripcaged weapons 25321 34228
                                                                                +
                                                                                +
                                                                                -

                                                                                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.

                                                                                @@ -2790,7 +2833,7 @@ existing DF screens, they deliberately use red instead of green for the key.

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

                                                                                images/manipulator.png @@ -2798,8 +2841,10 @@ press 'l'.

                                                                                far left column displays the unit's Happiness (color-coded based on its value), and the right half of the screen displays each dwarf's labor settings and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand -Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds -denote skills not controlled by labors.

                                                                                +Master, and U-Z for Legendary thru Legendary+5).

                                                                                +

                                                                                Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills.

                                                                                +images/manipulator2.png

                                                                                Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time.

                                                                                Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill @@ -2827,7 +2872,7 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

                                                                                -

                                                                                AutoMaterial

                                                                                +

                                                                                AutoMaterial

                                                                                The automaterial plugin makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one.

                                                                                @@ -2874,14 +2919,14 @@ materials, it returns you back to this screen. If you use this along with severa enabled materials, you should be able to place complex constructions more conveniently.

                                                                                -

                                                                                gui/liquids

                                                                                +

                                                                                gui/liquids

                                                                                To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

                                                                                images/liquids.png

                                                                                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 (the example config uses Ctrl-M) and activate in the 'q' mode.

                                                                                images/mechanisms.png

                                                                                Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -2891,7 +2936,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.

                                                                                  @@ -2914,7 +2959,7 @@ their species string.

                                                                                  unit profession change to Ctrl-Shift-T.

                                                                                -

                                                                                gui/room-list

                                                                                +

                                                                                gui/room-list

                                                                                To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

                                                                                images/room-list.png @@ -2922,7 +2967,7 @@ either immediately or after opening the assign owner page.

                                                                                list, and allows unassigning them.

                                                                                -

                                                                                gui/choose-weapons

                                                                                +

                                                                                gui/choose-weapons

                                                                                Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

                                                                                Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -2933,7 +2978,7 @@ only that entry, and does it even if it is not 'individual choice'.

                                                                                and may lead to inappropriate weapons being selected.

                                                                                -

                                                                                gui/guide-path

                                                                                +

                                                                                gui/guide-path

                                                                                Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

                                                                                images/guide-path.png @@ -2941,7 +2986,7 @@ the cursor over a Guide order.

                                                                                computes it when the order is executed for the first time.

                                                                                -

                                                                                gui/workshop-job

                                                                                +

                                                                                gui/workshop-job

                                                                                Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

                                                                                images/workshop-job.png @@ -2977,7 +3022,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

                                                                                -

                                                                                gui/workflow

                                                                                +

                                                                                gui/workflow

                                                                                Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

                                                                                images/workflow.png @@ -3007,7 +3052,7 @@ suit your need, and set the item count range.

                                                                                If you don't need advanced settings, you can just press 'y' to confirm creation.

                                                                                -

                                                                                gui/assign-rack

                                                                                +

                                                                                gui/assign-rack

                                                                                Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

                                                                                images/assign-rack.png @@ -3032,7 +3077,7 @@ of currently assigned racks for every valid squad.

                                                                                -

                                                                                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.

                                                                                @@ -3043,20 +3088,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 (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

                                                                                @@ -3079,7 +3124,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 @@ -3090,11 +3135,11 @@ 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 @@ -3105,7 +3150,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.

                                                                                @@ -3129,7 +3174,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 @@ -3160,7 +3205,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 @@ -3169,7 +3214,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 @@ -3180,7 +3225,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 3434a240d..b9844debd 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2032,8 +2032,12 @@ This tool implements a Dwarf Therapist-like interface within the game UI. The far left column displays the unit's Happiness (color-coded based on its value), and the right half of the screen displays each dwarf's labor settings and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand -Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds -denote skills not controlled by labors. +Master, and U-Z for Legendary thru Legendary+5). + +Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills. + +.. image:: images/manipulator2.png Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time. diff --git a/images/manipulator.png b/images/manipulator.png index 0a546034557fceb446aa0ccb8ddb51883fd71a84..44b603600b36d2954163982c2ad4aa37a36b12e6 100644 GIT binary patch literal 9024 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 diff --git a/images/manipulator2.png b/images/manipulator2.png new file mode 100644 index 0000000000000000000000000000000000000000..250b2f2ab7492a51efaefcac761811b3acef4089 GIT binary patch literal 8840 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 Date: Tue, 27 Nov 2012 11:55:53 -0600 Subject: [PATCH 218/472] Pack manipulator screenshots --- images/manipulator.png | Bin 9024 -> 7724 bytes images/manipulator2.png | Bin 8840 -> 7558 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/manipulator.png b/images/manipulator.png index 44b603600b36d2954163982c2ad4aa37a36b12e6..1f452549f9e0abc9998f298eb19a9a3650ee1ed5 100644 GIT binary patch literal 7724 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 POaWs<3w)LSmFWKgF`t0@ literal 8840 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 Date: Wed, 28 Nov 2012 13:33:07 +0100 Subject: [PATCH 219/472] ruby: add DFHack::VERSION --- plugins/ruby/ruby.cpp | 7 +++++++ plugins/ruby/ruby.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 13190f70d..db94ad650 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -438,6 +438,12 @@ static VALUE rb_cDFHack; // DFHack module ruby methods, binds specific dfhack methods +// df-dfhack version (eg "0.34.11-r2") +static VALUE rb_dfversion(VALUE self) +{ + return rb_str_new(DFHACK_VERSION, strlen(DFHACK_VERSION)); +} + // enable/disable calls to DFHack.onupdate() static VALUE rb_dfonupdate_active(VALUE self) { @@ -955,6 +961,7 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); + rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0); rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2); rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index b7f7590e9..27cde675a 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -23,6 +23,8 @@ module Kernel end module DFHack + VERSION = version + class OnupdateCallback attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description def initialize(descr, cb, tl, initdelay=0) From 593dc4f55486b02a258953558db2054cd3607123 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 25 Nov 2012 19:01:58 +1300 Subject: [PATCH 220/472] Fix handling of manipulator hotkey in unit search screen --- plugins/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 742fa9277..a14397fba 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -521,7 +521,8 @@ private: virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!is_entry_mode()) { From bfc11cf94636856a55d0a26ddcd139d41c3d11e1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 28 Nov 2012 19:25:01 +0400 Subject: [PATCH 221/472] Add persistent history of per-constraint item counts in workflow. This will be needed for properly merging or integrating the status screen by falconne. The history is maintained as a circular buffer of up to 28 entries, and persists in save files. --- library/include/modules/World.h | 49 ++++++++++++++++ plugins/workflow.cpp | 101 +++++++++++++++++++++++++++++--- 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/library/include/modules/World.h b/library/include/modules/World.h index a945c4e72..f1fea52a1 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -81,6 +81,55 @@ namespace DFHack int &ival(int i) { return int_values[i]; } int ival(int i) const { return int_values[i]; } + // Pack binary data into string field. + // Since DF serialization chokes on NUL bytes, + // use bit magic to ensure none of the bytes is 0. + // Choose the lowest bit for padding so that + // sign-extend can be used normally. + + size_t data_size() const { return str_value->size(); } + + bool check_data(size_t off, size_t sz = 1) { + return (str_value->size() >= off+sz); + } + void ensure_data(size_t off, size_t sz = 0) { + if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01'); + } + uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; } + + static const size_t int7_size = 1; + uint8_t get_uint7(size_t off) { + uint8_t *p = pdata(off); + return p[0]>>1; + } + int8_t get_int7(size_t off) { + uint8_t *p = pdata(off); + return int8_t(p[0])>>1; + } + void set_uint7(size_t off, uint8_t val) { + uint8_t *p = pdata(off); + p[0] = uint8_t((val<<1) | 1); + } + void set_int7(size_t off, int8_t val) { set_uint7(off, val); } + + static const size_t int28_size = 4; + uint32_t get_uint28(size_t off) { + uint8_t *p = pdata(off); + return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20); + } + int32_t get_int28(size_t off) { + uint8_t *p = pdata(off); + return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20); + } + void set_uint28(size_t off, uint32_t val) { + uint8_t *p = pdata(off); + p[0] = uint8_t((val<<1) | 1); + p[1] = uint8_t((val>>6) | 1); + p[2] = uint8_t((val>>13) | 1); + p[3] = uint8_t((val>>20) | 1); + } + void set_int28(size_t off, int32_t val) { set_uint28(off, val); } + PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) : id(id), key_value(key), str_value(sv), int_values(iv) {} diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 6e15a4537..813326175 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -293,6 +293,7 @@ typedef std::map, bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; + PersistentDataItem history; // Fixed key parsed into fields bool is_craft; @@ -308,7 +309,7 @@ struct ItemConstraint { int weight; std::vector jobs; - int item_amount, item_count, item_inuse; + int item_amount, item_count, item_inuse_amount, item_inuse_count; bool request_suspend, request_resume; bool is_active, cant_resume_reported; @@ -318,7 +319,7 @@ struct ItemConstraint { public: ItemConstraint() : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), - weight(0), item_amount(0), item_count(0), item_inuse(0), + weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), is_active(false), cant_resume_reported(false) {} @@ -352,6 +353,44 @@ public: request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); } + + static const size_t int28_size = PersistentDataItem::int28_size; + static const size_t hist_entry_size = PersistentDataItem::int28_size * 4; + + size_t history_size() { + return history.data_size() / hist_entry_size; + } + size_t history_base(int idx) { + size_t hsize = history_size(); + return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size; + } + int history_count(int idx) { + return history.get_int28(history_base(idx) + 0*int28_size); + } + int history_amount(int idx) { + return history.get_int28(history_base(idx) + 1*int28_size); + } + int history_inuse_count(int idx) { + return history.get_int28(history_base(idx) + 2*int28_size); + } + int history_inuse_amount(int idx) { + return history.get_int28(history_base(idx) + 3*int28_size); + } + + void updateHistory() + { + size_t buffer_size = history_size(); + if (buffer_size < 28) + history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size); + history.ival(0) = (history.ival(0)+1) % buffer_size; + + size_t base = history.ival(0) * hist_entry_size; + + history.set_int28(base + 0*int28_size, item_count); + history.set_int28(base + 1*int28_size, item_amount); + history.set_int28(base + 2*int28_size, item_inuse_count); + history.set_int28(base + 3*int28_size, item_inuse_amount); + } }; /****************************** @@ -649,6 +688,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) update_job_data(out); process_constraints(out); + + for (size_t i = 0; i < constraints.size(); i++) + constraints[i]->updateHistory(); } } @@ -659,6 +701,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) * ITEM COUNT CONSTRAINT * ******************************/ +static std::string history_key(PersistentDataItem &config) { + return stl_sprintf("workflow/history/%d", config.entry_id()); +} + static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg, bool create) { std::vector tokens; @@ -776,6 +822,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->init(str); } + nct->history = World::GetPersistentData(history_key(nct->config), NULL); + constraints.push_back(nct); return nct; } @@ -787,6 +835,7 @@ static void delete_constraint(ItemConstraint *cv) vector_erase_at(constraints, idx); World::DeletePersistentData(cv->config); + World::DeletePersistentData(cv->history); delete cv; } @@ -1064,7 +1113,8 @@ static void map_job_items(color_ostream &out) { constraints[i]->item_amount = 0; constraints[i]->item_count = 0; - constraints[i]->item_inuse = 0; + constraints[i]->item_inuse_amount = 0; + constraints[i]->item_inuse_count = 0; } meltable_count = 0; @@ -1177,7 +1227,8 @@ static void map_job_items(color_ostream &out) isAssignedSquad(item)) { is_invalid = true; - cv->item_inuse++; + cv->item_inuse_count++; + cv->item_inuse_amount += item->getStackSize(); } else { @@ -1367,7 +1418,8 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); Lua::SetField(L, cv->item_count, ctable, "cur_count"); - Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); + Lua::SetField(L, cv->item_inuse_amount, ctable, "cur_in_use_amount"); + Lua::SetField(L, cv->item_inuse_count, ctable, "cur_in_use_count"); // Current state value @@ -1463,6 +1515,40 @@ static int setConstraint(lua_State *L) return 1; } +static int getCountHistory(lua_State *L) +{ + auto token = luaL_checkstring(L, 1); + + color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); + + ItemConstraint *icv = get_constraint(out, token, NULL, false); + + if (icv) + { + size_t hsize = icv->history_size(); + + lua_createtable(L, hsize, 0); + + for (int i = hsize-1; i >= 0; i--) + { + lua_createtable(L, 0, 4); + + Lua::SetField(L, icv->history_amount(i), -1, "cur_amount"); + Lua::SetField(L, icv->history_count(i), -1, "cur_count"); + Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount"); + Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count"); + + lua_rawseti(L, -2, hsize-i); // reverse order + } + } + else + lua_pushnil(L); + + return 1; +} + + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isEnabled), DFHACK_LUA_FUNCTION(setEnabled), @@ -1474,6 +1560,7 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(listConstraints), DFHACK_LUA_COMMAND(findConstraint), DFHACK_LUA_COMMAND(setConstraint), + DFHACK_LUA_COMMAND(getCountHistory), DFHACK_LUA_END }; @@ -1521,10 +1608,10 @@ static void print_constraint(color_ostream &out, ItemConstraint *cv, bool no_job << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; out.reset_color(); - if (cv->item_count || cv->item_inuse) + if (cv->item_count || cv->item_inuse_count) out << prefix << " items: amount " << cv->item_amount << "; " << cv->item_count << " stacks available, " - << cv->item_inuse << " in use." << endl; + << cv->item_inuse_count << " in use." << endl; if (no_job) return; From 3964c8a5816822826647229d4979bb04cf2767c5 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 28 Nov 2012 17:40:37 +0200 Subject: [PATCH 222/472] gm-editor, set field to lua return value. --- scripts/gui/gm-editor.lua | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index ce169dedc..b288b8a25 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -169,6 +169,19 @@ function GmEditorUi:onRenderBody( dc) current_item=current_item+1 end +end +function GmEditorUi:set(input) + local trg=self:currentTarget() + + if input== nil then + dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",dfhack.curry(self.set,self)) + return + end + local e,what=load("return function(v) return "..input.." end") + if e==nil then + dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED) + end + trg.target[trg.keys[trg.selected]]=e()(trg) end function GmEditorUi:onInput(keys) if self.mode==MODE_BROWSE then @@ -190,6 +203,8 @@ end dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d",size,off,size,off),COLOR_WHITE) elseif keys.CUSTOM_ALT_F then self:find() + elseif keys.CUSTOM_ALT_S then + self:set() elseif keys.CUSTOM_ALT_E then --self:specialEditor() elseif keys.CUSTOM_ALT_I then --insert From 8081ab8f51665d04a56dc08680f4ddabc4925453 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 28 Nov 2012 19:13:17 +0200 Subject: [PATCH 223/472] Added minimal width specification to label text tokens. --- Lua API.rst | 4 ++++ library/lua/gui/widgets.lua | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Lua API.rst b/Lua API.rst index 126bc7f9d..dd2ea3c8a 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2736,7 +2736,11 @@ containing newlines, or a table with the following possible fields: * ``token.id`` Specifies a unique identifier for the token. + +* ``token.minw`` + Specifies minimal token width + * ``token.line``, ``token.x1``, ``token.x2`` Reserved for internal use. diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ad408a2ea..c00c7254c 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -282,7 +282,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end end end - + if token.minw and x Date: Wed, 28 Nov 2012 19:46:56 +0100 Subject: [PATCH 224/472] follow rename itemst.flags.artifact1 -> artifact --- plugins/autodump.cpp | 4 ++-- plugins/autolabor.cpp | 2 +- plugins/devel/stockcheck.cpp | 2 +- plugins/workflow.cpp | 2 +- scripts/autofarm.rb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 5eb25964e..5b4804647 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -161,7 +161,7 @@ static command_result autodump_main(color_ostream &out, vector & parame || itm->flags.bits.in_building || itm->flags.bits.in_chest // || itm->flags.bits.in_inventory - || itm->flags.bits.artifact1 + || itm->flags.bits.artifact ) continue; @@ -271,7 +271,7 @@ command_result df_autodump_destroy_item(color_ostream &out, vector & pa if (item->flags.bits.construction || item->flags.bits.in_building || - item->flags.bits.artifact1) + item->flags.bits.artifact) { out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n"); return CR_FAILURE; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 83718bd09..e5047b434 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1556,7 +1556,7 @@ static int stockcheck(color_ostream &out, vector & parameters) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); F(spider_web); F(owned); F(in_job); #undef F diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 666db0d79..679411b0e 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -144,7 +144,7 @@ static command_result stockcheck(color_ostream &out, vector & parameter #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); F(spider_web); F(owned); F(in_job); #undef F diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 813326175..05fdca55b 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1126,7 +1126,7 @@ static void map_job_items(color_ostream &out) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); #undef F bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index c89cb9ff4..cd381089e 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -66,7 +66,7 @@ class AutoFarm if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact1 && plantable.has_key?(i.mat_index)) + !i.flags.artifact && plantable.has_key?(i.mat_index)) counts[i.mat_index] = counts[i.mat_index] + i.stack_size end } From 771a5ac50bfae154773a4b083277ec81ffa38148 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 28 Nov 2012 20:08:34 +0100 Subject: [PATCH 225/472] ruby: tweak flagarray#inspect --- plugins/ruby/ruby-autogen-defs.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index c3203bd52..4148659a6 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -138,7 +138,6 @@ module DFHack @@inspecting = {} # avoid infinite recursion on mutually-referenced objects def inspect cn = self.class.name.sub(/^DFHack::/, '') - cn << ' @' << ('0x%X' % _memaddr) if cn != '' out = "#<#{cn}" return out << ' ...>' if @@inspecting[_memaddr] @@inspecting[_memaddr] = true @@ -655,6 +654,13 @@ module DFHack DFHack.memory_bitarray_set(@_memaddr, idx, v) end end + def inspect + out = "#' + end include Enumerable end From 01966167f6e9f385632dbccd4149ae61c494faf4 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 28 Nov 2012 21:13:42 +0200 Subject: [PATCH 226/472] gm-editor updated to use widgets. --- scripts/gui/gm-editor.lua | 278 +++++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 122 deletions(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index b288b8a25..ade78a2df 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -1,7 +1,17 @@ -- Interface powered item editor. local gui = require 'gui' local dialog = require 'gui.dialogs' +local widgets =require 'gui.widgets' local args={...} + +local keybindings={ + offset={key="CUSTOM_ALT_O",desc="Show current items offset"}, + find={key="CUSTOM_F",desc="Find a value by entering a predicate"}, + lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"}, + insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"}, + delete={key="CUSTOM_ALT_D",desc="Delete selected entry"}, + help={key="HELP",desc="Show this help"}, +} function getTargetFromScreens() local my_trg if dfhack.gui.getCurFocus() == 'item' then @@ -28,46 +38,94 @@ function getTargetFromScreens() return my_trg end - -local MODE_BROWSE=0 -local MODE_EDIT=1 GmEditorUi = defclass(GmEditorUi, gui.FramedScreen) GmEditorUi.ATTRS={ frame_style = gui.GREY_LINE_FRAME, frame_title = "GameMaster's editor", } +function GmEditorUi:onHelp() + self.subviews.pages:setSelected(2) +end +function burning_red(input) -- todo does not work! bug angavrilov that so that he would add this, very important!! + local col=COLOR_LIGHTRED + return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}} +end function GmEditorUi:init(args) self.stack={} self.item_count=0 - self.mode=MODE_BROWSE self.keys={} - self:pushTarget(args.target) + local helptext={{text="Help"},NEWLINE,NEWLINE} + for k,v in pairs(keybindings) do + table.insert(helptext,{text=v.desc,key=v.key,key_sep=':'}) + table.insert(helptext,NEWLINE) + end + table.insert(helptext,NEWLINE) + table.insert(helptext,{text="DISCLAIMER:",pen=dfhack.pen.parse{fg=COLOR_RED,bg=0}}) + table.insert(helptext,NEWLINE) + table.insert(helptext,"Association Of ") + table.insert(helptext,{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}) + table.insert(helptext,"Dwarves (AOPD) is not responsible for all the damage") + table.insert(helptext,NEWLINE) + table.insert(helptext,"that this tool can (and will) cause to you and your loved dwarves") + table.insert(helptext,NEWLINE) + table.insert(helptext,"and/or saves.Please use with caution.") + table.insert(helptext,NEWLINE) + table.insert(helptext,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}) - return self + local helpPage=widgets.Panel{ + subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} + local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), + text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} + local mainPage=widgets.Panel{ + subviews={ + mainList, + widgets.Label{text={{text="",id="name",minw=50},{gap=1,text="Help",key="HELP",key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, + --widgets.Label{text="BLAH2"} + } + ,view_id='page_main'} + + local pages=widgets.Pages{subviews={mainPage,helpPage},view_id="pages"} + self:addviews{ + pages + } + self:pushTarget(args.target) end function GmEditorUi:find(test) local trg=self:currentTarget() + + if test== nil then + dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self)) + return + end + + local e,what=load("return function(k,v) return "..test.." end") + if e==nil then + dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED) + end + if trg.target and trg.target._kind and trg.target._kind=="container" then - if test== nil then - dialog.showInputPrompt("Test function","Input function that tests(k,v as argument):",COLOR_WHITE,"",dfhack.curry(self.find,self)) - return - end - local e,what=load("return function(k,v) return "..test.." end") - if e==nil then - dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED) - end + for k,v in pairs(trg.target) do if e()(k,v)==true then self:pushTarget(v) return end end + else + local i=1 + for k,v in pairs(trg.target) do + if e()(k,v)==true then + self.subviews.list_main:setSelected(i) + return + end + i=i+1 + end end end function GmEditorUi:insertNew(typename) local tp=typename if typename== nil then - dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",dfhack.curry(self.insertNew,self)) + dialog.showInputPrompt("Class type","Input class type:",COLOR_WHITE,"",self:callback("insertNew")) return end local ntype=df[tp] @@ -79,152 +137,124 @@ function GmEditorUi:insertNew(typename) local trg=self:currentTarget() if trg.target and trg.target._kind and trg.target._kind=="container" then local thing=ntype:new() - dfhack.call_with_finalizer(1,false,df.delete,thing,trg.target.insert,trg.target,'#',thing) + dfhack.call_with_finalizer(1,false,df.delete,thing,function (tscreen,target,to_insert) + target:insert("#",to_insert); tscreen:updateTarget(true,true);end,self,trg.target,thing) end end -function GmEditorUi:deleteSelected() +function GmEditorUi:deleteSelected(key) local trg=self:currentTarget() if trg.target and trg.target._kind and trg.target._kind=="container" then - trg.target:erase(trg.keys[trg.selected]) + trg.target:erase(key) + self:updateTarget(true,true) end end +function GmEditorUi:getSelectedKey() + return self:currentTarget().keys[self.subviews.list_main:getSelected()] +end function GmEditorUi:currentTarget() return self.stack[#self.stack] end -function GmEditorUi:changeSelected(delta) - local trg=self:currentTarget() - if trg.item_count <= 1 then return end - trg.selected = 1 + (trg.selected + delta - 1) % trg.item_count -end -function GmEditorUi:editSelected() +function GmEditorUi:editSelected(index,choice) local trg=self:currentTarget() + local trg_key=trg.keys[index] if trg.target and trg.target._kind and trg.target._kind=="bitfield" then - trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] + trg.target[trg_key]= not trg.target[trg_key] + self:updateTarget(true) else --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") - local trg_type=type(trg.target[trg.keys[trg.selected]]) + local trg_type=type(trg.target[trg_key]) if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected - self.mode=MODE_EDIT - self.input=tostring(trg.target[trg.keys[trg.selected]]) + dialog.showInputPrompt(trg_key,"Enter new value:",COLOR_WHITE, + tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) + elseif trg_type=='boolean' then - trg.target[trg.keys[trg.selected]]= not trg.target[trg.keys[trg.selected]] + trg.target[trg_key]= not trg.target[trg_key] + self:updateTarget(true) elseif trg_type=='userdata' then - self:pushTarget(trg.target[trg.keys[trg.selected]]) - --local screen = mkinstance(gui.FramedScreen,GmEditorUi):init(trg.target[trg.keys[trg.selected]]) -- does not work - --screen:show() + self:pushTarget(trg.target[trg_key]) else print("Unknow type:"..trg_type) - print("Subtype:"..tostring(trg.target[trg.keys[trg.selected]]._kind)) + print("Subtype:"..tostring(trg.target[trg_key]._kind)) end end end -function GmEditorUi:cancelEdit() - self.mode=MODE_BROWSE - self.input="" -end -function GmEditorUi:commitEdit() - local trg=self:currentTarget() - self.mode=MODE_BROWSE - if type(trg.target[trg.keys[trg.selected]])=='number' then - trg.target[trg.keys[trg.selected]]=tonumber(self.input) - elseif type(trg.target[trg.keys[trg.selected]])=='string' then - trg.target[trg.keys[trg.selected]]=self.input - end -end -function GmEditorUi:onRenderBody( dc) + +function GmEditorUi:commitEdit(key,value) local trg=self:currentTarget() - dc:seek(2,1):string(tostring(trg.target), COLOR_RED) - local offset=2 - local page_offset=0 - local current_item=1 - local t_col - local width,height=self:getWindowSize() - local window_height=height-offset-2 - local cursor_window=math.floor(trg.selected / window_height) - if cursor_window>0 then - page_offset=cursor_window*window_height-1 - end - for k,v in pairs(trg.target) do - - if current_item==trg.selected then - t_col=COLOR_LIGHTGREEN - else - t_col=COLOR_GRAY - end - - if current_item-page_offset > 0 then - local y_pos=current_item-page_offset+offset - dc:seek(2,y_pos):string(tostring(k),t_col) - - if self.mode==MODE_EDIT and current_item==trg.selected then - dc:seek(20,y_pos):string(self.input..'_',COLOR_GREEN) - else - dc:seek(20,y_pos):string(tostring(v),t_col) - end - if y_pos+3>height then - break - end - end - current_item=current_item+1 - + if type(trg.target[key])=='number' then + trg.target[key]=tonumber(value) + elseif type(trg.target[key])=='string' then + trg.target[key]=value end + self:updateTarget(true) end -function GmEditorUi:set(input) + +function GmEditorUi:set(key,input) local trg=self:currentTarget() if input== nil then - dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",dfhack.curry(self.set,self)) + dialog.showInputPrompt("Set to what?","Lua code to set to (v cur target):",COLOR_WHITE,"",self:callback("set",key)) return end local e,what=load("return function(v) return "..input.." end") if e==nil then dialog.showMessage("Error!","function failed to compile\n"..what,COLOR_RED) + return end - trg.target[trg.keys[trg.selected]]=e()(trg) + trg.target[key]=e()(trg) + self:updateTarget(true) end - function GmEditorUi:onInput(keys) - if self.mode==MODE_BROWSE then - if keys.LEAVESCREEN then +function GmEditorUi:onInput(keys) + + if keys.LEAVESCREEN then + if self.subviews.pages:getSelected()==2 then + self.subviews.pages:setSelected(1) + else self:popTarget() - elseif keys.CURSOR_UP then - self:changeSelected(-1) - elseif keys.CURSOR_DOWN then - self:changeSelected(1) - elseif keys.CURSOR_UP_FAST then - self:changeSelected(-10) - elseif keys.CURSOR_DOWN_FAST then - self:changeSelected(10) - elseif keys.SELECT then - self:editSelected() - elseif keys.CUSTOM_ALT_O then - local trg=self:currentTarget() - local size,off=df.sizeof(trg.target:_field(trg.keys[trg.selected])) - dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d",size,off,size,off),COLOR_WHITE) - elseif keys.CUSTOM_ALT_F then - self:find() - elseif keys.CUSTOM_ALT_S then - self:set() - elseif keys.CUSTOM_ALT_E then - --self:specialEditor() - elseif keys.CUSTOM_ALT_I then --insert - self:insertNew() - elseif keys.CUSTOM_ALT_D then --delete - self:deleteSelected() end - elseif self.mode==MODE_EDIT then - if keys.LEAVESCREEN then - self:cancelEdit() - elseif keys.SELECT then - self:commitEdit() - elseif keys._STRING then - if keys._STRING==0 then - self.input=string.sub(self.input,1,-2) - else - self.input=self.input.. string.char(keys._STRING) - end + elseif keys[keybindings.offset.key] then + local trg=self:currentTarget() + local size,off=df.sizeof(trg.target:_field(trg.keys[trg.selected])) + dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d",size,off,size,off),COLOR_WHITE) + --elseif keys.CUSTOM_ALT_F then --filter? + elseif keys[keybindings.find.key] then + self:find() + elseif keys[keybindings.lua_set.key] then + self:set(self:getSelectedKey()) + --elseif keys.CUSTOM_I then + -- self:insertSimple() + elseif keys[keybindings.insert.key] then --insert + self:insertNew() + elseif keys[keybindings.delete.key] then --delete + self:deleteSelected(self:getSelectedKey()) + end + + self.super.onInput(self,keys) +end +function GmEditorUi:updateTarget(preserve_pos,reindex) + local trg=self:currentTarget() + if reindex then + trg.keys={} + for k,v in pairs(trg.target) do + table.insert(trg.keys,k) end end + self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target) + local t={} + for k,v in pairs(trg.keys) do + table.insert(t,{text={{text=tostring(v),minw=25},{gap=1,text=tostring(trg.target[v]),}}}) + end + local last_pos + if preserve_pos then + last_pos=self.subviews.list_main:getSelected() + end + self.subviews.list_main:setChoices(t) + if last_pos then + self.subviews.list_main:setSelected(last_pos) + else + self.subviews.list_main:setSelected(1) + end end function GmEditorUi:pushTarget(target_to_push) local new_tbl={} @@ -236,12 +266,16 @@ function GmEditorUi:pushTarget(target_to_push) end new_tbl.item_count=#new_tbl.keys table.insert(self.stack,new_tbl) + + self:updateTarget() end function GmEditorUi:popTarget() table.remove(self.stack) --removes last element if #self.stack==0 then self:dismiss() + return end + self:updateTarget() end function show_editor(trg) local screen = GmEditorUi{target=trg} From 2c9b560872dd328c93383248a967bc20dc715d74 Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 28 Nov 2012 22:13:28 +0200 Subject: [PATCH 227/472] Made dfusion not stop df on input. --- plugins/lua/dfusion.lua | 2 +- plugins/lua/dfusion/tools.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/lua/dfusion.lua b/plugins/lua/dfusion.lua index c7bd9bef3..107ccb870 100644 --- a/plugins/lua/dfusion.lua +++ b/plugins/lua/dfusion.lua @@ -220,7 +220,7 @@ function SimpleMenu:display() local ans repeat local r - r=io.stdin:read() + r=dfhack.lineedit() if r==nil then return end if r=='q' then return end ans=tonumber(r) diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index 3e24c169d..707c62f9d 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -26,7 +26,7 @@ function setrace(name) if name == nil then print("Type new race's token name in full caps (q to quit):") repeat - local entry=io.stdin:read() + local entry=dfhack.lineedit() if entry=="q" then return end @@ -48,7 +48,7 @@ function GiveSentience(names) ids={} print("Type race's token name in full caps to give sentience to:") repeat - id=io.stdin:read() + id=dfhack.lineedit() id=RaceTable[entry] if id~=nil then table.insert(ids,id) From fe2fbe347c949ee8f6c782d353fb9bc715b9d329 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 02:26:10 +0200 Subject: [PATCH 228/472] Added advfort script, now you can have adventurer forts!!!!! --- scripts/advfort.lua | 437 ++++++++++++++++++++++++++++++++++++++ scripts/gui/gm-editor.lua | 22 +- 2 files changed, 448 insertions(+), 11 deletions(-) create mode 100644 scripts/advfort.lua diff --git a/scripts/advfort.lua b/scripts/advfort.lua new file mode 100644 index 000000000..6874da0fe --- /dev/null +++ b/scripts/advfort.lua @@ -0,0 +1,437 @@ +-- allows to do jobs in adv. mode. +local gui = require 'gui' +local wid=require 'gui.widgets' +local dialog=require 'gui.dialogs' +local buildings=require 'dfhack.buildings' +--[[********************** + tools and their uses: + 1. axe -> chop trees + 2. pickaxe -> dig, carve floors/ramps/stairs/etc (engrave too for now) +--]] +mode=mode or 0 +keybinds={ +key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, +key_prev={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, +key_continue={key="A_WAIT",desc="Continue job if available"}, +--key_down_alt1={key="A_CUSTOM_CTRL_D",desc="Use job down"},--does not work? +key_down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, +--key_up_alt1={key="A_CUSTOM_CTRL_E",desc="Use job up"}, --does not work? +key_up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, +key_use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, +} +function Disclaimer(tlb) + local dsc={"The Gathering Against ",{text="Goblin ",pen=dfhack.pen.parse{fg=COLOR_GREEN,bg=0}}, "Oppresion ", + "(TGAGO) is not responsible for all ",NEWLINE,"the damage that this tool can (and will) cause to you and your loved worlds",NEWLINE,"and/or sanity.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} + if tlb then + for _,v in ipairs(dsc) do + table.insert(tlb,v) + end + end + return dsc +end +function showHelp() + local helptext={ + "This tool allow you to perform jobs as a dwarf would in dwarf mode. When ",NEWLINE, + "cursor is available you can press ",{key="SELECT", text="select",key_sep="()"}, + " to enqueue a job from",NEWLINE,"pointer location. If job is 'Build' and there is no planed construction",NEWLINE, + "at cursor this tool show possible building choices.",NEWLINE,NEWLINE,{text="Keybindings:",pen=dfhack.pen.parse{fg=COLOR_CYAN,bg=0}},NEWLINE + } + for k,v in pairs(keybinds) do + table.insert(helptext,{key=v.key,text=v.desc,key_sep=":"}) + table.insert(helptext,NEWLINE) + end + table.insert(helptext,{text="CAREFULL MOVE",pen=dfhack.pen.parse{fg=COLOR_LIGHTGREEN,bg=0}}) + table.insert(helptext,": use job in that direction") + table.insert(helptext,NEWLINE) + table.insert(helptext,NEWLINE) + Disclaimer(helptext) + require("gui.dialogs").showMessage("Help!?!",helptext) +end +function getLastJobLink() + local st=df.global.world.job_list + while st.next~=nil do + st=st.next + end + return st +end +function AddNewJob(job) + local nn=getLastJobLink() + local nl=df.job_list_link:new() + nl.prev=nn + nn.next=nl + nl.item=job + job.list_link=nl +end +function MakeJob(unit,pos,job_type,unit_pos,post_actions) + local nj=df.job:new() + nj.id=df.global.job_next_id + df.global.job_next_id=df.global.job_next_id+1 + --nj.flags.special=true + nj.job_type=job_type + nj.completion_timer=-1 + --nj.unk4a=12 + --nj.unk4b=0 + nj.pos:assign(pos) + AssignUnitToJob(nj,unit,unit_pos) + for k,v in ipairs(post_actions or {}) do + v{job=nj,pos=pos,old_pos=unit_pos,unit=unit} + end + AddNewJob(nj) + return nj +end + +function AssignUnitToJob(job,unit,unit_pos) + job.general_refs:insert("#",{new=df.general_ref_unit_workerst,unit_id=unit.id}) + unit.job.current_job=job + unit_pos=unit_pos or {x=job.pos.x,y=job.pos.y,z=job.pos.z} + unit.path.dest:assign(unit_pos) +end +function SetCreatureRef(args) + local job=args.job + local pos=args.pos + for k,v in pairs(df.global.world.units.active) do + if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then + job.general_refs:insert("#",{new=df.general_ref_unit_cageest,unit_id=v.id}) + return + end + end +end + +function SetPatientRef(args) + local job=args.job + local pos=args.pos + for k,v in pairs(df.global.world.units.active) do + if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then + job.general_refs:insert("#",{new=df.general_ref_unit_patientst,unit_id=v.id}) + return + end + end +end + +function MakePredicateWieldsItem(item_skill) + local pred=function(args) + local inv=args.unit.inventory + for k,v in pairs(inv) do + if v.mode==1 and df.item_weaponst:is_instance(v.item) then + if v.item.subtype.skill_melee==item_skill then --and unit.body.weapon_bp==v.body_part_id + return true + end + end + end + return false,"Correct tool not equiped" + end + return pred +end +function makeset(args) + local tbl={} + for k,v in pairs(args) do + tbl[v]=true + end + return tbl +end +function IsConstruct(args) + local tt=dfhack.maps.getTileType(args.pos) + local cwalls=makeset{ df.tiletype.ConstructedWallRD2, df.tiletype.ConstructedWallR2D, df.tiletype.ConstructedWallR2U, df.tiletype.ConstructedWallRU2, + df.tiletype.ConstructedWallL2U, df.tiletype.ConstructedWallLU2, df.tiletype.ConstructedWallL2D, df.tiletype.ConstructedWallLD2, + df.tiletype.ConstructedWallLRUD, df.tiletype.ConstructedWallRUD, df.tiletype.ConstructedWallLRD, df.tiletype.ConstructedWallLRU, + df.tiletype.ConstructedWallLUD, df.tiletype.ConstructedWallRD, df.tiletype.ConstructedWallRU, df.tiletype.ConstructedWallLU, + df.tiletype.ConstructedWallLD, df.tiletype.ConstructedWallUD, df.tiletype.ConstructedWallLR,} + if cwalls[tt] or dfhack.buildings.findAtTile(args.pos) then + return true + else + return false, "Can only do it on constructions" + end +end +function IsWall(args) + local tt=dfhack.maps.getTileType(args.pos) + local walls=makeset{df.tiletype.StoneWallWorn1, df.tiletype.StoneWallWorn2, df.tiletype.StoneWallWorn3, df.tiletype.StoneWall, + df.tiletype.SoilWall, df.tiletype.LavaWallSmoothRD2, df.tiletype.LavaWallSmoothR2D, df.tiletype.LavaWallSmoothR2U, df.tiletype.LavaWallSmoothRU2, + df.tiletype.LavaWallSmoothL2U, df.tiletype.LavaWallSmoothLU2, df.tiletype.LavaWallSmoothL2D, df.tiletype.LavaWallSmoothLD2, df.tiletype.LavaWallSmoothLRUD, + df.tiletype.LavaWallSmoothRUD, df.tiletype.LavaWallSmoothLRD, df.tiletype.LavaWallSmoothLRU, df.tiletype.LavaWallSmoothLUD, df.tiletype.LavaWallSmoothRD, + df.tiletype.LavaWallSmoothRU, df.tiletype.LavaWallSmoothLU, df.tiletype.LavaWallSmoothLD, df.tiletype.LavaWallSmoothUD, df.tiletype.LavaWallSmoothLR, + df.tiletype.FeatureWallSmoothRD2, df.tiletype.FeatureWallSmoothR2D, df.tiletype.FeatureWallSmoothR2U, df.tiletype.FeatureWallSmoothRU2, + df.tiletype.FeatureWallSmoothL2U, df.tiletype.FeatureWallSmoothLU2, df.tiletype.FeatureWallSmoothL2D, df.tiletype.FeatureWallSmoothLD2, + df.tiletype.FeatureWallSmoothLRUD, df.tiletype.FeatureWallSmoothRUD, df.tiletype.FeatureWallSmoothLRD, df.tiletype.FeatureWallSmoothLRU, + df.tiletype.FeatureWallSmoothLUD, df.tiletype.FeatureWallSmoothRD, df.tiletype.FeatureWallSmoothRU, df.tiletype.FeatureWallSmoothLU, + df.tiletype.FeatureWallSmoothLD, df.tiletype.FeatureWallSmoothUD, df.tiletype.FeatureWallSmoothLR, df.tiletype.StoneWallSmoothRD2, + df.tiletype.StoneWallSmoothR2D, df.tiletype.StoneWallSmoothR2U, df.tiletype.StoneWallSmoothRU2, df.tiletype.StoneWallSmoothL2U, + df.tiletype.StoneWallSmoothLU2, df.tiletype.StoneWallSmoothL2D, df.tiletype.StoneWallSmoothLD2, df.tiletype.StoneWallSmoothLRUD, + df.tiletype.StoneWallSmoothRUD, df.tiletype.StoneWallSmoothLRD, df.tiletype.StoneWallSmoothLRU, df.tiletype.StoneWallSmoothLUD, + df.tiletype.StoneWallSmoothRD, df.tiletype.StoneWallSmoothRU, df.tiletype.StoneWallSmoothLU, df.tiletype.StoneWallSmoothLD, + df.tiletype.StoneWallSmoothUD, df.tiletype.StoneWallSmoothLR, df.tiletype.LavaWallWorn1, df.tiletype.LavaWallWorn2, df.tiletype.LavaWallWorn3, + df.tiletype.LavaWall, df.tiletype.FeatureWallWorn1, df.tiletype.FeatureWallWorn2, df.tiletype.FeatureWallWorn3, df.tiletype.FeatureWall, + df.tiletype.FrozenWallWorn1, df.tiletype.FrozenWallWorn2, df.tiletype.FrozenWallWorn3, df.tiletype.FrozenWall, df.tiletype.MineralWallSmoothRD2, + df.tiletype.MineralWallSmoothR2D, df.tiletype.MineralWallSmoothR2U, df.tiletype.MineralWallSmoothRU2, df.tiletype.MineralWallSmoothL2U, + df.tiletype.MineralWallSmoothLU2, df.tiletype.MineralWallSmoothL2D, df.tiletype.MineralWallSmoothLD2, df.tiletype.MineralWallSmoothLRUD, + df.tiletype.MineralWallSmoothRUD, df.tiletype.MineralWallSmoothLRD, df.tiletype.MineralWallSmoothLRU, df.tiletype.MineralWallSmoothLUD, + df.tiletype.MineralWallSmoothRD, df.tiletype.MineralWallSmoothRU, df.tiletype.MineralWallSmoothLU, df.tiletype.MineralWallSmoothLD, + df.tiletype.MineralWallSmoothUD, df.tiletype.MineralWallSmoothLR, df.tiletype.MineralWallWorn1, df.tiletype.MineralWallWorn2, + df.tiletype.MineralWallWorn3, df.tiletype.MineralWall, df.tiletype.FrozenWallSmoothRD2, df.tiletype.FrozenWallSmoothR2D, + df.tiletype.FrozenWallSmoothR2U, df.tiletype.FrozenWallSmoothRU2, df.tiletype.FrozenWallSmoothL2U, df.tiletype.FrozenWallSmoothLU2, + df.tiletype.FrozenWallSmoothL2D, df.tiletype.FrozenWallSmoothLD2, df.tiletype.FrozenWallSmoothLRUD, df.tiletype.FrozenWallSmoothRUD, + df.tiletype.FrozenWallSmoothLRD, df.tiletype.FrozenWallSmoothLRU, df.tiletype.FrozenWallSmoothLUD, df.tiletype.FrozenWallSmoothRD, + df.tiletype.FrozenWallSmoothRU, df.tiletype.FrozenWallSmoothLU, df.tiletype.FrozenWallSmoothLD, df.tiletype.FrozenWallSmoothUD, + df.tiletype.FrozenWallSmoothLR, + } + if walls[tt] then + return true + else + return false, "Can only do it on walls" + end +end +function IsTree(args) + local tt=dfhack.maps.getTileType(args.pos) + if tt==24 then + return true + else + return false, "Can only do it on trees" + end + +end +function IsWater(args) + return true +end +function IsPlant(args) + return true +end +function IsUnit(args) + local pos=args.pos + for k,v in pairs(df.global.world.units.active) do + if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z then + return true + end + end + return false,"Unit must be present" +end +function itemsAtPos(pos) + local ret={} + for k,v in pairs(df.global.world.items.all) do + if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z and v.flags.on_ground then + table.insert(ret,v) + end + end + return ret +end +function AssignBuildingRef(args) + local bld=dfhack.buildings.findAtTile(args.pos) + args.job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=bld.id}) + bld.jobs:insert("#",args.job) +end +--[[ building submodule... ]]-- +function DialogBuildingChoose(on_select, on_cancel) + blist={} + for i=df.building_type._first_item,df.building_type._last_item do + table.insert(blist,df.building_type[i]) + end + dialog.showListPrompt("Building list", "Choose building:", COLOR_WHITE, blist, on_select, on_cancel, nil, true) +end +function DialogSubtypeChoose(subtype,on_select, on_cancel) + blist={} + for i=subtype._first_item,subtype._last_item do + table.insert(blist,subtype[i]) + end + dialog.showListPrompt("Subtype", "Choose subtype:", COLOR_WHITE, blist, on_select, on_cancel, nil, true) +end +--workshop, furnaces, traps +invalid_buildings={} +function SubtypeChosen(args,index) + args.subtype=index-1 + buildings.constructBuilding(args) +end +function BuildingChosen(st_pos,pos,index) + local b_type=index-2 + local args={} + args.type=b_type + args.pos=pos + args.items=itemsAtPos(st_pos) + if invalid_buildings[b_type] then + return + elseif b_type==df.building_type.Construction then + DialogSubtypeChoose(df.construction_type,dfhack.curry(SubtypeChosen,args)) + return + elseif b_type==df.building_type.Furnace then + DialogSubtypeChoose(df.furnace_type,dfhack.curry(SubtypeChosen,args)) + return + elseif b_type==df.building_type.Trap then + DialogSubtypeChoose(df.trap_type,dfhack.curry(SubtypeChosen,args)) + return + elseif b_type==df.building_type.Workshop then + DialogSubtypeChoose(df.workshop_type,dfhack.curry(SubtypeChosen,args)) + return + else + buildings.constructBuilding(args) + end +end + +--[[ end of buildings ]]-- +function AssignJobToBuild(args) + local bld=dfhack.buildings.findAtTile(args.pos) + if bld~=nil then + if #bld.jobs>0 then + AssignUnitToJob(bld.jobs[0],args.unit,args.old_pos) + else + local jb=MakeJob(args.unit,args.pos,df.job_type.ConstructBuilding,args.old_pos,{AssignBuildingRef}) + local its=itemsAtPos(args.old_pos) + for k,v in pairs(its) do + jb.items:insert("#",{new=true,item=v,role=2}) + end + + end + else + DialogBuildingChoose(dfhack.curry(BuildingChosen,args.old_pos,args.pos)) + end +end +function ContinueJob(unit) + local c_job=unit.job.current_job + if c_job then + for k,v in pairs(c_job.items) do + if v.is_fetching==1 then + unit.path.dest:assign(v.item.pos) + return + end + end + unit.path.dest:assign(c_job.pos) + end +end + +dig_modes={ + {"CarveFortification" ,df.job_type.CarveFortification,{IsWall}}, + {"DetailWall" ,df.job_type.DetailWall,{IsWall}}, + {"DetailFloor" ,df.job_type.DetailFloor}, + --{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work?? + {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, + {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, + {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, + {"CarveUpDownStaircase" ,df.job_type.CarveUpDownStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, + {"CarveRamp" ,df.job_type.CarveRamp,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, + {"DigChannel" ,df.job_type.DigChannel,{MakePredicateWieldsItem(df.job_skill.MINING)}}, + {"FellTree" ,df.job_type.FellTree,{MakePredicateWieldsItem(df.job_skill.AXE),IsTree}}, + {"Fish" ,df.job_type.Fish,{IsWater}}, + --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, + --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, + --{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, + {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant}}, + {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, + --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, + {"Build" ,AssignJobToBuild}, + +} + + +usetool=defclass(usetool,gui.Screen) +function usetool:getModeName() + local adv=df.global.world.units.active[0] + if adv.job.current_job then + return string.format("%s working(%d) ",(dig_modes[(mode or 0)+1][1] or ""),adv.job.current_job.completion_timer) + else + return dig_modes[(mode or 0)+1][1] or " " + end + +end +function usetool:init(args) + self:addviews{ + wid.Label{ + frame = {xalign=0,yalign=0}, + text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getModeName,self),minw=15},{gap=1,key=keybinds.key_next.key}} + } + } +end +function usetool:onRenderBody(dc) + self._native.parent:logic() + self:renderParent() +end +MOVEMENT_KEYS = { + A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, + A_CARE_MOVE_W = { -1, 0, 0 }, A_CARE_MOVE_E = { 1, 0, 0 }, + A_CARE_MOVE_NW = { -1, -1, 0 }, A_CARE_MOVE_NE = { 1, -1, 0 }, + A_CARE_MOVE_SW = { -1, 1, 0 }, A_CARE_MOVE_SE = { 1, 1, 0 }, + --[[A_MOVE_N = { 0, -1, 0 }, A_MOVE_S = { 0, 1, 0 }, + A_MOVE_W = { -1, 0, 0 }, A_MOVE_E = { 1, 0, 0 }, + A_MOVE_NW = { -1, -1, 0 }, A_MOVE_NE = { 1, -1, 0 }, + A_MOVE_SW = { -1, 1, 0 }, A_MOVE_SE = { 1, 1, 0 },--]] + --[[CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, + CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, + CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, + CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true },]]-- + A_CUSTOM_CTRL_D = { 0, 0, -1 }, + A_CUSTOM_CTRL_E = { 0, 0, 1 }, + CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, + A_MOVE_SAME_SQUARE={0,0,0}, + SELECT={0,0,0}, +} +function moddedpos(pos,delta) + return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} +end +function usetool:onDismiss() + local adv=df.global.world.units.active[0] + --TODO: cancel job + --[[if adv and adv.job.current_job then + local cj=adv.job.current_job + adv.jobs.current_job=nil + cj:delete() + end]] +end +function usetool:onHelp() + showHelp() +end +function usetool:onInput(keys) + + if keys.LEAVESCREEN then + self:dismiss() + elseif keys[keybinds.key_next.key] then + mode=(mode+1)%#dig_modes + elseif keys[keybinds.key_prev.key] then + mode=mode-1 + if mode<0 then mode=#dig_modes-1 end + --elseif keys.A_LOOK then + -- self:sendInputToParent("A_LOOK") + elseif keys[keybinds.key_continue.key] then + ContinueJob(df.global.world.units.active[0]) + self:sendInputToParent("A_WAIT") + else + local adv=df.global.world.units.active[0] + local cur_mode=dig_modes[(mode or 0)+1] + local failed=false + for code,_ in pairs(keys) do + --print(code) + if MOVEMENT_KEYS[code] then + local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], + old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} + if code=="SELECT" then + if df.global.cursor.x~=-30000 then + state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} + else + break + end + end + + for _,p in pairs(cur_mode[3] or {}) do + local t,v=p(state) + if t==false then + dfhack.gui.showAnnouncement(v,5,1) + failed=true + end + end + if not failed then + if type(cur_mode[2])=="function" then + cur_mode[2](state) + else + MakeJob(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) + end + + if code=="SELECT" then + self:sendInputToParent("LEAVESCREEN") + end + + self:sendInputToParent("A_WAIT") + + end + return code + end + if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then + self:sendInputToParent(code) + end + end + end +end +usetool():show() \ No newline at end of file diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index ade78a2df..f75c51f74 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -50,6 +50,16 @@ function burning_red(input) -- todo does not work! bug angavrilov that so that h local col=COLOR_LIGHTRED return {text=input,pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}} end +function Disclaimer(tlb) + local dsc={"Association Of ",{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}, + "Dwarves (AOPD) is not responsible for all the damage",NEWLINE,"that this tool can (and will) cause to you and your loved dwarves",NEWLINE,"and/or saves.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} + if tlb then + for _,v in ipairs(dsc) do + table.insert(tlb,v) + end + end + return dsc +end function GmEditorUi:init(args) self.stack={} self.item_count=0 @@ -60,17 +70,7 @@ function GmEditorUi:init(args) table.insert(helptext,NEWLINE) end table.insert(helptext,NEWLINE) - table.insert(helptext,{text="DISCLAIMER:",pen=dfhack.pen.parse{fg=COLOR_RED,bg=0}}) - table.insert(helptext,NEWLINE) - table.insert(helptext,"Association Of ") - table.insert(helptext,{text="Psychic ",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}) - table.insert(helptext,"Dwarves (AOPD) is not responsible for all the damage") - table.insert(helptext,NEWLINE) - table.insert(helptext,"that this tool can (and will) cause to you and your loved dwarves") - table.insert(helptext,NEWLINE) - table.insert(helptext,"and/or saves.Please use with caution.") - table.insert(helptext,NEWLINE) - table.insert(helptext,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}) + Disclaimer(helptext) local helpPage=widgets.Panel{ subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} From 5755addc8aeb3dbf1292c986dbf67bf65a9933bc Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 10:57:40 +0200 Subject: [PATCH 229/472] Revert "Added minimal width specification to label text tokens." This reverts commit 8081ab8f51665d04a56dc08680f4ddabc4925453. --- Lua API.rst | 4 ---- library/lua/gui/widgets.lua | 5 +---- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index dd2ea3c8a..126bc7f9d 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2736,11 +2736,7 @@ containing newlines, or a table with the following possible fields: * ``token.id`` Specifies a unique identifier for the token. - -* ``token.minw`` - Specifies minimal token width - * ``token.line``, ``token.x1``, ``token.x2`` Reserved for internal use. diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index c00c7254c..ad408a2ea 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -282,10 +282,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end end end - if token.minw and x Date: Thu, 29 Nov 2012 11:13:26 +0200 Subject: [PATCH 230/472] removed used minw for labels --- scripts/advfort.lua | 2 +- scripts/gui/gm-editor.lua | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/advfort.lua b/scripts/advfort.lua index 6874da0fe..ba9b532d3 100644 --- a/scripts/advfort.lua +++ b/scripts/advfort.lua @@ -332,7 +332,7 @@ function usetool:init(args) self:addviews{ wid.Label{ frame = {xalign=0,yalign=0}, - text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getModeName,self),minw=15},{gap=1,key=keybinds.key_next.key}} + text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getModeName,self)},{gap=1,key=keybinds.key_next.key}} } } end diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index f75c51f74..3f2598948 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -79,7 +79,7 @@ function GmEditorUi:init(args) local mainPage=widgets.Panel{ subviews={ mainList, - widgets.Label{text={{text="",id="name",minw=50},{gap=1,text="Help",key="HELP",key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, + widgets.Label{text={{text="",id="name"},{gap=1,text="Help",key="HELP",key_sep = '()'}}, view_id = 'lbl_current_item',frame = {l=1,t=1,yalign=0}}, --widgets.Label{text="BLAH2"} } ,view_id='page_main'} @@ -243,7 +243,7 @@ function GmEditorUi:updateTarget(preserve_pos,reindex) self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target) local t={} for k,v in pairs(trg.keys) do - table.insert(t,{text={{text=tostring(v),minw=25},{gap=1,text=tostring(trg.target[v]),}}}) + table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=tostring(trg.target[v]),}}}) end local last_pos if preserve_pos then From 94e669058604b8a129ff431164a987b951f15f0d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 29 Nov 2012 13:37:16 +0400 Subject: [PATCH 231/472] Don't complain about fake input tokens in simulateInput. --- library/lua/gui.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index cfb058f9d..2145cfad1 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -10,13 +10,19 @@ local to_pen = dfhack.pen.parse CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} +local FAKE_INPUT_KEYS = { + _MOUSE_L = true, + _MOUSE_R = true, + _STRING = true, +} + function simulateInput(screen,...) local keys = {} local function push_key(arg) local kv = arg if type(arg) == 'string' then kv = df.interface_key[arg] - if kv == nil then + if kv == nil and not FAKE_INPUT_KEYS[arg] then error('Invalid keycode: '..arg) end end From 1226f5c99037ea3582c8eb7fc3e9eb949ad12805 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 11:56:05 +0200 Subject: [PATCH 232/472] Increased speed of adv-fort a lot, now filters keypresses to be more intuitive. --- scripts/advfort.lua | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/scripts/advfort.lua b/scripts/advfort.lua index ba9b532d3..04aa17a7f 100644 --- a/scripts/advfort.lua +++ b/scripts/advfort.lua @@ -3,19 +3,15 @@ local gui = require 'gui' local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' local buildings=require 'dfhack.buildings' ---[[********************** - tools and their uses: - 1. axe -> chop trees - 2. pickaxe -> dig, carve floors/ramps/stairs/etc (engrave too for now) ---]] + mode=mode or 0 keybinds={ key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, key_prev={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, key_continue={key="A_WAIT",desc="Continue job if available"}, ---key_down_alt1={key="A_CUSTOM_CTRL_D",desc="Use job down"},--does not work? +key_down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"},--does not work? key_down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, ---key_up_alt1={key="A_CUSTOM_CTRL_E",desc="Use job up"}, --does not work? +key_up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, --does not work? key_up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, key_use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, } @@ -337,9 +333,13 @@ function usetool:init(args) } end function usetool:onRenderBody(dc) - self._native.parent:logic() + self:renderParent() end +function usetool:onIdle() + + self._native.parent:logic() +end MOVEMENT_KEYS = { A_CARE_MOVE_N = { 0, -1, 0 }, A_CARE_MOVE_S = { 0, 1, 0 }, A_CARE_MOVE_W = { -1, 0, 0 }, A_CARE_MOVE_E = { 1, 0, 0 }, @@ -349,16 +349,16 @@ MOVEMENT_KEYS = { A_MOVE_W = { -1, 0, 0 }, A_MOVE_E = { 1, 0, 0 }, A_MOVE_NW = { -1, -1, 0 }, A_MOVE_NE = { 1, -1, 0 }, A_MOVE_SW = { -1, 1, 0 }, A_MOVE_SE = { 1, 1, 0 },--]] - --[[CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, - CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, - CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, - CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true },]]-- A_CUSTOM_CTRL_D = { 0, 0, -1 }, A_CUSTOM_CTRL_E = { 0, 0, 1 }, CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, A_MOVE_SAME_SQUARE={0,0,0}, SELECT={0,0,0}, } +ALLOWED_KEYS={ + A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, + A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true +} function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} end @@ -393,7 +393,7 @@ function usetool:onInput(keys) local cur_mode=dig_modes[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do - --print(code) + print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} @@ -429,9 +429,12 @@ function usetool:onInput(keys) return code end if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then - self:sendInputToParent(code) + if ALLOWED_KEYS[code] then + self:sendInputToParent(code) + end end end + end end usetool():show() \ No newline at end of file From f4d048526a0d3d1d1b40c2ffe339ccd3f0fdcc7e Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 12:10:25 +0200 Subject: [PATCH 233/472] Moved advfort to gui subfolder --- scripts/{ => gui}/advfort.lua | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename scripts/{ => gui}/advfort.lua (100%) diff --git a/scripts/advfort.lua b/scripts/gui/advfort.lua similarity index 100% rename from scripts/advfort.lua rename to scripts/gui/advfort.lua From 7889a2984238927ad28119ab5a8b4bc455e6e9a3 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 12:12:42 +0200 Subject: [PATCH 234/472] Some help on advfort and gm-editor --- Readme.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Readme.rst b/Readme.rst index 009d82d77..dbf04f9e8 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2194,6 +2194,30 @@ are assigned to the barracks/armory containing the selected stand as the intended user. +gui/advfort +============= + +This script allows to perform jobs in adventure mode. For more complete help +press '?' while script is running. It's most confortable to use this as a +keybinding. (e.g. keybinding set Ctrl-T gui/advfort) + +gui/gm-editor +============= + +There are three ways to open this editor: + +* using gui/gm-editor command/keybinding - opens editor on what is selected + or viewed (e.g. unit/item description screen) + +* using gui/gm-editor - executes lua command and opens editor on + it's results (e.g. gui/gm-editor "df.global.world.items.all" shows all items) + +* using gui/gm-edito dialog - shows an in game dialog to input lua command. Works + the same as version above. + +This editor allows to change and modify almost anything in df. Press '?' for an +in-game help. + ============= Behavior Mods ============= From 364dd01142873c20f073593b54a509f03aee0c24 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 12:41:27 +0200 Subject: [PATCH 235/472] Allowed moving up/down in advfort, now correctly checks for main weapon. --- scripts/gui/advfort.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 04aa17a7f..5cb0deabd 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -109,7 +109,7 @@ function MakePredicateWieldsItem(item_skill) local inv=args.unit.inventory for k,v in pairs(inv) do if v.mode==1 and df.item_weaponst:is_instance(v.item) then - if v.item.subtype.skill_melee==item_skill then --and unit.body.weapon_bp==v.body_part_id + if v.item.subtype.skill_melee==item_skill and args.unit.body.weapon_bp==v.body_part_id then return true end end @@ -357,7 +357,8 @@ MOVEMENT_KEYS = { } ALLOWED_KEYS={ A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, - A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true + A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, + A_MOVE_UP_AUX=true } function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} From 5ea26d9cae7ae43da8afdac905c7b3ab96f9991d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 29 Nov 2012 16:27:51 +0400 Subject: [PATCH 236/472] Only show the advanced new constraint dialog on Shift-Enter. Upon reflection it is a bit too scary to be always shown. --- Lua API.html | 6 ++++++ Lua API.rst | 6 ++++++ Readme.html | 4 ++-- Readme.rst | 6 ++---- images/workflow-new1.png | Bin 5662 -> 6674 bytes library/lua/gui/dialogs.lua | 22 +++++++++++++++++++++- library/lua/gui/widgets.lua | 19 +++++++++++++++++++ scripts/gui/workflow.lua | 20 ++++++++++++-------- 8 files changed, 68 insertions(+), 15 deletions(-) diff --git a/Lua API.html b/Lua API.html index 04af5d672..f42905d01 100644 --- a/Lua API.html +++ b/Lua API.html @@ -2853,6 +2853,9 @@ this may be extended with mouse click support.

                                                                                + + - + @@ -2928,6 +2938,8 @@ supports:

                                                                                + + diff --git a/Lua API.rst b/Lua API.rst index 714a41bfb..cedc36441 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2785,6 +2785,8 @@ It has the following attributes: :inactive_pen: If specified, used for the cursor when the widget is not active. :icon_pen: Default pen for icons. :on_select: Selection change callback; called as ``on_select(index,choice)``. + This is also called with *nil* arguments if ``setChoices`` is called + with an empty list. :on_submit: Enter key callback; if specified, the list reacts to the key and calls it as ``on_submit(index,choice)``. :on_submit2: Shift-Enter key callback; if specified, the list reacts to the key diff --git a/Readme.html b/Readme.html index deea72bef..d75be99f4 100644 --- a/Readme.html +++ b/Readme.html @@ -3034,11 +3034,11 @@ current job, and their current status.

                                                                                current count is below the lower bound of the range, the job is resumed; if it is above or equal to the top bound, it will be suspended. Within the range, the specific constraint has no effect on the job; others may still affect it.

                                                                                -

                                                                                Pressing 'c' switches the current constraint between counting stacks or items. -Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the -bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting -items and expanding the range each gives a 5x bonus).

                                                                                -

                                                                                Pressing 'n' produces a list of possible outputs of this job as guessed by +

                                                                                Pressing 'I' switches the current constraint between counting stacks or items. +Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting +items and expanding the range each gives a 2x bonus).

                                                                                +

                                                                                Pressing 'A' produces a list of possible outputs of this job as guessed by workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using job item-material or gui/workshop-job, @@ -3050,6 +3050,23 @@ added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range.

                                                                                images/workflow-new2.png +

                                                                                Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen) +opens the overall status screen, which was copied from the C++ implementation +by falconne for better integration with the rest of the lua script:

                                                                                +images/workflow-status.png +

                                                                                This screen shows all currently existing workflow constraints, and allows +monitoring and/or changing them from one screen. The constraint list can +be filtered by typing text in the field below.

                                                                                +

                                                                                The color of the stock level number indicates how "healthy" the stock level +is, based on current count and trend. Bright green is very good, green is good, +red is bad, bright red is very bad.

                                                                                +

                                                                                The limit number is also color-coded. Red means that there are currently no +workshops producing that item (i.e. no jobs). If it's yellow, that means the +production has been delayed, possibly due to lack of input materials.

                                                                                +

                                                                                The chart on the right is a plot of the last 14 days (28 half day plots) worth +of stock history for the selected item, with the rightmost point representing +the current stock value. The bright green dashed line is the target +limit (maximum) and the dark green line is that minus the gap (minimum).

                                                                                gui/assign-rack

                                                                                diff --git a/Readme.rst b/Readme.rst index a214a6ecb..a84691b05 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2295,12 +2295,12 @@ current count is below the lower bound of the range, the job is resumed; if it is above or equal to the top bound, it will be suspended. Within the range, the specific constraint has no effect on the job; others may still affect it. -Pressing 'c' switches the current constraint between counting stacks or items. -Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the -bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting -items and expanding the range each gives a 5x bonus). +Pressing 'I' switches the current constraint between counting stacks or items. +Pressing 'R' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the +bounds by 5, 10, or 20 depending on the direction and the 'I' setting (counting +items and expanding the range each gives a 2x bonus). -Pressing 'n' produces a list of possible outputs of this job as guessed by +Pressing 'A' produces a list of possible outputs of this job as guessed by workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using ``job item-material`` or ``gui/workshop-job``, @@ -2316,6 +2316,29 @@ suit your need, and set the item count range. .. image:: images/workflow-new2.png +Pressing 'S' (or, with the example config, Alt-W in the 'z' stocks screen) +opens the overall status screen, which was copied from the C++ implementation +by falconne for better integration with the rest of the lua script: + +.. image:: images/workflow-status.png + +This screen shows all currently existing workflow constraints, and allows +monitoring and/or changing them from one screen. The constraint list can +be filtered by typing text in the field below. + +The color of the stock level number indicates how "healthy" the stock level +is, based on current count and trend. Bright green is very good, green is good, +red is bad, bright red is very bad. + +The limit number is also color-coded. Red means that there are currently no +workshops producing that item (i.e. no jobs). If it's yellow, that means the +production has been delayed, possibly due to lack of input materials. + +The chart on the right is a plot of the last 14 days (28 half day plots) worth +of stock history for the selected item, with the rightmost point representing +the current stock value. The bright green dashed line is the target +limit (maximum) and the dark green line is that minus the gap (minimum). + gui/assign-rack =============== diff --git a/dfhack.init-example b/dfhack.init-example index 8fafa4cf4..7617b9f6e 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -91,6 +91,7 @@ keybinding add Alt-A@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workshop-job # workflow front-end keybinding add Alt-W@dwarfmode/QueryBuilding/Some/Workshop/Job gui/workflow +keybinding add Alt-W@overallstatus "gui/workflow status" # assign weapon racks to squads so that they can be used keybinding add P@dwarfmode/QueryBuilding/Some/Weaponrack gui/assign-rack diff --git a/images/workflow-new1.png b/images/workflow-new1.png index 50d0e1f421ac9de1abf87545c8d7ade31bd4cd4a..498fc4e80b4eb9736a00429195a9eee6dbd17d14 100644 GIT binary patch literal 6775 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 H@rwOF@G~;- literal 6862 zcmXY0c{tSH_n!@K``fvUwv17-6b8vMd}Pa76g8uXu{KSXtQ8?yVvwcC zzP2FIA~8`BLWxj*zR&af>)d;ubMN!q=bZC;opWxIovjr=k2DVgf#A0$SU4aMXb6Eo z8e-6Unp{Z|c2B6=9Y1BcCn*$4dwcut?k#;e9h>oPpyz_)~abN^@^ z#hes*Nzy#4JI<#j##lN#ZFRlA$ylj+Tk#;YAJre6d*yDdM1jheQcEIqEhmj(oUU`4 z4Dg7~B|N3)21_uG;NIy(zo-Efpqck}Ix1j)^PIEqtndui!s)kDUie19u{jFKYMIR792?9mh}swRrbL6fa41oepe&^5~4dI^A2 zv14XQB$nfT3Q9RD^7-|rAl)caK+Q^c`6MJOe9;s+(lFsX_ZaxsR% z#as6o_8cfWIoE84D&Scx^3j?-4cC9r`Z^6e(=PMURb~a0)Lk0)k-6Q_wLuJNdd`SE zW)v54gjW54ht3n3FQ%ndhL~yFUUavtd5*4SBZb9JpZu`ndu9#=7>%DLt()9`8ehMR zy$hHBz_#QOGENuInQwrok+VX49N288yY|K2d&^heT+MhQuui%Te97Hl{5-#KCvVFQ z`3Lc*FI}nx=IewbS2!j;8f(*Bss?6!`|-~ob`iuZ{+kKhsS z;(TXx`9;7T#z8Xnb6dmIUp?caP{Ukg)S^S;anAc492tSaXK(2w{llt4AG}%rnfAVW zN39VLgTgH2<-AN>Q6}u~~0dYEE#0&JJzsmRiLenTZ{MuNYii%U|(1 z1|NxiANzKg1DSK92SI)<&|k)bIfG*Bz82{Z90E)|hjEbAkTQ4#Xw<%X)5)&dlK!g$3PKBOC*x#~oV&a=3;9TcY8DHE0iYs#3Z6T|}(YRe> zCWb&}X6mlLRn|aKttO_!$kBhq8eBI}OiFCrHd1ZPpk&GfN&hK!h!JY|!oXJQ1*_`& zMp}+6u+4b!;Y-yCx@tp(*q;+NR#?i_X+J)pfCK&FY@Ipi zTRXuUfAY4)auK6i_PRlaoql#uF9;up4^}A$I&-`UX^eOlE(Guz2X(wWsw4H|&HRcg zyt&~T;5%HDCrywUc!;uyn+m@0DgV)?((LWi!Tdi}+}T2Bkw`z+2AR>>PrN<}i3fMr z%C{Jt1dW0B`tT)dac=}hmG?!-9|<}tnVX{euk~pk;AJ1sX)W;0+|_#gNtrye2QUoQ9t|bgvj6x$qHJ@k?t!SRaz1>-xDv2yC*0qs@5s(LCe(X=JQm zN6jG@mdl@V*D)TO!*7H0TD-&(#|TVcL5a616M`zsV}~9g6vIs#W=f9W2*o8Xx4|XL~yb+JW>3R@Rp2zv^5s9Zc-vJgwTFj$kjE`(GJgyK|PA$W~*Z zS4!x$p-0blc%X*Gd<5xSt5c{g6pYgtdW=zAa)mSaXOAdCgyj}XLPfWZQJ0e5P2_+Q z!r?bo)^~ol?cbx`_VMQ9a=YTpIWBLV+Pj5Ue9?x+&eg(?5?k~ifBf;&1RQIL`qcFI z%{GGx_*y+{YYUKc&A{6a@nqmpR1v+)0(7C8XoHMhEw|5o#{>ioaCb&6L2TZMcjb;3 zD9CL820C~}hO=7|d7t~%SF~u585Cm+h5f<~Dv|zLk#x;$w;3v0?Wim2kf)Fw!?w6G zGl7>;`8-ICWDEJ9;Udg?_nT+%DEJByj0SYS(B~r0m;kbG7VZTdA1OC~pq`x_a_11d z{4>yse&z4?k)B=tAsuu8@iag;QTrAF4;u9 zCxxTCwn=c{DThTttlyH8ucmX51Q_FZ<-9u?RyC?vCrrcT<&UhiUZ6$y}iT{G)LJ#(f_d$NPNa)9ygM%t;JwRIhI$)M|nij-S2?5uKt-kNkd$FD|VEa2m3N z+-&_)a&Wx==&>r_5?ArpD?^4xA7jgyUz*P#(*eMDpKO0BPNT7u+=HPFoG}1njUu3! z1MuwPMgFgz9r*q%9n|aUuGH?~Hk$M?;a(wVV!Q1gQ_dk#zHBiNq;&g7ULOgC!hdd2JKEs2>udNrx6 z>F?j|izS0cOTHUvr@&6t)o4wW>Hkh+7EZ*8WO(c(IV_T{{-zt zsF~5>?mj}ZLam-}u+@VkgoXY}FcQ$FlxPZtBnasWO!}SADS3gH=bg#(K?##0l<4@TLbvw);@1tpdXY)Xg8e9@^!dL72hxhb{@ns0)e zNAT5)Q8`r1m1@`DorPU!&#_k{JOn;&Og0kU^Ct$yM>(JhmRzH3;%q`1rfA&I%ndAf zjE*MA$?{3UhsVxhEC3Uq5+%m7e;o0YKU0UyI?a^rSb#;ys#c?G8JT(=Dmj=&_!2STANtxA>1>s%JqS!pt2rYQ>sHA0uIkhR?ac!_r{? zLor6Cz%6`p&-VEHz1P0Km&8diK-qH{KY&b&Vcx1Oq^nm0zG+(rBHI_$Qu%Hs(xmmG zZjkwX6Lb6)UZYouPFKzOCL!>`*KDUNYbGPoO<9=%q!nS{P7AgDVkX{4>&v3wtQzU& z%OUTNgB%ujPM+Q*?HgdDWVpMhsX5^lmBxbsk1%YT69%;vVqonN@>a|^MtJEdK7rDT z=D)+7Z60fDAe*$&^%Ew|hr*MyeUJk^PbFa9`Vvl zAzXzo-Hx>b+iro&_w@A{t-?Q9p@P{Hm`*5k4jY~$qis02fTS@kp<9G36K&pXdxR-S zfMORg@nuNn>8Jhr31tN>UL}F|Z3`S((8>Jl%;JU{Ws!ua7^zL!bS+5MRt{bQ1J%N!GpGW(jKeAeqvfz~BxBW4l4eRG>SZQ`M;^R4Ixqlr4~ zin{4rDRM}bfw72Cx;qr*kv2pkimye4Y^OU_!cFJKai6PABQo)|XPp+|aZT!TdzLc8 z_$&`2hM1Mgc#x&!ug%Czuwm+(fWP0`Lb1MW%&2GQ4@cF|D;*~^vBVI2f>Um`Wu6mj z^a@G$q-gK?r84gR(4nHgaP)wyxVDBig`AKk$?N)D;PYAWs--N}3z|E_^<{dEtv*KD_}kP&9CU-4{yDTzW&HC%M#Rc}yzEe9Xb`iK?OD;{UIw5V>7_A7fWJ#WVWKWyq_*-+LYQ^d@~L6s zwr`KYV6H6T{dSzA6kHm?m)Q78<7a`7|H-hP5v(z9wulgEhzF^t7bVh?@IrFa@_*&Y ztjEI;$T$V~bb-U$pur^89(zd0RTfmf=v&MS#(t0|-0SvozzF*#q~VZWCPW*^xP+gp z-b*9!;b{(w*e9dkb)cNum+LQy{{@$G_+5B+J>X4j$A+um<@uGXk)M$c(!PCiSc6V5 zuT{gvUKF7yYz~RSE2cy=mncD z1K8}#B2z8j_97*H|4_(&-#M5Z1qCO?LxpUzTuJa+s|?VE`NWA3^0#}#E+u(({Ny&= zDN0@SH9!bRl_n|i-E&*D7C&)zkFG1SO02j;i{GcEo_ORPOJj=J9nPuD^3}Mxe`=#M z%@xcc2Wvgr!0^63aPCxY_8hVkZ65mP_4%WJdvl&GQAOE!_r8*Mm;NieO)Wdu<8q-*xgp@z=Qs_Y13n1* zm@%fkt=x~Rj_!LgUgnd^2 zvpFtQb8$`rqH&N_ar$`IYKEpk`#fg0u%Xib)&50h`FD-~7CdX;#*pgp1(FdJKUu)+ z+7(jG6zrgB^Rg=ZHx|=*e*Q4pdfPWf#jvv8!{~5S+&R)OKQ8Fp<$ud2NKEr z!{z&+417tCy4fM{w5WH6{xz>hZ@-%O4HU%?_ibHD@^bZ2r8bFl6~e+G{pxTI69XS| zq3na?8(hzA>Qn6szkLx@F8s?f4k+nBokGkm#nSPLW^4p|@?wIXq|5Bq*5{?~f-2NV z?*4XWiJTL9M*UjjUf)0vZ%k?s&`Pyt{kjgEL(NvzKRTb+s9?jut0X*B^Y6itLZDPe zVuHd(EajO3-@;XR_v_mRvYpL?as3lEoTTeAS=!vmv*#u!*<@eGYz z>-fo;-O$%+tUj=&gJIZ)Lk)}- zKAgUmy)ezipoB(E;~)}(@n4UMmLSH#5#6sAZwKWF!SVu#H3URs@j;nfj7#Nd zkNGz)p=Wa8W?K-B7F6sjIJnX+hD}tu#zLCbxfL2KFT{`#r~?NsOWP?xYrnRhx0Nki zc3(36Zv#t|BO0M%O35>VMPH-@hu4ty_AiGXKII}j17`ewtm|jL6yB>GQSq<*r`rKT z00IOM9VJK2ybH4RS>&)2kn99^pzsca?j6*xjp64qn)KF){;jXB7^2Y07ec0v!D!P z@Cg;#|MZEYLNQc<_ z)S(CqHX5aPwd}ZwI`pHAgGt#8c7?e6i6^<0^~?*6I=U`(-=yc{RFSL3MNs<(350_T zyd=H$_u;t_qmU;zB>Dqhj3%j|TBcyxGKtaSjjA&@q4HCU|kVTngy9Q*{zzgrym3$SuiYCS-C<46kwH*_3__AR0P#{ZYG9$WSJ4&m|1Z65albjQqA#B?{VWKFO*pi5mu==Q`zQN6lc zY(^r5*Sv4);9=Cu&ZRT%DGKxfWsurM3`ibLQUGsT4+bJ{RavQ3TSMDq4ftCV>hb((r0B(o=fSdiS!c8#X-A!xYwW={72+Qp9+9p8BX-)y4`{0BhF04aXcUdCp`}WiRH)y|_0}`W^KKLO zaRZ&*{N35#h0daMb8%33-Nxgtsq`NGQo09p1lJkkIh4xqgg#XF=*v5VY-QR`nUwEC zS;L3LCjjN|{P!(b@BrIsym>AbZjqQ6_6w_u(6m{~xTM~J2Y^6QvoMF|S%uNorcyrn zUn!u!_==_|z~k!Q9^-T5!*6``Iw#Agtc?H#b^M`5YE*|EY*XmRnJci2A$rPP9;HB1 zL2}^Y*k!c+(D$+CvrdJ@m(PDJ*YM%guD+gaoAo}ioAE#3uDortue>Qb3W9}rl;5!2 zRlXo4U!w_bIAV#&r$H+t?jThT^y6P0-;#ow0KrC#^*{3FM! zhyi_uMFVkYN^~HphGT+-P&PJmiQEU;`ok%KIId*Sf1JH%B#Avk4Sm`8_KU}U15sCV zq!$G*0(h55fXxm!GC!75oezh9^*4b;Nq*&pW2`51)R0t*H=`ysyYk}-PcMtpF5{34o zK`i7ZxAxgKxScrMGtJp)b_NjLsAY_G%qVR$#90*@L{LbH$TKR@Bf%GD40 zZ^J)a)?JlCe|5aK?x95o{pcJg%j&l&w6Psy0l5512R9?&%4@u&U#qAYQ#w&RwLN|a z`2jib-)JHPA6qnE)Z?AmVfuEP(w#c!6eRhI5Nov1b(~U+CnZM^#JN4|gRUM+V{$01 z-jxG4NeE=>>KeG5?JE&apt$zETD%6HD;9@+Okd!{iD@H9DO)7DD z?TTUskn#o<_hz4}Vj>L8?B|btPaaT}q4$BW_u_I#RD`iVtl|Mh8N&VW92hM|Tf;7g zN1XXr_AJkw+M{nvs0N_iKW;+YgUZB{d4e)cs(Z$F)TKkc5KiCc_6AmG4Dn6wRaIQC zCs?3dXd^d`i|-tcqH5w>YCEunx%dQbFX|LRGF~!E;*yP&j8LRQaJ^HgemQ>-8Nvx; zN$5cKj-o|2te&`Xeptt|bisUYHpgpmjPRxt&C}7(EV)nS&-=F5#fVa5Kh`#W4fH{U z5ype1Gm_wMaI$!pYT%zx^|lj#3G7eoKykCg?u*?&=e)$VnZy$GIjV!- Q`h($ diff --git a/images/workflow-status.png b/images/workflow-status.png new file mode 100644 index 0000000000000000000000000000000000000000..6c3d989f395b6ab73fd6fe04be55bb52673b688d GIT binary patch literal 5118 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 CU+(Jw literal 0 HcmV?d00001 diff --git a/images/workflow.png b/images/workflow.png index a0a0d4216b51bcecc99c82e4ab6e527225410501..7506c730f520d74f42f35b01034e4b083db83940 100644 GIT binary patch literal 5779 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 DZgqQZ diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ebbffbbbd..cb9e1c9be 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -418,7 +418,13 @@ List.ATTRS{ function List:init(info) self.page_top = 1 self.page_size = 1 - self:setChoices(info.choices, info.selected) + + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + self.selected = 1 + end end function List:setChoices(choices, selected) @@ -481,6 +487,9 @@ function List:moveCursor(delta, force_cb) if cnt < 1 then self.page_top = 1 self.selected = 1 + if force_cb and self.on_select then + self.on_select(nil,nil) + end return end @@ -657,13 +666,17 @@ function FilteredList:init(info) end end self.not_found = Label{ - visible = false, + visible = true, text = info.not_found_label or 'No matches', text_pen = COLOR_LIGHTRED, frame = { l = info.icon_width, t = self.list.frame.t }, } self:addviews{ self.edit, self.list, self.not_found } - self:setChoices(info.choices, info.selected) + if info.choices then + self:setChoices(info.choices, info.selected) + else + self.choices = {} + end end function FilteredList:getChoices() diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 9a45f6554..77a87c9ce 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -130,12 +130,20 @@ RangeEditor = defclass(RangeEditor, widgets.Label) RangeEditor.ATTRS { get_cb = DEFAULT_NIL, - save_cb = DEFAULT_NIL + save_cb = DEFAULT_NIL, + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + min_inc = 'BUILDING_TRIGGER_MIN_SIZE_UP', + max_dec = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + max_inc = 'BUILDING_TRIGGER_MAX_SIZE_UP', + } } function RangeEditor:init(args) self:setText{ - { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + { key = self.keys.count, text = function() local cons = self.get_cb() or null_cons if cons.goal_by_count then @@ -145,21 +153,21 @@ function RangeEditor:init(args) end end, on_activate = self:callback('onChangeUnit') }, - { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + { key = self.keys.modify, text = ': Range', on_activate = self:callback('onEditRange') }, NEWLINE, ' ', - { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - on_activate = self:callback('onIncRange', 'goal_gap', 5) }, - { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + { key = self.keys.min_dec, + on_activate = self:callback('onIncRange', 'goal_gap', 2) }, + { key = self.keys.min_inc, on_activate = self:callback('onIncRange', 'goal_gap', -1) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) end }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + { key = self.keys.max_dec, on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', - on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { key = self.keys.max_inc, + on_activate = self:callback('onIncRange', 'goal_value', 2) }, { text = function() local cons = self.get_cb() or null_cons return string.format(': Max %-4d', cons.goal_value) @@ -200,9 +208,9 @@ end function RangeEditor:onIncRange(field, delta) local cons = self.get_cb() if not cons.goal_by_count then - delta = delta * 5 + delta = delta * 2 end - cons[field] = math.max(1, cons[field] + delta) + cons[field] = math.max(1, cons[field] + delta*5) self.save_cb(cons) end @@ -295,7 +303,7 @@ function NewConstraint:init(args) } }, widgets.Label{ - frame = { l = 0, t = 13 }, + frame = { l = 0, t = 14 }, text = { 'Desired range: ', { pen = COLOR_LIGHTCYAN, @@ -311,7 +319,7 @@ function NewConstraint:init(args) } }, RangeEditor{ - frame = { l = 1, t = 15 }, + frame = { l = 1, t = 16 }, get_cb = self:cb_getfield('constraint'), save_cb = self:callback('onRangeChange'), }, @@ -353,7 +361,7 @@ function NewConstraint:postinit() end function NewConstraint:isValid() - return self.constraint.item_type >= 0 + return self.constraint.item_type >= 0 or self.constraint.is_craft end function NewConstraint:onChange() @@ -455,6 +463,59 @@ function NewConstraint:onRangeChange() cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) end +------------------------------ +-- CONSTRAINT HISTORY GRAPH -- +------------------------------ + +HistoryGraph = defclass(HistoryGraph, widgets.Widget) + +HistoryGraph.ATTRS { + frame_inset = 1, + history_pen = COLOR_CYAN, +} + +function HistoryGraph:init(info) +end + +function HistoryGraph:setData(history, bars) + self.history = history or {} + self.bars = bars or {} + + local maxval = 1 + for i,v in ipairs(self.history) do + maxval = math.max(maxval, v) + end + for i,v in ipairs(self.bars) do + maxval = math.max(maxval, v.value) + end + self.max_value = maxval +end + +function HistoryGraph:onRenderFrame(dc,rect) + dc:fill(rect.x1,rect.y1,rect.x1,rect.y2,{ch='\xb3', fg=COLOR_BROWN}) + dc:fill(rect.x1,rect.y2,rect.x2,rect.y2,{ch='\xc4', fg=COLOR_BROWN}) + dc:seek(rect.x1,rect.y1):char('\x1e', COLOR_BROWN) + dc:seek(rect.x1,rect.y2):char('\xc5', COLOR_BROWN) + dc:seek(rect.x2,rect.y2):char('\x10', COLOR_BROWN) + dc:seek(rect.x1,rect.y2-1):char('0', COLOR_BROWN) +end + +function HistoryGraph:onRenderBody(dc) + local coeff = (dc.height-1)/self.max_value + + for i,v in ipairs(self.bars) do + local y = dc.height-1-math.floor(0.5 + coeff*v.value) + dc:fill(0,y,dc.width-1,y,v.pen or {ch='-', fg=COLOR_GREEN}) + end + + local xbase = dc.width-1-#self.history + for i,v in ipairs(self.history) do + local x = xbase + i + local y = dc.height-1-math.floor(0.5 + coeff*v) + dc:seek(x,y):char('*', self.history_pen) + end +end + ------------------------------ -- GLOBAL CONSTRAINT SCREEN -- ------------------------------ @@ -478,47 +539,7 @@ function ConstraintList:init(args) self:addviews{ widgets.Panel{ - frame = { w = 31, r = 0, h = 6, t = 0 }, - frame_inset = 1, - subviews = { - widgets.Label{ - frame = { l = 0, t = 0 }, - enabled = self:callback('isAnySelected'), - text = { - { text = function() - local cur = self:getCurConstraint() - if cur then - return string.format( - 'Currently %d (%d in use)', - current_stock(cur), - if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) - ) - else - return 'No constraint selected' - end - end } - } - }, - RangeEditor{ - frame = { l = 0, t = 2 }, - enabled = self:callback('isAnySelected'), - get_cb = self:callback('getCurConstraint'), - save_cb = self:callback('saveConstraint'), - }, - } - }, - widgets.Widget{ - active = false, - frame = { w = 1, r = 31 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Widget{ - active = false, - frame = { w = 31, r = 0, h = 1, t = 6 }, - frame_background = gui.BOUNDARY_FRAME.frame_pen, - }, - widgets.Panel{ - frame = { l = 0, r = 32 }, + frame = { l = 0, r = 31 }, frame_inset = 1, on_layout = function(body) self.fwidth = body.width - (12+1+1+7+1+1+1+7) @@ -541,6 +562,7 @@ function ConstraintList:init(args) edit_pen = COLOR_LIGHTCYAN, text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + on_select = self:callback('onSelectConstraint'), }, widgets.Label{ frame = { b = 0, h = 1 }, @@ -557,27 +579,66 @@ function ConstraintList:init(args) else return COLOR_WHITE end - end }, ', ', - { key = 'CUSTOM_SHIFT_S', text = ': Search', - on_activate = function() - self.subviews.list.edit.active = not self.subviews.list.edit.active - end, - pen = function() - if self.subviews.list.edit.active then - return COLOR_LIGHTCYAN + end }, + } + } + } + }, + widgets.Panel{ + frame = { w = 30, r = 0, h = 6, t = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + enabled = self:callback('isAnySelected'), + text = { + { text = function() + local cur = self:getCurConstraint() + if cur then + return string.format( + 'Currently %d (%d in use)', + current_stock(cur), + if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) + ) else - return COLOR_WHITE + return 'No constraint selected' end end } } - } + }, + RangeEditor{ + frame = { l = 0, t = 2 }, + enabled = self:callback('isAnySelected'), + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), + keys = { + count = 'CUSTOM_SHIFT_I', + modify = 'CUSTOM_SHIFT_R', + min_dec = 'SECONDSCROLL_PAGEUP', + min_inc = 'SECONDSCROLL_PAGEDOWN', + max_dec = 'SECONDSCROLL_UP', + max_inc = 'SECONDSCROLL_DOWN', + } + }, } }, + widgets.Widget{ + active = false, + frame = { w = 1, r = 30 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Widget{ + active = false, + frame = { w = 30, r = 0, h = 1, t = 6 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + HistoryGraph{ + view_id = 'graph', + frame = { w = 30, r = 0, t = 7, b = 0 }, + } } - self.subviews.list.edit.active = false - - self:initListChoices() + self:initListChoices(nil, args.select_token) end function stock_trend_color(cons) @@ -733,6 +794,29 @@ function ConstraintList:onDeleteConstraint() ) end +function ConstraintList:onSelectConstraint(idx,item) + local history, bars + + if item then + local cons = item.obj + local vfield = if_by_count(cons, 'cur_count', 'cur_amount') + + bars = { + { value = cons.goal_value - cons.goal_gap, pen = {ch='-', fg=COLOR_GREEN} }, + { value = cons.goal_value, pen = {ch='-', fg=COLOR_LIGHTGREEN} }, + } + + history = {} + for i,v in ipairs(cons.history or {}) do + table.insert(history, v[vfield]) + end + + table.insert(history, cons[vfield]) + end + + self.subviews.graph:setData(history, bars) +end + ------------------------------- -- WORKSHOP JOB INFO OVERLAY -- ------------------------------- @@ -772,14 +856,20 @@ function JobConstraints:init(args) widgets.Label{ frame = { l = 0, b = 0 }, text = { - { key = 'CUSTOM_N', text = ': New limit, ', + { key = 'CUSTOM_SHIFT_A', text = ': Add limit, ', on_activate = self:callback('onNewConstraint') }, - { key = 'CUSTOM_X', text = ': Delete', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', enabled = self:callback('isAnySelected'), on_activate = self:callback('onDeleteConstraint') }, NEWLINE, NEWLINE, { key = 'LEAVESCREEN', text = ': Back', - on_activate = self:callback('dismiss') } + on_activate = self:callback('dismiss') }, + ' ', + { key = 'CUSTOM_SHIFT_S', text = ': Status', + on_activate = function() + local sel = self:getCurConstraint() + ConstraintList{ select_token = (sel or {}).token }:show() + end } } }, } @@ -873,22 +963,16 @@ function JobConstraints:onNewConstraint() local choices = {} for i,cons in ipairs(variants) do local itemstr = describe_item_type(cons) - local matstr = describe_material(cons) - local matflags = utils.list_bitfield_flags(cons.mat_mask) - if #matflags > 0 then - local fstr = table.concat(matflags, '/') - if matstr == 'any material' then - matstr = 'any '..fstr - else - matstr = 'any '..fstr..' '..matstr - end + local matstr,matflags = describe_material(cons) + if matflags then + matstr = matflags..' '..matstr end table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) end dlg.ListBox{ - frame_title = 'New limit', + frame_title = 'Add limit', text = 'Select one of the possible outputs:', text_pen = COLOR_WHITE, choices = choices, @@ -930,7 +1014,7 @@ end local args = {...} -if args[1] == 'list' then +if args[1] == 'status' then check_enabled(function() ConstraintList{}:show() end) else if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then From df2e9f00e15cfe1f2b1d548f9cba1f8b11e4fac7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Dec 2012 17:21:06 +0400 Subject: [PATCH 262/472] Document that search now works in the stockpile settings screen. --- NEWS | 20 ++++++++++---------- Readme.html | 9 ++++++++- Readme.rst | 12 +++++++++++- images/search-stockpile.png | Bin 0 -> 5971 bytes plugins/search.cpp | 2 +- 5 files changed, 30 insertions(+), 13 deletions(-) create mode 100644 images/search-stockpile.png diff --git a/NEWS b/NEWS index 65c647337..4856b06c3 100644 --- a/NEWS +++ b/NEWS @@ -25,18 +25,18 @@ DFHack future New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. - - gui/workflow: a front-end for the workflow plugin. + - gui/workflow: a front-end for the workflow plugin (part inspired by falconne). - gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/gm-editor: an universal editor for lots of dfhack things. - gui/companion-order: a adventure mode command interface for your companions. - New binary patches: - - armorstand-capacity - - custom-reagent-size - - deconstruct-heapfall - - deconstruct-teleport - - hospital-overstocking - - training-ammo - - weaponrack-unassign + New binary patches (for use with binpatch): + - armorstand-capacity: doubles the capacity of armor stands. + - custom-reagent-size: lets custom reactions use small amounts of inputs. + - deconstruct-heapfall: stops some items still falling on head when deconstructing. + - deconstruct-teleport: stops items from 16x16 block teleporting when deconstructing. + - hospital-overstocking: stops hospital overstocking with supplies. + - training-ammo: lets dwarves with quiver full of combat-only ammo train. + - weaponrack-unassign: fixes bug that negates work done by gui/assign-rack. Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. @@ -48,7 +48,7 @@ DFHack future this plugin makes weapon racks, armor stands, chests and cabinets in properly designated barracks be used again for storage of squad equipment. New Search plugin by falconne: - Adds an incremental search function to the Stocks, Trading and Unit List screens. + Adds an incremental search function to the Stocks, Trading, Stockpile and Unit List screens. New AutoMaterial plugin by falconne: Makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one. diff --git a/Readme.html b/Readme.html index d75be99f4..54deb013f 100644 --- a/Readme.html +++ b/Readme.html @@ -2873,7 +2873,7 @@ directly to the main dwarf mode screen.

                                                                                AutoMaterial

                                                                                diff --git a/Readme.rst b/Readme.rst index a84691b05..afad3ef4c 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2074,7 +2074,7 @@ directly to the main dwarf mode screen. Search ====== -The search plugin adds search to the Stocks, Trading and Unit List screens. +The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens. .. image:: images/search.png @@ -2097,6 +2097,16 @@ are actually visible in the list; the same effect applies to the Trade Value numbers displayed by the screen. Because of this, pressing the 't' key while search is active clears the search instead of executing the trade. +In the stockpile screen the option only appears if the cursor is in the +rightmost list: + +.. image:: images/search-stockpile.png + +Note that the 'Permit XXX'/'Forbid XXX' keys conveniently operate only +on items actually shown in the rightmost list, so it is possible to select +only fat or tallow by forbidding fats, then searching for fat/tallow, and +using Permit Fats again while the list is filtered. + AutoMaterial ============ diff --git a/images/search-stockpile.png b/images/search-stockpile.png new file mode 100644 index 0000000000000000000000000000000000000000..37a0e57cd3a74a4f4f7b80d76dcd996cc92da1fa GIT binary patch literal 5971 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 Date: Sat, 1 Dec 2012 18:20:27 +0200 Subject: [PATCH 263/472] Work started on adventurer workshops. --- scripts/gui/advfort.lua | 436 ++++++++++++++++++++++++++++++---------- 1 file changed, 328 insertions(+), 108 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index e62088dd9..8f4fb3f61 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -1,4 +1,16 @@ -- allows to do jobs in adv. mode. +keybinds={ +nextJob={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, +prevJob={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, +continue={key="A_WAIT",desc="Continue job if available"}, +down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, +down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, +up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, +up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, +use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, +workshop={key="CHANGETAB",desc="Show workshop jobs"}, +} + local gui = require 'gui' local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' @@ -6,6 +18,7 @@ local buildings=require 'dfhack.buildings' local bdialog=require 'gui.buildings' local tile_attrs = df.tiletype.attrs + settings={build_by_items=false,check_inv=false,df_assign=true} for k,v in ipairs({...}) do if v=="-c" or v=="--cheat" then @@ -18,17 +31,10 @@ for k,v in ipairs({...}) do settings.df_assign=false end end + mode=mode or 0 -keybinds={ -key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, -key_prev={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, -key_continue={key="A_WAIT",desc="Continue job if available"}, -key_down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, -key_down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -key_up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, -key_up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, -key_use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, -} + + function Disclaimer(tlb) local dsc={"The Gathering Against ",{text="Goblin ",pen=dfhack.pen.parse{fg=COLOR_GREEN,bg=0}}, "Oppresion ", "(TGAGO) is not responsible for all ",NEWLINE,"the damage that this tool can (and will) cause to you and your loved worlds",NEWLINE,"and/or sanity.Please use with caution.",NEWLINE,{text="Magma not included.",pen=dfhack.pen.parse{fg=COLOR_LIGHTRED,bg=0}}} @@ -57,6 +63,7 @@ function showHelp() Disclaimer(helptext) require("gui.dialogs").showMessage("Help!?!",helptext) end + function getLastJobLink() local st=df.global.world.job_list while st.next~=nil do @@ -64,24 +71,24 @@ function getLastJobLink() end return st end -function AddNewJob(job) - local nn=getLastJobLink() - local nl=df.job_list_link:new() - nl.prev=nn - nn.next=nl - nl.item=job - job.list_link=nl +function addNewJob(job) + local lastLink=getLastJobLink() + local newLink=df.job_list_link:new() + newLink.prev=lastLink + lastLink.next=newLink + newLink.item=job + job.list_link=newLink end function MakeJob(args) - local nj=df.job:new() - nj.id=df.global.job_next_id + local newJob=df.job:new() + newJob.id=df.global.job_next_id df.global.job_next_id=df.global.job_next_id+1 - --nj.flags.special=true - nj.job_type=args.job_type - nj.completion_timer=-1 + --newJob.flags.special=true + newJob.job_type=args.job_type + newJob.completion_timer=-1 - nj.pos:assign(args.pos) - args.job=nj + newJob.pos:assign(args.pos) + args.job=newJob local failed=false for k,v in ipairs(args.pre_actions or {}) do local ok,msg=v(args) @@ -91,13 +98,13 @@ function MakeJob(args) end end if not failed then - AssignUnitToJob(nj,args.unit,args.from_pos) + AssignUnitToJob(newJob,args.unit,args.from_pos) end - for k,v in ipairs(args.post_actions or {}) do + for k,v in ipairs(args.post_actionss or {}) do v(args) end - AddNewJob(nj) - return nj + addNewJob(newJob) + return newJob end function AssignUnitToJob(job,unit,unit_pos) @@ -219,7 +226,7 @@ function IsTree(args) end function IsPlant(args) local tt=dfhack.maps.getTileType(args.pos) - if tile_attrs[tt].shape==df.tiletype_shape.PLANT then + if tile_attrs[tt].shape==df.tiletype_shape.SHRUB then return true else return false, "Can only do it on plants" @@ -282,10 +289,58 @@ function RemoveBuilding(args) return false,"No building to remove" end end -function AssignJobItems(args) - if settings.df_assign then +function isSuitableItem(job_item,item) + + if job_item.item_type~=-1 then + if item:getType()~= job_item.item_type then + return false, "type" + elseif job_item.item_subtype~=-1 then + if item:getSubtype()~=job_item.item_subtype then + return false,"subtype" + end + end + end + + if job_item.mat_type~=-1 then + if item:getActualMaterial()~= job_item.mat_type then --unless we would want to make hist-fig specific reactions + return false, "material" + elseif job_item.mat_index~=-1 then + if item:getActualMaterialIndex()~=job_item.mat_index then + return false,"material index" + end + end + end + local matinfo=dfhack.matinfo.decode(item) + --print(matinfo:getCraftClass()) + if not matinfo:matches(job_item) then + return false,"matinfo" + end + --reagen_index?? reaction_id?? + if job_item.metal_ore~=-1 and not item:isMetalOre(job_item.metal_ore) then + return false,"metal ore" + end + if job_item.min_dimension~=-1 then + end + if #job_item.contains~=0 then + end + if job_item.has_tool_use~=-1 then + if not item:hasToolUse(job_item.has_tool_use) then + return false,"tool use" + end + end + if job_item.has_material_reaction_product~="" then + + end + if job_item.reaction_class~="" then + + end + return true +end +function AssignJobItems(args,is_workshop_job) + if settings.df_assign and not is_workshop_job then return true end + --print("Assign") --printall(args) local job=args.job local its=itemsAtPos(args.from_pos) @@ -293,6 +348,16 @@ function AssignJobItems(args) for k,v in pairs(args.unit.inventory) do table.insert(its,v.item) end + local contained={} + for k,v in pairs(its) do + local cc=dfhack.items.getContainedItems(v) + for _,c_item in pairs(cc) do + table.insert(contained,c_item) + end + end + for k,v in pairs(contained) do + table.insert(its,v) + end end --[[while(#job.items>0) do --clear old job items job.items[#job.items-1]:delete() @@ -302,8 +367,12 @@ function AssignJobItems(args) for job_id, trg_job_item in ipairs(job.job_items) do for _,cur_item in pairs(its) do if not used_item_id[cur_item.id] then - if (trg_job_item.quantity>0 and dfhack.job.isSuitableItem(trg_job_item, cur_item:getType(), cur_item:getSubtype()) and - dfhack.job.isSuitableMaterial(trg_job_item, cur_item:getMaterial(), cur_item:getMaterialIndex())) or settings.build_by_items then + + local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) + --if msg then + -- print(cur_item,msg) + --end + if (trg_job_item.quantity>0 and item_suitable) or settings.build_by_items then job.items:insert("#",{new=true,item=cur_item,role=2,job_item_idx=job_id}) trg_job_item.quantity=trg_job_item.quantity-1 --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),trg_job_item.quantity)) @@ -312,11 +381,7 @@ function AssignJobItems(args) end end end - --[[print("+============+") - printall(job.items) - print("-============-") - printall(job.job_items) - print("+============+")]]-- + if not settings.build_by_items then for job_id, trg_job_item in ipairs(job.job_items) do if trg_job_item.quantity>0 then @@ -351,7 +416,14 @@ end function CancelJob(unit) local c_job=unit.job.current_job if c_job then - unit.job.current_job =nil --todo add real cancelation + unit.job.current_job =nil --todo add real cancelation + for k,v in pairs(c_job.general_refs) do + if df.general_ref_unit_workerst:is_instance(v) then + v:delete() + c_job.general_refs:erase(k) + return + end + end end end function ContinueJob(unit) @@ -367,14 +439,29 @@ function ContinueJob(unit) unit.path.dest:assign(c_job.pos) end end -function AddItemRefMason(args) - --printall(args) - args.job.job_items:insert("#",{new=true,mat_type=0,quantity=1}) - return true -end workshops={ - [df.workshop_type.Mason]={ - common={item_type=df.item_type.BOULDER,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,flags2={non_economic=true,},flags3={hard=true}}, + --[=[ + [df.workshop_type.Jewelers]={ + --TODO add material.IS_GEM + [df.job_type.CutGems]={{test={isMaterialGem},item_type=df.item_type.BOULDER}, + [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}, + [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}, + [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}, + } + ]=] + [df.workshop_type.Fishery]={ + common={quantity=1}, + [df.job_type.PrepareRawFish]={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}}, + [df.job_type.ExtractFromRawFish]={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}}, + [df.job_type.CatchLiveFish]={}, -- no items? + }, + [df.workshop_type.Still]={ + common={quantity=1}, + [df.job_type.BrewDrink]={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}}, + [df.job_type.ExtractFromPlants]={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, + }, + [df.workshop_type.Masons]={ + common={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags2={non_economic=true},flags3={hard=true}}, [df.job_type.ConstructArmorStand]={{}}, [df.job_type.ConstructBlocks]={{}}, [df.job_type.ConstructThrone]={{}}, @@ -384,7 +471,7 @@ workshops={ [df.job_type.ConstructHatchCover]={{}}, [df.job_type.ConstructGrate]={{}}, [df.job_type.ConstructCabinet]={{}}, - [df.job_type.ConstructCofer]={{}}, + [df.job_type.ConstructChest]={{}}, [df.job_type.ConstructStatue]={{}}, [df.job_type.ConstructSlab]={{}}, [df.job_type.ConstructTable]={{}}, @@ -392,7 +479,52 @@ workshops={ [df.job_type.ConstructQuern]={{}}, [df.job_type.ConstructMillstone]={{}}, }, + [df.workshop_type.Carpenters]={ + common={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD,quantity=1}, + + [df.job_type.MakeBarrel]={{}}, + --[[ from raws + [df.job_type.MakeShield]={{}}, + [df.job_type.MakeShield]={item_subtype=1,{}}, --buckler + [df.job_type.MakeWeapon]={item_subtype=23,{}}, --training spear -> from raws... + [df.job_type.MakeWeapon]={item_subtype=22,{}}, --training sword + [df.job_type.MakeWeapon]={item_subtype=21,{}}, --training axe + --]] + [df.job_type.ConstructBlocks]={{}}, + [df.job_type.MakeBucket]={{}}, + [df.job_type.MakeAnimalTrap]={{}}, + [df.job_type.MakeCage]={{}}, + [df.job_type.ConstructArmorStand]={{}}, + [df.job_type.ConstructBed]={{}}, + [df.job_type.ConstructThrone]={{}}, + [df.job_type.ConstructCoffin]={{}}, + [df.job_type.ConstructDoor]={{}}, + [df.job_type.ConstructFloodgate]={{}}, + [df.job_type.ConstructHatchCover]={{}}, + [df.job_type.ConstructGrate]={{}}, + [df.job_type.ConstructCabinet]={{}}, + [df.job_type.ConstructBin]={{}}, + [df.job_type.ConstructChest]={{}}, + [df.job_type.ConstructStatue]={{}}, + [df.job_type.ConstructSlab]={{}}, + [df.job_type.ConstructTable]={{}}, + [df.job_type.ConstructWeaponRack]={{}}, + --[[ from raws + [df.job_type.MakeTrapComponent]={item_subtype=1,{}}, --corkscrew + [df.job_type.MakeTrapComponent]={item_subtype=2,{}}, --ball + [df.job_type.MakeTrapComponent]={item_subtype=4,{}}, --spike + [df.job_type.MakeTool]={item_subtype=16,{}}, --minecart (?? maybe from raws?) + --]] + [df.job_type.ConstructSplint]={{}}, + [df.job_type.ConstructCrutch]={{}}, + }, + [df.workshop_type.Mechanics]={ + common={quantity=1}, + [df.job_type.ConstructMechanisms]={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags2={non_economic=true},flags3={hard=true}}}, + [df.job_type.ConstructTractionBench]={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}} + }, } + dig_modes={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMat}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMat}}, @@ -422,9 +554,6 @@ dig_modes={ usetool=defclass(usetool,gui.Screen) -function usetool:getShopMode() - return "In shop" -end function usetool:getModeName() local adv=df.global.world.units.active[0] if adv.job.current_job then @@ -434,23 +563,36 @@ function usetool:getModeName() end end - +function usetool:isOnWorkshop() + local adv=df.global.world.units.active[0] + local bld=dfhack.buildings.findAtTile(adv.pos) + if bld and bld:getType()==df.building_type.Workshop and bld.construction_stage==3 then + return true,bld + else + return false + end +end function usetool:init(args) self:addviews{ wid.Label{ - view_id="main_label", + view_id="mainLabel", frame = {xalign=0,yalign=0}, - text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getModeName,self)},{gap=1,key=keybinds.key_next.key}} + text={{key=keybinds.prevJob.key},{gap=1,text=dfhack.curry(usetool.getModeName,self)},{gap=1,key=keybinds.nextJob.key}, + } }, + + wid.Label{ - view_id="shop_label", + view_id="shopLabel", + frame = {l=35,xalign=0,yalign=0}, visible=false, - frame = {xalign=0,yalign=0}, - text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getShopMode,self),pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{gap=1,key=keybinds.key_next.key}} + text={ + {gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop Mode",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} } } end function usetool:onRenderBody(dc) + self:shopMode(self:isOnWorkshop()) self:renderParent() end function usetool:onIdle() @@ -494,6 +636,128 @@ end function usetool:onHelp() showHelp() end +function setFiltersUp(common,specific,args) + local job=args.job + for k,v in pairs(specific) do + if type(k)=="string" then + job[k]=v + end + end + for _,v in ipairs(specific) do + local filter + filter=require("utils").clone(common or {}) + filter.new=true + require("utils").assign(filter,v) + --printall(filter) + job.job_items:insert("#",filter) + end + return true +end +function onWorkShopJobChosen(args,idx,choice) + args.pos=args.from_pos + args.job_type=choice.job_id + args.post_actions={AssignBuildingRef} + args.pre_actions={dfhack.curry(setFiltersUp,args.common,choice.filter),function(args)return AssignJobItems(args,true) end} + local job,msg=MakeJob(args) + if not job then + dfhack.gui.showAnnouncement(msg,5,1) + end + args.job=job + + --[[for _,v in ipairs(choice.filter) do + local filter=require("utils").clone(args.common) + filter.new=true + require("utils").assign(filter,v) + --printall(filter) + job.job_items:insert("#",filter) + end--]] + --local ok,msg=AssignJobItems(args) + --print(ok,msg) +end +function usetool:openShopWindow(building) + local adv=df.global.world.units.active[0] + + local shop_type=building:getSubtype() + + local filter_pile=workshops[shop_type] + + if filter_pile then + local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z} + ,screen=self,bld=building,common=filter_pile.common} + choices={} + for k,v in pairs(filter_pile) do + if k~= "common" then + table.insert(choices,{job_id=k,text=df.job_type[k]:lower(),filter=v}) + + end + end + require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) + ,nil, nil,true) + end +end +function usetool:shopMode(enable,wshop) + self.subviews.shopLabel.visible=enable + self.in_shop=wshop +end +function usetool:shopInput(keys) + if keys[keybinds.workshop.key] then + self:openShopWindow(self.in_shop) + end +end +function usetool:fieldInput(keys) + local adv=df.global.world.units.active[0] + local cur_mode=dig_modes[(mode or 0)+1] + local failed=false + for code,_ in pairs(keys) do + --print(code) + if MOVEMENT_KEYS[code] then + local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], + from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_actions=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} + if code=="SELECT" then + if df.global.cursor.x~=-30000 then + state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} + else + break + end + end + + for _,p in pairs(cur_mode[3] or {}) do + local ok,msg=p(state) + if ok==false then + dfhack.gui.showAnnouncement(msg,5,1) + failed=true + end + end + + if not failed then + local ok,msg + if type(cur_mode[2])=="function" then + ok,msg=cur_mode[2](state) + else + ok,msg=MakeJob(state) + --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) + + end + + if code=="SELECT" then + self:sendInputToParent("LEAVESCREEN") + end + if ok then + self:sendInputToParent("A_WAIT") + else + dfhack.gui.showAnnouncement(msg,5,1) + end + end + return code + end + if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then + if ALLOWED_KEYS[code] then + self:sendInputToParent(code) + end + end + end + +end function usetool:onInput(keys) local adv=df.global.world.units.active[0] if keys.LEAVESCREEN then @@ -503,66 +767,22 @@ function usetool:onInput(keys) self:dismiss() CancelJob(adv) end - elseif keys[keybinds.key_next.key] then + elseif keys[keybinds.nextJob.key] then mode=(mode+1)%#dig_modes - elseif keys[keybinds.key_prev.key] then + elseif keys[keybinds.prevJob.key] then mode=mode-1 if mode<0 then mode=#dig_modes-1 end --elseif keys.A_LOOK then -- self:sendInputToParent("A_LOOK") - elseif keys[keybinds.key_continue.key] then + elseif keys[keybinds.continue.key] then ContinueJob(adv) self:sendInputToParent("A_WAIT") else - local cur_mode=dig_modes[(mode or 0)+1] - local failed=false - for code,_ in pairs(keys) do - --print(code) - if MOVEMENT_KEYS[code] then - local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], - from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_action=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} - if code=="SELECT" then - if df.global.cursor.x~=-30000 then - state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} - else - break - end - end - - for _,p in pairs(cur_mode[3] or {}) do - local ok,msg=p(state) - if ok==false then - dfhack.gui.showAnnouncement(msg,5,1) - failed=true - end - end - - if not failed then - local ok,msg - if type(cur_mode[2])=="function" then - ok,msg=cur_mode[2](state) - else - ok,msg=MakeJob(state) - --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) - - end - - if code=="SELECT" then - self:sendInputToParent("LEAVESCREEN") - end - if ok then - self:sendInputToParent("A_WAIT") - else - dfhack.gui.showAnnouncement(msg,5,1) - end - end - return code - end - if code~="_STRING" and code~="_MOUSE_L" and code~="_MOUSE_R" then - if ALLOWED_KEYS[code] then - self:sendInputToParent(code) - end - end + if self.in_shop then + self:shopInput(keys) + self:fieldInput(keys) + else + self:fieldInput(keys) end end end From 92503db505d52647cebb58b7500c612ff5acd7e6 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 1 Dec 2012 18:42:23 +0200 Subject: [PATCH 264/472] Sanity check before showing gui. --- scripts/gui/advfort.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 8f4fb3f61..f95387a4c 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -461,7 +461,7 @@ workshops={ [df.job_type.ExtractFromPlants]={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, }, [df.workshop_type.Masons]={ - common={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags2={non_economic=true},flags3={hard=true}}, + common={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags3={hard=true}},--flags2={non_economic=true}, [df.job_type.ConstructArmorStand]={{}}, [df.job_type.ConstructBlocks]={{}}, [df.job_type.ConstructThrone]={{}}, @@ -520,7 +520,8 @@ workshops={ }, [df.workshop_type.Mechanics]={ common={quantity=1}, - [df.job_type.ConstructMechanisms]={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags2={non_economic=true},flags3={hard=true}}}, + [df.job_type.ConstructMechanisms]={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1, + flags3={hard=true}}}, -- flags2={non_economic=true} [df.job_type.ConstructTractionBench]={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}} }, } @@ -554,6 +555,7 @@ dig_modes={ usetool=defclass(usetool,gui.Screen) +usetool.focus_path = 'advfort' function usetool:getModeName() local adv=df.global.world.units.active[0] if adv.job.current_job then @@ -786,4 +788,7 @@ function usetool:onInput(keys) end end end +if not (dfhack.gui.getCurFocus()=="dungeonmode/Look" or dfhack.gui.getCurFocus()=="dungeonmode/Default") then + qerror("This script requires an adventurer mode with (l)ook or default mode.") +end usetool():show() \ No newline at end of file From 201430ed08bfa0d9d406c025e4bbe332b9aa0e1f Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 14:08:15 -0600 Subject: [PATCH 265/472] Autolabor: add health awareness, fix initialization crash, fix idle dwarf loop crash --- plugins/autolabor.cpp | 1343 ++++++++++++++++++++++------------------- 1 file changed, 724 insertions(+), 619 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5736480fb..e1655bba4 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -50,7 +50,8 @@ #include #include #include - +#include +#include #include @@ -67,29 +68,29 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) /* - * Autolabor module for dfhack - * - * The idea behind this module is to constantly adjust labors so that the right dwarves - * are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins - * a job it will finish that job even if the associated labor is removed. Thus the - * strategy is to frequently decide, for each labor, which dwarves should possibly take - * a new job for that labor if it comes in and which shouldn't, and then set the labors - * appropriately. The updating should happen as often as can be reasonably done without - * causing lag. - * - * The obvious thing to do is to just set each labor on a single idle dwarf who is best - * suited to doing new jobs of that labor. This works in a way, but it leads to a lot - * of idle dwarves since only one dwarf will be dispatched for each labor in an update - * cycle, and dwarves that finish tasks will wait for the next update before being - * dispatched. An improvement is to also set some labors on dwarves that are currently - * doing a job, so that they will immediately take a new job when they finish. The - * details of which dwarves should have labors set is mostly a heuristic. - * - * A complication to the above simple scheme is labors that have associated equipment. - * Enabling/disabling these labors causes dwarves to change equipment, and disabling - * them in the middle of a job may cause the job to be abandoned. Those labors - * (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. - */ +* Autolabor module for dfhack +* +* The idea behind this module is to constantly adjust labors so that the right dwarves +* are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins +* a job it will finish that job even if the associated labor is removed. Thus the +* strategy is to frequently decide, for each labor, which dwarves should possibly take +* a new job for that labor if it comes in and which shouldn't, and then set the labors +* appropriately. The updating should happen as often as can be reasonably done without +* causing lag. +* +* The obvious thing to do is to just set each labor on a single idle dwarf who is best +* suited to doing new jobs of that labor. This works in a way, but it leads to a lot +* of idle dwarves since only one dwarf will be dispatched for each labor in an update +* cycle, and dwarves that finish tasks will wait for the next update before being +* dispatched. An improvement is to also set some labors on dwarves that are currently +* doing a job, so that they will immediately take a new job when they finish. The +* details of which dwarves should have labors set is mostly a heuristic. +* +* A complication to the above simple scheme is labors that have associated equipment. +* Enabling/disabling these labors causes dwarves to change equipment, and disabling +* them in the middle of a job may cause the job to be abandoned. Those labors +* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. +*/ static int enable_autolabor = 0; @@ -538,101 +539,611 @@ struct dwarf_info_t (labor == df::unit_labor::HUNT && !has_crossbow)) dwarf->military.pickup_flags.bits.update = 1; } + + void clear_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = false; + if ((labor == df::unit_labor::MINE && has_pick) || + (labor == df::unit_labor::CUTWOOD && has_axe) || + (labor == df::unit_labor::HUNT && has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } + }; static df::unit_labor hauling_labor_map[] = +{ + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ +}; + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::HERBALIST, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +static df::building* get_building_from_job(df::job* j) +{ + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; +} + +class JobLaborMapper { +private: + class jlfunc + { + public: + virtual df::unit_labor get_labor(df::job* j) = 0; + }; + + class jlfunc_const : public jlfunc + { + private: + df::unit_labor labor; + public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; + }; + + class jlfunc_hauling : public jlfunc { - df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_ITEM, /* SMALLGEM */ - df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_ITEM, /* ROUGH */ - df::unit_labor::HAUL_STONE, /* BOULDER */ - df::unit_labor::HAUL_WOOD, /* WOOD */ - df::unit_labor::HAUL_FURNITURE, /* DOOR */ - df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ - df::unit_labor::HAUL_FURNITURE, /* BED */ - df::unit_labor::HAUL_FURNITURE, /* CHAIR */ - df::unit_labor::HAUL_ITEM, /* CHAIN */ - df::unit_labor::HAUL_ITEM, /* FLASK */ - df::unit_labor::HAUL_ITEM, /* GOBLET */ - df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ - df::unit_labor::HAUL_ITEM, /* TOY */ - df::unit_labor::HAUL_FURNITURE, /* WINDOW */ - df::unit_labor::HAUL_ANIMAL, /* CAGE */ - df::unit_labor::HAUL_ITEM, /* BARREL */ - df::unit_labor::HAUL_ITEM, /* BUCKET */ - df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ - df::unit_labor::HAUL_FURNITURE, /* TABLE */ - df::unit_labor::HAUL_FURNITURE, /* COFFIN */ - df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_BODY, /* CORPSE */ - df::unit_labor::HAUL_ITEM, /* WEAPON */ - df::unit_labor::HAUL_ITEM, /* ARMOR */ - df::unit_labor::HAUL_ITEM, /* SHOES */ - df::unit_labor::HAUL_ITEM, /* SHIELD */ - df::unit_labor::HAUL_ITEM, /* HELM */ - df::unit_labor::HAUL_ITEM, /* GLOVES */ - df::unit_labor::HAUL_FURNITURE, /* BOX */ - df::unit_labor::HAUL_ITEM, /* BIN */ - df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ - df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ - df::unit_labor::HAUL_FURNITURE, /* CABINET */ - df::unit_labor::HAUL_ITEM, /* FIGURINE */ - df::unit_labor::HAUL_ITEM, /* AMULET */ - df::unit_labor::HAUL_ITEM, /* SCEPTER */ - df::unit_labor::HAUL_ITEM, /* AMMO */ - df::unit_labor::HAUL_ITEM, /* CROWN */ - df::unit_labor::HAUL_ITEM, /* RING */ - df::unit_labor::HAUL_ITEM, /* EARRING */ - df::unit_labor::HAUL_ITEM, /* BRACELET */ - df::unit_labor::HAUL_ITEM, /* GEM */ - df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ - df::unit_labor::HAUL_REFUSE, /* REMAINS */ - df::unit_labor::HAUL_FOOD, /* MEAT */ - df::unit_labor::HAUL_FOOD, /* FISH */ - df::unit_labor::HAUL_FOOD, /* FISH_RAW */ - df::unit_labor::HAUL_REFUSE, /* VERMIN */ - df::unit_labor::HAUL_ITEM, /* PET */ - df::unit_labor::HAUL_FOOD, /* SEEDS */ - df::unit_labor::HAUL_FOOD, /* PLANT */ - df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ - df::unit_labor::HAUL_FOOD, /* LEAVES */ - df::unit_labor::HAUL_ITEM, /* THREAD */ - df::unit_labor::HAUL_ITEM, /* CLOTH */ - df::unit_labor::HAUL_ITEM, /* TOTEM */ - df::unit_labor::HAUL_ITEM, /* PANTS */ - df::unit_labor::HAUL_ITEM, /* BACKPACK */ - df::unit_labor::HAUL_ITEM, /* QUIVER */ - df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ - df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ - df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ - df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ - df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ - df::unit_labor::HAUL_FOOD, /* DRINK */ - df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ - df::unit_labor::HAUL_FOOD, /* CHEESE */ - df::unit_labor::HAUL_FOOD, /* FOOD */ - df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ - df::unit_labor::HAUL_ITEM, /* COIN */ - df::unit_labor::HAUL_FOOD, /* GLOB */ - df::unit_labor::HAUL_STONE, /* ROCK */ - df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ - df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ - df::unit_labor::HAUL_FURNITURE, /* GRATE */ - df::unit_labor::HAUL_FURNITURE, /* QUERN */ - df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ - df::unit_labor::HAUL_ITEM, /* SPLINT */ - df::unit_labor::HAUL_ITEM, /* CRUTCH */ - df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ - df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ - df::unit_labor::HAUL_ITEM, /* TOOL */ - df::unit_labor::HAUL_FURNITURE, /* SLAB */ - df::unit_labor::HAUL_FOOD, /* EGG */ - df::unit_labor::HAUL_ITEM, /* BOOK */ + public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = j->items[0]->item; + return hauling_labor_map[item->getType()]; + } + jlfunc_hauling() {}; }; + class jlfunc_construct_bld : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + + break; + } + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} + }; + + class jlfunc_destroy_bld : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + df::building_type type = bld->getType(); + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} + }; + + class jlfunc_make : public jlfunc + { + private: + df::unit_labor metaltype; + public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone) + return df::unit_labor::BONE_CARVE; + else + return df::unit_labor::NONE; //FIXME + default: + return df::unit_labor::NONE; //FIXME + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + return df::unit_labor::NONE; // FIXME + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + return df::unit_labor::NONE; // FIXME + } + } + + return df::unit_labor::NONE; // FIXME + } + + jlfunc_make (df::unit_labor mt) : metaltype(mt) {} + }; + + class jlfunc_custom : public jlfunc + { + public: + df::unit_labor get_labor(df::job* j) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} + }; + + map jlf_cache; + + jlfunc* jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = new jlfunc_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; + } +private: + jlfunc *jlf_hauling, *jlf_make_furniture, *jlf_make_object, *jlf_make_armor, *jlf_make_weapon; + jlfunc *job_to_labor_table[ENUM_LAST_ITEM(job_type)+1]; + +public: + ~JobLaborMapper() + { + delete jlf_hauling; + delete jlf_make_furniture; + delete jlf_make_object; + delete jlf_make_armor; + delete jlf_make_weapon; + } + + JobLaborMapper() + { + jlf_hauling = new jlfunc_hauling(); + jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + + jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); + + job_to_labor_table[df::job_type::CarveFortification] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailWall] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::DetailFloor] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::Dig] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveDownwardStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveUpDownStaircase] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::CarveRamp] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::DigChannel] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::FellTree] = jlf_const(df::unit_labor::CUTWOOD); + job_to_labor_table[df::job_type::GatherPlants] = jlf_const(df::unit_labor::HERBALIST); + job_to_labor_table[df::job_type::RemoveConstruction] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectWebs] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::BringItemToDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::BringItemToShop] = jlf_no_labor; + job_to_labor_table[df::job_type::Eat] = jlf_no_labor; + job_to_labor_table[df::job_type::GetProvisions] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink] = jlf_no_labor; + job_to_labor_table[df::job_type::Drink2] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; + job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; + job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; + job_to_labor_table[df::job_type::Kidnap] = jlf_no_labor; + job_to_labor_table[df::job_type::BeatCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::StartingFistFight] = jlf_no_labor; + job_to_labor_table[df::job_type::CollectTaxes] = jlf_no_labor; + job_to_labor_table[df::job_type::GuardTaxCollector] = jlf_no_labor; + job_to_labor_table[df::job_type::CatchLiveLandAnimal] = jlf_const(df::unit_labor::HUNT); + job_to_labor_table[df::job_type::CatchLiveFish] = jlf_const(df::unit_labor::FISH); + job_to_labor_table[df::job_type::ReturnKill] = jlf_no_labor; + job_to_labor_table[df::job_type::CheckChest] = jlf_no_labor; + job_to_labor_table[df::job_type::StoreOwnedItem] = jlf_no_labor; + job_to_labor_table[df::job_type::PlaceItemInTomb] = jlf_const(df::unit_labor::HAUL_BODY); + job_to_labor_table[df::job_type::StoreItemInStockpile] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBag] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInHospital] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInChest] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInCabinet] = jlf_hauling; + job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; + job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; + job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; + job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping] = jlf_no_labor; + job_to_labor_table[df::job_type::GoShopping2] = jlf_no_labor; + job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); + job_to_labor_table[df::job_type::Rest] = jlf_no_labor; + job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; + job_to_labor_table[df::job_type::DumpItem] = jlf_hauling; + job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMagmaForge] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBrooding] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodFell] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodCarpenter] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMason] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodBowyer] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodTanner] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodWeaver] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodGlassmaker] = jlf_no_labor; + job_to_labor_table[df::job_type::StrangeMoodMechanics] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructBuilding] = new jlfunc_construct_bld(); + job_to_labor_table[df::job_type::ConstructDoor] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructFloodgate] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBed] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructThrone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCoffin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructTable] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructChest] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBin] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructArmorStand] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructWeaponRack] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCabinet] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructStatue] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructBlocks] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeRawGlass] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::MakeCrafts] = jlf_make_object; + job_to_labor_table[df::job_type::MintCoins] = jlf_const(df::unit_labor::METAL_CRAFT); + job_to_labor_table[df::job_type::CutGems] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::CutGlass] = jlf_const(df::unit_labor::CUT_GEM); + job_to_labor_table[df::job_type::EncrustWithGems] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::EncrustWithGlass] = jlf_const(df::unit_labor::ENCRUST_GEM); + job_to_labor_table[df::job_type::DestroyBuilding] = new jlfunc_destroy_bld(); + job_to_labor_table[df::job_type::SmeltOre] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::MeltMetalObject] = jlf_const(df::unit_labor::SMELT); + job_to_labor_table[df::job_type::ExtractMetalStrands] = jlf_const(df::unit_labor::EXTRACT_STRAND); + job_to_labor_table[df::job_type::PlantSeeds] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::HarvestPlants] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::TrainHuntingAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::TrainWarAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::MakeWeapon] = jlf_make_weapon; + job_to_labor_table[df::job_type::ForgeAnvil] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCatapultParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::ConstructBallistaParts] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::MakeArmor] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeHelm] = jlf_make_armor; + job_to_labor_table[df::job_type::MakePants] = jlf_make_armor; + job_to_labor_table[df::job_type::StudWith] = jlf_make_object; + job_to_labor_table[df::job_type::ButcherAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::PrepareRawFish] = jlf_const(df::unit_labor::CLEAN_FISH); + job_to_labor_table[df::job_type::MillPlants] = jlf_const(df::unit_labor::MILLER); + job_to_labor_table[df::job_type::BaitTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MilkCreature] = jlf_const(df::unit_labor::MILK); + job_to_labor_table[df::job_type::MakeCheese] = jlf_const(df::unit_labor::MAKE_CHEESE); + job_to_labor_table[df::job_type::ProcessPlants] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBag] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsVial] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::ProcessPlantsBarrel] = jlf_const(df::unit_labor::PROCESS_PLANT); + job_to_labor_table[df::job_type::PrepareMeal] = jlf_const(df::unit_labor::COOK); + job_to_labor_table[df::job_type::WeaveCloth] = jlf_const(df::unit_labor::WEAVER); + job_to_labor_table[df::job_type::MakeGloves] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShoes] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeShield] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeCage] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeChain] = jlf_make_object; + job_to_labor_table[df::job_type::MakeFlask] = jlf_make_object; + job_to_labor_table[df::job_type::MakeGoblet] = jlf_make_object; + job_to_labor_table[df::job_type::MakeInstrument] = jlf_make_object; + job_to_labor_table[df::job_type::MakeToy] = jlf_make_object; + job_to_labor_table[df::job_type::MakeAnimalTrap] = jlf_const(df::unit_labor::TRAPPER); + job_to_labor_table[df::job_type::MakeBarrel] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeBucket] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeWindow] = jlf_make_furniture; + job_to_labor_table[df::job_type::MakeTotem] = jlf_const(df::unit_labor::BONE_CARVE); + job_to_labor_table[df::job_type::MakeAmmo] = jlf_make_weapon; + job_to_labor_table[df::job_type::DecorateWith] = jlf_make_object; + job_to_labor_table[df::job_type::MakeBackpack] = jlf_make_object; + job_to_labor_table[df::job_type::MakeQuiver] = jlf_make_armor; + job_to_labor_table[df::job_type::MakeBallistaArrowHead] = jlf_make_weapon; + job_to_labor_table[df::job_type::AssembleSiegeAmmo] = jlf_const(df::unit_labor::SIEGECRAFT); + job_to_labor_table[df::job_type::LoadCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::LoadBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireCatapult] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::FireBallista] = jlf_const(df::unit_labor::SIEGEOPERATE); + job_to_labor_table[df::job_type::ConstructMechanisms] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::MakeTrapComponent] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadCageTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadStoneTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::LoadWeaponTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::CleanTrap] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::CastSpell] = jlf_no_labor; + job_to_labor_table[df::job_type::LinkBuildingToTrigger] = jlf_const(df::unit_labor::MECHANIC) ; + job_to_labor_table[df::job_type::PullLever] = jlf_no_labor; + job_to_labor_table[df::job_type::BrewDrink] = jlf_const(df::unit_labor::BREWER) ; + job_to_labor_table[df::job_type::ExtractFromPlants] = jlf_const(df::unit_labor::HERBALIST) ; + job_to_labor_table[df::job_type::ExtractFromRawFish] = jlf_const(df::unit_labor::DISSECT_FISH) ; + job_to_labor_table[df::job_type::ExtractFromLandAnimal] = jlf_const(df::unit_labor::DISSECT_VERMIN) ; + job_to_labor_table[df::job_type::TameVermin] = jlf_const(df::unit_labor::ANIMALTRAIN) ; + job_to_labor_table[df::job_type::TameAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN) ; + job_to_labor_table[df::job_type::ChainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::UnchainPet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleasePet] = jlf_no_labor; + job_to_labor_table[df::job_type::ReleaseSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::HandleLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::CageLargeCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::CageSmallCreature] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverWounded] = jlf_const(df::unit_labor::RECOVER_WOUNDED); + job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; + job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::CLEAN) ; + job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; + job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); + job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::PlaceInTraction] = jlf_const(df::unit_labor::BONE_SETTING) ; + job_to_labor_table[df::job_type::DrainAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillAquarium] = jlf_no_labor; + job_to_labor_table[df::job_type::FillPond] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveWater] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; + job_to_labor_table[df::job_type::GiveFood] = jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) ; + job_to_labor_table[df::job_type::GiveWater2] = jlf_no_labor; + job_to_labor_table[df::job_type::GiveFood2] = jlf_no_labor; + job_to_labor_table[df::job_type::RecoverPet] = jlf_no_labor; + job_to_labor_table[df::job_type::PitLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PitSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::SlaughterAnimal] = jlf_const(df::unit_labor::BUTCHER); + job_to_labor_table[df::job_type::MakeCharcoal] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeAsh] = jlf_const(df::unit_labor::BURN_WOOD); + job_to_labor_table[df::job_type::MakeLye] = jlf_const(df::unit_labor::LYE_MAKING); + job_to_labor_table[df::job_type::MakePotashFromLye] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::FertilizeField] = jlf_const(df::unit_labor::PLANT); + job_to_labor_table[df::job_type::MakePotashFromAsh] = jlf_const(df::unit_labor::POTASH_MAKING); + job_to_labor_table[df::job_type::DyeThread] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::DyeCloth] = jlf_const(df::unit_labor::DYER); + job_to_labor_table[df::job_type::SewImage] = jlf_make_object; + job_to_labor_table[df::job_type::MakePipeSection] = jlf_make_furniture; + job_to_labor_table[df::job_type::OperatePump] = jlf_const(df::unit_labor::OPERATE_PUMP); + job_to_labor_table[df::job_type::ManageWorkOrders] = jlf_no_labor; + job_to_labor_table[df::job_type::UpdateStockpileRecords] = jlf_no_labor; + job_to_labor_table[df::job_type::TradeAtDepot] = jlf_no_labor; + job_to_labor_table[df::job_type::ConstructHatchCover] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; + job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); + job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture ; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_object ; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_object; + job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); + job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; + job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); + job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); + job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::STONE_CRAFT); + job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); + job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); + job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::PenSmallAnimal] = jlf_no_labor; + job_to_labor_table[df::job_type::MakeTool] = jlf_make_furniture; + job_to_labor_table[df::job_type::CollectClay] = jlf_const(df::unit_labor::POTTERY); + job_to_labor_table[df::job_type::InstallColonyInHive] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CollectHiveProducts] = jlf_const(df::unit_labor::BEEKEEPING); + job_to_labor_table[df::job_type::CauseTrouble] = jlf_no_labor; + job_to_labor_table[df::job_type::DrinkBlood] = jlf_no_labor; + job_to_labor_table[df::job_type::ReportCrime] = jlf_no_labor; + job_to_labor_table[df::job_type::ExecuteCriminal] = jlf_no_labor; + job_to_labor_table[df::job_type::TrainAnimal] = jlf_const(df::unit_labor::ANIMALTRAIN); + job_to_labor_table[df::job_type::CarveTrack] = jlf_const(df::unit_labor::DETAIL); + job_to_labor_table[df::job_type::PushTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::PlaceTrackVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + job_to_labor_table[df::job_type::StoreItemInVehicle] = jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE); + }; + + df::unit_labor find_job_labor(df::job* j) + { + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + df::job_skill skill; + df::unit_labor labor; + skill = ENUM_ATTR(job_type, skill, j->job_type); + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + else + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == df::unit_labor::NONE) + labor = job_to_labor_table[j->job_type]->get_labor(j); + + return labor; + } +}; + +static JobLaborMapper* labor_mapper; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -652,6 +1163,11 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { labor_infos.clear(); + if (labor_mapper) + { + delete labor_mapper; + labor_mapper = 0; + } } static void reset_labor(df::unit_labor labor) @@ -717,6 +1233,9 @@ static void init_state() generate_labor_to_skill_map(); + if (!labor_mapper) + labor_mapper = new JobLaborMapper(); + } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -804,7 +1323,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; -} - - - -static df::unit_labor workshop_build_labor[] = -{ - /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::HERBALIST, - /* Masons */ df::unit_labor::MASON, - /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, - /* Jewelers */ df::unit_labor::CUT_GEM, - /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, - /* MagmaForge */ df::unit_labor::METAL_CRAFT, - /* Bowyers */ df::unit_labor::BOWYER, - /* Mechanics */ df::unit_labor::MECHANIC, - /* Siege */ df::unit_labor::SIEGECRAFT, - /* Butchers */ df::unit_labor::BUTCHER, - /* Leatherworks */ df::unit_labor::LEATHER, - /* Tanners */ df::unit_labor::TANNER, - /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::FISH, - /* Still */ df::unit_labor::BREWER, - /* Loom */ df::unit_labor::WEAVER, - /* Quern */ df::unit_labor::MILLER, - /* Kennels */ df::unit_labor::ANIMALTRAIN, - /* Kitchen */ df::unit_labor::COOK, - /* Ashery */ df::unit_labor::LYE_MAKING, - /* Dyers */ df::unit_labor::DYER, - /* Millstone */ df::unit_labor::MILLER, - /* Custom */ df::unit_labor::NONE, - /* Tool */ df::unit_labor::NONE -}; - -class jlfunc -{ - public: - virtual df::unit_labor get_labor(df::job* j) = 0; -}; - -class jlfunc_const : public jlfunc -{ - private: - df::unit_labor labor; - public: - df::unit_labor get_labor(df::job* j) - { - return labor; - } - jlfunc_const(df::unit_labor l) : labor(l) {}; -}; - -class jlfunc_hauling : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::item* item = j->items[0]->item; - return hauling_labor_map[item->getType()]; - } - jlfunc_hauling() {}; -}; - -class jlfunc_construct_bld : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) - { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - - break; - } - - // FIXME - return df::unit_labor::NONE; - } - jlfunc_construct_bld() {} -}; - -class jlfunc_destroy_bld : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job (j); - df::building_type type = bld->getType(); - - // FIXME - return df::unit_labor::NONE; - } - jlfunc_destroy_bld() {} -}; - -class jlfunc_make : public jlfunc -{ -private: - df::unit_labor metaltype; -public: - df::unit_labor get_labor(df::job* j) - { - df::building* bld = get_building_from_job(j); - if (bld->getType() == df::building_type::Workshop) - { - df::workshop_type type = ((df::building_workshopst*)(bld))->type; - switch (type) - { - case df::workshop_type::Craftsdwarfs: - { - df::item_type jobitem = j->job_items[0]->item_type; - switch (jobitem) - { - case df::item_type::BOULDER: - return df::unit_labor::STONE_CRAFT; - case df::item_type::NONE: - if (j->material_category.bits.bone) - return df::unit_labor::BONE_CARVE; - else - return df::unit_labor::NONE; //FIXME - default: - return df::unit_labor::NONE; //FIXME - } - } - case df::workshop_type::Masons: - return df::unit_labor::MASON; - case df::workshop_type::Carpenters: - return df::unit_labor::CARPENTER; - case df::workshop_type::Leatherworks: - return df::unit_labor::LEATHER; - case df::workshop_type::Clothiers: - return df::unit_labor::CLOTHESMAKER; - case df::workshop_type::MagmaForge: - case df::workshop_type::MetalsmithsForge: - return metaltype; - default: - return df::unit_labor::NONE; // FIXME - } - } - else if (bld->getType() == df::building_type::Furnace) - { - df::furnace_type type = ((df::building_furnacest*)(bld))->type; - switch (type) - { - case df::furnace_type::MagmaGlassFurnace: - case df::furnace_type::GlassFurnace: - return df::unit_labor::GLASSMAKER; - default: - return df::unit_labor::NONE; // FIXME - } - } - - return df::unit_labor::NONE; // FIXME - } - - jlfunc_make (df::unit_labor mt) : metaltype(mt) {} -}; - -class jlfunc_custom : public jlfunc -{ -public: - df::unit_labor get_labor(df::job* j) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); - return labor; - } - } - return df::unit_labor::NONE; - } - jlfunc_custom() {} -}; - -static map jlf_cache; - -jlfunc* jlf_const(df::unit_labor l) { - jlfunc* jlf; - if (jlf_cache.count(l) == 0) - { - jlf = jlf_const(l); - jlf_cache[l] = jlf; - } - else - jlf = jlf_cache[l]; - - return jlf; -} - -static jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); -static jlfunc* jlf_hauling = new jlfunc_hauling(); -static jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); -static jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); -static jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); -static jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); - -static jlfunc* job_to_labor_table[] = { - jlf_const(df::unit_labor::DETAIL) /* CarveFortification */, - jlf_const(df::unit_labor::DETAIL) /* DetailWall */, - jlf_const(df::unit_labor::DETAIL) /* DetailFloor */, - jlf_const(df::unit_labor::MINE) /* Dig */, - jlf_const(df::unit_labor::MINE) /* CarveUpwardStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveDownwardStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveUpDownStaircase */, - jlf_const(df::unit_labor::MINE) /* CarveRamp */, - jlf_const(df::unit_labor::MINE) /* DigChannel */, - jlf_const(df::unit_labor::CUTWOOD) /* FellTree */, - jlf_const(df::unit_labor::HERBALIST) /* GatherPlants */, - jlf_no_labor /* RemoveConstruction */, - jlf_const(df::unit_labor::WEAVER) /* CollectWebs */, - jlf_no_labor /* BringItemToDepot */, - jlf_no_labor /* BringItemToShop */, - jlf_no_labor /* Eat */, - jlf_no_labor /* GetProvisions */, - jlf_no_labor /* Drink */, - jlf_no_labor /* Drink2 */, - jlf_no_labor /* FillWaterskin */, - jlf_no_labor /* FillWaterskin2 */, - jlf_no_labor /* Sleep */, - jlf_const(df::unit_labor::GLASSMAKER) /* CollectSand */, - jlf_const(df::unit_labor::FISH) /* Fish */, - jlf_const(df::unit_labor::HUNT) /* Hunt */, - jlf_no_labor /* HuntVermin */, - jlf_no_labor /* Kidnap */, - jlf_no_labor /* BeatCriminal */, - jlf_no_labor /* StartingFistFight */, - jlf_no_labor /* CollectTaxes */, - jlf_no_labor /* GuardTaxCollector */, - jlf_const(df::unit_labor::HUNT) /* CatchLiveLandAnimal */, - jlf_const(df::unit_labor::FISH) /* CatchLiveFish */, - jlf_no_labor /* ReturnKill */, - jlf_no_labor /* CheckChest */, - jlf_no_labor /* StoreOwnedItem */, - jlf_const(df::unit_labor::HAUL_BODY) /* PlaceItemInTomb */, - jlf_hauling /* StoreItemInStockpile */, - jlf_hauling /* StoreItemInBag */, - jlf_hauling /* StoreItemInHospital */, - jlf_hauling /* StoreItemInChest */, - jlf_hauling /* StoreItemInCabinet */, - jlf_hauling /* StoreWeapon */, - jlf_hauling /* StoreArmor */, - jlf_hauling /* StoreItemInBarrel */, - jlf_hauling /* StoreItemInBin */, - jlf_no_labor /* SeekArtifact */, - jlf_no_labor /* SeekInfant */, - jlf_no_labor /* AttendParty */, - jlf_no_labor /* GoShopping */, - jlf_no_labor /* GoShopping2 */, - jlf_const(df::unit_labor::CLEAN) /* Clean */, - jlf_no_labor /* Rest */, - jlf_no_labor /* PickupEquipment */, - jlf_hauling /* DumpItem */, - jlf_no_labor /* StrangeMoodCrafter */, - jlf_no_labor /* StrangeMoodJeweller */, - jlf_no_labor /* StrangeMoodForge */, - jlf_no_labor /* StrangeMoodMagmaForge */, - jlf_no_labor /* StrangeMoodBrooding */, - jlf_no_labor /* StrangeMoodFell */, - jlf_no_labor /* StrangeMoodCarpenter */, - jlf_no_labor /* StrangeMoodMason */, - jlf_no_labor /* StrangeMoodBowyer */, - jlf_no_labor /* StrangeMoodTanner */, - jlf_no_labor /* StrangeMoodWeaver */, - jlf_no_labor /* StrangeMoodGlassmaker */, - jlf_no_labor /* StrangeMoodMechanics */, - new jlfunc_construct_bld() /* ConstructBuilding */, - jlf_make_furniture /* ConstructDoor */, - jlf_make_furniture /* ConstructFloodgate */, - jlf_make_furniture /* ConstructBed */, - jlf_make_furniture /* ConstructThrone */, - jlf_make_furniture /* ConstructCoffin */, - jlf_make_furniture /* ConstructTable */, - jlf_make_furniture /* ConstructChest */, - jlf_make_furniture /* ConstructBin */, - jlf_make_furniture /* ConstructArmorStand */, - jlf_make_furniture /* ConstructWeaponRack */, - jlf_make_furniture /* ConstructCabinet */, - jlf_make_furniture /* ConstructStatue */, - jlf_make_furniture /* ConstructBlocks */, - jlf_const(df::unit_labor::GLASSMAKER) /* MakeRawGlass */, - jlf_make_object /* MakeCrafts */, - jlf_const(df::unit_labor::METAL_CRAFT) /* MintCoins */, - jlf_const(df::unit_labor::CUT_GEM) /* CutGems */, - jlf_const(df::unit_labor::CUT_GEM) /* CutGlass */, - jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGems */, - jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGlass */, - new jlfunc_destroy_bld() /* DestroyBuilding */, - jlf_const(df::unit_labor::SMELT) /* SmeltOre */, - jlf_const(df::unit_labor::SMELT) /* MeltMetalObject */, - jlf_const(df::unit_labor::EXTRACT_STRAND) /* ExtractMetalStrands */, - jlf_const(df::unit_labor::PLANT) /* PlantSeeds */, - jlf_const(df::unit_labor::PLANT) /* HarvestPlants */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainHuntingAnimal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainWarAnimal */, - jlf_make_weapon /* MakeWeapon */, - jlf_make_furniture /* ForgeAnvil */, - jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructCatapultParts */, - jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructBallistaParts */, - jlf_make_armor /* MakeArmor */, - jlf_make_armor /* MakeHelm */, - jlf_make_armor /* MakePants */, - jlf_make_object /* StudWith */, - jlf_const(df::unit_labor::BUTCHER) /* ButcherAnimal */, - jlf_const(df::unit_labor::CLEAN_FISH) /* PrepareRawFish */, - jlf_const(df::unit_labor::MILLER) /* MillPlants */, - jlf_const(df::unit_labor::TRAPPER) /* BaitTrap */, - jlf_const(df::unit_labor::MILK) /* MilkCreature */, - jlf_const(df::unit_labor::MAKE_CHEESE) /* MakeCheese */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlants */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBag */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsVial */, - jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBarrel */, - jlf_const(df::unit_labor::COOK) /* PrepareMeal */, - jlf_const(df::unit_labor::WEAVER) /* WeaveCloth */, - jlf_make_armor /* MakeGloves */, - jlf_make_armor /* MakeShoes */, - jlf_make_armor /* MakeShield */, - jlf_make_furniture /* MakeCage */, - jlf_make_object /* MakeChain */, - jlf_make_object /* MakeFlask */, - jlf_make_object /* MakeGoblet */, - jlf_make_object/* MakeInstrument */, - jlf_make_object/* MakeToy */, - jlf_const(df::unit_labor::TRAPPER) /* MakeAnimalTrap */, - jlf_make_furniture /* MakeBarrel */, - jlf_make_furniture /* MakeBucket */, - jlf_make_furniture /* MakeWindow */, - jlf_const(df::unit_labor::BONE_CARVE) /* MakeTotem */, - jlf_make_weapon /* MakeAmmo */, - jlf_make_object /* DecorateWith */, - jlf_make_object /* MakeBackpack */, - jlf_make_armor /* MakeQuiver */, - jlf_make_weapon /* MakeBallistaArrowHead */, - jlf_const(df::unit_labor::SIEGECRAFT) /* AssembleSiegeAmmo */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadCatapult */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadBallista */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* FireCatapult */, - jlf_const(df::unit_labor::SIEGEOPERATE) /* FireBallista */, - jlf_const(df::unit_labor::MECHANIC) /* ConstructMechanisms */, - jlf_const(df::unit_labor::MECHANIC) /* MakeTrapComponent */, - jlf_const(df::unit_labor::MECHANIC) /* LoadCageTrap */, - jlf_const(df::unit_labor::MECHANIC) /* LoadStoneTrap */, - jlf_const(df::unit_labor::MECHANIC) /* LoadWeaponTrap */, - jlf_const(df::unit_labor::MECHANIC) /* CleanTrap */, - jlf_no_labor /* CastSpell */, - jlf_const(df::unit_labor::MECHANIC) /* LinkBuildingToTrigger */, - jlf_no_labor /* PullLever */, - jlf_const(df::unit_labor::BREWER) /* BrewDrink */, - jlf_const(df::unit_labor::HERBALIST) /* ExtractFromPlants */, - jlf_const(df::unit_labor::DISSECT_FISH) /* ExtractFromRawFish */, - jlf_const(df::unit_labor::DISSECT_VERMIN) /* ExtractFromLandAnimal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TameVermin */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TameAnimal */, - jlf_no_labor /* ChainAnimal */, - jlf_no_labor /* UnchainAnimal */, - jlf_no_labor /* UnchainPet */, - jlf_no_labor /* ReleaseLargeCreature */, - jlf_no_labor /* ReleasePet */, - jlf_no_labor /* ReleaseSmallCreature */, - jlf_no_labor /* HandleSmallCreature */, - jlf_no_labor /* HandleLargeCreature */, - jlf_no_labor /* CageLargeCreature */, - jlf_no_labor /* CageSmallCreature */, - jlf_const(df::unit_labor::RECOVER_WOUNDED) /* RecoverWounded */, - jlf_const(df::unit_labor::DIAGNOSE) /* DiagnosePatient */, - jlf_const(df::unit_labor::BONE_SETTING) /* ImmobilizeBreak */, - jlf_const(df::unit_labor::DRESSING_WOUNDS) /* DressWound */, - jlf_const(df::unit_labor::CLEAN) /* CleanPatient */, - jlf_const(df::unit_labor::SURGERY) /* Surgery */, - jlf_const(df::unit_labor::SUTURING) /* Suture */, - jlf_const(df::unit_labor::BONE_SETTING) /* SetBone */, - jlf_const(df::unit_labor::BONE_SETTING) /* PlaceInTraction */, - jlf_no_labor /* DrainAquarium */, - jlf_no_labor /* FillAquarium */, - jlf_no_labor /* FillPond */, - jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveWater */, - jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveFood */, - jlf_no_labor /* GiveWater2 */, - jlf_no_labor /* GiveFood2 */, - jlf_no_labor /* RecoverPet */, - jlf_no_labor /* PitLargeAnimal */, - jlf_no_labor /* PitSmallAnimal */, - jlf_const(df::unit_labor::BUTCHER) /* SlaughterAnimal */, - jlf_const(df::unit_labor::BURN_WOOD) /* MakeCharcoal */, - jlf_const(df::unit_labor::BURN_WOOD) /* MakeAsh */, - jlf_const(df::unit_labor::LYE_MAKING) /* MakeLye */, - jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromLye */, - jlf_const(df::unit_labor::PLANT) /* FertilizeField */, - jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromAsh */, - jlf_const(df::unit_labor::DYER) /* DyeThread */, - jlf_const(df::unit_labor::DYER) /* DyeCloth */, - jlf_make_object /* SewImage */, - jlf_make_furniture /* MakePipeSection */, - jlf_const(df::unit_labor::OPERATE_PUMP) /* OperatePump */, - jlf_no_labor /* ManageWorkOrders */, - jlf_no_labor /* UpdateStockpileRecords */, - jlf_no_labor /* TradeAtDepot */, - jlf_make_furniture /* ConstructHatchCover */, - jlf_make_furniture /* ConstructGrate */, - jlf_const(df::unit_labor::MINE) /* RemoveStairs */, - jlf_make_furniture /* ConstructQuern */, - jlf_make_furniture /* ConstructMillstone */, - jlf_make_object /* ConstructSplint */, - jlf_make_object /* ConstructCrutch */, - jlf_const(df::unit_labor::MECHANIC) /* ConstructTractionBench */, - jlf_no_labor /* CleanSelf */, - jlf_no_labor /* BringCrutch */, - jlf_const(df::unit_labor::BONE_SETTING) /* ApplyCast */, - new jlfunc_custom() /* CustomReaction */, - jlf_make_furniture /* ConstructSlab */, - jlf_const(df::unit_labor::STONE_CRAFT) /* EngraveSlab */, - jlf_const(df::unit_labor::SHEARER) /* ShearCreature */, - jlf_const(df::unit_labor::SPINNER) /* SpinThread */, - jlf_no_labor /* PenLargeAnimal */, - jlf_no_labor /* PenSmallAnimal */, - jlf_make_furniture /* MakeTool */, - jlf_const(df::unit_labor::POTTERY) /* CollectClay */, - jlf_const(df::unit_labor::BEEKEEPING) /* InstallColonyInHive */, - jlf_const(df::unit_labor::BEEKEEPING) /* CollectHiveProducts */, - jlf_no_labor /* CauseTrouble */, - jlf_no_labor /* DrinkBlood */, - jlf_no_labor /* ReportCrime */, - jlf_no_labor /* ExecuteCriminal */, - jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainAnimal */, - jlf_const(df::unit_labor::DETAIL) /* CarveTrack */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PushTrackVehicle */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PlaceTrackVehicle */, - jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* StoreItemInVehicle */ -}; - - -static df::unit_labor find_job_labor(df::job* j) -{ - if (j->job_type == df::job_type::CustomReaction) - { - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - df::job_skill skill = (*r)->skill; - return ENUM_ATTR(job_skill, labor, skill); - } - } - return df::unit_labor::NONE; - } - - df::job_skill skill; - df::unit_labor labor; - skill = ENUM_ATTR(job_type, skill, j->job_type); - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - else - labor = ENUM_ATTR(job_type, labor, j->job_type); - - if (labor == df::unit_labor::NONE) - labor = job_to_labor_table[j->job_type]->get_labor(j); - - return labor; -} - class AutoLaborManager { color_ostream& out; @@ -1337,9 +1372,21 @@ private: int pick_count; int axe_count; + int cnt_recover_wounded; + int cnt_diagnosis; + int cnt_immobilize; + int cnt_dressing; + int cnt_cleaning; + int cnt_surgery; + int cnt_suture; + int cnt_setting; + int cnt_traction; + int cnt_crutch; + + std::map labor_needed; std::vector dwarf_info; - std::deque idle_dwarfs; + std::list idle_dwarfs; private: void scan_buildings() @@ -1435,7 +1482,7 @@ private: df::item* item = world->items.all[i]; if (item->flags.whole & bad_flags.whole) continue; - + if (!item->isWeapon()) continue; @@ -1472,7 +1519,7 @@ private: if (worker != -1) continue; - df::unit_labor labor = find_job_labor (j); + df::unit_labor labor = labor_mapper->find_job_labor (j); if (print_debug) out.print ("Job requiring labor %d found\n", labor); @@ -1492,9 +1539,6 @@ private: if (Units::isCitizen(cre)) { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - dwarf_info_t* dwarf = add_dwarf(cre); df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); @@ -1557,7 +1601,7 @@ private: { state = CHILD; } - else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + else if (dwarf->dwarf->military.cur_uniform != 0) state = MILITARY; else if (dwarf->dwarf->job.current_job == NULL) { @@ -1565,6 +1609,8 @@ private: state = OTHER; else if (dwarf->dwarf->specific_refs.size() > 0) state = OTHER; + else if (dwarf->dwarf->burrows.size() > 0) + state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy else state = IDLE; } @@ -1585,6 +1631,31 @@ private: if (print_debug) out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + // determine if dwarf has medical needs + if (dwarf->dwarf->health) + { + if (dwarf->dwarf->health->flags.bits.needs_recovery) + cnt_recover_wounded++; + if (dwarf->dwarf->health->flags.bits.rq_diagnosis) + cnt_diagnosis++; + if (dwarf->dwarf->health->flags.bits.rq_immobilize) + cnt_immobilize++; + if (dwarf->dwarf->health->flags.bits.rq_dressing) + cnt_dressing++; + if (dwarf->dwarf->health->flags.bits.rq_cleaning) + cnt_cleaning++; + if (dwarf->dwarf->health->flags.bits.rq_surgery) + cnt_surgery++; + if (dwarf->dwarf->health->flags.bits.rq_suture) + cnt_suture++; + if (dwarf->dwarf->health->flags.bits.rq_setting) + cnt_setting++; + if (dwarf->dwarf->health->flags.bits.rq_traction) + cnt_traction++; + if (dwarf->dwarf->health->flags.bits.rq_crutch) + cnt_crutch++; + } + // check if dwarf has an axe, pick, or crossbow for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) @@ -1620,9 +1691,9 @@ private: } } - // clear labors if currently idle + // clear labors of dwarfs with clear_all set - if (state == IDLE || dwarf->clear_all) + if (dwarf->clear_all) { FOR_ENUM_ITEMS(unit_labor, labor) { @@ -1644,6 +1715,10 @@ private: public: void process() { + dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; + cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = + cnt_setting = cnt_traction = cnt_crutch = 0; + // scan for specific buildings of interest scan_buildings(); @@ -1656,8 +1731,6 @@ public: count_tools(); - // create job entries for designation - // collect current job list collect_job_list(); @@ -1666,35 +1739,67 @@ public: collect_dwarf_list(); + // add job entries for designation-related jobs + + labor_needed[df::unit_labor::MINE] += std::min(pick_count, dig_count); + labor_needed[df::unit_labor::CUTWOOD] += std::min(axe_count, tree_count); + labor_needed[df::unit_labor::DETAIL] += detail_count; + labor_needed[df::unit_labor::HERBALIST] += plant_count; + + // add job entries for health care + + labor_needed[df::unit_labor::RECOVER_WOUNDED] += cnt_recover_wounded; + labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; + labor_needed[df::unit_labor::CLEAN] += cnt_cleaning; + labor_needed[df::unit_labor::SURGERY] += cnt_surgery; + labor_needed[df::unit_labor::SUTURING] += cnt_suture; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; + labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + + if (print_debug) + { + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + out.print ("labor_needed [%d] = %d\n", i->first, i->second); + } + } + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count // and remove the idle dwarf from the idle list - for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) + for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) { FOR_ENUM_ITEMS(unit_labor, l) { - if ((*i)->dwarf->status.labors[l]) + if (l == df::unit_labor::NONE) + continue; + + if ((*d)->dwarf->status.labors[l]) if (labor_needed[l] > 0) { if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + out.print("assign \"%s\" labor %d (carried through)\n", (*d)->dwarf->name.first_name.c_str(), l); labor_needed[l]--; - idle_dwarfs.erase(i); // remove from idle list + d = idle_dwarfs.erase(d); // remove from idle list + d--; // and go back one break; } else { - (*i)->dwarf->status.labors[l] = false; + (*d)->clear_labor(l); } } } priority_queue> pq; - + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { if (i->second > 0) pq.push(make_pair(i->second, i->first)); } - + while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1704,13 +1809,13 @@ public: if (print_debug) out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - std::deque::iterator bestdwarf = idle_dwarfs.begin(); + std::list::iterator bestdwarf = idle_dwarfs.begin(); if (skill != df::job_skill::NONE) { int best_skill_level = -1; - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + for (std::list::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) { dwarf_info_t* d = (*k); int skill_level = Units::getEffectiveSkill(d->dwarf, skill); @@ -1809,7 +1914,7 @@ command_result autolabor (color_ostream &out, std::vector & parame if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "enable" || - parameters[0] == "1" || parameters[0] == "disable")) + parameters[0] == "1" || parameters[0] == "disable")) { bool enable = (parameters[0] == "1" || parameters[0] == "enable"); if (enable && !enable_autolabor) From bb2b97baa3ac528810d003ce8683387c8d10e911 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 16:09:52 -0600 Subject: [PATCH 266/472] autolabor: exclude "item lost" jobs, exclude jobs that are not first-in-queue at workshop, improve debug messages --- plugins/autolabor.cpp | 46 ++++++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e1655bba4..fd52d522d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1507,18 +1507,48 @@ private: if (!j) continue; - if (j->flags.bits.suspend) + if (j->flags.bits.suspend || j->flags.bits.item_lost) continue; int worker = -1; + int bld = -1; for (int r = 0; r < j->general_refs.size(); ++r) + { if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + if (j->general_refs[r]->getType() == df::general_ref_type::BUILDING_HOLDER) + bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; + } if (worker != -1) continue; + if (bld != -1) + { + if (print_debug) + out.print("Checking job %d for first in queue at building %d\n", j->id, bld); + df::building* b = binsearch_in_vector(world->buildings.all, bld); + int fjid = -1; + for (int jn = 0; jn < b->jobs.size(); jn++) + { + if (b->jobs[jn]->flags.bits.suspend) + continue; + fjid = b->jobs[jn]->id; + break; + } + // check if this job is the first nonsuspended job on this building; if not, ignore it + if (fjid != j->id) { + if (print_debug) + out.print("Job %d is not first in queue at building %d (%d), skipping\n", j->id, bld, fjid); + continue; + } + + if (print_debug) + out.print("Job %d is in queue at building %d\n", j->id, bld); + + } + df::unit_labor labor = labor_mapper->find_job_labor (j); if (print_debug) @@ -1763,13 +1793,16 @@ public: { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%d] = %d\n", i->first, i->second); + out.print ("labor_needed [%s] = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second); } } // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count // and remove the idle dwarf from the idle list + if (print_debug) + out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), labor_needed.size()); + for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) { FOR_ENUM_ITEMS(unit_labor, l) @@ -1781,7 +1814,7 @@ public: if (labor_needed[l] > 0) { if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*d)->dwarf->name.first_name.c_str(), l); + out.print("assign \"%s\" labor %s (carried through)\n", (*d)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, l).c_str()); labor_needed[l]--; d = idle_dwarfs.erase(d); // remove from idle list d--; // and go back one @@ -1800,6 +1833,9 @@ public: pq.push(make_pair(i->second, i->first)); } + if (print_debug) + out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), pq.size()); + while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1807,7 +1843,7 @@ public: df::job_skill skill = labor_to_skill[labor]; if (print_debug) - out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); + out.print("labor %s skill %s remaining %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), remaining); std::list::iterator bestdwarf = idle_dwarfs.begin(); @@ -1829,7 +1865,7 @@ public: } if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + out.print("assign \"%s\" labor %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); (*bestdwarf)->set_labor(labor); idle_dwarfs.erase(bestdwarf); From 15f7ffa0e2a0c7d764e1d7e25bd6e6316bdccde1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 17:39:01 -0600 Subject: [PATCH 267/472] autolabor: add ConstructBuilding (Farm); change priority calculation --- plugins/autolabor.cpp | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index fd52d522d..5d90610d9 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -727,16 +727,19 @@ private: switch (bld->getType()) { case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - if (ws->type == df::workshop_type::Custom) { - df::building_def* def = df::building_def::find(ws->custom_type); - return def->build_labors[0]; - } - else - return workshop_build_labor[ws->type]; - + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; } // FIXME @@ -1830,7 +1833,7 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { if (i->second > 0) - pq.push(make_pair(i->second, i->first)); + pq.push(make_pair(100, i->first)); } if (print_debug) @@ -1839,11 +1842,11 @@ public: while (!idle_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; - int remaining = pq.top().first; + int priority = pq.top().first; df::job_skill skill = labor_to_skill[labor]; if (print_debug) - out.print("labor %s skill %s remaining %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), remaining); + out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); std::list::iterator bestdwarf = idle_dwarfs.begin(); @@ -1870,8 +1873,11 @@ public: idle_dwarfs.erase(bestdwarf); pq.pop(); - if (--remaining) - pq.push(make_pair(remaining, labor)); + if (--labor_needed[labor] > 0) + { + priority /= 2; + pq.push(make_pair(priority, labor)); + } } print_debug = 0; From 45564ca0cb82d512dad4c6aa0c6dbe18b63f207e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 23:12:41 -0600 Subject: [PATCH 268/472] Autolabor: generating haulers based on unstockpiled items (less than ideal). Fix wrong build labor for Farmer's workshop. Add build labor function for constructions (also works for furnaces and trade depots). Add architect detection. Use a different mechanism for selecting dwarfs for labors. --- plugins/autolabor.cpp | 148 +++++++++++++++++++++++++----------------- 1 file changed, 88 insertions(+), 60 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5d90610d9..846ca8e64 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include @@ -529,7 +530,11 @@ struct dwarf_info_t bool has_pick; bool has_crossbow; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + int high_skill; + + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) + { + } void set_labor(df::unit_labor labor) { @@ -647,7 +652,7 @@ static df::unit_labor hauling_labor_map[] = static df::unit_labor workshop_build_labor[] = { /* Carpenters */ df::unit_labor::CARPENTER, - /* Farmers */ df::unit_labor::HERBALIST, + /* Farmers */ df::unit_labor::PROCESS_PLANT, /* Masons */ df::unit_labor::MASON, /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, /* Jewelers */ df::unit_labor::CUT_GEM, @@ -687,6 +692,19 @@ static df::building* get_building_from_job(df::job* j) return 0; } +static df::unit_labor construction_build_labor (df::item* i) +{ + MaterialInfo matinfo; + if (matinfo.decode(i)) + { + if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) + return df::unit_labor::METAL_CRAFT; + if (matinfo.material->flags.is_set(df::material_flags::WOOD)) + return df::unit_labor::CARPENTER; + } + return df::unit_labor::MASON; +} + class JobLaborMapper { private: class jlfunc @@ -729,6 +747,8 @@ private: case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->design && !ws->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; if (ws->type == df::workshop_type::Custom) { df::building_def* def = df::building_def::find(ws->custom_type); @@ -738,6 +758,16 @@ private: return workshop_build_labor[ws->type]; } break; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Construction: + { + df::building_actual* b = (df::building_actual*) bld; + if (b->design && !b->design->flags.bits.designed) + return df::unit_labor::ARCHITECT; + return construction_build_labor(j->items[0]->item); + } + break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; } @@ -1386,10 +1416,9 @@ private: int cnt_traction; int cnt_crutch; - std::map labor_needed; std::vector dwarf_info; - std::list idle_dwarfs; + std::list available_dwarfs; private: void scan_buildings() @@ -1486,6 +1515,9 @@ private: if (item->flags.whole & bad_flags.whole) continue; + if (!item->flags.bits.in_inventory && !item->isAssignedToStockpile()) + labor_needed[hauling_labor_map[item->getType()]]++; + if (!item->isWeapon()) continue; @@ -1504,6 +1536,8 @@ private: void collect_job_list() { + labor_needed.clear(); + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; @@ -1524,9 +1558,6 @@ private: bld = ((df::general_ref_building_holderst *)(j->general_refs[r]))->building_id; } - if (worker != -1) - continue; - if (bld != -1) { if (print_debug) @@ -1617,7 +1648,7 @@ private: } } - // Find the activity state for each dwarf-> + // Find the activity state for each dwarf bool is_on_break = false; dwarf_state state = OTHER; @@ -1689,6 +1720,17 @@ private: cnt_crutch++; } + // find dwarf's highest effective skill + + int high_skill = 0; + + FOR_ENUM_ITEMS (job_skill, skill) + { + int skill_level = Units::getEffectiveSkill(dwarf->dwarf, skill); + high_skill = std::max(high_skill, skill_level); + } + + dwarf->high_skill = high_skill; // check if dwarf has an axe, pick, or crossbow for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) @@ -1733,17 +1775,17 @@ private: if (labor == unit_labor::NONE) continue; - dwarf->dwarf->status.labors[labor] = false; + dwarf->clear_labor(labor); } } - if (state == IDLE && !dwarf->clear_all) - idle_dwarfs.push_back(dwarf); + if ((state == IDLE || state == BUSY) && !dwarf->clear_all) + available_dwarfs.push_back(dwarf); } } - } + } public: void process() @@ -1760,14 +1802,14 @@ public: count_map_designations(); - // count number of picks and axes available for use - - count_tools(); - // collect current job list collect_job_list(); + // count number of picks and axes available for use + + count_tools(); + // collect list of dwarfs collect_dwarf_list(); @@ -1800,34 +1842,6 @@ public: } } - // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count - // and remove the idle dwarf from the idle list - - if (print_debug) - out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), labor_needed.size()); - - for (auto d = idle_dwarfs.begin(); d != idle_dwarfs.end(); d++) - { - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - - if ((*d)->dwarf->status.labors[l]) - if (labor_needed[l] > 0) - { - if (print_debug) - out.print("assign \"%s\" labor %s (carried through)\n", (*d)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, l).c_str()); - labor_needed[l]--; - d = idle_dwarfs.erase(d); // remove from idle list - d--; // and go back one - break; - } else { - (*d)->clear_labor(l); - } - } - } - priority_queue> pq; for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1837,9 +1851,9 @@ public: } if (print_debug) - out.print("idle count = %d, labor need count = %d\n", idle_dwarfs.size(), pq.size()); + out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); - while (!idle_dwarfs.empty() && !pq.empty()) + while (!available_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; int priority = pq.top().first; @@ -1848,30 +1862,44 @@ public: if (print_debug) out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); - std::list::iterator bestdwarf = idle_dwarfs.begin(); + std::list::iterator bestdwarf = available_dwarfs.begin(); - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; + int best_score = -10000; - for (std::list::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = 0; + if (skill != df::job_skill::NONE) { - dwarf_info_t* d = (*k); int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + } - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } + int score = skill_level * 100 - (d->high_skill - skill) * 100; + if (d->dwarf->status.labors[labor]) + score += 300; + if ((labor == df::unit_labor::MINE && d->has_pick) || + (labor == df::unit_labor::CUTWOOD && d->has_axe) || + (labor == df::unit_labor::HUNT && d->has_crossbow)) + score += 300; + if (score > best_score) + { + bestdwarf = k; + best_score = score; } } if (print_debug) - out.print("assign \"%s\" labor %s\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); - (*bestdwarf)->set_labor(labor); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), best_score); + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == labor) + (*bestdwarf)->set_labor(l); + else + (*bestdwarf)->clear_labor(l); + } - idle_dwarfs.erase(bestdwarf); + available_dwarfs.erase(bestdwarf); pq.pop(); if (--labor_needed[labor] > 0) { From 799da41f70163b1bd1d29e75837233c2f8427d21 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 02:02:16 -0600 Subject: [PATCH 269/472] Autolabor: Add debugging messages from the labor deduction module; add deduction rules for some building destroy jobs; automatically exclude handless dwarfs from labor poor; use DF's own hauling job counts to compute hauling demand (and arrange for the "hauling canary" so that this always works) --- plugins/autolabor.cpp | 117 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 107 insertions(+), 10 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 846ca8e64..daa261da3 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -705,7 +705,22 @@ static df::unit_labor construction_build_labor (df::item* i) return df::unit_labor::MASON; } +color_ostream* debug_stream; + +void debug (char* fmt, ...) +{ + if (debug_stream) + { + va_list args; + va_start(args,fmt); + debug_stream->vprint(fmt, args); + va_end(args); + } +} + + class JobLaborMapper { + private: class jlfunc { @@ -772,7 +787,9 @@ private: return df::unit_labor::PLANT; } - // FIXME + debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + return df::unit_labor::NONE; } jlfunc_construct_bld() {} @@ -786,7 +803,35 @@ private: df::building* bld = get_building_from_job (j); df::building_type type = bld->getType(); - // FIXME + switch (bld->getType()) + { + case df::building_type::Workshop: + { + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + } + break; + case df::building_type::Furnace: + case df::building_type::TradeDepot: + case df::building_type::Construction: + { + df::building_actual* b = (df::building_actual*) bld; + return construction_build_labor(j->items[0]->item); + } + break; + case df::building_type::FarmPlot: + return df::unit_labor::PLANT; + } + + debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + return df::unit_labor::NONE; } jlfunc_destroy_bld() {} @@ -816,9 +861,14 @@ private: if (j->material_category.bits.bone) return df::unit_labor::BONE_CARVE; else - return df::unit_labor::NONE; //FIXME + { + debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); + return df::unit_labor::NONE; + } default: - return df::unit_labor::NONE; //FIXME + debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", + ENUM_KEY_STR(item_type, jobitem).c_str()); + return df::unit_labor::NONE; } } case df::workshop_type::Masons: @@ -833,7 +883,9 @@ private: case df::workshop_type::MetalsmithsForge: return metaltype; default: - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, workshop type %s\n", + ENUM_KEY_STR(workshop_type, type).c_str()); + return df::unit_labor::NONE; } } else if (bld->getType() == df::building_type::Furnace) @@ -845,11 +897,16 @@ private: case df::furnace_type::GlassFurnace: return df::unit_labor::GLASSMAKER; default: - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, furnace type %s\n", + ENUM_KEY_STR(furnace_type, type).c_str()); + return df::unit_labor::NONE; } } - return df::unit_labor::NONE; // FIXME + debug ("AUTOLABOR: Cannot deduce labor for make job, building type %s\n", + ENUM_KEY_STR(building_type, bld->getType()).c_str()); + + return df::unit_labor::NONE; } jlfunc_make (df::unit_labor mt) : metaltype(mt) {} @@ -1515,9 +1572,6 @@ private: if (item->flags.whole & bad_flags.whole) continue; - if (!item->flags.bits.in_inventory && !item->isAssignedToStockpile()) - labor_needed[hauling_labor_map[item->getType()]]++; - if (!item->isWeapon()) continue; @@ -1675,6 +1729,11 @@ private: state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy + else if (dwarf->dwarf->status2.able_grasp_impair == 0) + { + state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors + dwarf->clear_all = true; + } else state = IDLE; } @@ -1834,6 +1893,17 @@ public: labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + // add entries for hauling jobs + + labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; + labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; + labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; + labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; + labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; + labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1853,6 +1923,15 @@ public: if (print_debug) out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); + int canary = (1 << df::unit_labor::HAUL_STONE) | + (1 << df::unit_labor::HAUL_WOOD) | + (1 << df::unit_labor::HAUL_BODY) | + (1 << df::unit_labor::HAUL_FOOD) | + (1 << df::unit_labor::HAUL_REFUSE) | + (1 << df::unit_labor::HAUL_ITEM) | + (1 << df::unit_labor::HAUL_FURNITURE) | + (1 << df::unit_labor::HAUL_ANIMAL); + while (!available_dwarfs.empty() && !pq.empty()) { df::unit_labor labor = pq.top().second; @@ -1899,6 +1978,9 @@ public: (*bestdwarf)->clear_labor(l); } + if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) + canary &= ~(1 << labor); + available_dwarfs.erase(bestdwarf); pq.pop(); if (--labor_needed[labor] > 0) @@ -1908,6 +1990,19 @@ public: } } + if (canary != 0) + { + dwarf_info_t* d = dwarf_info.front(); + FOR_ENUM_ITEMS (unit_labor, l) + { + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL && + canary & (1 << l)) + d->set_labor(l); + } + if (print_debug) + out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); + } + print_debug = 0; } @@ -1946,6 +2041,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; + debug_stream = &out; + AutoLaborManager alm(out); alm.process(); From 9703d3fd8fd2660ad0b9c51f738cfdd37fa08786 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Dec 2012 14:43:23 +0400 Subject: [PATCH 270/472] Detect mouse press events for lua. --- Lua API.html | 5 ++++- Lua API.rst | 5 ++++- library/lua/gui.lua | 2 ++ library/modules/Screen.cpp | 14 ++++++++++++-- 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Lua API.html b/Lua API.html index f14239a10..aa41b8b63 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1749,7 +1749,10 @@ options; if multiple interpretations exist, the table will contain multiple keys

                                                                                Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience.

                                                                                _MOUSE_L, _MOUSE_R
                                                                                -

                                                                                If the left or right mouse button is pressed.

                                                                                +

                                                                                If the left or right mouse button is being pressed.

                                                                                +
                                                                                +
                                                                                _MOUSE_L_DOWN, _MOUSE_R_DOWN
                                                                                +

                                                                                If the left or right mouse button was just pressed.

                                                                                If this method is omitted, the screen is dismissed on receival of the LEAVESCREEN key.

                                                                                diff --git a/Lua API.rst b/Lua API.rst index cedc36441..eaef74997 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -1610,7 +1610,10 @@ Supported callbacks and fields are: Maps to an integer in range 0-255. Duplicates a separate "STRING_A???" code for convenience. ``_MOUSE_L, _MOUSE_R`` - If the left or right mouse button is pressed. + If the left or right mouse button is being pressed. + + ``_MOUSE_L_DOWN, _MOUSE_R_DOWN`` + If the left or right mouse button was just pressed. If this method is omitted, the screen is dismissed on receival of the ``LEAVESCREEN`` key. diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 99bf9263c..603c7ab44 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -13,6 +13,8 @@ CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} local FAKE_INPUT_KEYS = { _MOUSE_L = true, _MOUSE_R = true, + _MOUSE_L_DOWN = true, + _MOUSE_R_DOWN = true, _STRING = true, } diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index cd20bc25e..782bb317d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -664,14 +664,24 @@ int dfhack_lua_viewscreen::do_input(lua_State *L) if (enabler && enabler->tracking_on) { - if (enabler->mouse_lbut) { + if (enabler->mouse_lbut_down) { lua_pushboolean(L, true); lua_setfield(L, -2, "_MOUSE_L"); } - if (enabler->mouse_rbut) { + if (enabler->mouse_rbut_down) { lua_pushboolean(L, true); lua_setfield(L, -2, "_MOUSE_R"); } + if (enabler->mouse_lbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_L_DOWN"); + enabler->mouse_lbut = 0; + } + if (enabler->mouse_rbut) { + lua_pushboolean(L, true); + lua_setfield(L, -2, "_MOUSE_R_DOWN"); + enabler->mouse_rbut = 0; + } } lua_call(L, 2, 0); From dc7f9f56cd9d9ac6ebda4b59027841458ecbf82c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 2 Dec 2012 15:31:43 +0400 Subject: [PATCH 271/472] Implement a low stock level announcement as suggested by falconne. --- NEWS | 1 + plugins/workflow.cpp | 21 +++++++++++++++++++-- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 4856b06c3..b8ad53830 100644 --- a/NEWS +++ b/NEWS @@ -43,6 +43,7 @@ DFHack future - logic fix: collecting webs produces silk, and ungathered webs are not thread. - items assigned to squads are considered busy, even if not in inventory. - shearing and milking jobs are supported, but only with generic MILK or YARN outputs. + - workflow announces when the stock level gets very low once a season. New Fix Armory plugin: Together with a couple of binary patches and the gui/assign-rack script, this plugin makes weapon racks, armor stands, chests and cabinets in diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 12174af5c..b70fd8b0c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -322,6 +322,7 @@ struct ItemConstraint { bool request_suspend, request_resume; bool is_active, cant_resume_reported; + int low_stock_reported; TMaterialCache material_cache; @@ -329,7 +330,7 @@ public: ItemConstraint() : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), - is_active(false), cant_resume_reported(false) + is_active(false), cant_resume_reported(false), low_stock_reported(-1) {} int goalCount() { return config.ival(0); } @@ -349,6 +350,8 @@ public: config.ival(2) &= ~1; } + int curItemStock() { return goalByCount() ? item_count : item_amount; } + void init(const std::string &str) { config.val() = str; @@ -358,7 +361,7 @@ public: void computeRequest() { - int size = goalByCount() ? item_count : item_amount; + int size = curItemStock(); request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); } @@ -1323,6 +1326,20 @@ static void update_jobs_by_constraints(color_ostream &out) else if (ct->mat_mask.whole) info = bitfield_to_string(ct->mat_mask) + " " + info; + if (ct->low_stock_reported != DF_GLOBAL_VALUE(cur_season,-1)) + { + int count = ct->goalCount(), gap = ct->goalGap(); + + if (count >= gap*3 && ct->curItemStock() < std::min(gap*2, (count-gap)/2)) + { + ct->low_stock_reported = DF_GLOBAL_VALUE(cur_season,-1); + + Gui::showAnnouncement("Stock level is low: " + info, COLOR_BROWN, true); + } + else + ct->low_stock_reported = -1; + } + if (is_running != ct->is_active) { if (is_running && ct->request_resume) From b743f4f42db8f217c633d3dd7b31321b2dc5aa23 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 09:47:15 -0600 Subject: [PATCH 272/472] Autolabor: remove some debug spam, and fix an error in computing preference scoring --- plugins/autolabor.cpp | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index daa261da3..57b902ee3 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1521,9 +1521,6 @@ private: if (!bl->flags.bits.designated) continue; - if (print_debug) - out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); - for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { @@ -1614,8 +1611,6 @@ private: if (bld != -1) { - if (print_debug) - out.print("Checking job %d for first in queue at building %d\n", j->id, bld); df::building* b = binsearch_in_vector(world->buildings.all, bld); int fjid = -1; for (int jn = 0; jn < b->jobs.size(); jn++) @@ -1627,21 +1622,13 @@ private: } // check if this job is the first nonsuspended job on this building; if not, ignore it if (fjid != j->id) { - if (print_debug) - out.print("Job %d is not first in queue at building %d (%d), skipping\n", j->id, bld, fjid); continue; } - if (print_debug) - out.print("Job %d is in queue at building %d\n", j->id, bld); - } df::unit_labor labor = labor_mapper->find_job_labor (j); - if (print_debug) - out.print ("Job requiring labor %d found\n", labor); - if (labor != df::unit_labor::NONE) labor_needed[labor]++; } @@ -1805,22 +1792,16 @@ private: dwarf->has_axe = 1; if (state != IDLE) axe_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); } else if (weaponsk == df::job_skill::MINING) { dwarf->has_pick = 1; if (state != IDLE) pick_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); } else if (rangesk == df::job_skill::CROSSBOW) { dwarf->has_crossbow = 1; - if (print_debug) - out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); } } } @@ -1921,7 +1902,7 @@ public: } if (print_debug) - out.print("idle count = %d, labor need count = %d\n", available_dwarfs.size(), pq.size()); + out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); int canary = (1 << df::unit_labor::HAUL_STONE) | (1 << df::unit_labor::HAUL_WOOD) | @@ -1954,7 +1935,7 @@ public: int skill_level = Units::getEffectiveSkill(d->dwarf, skill); } - int score = skill_level * 100 - (d->high_skill - skill) * 100; + int score = skill_level * 100 - (d->high_skill - skill_level) * 100; if (d->dwarf->status.labors[labor]) score += 300; if ((labor == df::unit_labor::MINE && d->has_pick) || From 4cd217b782625df88ee82d2e6238587e0b538cc7 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 14:27:13 -0600 Subject: [PATCH 273/472] to new assignment algorithm. Add priority boost for labors based on how long it's been since they were last used, to avoid labor starvation. Move persistent configuration to "autolabor/2.0" to avoid conflicting with older versions. --- plugins/autolabor.cpp | 426 ++++++++++++++++++------------------------ 1 file changed, 177 insertions(+), 249 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 57b902ee3..3a88ef630 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1,6 +1,9 @@ -// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D +/* +* Autolabor 2.0 module for dfhack +* +* */ + -// some headers required for a plugin. Nothing special, just the basics. #include "Core.h" #include #include @@ -68,31 +71,6 @@ using df::global::world; #define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) -/* -* Autolabor module for dfhack -* -* The idea behind this module is to constantly adjust labors so that the right dwarves -* are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins -* a job it will finish that job even if the associated labor is removed. Thus the -* strategy is to frequently decide, for each labor, which dwarves should possibly take -* a new job for that labor if it comes in and which shouldn't, and then set the labors -* appropriately. The updating should happen as often as can be reasonably done without -* causing lag. -* -* The obvious thing to do is to just set each labor on a single idle dwarf who is best -* suited to doing new jobs of that labor. This works in a way, but it leads to a lot -* of idle dwarves since only one dwarf will be dispatched for each labor in an update -* cycle, and dwarves that finish tasks will wait for the next update before being -* dispatched. An improvement is to also set some labors on dwarves that are currently -* doing a job, so that they will immediately take a new job when they finish. The -* details of which dwarves should have labors set is mostly a heuristic. -* -* A complication to the above simple scheme is labors that have associated equipment. -* Enabling/disabling these labors causes dwarves to change equipment, and disabling -* them in the middle of a job may cause the job to be abandoned. Those labors -* (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. -*/ - static int enable_autolabor = 0; static bool print_debug = 0; @@ -116,12 +94,6 @@ DFHACK_PLUGIN("autolabor"); static void generate_labor_to_skill_map(); -enum labor_mode { - DISABLE, - HAULERS, - AUTOMATIC, -}; - enum dwarf_state { // Ready for a new task IDLE, @@ -387,136 +359,108 @@ struct labor_info { PersistentDataItem config; - bool is_exclusive; int active_dwarfs; - labor_mode mode() { return (labor_mode) config.ival(0); } - void set_mode(labor_mode mode) { config.ival(0) = mode; } - - int minimum_dwarfs() { return config.ival(1); } - void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(1) = minimum_dwarfs; } + int priority() { return config.ival(1); } + void set_priority(int priority) { config.ival(1) = priority; } int maximum_dwarfs() { return config.ival(2); } void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } + int time_since_last_assigned() + { + return (*df::global::cur_year - config.ival(3)) * 403200 + *df::global::cur_year_tick - config.ival(4); + } + void mark_assigned() { + config.ival(3) = (* df::global::cur_year); + config.ival(4) = (* df::global::cur_year_tick); + } + }; struct labor_default { - labor_mode mode; - bool is_exclusive; - int minimum_dwarfs; + int priority; int maximum_dwarfs; - int active_dwarfs; }; -static int hauler_pct = 33; - static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {AUTOMATIC, true, 2, 200, 0}, - /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, - /* CLEAN */ {HAULERS, false, 1, 200, 0}, - /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, - /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, - /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, - /* MASON */ {AUTOMATIC, false, 1, 200, 0}, - /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, - /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, - /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, - /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, - /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, - /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, - /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, - /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, - /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, - /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, - /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, - /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, - /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, - /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, - /* MILK */ {AUTOMATIC, false, 1, 200, 0}, - /* COOK */ {AUTOMATIC, false, 1, 200, 0}, - /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, - /* FISH */ {AUTOMATIC, false, 1, 1, 0}, - /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, - /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, - /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, - /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, - /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, - /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, - /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* DYER */ {AUTOMATIC, false, 1, 200, 0}, - /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, - /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, - /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, - /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, - /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, - /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, - /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, - /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) - /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, - /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} -}; - -static const int responsibility_penalties[] = { - 0, /* LAW_MAKING */ - 0, /* LAW_ENFORCEMENT */ - 3000, /* RECEIVE_DIPLOMATS */ - 0, /* MEET_WORKERS */ - 1000, /* MANAGE_PRODUCTION */ - 3000, /* TRADE */ - 1000, /* ACCOUNTING */ - 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ - 0, /* MAKE_INTRODUCTIONS */ - 0, /* MAKE_PEACE_AGREEMENTS */ - 0, /* MAKE_TOPIC_AGREEMENTS */ - 0, /* COLLECT_TAXES */ - 0, /* ESCORT_TAX_COLLECTOR */ - 0, /* EXECUTIONS */ - 0, /* TAME_EXOTICS */ - 0, /* RELIGION */ - 0, /* ATTACK_ENEMIES */ - 0, /* PATROL_TERRITORY */ - 0, /* MILITARY_GOALS */ - 0, /* MILITARY_STRATEGY */ - 0, /* UPGRADE_SQUAD_EQUIPMENT */ - 0, /* EQUIPMENT_MANIFESTS */ - 0, /* SORT_AMMUNITION */ - 0, /* BUILD_MORALE */ - 5000 /* HEALTH_MANAGEMENT */ + /* MINE */ {200, 0}, + /* HAUL_STONE */ {100, 0}, + /* HAUL_WOOD */ {100, 0}, + /* HAUL_BODY */ {200, 0}, + /* HAUL_FOOD */ {300, 0}, + /* HAUL_REFUSE */ {100, 0}, + /* HAUL_ITEM */ {100, 0}, + /* HAUL_FURNITURE */ {100, 0}, + /* HAUL_ANIMAL */ {100, 0}, + /* CLEAN */ {200, 0}, + /* CUTWOOD */ {200, 0}, + /* CARPENTER */ {200, 0}, + /* DETAIL */ {200, 0}, + /* MASON */ {200, 0}, + /* ARCHITECT */ {400, 0}, + /* ANIMALTRAIN */ {200, 0}, + /* ANIMALCARE */ {200, 0}, + /* DIAGNOSE */ {1000, 0}, + /* SURGERY */ {1000, 0}, + /* BONE_SETTING */ {1000, 0}, + /* SUTURING */ {1000, 0}, + /* DRESSING_WOUNDS */ {1000, 0}, + /* FEED_WATER_CIVILIANS */ {1000, 0}, + /* RECOVER_WOUNDED */ {200, 0}, + /* BUTCHER */ {200, 0}, + /* TRAPPER */ {200, 0}, + /* DISSECT_VERMIN */ {200, 0}, + /* LEATHER */ {200, 0}, + /* TANNER */ {200, 0}, + /* BREWER */ {200, 0}, + /* ALCHEMIST */ {200, 0}, + /* SOAP_MAKER */ {200, 0}, + /* WEAVER */ {200, 0}, + /* CLOTHESMAKER */ {200, 0}, + /* MILLER */ {200, 0}, + /* PROCESS_PLANT */ {200, 0}, + /* MAKE_CHEESE */ {200, 0}, + /* MILK */ {200, 0}, + /* COOK */ {200, 0}, + /* PLANT */ {200, 0}, + /* HERBALIST */ {200, 0}, + /* FISH */ {100, 0}, + /* CLEAN_FISH */ {200, 0}, + /* DISSECT_FISH */ {200, 0}, + /* HUNT */ {100, 0}, + /* SMELT */ {200, 0}, + /* FORGE_WEAPON */ {200, 0}, + /* FORGE_ARMOR */ {200, 0}, + /* FORGE_FURNITURE */ {200, 0}, + /* METAL_CRAFT */ {200, 0}, + /* CUT_GEM */ {200, 0}, + /* ENCRUST_GEM */ {200, 0}, + /* WOOD_CRAFT */ {200, 0}, + /* STONE_CRAFT */ {200, 0}, + /* BONE_CARVE */ {200, 0}, + /* GLASSMAKER */ {200, 0}, + /* EXTRACT_STRAND */ {200, 0}, + /* SIEGECRAFT */ {200, 0}, + /* SIEGEOPERATE */ {200, 0}, + /* BOWYER */ {200, 0}, + /* MECHANIC */ {200, 0}, + /* POTASH_MAKING */ {200, 0}, + /* LYE_MAKING */ {200, 0}, + /* DYER */ {200, 0}, + /* BURN_WOOD */ {200, 0}, + /* OPERATE_PUMP */ {200, 0}, + /* SHEARER */ {200, 0}, + /* SPINNER */ {200, 0}, + /* POTTERY */ {200, 0}, + /* GLAZING */ {200, 0}, + /* PRESSING */ {200, 0}, + /* BEEKEEPING */ {200, 1}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) + /* WAX_WORKING */ {200, 0}, + /* PUSH_HAUL_VEHICLES */ {200, 0} }; struct dwarf_info_t @@ -556,6 +500,12 @@ struct dwarf_info_t }; +/* +* Here starts all the complicated stuff to try to deduce labors from jobs. +* This is all way more complicated than it really ought to be, but I have +* not found a way to make it simpler. +*/ + static df::unit_labor hauling_labor_map[] = { df::unit_labor::HAUL_ITEM, /* BAR */ @@ -1232,6 +1182,8 @@ public: } }; +/* End of labor deducer */ + static JobLaborMapper* labor_mapper; static bool isOptionEnabled(unsigned flag) @@ -1262,14 +1214,13 @@ static void cleanup_state() static void reset_labor(df::unit_labor labor) { - labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); + labor_infos[labor].set_priority(default_labor_infos[labor].priority); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); - labor_infos[labor].set_mode(default_labor_infos[labor].mode); } static void init_state() { - config = World::GetPersistentData("autolabor/config"); + config = World::GetPersistentData("autolabor/2.0/config"); if (config.isValid() && config.ival(0) == -1) config.ival(0) = 0; @@ -1278,30 +1229,19 @@ static void init_state() if (!enable_autolabor) return; - auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); - if (cfg_haulpct.isValid()) - { - hauler_pct = cfg_haulpct.ival(0); - } - else - { - hauler_pct = 33; - } - // Load labors from save labor_infos.resize(ARRAY_COUNT(default_labor_infos)); std::vector items; - World::GetPersistentData(&items, "autolabor/labors/", true); + World::GetPersistentData(&items, "autolabor/2.0/labors/", true); for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); - df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/2.0/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; - labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; labor_infos[labor].active_dwarfs = 0; } } @@ -1312,11 +1252,10 @@ static void init_state() continue; std::stringstream name; - name << "autolabor/labors/" << i; + name << "autolabor/2.0/labors/" << i; labor_infos[i].config = World::AddPersistentData(name.str()); - - labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; + labor_infos[i].mark_assigned(); labor_infos[i].active_dwarfs = 0; reset_labor((df::unit_labor) i); } @@ -1358,7 +1297,7 @@ static void enable_plugin(color_ostream &out) { if (!config.isValid()) { - config = World::AddPersistentData("autolabor/config"); + config = World::AddPersistentData("autolabor/2.0/config"); config.ival(0) = 0; } @@ -1384,13 +1323,13 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector []\n" - " Set number of dwarves assigned to a labor.\n" - " autolabor haulers\n" - " Set a labor to be handled by hauler dwarves.\n" - " autolabor disable\n" - " Turn off autolabor for a specific labor.\n" - " autolabor reset\n" + " autolabor max \n" + " Set max number of dwarves assigned to a labor.\n" + " autolabor max none\n" + " Unrestrict the number of dwarves assigned to a labor.\n" + " autolabor priority \n" + " Change the assignment priority of a labor (default is 100)\n" + " autolabor reset \n" " Return a labor to the default handling.\n" " autolabor reset-all\n" " Return all labors to the default handling.\n" @@ -1400,19 +1339,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector items.all.size(); ++i) + auto& v = world->items.other[df::items_other_id::WEAPON]; + for (auto i = v.begin(); i != v.end(); i++) { - df::item* item = world->items.all[i]; + df::item* item = *i; if (item->flags.whole & bad_flags.whole) continue; @@ -1629,8 +1559,13 @@ private: df::unit_labor labor = labor_mapper->find_job_labor (j); - if (labor != df::unit_labor::NONE) + if (labor != df::unit_labor::NONE) + { labor_needed[labor]++; + + if (worker != -1) + labor_infos[labor].mark_assigned(); + } } } @@ -1897,8 +1832,16 @@ public: for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { + df::unit_labor l = i->first; + if (labor_infos[l].maximum_dwarfs() > 0 && + i->second > labor_infos[l].maximum_dwarfs()) + i->second = labor_infos[l].maximum_dwarfs(); if (i->second > 0) - pq.push(make_pair(100, i->first)); + { + int priority = labor_infos[l].priority(); + priority += labor_infos[l].time_since_last_assigned()/12; + pq.push(make_pair(priority, l)); + } } if (print_debug) @@ -2038,16 +1981,22 @@ void print_labor (df::unit_labor labor, color_ostream &out) out << labor_name << ": "; for (int i = 0; i < 20 - (int)labor_name.length(); i++) out << ' '; - if (labor_infos[labor].mode() == DISABLE) - out << "disabled" << endl; - else + out << "priority " << labor_infos[labor].priority() + << ", maximum " << labor_infos[labor].maximum_dwarfs() + << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; +} + +df::unit_labor lookup_labor_by_name (std::string& name) +{ + df::unit_labor labor = df::unit_labor::NONE; + + FOR_ENUM_ITEMS(unit_labor, test_labor) { - if (labor_infos[labor].mode() == HAULERS) - out << "haulers"; - else - out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs(); - out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + if (name == ENUM_KEY_STR(unit_labor, test_labor)) + labor = test_labor; } + + return labor; } @@ -2061,10 +2010,9 @@ command_result autolabor (color_ostream &out, std::vector & parame } if (parameters.size() == 1 && - (parameters[0] == "0" || parameters[0] == "enable" || - parameters[0] == "1" || parameters[0] == "disable")) + (parameters[0] == "enable" || parameters[0] == "disable")) { - bool enable = (parameters[0] == "1" || parameters[0] == "enable"); + bool enable = (parameters[0] == "enable"); if (enable && !enable_autolabor) { enable_plugin(out); @@ -2079,7 +2027,8 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_OK; } - else if (parameters.size() == 2 && parameters[0] == "haulpct") + else if (parameters.size() == 3 && + (parameters[0] == "max" || parameters[0] == "priority")) { if (!enable_autolabor) { @@ -2087,11 +2036,30 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - int pct = atoi (parameters[1].c_str()); - hauler_pct = pct; + df::unit_labor labor = lookup_labor_by_name(parameters[1]); + + if (labor == df::unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + + int v; + + if (parameters[2] == "none") + v = 0; + else + v = atoi (parameters[2].c_str()); + + if (parameters[0] == "max") + labor_infos[labor].set_maximum_dwarfs(v); + else if (parameters[0] == "priority") + labor_infos[labor].set_priority(v); + + print_labor(labor, out); return CR_OK; } - else if (parameters.size() == 2 || parameters.size() == 3) + else if (parameters.size() == 2 && parameters[0] == "reset") { if (!enable_autolabor) { @@ -2099,55 +2067,15 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_FAILURE; } - df::unit_labor labor = unit_labor::NONE; + df::unit_labor labor = lookup_labor_by_name(parameters[1]); - FOR_ENUM_ITEMS(unit_labor, test_labor) - { - if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor)) - labor = test_labor; - } - - if (labor == unit_labor::NONE) + if (labor == df::unit_labor::NONE) { out.printerr("Could not find labor %s.\n", parameters[0].c_str()); return CR_WRONG_USAGE; } - - if (parameters[1] == "haulers") - { - labor_infos[labor].set_mode(HAULERS); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "disable") - { - labor_infos[labor].set_mode(DISABLE); - print_labor(labor, out); - return CR_OK; - } - if (parameters[1] == "reset") - { - reset_labor(labor); - print_labor(labor, out); - return CR_OK; - } - - int minimum = atoi (parameters[1].c_str()); - int maximum = 200; - if (parameters.size() == 3) - maximum = atoi (parameters[2].c_str()); - - if (maximum < minimum || maximum < 0 || minimum < 0) - { - out.printerr("Syntax: autolabor []\n", maximum, minimum); - return CR_WRONG_USAGE; - } - - labor_infos[labor].set_minimum_dwarfs(minimum); - labor_infos[labor].set_maximum_dwarfs(maximum); - labor_infos[labor].set_mode(AUTOMATIC); + reset_labor(labor); print_labor(labor, out); - return CR_OK; } else if (parameters.size() == 1 && parameters[0] == "reset-all") @@ -2213,8 +2141,8 @@ command_result autolabor (color_ostream &out, std::vector & parame else { out.print("Automatically assigns labors to dwarves.\n" - "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" - "Current state: %d.\n", enable_autolabor); + "Activate with 'autolabor enable', deactivate with 'autolabor disable'.\n" + "Current state: %s.\n", enable_autolabor ? "enabled" : "disabled"); return CR_OK; } From 44bb965c97c81983f53edad71c32de52a84324d9 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 2 Dec 2012 18:41:20 -0600 Subject: [PATCH 274/472] Autolabor: add more building labors --- plugins/autolabor.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 3a88ef630..711701f78 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -735,6 +735,21 @@ private: break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: + case df::building_type::Cage: + return df::unit_labor::HAUL_FURNITURE; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", From 9563dae5d738950b4201b87194b7860ff2b8ceb6 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 01:41:02 -0600 Subject: [PATCH 275/472] Autolabor: add labors for construct bridge, construct nestbox, construct trap, deconstruct wagon; fix error in labor for deconstruct furnace/tradedepot/construction; actually update the "active dwarf" numbers displayed in "autolabor list"; increase assignment penalty for dwarfs using skills lower than their best skill; increase assignment bonus for continuing in the same labor and for having the right tool for the job. --- plugins/autolabor.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 711701f78..d880d3fcc 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -726,6 +726,7 @@ private: case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Construction: + case df::building_type::Bridge: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -749,7 +750,10 @@ private: case df::building_type::WindowGlass: case df::building_type::WindowGem: case df::building_type::Cage: + case df::building_type::NestBox: return df::unit_labor::HAUL_FURNITURE; + case df::building_type::Trap: + return df::unit_labor::MECHANIC; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", @@ -785,9 +789,10 @@ private: case df::building_type::Furnace: case df::building_type::TradeDepot: case df::building_type::Construction: + case df::building_type::Wagon: { df::building_actual* b = (df::building_actual*) bld; - return construction_build_labor(j->items[0]->item); + return construction_build_labor(b->contained_items[0]->item); } break; case df::building_type::FarmPlot: @@ -1784,6 +1789,14 @@ public: cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + + labor_infos[l].active_dwarfs = 0; + } + // scan for specific buildings of interest scan_buildings(); @@ -1893,13 +1906,13 @@ public: int skill_level = Units::getEffectiveSkill(d->dwarf, skill); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 100; + int score = skill_level * 100 - (d->high_skill - skill_level) * 200; if (d->dwarf->status.labors[labor]) - score += 300; + score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 300; + score += 500; if (score > best_score) { bestdwarf = k; @@ -1919,6 +1932,7 @@ public: if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) canary &= ~(1 << labor); + labor_infos[labor].active_dwarfs++; available_dwarfs.erase(bestdwarf); pq.pop(); From 208b9915eae8b8150fd4053c0a32b266e76b1ec3 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 04:28:08 -0600 Subject: [PATCH 276/472] Autolabor: splints and crutches are furniture (at least at a forge); remove test that excludes pet owners from being given jobs when they're idle; add test for hungry/thirsty dwarves to trigger a feed/water civilians requirement; add a vehicle hauling requirement based on the existence of hauling routes --- plugins/autolabor.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d880d3fcc..551e2098e 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -56,6 +56,7 @@ #include #include #include +#include #include @@ -1143,9 +1144,9 @@ public: job_to_labor_table[df::job_type::ConstructGrate] = jlf_make_furniture; job_to_labor_table[df::job_type::RemoveStairs] = jlf_const(df::unit_labor::MINE); job_to_labor_table[df::job_type::ConstructQuern] = jlf_make_furniture; - job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture ; - job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_object ; - job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_object; + job_to_labor_table[df::job_type::ConstructMillstone] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructSplint] = jlf_make_furniture; + job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; @@ -1422,6 +1423,8 @@ private: int cnt_traction; int cnt_crutch; + int need_food_water; + std::map labor_needed; std::vector dwarf_info; std::list available_dwarfs; @@ -1667,8 +1670,6 @@ private: { if (is_on_break) state = OTHER; - else if (dwarf->dwarf->specific_refs.size() > 0) - state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy else if (dwarf->dwarf->status2.able_grasp_impair == 0) @@ -1721,6 +1722,9 @@ private: cnt_crutch++; } + if (dwarf->dwarf->counters2.hunger_timer > 60000 || dwarf->dwarf->counters2.thirst_timer > 40000) + need_food_water++; + // find dwarf's highest effective skill int high_skill = 0; @@ -1788,6 +1792,7 @@ public: dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; + need_food_water = 0; FOR_ENUM_ITEMS(unit_labor, l) { @@ -1837,6 +1842,8 @@ public: labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; + // add entries for hauling jobs labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; @@ -1848,6 +1855,12 @@ public: labor_needed[df::unit_labor::HAUL_FURNITURE] += world->stockpile.num_jobs[8]; labor_needed[df::unit_labor::HAUL_ANIMAL] += world->stockpile.num_jobs[9]; + // add entries for vehicle hauling + + for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) + if ((*v)->route_id != -1) + labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) From 3953112eb936e7afddea09413bf6dfcefa795566 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 3 Dec 2012 19:03:07 +0100 Subject: [PATCH 277/472] dump Vegetation::t_plant, fix plant.is_burning --- library/include/modules/Vegetation.h | 22 ---------------------- library/modules/Vegetation.cpp | 25 ------------------------- plugins/plants.cpp | 4 ++-- 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h index 89ba5ff6c..f293ec52c 100644 --- a/library/include/modules/Vegetation.h +++ b/library/include/modules/Vegetation.h @@ -40,31 +40,9 @@ namespace Vegetation { const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years -// "Simplified" copy of plant -struct t_plant { - df::language_name name; - df::plant_flags flags; - int16_t material; - df::coord pos; - int32_t grow_counter; - uint16_t temperature_1; - uint16_t temperature_2; - int32_t is_burning; - int32_t hitpoints; - int16_t update_order; - //std::vector unk1; - //int32_t unk2; - //uint16_t temperature_3; - //uint16_t temperature_4; - //uint16_t temperature_5; - // Pointer to original object, in case you want to modify it - df::plant *origin; -}; - DFHACK_EXPORT bool isValid(); DFHACK_EXPORT uint32_t getCount(); DFHACK_EXPORT df::plant * getPlant(const int32_t index); -DFHACK_EXPORT bool copyPlant (const int32_t index, t_plant &out); } } #endif diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp index f7c4c9b0c..9b14a3cc0 100644 --- a/library/modules/Vegetation.cpp +++ b/library/modules/Vegetation.cpp @@ -58,28 +58,3 @@ df::plant * Vegetation::getPlant(const int32_t index) return NULL; return world->plants.all[index]; } - -bool Vegetation::copyPlant(const int32_t index, t_plant &out) -{ - if (uint32_t(index) >= getCount()) - return false; - - out.origin = world->plants.all[index]; - - out.name = out.origin->name; - out.flags = out.origin->flags; - out.material = out.origin->material; - out.pos = out.origin->pos; - out.grow_counter = out.origin->grow_counter; - out.temperature_1 = out.origin->temperature.whole; - out.temperature_2 = out.origin->temperature.fraction; - out.is_burning = out.origin->is_burning; - out.hitpoints = out.origin->hitpoints; - out.update_order = out.origin->update_order; - //out.unk1 = out.origin->anon_1; - //out.unk2 = out.origin->anon_2; - //out.temperature_3 = out.origin->temperature_unk; - //out.temperature_4 = out.origin->min_safe_temp; - //out.temperature_5 = out.origin->max_safe_temp; - return true; -} diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 5ab09868f..22e60c0d0 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -113,7 +113,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs if(shrubs && p->flags.bits.is_shrub || trees && !p->flags.bits.is_shrub) { if (what == do_immolate) - p->is_burning = true; + p->damage_flags.bits.is_burning = true; p->hitpoints = 0; destroyed ++; } @@ -136,7 +136,7 @@ static command_result immolations (color_ostream &out, do_what what, bool shrubs if(tree->pos.x == x && tree->pos.y == y && tree->pos.z == z) { if(what == do_immolate) - tree->is_burning = true; + tree->damage_flags.bits.is_burning = true; tree->hitpoints = 0; didit = true; break; From d9a5eefb9a4b7cedc4d24e2924a53224738da749 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 3 Dec 2012 21:48:23 +0200 Subject: [PATCH 278/472] gm-editor fix: cursor remembers its position. --- scripts/gui/gm-editor.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 3f2598948..54a2fc13d 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -253,7 +253,7 @@ function GmEditorUi:updateTarget(preserve_pos,reindex) if last_pos then self.subviews.list_main:setSelected(last_pos) else - self.subviews.list_main:setSelected(1) + self.subviews.list_main:setSelected(trg.selected) end end function GmEditorUi:pushTarget(target_to_push) @@ -261,6 +261,9 @@ function GmEditorUi:pushTarget(target_to_push) new_tbl.target=target_to_push new_tbl.keys={} new_tbl.selected=1 + if self:currentTarget()~=nil then + self:currentTarget().selected=self.subviews.list_main:getSelected() + end for k,v in pairs(target_to_push) do table.insert(new_tbl.keys,k) end From 3bce3838af2b61bf799b47f307cbbb7544e362a0 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 3 Dec 2012 21:49:17 +0200 Subject: [PATCH 279/472] Advfort now supports workshops and siege weapons >:) --- Readme.rst | 3 +- library/lua/dfhack/buildings.lua | 19 +- library/lua/dfhack/workshops.lua | 469 +++++++++++++++++++++++++++++++ library/lua/gui/buildings.lua | 21 +- scripts/gui/advfort.lua | 375 ++++++++++++------------ 5 files changed, 708 insertions(+), 179 deletions(-) create mode 100644 library/lua/dfhack/workshops.lua diff --git a/Readme.rst b/Readme.rst index 415f1a0e0..f2b1edc66 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2362,7 +2362,8 @@ keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments: * -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones). implies -a - + +* job - selects that job (e.g. Dig or FellTree) gui/gm-editor diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua index af9ad2605..49e1bd09e 100644 --- a/library/lua/dfhack/buildings.lua +++ b/library/lua/dfhack/buildings.lua @@ -334,7 +334,22 @@ local trap_inputs = { }, [df.trap_type.TrackStop] = { { flags2={ building_material=true, non_economic=true } } } } - +local siegeengine_input = { + [df.siegeengine_type.Catapult] = { + { + item_type=df.item_type.CATAPULTPARTS, + vector_id=df.job_item_vector_id.CATAPULTPARTS, + quantity=3 + } + }, + [df.siegeengine_type.Ballista] = { + { + item_type=df.item_type.BALLISTAPARTS, + vector_id=df.job_item_vector_id.BALLISTAPARTS, + quantity=3 + } + }, +} --[[ Functions for lookup in tables. ]] local function get_custom_inputs(custom) @@ -359,6 +374,8 @@ local function get_inputs_by_type(type,subtype,custom) end elseif type == df.building_type.Trap then return trap_inputs[subtype] + elseif type == df.building_type.SiegeEngine then + return siegeengine_input[subtype] else return building_inputs[type] end diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua new file mode 100644 index 000000000..42da3e766 --- /dev/null +++ b/library/lua/dfhack/workshops.lua @@ -0,0 +1,469 @@ +local _ENV = mkmodule('dfhack.workshops') + +local utils = require 'utils' + +input_filter_defaults = { + item_type = -1, + item_subtype = -1, + mat_type = -1, + mat_index = -1, + flags1 = {}, + -- Instead of noting those that allow artifacts, mark those that forbid them. + -- Leaves actually enabling artifacts to the discretion of the API user, + -- which is the right thing because unlike the game UI these filters are + -- used in a way that does not give the user a chance to choose manually. + flags2 = { allow_artifact = true }, + flags3 = {}, + flags4 = 0, + flags5 = 0, + reaction_class = '', + has_material_reaction_product = '', + metal_ore = -1, + min_dimension = -1, + has_tool_use = -1, + quantity = 1 +} +jobs_workshop={ + + [df.workshop_type.Jewelers]={ + { + name="cut gems", + items={{item_type=df.item_type.ROUGH,flags1={unrotten=true}}}, + job_fields={job_type=df.job_type.CutGems} + }, + { + name="encrust finished goods with gems", + items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}}, + job_fields={job_type=df.job_type.EncrustWithGems} + }, + { + name="encrust ammo with gems", + items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}}, + job_fields={job_type=df.job_type.EncrustWithGems} + }, + { + name="encrust furniture with gems", + items={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}}, + job_fields={job_type=df.job_type.EncrustWithGems} + }, + }, + [df.workshop_type.Fishery]={ + { + name="prepare raw fish", + items={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}}, + job_fields={job_type=df.job_type.PrepareRawFish} + }, + { + name="extract from raw fish", + items={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}}, + job_fields={job_type=df.job_type.ExtractFromRawFish} + }, + { + name="catch live fish", + items={}, + job_fields={job_type=df.job_type.CatchLiveFish} + }, -- no items? + }, + [df.workshop_type.Still]={ + { + name="brew drink", + items={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}}, + job_fields={job_type=df.job_type.BrewDrink} + }, + { + name="extract from plants", + items={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, + job_fields={job_type=df.job_type.ExtractFromPlants} + }, + --mead from raws? + }, + [df.workshop_type.Masons]={ + defaults={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,flags3={hard=true}},--flags2={non_economic=true}, + { + name="construct armor stand", + items={{}}, + job_fields={job_type=df.job_type.ConstructArmorStand} + }, + + { + name="construct blocks", + items={{}}, + job_fields={job_type=df.job_type.ConstructBlocks} + }, + { + name="construct throne", + items={{}}, + job_fields={job_type=df.job_type.ConstructThrone} + }, + { + name="construct coffin", + items={{}}, + job_fields={job_type=df.job_type.ConstructCoffin} + }, + { + name="construct door", + items={{}}, + job_fields={job_type=df.job_type.ConstructDoor} + }, + { + name="construct floodgate", + items={{}}, + job_fields={job_type=df.job_type.ConstructFloodgate} + }, + { + name="construct hatch cover", + items={{}}, + job_fields={job_type=df.job_type.ConstructHatchCover} + }, + { + name="construct grate", + items={{}}, + job_fields={job_type=df.job_type.ConstructGrate} + }, + { + name="construct cabinet", + items={{}}, + job_fields={job_type=df.job_type.ConstructCabinet} + }, + { + name="construct chest", + items={{}}, + job_fields={job_type=df.job_type.ConstructChest} + }, + { + name="construct statue", + items={{}}, + job_fields={job_type=df.job_type.ConstructStatue} + }, + { + name="construct slab", + items={{}}, + job_fields={job_type=df.job_type.ConstructSlab} + }, + { + name="construct table", + items={{}}, + job_fields={job_type=df.job_type.ConstructTable} + }, + { + name="construct weapon rack", + items={{}}, + job_fields={job_type=df.job_type.ConstructWeaponRack} + }, + { + name="construct quern", + items={{}}, + job_fields={job_type=df.job_type.ConstructQuern} + }, + { + name="construct millstone", + items={{}}, + job_fields={job_type=df.job_type.ConstructMillstone} + }, + }, + [df.workshop_type.Carpenters]={ + defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD}, + + { + name="make barrel", + items={{}}, + job_fields={job_type=df.job_type.MakeBarrel} + }, + + { + name="make bucket", + items={{}}, + job_fields={job_type=df.job_type.MakeBucket} + }, + { + name="make animal trap", + items={{}}, + job_fields={job_type=df.job_type.MakeAnimalTrap} + }, + { + name="make cage", + items={{}}, + job_fields={job_type=df.job_type.MakeCage} + }, + { + name="construct bed", + items={{}}, + job_fields={job_type=df.job_type.ConstructBed} + }, + { + name="construct bin", + items={{}}, + job_fields={job_type=df.job_type.ConstructBin} + }, + { + name="construct armor stand", + items={{}}, + job_fields={job_type=df.job_type.ConstructArmorStand} + }, + { + name="construct blocks", + items={{}}, + job_fields={job_type=df.job_type.ConstructBlocks} + }, + { + name="construct throne", + items={{}}, + job_fields={job_type=df.job_type.ConstructThrone} + }, + { + name="construct coffin", + items={{}}, + job_fields={job_type=df.job_type.ConstructCoffin} + }, + { + name="construct door", + items={{}}, + job_fields={job_type=df.job_type.ConstructDoor} + }, + { + name="construct floodgate", + items={{}}, + job_fields={job_type=df.job_type.ConstructFloodgate} + }, + { + name="construct hatch cover", + items={{}}, + job_fields={job_type=df.job_type.ConstructHatchCover} + }, + { + name="construct grate", + items={{}}, + job_fields={job_type=df.job_type.ConstructGrate} + }, + { + name="construct cabinet", + items={{}}, + job_fields={job_type=df.job_type.ConstructCabinet} + }, + { + name="construct chest", + items={{}}, + job_fields={job_type=df.job_type.ConstructChest} + }, + { + name="construct statue", + items={{}}, + job_fields={job_type=df.job_type.ConstructStatue} + }, + { + name="construct table", + items={{}}, + job_fields={job_type=df.job_type.ConstructTable} + }, + { + name="construct weapon rack", + items={{}}, + job_fields={job_type=df.job_type.ConstructWeaponRack} + }, + { + name="construct splint", + items={{}}, + job_fields={job_type=df.job_type.ConstructSplint} + }, + { + name="construct crutch", + items={{}}, + job_fields={job_type=df.job_type.ConstructCrutch} + }, + }, + [df.workshop_type.Kitchen]={ + --mat_type=2,3,4 + defaults={flags1={unrotten=true,cookable=true}}, + { + name="prepare easy meal", + items={{flags1={solid=true}},{}}, + job_fields={job_type=df.job_type.PrepareMeal,mat_type=2} + }, + { + name="prepare fine meal", + items={{flags1={solid=true}},{},{}}, + job_fields={job_type=df.job_type.PrepareMeal,mat_type=3} + }, + { + name="prepare lavish meal", + items={{flags1={solid=true}},{},{},{}}, + job_fields={job_type=df.job_type.PrepareMeal,mat_type=4} + }, + }, + [df.workshop_type.Butchers]={ + { + name="butcher an animal", + items={{flags1={butcherable=true,unrotten=true,nearby=true}}}, + job_fields={job_type=df.job_type.ButcherAnimal} + }, + { + name="extract from land animal", + items={{flags1={extract_bearing_vermin=true,unrotten=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}}, + job_fields={job_type=df.job_type.ExtractFromLandAnimal} + }, + { + name="catch live land animal", + items={}, + job_fields={job_type=df.job_type.CatchLiveLandAnimal} + }, + }, + [df.workshop_type.Mechanics]={ + { + name="construct mechanisms", + items={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1, + flags3={hard=true}}}, + job_fields={job_type=df.job_type.ConstructMechanisms} + }, + { + name="construct traction bench", + items={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}}, + job_fields={job_type=df.job_type.ConstructTractionBench} + }, + }, + [df.workshop_type.Loom]={ + { + name="weave plant thread cloth", + items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={plant=true}}}, + job_fields={job_type=df.job_type.WeaveCloth} + }, + { + name="weave silk thread cloth", + items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={silk=true}}}, + job_fields={job_type=df.job_type.WeaveCloth} + }, + { + name="weave yarn cloth", + items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={yarn=true}}}, + job_fields={job_type=df.job_type.WeaveCloth} + }, + { + name="weave inorganic cloth", + items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},mat_type=0}}, + job_fields={job_type=df.job_type.WeaveCloth} + }, + { + name="collect webs", + items={{item_type=df.item_type.THREAD,quantity=10,min_dimension=10,flags1={undisturbed=true}}}, + job_fields={job_type=df.job_type.CollectWebs} + }, + }, + [df.workshop_type.Leatherworks]={ + defaults={item_type=SKIN_TANNED}, + { + name="construct leather bag", + items={{}}, + job_fields={job_type=df.job_type.ConstructChest} + }, + { + name="construct waterskin", + items={{}}, + job_fields={job_type=df.job_type.MakeFlask} + }, + { + name="construct backpack", + items={{}}, + job_fields={job_type=df.job_type.MakeBackpack} + }, + { + name="construct quiver", + items={{}}, + job_fields={job_type=df.job_type.MakeQuiver} + }, + { + name="sew leather image", + items={{item_type=-1,flags1={empty=true},flags2={sewn_imageless=true}},{}}, + job_fields={job_type=df.job_type.SewImage} + }, + }, + [df.workshop_type.Dyers]={ + { + name="dye thread", + items={{item_type=df.item_type.THREAD,quantity=15000,min_dimension=15000,flags1={collected=true},flags2={dyeable=true}}, + {flags1={unrotten=true},flags2={dye=true}}}, + job_fields={job_type=df.job_type.DyeThread} + }, + { + name="dye cloth", + items={{item_type=df.item_type.CLOTH,quantity=10000,min_dimension=10000,flags2={dyeable=true}}, + {flags1={unrotten=true},flags2={dye=true}}}, + job_fields={job_type=df.job_type.DyeThread} + }, + }, + [df.workshop_type.Siege]={ + { + name="construct balista parts", + items={{item_type=df.item_type.WOOD}}, + job_fields={job_type=df.job_type.ConstructBallistaParts} + }, + { + name="construct catapult parts", + items={{item_type=df.item_type.WOOD}}, + job_fields={job_type=df.job_type.ConstructCatapultParts} + }, + { + name="assemble balista arrow", + items={{item_type=df.item_type.WOOD}}, + job_fields={job_type=df.job_type.AssembleSiegeAmmo} + }, + { + name="assemble tipped balista arrow", + items={{item_type=df.item_type.WOOD},{item_type=df.item_type.BALLISTAARROWHEAD}}, + job_fields={job_type=df.job_type.AssembleSiegeAmmo} + }, + }, +} +local function matchIds(bid1,wid1,cid1,bid2,wid2,cid2) + if bid1~=-1 and bid2~=-1 and bid1~=bid2 then + return false + end + if wid1~=-1 and wid2~=-1 and wid1~=wid2 then + return false + end + if cid1~=-1 and cid2~=-1 and cid1~=cid2 then + return false + end + return true +end +local function scanRawsReaction(buildingId,workshopId,customId) + local ret={} + for idx,reaction in ipairs(df.global.world.raws.reactions) do + for k,v in pairs(reaction.building.type) do + if matchIds(buildingId,workshopId,customId,v,reaction.building.subtype[k],reaction.building.custom[k]) then + table.insert(ret,reaction) + end + end + end + return ret +end +function getJobs(building_id,workshopId,customId) + local ret={} + local c_jobs + if building_id==df.building_type.Workshop then + c_jobs=jobs_workshop[workshopId] + else + return nil + end + for jobId,contents in pairs(c_jobs) do + if jobId~="defaults" then + local entry={} + entry.name=contents.name + local lclDefaults=utils.clone(input_filter_defaults,true) + if c_jobs.defaults ~=nil then + utils.assign(lclDefaults,c_jobs.defaults) + end + entry.items={} + for k,item in pairs(contents.items) do + entry.items[k]=utils.clone(lclDefaults,true) + utils.assign(entry.items[k],item) + end + if contents.job_fields~=nil then + entry.job_fields={} + utils.assign(entry.job_fields,contents.job_fields) + end + ret[jobId]=entry + end + end + --get jobs, add in from raws + return ret +end +return _ENV \ No newline at end of file diff --git a/library/lua/gui/buildings.lua b/library/lua/gui/buildings.lua index 98bee3be5..97bc884da 100644 --- a/library/lua/gui/buildings.lua +++ b/library/lua/gui/buildings.lua @@ -14,7 +14,7 @@ WORKSHOP_ABSTRACT={ } WORKSHOP_SPECIAL={ [df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true, - [df.building_type.Construction]=true + [df.building_type.Construction]=true,[df.building_type.SiegeEngine]=true } BuildingDialog = defclass(BuildingDialog, gui.FramedScreen) @@ -33,6 +33,7 @@ BuildingDialog.ATTRS{ use_tool_workshop=true, use_furnace = true, use_construction = true, + use_siege = true, use_trap = true, use_custom = true, building_filter = DEFAULT_NIL, @@ -116,13 +117,20 @@ function BuildingDialog:initBuiltinMode() cb = self:callback('initConstructionMode') }) end + if self.use_siege then + table.insert(choices, { + icon = ARROW, text = 'siege engine', key = 'CUSTOM_SHIFT_S', + cb = self:callback('initSiegeMode') + }) + end if self.use_custom then table.insert(choices, { icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U', cb = self:callback('initCustomMode') }) end - + + for i=0,df.building_type._last_item do if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then @@ -174,6 +182,15 @@ function BuildingDialog:initFurnaceMode() self:pushContext('Furnaces', choices) end +function BuildingDialog:initSiegeMode() + local choices = {} + + for i=0,df.siegeengine_type._last_item do + self:addBuilding(choices, df.siegeengine_type[i], df.building_type.SiegeEngine, i,-1,nil) + end + + self:pushContext('Siege weapons', choices) +end function BuildingDialog:initCustomMode() local choices = {} local raws=df.global.world.raws.buildings.all diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index f95387a4c..0ace5de0d 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -16,11 +16,16 @@ local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' local buildings=require 'dfhack.buildings' local bdialog=require 'gui.buildings' +local workshopJobs=require 'dfhack.workshops' local tile_attrs = df.tiletype.attrs settings={build_by_items=false,check_inv=false,df_assign=true} -for k,v in ipairs({...}) do + + + +local mode_name +for k,v in ipairs({...}) do --setting parsing if v=="-c" or v=="--cheat" then settings.build_by_items=true settings.df_assign=false @@ -29,6 +34,9 @@ for k,v in ipairs({...}) do settings.df_assign=false elseif v=="-a" or v=="--nodfassign" then settings.df_assign=false + else + mode_name=v + end end @@ -63,7 +71,7 @@ function showHelp() Disclaimer(helptext) require("gui.dialogs").showMessage("Help!?!",helptext) end - +--[[ low level job management ]]-- function getLastJobLink() local st=df.global.world.job_list while st.next~=nil do @@ -79,7 +87,7 @@ function addNewJob(job) newLink.item=job job.list_link=newLink end -function MakeJob(args) +function makeJob(args) local newJob=df.job:new() newJob.id=df.global.job_next_id df.global.job_next_id=df.global.job_next_id+1 @@ -89,29 +97,46 @@ function MakeJob(args) newJob.pos:assign(args.pos) args.job=newJob - local failed=false + local failed for k,v in ipairs(args.pre_actions or {}) do local ok,msg=v(args) if not ok then - failed=true - return false,msg + failed=msg + break end end - if not failed then + if failed==nil then AssignUnitToJob(newJob,args.unit,args.from_pos) + for k,v in ipairs(args.post_actions or {}) do + local ok,msg=v(args) + if not ok then + failed=msg + break + end + end + if failed then + UnassignJob(newJob,args.unit) + end end - for k,v in ipairs(args.post_actionss or {}) do - v(args) + if failed==nil then + addNewJob(newJob) + return newJob + else + newJob:delete() + return false,failed end - addNewJob(newJob) - return newJob + end +function UnassignJob(job,unit,unit_pos) + unit.job.current_job=nil +end function AssignUnitToJob(job,unit,unit_pos) job.general_refs:insert("#",{new=df.general_ref_unit_workerst,unit_id=unit.id}) unit.job.current_job=job unit_pos=unit_pos or {x=job.pos.x,y=job.pos.y,z=job.pos.z} unit.path.dest:assign(unit_pos) + return true end function SetCreatureRef(args) local job=args.job @@ -258,6 +283,7 @@ function AssignBuildingRef(args) local bld=dfhack.buildings.findAtTile(args.pos) args.job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=bld.id}) bld.jobs:insert("#",args.job) + return true end function BuildingChosen(inp_args,type_id,subtype_id,custom_id) @@ -289,8 +315,8 @@ function RemoveBuilding(args) return false,"No building to remove" end end -function isSuitableItem(job_item,item) +function isSuitableItem(job_item,item) if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then return false, "type" @@ -312,9 +338,16 @@ function isSuitableItem(job_item,item) end local matinfo=dfhack.matinfo.decode(item) --print(matinfo:getCraftClass()) + --print("Matching ",item," vs ",job_item) if not matinfo:matches(job_item) then return false,"matinfo" end + -- some bonus checks: + if job_item.flags2.building_material and not item:isBuildMat() then + return false,"non-build mat" + end + -- ***************** + --print("--Matched") --reagen_index?? reaction_id?? if job_item.metal_ore~=-1 and not item:isMetalOre(job_item.metal_ore) then return false,"metal ore" @@ -336,15 +369,25 @@ function isSuitableItem(job_item,item) end return true end -function AssignJobItems(args,is_workshop_job) - if settings.df_assign and not is_workshop_job then +function getItemsUncollected(job) + local ret={} + for id,jitem in pairs(job.items) do + local x,y,z=dfhack.items.getPosition(jitem.item) + if x~=job.pos.x or y~=job.pos.y or z~=job.pos.z then + table.insert(ret,jitem) + end + end + return ret +end +function AssignJobItems(args) + + if settings.df_assign then --use df default logic and hope that it would work return true end - --print("Assign") - --printall(args) + -- first find items that you want to use for the job local job=args.job local its=itemsAtPos(args.from_pos) - if settings.check_inv then + if settings.check_inv then --check inventory and contained items for k,v in pairs(args.unit.inventory) do table.insert(its,v.item) end @@ -363,19 +406,27 @@ function AssignJobItems(args,is_workshop_job) job.items[#job.items-1]:delete() job.items:erase(#job.items-1) end]] + local item_counts={} + for job_id, trg_job_item in ipairs(job.job_items) do + item_counts[job_id]=trg_job_item.quantity + printall(trg_job_item) + printall(trg_job_item.flags2) + end local used_item_id={} for job_id, trg_job_item in ipairs(job.job_items) do for _,cur_item in pairs(its) do if not used_item_id[cur_item.id] then - local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) + local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) --if msg then -- print(cur_item,msg) --end - if (trg_job_item.quantity>0 and item_suitable) or settings.build_by_items then - job.items:insert("#",{new=true,item=cur_item,role=2,job_item_idx=job_id}) - trg_job_item.quantity=trg_job_item.quantity-1 - --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),trg_job_item.quantity)) + + if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then + cur_item.flags.in_job=true + job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) + item_counts[job_id]=item_counts[job_id]-1 + print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) used_item_id[cur_item.id]=true end end @@ -384,28 +435,44 @@ function AssignJobItems(args,is_workshop_job) if not settings.build_by_items then for job_id, trg_job_item in ipairs(job.job_items) do - if trg_job_item.quantity>0 then + if item_counts[job_id]>0 then return false, "Not enough items for this job" end end end + local uncollected = getItemsUncollected(job) + if #uncollected == 0 then + job.flags.working=true + else + uncollected[1].is_fetching=1 + job.flags.fetching=true + end + --todo set working for workshops if items are present, else set fetching (and at least one item to is_fetching=1) + --job.flags.working=true return true end function AssignJobToBuild(args) local bld=dfhack.buildings.findAtTile(args.pos) args.job_type=df.job_type.ConstructBuilding if bld~=nil then - if #bld.jobs>0 then - args.job=bld.jobs[0] + for idx,job in pairs(bld.jobs) do + if job.job_type==df.job_type.ConstructBuilding then + args.job=job + break + end + end + + if args.job~=nil then local ok,msg=AssignJobItems(args) if not ok then return false,msg else - AssignUnitToJob(bld.jobs[0],args.unit,args.from_pos) --todo check if correct job type + AssignUnitToJob(args.job,args.unit,args.from_pos) end else - args.pre_actions={AssignJobItems} - local ok,msg=MakeJob(args) + local t={items=buildings.getFiltersByType({},bld:getType(),bld:getSubtype(),bld:getCustomType())} + args.pre_actions={dfhack.curry(setFiltersUp,t),AssignJobItems,AssignBuildingRef} + local ok,msg=makeJob(args) return ok,msg end else @@ -439,94 +506,8 @@ function ContinueJob(unit) unit.path.dest:assign(c_job.pos) end end -workshops={ - --[=[ - [df.workshop_type.Jewelers]={ - --TODO add material.IS_GEM - [df.job_type.CutGems]={{test={isMaterialGem},item_type=df.item_type.BOULDER}, - [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,finished_goods=true}}, - [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,ammo=true}}, - [df.job_type.EncrustWithGems]={{item_type=df.item_type.SMALLGEM},{flags1={improvable=true,furniture=true}}, - } - ]=] - [df.workshop_type.Fishery]={ - common={quantity=1}, - [df.job_type.PrepareRawFish]={{item_type=df.item_type.FISH_RAW,flags1={unrotten=true}}}, - [df.job_type.ExtractFromRawFish]={{flags1={unrotten=true,extract_bearing_fish=true}},{item_type=df.item_type.FLASK,flags1={empty=true,glass=true}}}, - [df.job_type.CatchLiveFish]={}, -- no items? - }, - [df.workshop_type.Still]={ - common={quantity=1}, - [df.job_type.BrewDrink]={{flags1={distillable=true},vector_id=22},{flags1={empty=true},flags3={food_storage=true}}}, - [df.job_type.ExtractFromPlants]={{item_type=df.item_type.PLANT,flags1={unrotten=true,extract_bearing_plant=true}},{item_type=df.item_type.FLASK,flags1={empty=true}}}, - }, - [df.workshop_type.Masons]={ - common={item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1,flags3={hard=true}},--flags2={non_economic=true}, - [df.job_type.ConstructArmorStand]={{}}, - [df.job_type.ConstructBlocks]={{}}, - [df.job_type.ConstructThrone]={{}}, - [df.job_type.ConstructCoffin]={{}}, - [df.job_type.ConstructDoor]={{}}, - [df.job_type.ConstructFloodgate]={{}}, - [df.job_type.ConstructHatchCover]={{}}, - [df.job_type.ConstructGrate]={{}}, - [df.job_type.ConstructCabinet]={{}}, - [df.job_type.ConstructChest]={{}}, - [df.job_type.ConstructStatue]={{}}, - [df.job_type.ConstructSlab]={{}}, - [df.job_type.ConstructTable]={{}}, - [df.job_type.ConstructWeaponRack]={{}}, - [df.job_type.ConstructQuern]={{}}, - [df.job_type.ConstructMillstone]={{}}, - }, - [df.workshop_type.Carpenters]={ - common={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD,quantity=1}, - - [df.job_type.MakeBarrel]={{}}, - --[[ from raws - [df.job_type.MakeShield]={{}}, - [df.job_type.MakeShield]={item_subtype=1,{}}, --buckler - [df.job_type.MakeWeapon]={item_subtype=23,{}}, --training spear -> from raws... - [df.job_type.MakeWeapon]={item_subtype=22,{}}, --training sword - [df.job_type.MakeWeapon]={item_subtype=21,{}}, --training axe - --]] - [df.job_type.ConstructBlocks]={{}}, - [df.job_type.MakeBucket]={{}}, - [df.job_type.MakeAnimalTrap]={{}}, - [df.job_type.MakeCage]={{}}, - [df.job_type.ConstructArmorStand]={{}}, - [df.job_type.ConstructBed]={{}}, - [df.job_type.ConstructThrone]={{}}, - [df.job_type.ConstructCoffin]={{}}, - [df.job_type.ConstructDoor]={{}}, - [df.job_type.ConstructFloodgate]={{}}, - [df.job_type.ConstructHatchCover]={{}}, - [df.job_type.ConstructGrate]={{}}, - [df.job_type.ConstructCabinet]={{}}, - [df.job_type.ConstructBin]={{}}, - [df.job_type.ConstructChest]={{}}, - [df.job_type.ConstructStatue]={{}}, - [df.job_type.ConstructSlab]={{}}, - [df.job_type.ConstructTable]={{}}, - [df.job_type.ConstructWeaponRack]={{}}, - --[[ from raws - [df.job_type.MakeTrapComponent]={item_subtype=1,{}}, --corkscrew - [df.job_type.MakeTrapComponent]={item_subtype=2,{}}, --ball - [df.job_type.MakeTrapComponent]={item_subtype=4,{}}, --spike - [df.job_type.MakeTool]={item_subtype=16,{}}, --minecart (?? maybe from raws?) - --]] - [df.job_type.ConstructSplint]={{}}, - [df.job_type.ConstructCrutch]={{}}, - }, - [df.workshop_type.Mechanics]={ - common={quantity=1}, - [df.job_type.ConstructMechanisms]={{item_type=df.item_type.BOULDER,item_subtype=-1,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,mat_index=-1,quantity=1, - flags3={hard=true}}}, -- flags2={non_economic=true} - [df.job_type.ConstructTractionBench]={{item_type=df.item_type.TABLE},{item_type=df.item_type.MECHANISM},{item_type=df.item_type.CHAIN}} - }, - } -dig_modes={ +actions={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMat}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMat}}, {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMat}}, @@ -541,39 +522,34 @@ dig_modes={ {"Fish" ,df.job_type.Fish,{IsWater}}, --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, - --{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, + {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, + {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, - --{"ConstructBlocks" ,df.job_type.ConstructBlocks,{},{AssignBuildingRef},{AddItemRefMason,AssignJobItems}}, - {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, - --{"LoadCageTrap" ,df.job_type.LoadCageTrap,{},{SetBuildingRef},{AssignJobItems}}, } - +for id,action in pairs(actions) do + if action[1]==mode_name then + mode=id-1 + break + end +end usetool=defclass(usetool,gui.Screen) usetool.focus_path = 'advfort' function usetool:getModeName() local adv=df.global.world.units.active[0] if adv.job.current_job then - return string.format("%s working(%d) ",(dig_modes[(mode or 0)+1][1] or ""),adv.job.current_job.completion_timer) + return string.format("%s working(%d) ",(actions[(mode or 0)+1][1] or ""),adv.job.current_job.completion_timer) else - return dig_modes[(mode or 0)+1][1] or " " + return actions[(mode or 0)+1][1] or " " end end -function usetool:isOnWorkshop() - local adv=df.global.world.units.active[0] - local bld=dfhack.buildings.findAtTile(adv.pos) - if bld and bld:getType()==df.building_type.Workshop and bld.construction_stage==3 then - return true,bld - else - return false - end -end + function usetool:init(args) self:addviews{ wid.Label{ @@ -589,14 +565,11 @@ function usetool:init(args) frame = {l=35,xalign=0,yalign=0}, visible=false, text={ - {gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop Mode",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} + {id="text1",gap=1,key=keybinds.workshop.key,key_sep="()", text="Workshop menu",pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}}} } } end -function usetool:onRenderBody(dc) - self:shopMode(self:isOnWorkshop()) - self:renderParent() -end + function usetool:onIdle() self._native.parent:logic() @@ -626,31 +599,19 @@ ALLOWED_KEYS={ function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} end -function usetool:onDismiss() - local adv=df.global.world.units.active[0] - --TODO: cancel job - --[[if adv and adv.job.current_job then - local cj=adv.job.current_job - adv.jobs.current_job=nil - cj:delete() - end]] -end function usetool:onHelp() showHelp() end -function setFiltersUp(common,specific,args) +function setFiltersUp(specific,args) local job=args.job - for k,v in pairs(specific) do - if type(k)=="string" then - job[k]=v - end + if specific.job_fields~=nil then + job:assign(specific.job_fields) end - for _,v in ipairs(specific) do - local filter - filter=require("utils").clone(common or {}) + --printall(specific) + for _,v in ipairs(specific.items) do + --printall(v) + local filter=v filter.new=true - require("utils").assign(filter,v) - --printall(filter) job.job_items:insert("#",filter) end return true @@ -659,8 +620,8 @@ function onWorkShopJobChosen(args,idx,choice) args.pos=args.from_pos args.job_type=choice.job_id args.post_actions={AssignBuildingRef} - args.pre_actions={dfhack.curry(setFiltersUp,args.common,choice.filter),function(args)return AssignJobItems(args,true) end} - local job,msg=MakeJob(args) + args.pre_actions={dfhack.curry(setFiltersUp,choice.filter),AssignJobItems} + local job,msg=makeJob(args) if not job then dfhack.gui.showAnnouncement(msg,5,1) end @@ -676,30 +637,79 @@ function onWorkShopJobChosen(args,idx,choice) --local ok,msg=AssignJobItems(args) --print(ok,msg) end +function siegeWeaponActionChosen(building,actionid) + local args + if actionid==1 then + building.facing=(building.facing+1)%4 + elseif actionid==2 then + local action=df.job_type.LoadBallista + if building:getSubtype()==df.siegeengine_type.Catapult then + action=df.job_type.LoadCatapult + end + args={} + args.job_type=action + args.unit=df.global.world.units.active[0] + local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} + args.from_pos=from_pos + args.pos=from_pos + args.pre_actions={dfhack.curry(setFiltersUp,{items={{}}})} + --issue a job... + elseif actionid==3 then + local action=df.job_type.FireBallista + if building:getSubtype()==df.siegeengine_type.Catapult then + action=df.job_type.FireCatapult + end + args={} + args.job_type=action + args.unit=df.global.world.units.active[0] + local from_pos={x=args.unit.pos.x,y=args.unit.pos.y, z=args.unit.pos.z} + args.from_pos=from_pos + args.pos=from_pos + --another job? + end + if args~=nil then + args.post_actions={AssignBuildingRef} + local ok,msg=makeJob(args) + if not ok then + dfhack.gui.showAnnouncement(msg,5,1) + end + end +end +function usetool:openSiegeWindow(building) + require("gui.dialogs").showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, + dfhack.curry(siegeWeaponActionChosen,building)) +end function usetool:openShopWindow(building) local adv=df.global.world.units.active[0] - - local shop_type=building:getSubtype() - - local filter_pile=workshops[shop_type] - + local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) if filter_pile then local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z} ,screen=self,bld=building,common=filter_pile.common} choices={} for k,v in pairs(filter_pile) do - if k~= "common" then - table.insert(choices,{job_id=k,text=df.job_type[k]:lower(),filter=v}) - - end + table.insert(choices,{job_id=0,text=v.name:lower(),filter=v}) end require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) ,nil, nil,true) end end -function usetool:shopMode(enable,wshop) +MODES={ + [df.building_type.Workshop]={ + name="Workshop menu", + input=usetool.openShopWindow, + }, + [df.building_type.SiegeEngine]={ + name="Siege menu", + input=usetool.openSiegeWindow, + }, +} +function usetool:shopMode(enable,mode,building) self.subviews.shopLabel.visible=enable - self.in_shop=wshop + if mode then + self.subviews.shopLabel:itemById("text1").text=mode.name + self.building=building + end + self.mode=mode end function usetool:shopInput(keys) if keys[keybinds.workshop.key] then @@ -708,7 +718,7 @@ function usetool:shopInput(keys) end function usetool:fieldInput(keys) local adv=df.global.world.units.active[0] - local cur_mode=dig_modes[(mode or 0)+1] + local cur_mode=actions[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do --print(code) @@ -736,7 +746,7 @@ function usetool:fieldInput(keys) if type(cur_mode[2])=="function" then ok,msg=cur_mode[2](state) else - ok,msg=MakeJob(state) + ok,msg=makeJob(state) --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) end @@ -770,24 +780,39 @@ function usetool:onInput(keys) CancelJob(adv) end elseif keys[keybinds.nextJob.key] then - mode=(mode+1)%#dig_modes + mode=(mode+1)%#actions elseif keys[keybinds.prevJob.key] then mode=mode-1 - if mode<0 then mode=#dig_modes-1 end + if mode<0 then mode=#actions-1 end --elseif keys.A_LOOK then -- self:sendInputToParent("A_LOOK") elseif keys[keybinds.continue.key] then ContinueJob(adv) self:sendInputToParent("A_WAIT") else - if self.in_shop then - self:shopInput(keys) - self:fieldInput(keys) + if self.mode~=nil then + if keys[keybinds.workshop.key] then + self.mode.input(self,self.building) + end + self:fieldInput(keys) else self:fieldInput(keys) end end end +function usetool:isOnBuilding() + local adv=df.global.world.units.active[0] + local bld=dfhack.buildings.findAtTile(adv.pos) + if bld and MODES[bld:getType()]~=nil and bld.construction_stage==3 then + return true,MODES[bld:getType()],bld + else + return false + end +end +function usetool:onRenderBody(dc) + self:shopMode(self:isOnBuilding()) + self:renderParent() +end if not (dfhack.gui.getCurFocus()=="dungeonmode/Look" or dfhack.gui.getCurFocus()=="dungeonmode/Default") then qerror("This script requires an adventurer mode with (l)ook or default mode.") end From 287ee2bc0413fb0ccf5e5566288465479d46f23c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 3 Dec 2012 14:20:57 -0600 Subject: [PATCH 280/472] Autolabor: allow multiple simultaneous jobs at farms. --- plugins/autolabor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 551e2098e..de4f2eb49 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1574,7 +1574,8 @@ private: break; } // check if this job is the first nonsuspended job on this building; if not, ignore it - if (fjid != j->id) { + // (except for farms) + if (fjid != j->id && b->getType() != df::building_type::FarmPlot) { continue; } From 0f1aaa6ec48d43e61f7889d97658b94f57965c67 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 4 Dec 2012 01:59:44 -0600 Subject: [PATCH 281/472] Autolabor: Items marked for dump now generate haul refuse demand. Also corrected labor for dump item jobs. --- plugins/autolabor.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index de4f2eb49..c231a4cf2 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -994,7 +994,7 @@ public: job_to_labor_table[df::job_type::Clean] = jlf_const(df::unit_labor::CLEAN); job_to_labor_table[df::job_type::Rest] = jlf_no_labor; job_to_labor_table[df::job_type::PickupEquipment] = jlf_no_labor; - job_to_labor_table[df::job_type::DumpItem] = jlf_hauling; + job_to_labor_table[df::job_type::DumpItem] = jlf_const(df::unit_labor::HAUL_REFUSE); job_to_labor_table[df::job_type::StrangeMoodCrafter] = jlf_no_labor; job_to_labor_table[df::job_type::StrangeMoodJeweller] = jlf_no_labor; job_to_labor_table[df::job_type::StrangeMoodForge] = jlf_no_labor; @@ -1515,10 +1515,14 @@ private: F(in_building); F(construction); F(artifact); #undef F - auto& v = world->items.other[df::items_other_id::WEAPON]; + auto& v = world->items.all; for (auto i = v.begin(); i != v.end(); i++) { df::item* item = *i; + + if (item->flags.bits.dump) + labor_needed[df::unit_labor::HAUL_REFUSE]++; + if (item->flags.whole & bad_flags.whole) continue; From 0b80dff09d0c0dbb6dacd469249e738e1cf25978 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 4 Dec 2012 17:18:09 +0100 Subject: [PATCH 282/472] ruby: add d-float support --- plugins/ruby/codegen.pl | 3 +++ plugins/ruby/ruby-autogen-defs.rb | 16 ++++++++++++++++ plugins/ruby/ruby.cpp | 13 +++++++++++++ 3 files changed, 32 insertions(+) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index ff69853af..9d76c6872 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -849,6 +849,9 @@ sub render_item_number { } elsif ($subtype eq 's-float') { push @lines_rb, 'float'; return; + } elsif ($subtype eq 'd-float') { + push @lines_rb, 'double'; + return; } else { print "no render number $subtype\n"; return; diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index a3e810178..e657962d5 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -35,6 +35,9 @@ module DFHack def float Float.new end + def double + Double.new + end def bit(shift, enum=nil) BitField.new(shift, 1, enum) end @@ -237,6 +240,19 @@ module DFHack _set(0.0) end end + class Double < MemStruct + def _get + DFHack.memory_read_double(@_memaddr) + end + + def _set(v) + DFHack.memory_write_double(@_memaddr, v) + end + + def _cpp_init + _set(0.0) + end + end class BitField < MemStruct attr_accessor :_shift, :_len, :_enum def initialize(shift, len, enum=nil) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index db94ad650..d75fa2402 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -578,6 +578,11 @@ static VALUE rb_dfmemory_read_float(VALUE self, VALUE addr) return rb_float_new(*(float*)rb_num2ulong(addr)); } +static VALUE rb_dfmemory_read_double(VALUE self, VALUE addr) +{ + return rb_float_new(*(double*)rb_num2ulong(addr)); +} + // memory writing (buffer) static VALUE rb_dfmemory_write(VALUE self, VALUE addr, VALUE raw) @@ -613,6 +618,12 @@ static VALUE rb_dfmemory_write_float(VALUE self, VALUE addr, VALUE val) return Qtrue; } +static VALUE rb_dfmemory_write_double(VALUE self, VALUE addr, VALUE val) +{ + *(double*)rb_num2ulong(addr) = rb_num2dbl(val); + return Qtrue; +} + // return memory permissions at address (eg "rx", nil if unmapped) static VALUE rb_dfmemory_check(VALUE self, VALUE addr) { @@ -968,12 +979,14 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "memory_read_int16", RUBY_METHOD_FUNC(rb_dfmemory_read_int16), 1); rb_define_singleton_method(rb_cDFHack, "memory_read_int32", RUBY_METHOD_FUNC(rb_dfmemory_read_int32), 1); rb_define_singleton_method(rb_cDFHack, "memory_read_float", RUBY_METHOD_FUNC(rb_dfmemory_read_float), 1); + rb_define_singleton_method(rb_cDFHack, "memory_read_double", RUBY_METHOD_FUNC(rb_dfmemory_read_double), 1); rb_define_singleton_method(rb_cDFHack, "memory_write", RUBY_METHOD_FUNC(rb_dfmemory_write), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int8", RUBY_METHOD_FUNC(rb_dfmemory_write_int8), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int16", RUBY_METHOD_FUNC(rb_dfmemory_write_int16), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_int32", RUBY_METHOD_FUNC(rb_dfmemory_write_int32), 2); rb_define_singleton_method(rb_cDFHack, "memory_write_float", RUBY_METHOD_FUNC(rb_dfmemory_write_float), 2); + rb_define_singleton_method(rb_cDFHack, "memory_write_double", RUBY_METHOD_FUNC(rb_dfmemory_write_double), 2); rb_define_singleton_method(rb_cDFHack, "memory_check", RUBY_METHOD_FUNC(rb_dfmemory_check), 1); rb_define_singleton_method(rb_cDFHack, "memory_patch", RUBY_METHOD_FUNC(rb_dfmemory_patch), 2); From 74ebe7d2070a4487f9f47377a1a992254167e298 Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 4 Dec 2012 17:46:13 +0100 Subject: [PATCH 283/472] ruby: add df-static-flagarray --- plugins/ruby/codegen.pl | 15 +++++++++-- plugins/ruby/ruby-autogen-defs.rb | 45 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/plugins/ruby/codegen.pl b/plugins/ruby/codegen.pl index 9d76c6872..03017a0f5 100755 --- a/plugins/ruby/codegen.pl +++ b/plugins/ruby/codegen.pl @@ -698,6 +698,8 @@ sub sizeof { return 12; } elsif ($subtype eq 'df-flagarray') { return 8; + } elsif ($subtype eq 'df-static-flagarray') { + return $field->getAttribute('count'); } elsif ($subtype eq 'df-array') { return 8; # XXX 6 ? } else { @@ -913,6 +915,7 @@ sub render_item_container { my $rbmethod = join('_', split('-', $subtype)); my $tg = $item->findnodes('child::ld:item')->[0]; my $indexenum = $item->getAttribute('index-enum'); + my $count = $item->getAttribute('count'); if ($tg) { if ($rbmethod eq 'df_linked_list') { @@ -929,11 +932,19 @@ sub render_item_container { elsif ($indexenum) { $indexenum = rb_ucase($indexenum); - push @lines_rb, "$rbmethod($indexenum)"; + if ($count) { + push @lines_rb, "$rbmethod($count, $indexenum)"; + } else { + push @lines_rb, "$rbmethod($indexenum)"; + } } else { - push @lines_rb, "$rbmethod"; + if ($count) { + push @lines_rb, "$rbmethod($count)"; + } else { + push @lines_rb, "$rbmethod"; + } } } diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index e657962d5..ffd68bf1e 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -78,6 +78,9 @@ module DFHack def df_flagarray(indexenum=nil) DfFlagarray.new(indexenum) end + def df_static_flagarray(len, indexenum=nil) + DfStaticFlagarray.new(len, indexenum) + end def df_array(tglen) DfArray.new(tglen, yield) end @@ -680,6 +683,48 @@ module DFHack include Enumerable end + class DfStaticFlagarray < MemStruct + attr_accessor :_indexenum + def initialize(len, indexenum) + @len = len*8 + @_indexenum = indexenum + end + def length + @len + end + def size ; length ; end + def [](idx) + idx = _indexenum.int(idx) if _indexenum + idx += length if idx < 0 + return if idx < 0 or idx >= length + byte = DFHack.memory_read_int8(@_memaddr + idx/8) + (byte & (1 << (idx%8))) > 0 + end + def []=(idx, v) + idx = _indexenum.int(idx) if _indexenum + idx += length if idx < 0 + if idx >= length or idx < 0 + raise 'index out of bounds' + else + byte = DFHack.memory_read_int8(@_memaddr + idx/8) + if (v == nil or v == false or v == 0) + byte &= 0xff ^ (1 << (idx%8)) + else + byte |= (1 << (idx%8)) + end + DFHack.memory_write_int8(@_memaddr + idx/8, byte) + end + end + def inspect + out = "#' + end + + include Enumerable + end class DfArray < Compound attr_accessor :_tglen, :_tg def initialize(tglen, tg) From f8d6b83088c1e74d0ceef1c2582a0757191e00ce Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 4 Dec 2012 20:23:19 -0600 Subject: [PATCH 284/472] Add 'allow fishing' and 'allow hunting' config options. Protect against accidentially trying to set or unset the NONE labor or any other invalid labor value (which corrupts DF). Add traction benches. Change prioritization around quite a bit. --- plugins/autolabor.cpp | 162 +++++++++++++++++++++++++++++------------- 1 file changed, 114 insertions(+), 48 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c231a4cf2..bad80a515 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -82,6 +82,8 @@ static PersistentDataItem config; enum ConfigFlags { CF_ENABLED = 1, + CF_ALLOW_FISHING = 2, + CF_ALLOW_HUNTING = 4, }; @@ -483,20 +485,26 @@ struct dwarf_info_t void set_labor(df::unit_labor labor) { - dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } } void clear_labor(df::unit_labor labor) { - dwarf->status.labors[labor] = false; - if ((labor == df::unit_labor::MINE && has_pick) || - (labor == df::unit_labor::CUTWOOD && has_axe) || - (labor == df::unit_labor::HUNT && has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; + if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) + { + dwarf->status.labors[labor] = false; + if ((labor == df::unit_labor::MINE && has_pick) || + (labor == df::unit_labor::CUTWOOD && has_axe) || + (labor == df::unit_labor::HUNT && has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } } }; @@ -752,6 +760,7 @@ private: case df::building_type::WindowGem: case df::building_type::Cage: case df::building_type::NestBox: + case df::building_type::TractionBench: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: return df::unit_labor::MECHANIC; @@ -1736,7 +1745,7 @@ private: FOR_ENUM_ITEMS (job_skill, skill) { - int skill_level = Units::getEffectiveSkill(dwarf->dwarf, skill); + int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); high_skill = std::max(high_skill, skill_level); } @@ -1866,6 +1875,14 @@ public: if ((*v)->route_id != -1) labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + // add fishing & hunting + + if (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) + labor_needed[df::unit_labor::FISH] ++; + + if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) + labor_needed[df::unit_labor::HUNT] ++; + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -1893,6 +1910,31 @@ public: if (print_debug) out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); + std::map to_assign; + + to_assign.clear(); + + int av = available_dwarfs.size(); + + while (!pq.empty() && av > 0) + { + df::unit_labor labor = pq.top().second; + int priority = pq.top().first; + to_assign[labor]++; + pq.pop(); + av--; + + if (print_debug) + out.print("Will assign: %s priority %d (%d)\n", ENUM_KEY_STR(unit_labor, labor).c_str(), priority, to_assign[labor]); + + if (--labor_needed[labor] > 0) + { + priority /= 2; + pq.push(make_pair(priority, labor)); + } + + } + int canary = (1 << df::unit_labor::HAUL_STONE) | (1 << df::unit_labor::HAUL_WOOD) | (1 << df::unit_labor::HAUL_BODY) | @@ -1902,63 +1944,65 @@ public: (1 << df::unit_labor::HAUL_FURNITURE) | (1 << df::unit_labor::HAUL_ANIMAL); - while (!available_dwarfs.empty() && !pq.empty()) + while (!available_dwarfs.empty()) { - df::unit_labor labor = pq.top().second; - int priority = pq.top().first; - df::job_skill skill = labor_to_skill[labor]; - - if (print_debug) - out.print("labor %s skill %s priority %d\n", ENUM_KEY_STR(unit_labor, labor).c_str(), ENUM_KEY_STR(job_skill, skill).c_str(), priority); - std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = -10000; + df::unit_labor best_labor = df::unit_labor::NONE; - for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) + for (auto j = to_assign.begin(); j != to_assign.end(); j++) { - dwarf_info_t* d = (*k); - int skill_level = 0; - if (skill != df::job_skill::NONE) - { - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - } + if (j->second <= 0) + continue; + + df::unit_labor labor = j->first; + df::job_skill skill = labor_to_skill[labor]; - int score = skill_level * 100 - (d->high_skill - skill_level) * 200; - if (d->dwarf->status.labors[labor]) - score += 500; - if ((labor == df::unit_labor::MINE && d->has_pick) || - (labor == df::unit_labor::CUTWOOD && d->has_axe) || - (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 500; - if (score > best_score) + for (std::list::iterator k = available_dwarfs.begin(); k != available_dwarfs.end(); k++) { - bestdwarf = k; - best_score = score; + dwarf_info_t* d = (*k); + int skill_level = 0; + if (skill != df::job_skill::NONE) + { + skill_level = Units::getEffectiveSkill(d->dwarf, skill); + } + int score = skill_level * 100 - (d->high_skill - skill_level) * 500; + if (d->dwarf->status.labors[labor]) + score += 500; + if ((labor == df::unit_labor::MINE && d->has_pick) || + (labor == df::unit_labor::CUTWOOD && d->has_axe) || + (labor == df::unit_labor::HUNT && d->has_crossbow)) + score += 500; + + if (score > best_score) + { + bestdwarf = k; + best_score = score; + best_labor = labor; + } } } if (print_debug) - out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), best_score); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); + FOR_ENUM_ITEMS(unit_labor, l) { - if (l == labor) + if (l == df::unit_labor::NONE) + continue; + + if (l == best_labor) (*bestdwarf)->set_labor(l); else (*bestdwarf)->clear_labor(l); } - if (labor >= df::unit_labor::HAUL_STONE && labor <= df::unit_labor::HAUL_ANIMAL) - canary &= ~(1 << labor); - labor_infos[labor].active_dwarfs++; - + if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) + canary &= ~(1 << best_labor); + labor_infos[best_labor].active_dwarfs++; + to_assign[best_labor]--; available_dwarfs.erase(bestdwarf); - pq.pop(); - if (--labor_needed[labor] > 0) - { - priority /= 2; - pq.push(make_pair(priority, labor)); - } } if (canary != 0) @@ -2125,6 +2169,28 @@ command_result autolabor (color_ostream &out, std::vector & parame print_labor(labor, out); return CR_OK; } + else if (parameters.size() == 1 && (parameters[0] == "allow-fishing" || parameters[0] == "forbid-fishing")) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_FISHING, (parameters[0] == "allow-fishing")); + return CR_OK; + } + else if (parameters.size() == 1 && (parameters[0] == "allow-hunting" || parameters[0] == "forbid-hunting")) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + setOptionEnabled(CF_ALLOW_HUNTING, (parameters[0] == "allow-hunting")); + return CR_OK; + } else if (parameters.size() == 1 && parameters[0] == "reset-all") { if (!enable_autolabor) From 0df60a0b4f48a9680bb53bcc7cae0a619f422efe Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 6 Dec 2012 01:38:43 -0600 Subject: [PATCH 285/472] Autolabor: slabs, animal trainers --- plugins/autolabor.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index bad80a515..56dc61111 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -57,6 +57,9 @@ #include #include #include +#include +#include +#include #include @@ -761,6 +764,7 @@ private: case df::building_type::Cage: case df::building_type::NestBox: case df::building_type::TractionBench: + case df::building_type::Slab: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: return df::unit_labor::MECHANIC; @@ -1162,7 +1166,7 @@ public: job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; - job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::STONE_CRAFT); + job_to_labor_table[df::job_type::EngraveSlab] = jlf_const(df::unit_labor::DETAIL); job_to_labor_table[df::job_type::ShearCreature] = jlf_const(df::unit_labor::SHEARER); job_to_labor_table[df::job_type::SpinThread] = jlf_const(df::unit_labor::SPINNER); job_to_labor_table[df::job_type::PenLargeAnimal] = jlf_no_labor; @@ -1873,7 +1877,7 @@ public: for (auto v = world->vehicles.all.begin(); v != world->vehicles.all.end(); v++) if ((*v)->route_id != -1) - labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; + labor_needed[df::unit_labor::PUSH_HAUL_VEHICLE]++; // add fishing & hunting @@ -1883,6 +1887,16 @@ public: if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) labor_needed[df::unit_labor::HUNT] ++; + /* add animal trainers */ + for (auto a = df::global::ui->equipment.training_assignments.begin(); + a != df::global::ui->equipment.training_assignments.end(); + a++) + { + labor_needed[df::unit_labor::ANIMALTRAIN]++; + // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. + } + + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) From cd6eb9edd38cf919ba07b158a56656731494a6e7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Dec 2012 12:00:18 +0400 Subject: [PATCH 286/472] If training ammo is forbidden for all use, don't move it to combat chests. --- plugins/fix-armory.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index efa9350ff..5a4821b4b 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -110,7 +110,8 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out) * 1. Combat ammo and ammo without any allowed use can be stored * in BOXes marked for Squad Equipment, either directly or via * containing room. No-allowed-use ammo is assumed to be reserved - * for emergency combat use, or something like that. + * for emergency combat use, or something like that; however if + * it is already stored in a training chest, it won't be moved. * 1a. If assigned to a squad position, that box can be used _only_ * for ammo assigned to that specific _squad_. Otherwise, if * multiple squads can use this room, they will store their @@ -158,8 +159,8 @@ static bool is_squad_ammo(df::item *item, df::squad *squad, bool combat, bool tr bool cs = spec->flags.bits.use_combat; bool ts = spec->flags.bits.use_training; - // no-use ammo assumed to be combat - if (((cs || !ts) && combat) || (ts && train)) + // no-use ammo assumed to fit any category + if (((cs || !ts) && combat) || ((ts || !cs) && train)) { if (binsearch_index(spec->assigned, item->id) >= 0) return true; From e1b70d171cacc74a58bbf94b0f1febe8b8cdc85d Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 11:00:19 +0100 Subject: [PATCH 287/472] ruby: tweak is_citizen test --- plugins/ruby/unit.rb | 52 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 4c638b1a9..13c3711b0 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -63,12 +63,54 @@ module DFHack } end + def unit_testflagcurse(u, flag) + return false if u.curse.rem_tags1.send(flag) + return true if u.curse.add_tags1.send(flag) + return false if u.caste < 0 + u.race_tg.caste[u.caste].flags[flag] + end + + def unit_isfortmember(u) + # RE from viewscreen_unitlistst ctor + return false if df.gamemode != :DWARF or + u.mood == :Berserk or + unit_testflagcurse(u, :CRAZED) or + unit_testflagcurse(u, :OPPOSED_TO_LIFE) or + u.unknown8.unk2 or + u.flags3.ghostly or + u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or + u.flags1.forest or + u.flags1.merchant or u.flags1.diplomat + return true if u.flags1.tame + return false if u.flags2.underworld or u.flags2.resident or + u.flags2.visitor_uninvited or u.flags2.visitor or + u.civ_id == -1 or + u.civ_id != df.ui.civ_id + true + end + + # return the page in viewscreen_unitlist where the unit would appear + def unit_category(u) + return if u.flags1.left or u.flags1.incoming + # return if hostile & unit_invisible(u) (hidden_in_ambush or caged+mapblock.hidden or caged+holder.ambush + return :Dead if u.flags1.dead + return :Dead if u.flags3.ghostly # hostile ? + return :Others if !unit_isfortmember(u) + casteflags = u.race_tg.caste[u.caste].flags if u.caste >= 0 + return :Livestock if casteflags and (casteflags[:PET] or casteflags[:PET_EXOTIC]) + return :Citizens if unit_testflagcurse(u, :CAN_SPEAK) + :Livestock + # some other stuff with ui.race_id ? (jobs only?) + end + def unit_iscitizen(u) - u.race == ui.race_id and u.civ_id == ui.civ_id and !u.flags1.dead and !u.flags1.merchant and !u.flags1.forest and - !u.flags1.diplomat and !u.flags2.resident and !u.flags3.ghostly and - !u.curse.add_tags1.OPPOSED_TO_LIFE and !u.curse.add_tags1.CRAZED and - u.mood != :Berserk - # TODO check curse ; currently this should keep vampires, but may include werebeasts + unit_category(u) == :Citizens + end + + def unit_ishostile(u) + unit_category(u) == :Others and + # TODO + true end # list workers (citizen, not crazy / child / inmood / noble) From 9a6eff0370b623f36ae4e4d5f428dc9bbbe20fe0 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 13:00:33 +0100 Subject: [PATCH 288/472] deathcause: allow selection from unitlist screen --- NEWS | 1 + scripts/deathcause.rb | 31 +++++++++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/NEWS b/NEWS index b8ad53830..9cb34fcb8 100644 --- a/NEWS +++ b/NEWS @@ -12,6 +12,7 @@ DFHack future - removebadthoughts: add --dry-run option - superdwarf: work in adventure mode too - tweak stable-cursor: carries cursor location from/to Build menu. + - deathcause: allow selection from the unitlist screen New tweaks: - tweak military-training: speed up melee squad training up to 10x (normally 3-5x). New scripts: diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index ab3e44a39..b85a85ac2 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -11,33 +11,40 @@ def display_death_event(e) end item = df.item_find(:selected) +unit = df.unit_find(:selected) if !item or !item.kind_of?(DFHack::ItemBodyComponent) item = df.world.items.other[:ANY_CORPSE].find { |i| df.at_cursor?(i) } end -if !item or !item.kind_of?(DFHack::ItemBodyComponent) - puts "Please select a corpse in the loo'k' menu" -else +if item and item.kind_of?(DFHack::ItemBodyComponent) hf = item.hist_figure_id - if hf == -1 - # TODO try to retrieve info from the unit (u = item.unit_tg) - puts "Not a historical figure, cannot death find info" +elsif unit + hf = unit.hist_figure_id +end + +if not hf + puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" + +elsif hf == -1 + # TODO try to retrieve info from the unit (u = item.unit_tg) + puts "Not a historical figure, cannot death find info" + +else + histfig = df.world.history.figures.binsearch(hf) + unit = histfig ? df.unit_find(histfig.unit_id) : nil + if unit and not unit.flags1.dead + puts "#{unit.name} is not dead yet !" + else events = df.world.history.events - found = false (0...events.length).reverse_each { |i| e = events[i] if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf display_death_event(e) - found = true break end } - if not found - u = item.unit_tg - puts "#{u.name} is not dead yet !" if u and not u.flags1.dead - end end end From 126c31684ec70e149436b3799326cd8aab5891fe Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 13:43:58 +0100 Subject: [PATCH 289/472] deathcause: ghosts are dead --- scripts/deathcause.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index b85a85ac2..73e29c890 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -33,7 +33,7 @@ elsif hf == -1 else histfig = df.world.history.figures.binsearch(hf) unit = histfig ? df.unit_find(histfig.unit_id) : nil - if unit and not unit.flags1.dead + if unit and not unit.flags1.dead and not unit.flags3.ghostly puts "#{unit.name} is not dead yet !" else From 885059c887c0da9b8d56b7e5fadc7740f505873f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Dec 2012 19:00:48 +0400 Subject: [PATCH 290/472] Add a script to expose the correct season to soundsense on world load. --- NEWS | 1 + dfhack.init-example | 7 +++++++ scripts/soundsense-season.lua | 26 ++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) create mode 100644 scripts/soundsense-season.lua diff --git a/NEWS b/NEWS index 9cb34fcb8..a0e01dba1 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,7 @@ DFHack future - embark: lets you embark anywhere. - lever: list and pull fort levers from the dfhack console. - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. + - soundsense-season: writes the correct season to gamelog.txt on world load. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/dfhack.init-example b/dfhack.init-example index 7617b9f6e..1a5aee48f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -137,6 +137,13 @@ tweak military-color-assigned # remove inverse dependency of squad training speed on unit list size and use more sparring tweak military-training +########### +# Scripts # +########### + +# write the correct season to gamelog on world load +soundsense-season + ####################################################### # Apply binary patches at runtime # # # diff --git a/scripts/soundsense-season.lua b/scripts/soundsense-season.lua new file mode 100644 index 000000000..6b7d43cfa --- /dev/null +++ b/scripts/soundsense-season.lua @@ -0,0 +1,26 @@ +-- On map load writes the current season to gamelog.txt + +local seasons = { + [0] = 'Spring', + [1] = 'Summer', + [2] = 'Autumn', + [3] = 'Winter', +} + +local args = {...} + +local function write_gamelog(msg) + local log = io.open('gamelog.txt', 'a') + log:write(msg.."\n") + log:close() +end + +if args[1] == 'disable' then + dfhack.onStateChange[_ENV] = nil +else + dfhack.onStateChange[_ENV] = function(op) + if op == SC_WORLD_LOADED then + write_gamelog(seasons[df.global.cur_season]..' has arrived on the calendar.') + end + end +end From fa9b71adc557fe426d6efca3806256d3f8cc5e01 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 6 Dec 2012 09:39:14 -0600 Subject: [PATCH 291/472] autolabor: add archery targets, improve JobLaborMapper's destructor --- plugins/autolabor.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 56dc61111..ddcd7c1ca 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -739,6 +739,7 @@ private: case df::building_type::TradeDepot: case df::building_type::Construction: case df::building_type::Bridge: + case df::building_type::ArcheryTarget: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -936,6 +937,9 @@ private: public: ~JobLaborMapper() { + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) + delete i->second; + delete jlf_hauling; delete jlf_make_furniture; delete jlf_make_object; From c174998fecefb56900192910a314a8ae900a004e Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 6 Dec 2012 18:27:54 +0200 Subject: [PATCH 292/472] Advfort: added ability to put items on table or in buildings that can hold them. --- scripts/gui/advfort.lua | 115 ++++++++++++++++++++++++++++++++-------- 1 file changed, 94 insertions(+), 21 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 0ace5de0d..a68e57d3e 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -8,7 +8,7 @@ down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, -workshop={key="CHANGETAB",desc="Show workshop jobs"}, +workshop={key="CHANGETAB",desc="Show building menu"}, } local gui = require 'gui' @@ -190,6 +190,13 @@ function NotConstruct(args) return false, "Can only do it on non constructions" end end +function NoConstructedBuilding(args) + local bld=dfhack.buildings.findAtTile(args.pos) + if bld and bld.construction_stage==3 then + return false, "Can only do it on clear area or non-finished buildings" + end + return true +end function IsBuilding(args) if dfhack.buildings.findAtTile(args.pos) then return true @@ -270,8 +277,8 @@ function IsUnit(args) end return false,"Unit must be present" end -function itemsAtPos(pos) - local ret={} +function itemsAtPos(pos,tbl) + local ret=tbl or {} for k,v in pairs(df.global.world.items.all) do if v.pos.x==pos.x and v.pos.y==pos.y and v.pos.z==pos.z and v.flags.on_ground then table.insert(ret,v) @@ -379,6 +386,41 @@ function getItemsUncollected(job) end return ret end +function AddItem(tbl,item,recurse) + table.insert(tbl,item) + if recurse then + local subitems=dfhack.items.getContainedItems(item) + if subitems~=nil then + for k,v in pairs(subitems) do + AddItem(tbl,v,recurse) + end + end + end +end +function EnumItems(args) + local ret=args.table or {} + if args.all then + for k,v in pairs(df.global.world.items.all) do + if v.flags.on_ground then + AddItem(ret,v,args.deep) + end + end + elseif args.pos~=nil then + for k,v in pairs(df.global.world.items.all) do + if v.pos.x==args.pos.x and v.pos.y==args.pos.y and v.pos.z==args.pos.z and v.flags.on_ground then + AddItem(ret,v,args.deep) + end + end + end + if args.unit~=nil then + for k,v in pairs(args.unit.inventory) do + if args.inv[v.mode] then + AddItem(ret,v.item,args.deep) + end + end + end + return ret +end function AssignJobItems(args) if settings.df_assign then --use df default logic and hope that it would work @@ -386,22 +428,9 @@ function AssignJobItems(args) end -- first find items that you want to use for the job local job=args.job - local its=itemsAtPos(args.from_pos) - if settings.check_inv then --check inventory and contained items - for k,v in pairs(args.unit.inventory) do - table.insert(its,v.item) - end - local contained={} - for k,v in pairs(its) do - local cc=dfhack.items.getContainedItems(v) - for _,c_item in pairs(cc) do - table.insert(contained,c_item) - end - end - for k,v in pairs(contained) do - table.insert(its,v) - end - end + local its=EnumItems{pos=args.from_pos,unit=args.unit, + inv={[df.unit_inventory_item.T_mode.Hauled]=settings.check_inv,[df.unit_inventory_item.T_mode.Worn]=settings.check_inv, + [df.unit_inventory_item.T_mode.Weapon]=settings.check_inv,},deep=true} --[[while(#job.items>0) do --clear old job items job.items[#job.items-1]:delete() job.items:erase(#job.items-1) @@ -528,7 +557,7 @@ actions={ {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, - {"Build" ,AssignJobToBuild}, + {"Build" ,AssignJobToBuild,{NoConstructedBuilding}}, } @@ -675,6 +704,26 @@ function siegeWeaponActionChosen(building,actionid) end end end +function putItemToBuilding(building,item) + if building:getType()==df.building_type.Table then + dfhack.items.moveToBuilding(item,building,0) + else + local container=building.contained_items[0].item --todo maybe iterate over all, add if usemode==2? + dfhack.items.moveToContainer(item,container) + end +end +function usetool:openPutWindow(building) + + local adv=df.global.world.units.active[0] + local items=EnumItems{pos=adv.pos,unit=adv, + inv={[df.unit_inventory_item.T_mode.Hauled]=true,[df.unit_inventory_item.T_mode.Worn]=true, + [df.unit_inventory_item.T_mode.Weapon]=true,},deep=true} + local choices={} + for k,v in pairs(items) do + table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) + end + require("gui.dialogs").showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) +end function usetool:openSiegeWindow(building) require("gui.dialogs").showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, dfhack.curry(siegeWeaponActionChosen,building)) @@ -694,6 +743,30 @@ function usetool:openShopWindow(building) end end MODES={ + [df.building_type.Table]={ --todo filters... + name="Put items", + input=usetool.openPutWindow, + }, + [df.building_type.Coffin]={ + name="Put items", + input=usetool.openPutWindow, + }, + [df.building_type.Box]={ + name="Put items", + input=usetool.openPutWindow, + }, + [df.building_type.Weaponrack]={ + name="Put items", + input=usetool.openPutWindow, + }, + [df.building_type.Armorstand]={ + name="Put items", + input=usetool.openPutWindow, + }, + [df.building_type.Cabinet]={ + name="Put items", + input=usetool.openPutWindow, + }, [df.building_type.Workshop]={ name="Workshop menu", input=usetool.openShopWindow, @@ -803,7 +876,7 @@ end function usetool:isOnBuilding() local adv=df.global.world.units.active[0] local bld=dfhack.buildings.findAtTile(adv.pos) - if bld and MODES[bld:getType()]~=nil and bld.construction_stage==3 then + if bld and MODES[bld:getType()]~=nil then return true,MODES[bld:getType()],bld else return false From ebc2625d970eb0ff6ec871ba9b3301a7004f00c5 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 6 Dec 2012 23:46:59 +0100 Subject: [PATCH 293/472] ditch the unused Vegetation module --- library/CMakeLists.txt | 2 - library/include/DFHack.h | 1 - library/include/ModuleFactory.h | 1 - library/include/modules/Maps.h | 1 - library/include/modules/Vegetation.h | 48 ---------------------- library/modules/Maps.cpp | 1 + library/modules/Vegetation.cpp | 60 ---------------------------- plugins/cleaners.cpp | 1 + plugins/devel/tiles.cpp | 1 - plugins/getplants.cpp | 2 +- plugins/liquids.cpp | 1 - plugins/mapexport/mapexport.cpp | 1 + plugins/plants.cpp | 8 ++-- plugins/prospector.cpp | 1 + plugins/tiletypes.cpp | 1 - 15 files changed, 10 insertions(+), 120 deletions(-) delete mode 100644 library/include/modules/Vegetation.h delete mode 100644 library/modules/Vegetation.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index f67b6fe44..784b54c90 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -121,7 +121,6 @@ include/modules/Materials.h include/modules/Notes.h include/modules/Screen.h include/modules/Translation.h -include/modules/Vegetation.h include/modules/Vermin.h include/modules/World.h include/modules/Graphic.h @@ -142,7 +141,6 @@ modules/Materials.cpp modules/Notes.cpp modules/Screen.cpp modules/Translation.cpp -modules/Vegetation.cpp modules/Vermin.cpp modules/World.cpp modules/Graphic.cpp diff --git a/library/include/DFHack.h b/library/include/DFHack.h index d606df94b..8a094cf86 100644 --- a/library/include/DFHack.h +++ b/library/include/DFHack.h @@ -61,7 +61,6 @@ distribution. #include "modules/Translation.h" #include "modules/World.h" #include "modules/Items.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" diff --git a/library/include/ModuleFactory.h b/library/include/ModuleFactory.h index 1f3d4222a..87c9a726f 100644 --- a/library/include/ModuleFactory.h +++ b/library/include/ModuleFactory.h @@ -33,7 +33,6 @@ namespace DFHack Module* createGui(); Module* createWorld(); Module* createMaterials(); - Module* createVegetation(); Module* createNotes(); Module* createGraphic(); } diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 632e8ec13..82f79e94b 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -32,7 +32,6 @@ distribution. #include "Export.h" #include "Module.h" -#include "modules/Vegetation.h" #include #include "BitArray.h" #include "modules/Materials.h" diff --git a/library/include/modules/Vegetation.h b/library/include/modules/Vegetation.h deleted file mode 100644 index f293ec52c..000000000 --- a/library/include/modules/Vegetation.h +++ /dev/null @@ -1,48 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - -#pragma once -#ifndef CL_MOD_VEGETATION -#define CL_MOD_VEGETATION -/** - * \defgroup grp_vegetation Vegetation : stuff that grows and gets cut down or trampled by dwarves - * @ingroup grp_modules - */ - -#include "Export.h" -#include "DataDefs.h" -#include "df/plant.h" - -namespace DFHack -{ -namespace Vegetation -{ -const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years - -DFHACK_EXPORT bool isValid(); -DFHACK_EXPORT uint32_t getCount(); -DFHACK_EXPORT df::plant * getPlant(const int32_t index); -} -} -#endif diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 363de8064..38f8bfb9f 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -59,6 +59,7 @@ using namespace std; #include "df/z_level_flags.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/plant.h" using namespace DFHack; using namespace df::enums; diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp deleted file mode 100644 index 9b14a3cc0..000000000 --- a/library/modules/Vegetation.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* -https://github.com/peterix/dfhack -Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com) - -This software is provided 'as-is', without any express or implied -warranty. In no event will the authors be held liable for any -damages arising from the use of this software. - -Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and -redistribute it freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must -not claim that you wrote the original software. If you use this -software in a product, an acknowledgment in the product documentation -would be appreciated but is not required. - -2. Altered source versions must be plainly marked as such, and -must not be misrepresented as being the original software. - -3. This notice may not be removed or altered from any source -distribution. -*/ - - -#include "Internal.h" - -#include -#include -#include -using namespace std; - -#include "VersionInfo.h" -#include "MemAccess.h" -#include "Types.h" -#include "Core.h" -using namespace DFHack; - -#include "modules/Vegetation.h" -#include "df/world.h" - -using namespace DFHack; -using df::global::world; - -bool Vegetation::isValid() -{ - return (world != NULL); -} - -uint32_t Vegetation::getCount() -{ - return world->plants.all.size(); -} - -df::plant * Vegetation::getPlant(const int32_t index) -{ - if (uint32_t(index) >= getCount()) - return NULL; - return world->plants.all[index]; -} diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 319b83c1f..1a52f8a17 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -12,6 +12,7 @@ #include "df/global_objects.h" #include "df/builtin_mats.h" #include "df/contaminant.h" +#include "df/plant.h" using std::vector; using std::string; diff --git a/plugins/devel/tiles.cpp b/plugins/devel/tiles.cpp index 1d30ca953..972b7fd0d 100644 --- a/plugins/devel/tiles.cpp +++ b/plugins/devel/tiles.cpp @@ -11,7 +11,6 @@ using std::string; #include #include #include -#include #include #include #include diff --git a/plugins/getplants.cpp b/plugins/getplants.cpp index 56c8457cc..eaa8077f2 100644 --- a/plugins/getplants.cpp +++ b/plugins/getplants.cpp @@ -11,8 +11,8 @@ #include "df/map_block.h" #include "df/tile_dig_designation.h" #include "df/plant_raw.h" +#include "df/plant.h" -#include "modules/Vegetation.h" #include using std::string; diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 6df530a92..15ae84c9b 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -37,7 +37,6 @@ using std::set; #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" diff --git a/plugins/mapexport/mapexport.cpp b/plugins/mapexport/mapexport.cpp index 6bc2d6fb2..9d1ba1c1d 100644 --- a/plugins/mapexport/mapexport.cpp +++ b/plugins/mapexport/mapexport.cpp @@ -13,6 +13,7 @@ using namespace google::protobuf::io; #include "DataDefs.h" #include "df/world.h" +#include "df/plant.h" #include "modules/Constructions.h" #include "proto/Map.pb.h" diff --git a/plugins/plants.cpp b/plugins/plants.cpp index 22e60c0d0..89a3257fa 100644 --- a/plugins/plants.cpp +++ b/plugins/plants.cpp @@ -9,17 +9,19 @@ #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" #include "modules/MapCache.h" +#include "df/plant.h" using std::vector; using std::string; using namespace DFHack; using df::global::world; +const uint32_t sapling_to_tree_threshold = 120 * 28 * 12 * 3; // 3 years + command_result df_grow (color_ostream &out, vector & parameters); command_result df_immolate (color_ostream &out, vector & parameters); command_result df_extirpate (color_ostream &out, vector & parameters); @@ -219,7 +221,7 @@ command_result df_grow (color_ostream &out, vector & parameters) if(tileShape(map.tiletypeAt(DFCoord(x,y,z))) == tiletype_shape::SAPLING && tileSpecial(map.tiletypeAt(DFCoord(x,y,z))) != tiletype_special::DEAD) { - tree->grow_counter = Vegetation::sapling_to_tree_threshold; + tree->grow_counter = sapling_to_tree_threshold; } break; } @@ -235,7 +237,7 @@ command_result df_grow (color_ostream &out, vector & parameters) df::tiletype ttype = map.tiletypeAt(df::coord(p->pos.x,p->pos.y,p->pos.z)); if(!p->flags.bits.is_shrub && tileShape(ttype) == tiletype_shape::SAPLING && tileSpecial(ttype) != tiletype_special::DEAD) { - p->grow_counter = Vegetation::sapling_to_tree_threshold; + p->grow_counter = sapling_to_tree_threshold; } } } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index 5eab897c0..efd457dfd 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -33,6 +33,7 @@ using namespace std; #include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" +#include "df/plant.h" using namespace DFHack; using namespace df::enums; diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 6af94f2ee..a48b1a385 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -34,7 +34,6 @@ using std::set; #include "Console.h" #include "Export.h" #include "PluginManager.h" -#include "modules/Vegetation.h" #include "modules/Maps.h" #include "modules/Gui.h" #include "TileTypes.h" From a1eeb02a1b0fc9ae13615089bdd314fe6402350b Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 7 Dec 2012 01:16:40 +0100 Subject: [PATCH 294/472] autocomplete command names from the console --- library/Core.cpp | 53 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index fd96d5601..7e9c90e98 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -343,6 +343,50 @@ command_result Core::runCommand(color_ostream &out, const std::string &command) return CR_NOT_IMPLEMENTED; } +static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed) +{ + std::vector possible; + + auto plug_mgr = Core::getInstance().getPluginManager(); + for(size_t i = 0; i < plug_mgr->size(); i++) + { + const Plugin * plug = (plug_mgr->operator[](i)); + for (size_t j = 0; j < plug->size(); j++) + { + const PluginCommand &pcmd = plug->operator[](j); + if (pcmd.isHotkeyCommand()) + continue; + if (pcmd.name.substr(0, first.size()) == first) + possible.push_back(pcmd.name); + } + } + + bool all = (first.find('/') != std::string::npos); + + std::map scripts; + listScripts(plug_mgr, scripts, Core::getInstance().getHackPath() + "scripts/", all); + for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) + if (iter->first.substr(0, first.size()) == first) + possible.push_back(iter->first); + + if (possible.size() == 1) + { + completed = possible[0]; + fprintf(stderr, "Autocompleted %s to %s\n", first.c_str(), completed.c_str()); + return true; + } + + if (possible.size() > 1 && possible.size() < 8) + { + std::string out; + for (size_t i = 0; i < possible.size(); i++) + out += " " + possible[i]; + con.print("Possible completions:%s\n", out.c_str()); + } + + return false; +} + command_result Core::runCommand(color_ostream &con, const std::string &first, vector &parts) { if (!first.empty()) @@ -665,10 +709,14 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve if(res == CR_NOT_IMPLEMENTED) { auto filename = getHackPath() + "scripts/" + first; + std::string completed; + if (fileExists(filename + ".lua")) res = runLuaScript(con, first, parts); else if (plug_mgr->eval_ruby && fileExists(filename + ".rb")) res = runRubyScript(con, plug_mgr, first, parts); + else if (try_autocomplete(con, first, completed)) + return runCommand(con, completed, parts); else con.printerr("%s is not a recognized command.\n", first.c_str()); } @@ -733,7 +781,6 @@ void fIOthread(void * iodata) { string command = ""; int ret = con.lineedit("[DFHack]# ",command, main_history); - fprintf(stderr,"Command: [%s]\n",command.c_str()); if(ret == -2) { cerr << "Console is shutting down properly." << endl; @@ -747,14 +794,10 @@ void fIOthread(void * iodata) else if(ret) { // a proper, non-empty command was entered - fprintf(stderr,"Adding command to history\n"); main_history.add(command); - fprintf(stderr,"Saving history\n"); main_history.save("dfhack.history"); } - fprintf(stderr,"Running command\n"); - auto rv = core->runCommand(con, command); if (rv == CR_NOT_IMPLEMENTED) From 99e9785826f9f12a513c4fc66d5d0ffe7bf1963d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Dec 2012 18:10:24 +0400 Subject: [PATCH 295/472] Add a script for inspecting screen tile parameters. --- scripts/devel/inspect-screen.lua | 101 +++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 scripts/devel/inspect-screen.lua diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua new file mode 100644 index 000000000..1cc24c976 --- /dev/null +++ b/scripts/devel/inspect-screen.lua @@ -0,0 +1,101 @@ +-- Read the tiles from the screen and display info about them. + +local utils = require 'utils' +local gui = require 'gui' + +InspectScreen = defclass(InspectScreen, gui.Screen) + +function InspectScreen:init(args) + local w,h = dfhack.screen.getWindowSize() + self.cursor_x = math.floor(w/2) + self.cursor_y = math.floor(h/2) +end + +function InspectScreen:computeFrame(parent_rect) + local sw, sh = parent_rect.width, parent_rect.height + self.cursor_x = math.max(0, math.min(self.cursor_x, sw-1)) + self.cursor_y = math.max(0, math.min(self.cursor_y, sh-1)) + + local frame = { w = 14, r = 1, h = 10, t = 1 } + if self.cursor_x > sw/2 then + frame = { w = 14, l = 1, h = 10, t = 1 } + end + + return gui.compute_frame_body(sw, sh, frame, 1, 0, false) +end + +function InspectScreen:onRenderFrame(dc, rect) + self:renderParent() + self.cursor_pen = dfhack.screen.readTile(self.cursor_x, self.cursor_y) + if gui.blink_visible(100) then + dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) + end + dc:fill(rect, {ch=' ',fg=COLOR_BLACK,bg=COLOR_CYAN}) +end + +local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} +local BG_PEN = {fg=COLOR_BLACK,bg=COLOR_WHITE,tile_color=true} +local TXT_PEN = {fg=COLOR_WHITE} + +function InspectScreen:onRenderBody(dc) + dc:pen(COLOR_WHITE, COLOR_CYAN) + if self.cursor_pen then + local info = self.cursor_pen + dc:string('CH: '):char(info.ch, FG_PEN):char(info.ch, BG_PEN):string(' '):string(''..info.ch,TXT_PEN):newline() + local fgcolor = info.fg + local fgstr = info.fg + if info.bold then + fgcolor = (fgcolor+8)%16 + fgstr = fgstr..'+8' + end + dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN):newline() + dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN):newline() + local bstring = 'false' + if info.bold then bstring = 'true' end + dc:string('Bold: '..bstring):newline():newline() + + if info.tile and gui.USE_GRAPHICS then + dc:string('TL: '):tile(' ', info.tile, FG_PEN):tile(' ', info.tile, BG_PEN):string(' '..info.tile):newline() + if info.tile_color then + dc:string('Color: true') + elseif info.tile_fg then + dc:string('FG: '):string('NN',{fg=info.tile_fg}):string(' '):string(''..info.tile_fg,TXT_PEN):newline() + dc:string('BG: '):string('NN',{fg=info.tile_bg}):string(' '):string(''..info.tile_bg,TXT_PEN):newline() + end + end + else + dc:string('Invalid', COLOR_LIGHTRED) + end +end + +local MOVEMENT_KEYS = { + CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, + CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, + CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, + CURSOR_DOWNLEFT = { -1, 1, 0 }, CURSOR_DOWNRIGHT = { 1, 1, 0 }, + CURSOR_UP_FAST = { 0, -1, 0, true }, CURSOR_DOWN_FAST = { 0, 1, 0, true }, + CURSOR_LEFT_FAST = { -1, 0, 0, true }, CURSOR_RIGHT_FAST = { 1, 0, 0, true }, + CURSOR_UPLEFT_FAST = { -1, -1, 0, true }, CURSOR_UPRIGHT_FAST = { 1, -1, 0, true }, + CURSOR_DOWNLEFT_FAST = { -1, 1, 0, true }, CURSOR_DOWNRIGHT_FAST = { 1, 1, 0, true }, +} + +function InspectScreen:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + else + for k,v in pairs(MOVEMENT_KEYS) do + if keys[k] then + local delta = 1 + if v[4] then + delta = 10 + end + self.cursor_x = self.cursor_x + delta*v[1] + self.cursor_y = self.cursor_y + delta*v[2] + self:updateLayout() + return + end + end + end +end + +InspectScreen{}:show() From 42670f0233daff17be5151482c9895a3936cce9c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 7 Dec 2012 15:41:39 -0600 Subject: [PATCH 296/472] Autolabor: only care about skills that are used for labors, when determining a dwarf's highest skill. --- plugins/autolabor.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ddcd7c1ca..1bf74f558 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1751,10 +1751,14 @@ private: int high_skill = 0; - FOR_ENUM_ITEMS (job_skill, skill) + FOR_ENUM_ITEMS (unit_labor, labor) { - int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); - high_skill = std::max(high_skill, skill_level); + df::job_skill skill = labor_to_skill[labor]; + if (skill != df::job_skill::NONE) + { + int skill_level = Units::getNominalSkill(dwarf->dwarf, skill, false); + high_skill = std::max(high_skill, skill_level); + } } dwarf->high_skill = high_skill; From 6fd306b5586aa1b90c83d6904fe5d1d65795b18e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 09:51:09 +0400 Subject: [PATCH 297/472] Add tiles colored separately by fg and bg in inspect-screen. --- scripts/devel/inspect-screen.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua index 1cc24c976..a4fbafbbc 100644 --- a/scripts/devel/inspect-screen.lua +++ b/scripts/devel/inspect-screen.lua @@ -48,8 +48,10 @@ function InspectScreen:onRenderBody(dc) fgcolor = (fgcolor+8)%16 fgstr = fgstr..'+8' end - dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN):newline() - dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN):newline() + dc:string('FG: '):string('NN',{fg=fgcolor}):string(' '):string(''..fgstr,TXT_PEN) + dc:seek(dc.width-1):char(info.ch,{fg=info.fg,bold=info.bold}):newline() + dc:string('BG: '):string('NN',{fg=info.bg}):string(' '):string(''..info.bg,TXT_PEN) + dc:seek(dc.width-1):char(info.ch,{fg=COLOR_BLACK,bg=info.bg}):newline() local bstring = 'false' if info.bold then bstring = 'true' end dc:string('Bold: '..bstring):newline():newline() From 7307f4e870d7b9381f5000242d7c2726ffaace44 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 09:51:35 +0400 Subject: [PATCH 298/472] Fix crash and confusing behavior in automaterial. --- plugins/automaterial.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index 9f383b935..6f613cf0e 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -148,8 +148,8 @@ static MaterialDescriptor get_material_in_list(size_t i) } else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) { - result.item_type = gen->item_type; - result.item_subtype = gen->item_subtype; + result.item_type = spec->candidate->getType(); + result.item_subtype = spec->candidate->getSubtype(); result.type = spec->candidate->getActualMaterial(); result.index = spec->candidate->getActualMaterialIndex(); result.valid = true; @@ -294,7 +294,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest { if (in_material_choice_stage()) { - if (!last_used_moved) + if (!last_used_moved && ui_build_selector->is_grouped) { if (auto_choose_materials && get_curr_constr_prefs().size() > 0) { @@ -304,7 +304,7 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest return; } } - else if (ui_build_selector->is_grouped) + else { last_used_moved = true; move_material_to_top(get_last_used_material()); From e7d3fbe97ba37e32513fcf1eaaf7e821e0e4bb5c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 02:42:22 -0600 Subject: [PATCH 299/472] Autolabor: track labors actually being used (to avoid "gone fishing" bug); fix several wrong labor map entries; add several special cases for hauling (still not all there yet); add debug warning if job deduction appears wrong; flail about mightily trying to resolve heap corruption on unload --- plugins/autolabor.cpp | 148 ++++++++++++++++++++++++++++-------------- 1 file changed, 98 insertions(+), 50 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 1bf74f558..d3e91a31d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -60,6 +60,7 @@ #include #include #include +#include #include @@ -482,6 +483,8 @@ struct dwarf_info_t int high_skill; + df::unit_labor using_labor; + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) { } @@ -521,9 +524,9 @@ struct dwarf_info_t static df::unit_labor hauling_labor_map[] = { df::unit_labor::HAUL_ITEM, /* BAR */ - df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_STONE, /* SMALLGEM */ df::unit_labor::HAUL_ITEM, /* BLOCKS */ - df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* ROUGH */ df::unit_labor::HAUL_STONE, /* BOULDER */ df::unit_labor::HAUL_WOOD, /* WOOD */ df::unit_labor::HAUL_FURNITURE, /* DOOR */ @@ -543,7 +546,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_FURNITURE, /* TABLE */ df::unit_labor::HAUL_FURNITURE, /* COFFIN */ df::unit_labor::HAUL_FURNITURE, /* STATUE */ - df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_REFUSE, /* CORPSE */ df::unit_labor::HAUL_ITEM, /* WEAPON */ df::unit_labor::HAUL_ITEM, /* ARMOR */ df::unit_labor::HAUL_ITEM, /* SHOES */ @@ -565,7 +568,7 @@ static df::unit_labor hauling_labor_map[] = df::unit_labor::HAUL_ITEM, /* BRACELET */ df::unit_labor::HAUL_ITEM, /* GEM */ df::unit_labor::HAUL_FURNITURE, /* ANVIL */ - df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* CORPSEPIECE */ df::unit_labor::HAUL_REFUSE, /* REMAINS */ df::unit_labor::HAUL_FOOD, /* MEAT */ df::unit_labor::HAUL_FOOD, /* FISH */ @@ -707,8 +710,31 @@ private: public: df::unit_labor get_labor(df::job* j) { - df::item* item = j->items[0]->item; - return hauling_labor_map[item->getType()]; + if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) + return (df::unit_labor) j->item_subtype; + + df::item* item; +// if (j->job_type == df::job_type::StoreItemInBarrel) +// item = j->items[1]->item; +// else + item = j->items[0]->item; + + if (item->flags.bits.container && item->getType() != df::item_type::BIN) + { + for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) + { + if ((*a)->getType() == df::general_ref_type::CONTAINS_ITEM) + { + int item_id = ((df::general_ref_contains_itemst *) (*a))->item_id; + item = binsearch_in_vector(world->items.all, item_id); + break; + } + } + } + df::unit_labor l = hauling_labor_map[item->getType()]; + if (l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + l = df::unit_labor::HAUL_BODY; + return l; } jlfunc_hauling() {}; }; @@ -931,29 +957,45 @@ private: return jlf; } private: - jlfunc *jlf_hauling, *jlf_make_furniture, *jlf_make_object, *jlf_make_armor, *jlf_make_weapon; - jlfunc *job_to_labor_table[ENUM_LAST_ITEM(job_type)+1]; + std::map job_to_labor_table; public: ~JobLaborMapper() { + std::set log; + for (auto i = jlf_cache.begin(); i != jlf_cache.end(); i++) - delete i->second; + { + if (!log.count(i->second)) + { + log.insert(i->second); + delete i->second; + } + i->second = 0; + } + + FOR_ENUM_ITEMS (job_type, j) + { + if (j < 0) + continue; - delete jlf_hauling; - delete jlf_make_furniture; - delete jlf_make_object; - delete jlf_make_armor; - delete jlf_make_weapon; + jlfunc* p = job_to_labor_table[j]; + if (!log.count(p)) + { + log.insert(p); + delete p; + } + job_to_labor_table[j] = 0; + } } JobLaborMapper() { - jlf_hauling = new jlfunc_hauling(); - jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); - jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); - jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); - jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + jlfunc* jlf_hauling = new jlfunc_hauling(); + jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); + jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); + jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); + jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); @@ -979,7 +1021,7 @@ public: job_to_labor_table[df::job_type::FillWaterskin] = jlf_no_labor; job_to_labor_table[df::job_type::FillWaterskin2] = jlf_no_labor; job_to_labor_table[df::job_type::Sleep] = jlf_no_labor; - job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::GLASSMAKER); + job_to_labor_table[df::job_type::CollectSand] = jlf_const(df::unit_labor::HAUL_ITEM); job_to_labor_table[df::job_type::Fish] = jlf_const(df::unit_labor::FISH); job_to_labor_table[df::job_type::Hunt] = jlf_const(df::unit_labor::HUNT); job_to_labor_table[df::job_type::HuntVermin] = jlf_no_labor; @@ -1002,7 +1044,7 @@ public: job_to_labor_table[df::job_type::StoreWeapon] = jlf_hauling; job_to_labor_table[df::job_type::StoreArmor] = jlf_hauling; job_to_labor_table[df::job_type::StoreItemInBarrel] = jlf_hauling; - job_to_labor_table[df::job_type::StoreItemInBin] = jlf_hauling; + job_to_labor_table[df::job_type::StoreItemInBin] = jlf_const(df::unit_labor::HAUL_ITEM); job_to_labor_table[df::job_type::SeekArtifact] = jlf_no_labor; job_to_labor_table[df::job_type::SeekInfant] = jlf_no_labor; job_to_labor_table[df::job_type::AttendParty] = jlf_no_labor; @@ -1205,16 +1247,9 @@ public: return df::unit_labor::NONE; } - df::job_skill skill; - df::unit_labor labor; - skill = ENUM_ATTR(job_type, skill, j->job_type); - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - else - labor = ENUM_ATTR(job_type, labor, j->job_type); - if (labor == df::unit_labor::NONE) - labor = job_to_labor_table[j->job_type]->get_labor(j); + df::unit_labor labor; + labor = job_to_labor_table[j->job_type]->get_labor(j); return labor; } @@ -1222,7 +1257,7 @@ public: /* End of labor deducer */ -static JobLaborMapper* labor_mapper; +static JobLaborMapper* labor_mapper = 0; static bool isOptionEnabled(unsigned flag) { @@ -1243,11 +1278,6 @@ static void setOptionEnabled(ConfigFlags flag, bool on) static void cleanup_state() { labor_infos.clear(); - if (labor_mapper) - { - delete labor_mapper; - labor_mapper = 0; - } } static void reset_labor(df::unit_labor labor) @@ -1298,11 +1328,6 @@ static void init_state() reset_labor((df::unit_labor) i); } - generate_labor_to_skill_map(); - - if (!labor_mapper) - labor_mapper = new JobLaborMapper(); - } static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; @@ -1382,6 +1407,10 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector ::iterator i = dwarf_info.begin(); - i != dwarf_info.end(); i++) - delete (*i); + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + delete (*d); + } } dwarf_info_t* add_dwarf(df::unit* u) @@ -1704,7 +1737,7 @@ private: } else { - int job = dwarf->dwarf->job.current_job->job_type; + df::job_type job = dwarf->dwarf->job.current_job->job_type; if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) state = dwarf_states[job]; else @@ -1712,6 +1745,20 @@ private: out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); state = OTHER; } + if (state == BUSY) + { + df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); + if (labor != df::unit_labor::NONE) + { + labor_needed[labor]--; + if (!dwarf->dwarf->status.labors[labor]) + { + out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", + dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, + ENUM_KEY_STR(job_type, job).c_str(), job, ENUM_KEY_STR(unit_labor, labor).c_str(), labor); + } + } + } } dwarf->state = state; @@ -1753,6 +1800,9 @@ private: FOR_ENUM_ITEMS (unit_labor, labor) { + if (labor == df::unit_labor::NONE) + continue; + df::job_skill skill = labor_to_skill[labor]; if (skill != df::job_skill::NONE) { @@ -1804,7 +1854,7 @@ private: } } - if ((state == IDLE || state == BUSY) && !dwarf->clear_all) + if ((state == IDLE) && !dwarf->clear_all) available_dwarfs.push_back(dwarf); } @@ -1815,6 +1865,8 @@ private: public: void process() { + dwarf_info.clear(); + dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; @@ -1904,7 +1956,6 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } - if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) @@ -2079,12 +2130,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) step_count = 0; debug_stream = &out; - AutoLaborManager alm(out); - alm.process(); - return CR_OK; } From 6ae82187d257c33252b9f3c269c2214d52ccd531 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 03:51:07 -0600 Subject: [PATCH 300/472] Autolabor: more tweaks to hauling labor decoding, fix heap corruption due to array underflow --- plugins/autolabor.cpp | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d3e91a31d..e7dc5692b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -710,16 +710,20 @@ private: public: df::unit_labor get_labor(df::job* j) { + df::item* item = 0; if (j->job_type == df::job_type::StoreItemInStockpile && j->item_subtype != -1) return (df::unit_labor) j->item_subtype; - df::item* item; -// if (j->job_type == df::job_type::StoreItemInBarrel) -// item = j->items[1]->item; -// else - item = j->items[0]->item; + for (auto i = j->items.begin(); i != j->items.end(); i++) + { + if ((*i)->role == 7) + { + item = (*i)->item; + break; + } + } - if (item->flags.bits.container && item->getType() != df::item_type::BIN) + if (item && item->flags.bits.container) { for (auto a = item->general_refs.begin(); a != item->general_refs.end(); a++) { @@ -731,8 +735,9 @@ private: } } } - df::unit_labor l = hauling_labor_map[item->getType()]; - if (l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) + + df::unit_labor l = item ? hauling_labor_map[item->getType()] : df::unit_labor::HAUL_ITEM; + if (item && l == df::unit_labor::HAUL_REFUSE && item->flags.bits.dead_dwarf) l = df::unit_labor::HAUL_BODY; return l; } @@ -1751,7 +1756,7 @@ private: if (labor != df::unit_labor::NONE) { labor_needed[labor]--; - if (!dwarf->dwarf->status.labors[labor]) + if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", dwarf->dwarf->name.first_name.c_str(), dwarf->dwarf->id, @@ -1986,7 +1991,7 @@ public: std::map to_assign; to_assign.clear(); - + int av = available_dwarfs.size(); while (!pq.empty() && av > 0) @@ -2058,7 +2063,7 @@ public: } if (print_debug) - out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); + out.print("assign \"%s\" labor %s score=%d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, best_labor).c_str(), best_score); FOR_ENUM_ITEMS(unit_labor, l) { @@ -2073,8 +2078,13 @@ public: if (best_labor >= df::unit_labor::HAUL_STONE && best_labor <= df::unit_labor::HAUL_ANIMAL) canary &= ~(1 << best_labor); - labor_infos[best_labor].active_dwarfs++; - to_assign[best_labor]--; + + if (best_labor != df::unit_labor::NONE) + { + labor_infos[best_labor].active_dwarfs++; + to_assign[best_labor]--; + } + available_dwarfs.erase(bestdwarf); } From 10667dfb9ee09c664959499257bca8cfbfed4e64 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 14:45:17 +0400 Subject: [PATCH 301/472] Make the inspect screen background look more sane on some tilesets. Namely where ' ' is not totally transparent. --- scripts/devel/inspect-screen.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/devel/inspect-screen.lua b/scripts/devel/inspect-screen.lua index a4fbafbbc..ae8334ad7 100644 --- a/scripts/devel/inspect-screen.lua +++ b/scripts/devel/inspect-screen.lua @@ -30,7 +30,7 @@ function InspectScreen:onRenderFrame(dc, rect) if gui.blink_visible(100) then dfhack.screen.paintTile({ch='X',fg=COLOR_LIGHTGREEN}, self.cursor_x, self.cursor_y) end - dc:fill(rect, {ch=' ',fg=COLOR_BLACK,bg=COLOR_CYAN}) + dc:fill(rect, {ch=' ',fg=COLOR_WHITE,bg=COLOR_CYAN}) end local FG_PEN = {fg=COLOR_WHITE,bg=COLOR_BLACK,tile_color=true} From a0e671d75d19a1a1728a7d0d2c4c174ce66acf60 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Dec 2012 20:39:57 +0400 Subject: [PATCH 302/472] Make rename unit reset the name if it becomes completely empty. --- library/modules/Translation.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 6f4ca2b04..90f8bbb81 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -115,6 +115,9 @@ void Translation::setNickname(df::language_name *name, std::string nick) if (!name->has_name) { + if (nick.empty()) + return; + *name = df::language_name(); name->language = 0; @@ -122,6 +125,18 @@ void Translation::setNickname(df::language_name *name, std::string nick) } name->nickname = nick; + + // If the nick is empty, check if this made the whole name empty + if (name->nickname.empty() && name->first_name.empty()) + { + bool has_words = false; + for (int i = 0; i < 7; i++) + if (name->words[i] >= 0) + has_words = true; + + if (!has_words) + name->has_name = false; + } } string Translation::TranslateName(const df::language_name * name, bool inEnglish, bool onlyLastPart) From 412a004751e1e2c1fb36d54fdf02821dedf2c282 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 10:55:44 -0600 Subject: [PATCH 303/472] Autolabor: identify labors that may involve going outside and apply an assignment penalty for such labors to dwarfs who have minor children (in order to keep the kids inside) --- plugins/autolabor.cpp | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e7dc5692b..541783354 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -483,9 +483,12 @@ struct dwarf_info_t int high_skill; + bool has_children; + df::unit_labor using_labor; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER), high_skill(0) + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), + state(OTHER), high_skill(0), has_children(false) { } @@ -1481,6 +1484,7 @@ private: int need_food_water; std::map labor_needed; + std::map labor_outside; std::vector dwarf_info; std::list available_dwarfs; @@ -1648,6 +1652,13 @@ private: if (worker != -1) labor_infos[labor].mark_assigned(); + + if (j->pos.isValid()) + { + df::tile_designation* d = Maps::getTileDesignation(j->pos); + if (d->bits.outside) + labor_outside[labor] = true; + } } } @@ -1707,6 +1718,21 @@ private: } } + // check to see if dwarf has minor children + + for (auto u2 = world->units.active.begin(); u2 != world->units.active.end(); ++u2) + { + if ((*u2)->relations.mother_id == dwarf->dwarf->id && + !(*u2)->flags1.bits.dead && + ((*u2)->profession == df::profession::CHILD || (*u2)->profession == df::profession::BABY)) + { + dwarf->has_children = true; + if (print_debug) + out.print("Dwarf %s has minor children\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + // Find the activity state for each dwarf bool is_on_break = false; @@ -1965,7 +1991,8 @@ public: { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second); + out.print ("labor_needed [%s] = %d, outside = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_outside[i->first]); } } @@ -2026,7 +2053,7 @@ public: { std::list::iterator bestdwarf = available_dwarfs.begin(); - int best_score = -10000; + int best_score = INT_MIN; df::unit_labor best_labor = df::unit_labor::NONE; for (auto j = to_assign.begin(); j != to_assign.end(); j++) @@ -2052,6 +2079,8 @@ public: (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) score += 500; + if (d->has_children && labor_outside[labor]) + score -= 5000; if (score > best_score) { From 72921fbfd513723f3a52fb06615083cbdc9f127c Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 8 Dec 2012 12:50:33 -0500 Subject: [PATCH 304/472] Made workNow only check jobs when the game becomes paused instead of constantly when paused. Also made it enable/disable on command. --- plugins/CMakeLists.txt | 1 + plugins/workNow.cpp | 44 +++++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..75da6f478 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(workNow workNow.cpp) endif() diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index acbb5bc30..906df33dc 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -8,34 +8,60 @@ #include using namespace std; - using namespace DFHack; DFHACK_PLUGIN("workNow"); static bool active = false; +DFhackCExport command_result workNow(color_ostream& out, vector& parameters); + +DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { + commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "When workNow is active, every time the game pauses, DF will make dwarves perform any appropriate available jobs. This includes when you one step through the game using the pause menu.\n" + "workNow 1\n" + " activate workNow\n" + "workNow 0\n" + " deactivate workNow\n")); + + return CR_OK; +} + DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { + active = false; return CR_OK; } -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - if ( !DFHack::Core::getInstance().getWorld()->ReadPauseState() ) +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event e) { + if ( !active ) + return CR_OK; + if ( e == DFHack::SC_WORLD_UNLOADED ) { + active = false; + return CR_OK; + } + if ( e != DFHack::SC_PAUSED ) return CR_OK; + *df::global::process_jobs = true; + return CR_OK; } -DFhackCExport command_result workNow(color_ostream& out, vector& parameters); -DFhackCExport command_result plugin_init(color_ostream& out, std::vector &commands) { - commands.push_back(PluginCommand("workNow", "makes dwarves look for jobs every time you pause", workNow, false, "Full help.")); - - return CR_OK; -} DFhackCExport command_result workNow(color_ostream& out, vector& parameters) { + if ( parameters.size() == 0 ) { + out.print("workNow status = %s\n", active ? "active" : "inactive"); + return CR_OK; + } + if ( parameters.size() > 1 ) { + return CR_WRONG_USAGE; + } + int32_t a = atoi(parameters[0].c_str()); + if (a < 0 || a > 1) + return CR_WRONG_USAGE; + + active = (bool)a; return CR_OK; } From 4f5fdebbe9251ee5b4699960773197ea4c4f2119 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 9 Dec 2012 00:53:03 +0200 Subject: [PATCH 305/472] furnaces added and custom reactions. --- library/lua/dfhack/workshops.lua | 124 ++++++++++++++++++++++++++++++- scripts/gui/advfort.lua | 10 ++- 2 files changed, 130 insertions(+), 4 deletions(-) diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua index 42da3e766..e34cf6da2 100644 --- a/library/lua/dfhack/workshops.lua +++ b/library/lua/dfhack/workshops.lua @@ -23,6 +23,68 @@ input_filter_defaults = { has_tool_use = -1, quantity = 1 } +local fuel={item_type=df.item_type.BAR,mat_type=df.builtin_mats.COAL} +jobs_furnace={ + [df.furnace_type.Smelter]={ + { + name="Melt metal object", + items={fuel,{flags2={allow_melt_dump=true}}},--also maybe melt_designated + job_fields={job_type=df.job_type.MeltMetalObject} + } + }, + [df.furnace_type.MagmaSmelter]={ + { + name="Melt metal object", + items={{flags2={allow_melt_dump=true}}},--also maybe melt_designated + job_fields={job_type=df.job_type.MeltMetalObject} + } + }, + --[[ [df.furnace_type.MetalsmithsForge]={ + unpack(concat(furnaces,mechanism,anvil,crafts,coins,flask)) + + }, + ]] + --MetalsmithsForge, + --MagmaForge + --[[ + forges: + weapons and ammo-> from raws... + armor -> raws + furniture -> builtins? + siege eq-> builtin (only balista head) + trap eq -> from raws+ mechanisms + other object-> anvil, crafts, goblets,toys,instruments,nestbox... (raws?) flask, coins,stud with iron + metal clothing-> raws??? + ]] + [df.furnace_type.GlassFurnace]={ + { + name="collect sand", + items={}, + job_fields={job_type=df.job_type.CollectSand} + }, + --glass crafts x3 + }, + [df.furnace_type.WoodFurnace]={ + defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD}, + { + name="make charcoal", + items={{}}, + job_fields={job_type=df.job_type.MakeCharcoal} + }, + { + name="make ash", + items={{}}, + job_fields={job_type=df.job_type.MakeAsh} + } + }, + [df.furnace_type.Kiln]={ + { + name="collect clay", + items={}, + job_fields={job_type=df.job_type.CollectClay} + } + }, +} jobs_workshop={ [df.workshop_type.Jewelers]={ @@ -162,6 +224,7 @@ jobs_workshop={ }, }, [df.workshop_type.Carpenters]={ + --training weapons, wooden shields defaults={item_type=df.item_type.WOOD,vector_id=df.job_item_vector_id.WOOD}, { @@ -435,14 +498,71 @@ local function scanRawsReaction(buildingId,workshopId,customId) end return ret end -function getJobs(building_id,workshopId,customId) +local function reagentToJobItem(reagent,react_id,reagentId) + local ret_item + ret_item=utils.clone_with_default(reagent, input_filter_defaults) + ret_item.reaction_id=react_id + ret_item.reagent_index=reagentId + return ret_item +end +local function addReactionJobs(ret,bid,wid,cid) + local reactions=scanRawsReaction(bid,wid or -1,cid or -1) + for idx,react in pairs(reactions) do + local job={name=react.name, + items={},job_fields={job_type=df.job_type.CustomReaction,reaction_name=react.code} + } + for reagentId,reagent in pairs(react.reagents) do + table.insert(job.items,reagentToJobItem(reagent,idx,reagentId)) + end + if react.flags.FUEL then + table.insert(job.items,fuel) + end + table.insert(ret,job) + end +end +local function scanRawsOres() + local ret={} + for idx,ore in ipairs(df.global.world.raws.inorganics) do + if #ore.metal_ore.mat_index~=0 then + ret[idx]=ore + end + end + return ret +end +local function addSmeltJobs(ret,use_fuel) + local ores=scanRawsOres() + for idx,ore in pairs(ores) do + print("adding:",ore.material.state_name.Solid) + printall(ore) + local job={name="smelt "..ore.material.state_name.Solid,job_fields={job_type=df.job_type.SmeltOre,mat_type=df.builtin_mats.INORGANIC,mat_index=idx},items={ + {item_type=df.item_type.BOULDER,mat_type=df.builtin_mats.INORGANIC,mat_index=idx,vector_id=df.job_item_vector_id.BOULDER}}} + if use_fuel then + table.insert(job.items,fuel) + end + table.insert(ret,job) + end + return ret +end +function getJobs(buildingId,workshopId,customId) local ret={} local c_jobs - if building_id==df.building_type.Workshop then + if buildingId==df.building_type.Workshop then c_jobs=jobs_workshop[workshopId] + elseif buildingId==df.building_type.Furnace then + c_jobs=jobs_furnace[workshopId] + + if workshopId == df.furnace_type.Smelter or workshopId == df.furnace_type.MagmaSmelter then + c_jobs=utils.clone(c_jobs,true) + addSmeltJobs(c_jobs,workshopId == df.furnace_type.Smelter) + end else return nil end + if c_jobs==nil then + return + end + c_jobs=utils.clone(c_jobs,true) + addReactionJobs(c_jobs,buildingId,workshopId,customId) for jobId,contents in pairs(c_jobs) do if jobId~="defaults" then local entry={} diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index a68e57d3e..2cee12954 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -454,7 +454,7 @@ function AssignJobItems(args) if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) - item_counts[job_id]=item_counts[job_id]-1 + item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) used_item_id[cur_item.id]=true end @@ -740,6 +740,8 @@ function usetool:openShopWindow(building) end require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) ,nil, nil,true) + else + qerror("No jobs for this workshop") end end MODES={ @@ -771,6 +773,10 @@ MODES={ name="Workshop menu", input=usetool.openShopWindow, }, + [df.building_type.Furnace]={ + name="Workshop menu", + input=usetool.openShopWindow, + }, [df.building_type.SiegeEngine]={ name="Siege menu", input=usetool.openSiegeWindow, @@ -876,7 +882,7 @@ end function usetool:isOnBuilding() local adv=df.global.world.units.active[0] local bld=dfhack.buildings.findAtTile(adv.pos) - if bld and MODES[bld:getType()]~=nil then + if bld and MODES[bld:getType()]~=nil and bld:getBuildStage()==bld:getMaxBuildStage() then return true,MODES[bld:getType()],bld else return false From bf0fe796ea0321e95b539c66ac40fe2744a06d93 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 9 Dec 2012 01:24:36 +0200 Subject: [PATCH 306/472] Added advfort to news. --- NEWS | 1 + 1 file changed, 1 insertion(+) diff --git a/NEWS b/NEWS index 65c647337..73ab7c5f3 100644 --- a/NEWS +++ b/NEWS @@ -29,6 +29,7 @@ DFHack future - gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/gm-editor: an universal editor for lots of dfhack things. - gui/companion-order: a adventure mode command interface for your companions. + - gui/advfort: a way to do jobs with your adventurer (e.g. build fort). New binary patches: - armorstand-capacity - custom-reagent-size From 468412b9fc9a39736f90da55be48b4358078b2b0 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 21:14:23 -0600 Subject: [PATCH 307/472] Autolabor: fix unitialized variable bug causing broker to be inappropriately excluded from work --- plugins/autolabor.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 541783354..5f66d3b69 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1762,6 +1762,8 @@ private: { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors dwarf->clear_all = true; + if (print_debug) + out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); } else state = IDLE; @@ -1795,7 +1797,7 @@ private: dwarf->state = state; if (print_debug) - out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); // determine if dwarf has medical needs if (dwarf->dwarf->health) @@ -1884,8 +1886,7 @@ private: dwarf->clear_labor(labor); } } - - if ((state == IDLE) && !dwarf->clear_all) + else if (state == IDLE) available_dwarfs.push_back(dwarf); } @@ -1903,6 +1904,8 @@ public: cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + trader_requested = false; + FOR_ENUM_ITEMS(unit_labor, l) { if (l == df::unit_labor::NONE) From 2018ac1d1751563f208399a0e898b535393a1228 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 8 Dec 2012 21:25:16 -0600 Subject: [PATCH 308/472] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 22b01b80a..506ab1e68 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit 506ab1e68d1522e2f282f134176b7da774f6a73c From a0a566dbf2666dec6c70d4ba24da2e4dd7ceb128 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 9 Dec 2012 13:00:49 +0200 Subject: [PATCH 309/472] Tidy up eventful.cpp --- plugins/eventful.cpp | 138 +++++++------------------------------------ 1 file changed, 20 insertions(+), 118 deletions(-) diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 6327583d6..038b11c9a 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -2,35 +2,15 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include "df/item_liquid_miscst.h" -#include "df/item_constructed.h" -#include "df/builtin_mats.h" +#include "df/unit.h" +#include "df/item.h" #include "df/world.h" -#include "df/job.h" -#include "df/job_item.h" -#include "df/job_item_ref.h" -#include "df/ui.h" -#include "df/report.h" #include "df/reaction.h" #include "df/reaction_reagent_itemst.h" #include "df/reaction_product_itemst.h" -#include "df/matter_state.h" -#include "df/contaminant.h" #include "MiscUtils.h" #include "LuaTools.h" @@ -47,7 +27,7 @@ using df::global::ui; typedef df::reaction_product_itemst item_product; -DFHACK_PLUGIN("reactionhooks"); +DFHACK_PLUGIN("eventful"); struct ReagentSource { int idx; @@ -96,94 +76,10 @@ static bool is_lua_hook(const std::string &name) return name.size() > 9 && memcmp(name.data(), "LUA_HOOK_", 9) == 0; } -static void find_material(int *type, int *index, df::item *input, MaterialSource &mat) -{ - if (input && mat.reagent) - { - MaterialInfo info(input); - - if (mat.product) - { - if (!info.findProduct(info, mat.product_name)) - { - color_ostream_proxy out(Core::getInstance().getConsole()); - out.printerr("Cannot find product '%s'\n", mat.product_name.c_str()); - } - } - - *type = info.type; - *index = info.index; - } - else - { - *type = mat.mat_type; - *index = mat.mat_index; - } -} - - /* * Hooks */ -typedef std::map > item_table; - -static void index_items(item_table &table, df::job *job, ReactionInfo *info) -{ - for (int i = job->items.size()-1; i >= 0; i--) - { - auto iref = job->items[i]; - if (iref->job_item_idx < 0) continue; - auto iitem = job->job_items[iref->job_item_idx]; - - if (iitem->contains.empty()) - { - table[iitem->reagent_index].push_back(iref->item); - } - else - { - std::vector contents; - Items::getContainedItems(iref->item, &contents); - - for (int j = contents.size()-1; j >= 0; j--) - { - for (int k = iitem->contains.size()-1; k >= 0; k--) - { - int ridx = iitem->contains[k]; - auto reag = info->react->reagents[ridx]; - - if (reag->matchesChild(contents[j], info->react, iitem->reaction_id)) - table[ridx].push_back(contents[j]); - } - } - } - } -} - -df::item* find_item(ReagentSource &info, item_table &table) -{ - if (!info.reagent) - return NULL; - if (table[info.idx].empty()) - return NULL; - return table[info.idx].back(); -} - - - -df::item* find_item( - ReagentSource &info, - std::vector *in_reag, - std::vector *in_items -) { - if (!info.reagent) - return NULL; - for (int i = in_items->size(); i >= 0; i--) - if ((*in_reag)[i] == info.reagent) - return (*in_items)[i]; - return NULL; -} - static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag , std::vector *out_items,bool *call_native){}; @@ -282,23 +178,29 @@ static void enable_hooks(bool enable) { INTERPOSE_HOOK(product_hook, produce).apply(enable); } - +static void world_specific_hooks(color_ostream &out,bool enable) +{ + if(enable && find_reactions(out)) + { + out.print("Detected reaction hooks - enabling plugin.\n"); + enable_hooks(true); + } + else + { + enable_hooks(false); + reactions.clear(); + products.clear(); + } +} DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { case SC_WORLD_LOADED: - if (find_reactions(out)) - { - out.print("Detected reaction hooks - enabling plugin.\n"); - enable_hooks(true); - } - else - enable_hooks(false); + world_specific_hooks(out,true); break; case SC_WORLD_UNLOADED: - enable_hooks(false); - reactions.clear(); - products.clear(); + world_specific_hooks(out,false); + break; default: break; From 6d0e505fd2335d7b8b16f5672f9d88fdcdc6e2ef Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 9 Dec 2012 14:15:39 +0200 Subject: [PATCH 310/472] Added onItemContaminate to eventful and readme/news for eventful --- NEWS | 4 +++- Readme.rst | 31 +++++++++++++++++++++++++++++++ plugins/eventful.cpp | 32 ++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index 73ab7c5f3..b0802b9be 100644 --- a/NEWS +++ b/NEWS @@ -55,7 +55,9 @@ DFHack future saving you from having to trawl through long lists of materials each time you place one. Dfusion plugin: Reworked to make use of lua modules, now all the scripts can be used from other scripts. - + New Eventful plugin: + A collection of lua events, that will allow new ways to interact with df world. + DFHack v0.34.11-r2 Internals: diff --git a/Readme.rst b/Readme.rst index f2b1edc66..325690ab2 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2576,3 +2576,34 @@ be bought from caravans. :) To be really useful this needs patches from bug 808, ``tweak fix-dimensions`` and ``tweak advmode-contained``. + +Eventful +======== + +This plugin exports some events to lua thus allowing to run lua functions +on DF world events. + +List of events +-------------- + +1. onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native) - auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. +2. onItemContaminateWound(item,unit,wound,number1,number2) - Is called when item tries to contaminate wound (e.g. stuck in) + +Examples +-------- +Spawn dragon breath on each item attempt to contaminate wound: +:: + + b=require "plugins.eventful" + b.onItemContaminateWound.one=function(item,unit,un_wound,x,y) + local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000) + end + +Reaction complete example" +:: + + b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native) + local pos=copyall(unit.pos) + dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) -- spawn dragonbreath after 100 ticks + call_native.value=false --do not call real item creation code + end diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 038b11c9a..267f01c29 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -7,6 +7,8 @@ #include #include "df/unit.h" #include "df/item.h" +#include "df/item_actual.h" +#include "df/unit_wound.h" #include "df/world.h" #include "df/reaction.h" #include "df/reaction_reagent_itemst.h" @@ -82,12 +84,14 @@ static bool is_lua_hook(const std::string &name) static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag , std::vector *out_items,bool *call_native){}; +static void handle_contaminate_wound(color_ostream &out,df::item_actual*,df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2){}; DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); - +DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t ); DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onReactionComplete), + DFHACK_LUA_EVENT(onItemContaminateWound), DFHACK_LUA_END }; @@ -120,7 +124,18 @@ struct product_hook : item_product { IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce); +struct item_hooks :df::item_actual { + typedef df::item_actual interpose_base; + DEFINE_VMETHOD_INTERPOSE(void, contaminateWound,(df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2)) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onItemContaminateWound(out,this,unit,wound,a1,a2); + INTERPOSE_NEXT(contaminateWound)(unit,wound,a1,a2); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(item_hooks, contaminateWound); /* @@ -176,22 +191,27 @@ static bool find_reactions(color_ostream &out) static void enable_hooks(bool enable) { - INTERPOSE_HOOK(product_hook, produce).apply(enable); + INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable); } static void world_specific_hooks(color_ostream &out,bool enable) { if(enable && find_reactions(out)) { out.print("Detected reaction hooks - enabling plugin.\n"); - enable_hooks(true); + INTERPOSE_HOOK(product_hook, produce).apply(true); } else { - enable_hooks(false); + INTERPOSE_HOOK(product_hook, produce).apply(false); reactions.clear(); products.clear(); } } +void disable_all_hooks(color_ostream &out) +{ + world_specific_hooks(out,false); + enable_hooks(false); +} DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { switch (event) { @@ -213,12 +233,12 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector Date: Sun, 9 Dec 2012 23:07:13 +0200 Subject: [PATCH 311/472] Removed debug spam, added support for "HAS_MATERIAL_REACTION_PRODUCT" type reactions. --- library/lua/dfhack/workshops.lua | 6 ++++-- scripts/gui/advfort.lua | 16 ++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/library/lua/dfhack/workshops.lua b/library/lua/dfhack/workshops.lua index e34cf6da2..eaad57de1 100644 --- a/library/lua/dfhack/workshops.lua +++ b/library/lua/dfhack/workshops.lua @@ -559,9 +559,11 @@ function getJobs(buildingId,workshopId,customId) return nil end if c_jobs==nil then - return + c_jobs={} + else + c_jobs=utils.clone(c_jobs,true) end - c_jobs=utils.clone(c_jobs,true) + addReactionJobs(c_jobs,buildingId,workshopId,customId) for jobId,contents in pairs(c_jobs) do if jobId~="defaults" then diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 2cee12954..3bf579594 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -324,6 +324,7 @@ function RemoveBuilding(args) end function isSuitableItem(job_item,item) + --todo butcher test if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then return false, "type" @@ -369,7 +370,16 @@ function isSuitableItem(job_item,item) end end if job_item.has_material_reaction_product~="" then - + local ok=false + for k,v in pairs(matinfo.material.reaction_product.id) do + if v.value==job_item.has_material_reaction_product then + ok=true + break + end + end + if not ok then + return false, "no material reaction product" + end end if job_item.reaction_class~="" then @@ -438,8 +448,6 @@ function AssignJobItems(args) local item_counts={} for job_id, trg_job_item in ipairs(job.job_items) do item_counts[job_id]=trg_job_item.quantity - printall(trg_job_item) - printall(trg_job_item.flags2) end local used_item_id={} for job_id, trg_job_item in ipairs(job.job_items) do @@ -455,7 +463,7 @@ function AssignJobItems(args) cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() - print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) + --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) used_item_id[cur_item.id]=true end end From bd368ea81c140ad43948e80cc32ec1d0a8321d87 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 10 Dec 2012 00:14:05 +0200 Subject: [PATCH 312/472] Added check for reaction class items --- scripts/gui/advfort.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 3bf579594..3fac0c646 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -382,7 +382,16 @@ function isSuitableItem(job_item,item) end end if job_item.reaction_class~="" then - + local ok=false + for k,v in pairs(matinfo.material.reaction_class) do + if v.value==job_item.reaction_class then + ok=true + break + end + end + if not ok then + return false, "no material reaction class" + end end return true end @@ -455,9 +464,9 @@ function AssignJobItems(args) if not used_item_id[cur_item.id] then local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) - --if msg then - -- print(cur_item,msg) - --end + if msg then + print(cur_item,msg) + end if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then cur_item.flags.in_job=true From e85f4eb880674a51fdf04ce7e5784a5ef70dfbe0 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 9 Dec 2012 21:18:29 -0500 Subject: [PATCH 313/472] First draft of autoSyndrome: a tool for replacing boiling rock syndromes with something more reliable. Uses non-df-recognized tags in material definition raws. --- plugins/CMakeLists.txt | 1 + plugins/autoSyndrome.cpp | 360 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 361 insertions(+) create mode 100644 plugins/autoSyndrome.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8aeeee8c3..ed4898112 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,6 +130,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) endif() diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp new file mode 100644 index 000000000..28a785adc --- /dev/null +++ b/plugins/autoSyndrome.cpp @@ -0,0 +1,360 @@ +#include "PluginManager.h" +#include "Export.h" +#include "DataDefs.h" +#include "Core.h" +#include "df/job.h" +#include "df/global_objects.h" +#include "df/ui.h" +#include "df/job_type.h" +#include "df/reaction.h" +#include "df/reaction_product.h" +#include "df/reaction_product_type.h" +#include "df/reaction_product_itemst.h" +#include "df/syndrome.h" +#include "df/unit_syndrome.h" +#include "df/unit.h" +#include "df/general_ref.h" +#include "df/general_ref_type.h" +#include "df/general_ref_unit_workerst.h" +#include "modules/Maps.h" +#include "modules/Job.h" + +#include +#include +#include +#include + +using namespace std; +using namespace DFHack; + +/* +Example usage: + +////////////////////////////////////////////// +//in file interaction_duck.txt +interaction_duck + +[OBJECT:INTERACTION] + +[INTERACTION:DUMMY_DUCK_INTERACTION] + [I_SOURCE:CREATURE_ACTION] + [I_TARGET:A:CREATURE] + [IT_LOCATION:CONTEXT_CREATURE] + [IT_MANUAL_INPUT:target] + [IT_IMMUNE_CREATURE:BIRD_DUCK:MALE] + [I_EFFECT:ADD_SYNDROME] + [IE_TARGET:A] + [IE_IMMEDIATE] + [SYNDROME] + [SYN_NAME:chronicDuckSyndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] +////////////////////////////////////////////// +//In file inorganic_duck.txt +inorganic_stone_duck + +[OBJECT:INORGANIC] + +[INORGANIC:DUCK_ROCK] +[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE] +[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] +[IS_STONE] +[SOLID_DENSITY:1][MELTING_POINT:25000] +[CAUSE_SYNDROME:chronicDuckSyndrome] +[BOILING_POINT:50000] +/////////////////////////////////////////////// +//In file building_duck.txt +building_duck + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:DUCK_WORKSHOP] + [NAME:Duck Workshop] + [NAME_COLOR:7:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BLOCK:1:0:0:0] + [TILE:0:1:236] + [COLOR:0:1:0:0:1] + [TILE:1:1:' '] + [COLOR:1:1:0:0:0] + [TILE:2:1:8] + [COLOR:2:1:0:0:1] + [TILE:3:1:8] + [COLOR:3:2:0:4:1] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE] + [BUILDMAT] + [WORTHLESS_STONE_ONLY] + [CAN_USE_ARTIFACT] +/////////////////////////////////////////////// +//In file reaction_duck.txt +reaction_duck + +[OBJECT:REACTION] + +[REACTION:DUCKIFICATION] +[NAME:become a duck] +[BUILDING:DUCK_WORKSHOP:NONE] +[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK] +////////////////////////////////////////////// +//Add the following lines to your entity in entity_default.txt (or wherever it is) + [PERMITTED_BUILDING:DUCK_WORKSHOP] + [PERMITTED_REACTION:DUCKIFICATION] +////////////////////////////////////////////// + +Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. +*/ + +const int32_t ticksPerYear = 403200; +int32_t lastRun = 0; +unordered_map prevJobs; +unordered_map jobWorkers; +bool enabled = true; + +DFHACK_PLUGIN("autoSyndrome"); + +command_result autoSyndrome(color_ostream& out, vector& parameters); +int32_t processJob(color_ostream& out, int32_t id); +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream& out) { + if ( !enabled ) + return CR_OK; + if(DFHack::Maps::IsValid() == false) { + return CR_OK; + } + + //don't run more than once per tick + int32_t time = (*df::global::cur_year)*ticksPerYear + (*df::global::cur_year_tick); + if ( time <= lastRun ) + return CR_OK; + lastRun = time; + + //keep track of all queued jobs. When one completes (and is not cancelled), check if it's a boiling rock job, and if so, give the worker the appropriate syndrome + unordered_map jobs; + df::job_list_link* link = &df::global::world->job_list; + for( ; link != NULL; link = link->next ) { + df::job* item = link->item; + if ( item == NULL ) + continue; + //-1 usually means it hasn't been assigned yet. + if ( item->completion_timer < 0 ) + continue; + //if the completion timer is more than one, then the job will never disappear next tick unless it's cancelled + if ( item->completion_timer > 1 ) + continue; + + //only consider jobs that have been started + int32_t workerId = -1; + for ( size_t a = 0; a < item->references.size(); a++ ) { + if ( item->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)item->references[a])->unit_id; + } + if ( workerId == -1 ) + continue; + + jobs[item->id] = item; + jobWorkers[item->id] = workerId; + } + + //if it's not on the job list anymore, and its completion timer was 0, then it probably finished and was not cancelled, so process the job. + for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* completion = (*i).second; + if ( jobs.find(id) != jobs.end() ) + continue; + if ( completion->completion_timer > 0 ) + continue; + if ( processJob(out, id) < 0 ) { + //enabled = false; + return CR_FAILURE; + } + } + + //delete obselete job copies + for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* oldJob = (*i).second; + DFHack::Job::deleteJobStruct(oldJob); + if ( jobs.find(id) == jobs.end() ) + jobWorkers.erase(id); + } + prevJobs.clear(); + + //make copies of the jobs we looked at this tick in case they disappear next frame. + for ( unordered_map::iterator i = jobs.begin(); i != jobs.end(); i++ ) { + int32_t id = (*i).first; + df::job* oldJob = (*i).second; + df::job* jobCopy = DFHack::Job::cloneJobStruct(oldJob); + prevJobs[id] = jobCopy; + } + + return CR_OK; +} + +/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { + return CR_OK; +}*/ + +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The material of one of the stones produced must have a token in its raw file of the form [CAUSE_SYNDROME:syndrome_name].\n" + "\n" + "If a syndrome with the tag [SYN_NAME:syndrome_name] exists, then the unit that completed the job will become afflicted with that syndrome as soon as the job is completed.\n")); + + + return CR_OK; +} + +command_result autoSyndrome(color_ostream& out, vector& parameters) { + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + + if ( parameters.size() == 1 ) { + if ( parameters[0] == "enable" ) { + enabled = true; + } else if ( parameters[0] == "disable" ) { + enabled = false; + } else { + int32_t a = atoi(parameters[0].c_str()); + if ( a < 0 || a > 1 ) + return CR_WRONG_USAGE; + + enabled = (bool)a; + } + } + + out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + return CR_OK; +} + +int32_t processJob(color_ostream& out, int32_t jobId) { + df::job* job = prevJobs[jobId]; + if ( job == NULL ) { + out.print("Error %s line %d: couldn't find job %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + + if ( job->job_type != df::job_type::CustomReaction ) + return 0; + + //find the custom reaction raws and see if we have any special tags there + //out.print("job: \"%s\"\n", job->reaction_name.c_str()); + + df::reaction* reaction = NULL; + for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { + df::reaction* candidate = df::global::world->raws.reactions[a]; + if ( candidate->code != job->reaction_name ) + continue; + reaction = candidate; + break; + } + if ( reaction == NULL ) { + out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); + return -1; + } + + //find all of the products it makes. Look for a stone with a material with special tags. + bool foundIt = false; + for ( size_t a = 0; a < reaction->products.size(); a++ ) { + df::reaction_product_type type = reaction->products[a]->getType(); + //out.print("type = %d\n", (int32_t)type); + if ( type != df::enums::reaction_product_type::item ) + continue; + df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a]; + //out.print("item_type = %d\n", (int32_t)bob->item_type); + if ( bob->item_type != df::enums::item_type::BOULDER ) + continue; + //for now don't worry about subtype + + df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; + const char* helper = "CAUSE_SYNDROME:"; + for ( size_t b = 0; b < inorganic->str.size(); b++ ) { + //out.print("inorganic str = \"%s\"\n", inorganic->str[b]->c_str()); + size_t c = inorganic->str[b]->find(helper); + if ( c == string::npos ) + continue; + string tail = inorganic->str[b]->substr(c + strlen(helper), inorganic->str[b]->length() - strlen(helper) - 2); + //out.print("tail = %s\n", tail.c_str()); + + //find the syndrome with this name, and give apply it to the dwarf working on the job + //first find out who completed the job + if ( jobWorkers.find(jobId) == jobWorkers.end() ) { + out.print("%s, line %d: could not find job worker for jobs %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + int32_t workerId = jobWorkers[jobId]; + + //find the syndrome + df::syndrome* syndrome = NULL; + for ( size_t d = 0; d < df::global::world->raws.syndromes.all.size(); d++ ) { + df::syndrome* candidate = df::global::world->raws.syndromes.all[d]; + if ( candidate->syn_name != tail ) + continue; + syndrome = candidate; + break; + } + if ( syndrome == NULL ) + return 0; + + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return -1; + //out.print("Gave syndrome.\n"); + } + } + if ( !foundIt ) + return 0; + + return -2; +} + +/* + * Heavily based on https://gist.github.com/4061959/ + **/ +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) { + int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( index < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[index]; + + df::unit_syndrome* unitSyndrome = new df::unit_syndrome(); + unitSyndrome->type = syndrome->id; + unitSyndrome->year = 0; + unitSyndrome->year_time = 0; + unitSyndrome->ticks = 1; + unitSyndrome->unk1 = 1; + unitSyndrome->flags = 0; //typecast + + for ( size_t a = 0; a < syndrome->ce.size(); a++ ) { + df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms(); + symptom->unk1 = 0; + symptom->unk2 = 0; + symptom->ticks = 1; + symptom->flags = 2; //TODO: ??? + unitSyndrome->symptoms.push_back(symptom); + } + unit->syndromes.active.push_back(unitSyndrome); + return 0; +} + From bd4f49598db15d994639e74b6b1be55b37f2e071 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 10 Dec 2012 07:23:05 +0200 Subject: [PATCH 314/472] Yet again forgot to disable debug spam. --- scripts/gui/advfort.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 3fac0c646..70a702768 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -464,9 +464,9 @@ function AssignJobItems(args) if not used_item_id[cur_item.id] then local item_suitable,msg=isSuitableItem(trg_job_item,cur_item) - if msg then - print(cur_item,msg) - end + --if msg then + -- print(cur_item,msg) + --end if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then cur_item.flags.in_job=true From a914f8e8e0ed0e2ae24bb839277d9be0bd13de84 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 10 Dec 2012 08:34:11 -0600 Subject: [PATCH 315/472] Autolabor: busy dwarfs may be reassigned now, but with a strong preference for their current job; armed dwarfs are given preference for outside jobs; include experience gained toward next level in preference weighting --- plugins/autolabor.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5f66d3b69..da04de508 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -484,11 +484,12 @@ struct dwarf_info_t int high_skill; bool has_children; + bool armed; df::unit_labor using_labor; dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), - state(OTHER), high_skill(0), has_children(false) + state(OTHER), high_skill(0), has_children(false), armed(false) { } @@ -1783,7 +1784,7 @@ private: df::unit_labor labor = labor_mapper->find_job_labor(dwarf->dwarf->job.current_job); if (labor != df::unit_labor::NONE) { - labor_needed[labor]--; + dwarf->using_labor = labor; if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", @@ -1852,6 +1853,7 @@ private: df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) { + dwarf->armed = true; df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; @@ -1886,7 +1888,7 @@ private: dwarf->clear_labor(labor); } } - else if (state == IDLE) + else if (state == IDLE || state == BUSY) available_dwarfs.push_back(dwarf); } @@ -2071,11 +2073,13 @@ public: { dwarf_info_t* d = (*k); int skill_level = 0; + int xp = 0; if (skill != df::job_skill::NONE) { skill_level = Units::getEffectiveSkill(d->dwarf, skill); + xp = Units::getExperience(d->dwarf, skill, false); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 500; + int score = skill_level * 100 - (d->high_skill - skill_level) * 500 + (xp / (skill_level + 5)); if (d->dwarf->status.labors[labor]) score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || @@ -2084,7 +2088,10 @@ public: score += 500; if (d->has_children && labor_outside[labor]) score -= 5000; - + if (d->armed && labor_outside[labor]) + score += 1000; + if (d->state == BUSY && d->using_labor == labor) + score += 5000; if (score > best_score) { bestdwarf = k; From 0403e008d5398bd048be5b47c95773fce785d314 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 10 Dec 2012 17:48:53 +0100 Subject: [PATCH 316/472] deathcause: show more info for non histfigs --- scripts/deathcause.rb | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index 73e29c890..84877765d 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -10,6 +10,20 @@ def display_death_event(e) puts str.chomp(',') + '.' end +def display_death_unit(u) + death_info = u.counters.death_tg + killer = death_info.killer_tg if death_info + + str = "The #{u.race_tg.name[0]}" + str << " #{u.name}" if u.name.has_name + str << " died" + str << " in year #{death_info.event_year}" if death_info + str << " (cause: #{u.counters.death_cause.to_s.downcase})," if u.counters.death_cause != -1 + str << " killed by the #{killer.race_tg.name[0]} #{killer.name}" if killer + + puts str.chomp(',') + '.' +end + item = df.item_find(:selected) unit = df.unit_find(:selected) @@ -27,8 +41,11 @@ if not hf puts "Please select a corpse in the loo'k' menu, or an unit in the 'u'nitlist screen" elsif hf == -1 - # TODO try to retrieve info from the unit (u = item.unit_tg) - puts "Not a historical figure, cannot death find info" + if unit ||= item.unit_tg + display_death_unit(unit) + else + puts "Not a historical figure, cannot death find info" + end else histfig = df.world.history.figures.binsearch(hf) From 6bc791d985db223907c51f54a3448d95935ddc90 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 10 Dec 2012 22:50:33 +0100 Subject: [PATCH 317/472] follow df-structure rename unit.unknown8 + able_stand_* --- library/modules/Units.cpp | 12 ++++++------ plugins/lua/dfusion/tools.lua | 10 +++++----- plugins/ruby/unit.rb | 5 ++++- plugins/tweak.cpp | 2 +- scripts/fix/loyaltycascade.rb | 6 +++--- 5 files changed, 19 insertions(+), 16 deletions(-) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index a603226e8..193dffb24 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -785,10 +785,10 @@ bool DFHack::Units::isSane(df::unit *unit) if (unit->flags1.bits.dead || unit->flags3.bits.ghostly || isOpposedToLife(unit) || - unit->unknown8.unk2) + unit->enemy.undead) return false; - if (unit->unknown8.normal_race == unit->unknown8.were_race && isCrazed(unit)) + if (unit->enemy.normal_race == unit->enemy.were_race && isCrazed(unit)) return false; switch (unit->mood) @@ -839,7 +839,7 @@ bool DFHack::Units::isDwarf(df::unit *unit) CHECK_NULL_POINTER(unit); return unit->race == ui->race_id || - unit->unknown8.normal_race == ui->race_id; + unit->enemy.normal_race == ui->race_id; } double DFHack::Units::getAge(df::unit *unit, bool true_age) @@ -1225,12 +1225,12 @@ int Units::computeMovementSpeed(df::unit *unit) // Stance - if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) + if (!unit->flags1.bits.on_ground && unit->status2.limbs_stand_max > 2) { // WTF - int as = unit->status2.able_stand; + int as = unit->status2.limbs_stand_max; int x = (as-1) - (as>>1); - int y = as - unit->status2.able_stand_impair; + int y = as - unit->status2.limbs_stand_count; if (unit->flags3.bits.on_crutch) y--; y = y * 500 / x; if (y > 0) speed += y; diff --git a/plugins/lua/dfusion/tools.lua b/plugins/lua/dfusion/tools.lua index 3e24c169d..fba502641 100644 --- a/plugins/lua/dfusion/tools.lua +++ b/plugins/lua/dfusion/tools.lua @@ -189,10 +189,10 @@ function healunit(unit) unit.body.wounds:resize(0) -- memory leak here :/ unit.body.blood_count=unit.body.blood_max --set flags for standing and grasping... - unit.status2.able_stand=4 - unit.status2.able_stand_impair=4 - unit.status2.able_grasp=4 - unit.status2.able_grasp_impair=4 + unit.status2.limbs_stand_max=4 + unit.status2.limbs_stand_count=4 + unit.status2.limbs_grasp_max=4 + unit.status2.limbs_grasp_count=4 --should also set temperatures, and flags for breath etc... unit.flags1.dead=false unit.flags2.calculated_bodyparts=false @@ -240,4 +240,4 @@ function powerup(unit,labor_rating,military_rating,skills) end menu:add("Power up",powerup) -return _ENV \ No newline at end of file +return _ENV diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 13c3711b0..5e2de110e 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -76,7 +76,7 @@ module DFHack u.mood == :Berserk or unit_testflagcurse(u, :CRAZED) or unit_testflagcurse(u, :OPPOSED_TO_LIFE) or - u.unknown8.unk2 or + u.enemy.undead or u.flags3.ghostly or u.flags1.marauder or u.flags1.active_invader or u.flags1.invader_origin or u.flags1.forest or @@ -113,6 +113,9 @@ module DFHack true end + # merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id } + # diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id } + # list workers (citizen, not crazy / child / inmood / noble) def unit_workers world.units.active.find_all { |u| diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 70d915ffc..e120cd172 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -694,7 +694,7 @@ static int adjust_unit_divisor(int value) { static bool can_spar(df::unit *unit) { return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap - (unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) && + (unit->status2.limbs_grasp_count > 0 || unit->status2.limbs_grasp_max == 0) && (!unit->health || (unit->health->flags.whole&0x7FF) == 0) && (!unit->job.current_job || unit->job.current_job != job_type::Rest); } diff --git a/scripts/fix/loyaltycascade.rb b/scripts/fix/loyaltycascade.rb index 6fad2947f..f9298801b 100644 --- a/scripts/fix/loyaltycascade.rb +++ b/scripts/fix/loyaltycascade.rb @@ -38,9 +38,9 @@ def fixunit(unit) end # fix the 'is an enemy' cache matrix (mark to be recalculated by the game when needed) - if fixed and unit.unknown8.enemy_status_slot != -1 - i = unit.unknown8.enemy_status_slot - unit.unknown8.enemy_status_slot = -1 + if fixed and unit.enemy.enemy_status_slot != -1 + i = unit.enemy.enemy_status_slot + unit.enemy.enemy_status_slot = -1 cache = df.world.enemy_status_cache cache.slot_used[i] = false cache.rel_map[i].map! { -1 } From 017b98698777d674b608585559e9a5ed4f43f2e6 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:06:37 -0600 Subject: [PATCH 318/472] Autolabor: fix wound cleaning labor. --- plugins/autolabor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index da04de508..634425e23 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1178,7 +1178,7 @@ public: job_to_labor_table[df::job_type::DiagnosePatient] = jlf_const(df::unit_labor::DIAGNOSE) ; job_to_labor_table[df::job_type::ImmobilizeBreak] = jlf_const(df::unit_labor::BONE_SETTING) ; job_to_labor_table[df::job_type::DressWound] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; - job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::CLEAN) ; + job_to_labor_table[df::job_type::CleanPatient] = jlf_const(df::unit_labor::DRESSING_WOUNDS) ; job_to_labor_table[df::job_type::Surgery] = jlf_const(df::unit_labor::SURGERY) ; job_to_labor_table[df::job_type::Suture] = jlf_const(df::unit_labor::SUTURING); job_to_labor_table[df::job_type::SetBone] = jlf_const(df::unit_labor::BONE_SETTING) ; @@ -1949,7 +1949,7 @@ public: labor_needed[df::unit_labor::DIAGNOSE] += cnt_diagnosis; labor_needed[df::unit_labor::BONE_SETTING] += cnt_immobilize; labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_dressing; - labor_needed[df::unit_labor::CLEAN] += cnt_cleaning; + labor_needed[df::unit_labor::DRESSING_WOUNDS] += cnt_cleaning; labor_needed[df::unit_labor::SURGERY] += cnt_surgery; labor_needed[df::unit_labor::SUTURING] += cnt_suture; labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; From 3e8ba2dd065f142001a9f4e4092f00a29b447940 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:19:38 -0600 Subject: [PATCH 319/472] Autolabor: fix bring-crutch labor --- plugins/autolabor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 634425e23..f26299d22 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1217,7 +1217,7 @@ public: job_to_labor_table[df::job_type::ConstructCrutch] = jlf_make_furniture; job_to_labor_table[df::job_type::ConstructTractionBench] = jlf_const(df::unit_labor::MECHANIC); job_to_labor_table[df::job_type::CleanSelf] = jlf_no_labor; - job_to_labor_table[df::job_type::BringCrutch] = jlf_no_labor; + job_to_labor_table[df::job_type::BringCrutch] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::ApplyCast] = jlf_const(df::unit_labor::BONE_SETTING); job_to_labor_table[df::job_type::CustomReaction] = new jlfunc_custom(); job_to_labor_table[df::job_type::ConstructSlab] = jlf_make_furniture; @@ -1954,7 +1954,7 @@ public: labor_needed[df::unit_labor::SUTURING] += cnt_suture; labor_needed[df::unit_labor::BONE_SETTING] += cnt_setting; labor_needed[df::unit_labor::BONE_SETTING] += cnt_traction; - labor_needed[df::unit_labor::HAUL_ITEM] += cnt_crutch; + labor_needed[df::unit_labor::BONE_SETTING] += cnt_crutch; labor_needed[df::unit_labor::FEED_WATER_CIVILIANS] += need_food_water; From bd1756e5d0c8e0bb0a43d8fb51ebdc4eb8735003 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 11 Dec 2012 09:29:03 -0600 Subject: [PATCH 320/472] Autolabor: change the fallback labor (for dwarfs for which nothing seems appropriate) from NONE to CLEAN. Fiddle with weights in assignment algorithm. --- plugins/autolabor.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index f26299d22..21cd299ee 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2059,7 +2059,7 @@ public: std::list::iterator bestdwarf = available_dwarfs.begin(); int best_score = INT_MIN; - df::unit_labor best_labor = df::unit_labor::NONE; + df::unit_labor best_labor = df::unit_labor::CLEAN; for (auto j = to_assign.begin(); j != to_assign.end(); j++) { @@ -2079,19 +2079,19 @@ public: skill_level = Units::getEffectiveSkill(d->dwarf, skill); xp = Units::getExperience(d->dwarf, skill, false); } - int score = skill_level * 100 - (d->high_skill - skill_level) * 500 + (xp / (skill_level + 5)); + int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) score += 500; if ((labor == df::unit_labor::MINE && d->has_pick) || (labor == df::unit_labor::CUTWOOD && d->has_axe) || (labor == df::unit_labor::HUNT && d->has_crossbow)) - score += 500; + score += 3000; if (d->has_children && labor_outside[labor]) - score -= 5000; + score -= 10000; if (d->armed && labor_outside[labor]) - score += 1000; - if (d->state == BUSY && d->using_labor == labor) score += 5000; + if (d->state == BUSY && d->using_labor == labor) + score += 7500; if (score > best_score) { bestdwarf = k; From 662d3101c70196a220b8226d0ebeb9121d76b5fc Mon Sep 17 00:00:00 2001 From: jj Date: Tue, 11 Dec 2012 17:25:51 +0100 Subject: [PATCH 321/472] ruby: fix onupdate tick limiting + advmode, add pageprotect, add :script_finished --- library/Core.cpp | 2 +- plugins/ruby/README | 1 + plugins/ruby/ruby.cpp | 90 +++++++++++++++++++++++++++++++++++++++++-- plugins/ruby/ruby.rb | 25 +++++++++--- scripts/superdwarf.rb | 7 +--- 5 files changed, 108 insertions(+), 17 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 7e9c90e98..7a0186362 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -316,7 +316,7 @@ static command_result runRubyScript(color_ostream &out, PluginManager *plug_mgr, rbcmd += "'" + args[i] + "', "; rbcmd += "]\n"; - rbcmd += "load './hack/scripts/" + name + ".rb'"; + rbcmd += "catch(:script_finished) { load './hack/scripts/" + name + ".rb' }"; return plug_mgr->eval_ruby(out, rbcmd.c_str()); } diff --git a/plugins/ruby/README b/plugins/ruby/README index d35c34bbe..9bead57d4 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -48,6 +48,7 @@ If you create such a script, e.g. 'test.rb', that will add a new dfhack console command 'test'. The script can access the console command arguments through the global variable '$script_args', which is an array of ruby Strings. +To exit early from a script, use 'throw :script_finished' The help string displayed in dfhack 'ls' command is the first line of the script, if it is a comment (ie starts with '# '). diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index d75fa2402..187b90e3d 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -39,7 +39,7 @@ static color_ostream *r_console; // color_ostream given as argument, if NU static const char *r_command; static tthread::thread *r_thread; static int onupdate_active; -static int onupdate_minyear, onupdate_minyeartick; +static int onupdate_minyear, onupdate_minyeartick=-1, onupdate_minyeartickadv=-1; static color_ostream_proxy *console_proxy; @@ -180,10 +180,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (!onupdate_active) return CR_OK; - if (*df::global::cur_year < onupdate_minyear) + if (df::global::cur_year && (*df::global::cur_year < onupdate_minyear)) return CR_OK; - if (*df::global::cur_year == onupdate_minyear && - *df::global::cur_year_tick < onupdate_minyeartick) + if (df::global::cur_year_tick && onupdate_minyeartick >= 0 && + (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick < onupdate_minyeartick)) + return CR_OK; + if (df::global::cur_year_tick_advmode && onupdate_minyeartickadv >= 0 && + (*df::global::cur_year == onupdate_minyear && + *df::global::cur_year_tick_advmode < onupdate_minyeartickadv)) return CR_OK; return plugin_eval_ruby(out, "DFHack.onupdate"); @@ -481,6 +486,17 @@ static VALUE rb_dfonupdate_minyeartick_set(VALUE self, VALUE val) return Qtrue; } +static VALUE rb_dfonupdate_minyeartickadv(VALUE self) +{ + return rb_uint2inum(onupdate_minyeartickadv); +} + +static VALUE rb_dfonupdate_minyeartickadv_set(VALUE self, VALUE val) +{ + onupdate_minyeartickadv = rb_num2ulong(val); + return Qtrue; +} + static VALUE rb_dfprint_str(VALUE self, VALUE s) { if (r_console) @@ -531,6 +547,23 @@ static VALUE rb_dfget_vtable_ptr(VALUE self, VALUE objptr) return rb_uint2inum(*(uint32_t*)rb_num2ulong(objptr)); } +// run a dfhack command, as if typed from the dfhack console +static VALUE rb_dfhack_run(VALUE self, VALUE cmd) +{ + if (!r_console) // XXX + return Qnil; + + std::string s; + int strlen = FIX2INT(rb_funcall(cmd, rb_intern("length"), 0)); + s.assign(rb_string_value_ptr(&cmd), strlen); + + // allow the target command to suspend + // FIXME + //CoreSuspendClaimer suspend(true); + Core::getInstance().runCommand(*r_console, s); + + return Qtrue; +} @@ -663,6 +696,49 @@ static VALUE rb_dfmemory_patch(VALUE self, VALUE addr, VALUE raw) return ret ? Qtrue : Qfalse; } +// allocate memory pages +static VALUE rb_dfmemory_pagealloc(VALUE self, VALUE len) +{ + void *ret = Core::getInstance().p->memAlloc(rb_num2ulong(len)); + + return (ret == (void*)-1) ? Qnil : rb_uint2inum((uint32_t)ret); +} + +// free memory from pagealloc +static VALUE rb_dfmemory_pagedealloc(VALUE self, VALUE ptr, VALUE len) +{ + int ret = Core::getInstance().p->memDealloc((void*)rb_num2ulong(ptr), rb_num2ulong(len)); + + return ret ? Qfalse : Qtrue; +} + +// change memory page permissions +// ptr must be page-aligned +// prot is a String, eg 'rwx', 'r', 'x' +static VALUE rb_dfmemory_pageprotect(VALUE self, VALUE ptr, VALUE len, VALUE prot_str) +{ + int ret, prot=0; + char *prot_p = rb_string_value_ptr(&prot_str); + + if (*prot_p == 'r') { + prot |= Process::MemProt::READ; + ++prot_p; + } + if (*prot_p == 'w') { + prot |= Process::MemProt::WRITE; + ++prot_p; + } + if (*prot_p == 'x') { + prot |= Process::MemProt::EXEC; + ++prot_p; + } + + Core::printerr("pageprot %x %x %x\n", rb_num2ulong(ptr), rb_num2ulong(len), prot); + ret = Core::getInstance().p->memProtect((void*)rb_num2ulong(ptr), rb_num2ulong(len), prot); + + return ret ? Qfalse : Qtrue; +} + // stl::string static VALUE rb_dfmemory_stlstring_new(VALUE self) @@ -963,14 +1039,20 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "onupdate_minyear=", RUBY_METHOD_FUNC(rb_dfonupdate_minyear_set), 1); rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick), 0); rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartick=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartick_set), 1); + rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv), 0); + rb_define_singleton_method(rb_cDFHack, "onupdate_minyeartickadv=", RUBY_METHOD_FUNC(rb_dfonupdate_minyeartickadv_set), 1); rb_define_singleton_method(rb_cDFHack, "get_global_address", RUBY_METHOD_FUNC(rb_dfget_global_address), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable", RUBY_METHOD_FUNC(rb_dfget_vtable), 1); rb_define_singleton_method(rb_cDFHack, "get_rtti_classname", RUBY_METHOD_FUNC(rb_dfget_rtti_classname), 1); rb_define_singleton_method(rb_cDFHack, "get_vtable_ptr", RUBY_METHOD_FUNC(rb_dfget_vtable_ptr), 1); + //rb_define_singleton_method(rb_cDFHack, "dfhack_run", RUBY_METHOD_FUNC(rb_dfhack_run), 1); rb_define_singleton_method(rb_cDFHack, "print_str", RUBY_METHOD_FUNC(rb_dfprint_str), 1); rb_define_singleton_method(rb_cDFHack, "print_err", RUBY_METHOD_FUNC(rb_dfprint_err), 1); rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); + rb_define_singleton_method(rb_cDFHack, "pagealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagealloc), 1); + rb_define_singleton_method(rb_cDFHack, "pagedealloc", RUBY_METHOD_FUNC(rb_dfmemory_pagedealloc), 2); + rb_define_singleton_method(rb_cDFHack, "pageprotect", RUBY_METHOD_FUNC(rb_dfmemory_pageprotect), 3); rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 27cde675a..47924dcdf 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -64,6 +64,7 @@ module DFHack # register a callback to be called every gframe or more # ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 } + # if ticklimit is given, do not call unless this much game ticks have passed. Handles advmode time stretching. def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b) raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String) @onupdate_list ||= [] @@ -82,7 +83,7 @@ module DFHack @onupdate_list.delete b if @onupdate_list.empty? DFHack.onupdate_active = false - DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = 0 + DFHack.onupdate_minyear = DFHack.onupdate_minyeartick = DFHack.onupdate_minyeartickadv = -1 end end @@ -94,20 +95,32 @@ module DFHack end TICKS_PER_YEAR = 1200*28*12 - # this method is called by dfhack every 'onupdate' if onupdate_active is true + # this method is called by ruby.cpp if df.onupdate_active is true def onupdate @onupdate_list ||= [] - ticks_per_year = TICKS_PER_YEAR - ticks_per_year *= 72 if gametype == :ADVENTURE_MAIN or gametype == :ADVENTURE_ARENA + y = cur_year + ytmax = TICKS_PER_YEAR + if df.gamemode == :ADVENTURE and df.respond_to?(:cur_year_tick_advmode) + yt = cur_year_tick_advmode + ytmax *= 144 + else + yt = cur_year_tick + end @onupdate_list.each { |o| - o.check_run(cur_year, cur_year_tick, ticks_per_year) + o.check_run(y, yt, ytmax) } if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear - DFHack.onupdate_minyeartick = onext.minyeartick + if ytmax > TICKS_PER_YEAR + DFHack.onupdate_minyeartick = -1 + DFHack.onupdate_minyeartickadv = onext.minyeartick + else + DFHack.onupdate_minyeartick = onext.minyeartick + DFHack.onupdate_minyeartickadv = -1 + end end end diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index eac9802fa..2dd873b50 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,12 +8,7 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode) - onupdate_delay = nil - else - onupdate_delay = 1 - end - $superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) { + $superdwarf_onupdate ||= df.onupdate_register('superdwarf', 1) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil From b75c1da95ec712d7ced4079f0ec7c4a75e13ce49 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 08:46:52 -0600 Subject: [PATCH 322/472] Autolabor: add build waterwheel. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 21cd299ee..8531e09c8 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -775,6 +775,7 @@ private: case df::building_type::Construction: case df::building_type::Bridge: case df::building_type::ArcheryTarget: + case df::building_type::WaterWheel: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 7315f8cec4ebdd86c52af5c9a05a4682a234285d Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 12 Dec 2012 16:43:34 +0100 Subject: [PATCH 323/472] scripts/devel/create-items.rb --- NEWS | 1 + scripts/devel/create-items.rb | 161 ++++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+) create mode 100644 scripts/devel/create-items.rb diff --git a/NEWS b/NEWS index a0e01dba1..fdde16e77 100644 --- a/NEWS +++ b/NEWS @@ -24,6 +24,7 @@ DFHack future - lever: list and pull fort levers from the dfhack console. - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. - soundsense-season: writes the correct season to gamelog.txt on world load. + - devel/create-items: spawn items New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/scripts/devel/create-items.rb b/scripts/devel/create-items.rb new file mode 100644 index 000000000..a1f85cc4c --- /dev/null +++ b/scripts/devel/create-items.rb @@ -0,0 +1,161 @@ +# create arbitrary items under cursor + +category = $script_args[0] || 'help' +mat_raw = $script_args[1] || 'list' +count = $script_args[2] + + +category = df.match_rawname(category, ['help', 'bars', 'boulders', 'plants', 'logs', 'webs']) || 'help' + +if category == 'help' + puts < Date: Wed, 12 Dec 2012 09:52:13 -0600 Subject: [PATCH 324/472] Autolabor: add construct labor for GearAssembly, AxleHorizonal, and AxleVertical (the last is a guess, but probably right) --- plugins/autolabor.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 8531e09c8..266a2771d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -804,6 +804,9 @@ private: case df::building_type::Slab: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: + case df::building_type::GearAssembly: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: return df::unit_labor::MECHANIC; } From 3b9f21a1eac3c3a0a1c1f00bab6f6e1406d28b99 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 10:37:09 -0600 Subject: [PATCH 325/472] Autolabor: do not count designations in hidden squares (since your dwarves can't reach them anyway). Also apply an assignment penalty for assigning a dwarf to a labor other than the one the dwarf is doing --- plugins/autolabor.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 266a2771d..fc8559df0 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1540,6 +1540,9 @@ private: for (int x = 0; x < 16; x++) for (int y = 0; y < 16; y++) { + if (bl->designation[x][y].bits.hidden) + continue; + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) { @@ -2094,8 +2097,11 @@ public: score -= 10000; if (d->armed && labor_outside[labor]) score += 5000; - if (d->state == BUSY && d->using_labor == labor) - score += 7500; + if (d->state == BUSY) + if (d->using_labor == labor) + score += 7500; + else + score -= 7500; if (score > best_score) { bestdwarf = k; From 722ed79a980c6c42b4437747708e111696ceedbe Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 12 Dec 2012 18:11:56 +0100 Subject: [PATCH 326/472] stripcaged: allow shortened arguments --- scripts/stripcaged.rb | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb index 07694f711..07f37c756 100644 --- a/scripts/stripcaged.rb +++ b/scripts/stripcaged.rb @@ -138,7 +138,10 @@ if here_only if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } end - puts 'Please select a cage' if list.empty? + if list.empty? + puts 'Please select a cage' + throw :script_finished + end elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first list = [] @@ -153,7 +156,10 @@ elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first list << it end } - puts 'Please use a valid cage id' if list.empty? + if list.empty? + puts 'Please use a valid cage id' + throw :script_finished + end else list = df.world.items.other[:ANY_CAGE_OR_TRAP] @@ -162,18 +168,16 @@ end # act case $script_args[0] -when 'items' - cage_dump_items(list) if not list.empty? -when 'armor' - cage_dump_armor(list) if not list.empty? -when 'weapons' - cage_dump_weapons(list) if not list.empty? +when /^it/i + cage_dump_items(list) +when /^arm/i + cage_dump_armor(list) +when /^wea/i + cage_dump_weapons(list) when 'all' - cage_dump_all(list) if not list.empty? - + cage_dump_all(list) when 'list' - cage_dump_list(list) if not list.empty? - + cage_dump_list(list) else puts < Date: Wed, 12 Dec 2012 14:28:11 -0600 Subject: [PATCH 327/472] Autolabor: rework tool management to try to reduce tool litter --- plugins/autolabor.cpp | 244 +++++++++++++++++++++++------------------- 1 file changed, 136 insertions(+), 108 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index fc8559df0..c0ef5d114 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -385,89 +385,96 @@ struct labor_info }; +enum tools_enum { + TOOL_NONE, TOOL_PICK, TOOL_AXE, TOOL_CROSSBOW, + TOOLS_MAX +}; + + struct labor_default { int priority; int maximum_dwarfs; + tools_enum tool; }; static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {200, 0}, - /* HAUL_STONE */ {100, 0}, - /* HAUL_WOOD */ {100, 0}, - /* HAUL_BODY */ {200, 0}, - /* HAUL_FOOD */ {300, 0}, - /* HAUL_REFUSE */ {100, 0}, - /* HAUL_ITEM */ {100, 0}, - /* HAUL_FURNITURE */ {100, 0}, - /* HAUL_ANIMAL */ {100, 0}, - /* CLEAN */ {200, 0}, - /* CUTWOOD */ {200, 0}, - /* CARPENTER */ {200, 0}, - /* DETAIL */ {200, 0}, - /* MASON */ {200, 0}, - /* ARCHITECT */ {400, 0}, - /* ANIMALTRAIN */ {200, 0}, - /* ANIMALCARE */ {200, 0}, - /* DIAGNOSE */ {1000, 0}, - /* SURGERY */ {1000, 0}, - /* BONE_SETTING */ {1000, 0}, - /* SUTURING */ {1000, 0}, - /* DRESSING_WOUNDS */ {1000, 0}, - /* FEED_WATER_CIVILIANS */ {1000, 0}, - /* RECOVER_WOUNDED */ {200, 0}, - /* BUTCHER */ {200, 0}, - /* TRAPPER */ {200, 0}, - /* DISSECT_VERMIN */ {200, 0}, - /* LEATHER */ {200, 0}, - /* TANNER */ {200, 0}, - /* BREWER */ {200, 0}, - /* ALCHEMIST */ {200, 0}, - /* SOAP_MAKER */ {200, 0}, - /* WEAVER */ {200, 0}, - /* CLOTHESMAKER */ {200, 0}, - /* MILLER */ {200, 0}, - /* PROCESS_PLANT */ {200, 0}, - /* MAKE_CHEESE */ {200, 0}, - /* MILK */ {200, 0}, - /* COOK */ {200, 0}, - /* PLANT */ {200, 0}, - /* HERBALIST */ {200, 0}, - /* FISH */ {100, 0}, - /* CLEAN_FISH */ {200, 0}, - /* DISSECT_FISH */ {200, 0}, - /* HUNT */ {100, 0}, - /* SMELT */ {200, 0}, - /* FORGE_WEAPON */ {200, 0}, - /* FORGE_ARMOR */ {200, 0}, - /* FORGE_FURNITURE */ {200, 0}, - /* METAL_CRAFT */ {200, 0}, - /* CUT_GEM */ {200, 0}, - /* ENCRUST_GEM */ {200, 0}, - /* WOOD_CRAFT */ {200, 0}, - /* STONE_CRAFT */ {200, 0}, - /* BONE_CARVE */ {200, 0}, - /* GLASSMAKER */ {200, 0}, - /* EXTRACT_STRAND */ {200, 0}, - /* SIEGECRAFT */ {200, 0}, - /* SIEGEOPERATE */ {200, 0}, - /* BOWYER */ {200, 0}, - /* MECHANIC */ {200, 0}, - /* POTASH_MAKING */ {200, 0}, - /* LYE_MAKING */ {200, 0}, - /* DYER */ {200, 0}, - /* BURN_WOOD */ {200, 0}, - /* OPERATE_PUMP */ {200, 0}, - /* SHEARER */ {200, 0}, - /* SPINNER */ {200, 0}, - /* POTTERY */ {200, 0}, - /* GLAZING */ {200, 0}, - /* PRESSING */ {200, 0}, - /* BEEKEEPING */ {200, 1}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) - /* WAX_WORKING */ {200, 0}, - /* PUSH_HAUL_VEHICLES */ {200, 0} + /* MINE */ {200, 0, TOOL_PICK}, + /* HAUL_STONE */ {100, 0, TOOL_NONE}, + /* HAUL_WOOD */ {100, 0, TOOL_NONE}, + /* HAUL_BODY */ {200, 0, TOOL_NONE}, + /* HAUL_FOOD */ {300, 0, TOOL_NONE}, + /* HAUL_REFUSE */ {100, 0, TOOL_NONE}, + /* HAUL_ITEM */ {100, 0, TOOL_NONE}, + /* HAUL_FURNITURE */ {100, 0, TOOL_NONE}, + /* HAUL_ANIMAL */ {100, 0, TOOL_NONE}, + /* CLEAN */ {200, 0, TOOL_NONE}, + /* CUTWOOD */ {200, 0, TOOL_AXE}, + /* CARPENTER */ {200, 0, TOOL_NONE}, + /* DETAIL */ {200, 0, TOOL_NONE}, + /* MASON */ {200, 0, TOOL_NONE}, + /* ARCHITECT */ {400, 0, TOOL_NONE}, + /* ANIMALTRAIN */ {200, 0, TOOL_NONE}, + /* ANIMALCARE */ {200, 0, TOOL_NONE}, + /* DIAGNOSE */ {1000, 0, TOOL_NONE}, + /* SURGERY */ {1000, 0, TOOL_NONE}, + /* BONE_SETTING */ {1000, 0, TOOL_NONE}, + /* SUTURING */ {1000, 0, TOOL_NONE}, + /* DRESSING_WOUNDS */ {1000, 0, TOOL_NONE}, + /* FEED_WATER_CIVILIANS */ {1000, 0, TOOL_NONE}, + /* RECOVER_WOUNDED */ {200, 0, TOOL_NONE}, + /* BUTCHER */ {200, 0, TOOL_NONE}, + /* TRAPPER */ {200, 0, TOOL_NONE}, + /* DISSECT_VERMIN */ {200, 0, TOOL_NONE}, + /* LEATHER */ {200, 0, TOOL_NONE}, + /* TANNER */ {200, 0, TOOL_NONE}, + /* BREWER */ {200, 0, TOOL_NONE}, + /* ALCHEMIST */ {200, 0, TOOL_NONE}, + /* SOAP_MAKER */ {200, 0, TOOL_NONE}, + /* WEAVER */ {200, 0, TOOL_NONE}, + /* CLOTHESMAKER */ {200, 0, TOOL_NONE}, + /* MILLER */ {200, 0, TOOL_NONE}, + /* PROCESS_PLANT */ {200, 0, TOOL_NONE}, + /* MAKE_CHEESE */ {200, 0, TOOL_NONE}, + /* MILK */ {200, 0, TOOL_NONE}, + /* COOK */ {200, 0, TOOL_NONE}, + /* PLANT */ {200, 0, TOOL_NONE}, + /* HERBALIST */ {200, 0, TOOL_NONE}, + /* FISH */ {100, 0, TOOL_NONE}, + /* CLEAN_FISH */ {200, 0, TOOL_NONE}, + /* DISSECT_FISH */ {200, 0, TOOL_NONE}, + /* HUNT */ {100, 0, TOOL_CROSSBOW}, + /* SMELT */ {200, 0, TOOL_NONE}, + /* FORGE_WEAPON */ {200, 0, TOOL_NONE}, + /* FORGE_ARMOR */ {200, 0, TOOL_NONE}, + /* FORGE_FURNITURE */ {200, 0, TOOL_NONE}, + /* METAL_CRAFT */ {200, 0, TOOL_NONE}, + /* CUT_GEM */ {200, 0, TOOL_NONE}, + /* ENCRUST_GEM */ {200, 0, TOOL_NONE}, + /* WOOD_CRAFT */ {200, 0, TOOL_NONE}, + /* STONE_CRAFT */ {200, 0, TOOL_NONE}, + /* BONE_CARVE */ {200, 0, TOOL_NONE}, + /* GLASSMAKER */ {200, 0, TOOL_NONE}, + /* EXTRACT_STRAND */ {200, 0, TOOL_NONE}, + /* SIEGECRAFT */ {200, 0, TOOL_NONE}, + /* SIEGEOPERATE */ {200, 0, TOOL_NONE}, + /* BOWYER */ {200, 0, TOOL_NONE}, + /* MECHANIC */ {200, 0, TOOL_NONE}, + /* POTASH_MAKING */ {200, 0, TOOL_NONE}, + /* LYE_MAKING */ {200, 0, TOOL_NONE}, + /* DYER */ {200, 0, TOOL_NONE}, + /* BURN_WOOD */ {200, 0, TOOL_NONE}, + /* OPERATE_PUMP */ {200, 0, TOOL_NONE}, + /* SHEARER */ {200, 0, TOOL_NONE}, + /* SPINNER */ {200, 0, TOOL_NONE}, + /* POTTERY */ {200, 0, TOOL_NONE}, + /* GLAZING */ {200, 0, TOOL_NONE}, + /* PRESSING */ {200, 0, TOOL_NONE}, + /* BEEKEEPING */ {200, 0, TOOL_NONE}, + /* WAX_WORKING */ {200, 0, TOOL_NONE}, + /* PUSH_HAUL_VEHICLES */ {200, 0, TOOL_NONE} }; struct dwarf_info_t @@ -477,9 +484,7 @@ struct dwarf_info_t bool clear_all; - bool has_axe; - bool has_pick; - bool has_crossbow; + bool has_tool[TOOLS_MAX]; int high_skill; @@ -488,9 +493,11 @@ struct dwarf_info_t df::unit_labor using_labor; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), has_axe(false), has_pick(false), has_crossbow(false), + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(false), state(OTHER), high_skill(0), has_children(false), armed(false) { + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + has_tool[e] = false; } void set_labor(df::unit_labor labor) @@ -498,10 +505,6 @@ struct dwarf_info_t if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) { dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; } } @@ -510,10 +513,6 @@ struct dwarf_info_t if (labor >= 0 && labor <= ENUM_LAST_ITEM(unit_labor)) { dwarf->status.labors[labor] = false; - if ((labor == df::unit_labor::MINE && has_pick) || - (labor == df::unit_labor::CUTWOOD && has_axe) || - (labor == df::unit_labor::HUNT && has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; } } @@ -1472,8 +1471,9 @@ private: int tree_count; int plant_count; int detail_count; - int pick_count; - int axe_count; + + int tool_count[TOOLS_MAX]; + bool reequip_needed[TOOLS_MAX]; int cnt_recover_wounded; int cnt_diagnosis; @@ -1542,7 +1542,7 @@ private: { if (bl->designation[x][y].bits.hidden) continue; - + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) { @@ -1570,8 +1570,8 @@ private: void count_tools() { - pick_count = 0; - axe_count = 0; + for (int e = TOOL_NONE; e < TOOLS_MAX; e++) + tool_count[e] = 0; df::item_flags bad_flags; bad_flags.whole = 0; @@ -1598,15 +1598,15 @@ private: df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill weaponsk2 = (df::job_skill) weapondef->skill_ranged; if (weaponsk == df::job_skill::AXE) - axe_count++; + tool_count[TOOL_AXE]++; else if (weaponsk == df::job_skill::MINING) - pick_count++; + tool_count[TOOL_PICK]++; + else if (weaponsk2 = df::job_skill::CROSSBOW) + tool_count[TOOL_CROSSBOW]++; } - if (print_debug) - out.print("Axes = %d, picks = %d\n", axe_count, pick_count); - } void collect_job_list() @@ -1866,19 +1866,21 @@ private: df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; if (weaponsk == df::job_skill::AXE) { - dwarf->has_axe = 1; + dwarf->has_tool[TOOL_AXE] = true; if (state != IDLE) - axe_count--; + tool_count[TOOL_AXE]--; } else if (weaponsk == df::job_skill::MINING) { - dwarf->has_pick = 1; + dwarf->has_tool[TOOL_PICK] = 1; if (state != IDLE) - pick_count--; + tool_count[TOOL_PICK]--; } else if (rangesk == df::job_skill::CROSSBOW) { - dwarf->has_crossbow = 1; + dwarf->has_tool[TOOL_CROSSBOW] = 1; + if (state != IDLE) + tool_count[TOOL_CROSSBOW]--; } } } @@ -1908,11 +1910,14 @@ public: { dwarf_info.clear(); - dig_count = tree_count = plant_count = detail_count = pick_count = axe_count = 0; + dig_count = tree_count = plant_count = detail_count = 0; cnt_recover_wounded = cnt_diagnosis = cnt_immobilize = cnt_dressing = cnt_cleaning = cnt_surgery = cnt_suture = cnt_setting = cnt_traction = cnt_crutch = 0; need_food_water = 0; + for (int e = 0; e < TOOLS_MAX; e++) + tool_count[e] = 0; + trader_requested = false; FOR_ENUM_ITEMS(unit_labor, l) @@ -1945,8 +1950,8 @@ public: // add job entries for designation-related jobs - labor_needed[df::unit_labor::MINE] += std::min(pick_count, dig_count); - labor_needed[df::unit_labor::CUTWOOD] += std::min(axe_count, tree_count); + labor_needed[df::unit_labor::MINE] += std::min(tool_count[TOOL_PICK], dig_count); + labor_needed[df::unit_labor::CUTWOOD] += std::min(tool_count[TOOL_AXE], tree_count); labor_needed[df::unit_labor::DETAIL] += detail_count; labor_needed[df::unit_labor::HERBALIST] += plant_count; @@ -2089,9 +2094,8 @@ public: int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) score += 500; - if ((labor == df::unit_labor::MINE && d->has_pick) || - (labor == df::unit_labor::CUTWOOD && d->has_axe) || - (labor == df::unit_labor::HUNT && d->has_crossbow)) + if (default_labor_infos[labor].tool != TOOL_NONE && + d->has_tool[default_labor_infos[labor].tool]) score += 3000; if (d->has_children && labor_outside[labor]) score -= 10000; @@ -2120,8 +2124,17 @@ public: continue; if (l == best_labor) + { (*bestdwarf)->set_labor(l); - else + tools_enum t = default_labor_infos[l].tool; + if (t != TOOL_NONE) + { + tool_count[t]--; + if (!(*bestdwarf)->has_tool[t]) + (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; + } + } + else (*bestdwarf)->clear_labor(l); } @@ -2150,6 +2163,21 @@ public: out.print ("Setting %s as the hauling canary\n", d->dwarf->name.first_name.c_str()); } + /* set reequip on any dwarfs who are carrying tools needed by others */ + + for (auto d = dwarf_info.begin(); d != dwarf_info.end(); d++) + { + FOR_ENUM_ITEMS (unit_labor, l) + { + tools_enum t = default_labor_infos[l].tool; + if (t != TOOL_NONE && tool_count[t] < 0 && (*d)->has_tool[t] && !(*d)->dwarf->status.labors[l]) + { + tool_count[t]++; + (*d)->dwarf->military.pickup_flags.bits.update = 1; + } + } + } + print_debug = 0; } From 723ff7d632c67c49ac989125aa583bccc6079668 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 13 Dec 2012 00:13:16 +0100 Subject: [PATCH 328/472] slayrace: target undeads, ignore chained creatures, fix magma column mode going through floors --- NEWS | 1 + Readme.rst | 7 +++++-- scripts/slayrace.rb | 44 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 42 insertions(+), 10 deletions(-) diff --git a/NEWS b/NEWS index fdde16e77..73c205c18 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,7 @@ DFHack future - superdwarf: work in adventure mode too - tweak stable-cursor: carries cursor location from/to Build menu. - deathcause: allow selection from the unitlist screen + - slayrace: allow targetting undeads New tweaks: - tweak military-training: speed up melee squad training up to 10x (normally 3-5x). New scripts: diff --git a/Readme.rst b/Readme.rst index afad3ef4c..150f0d488 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1850,13 +1850,16 @@ slayrace ======== Kills any unit of a given race. -With no argument, lists the available races. +With no argument, lists the available races and count eligible targets. With the special argument ``him``, targets only the selected creature. +With the special argument ``undead``, targets all undeads on the map, +regardless of their race. + Any non-dead non-caged unit of the specified race gets its ``blood_count`` set to 0, which means immediate death at the next game tick. For creatures -such as vampires, also set animal.vanish_countdown to 2. +such as vampires, it also sets animal.vanish_countdown to 2. An alternate mode is selected by adding a 2nd argument to the command, ``magma``. In this case, a column of 7/7 magma is generated on top of the diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index ca50020f7..9709b93fa 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -1,14 +1,17 @@ # slay all creatures of a given race # race = name of the race to eradicate, use 'him' to target only the selected creature +# use 'undead' to target all undeads race = $script_args[0] + # if the 2nd parameter is 'magma', magma rain for the targets instead of instant death magma = ($script_args[1] == 'magma') checkunit = lambda { |u| - u.body.blood_count != 0 and + (u.body.blood_count != 0 or u.body.blood_max == 0) and not u.flags1.dead and - not u.flags1.caged and + not u.flags1.caged and not u.flags1.chained and + #not u.flags1.hidden_in_ambush and not df.map_designation_at(u).hidden } @@ -26,7 +29,8 @@ slayit = lambda { |u| df.onupdate_unregister(ouh) else x, y, z = u.pos.x, u.pos.y, u.pos.z - z += 1 while tile = df.map_tile_at(x, y, z+1) and tile.shape_passableflow + z += 1 while tile = df.map_tile_at(x, y, z+1) and + tile.shape_passableflow and tile.shape_passablelow df.map_tile_at(x, y, z).spawn_magma(7) end } @@ -36,17 +40,41 @@ slayit = lambda { |u| all_races = Hash.new(0) df.world.units.active.map { |u| - all_races[u.race_tg.creature_id] += 1 if checkunit[u] + if checkunit[u] + if (u.enemy.undead or + (u.curse.add_tags1.OPPOSED_TO_LIFE and not + u.curse.del_tags1.OPPOSED_TO_LIFE)) + all_races['Undead'] += 1 + else + all_races[u.race_tg.creature_id] += 1 + end + end } -if !race +case race +when nil all_races.sort_by { |race, cnt| [cnt, race] }.each{ |race, cnt| puts " #{race} #{cnt}" } -elsif race == 'him' + +when 'him' if him = df.unit_find slayit[him] else - puts "Choose target" + puts "Select a target ingame" end + +when /^undead/i + count = 0 + df.world.units.active.each { |u| + if (u.enemy.undead or + (u.curse.add_tags1.OPPOSED_TO_LIFE and not + u.curse.del_tags1.OPPOSED_TO_LIFE)) and + checkunit[u] + slayit[u] + count += 1 + end + } + puts "slain #{count} undeads" + else raw_race = df.match_rawname(race, all_races.keys) raise 'invalid race' if not raw_race @@ -60,6 +88,6 @@ else count += 1 end } - puts "slain #{count} #{raw_race}" + end From 75ad052ac9debc598789e2221d26afe07e280594 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 13 Dec 2012 00:29:56 +0100 Subject: [PATCH 329/472] move create-items out of devel/, add Readme entry --- NEWS | 2 +- Readme.rst | 28 ++++++++++++++++++++++++++++ scripts/{devel => }/create-items.rb | 0 3 files changed, 29 insertions(+), 1 deletion(-) rename scripts/{devel => }/create-items.rb (100%) diff --git a/NEWS b/NEWS index 73c205c18..ed6290261 100644 --- a/NEWS +++ b/NEWS @@ -25,7 +25,7 @@ DFHack future - lever: list and pull fort levers from the dfhack console. - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. - soundsense-season: writes the correct season to gamelog.txt on world load. - - devel/create-items: spawn items + - create-items: spawn items New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/Readme.rst b/Readme.rst index 150f0d488..2eaa7318e 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1942,6 +1942,7 @@ deathcause ========== Focus a body part ingame, and this script will display the cause of death of the creature. +Also works when selecting units from the 'u'nitlist viewscreen. lua === @@ -2004,6 +2005,33 @@ alternatively pass cage IDs as arguments:: stripcaged weapons 25321 34228 +create-items +============ +Spawn arbitrary items under the cursor. + +The first argument gives the item category, the second gives the material, +and the optionnal third gives the number of items to create (defaults to 20). + +Currently supported item categories: ``boulder``, ``bar``, ``plant``, ``log``, +``web``. + +Instead of material, using ``list`` makes the script list eligible materials. + +The ``web`` item category will create an uncollected cobweb on the floor. + +Note that the script does not enforce anything, and will let you create +boulders of toad blood and stuff like that. +However the ``list`` mode will only show 'normal' materials. + +Exemples:: + + create-items boulders COAL_BITUMINOUS 12 + create-items plant tail_pig + create-items log list + create-items web CREATURE:SPIDER_CAVE_GIANT:SILK + create-items bar CREATURE:CAT:SOAP + create-items bar adamantine + ======================= In-game interface tools ======================= diff --git a/scripts/devel/create-items.rb b/scripts/create-items.rb similarity index 100% rename from scripts/devel/create-items.rb rename to scripts/create-items.rb From 19ff6962bcc573c1eb486a2faf7ba0f7f86a6059 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 13 Dec 2012 01:47:58 +0100 Subject: [PATCH 330/472] slayrace: typo --- scripts/slayrace.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index 9709b93fa..4844538d4 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -43,7 +43,7 @@ df.world.units.active.map { |u| if checkunit[u] if (u.enemy.undead or (u.curse.add_tags1.OPPOSED_TO_LIFE and not - u.curse.del_tags1.OPPOSED_TO_LIFE)) + u.curse.rem_tags1.OPPOSED_TO_LIFE)) all_races['Undead'] += 1 else all_races[u.race_tg.creature_id] += 1 @@ -67,7 +67,7 @@ when /^undead/i df.world.units.active.each { |u| if (u.enemy.undead or (u.curse.add_tags1.OPPOSED_TO_LIFE and not - u.curse.del_tags1.OPPOSED_TO_LIFE)) and + u.curse.rem_tags1.OPPOSED_TO_LIFE)) and checkunit[u] slayit[u] count += 1 From 3a541e26bec867a87192c9b37d7f9bff01a1cc73 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 12 Dec 2012 23:01:05 -0500 Subject: [PATCH 331/472] autoSyndrome: automatically detects boiling rock syndromes better, and checks if each syndrome is applicable to the unit that finished the job. --- plugins/autoSyndrome.cpp | 153 +++++++++++++++++++++------------------ 1 file changed, 82 insertions(+), 71 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 28a785adc..77217c6a0 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -2,9 +2,10 @@ #include "Export.h" #include "DataDefs.h" #include "Core.h" -#include "df/job.h" +#include "df/caste_raw.h" +#include "df/creature_raw.h" #include "df/global_objects.h" -#include "df/ui.h" +#include "df/job.h" #include "df/job_type.h" #include "df/reaction.h" #include "df/reaction_product.h" @@ -12,6 +13,7 @@ #include "df/reaction_product_itemst.h" #include "df/syndrome.h" #include "df/unit_syndrome.h" +#include "df/ui.h" #include "df/unit.h" #include "df/general_ref.h" #include "df/general_ref_type.h" @@ -30,25 +32,6 @@ using namespace DFHack; /* Example usage: -////////////////////////////////////////////// -//in file interaction_duck.txt -interaction_duck - -[OBJECT:INTERACTION] - -[INTERACTION:DUMMY_DUCK_INTERACTION] - [I_SOURCE:CREATURE_ACTION] - [I_TARGET:A:CREATURE] - [IT_LOCATION:CONTEXT_CREATURE] - [IT_MANUAL_INPUT:target] - [IT_IMMUNE_CREATURE:BIRD_DUCK:MALE] - [I_EFFECT:ADD_SYNDROME] - [IE_TARGET:A] - [IE_IMMEDIATE] - [SYNDROME] - [SYN_NAME:chronicDuckSyndrome] - [CE_BODY_TRANSFORMATION:PROB:100:START:0] - [CE:CREATURE:BIRD_DUCK:MALE] ////////////////////////////////////////////// //In file inorganic_duck.txt inorganic_stone_duck @@ -60,8 +43,11 @@ inorganic_stone_duck [STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] [IS_STONE] [SOLID_DENSITY:1][MELTING_POINT:25000] -[CAUSE_SYNDROME:chronicDuckSyndrome] -[BOILING_POINT:50000] +[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000 +[SYNDROME] + [SYN_NAME:Chronic Duck Syndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it /////////////////////////////////////////////// //In file building_duck.txt building_duck @@ -117,6 +103,29 @@ command_result autoSyndrome(color_ostream& out, vector& parameters); int32_t processJob(color_ostream& out, int32_t id); int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The stone must have a boiling temperature less than or equal to 10000.\n" + "\n" + "When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, but it ignores syn_affected_class tags.\n" + "Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n" + )); + + + return CR_OK; +} + DFhackCExport command_result plugin_shutdown(color_ostream& out) { return CR_OK; } @@ -204,27 +213,6 @@ DFhackCExport command_result plugin_onupdate(color_ostream& out) { return CR_OK; }*/ -DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { - commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, - "autoSyndrome:\n" - " autoSyndrome 0 //disable\n" - " autoSyndrome 1 //enable\n" - " autoSyndrome disable //disable\n" - " autoSyndrome enable //enable\n" - "\n" - "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" - "\n" - "Requirements:\n" - " 1) The job must be a custom reaction.\n" - " 2) The job must produce a stone of some inorganic material.\n" - " 3) The material of one of the stones produced must have a token in its raw file of the form [CAUSE_SYNDROME:syndrome_name].\n" - "\n" - "If a syndrome with the tag [SYN_NAME:syndrome_name] exists, then the unit that completed the job will become afflicted with that syndrome as soon as the job is completed.\n")); - - - return CR_OK; -} - command_result autoSyndrome(color_ostream& out, vector& parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; @@ -273,7 +261,22 @@ int32_t processJob(color_ostream& out, int32_t jobId) { return -1; } - //find all of the products it makes. Look for a stone with a material with special tags. + if ( jobWorkers.find(jobId) == jobWorkers.end() ) { + out.print("%s, line %d: couldn't find worker for job %d.\n", __FILE__, __LINE__, jobId); + return -1; + } + int32_t workerId = jobWorkers[jobId]; + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( workerIndex < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[workerIndex]; + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = creature->caste[unit->caste]->caste_id; + + //find all of the products it makes. Look for a stone with a low boiling point. bool foundIt = false; for ( size_t a = 0; a < reaction->products.size(); a++ ) { df::reaction_product_type type = reaction->products[a]->getType(); @@ -286,39 +289,47 @@ int32_t processJob(color_ostream& out, int32_t jobId) { continue; //for now don't worry about subtype + //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - const char* helper = "CAUSE_SYNDROME:"; - for ( size_t b = 0; b < inorganic->str.size(); b++ ) { - //out.print("inorganic str = \"%s\"\n", inorganic->str[b]->c_str()); - size_t c = inorganic->str[b]->find(helper); - if ( c == string::npos ) - continue; - string tail = inorganic->str[b]->substr(c + strlen(helper), inorganic->str[b]->length() - strlen(helper) - 2); - //out.print("tail = %s\n", tail.c_str()); + if ( inorganic->material.heat.boiling_point > 10000 ) + continue; - //find the syndrome with this name, and give apply it to the dwarf working on the job - //first find out who completed the job - if ( jobWorkers.find(jobId) == jobWorkers.end() ) { - out.print("%s, line %d: could not find job worker for jobs %d.\n", __FILE__, __LINE__, jobId); - return -1; + for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { + //add each syndrome to the guy who did the job + df::syndrome* syndrome = inorganic->material.syndrome[b]; + //check that the syndrome applies to that guy + bool applies = syndrome->syn_affected_creature_1.size() == 0; + if ( applies ) { + //out.print("No syn_affected_creature.\n"); } - int32_t workerId = jobWorkers[jobId]; - - //find the syndrome - df::syndrome* syndrome = NULL; - for ( size_t d = 0; d < df::global::world->raws.syndromes.all.size(); d++ ) { - df::syndrome* candidate = df::global::world->raws.syndromes.all[d]; - if ( candidate->syn_name != tail ) + for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature_1[c] ) continue; - syndrome = candidate; - break; + if ( *syndrome->syn_affected_creature_2[c] == "ALL" || + *syndrome->syn_affected_creature_2[c] == creature_caste ) { + applies = true; + break; + } + } + if ( !applies ) { + //out.print("Not in syn_affected_creature.\n"); + continue; + } + for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature_1[c] ) + continue; + if ( *syndrome->syn_immune_creature_2[c] == "ALL" || + *syndrome->syn_immune_creature_2[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + //out.print("Creature is immune.\n"); + continue; } - if ( syndrome == NULL ) - return 0; - if ( giveSyndrome(out, workerId, syndrome) < 0 ) return -1; - //out.print("Gave syndrome.\n"); } } if ( !foundIt ) From 2535b50bfc1d81952224d729da6ae189ab10b03e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 12 Dec 2012 22:25:23 -0600 Subject: [PATCH 332/472] Autolabor: add construct chain labor, add destruct trap labor, change overbroad test for military status (was catching uniformed reservists, who are eligible to do civilian labor) --- plugins/autolabor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c0ef5d114..357f9bd14 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -801,6 +801,7 @@ private: case df::building_type::NestBox: case df::building_type::TractionBench: case df::building_type::Slab: + case df::building_type::Chain: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -850,6 +851,8 @@ private: break; case df::building_type::FarmPlot: return df::unit_labor::PLANT; + case df::building_type::Trap: + return df::unit_labor::MECHANIC; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", @@ -1758,7 +1761,7 @@ private: { state = CHILD; } - else if (dwarf->dwarf->military.cur_uniform != 0) + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) state = MILITARY; else if (dwarf->dwarf->job.current_job == NULL) { From a28fc65e6d21578e31d25f3fa08133977cb5388d Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 13 Dec 2012 11:13:56 -0500 Subject: [PATCH 333/472] autoSyndrome now deals with creature_class. --- plugins/autoSyndrome.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 77217c6a0..a37d806fe 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -118,7 +118,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorunits.all[workerIndex]; df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; std::string& creature_name = creature->creature_id; - std::string& creature_caste = creature->caste[unit->caste]->caste_id; + std::string& creature_caste = caste->caste_id; //find all of the products it makes. Look for a stone with a low boiling point. bool foundIt = false; @@ -298,10 +299,40 @@ int32_t processJob(color_ostream& out, int32_t jobId) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; //check that the syndrome applies to that guy - bool applies = syndrome->syn_affected_creature_1.size() == 0; + bool applies = syndrome->syn_affected_class.size() == 0; if ( applies ) { + //out.print("No syn_affected_class.\n"); + } + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + if ( syndrome->syn_affected_creature_1.size() != 0 ) { + applies = false; + } else { //out.print("No syn_affected_creature.\n"); } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return -1; + } for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_affected_creature_1[c] ) continue; From bba96494f39b3fb2f9d1101a35d762e6552f24b1 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 13 Dec 2012 17:19:23 +0100 Subject: [PATCH 334/472] ruby: add unit_hostiles to list hostiles units (duh) --- plugins/ruby/unit.rb | 154 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 147 insertions(+), 7 deletions(-) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 5e2de110e..a5bf6e00e 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -103,18 +103,157 @@ module DFHack # some other stuff with ui.race_id ? (jobs only?) end + # merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id } + # diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id } + + + def unit_nemesis(u) + if ref = u.general_refs.find { |r| r.kind_of?(DFHack::GeneralRefIsNemesisst) } + ref.nemesis_tg + end + end + + # return the subcategory for :Others (from vs_unitlist) + def unit_other_category(u) + # comment is actual code returned by the df function + return :Berserk if u.mood == :Berserk # 5 + return :Berserk if unit_testflagcurse(u, :CRAZED) # 14 + return :Undead if unit_testflagcurse(u, :OPPOSED_TO_LIFE) # 1 + return :Undead if u.flags3.ghostly # 15 + + if df.gamemode == :ADVENTURE + return :Hostile if u.civ_id == -1 # 2 + if u.animal.population.region_x == -1 + return :Wild if u.flags2.roaming_wilderness_population_source_not_a_map_feature # 0 + else + return :Hostile if u.flags2.important_historical_figure and n = unit_nemesis(u) and n.flags[:ACTIVE_ADVENTURER] # 2 + end + return :Hostile if u.flags2.resident # 3 + return :Hostile # 4 + end + + return :Invader if u.flags1.active_invader or u.flags1.invader_origin # 6 + return :Friendly if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat # 8 + return :Hostile if u.flags1.tame # 7 + + if u.civ_id != -1 + return :Unsure if u.civ_id != df.ui.civ_id or u.flags1.resident or u.flags1.visitor or u.flags1.visitor_uninvited # 10 + return :Hostile # 7 + + elsif u.animal.population.region_x == -1 + return :Friendly if u.flags2.visitor # 8 + return :Uninvited if u.flags2.visitor_uninvited # 12 + return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9 + return :Resident if u.flags2.resident # 13 + return :Friendly # 8 + + else + return :Friendly if u.flags2.visitor # 8 + return :Underworld if r = u.race_tg and r.underground_layer_min == 5 # 9 + return :Wild if u.animal.population.feature_idx == -1 and u.animal.population.cave_id == -1 # 0 + return :Wild # 11 + end + end + def unit_iscitizen(u) unit_category(u) == :Citizens end + def unit_hostiles + world.units.active.find_all { |u| + unit_ishostile(u) + } + end + + # returns if an unit is openly hostile + # does not include ghosts / wildlife def unit_ishostile(u) - unit_category(u) == :Others and - # TODO - true + # return true if u.flags3.ghostly and not u.flags1.dead + return unless unit_category(u) == :Others + + case unit_other_category(u) + when :Berserk, :Undead, :Hostile, :Invader, :Underworld + # XXX :Resident, :Uninvited? + true + + when :Unsure + # from df code, with removed duplicate checks already in other_category + return true if u.enemy.undead or u.flags3.ghostly or u.flags1.marauder + return false if u.flags1.forest or u.flags1.merchant or u.flags1.diplomat or u.flags2.visitor + return true if u.flags1.tame or u.flags2.underworld + + if histfig = u.hist_figure_tg + group = df.ui.group_tg + case unit_checkdiplomacy_hf_ent(histfig, group) + when 4, 5 + true + end + + elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id) + diplo.relation != 1 and diplo.relation != 5 + + else + u.animal.population.region_x != -1 or u.flags2.resident or u.flags2.visitor_uninvited + end + end + end + + def unit_checkdiplomacy_hf_ent(histfig, group) + var_3d = var_3e = var_45 = var_46 = var_47 = var_48 = var_49 = nil + + var_3d = 1 if group.type == :Outcast or group.type == :NomadicGroup or + (group.type == :Civilization and group.entity_raw.flags[:LOCAL_BANDITRY]) + + histfig.entity_links.each { |link| + if link.entity_id == group.id + case link.getType + when :MEMBER, :MERCENARY, :SLAVE, :PRISONER, :POSITION, :HERO + var_47 = 1 + when :FORMER_MEMBER, :FORMER_MERCENARY, :FORMER_SLAVE, :FORMER_PRISONER + var_48 = 1 + when :ENEMY + var_49 = 1 + when :CRIMINAL + var_45 = 1 + end + else + case link.getType + when :MEMBER, :MERCENARY, :SLAVE + if link_entity = link.entity_tg + diplo = group.unknown1b.diplomacy.binsearch(link.entity_id, :group_id) + case diplo.relation + when 0, 3, 4 + var_48 = 1 + when 1, 5 + var_46 = 1 + end + + var_3e = 1 if link_entity.type == :Outcast or link_entity.type == :NomadicGroup or + (link_entity.type == :Civilization and link_entity.entity_raw.flags[:LOCAL_BANDITRY]) + end + end + end + } + + if var_49 + 4 + elsif var_46 + 5 + elsif !var_47 and group.resources.ethic[:KILL_NEUTRAL] == 16 + 4 + elsif df.gamemode == :ADVENTURE and !var_47 and (var_3e or !var_3d) + 4 + elsif var_45 + 3 + elsif var_47 + 2 + elsif var_48 + 1 + else + 0 + end end - # merchant: df.ui.caravans.find { |cv| cv.entity == u.civ_id } - # diplomat: df.ui.dip_meeting_info.find { |m| m.diplomat_id == u.hist_figure_id or m.diplomat_id2 == u.hist_figure_id } # list workers (citizen, not crazy / child / inmood / noble) def unit_workers @@ -125,6 +264,7 @@ module DFHack def unit_isworker(u) unit_iscitizen(u) and + u.race == df.ui.race_id and u.mood == :None and u.profession != :CHILD and u.profession != :BABY and @@ -153,8 +293,8 @@ module DFHack def unit_entitypositions(unit) list = [] - return list if not hf = unit.hist_figure_tg - hf.entity_links.each { |el| + return list if not histfig = unit.hist_figure_tg + histfig.entity_links.each { |el| next if el._rtti_classname != :histfig_entity_link_positionst next if not ent = el.entity_tg next if not pa = ent.positions.assignments.binsearch(el.assignment_id) From af7f11fdfac5b15227a5d65e757dc53926056fc4 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 13 Dec 2012 11:21:51 -0500 Subject: [PATCH 335/472] autoSyndrome: made the syndrome logic make more sense. --- plugins/autoSyndrome.cpp | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index a37d806fe..1298764de 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -299,10 +299,17 @@ int32_t processJob(color_ostream& out, int32_t jobId) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; //check that the syndrome applies to that guy - bool applies = syndrome->syn_affected_class.size() == 0; - if ( applies ) { - //out.print("No syn_affected_class.\n"); - } + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature_1.size() == 0; for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { if ( applies ) break; @@ -313,11 +320,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } } } - if ( syndrome->syn_affected_creature_1.size() != 0 ) { - applies = false; - } else { - //out.print("No syn_affected_creature.\n"); - } for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { if ( !applies ) break; @@ -342,10 +344,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { break; } } - if ( !applies ) { - //out.print("Not in syn_affected_creature.\n"); - continue; - } for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_immune_creature_1[c] ) continue; @@ -356,7 +354,6 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } } if ( !applies ) { - //out.print("Creature is immune.\n"); continue; } if ( giveSyndrome(out, workerId, syndrome) < 0 ) From e46d434c25e12a1731ea5b52b02035ef88c982c3 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 14 Dec 2012 16:33:26 +0200 Subject: [PATCH 336/472] Missing lua file for eventful. --- plugins/lua/eventful.lua | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 plugins/lua/eventful.lua diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua new file mode 100644 index 000000000..18d9fd7a0 --- /dev/null +++ b/plugins/lua/eventful.lua @@ -0,0 +1,7 @@ +local _ENV = mkmodule('plugins.eventful') +--[[ + Native events: +TODO +--]] + +return _ENV \ No newline at end of file From 08f454cc81a1735dd28c05f19ce00f422d9e8b35 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 14 Dec 2012 16:41:59 +0200 Subject: [PATCH 337/472] Removed old reactionhooks.lua --- plugins/lua/reactionhooks.lua | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 plugins/lua/reactionhooks.lua diff --git a/plugins/lua/reactionhooks.lua b/plugins/lua/reactionhooks.lua deleted file mode 100644 index 5f3622e2f..000000000 --- a/plugins/lua/reactionhooks.lua +++ /dev/null @@ -1,13 +0,0 @@ -local _ENV = mkmodule('plugins.reactionhooks') - ---[[ - - Native events: - - * onReactionComplete(burrow) - ---]] - -rawset_default(_ENV, dfhack.reactionhooks) - -return _ENV \ No newline at end of file From cd7c39f2db89204bd3ffdac94c818fd9975085b1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 14 Dec 2012 09:34:03 -0600 Subject: [PATCH 338/472] Autolabor: add deconstruct labor for cages --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 357f9bd14..49c2e4174 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -853,6 +853,8 @@ private: return df::unit_labor::PLANT; case df::building_type::Trap: return df::unit_labor::MECHANIC; + case df::building_type::Cage: + return df::unit_labor::HAUL_FURNITURE; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", From 747723187f342a983a35fa3aad8ae90741486f7d Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 21:05:38 -0500 Subject: [PATCH 339/472] EventManager: first draft. --- library/CMakeLists.txt | 2 ++ library/Core.cpp | 3 +++ library/include/modules/Job.h | 2 +- library/modules/Job.cpp | 4 ++-- plugins/CMakeLists.txt | 1 + plugins/eventExample.cpp | 32 ++++++++++++++++++++++++++++++++ 6 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 plugins/eventExample.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 6f33d5c8a..615c223c2 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -111,6 +111,7 @@ include/modules/Burrows.h include/modules/Constructions.h include/modules/Units.h include/modules/Engravings.h +include/modules/EventManager.h include/modules/Gui.h include/modules/Items.h include/modules/Job.h @@ -133,6 +134,7 @@ modules/Burrows.cpp modules/Constructions.cpp modules/Units.cpp modules/Engravings.cpp +modules/EventManager.cpp modules/Gui.cpp modules/Items.cpp modules/Job.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 26c0acbb0..a9164128f 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -44,6 +44,7 @@ using namespace std; #include "VersionInfo.h" #include "PluginManager.h" #include "ModuleFactory.h" +#include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/World.h" #include "modules/Graphic.h" @@ -1238,6 +1239,8 @@ static int buildings_timer = 0; void Core::onUpdate(color_ostream &out) { + EventManager::manageEvents(out); + // convert building reagents if (buildings_do_onupdate && (++buildings_timer & 1)) buildings_onUpdate(out); diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index 853813073..e865273d9 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -47,7 +47,7 @@ namespace DFHack { namespace Job { // Duplicate the job structure. It is not linked into any DF lists. - DFHACK_EXPORT df::job *cloneJobStruct(df::job *job); + DFHACK_EXPORT df::job *cloneJobStruct(df::job *job, bool keepWorkerData=false); // Delete a cloned structure. DFHACK_EXPORT void deleteJobStruct(df::job *job); diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index def3b4192..c0e18f44a 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -54,7 +54,7 @@ using namespace std; using namespace DFHack; using namespace df::enums; -df::job *DFHack::Job::cloneJobStruct(df::job *job) +df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepWorkerData) { CHECK_NULL_POINTER(job); @@ -75,7 +75,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) { df::general_ref *ref = pnew->references[i]; - if (virtual_cast(ref)) + if (!keepWorkerData && virtual_cast(ref)) vector_erase_at(pnew->references, i); else pnew->references[i] = ref->clone(); diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8aeeee8c3..16fffd422 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,6 +130,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(eventExample eventExample.cpp) endif() diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp new file mode 100644 index 000000000..ea8e2cfec --- /dev/null +++ b/plugins/eventExample.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "Export.h" +#include "modules/EventManager.h" +#include "DataDefs.h" + +using namespace DFHack; + +DFHACK_PLUGIN("eventExample"); + +void jobInitiated(color_ostream& out, void* job); +void jobCompleted(color_ostream& out, void* job); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + EventManager::EventHandler initiateHandler(jobInitiated); + EventManager::EventHandler completeHandler(jobCompleted); + + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, NULL); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, NULL); + + return CR_OK; +} + +void jobInitiated(color_ostream& out, void* job) { + out.print("Job initiated! 0x%X\n", job); +} + +void jobCompleted(color_ostream& out, void* job) { + out.print("Job completed! 0x%X\n", job); +} + From cf619a519eb031531609604fd29a6e60a0893691 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 22:14:38 -0500 Subject: [PATCH 340/472] EventManager: made event handlers unregister when plugins are unloaded. Also changed PluginManager so that plugins can call core.getPluginManager() during plugin_init. --- library/Core.cpp | 3 +++ library/PluginManager.cpp | 30 ++++++++++++++++++------------ library/include/PluginManager.h | 1 + plugins/eventExample.cpp | 6 ++++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index a9164128f..5c397dd05 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -905,6 +905,7 @@ bool Core::Init() cerr << "Initializing Plugins.\n"; // create plugin manager plug_mgr = new PluginManager(this); + plug_mgr->init(this); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1254,6 +1255,8 @@ void Core::onUpdate(color_ostream &out) void Core::onStateChange(color_ostream &out, state_change_event event) { + EventManager::onStateChange(out, event); + buildings_onStateChange(out, event); plug_mgr->OnStateChange(out, event); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 0c80639b4..86bab66cd 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -22,6 +22,7 @@ must not be misrepresented as being the original software. distribution. */ +#include "modules/EventManager.h" #include "Internal.h" #include "Core.h" #include "MemAccess.h" @@ -270,6 +271,7 @@ bool Plugin::unload(color_ostream &con) // if we are actually loaded if(state == PS_LOADED) { + EventManager::unregisterAll(this); // notify the plugin about an attempt to shutdown if (plugin_onstatechange && plugin_onstatechange(con, SC_BEGIN_UNLOAD) == CR_NOT_FOUND) @@ -598,6 +600,22 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) } PluginManager::PluginManager(Core * core) +{ + cmdlist_mutex = new mutex(); + eval_ruby = NULL; +} + +PluginManager::~PluginManager() +{ + for(size_t i = 0; i < all_plugins.size();i++) + { + delete all_plugins[i]; + } + all_plugins.clear(); + delete cmdlist_mutex; +} + +void PluginManager::init(Core * core) { #ifdef LINUX_BUILD string path = core->getHackPath() + "plugins/"; @@ -606,8 +624,6 @@ PluginManager::PluginManager(Core * core) string path = core->getHackPath() + "plugins\\"; const string searchstr = ".plug.dll"; #endif - cmdlist_mutex = new mutex(); - eval_ruby = NULL; vector filez; getdir(path, filez); for(size_t i = 0; i < filez.size();i++) @@ -622,16 +638,6 @@ PluginManager::PluginManager(Core * core) } } -PluginManager::~PluginManager() -{ - for(size_t i = 0; i < all_plugins.size();i++) - { - delete all_plugins[i]; - } - all_plugins.clear(); - delete cmdlist_mutex; -} - Plugin *PluginManager::getPluginByName (const std::string & name) { for(size_t i = 0; i < all_plugins.size(); i++) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 9ef16703a..62a195867 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -205,6 +205,7 @@ namespace DFHack friend class Plugin; PluginManager(Core * core); ~PluginManager(); + void init(Core* core); void OnUpdate(color_ostream &out); void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index ea8e2cfec..3b898ae7b 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -2,6 +2,7 @@ #include "Console.h" #include "Core.h" #include "Export.h" +#include "PluginManager.h" #include "modules/EventManager.h" #include "DataDefs.h" @@ -15,9 +16,10 @@ void jobCompleted(color_ostream& out, void* job); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, NULL); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, NULL); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); return CR_OK; } From 155a4d044c45cf58ccb0bcbbc8168793faa0bb60 Mon Sep 17 00:00:00 2001 From: expwnent Date: Fri, 14 Dec 2012 23:29:28 -0500 Subject: [PATCH 341/472] EventManager: fiddled with time events. Made it possible to register for time events before a world is loaded. Also added some files I forgot to add to the previous commit. --- library/include/modules/EventManager.h | 51 ++++++ library/modules/EventManager.cpp | 216 +++++++++++++++++++++++++ plugins/eventExample.cpp | 9 ++ 3 files changed, 276 insertions(+) create mode 100644 library/include/modules/EventManager.h create mode 100644 library/modules/EventManager.cpp diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h new file mode 100644 index 000000000..a357bbb81 --- /dev/null +++ b/library/include/modules/EventManager.h @@ -0,0 +1,51 @@ +#pragma once +#ifndef EVENT_MANAGER_H_INCLUDED +#define EVENT_MANAGER_H_INCLUDED + +#include "Core.h" +#include "Export.h" +#include "ColorText.h" +#include "PluginManager.h" +#include "Console.h" + +namespace DFHack { + namespace EventManager { + namespace EventType { + enum EventType { + NONE, + TICK, + TICK_TILE, + JOB_INITIATED, + JOB_COMPLETED, + LIFE, + CREATURE, + ITEM, + TILE, + EVENT_MAX=TILE + }; + } + + struct EventHandler { + void (*eventHandler)(color_ostream&, void*); //called when the event happens + + EventHandler(void (*eventHandlerIn)(color_ostream&, void*)): eventHandler(eventHandlerIn) { + } + + bool operator==(EventHandler& handle) const { + return eventHandler == handle.eventHandler; + } + bool operator!=(EventHandler& handle) const { + return !( *this == handle); + } + }; + + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void unregisterAll(Plugin* plugin); + void manageEvents(color_ostream& out); + void onStateChange(color_ostream& out, state_change_event event); + } +} + +#endif diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp new file mode 100644 index 000000000..c9a312841 --- /dev/null +++ b/library/modules/EventManager.cpp @@ -0,0 +1,216 @@ +#include "Core.h" +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/World.h" + +#include "df/job.h" +#include "df/global_objects.h" +#include "df/job_list_link.h" +#include "df/world.h" + +//#include +#include +//#include +using namespace std; +using namespace DFHack; +using namespace EventManager; + +/* + * TODO: + * error checking + **/ + +//map > tickQueue; +multimap tickQueue; + +multimap handlers[EventType::EVENT_MAX]; + +const uint32_t ticksPerYear = 403200; + +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { + handlers[e].insert(pair(plugin, handler)); +} + +void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( !Core::getInstance().isWorldLoaded() ) { + tick = 0; + } + + tickQueue.insert(pair(tick+(uint32_t)when, handler)); + handlers[EventType::TICK].insert(pair(plugin,handler)); + return; +} + +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { + for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { + if ( (*i).first != plugin ) + break; + EventHandler handle = (*i).second; + if ( handle == handler ) { + handlers[e].erase(i); + break; + } + } + return; +} + +void DFHack::EventManager::unregisterAll(Plugin* plugin) { + for ( auto i = handlers[EventType::TICK].find(plugin); i != handlers[EventType::TICK].end(); i++ ) { + if ( (*i).first != plugin ) + break; + + //shenanigans to avoid concurrent modification + EventHandler getRidOf = (*i).second; + bool didSomething; + do { + didSomething = false; + for ( auto j = tickQueue.begin(); j != tickQueue.end(); j++ ) { + EventHandler candidate = (*j).second; + if ( getRidOf != candidate ) + continue; + tickQueue.erase(j); + didSomething = true; + break; + } + } while(didSomething); + } + for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { + handlers[a].erase(plugin); + } + return; +} + +static void manageTickEvent(color_ostream& out); +static void manageJobInitiatedEvent(color_ostream& out); +static void manageJobCompletedEvent(color_ostream& out); + +uint32_t lastTick = 0; +int32_t lastJobId = -1; +static map prevJobs; + +void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { + if ( event == DFHack::SC_MAP_UNLOADED ) { + lastTick = 0; + lastJobId = -1; + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + tickQueue.clear(); + } else if ( event == DFHack::SC_MAP_LOADED ) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + multimap newTickQueue; + for ( auto i = tickQueue.begin(); i != tickQueue.end(); i++ ) { + newTickQueue.insert(pair(tick + (*i).first, (*i).second)); + } + tickQueue.clear(); + + tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); + } +} + +void DFHack::EventManager::manageEvents(color_ostream& out) { + if ( !Core::getInstance().isWorldLoaded() ) { + return; + } + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + if ( tick <= lastTick ) + return; + lastTick = tick; + + manageTickEvent(out); + manageJobInitiatedEvent(out); + manageJobCompletedEvent(out); + + return; +} + +static void manageTickEvent(color_ostream& out) { + uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + + DFHack::World::ReadCurrentTick(); + while ( !tickQueue.empty() ) { + if ( tick < (*tickQueue.begin()).first ) + break; + EventHandler handle = (*tickQueue.begin()).second; + tickQueue.erase(tickQueue.begin()); + handle.eventHandler(out, (void*)tick); + } + +} + +static void manageJobInitiatedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_INITIATED].empty() ) + return; + + if ( lastJobId == -1 ) { + lastJobId = *df::global::job_next_id - 1; + return; + } + + if ( lastJobId+1 == *df::global::job_next_id ) { + return; //no new jobs + } + + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + if ( link->item->id <= lastJobId ) + continue; + for ( multimap::iterator i = handlers[EventType::JOB_INITIATED].begin(); i != handlers[EventType::JOB_INITIATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)link->item); + } + } + + lastJobId = *df::global::job_next_id - 1; +} + + +static void manageJobCompletedEvent(color_ostream& out) { + if ( handlers[EventType::JOB_COMPLETED].empty() ) { + return; + } + + map nowJobs; + for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { + if ( link->item == NULL ) + continue; + nowJobs[link->item->id] = link->item; + } + + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + if ( nowJobs.find((*i).first) != nowJobs.end() ) + continue; + + //recently finished or cancelled job! + for ( multimap::iterator j = handlers[EventType::JOB_COMPLETED].begin(); j != handlers[EventType::JOB_COMPLETED].end(); j++ ) { + (*j).second.eventHandler(out, (void*)(*i).second); + } + } + + //erase old jobs, copy over possibly altered jobs + for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + Job::deleteJobStruct((*i).second); + } + prevJobs.clear(); + + //create new jobs + for ( map::iterator j = nowJobs.begin(); j != nowJobs.end(); j++ ) { + /*map::iterator i = prevJobs.find((*j).first); + if ( i != prevJobs.end() ) { + continue; + }*/ + + df::job* newJob = Job::cloneJobStruct((*j).second, true); + prevJobs[newJob->id] = newJob; + } + + /*//get rid of old pointers to deallocated jobs + for ( size_t a = 0; a < toDelete.size(); a++ ) { + prevJobs.erase(a); + }*/ +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index 3b898ae7b..c2774f422 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -12,14 +12,20 @@ DFHACK_PLUGIN("eventExample"); void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); +void timePassed(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); + EventManager::EventHandler timeHandler(timePassed); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); + EventManager::registerTick(timeHandler, 1, me); + EventManager::registerTick(timeHandler, 2, me); + EventManager::registerTick(timeHandler, 4, me); + EventManager::registerTick(timeHandler, 8, me); return CR_OK; } @@ -32,3 +38,6 @@ void jobCompleted(color_ostream& out, void* job) { out.print("Job completed! 0x%X\n", job); } +void timePassed(color_ostream& out, void* ptr) { + out.print("Time: %d\n", (int32_t)(ptr)); +} From 4af6b728b7b4252c3b2cd0abf487eafce5987179 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 15 Dec 2012 13:27:16 +0200 Subject: [PATCH 342/472] Small fix to gm-editor. Added proj_itemst and proj_unitst to eventful. Now you can make custom projectiles (e.g. rockets?) --- Lua API.rst | 16 +++++++++-- Readme.rst | 30 -------------------- plugins/eventful.cpp | 59 +++++++++++++++++++++++++++++++++++++++ scripts/gui/gm-editor.lua | 5 ++-- 4 files changed, 76 insertions(+), 34 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 8906d1d44..7315f63a0 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2965,6 +2965,10 @@ List of events 1. onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native) - auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. 2. onItemContaminateWound(item,unit,wound,number1,number2) - Is called when item tries to contaminate wound (e.g. stuck in) +3. onProjItemCheckMovement(projectile) - is called when projectile moves +4. onProjItemCheckImpact(projectile,somebool) - is called when projectile hits something +5. onProjUnitCheckMovement(projectile) - is called when projectile moves +6. onProjUnitCheckImpact(projectile,somebool) - is called when projectile hits something Examples -------- @@ -2976,15 +2980,23 @@ Spawn dragon breath on each item attempt to contaminate wound: local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000) end -Reaction complete example" +Reaction complete example: :: - + b=require "plugins.eventful" b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native) local pos=copyall(unit.pos) dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) -- spawn dragonbreath after 100 ticks call_native.value=false --do not call real item creation code end +Granade example: +:: + b=require "plugins.eventful" + b.onProjItemCheckImpact.one=function(projectile) + -- you can check if projectile.item e.g. has correct material + dfhack.maps.spawnFlow(projectile.cur_pos,6,0,0,50000) + end + ======= Scripts ======= diff --git a/Readme.rst b/Readme.rst index 7fb3a5565..60fd1b78f 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2608,33 +2608,3 @@ be bought from caravans. :) To be really useful this needs patches from bug 808, ``tweak fix-dimensions`` and ``tweak advmode-contained``. -Eventful -======== - -This plugin exports some events to lua thus allowing to run lua functions -on DF world events. - -List of events --------------- - -1. onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native) - auto activates if detects reactions starting with ``LUA_HOOK_``. Is called when reaction finishes. -2. onItemContaminateWound(item,unit,wound,number1,number2) - Is called when item tries to contaminate wound (e.g. stuck in) - -Examples --------- -Spawn dragon breath on each item attempt to contaminate wound: -:: - - b=require "plugins.eventful" - b.onItemContaminateWound.one=function(item,unit,un_wound,x,y) - local flw=dfhack.maps.spawnFlow(unit.pos,6,0,0,50000) - end - -Reaction complete example" -:: - - b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native) - local pos=copyall(unit.pos) - dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) -- spawn dragonbreath after 100 ticks - call_native.value=false --do not call real item creation code - end diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 267f01c29..02df231c4 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -14,6 +14,9 @@ #include "df/reaction_reagent_itemst.h" #include "df/reaction_product_itemst.h" +#include "df/proj_itemst.h" +#include "df/proj_unitst.h" + #include "MiscUtils.h" #include "LuaTools.h" @@ -85,13 +88,26 @@ static bool is_lua_hook(const std::string &name) static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag , std::vector *out_items,bool *call_native){}; static void handle_contaminate_wound(color_ostream &out,df::item_actual*,df::unit* unit, df::unit_wound* wound, uint8_t a1, int16_t a2){}; +static void handle_projitem_ci(color_ostream &out,df::proj_itemst*,bool){}; +static void handle_projitem_cm(color_ostream &out,df::proj_itemst*){}; +static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){}; +static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){}; DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t ); +//projectiles +DEFINE_LUA_EVENT_2(onProjItemCheckImpact, handle_projitem_ci, df::proj_itemst*,bool ); +DEFINE_LUA_EVENT_1(onProjItemCheckMovement, handle_projitem_cm, df::proj_itemst*); +DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,bool ); +DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* ); DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onReactionComplete), DFHACK_LUA_EVENT(onItemContaminateWound), + DFHACK_LUA_EVENT(onProjItemCheckImpact), + DFHACK_LUA_EVENT(onProjItemCheckMovement), + DFHACK_LUA_EVENT(onProjUnitCheckImpact), + DFHACK_LUA_EVENT(onProjUnitCheckMovement), DFHACK_LUA_END }; @@ -134,10 +150,49 @@ struct item_hooks :df::item_actual { onItemContaminateWound(out,this,unit,wound,a1,a2); INTERPOSE_NEXT(contaminateWound)(unit,wound,a1,a2); } + }; IMPLEMENT_VMETHOD_INTERPOSE(item_hooks, contaminateWound); +struct proj_item_hook: df::proj_itemst{ + typedef df::proj_itemst interpose_base; + DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode)) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onProjItemCheckImpact(out,this,mode); + return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not? + } + DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,()) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onProjItemCheckMovement(out,this); + return INTERPOSE_NEXT(checkMovement)(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkImpact); +IMPLEMENT_VMETHOD_INTERPOSE(proj_item_hook,checkMovement); +struct proj_unit_hook: df::proj_unitst{ + typedef df::proj_unitst interpose_base; + DEFINE_VMETHOD_INTERPOSE(bool,checkImpact,(bool mode)) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onProjUnitCheckImpact(out,this,mode); + return INTERPOSE_NEXT(checkImpact)(mode); //returns destroy item or not? + } + DEFINE_VMETHOD_INTERPOSE(bool,checkMovement,()) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onProjUnitCheckMovement(out,this); + return INTERPOSE_NEXT(checkMovement)(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkImpact); +IMPLEMENT_VMETHOD_INTERPOSE(proj_unit_hook,checkMovement); /* * Scan raws for matching reactions. */ @@ -192,6 +247,10 @@ static bool find_reactions(color_ostream &out) static void enable_hooks(bool enable) { INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable); + INTERPOSE_HOOK(proj_unit_hook,checkImpact).apply(enable); + INTERPOSE_HOOK(proj_unit_hook,checkMovement).apply(enable); + INTERPOSE_HOOK(proj_item_hook,checkImpact).apply(enable); + INTERPOSE_HOOK(proj_item_hook,checkMovement).apply(enable); } static void world_specific_hooks(color_ostream &out,bool enable) { diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 54a2fc13d..6f3683c55 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -215,8 +215,9 @@ function GmEditorUi:onInput(keys) end elseif keys[keybindings.offset.key] then local trg=self:currentTarget() - local size,off=df.sizeof(trg.target:_field(trg.keys[trg.selected])) - dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d",size,off,size,off),COLOR_WHITE) + local _,stoff=df.sizeof(trg.target) + local size,off=df.sizeof(trg.target:_field(self:getSelectedKey())) + dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE) --elseif keys.CUSTOM_ALT_F then --filter? elseif keys[keybindings.find.key] then self:find() From b0314755e0c03d66b6c11abccdffd1b5583d606e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 14:40:11 -0500 Subject: [PATCH 343/472] EventManager: added unit death event. --- library/include/modules/EventManager.h | 9 +++----- library/modules/EventManager.cpp | 31 ++++++++++++++++++++++++-- plugins/eventExample.cpp | 7 ++++++ 3 files changed, 39 insertions(+), 8 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index a357bbb81..0794b29e3 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -14,14 +14,11 @@ namespace DFHack { enum EventType { NONE, TICK, - TICK_TILE, JOB_INITIATED, JOB_COMPLETED, - LIFE, - CREATURE, - ITEM, - TILE, - EVENT_MAX=TILE + UNIT_DEATH, + //ITEM_CREATED, + EVENT_MAX=UNIT_DEATH }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index c9a312841..7e97cc3a4 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -6,6 +6,7 @@ #include "df/job.h" #include "df/global_objects.h" #include "df/job_list_link.h" +#include "df/unit.h" #include "df/world.h" //#include @@ -85,10 +86,12 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { static void manageTickEvent(color_ostream& out); static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); +static void manageUnitDeathEvent(color_ostream& out); -uint32_t lastTick = 0; -int32_t lastJobId = -1; +static uint32_t lastTick = 0; +static int32_t lastJobId = -1; static map prevJobs; +static set livingUnits; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -99,6 +102,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event } prevJobs.clear(); tickQueue.clear(); + livingUnits.clear(); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -125,6 +129,7 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageTickEvent(out); manageJobInitiatedEvent(out); manageJobCompletedEvent(out); + manageUnitDeathEvent(out); return; } @@ -214,3 +219,25 @@ static void manageJobCompletedEvent(color_ostream& out) { }*/ } +static void manageUnitDeathEvent(color_ostream& out) { + if ( handlers[EventType::UNIT_DEATH].empty() ) { + return; + } + + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit->counters.death_id == -1 ) { + livingUnits.insert(unit->id); + continue; + } + //dead: if dead since last check, trigger events + if ( livingUnits.find(unit->id) == livingUnits.end() ) + continue; + + for ( auto i = handlers[EventType::UNIT_DEATH].begin(); i != handlers[EventType::UNIT_DEATH].end(); i++ ) { + (*i).second.eventHandler(out, (void*)unit->id); + } + livingUnits.erase(unit->id); + } +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index c2774f422..2951c7f82 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -13,11 +13,13 @@ DFHACK_PLUGIN("eventExample"); void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); +void unitDeath(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); + EventManager::EventHandler deathHandler(unitDeath); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); @@ -26,6 +28,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 15 Dec 2012 16:49:13 -0500 Subject: [PATCH 344/472] EventManager: added item creation event. --- library/include/modules/EventManager.h | 4 +-- library/modules/EventManager.cpp | 42 ++++++++++++++++++++++++-- plugins/eventExample.cpp | 18 +++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 0794b29e3..31ac01c18 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -17,8 +17,8 @@ namespace DFHack { JOB_INITIATED, JOB_COMPLETED, UNIT_DEATH, - //ITEM_CREATED, - EVENT_MAX=UNIT_DEATH + ITEM_CREATED, + EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 7e97cc3a4..63455b664 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -3,8 +3,9 @@ #include "modules/Job.h" #include "modules/World.h" -#include "df/job.h" #include "df/global_objects.h" +#include "df/item.h" +#include "df/job.h" #include "df/job_list_link.h" #include "df/unit.h" #include "df/world.h" @@ -87,11 +88,13 @@ static void manageTickEvent(color_ostream& out); static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); static void manageUnitDeathEvent(color_ostream& out); +static void manageItemCreationEvent(color_ostream& out); static uint32_t lastTick = 0; static int32_t lastJobId = -1; static map prevJobs; static set livingUnits; +static int32_t nextItem; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -103,6 +106,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event prevJobs.clear(); tickQueue.clear(); livingUnits.clear(); + nextItem = -1; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -113,6 +117,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.clear(); tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); + + nextItem = *df::global::item_next_id; } } @@ -130,7 +136,8 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageJobInitiatedEvent(out); manageJobCompletedEvent(out); manageUnitDeathEvent(out); - + manageItemCreationEvent(out); + return; } @@ -241,3 +248,34 @@ static void manageUnitDeathEvent(color_ostream& out) { } } +static void manageItemCreationEvent(color_ostream& out) { + if ( handlers[EventType::ITEM_CREATED].empty() ) { + return; + } + + if ( nextItem >= *df::global::item_next_id ) { + return; + } + + size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); + for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { + df::item* item = df::global::world->items.all[a]; + //invaders + if ( item->flags.bits.foreign ) + continue; + //traders who bring back your items? + if ( item->flags.bits.trader ) + continue; + //migrants + if ( item->flags.bits.owned ) + continue; + //spider webs don't count + if ( item->flags.bits.spider_web ) + continue; + for ( auto i = handlers[EventType::ITEM_CREATED].begin(); i != handlers[EventType::ITEM_CREATED].end(); i++ ) { + (*i).second.eventHandler(out, (void*)item->id); + } + } + nextItem = *df::global::item_next_id; +} + diff --git a/plugins/eventExample.cpp b/plugins/eventExample.cpp index 2951c7f82..04657f1fd 100644 --- a/plugins/eventExample.cpp +++ b/plugins/eventExample.cpp @@ -6,6 +6,9 @@ #include "modules/EventManager.h" #include "DataDefs.h" +#include "df/item.h" +#include "df/world.h" + using namespace DFHack; DFHACK_PLUGIN("eventExample"); @@ -14,12 +17,14 @@ void jobInitiated(color_ostream& out, void* job); void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); +void itemCreate(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); EventManager::EventHandler deathHandler(unitDeath); + EventManager::EventHandler itemHandler(itemCreate); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); @@ -29,6 +34,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vectoritems.all, (int32_t)ptr); + if ( item_index == -1 ) { + out.print("%s, %d: Error.\n", __FILE__, __LINE__); + } + df::item* item = df::global::world->items.all[item_index]; + df::item_type type = item->getType(); + df::coord pos = item->pos; + out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); +} + From 935058f0a5edbdc52476d4a5e33d766f6fb650bd Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 17:43:41 -0500 Subject: [PATCH 345/472] EventManager: moved files around, made eventExample not run by default, and got rid of the silly NONE event type. --- library/include/modules/EventManager.h | 1 - plugins/CMakeLists.txt | 1 - plugins/devel/CMakeLists.txt | 1 + plugins/{ => devel}/eventExample.cpp | 13 ++++++++++++- 4 files changed, 13 insertions(+), 3 deletions(-) rename plugins/{ => devel}/eventExample.cpp (87%) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 31ac01c18..1a6f15245 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -12,7 +12,6 @@ namespace DFHack { namespace EventManager { namespace EventType { enum EventType { - NONE, TICK, JOB_INITIATED, JOB_COMPLETED, diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 16fffd422..8aeeee8c3 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -130,7 +130,6 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) - DFHACK_PLUGIN(eventExample eventExample.cpp) endif() diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..80d627fa9 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(eventExample eventExample.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/eventExample.cpp b/plugins/devel/eventExample.cpp similarity index 87% rename from plugins/eventExample.cpp rename to plugins/devel/eventExample.cpp index 04657f1fd..2099be110 100644 --- a/plugins/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -9,7 +9,10 @@ #include "df/item.h" #include "df/world.h" +#include + using namespace DFHack; +using namespace std; DFHACK_PLUGIN("eventExample"); @@ -19,7 +22,15 @@ void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); +command_result eventExample(color_ostream& out, vector& parameters); + + DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); + return CR_OK; +} + +command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler initiateHandler(jobInitiated); EventManager::EventHandler completeHandler(jobCompleted); EventManager::EventHandler timeHandler(timePassed); @@ -35,7 +46,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector Date: Sat, 15 Dec 2012 18:08:59 -0500 Subject: [PATCH 346/472] EventManager: Allowed absolute time registration. --- library/include/modules/EventManager.h | 2 +- library/modules/EventManager.cpp | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1a6f15245..c1c09da7d 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -36,7 +36,7 @@ namespace DFHack { }; DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); - DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin); + DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 63455b664..553a69668 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -1,4 +1,5 @@ #include "Core.h" +#include "Console.h" #include "modules/EventManager.h" #include "modules/Job.h" #include "modules/World.h" @@ -33,11 +34,17 @@ void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handlers[e].insert(pair(plugin, handler)); } -void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin) { +void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); if ( !Core::getInstance().isWorldLoaded() ) { tick = 0; + if ( absolute ) { + Core::getInstance().getConsole().print("Warning: absolute flag will not be honored.\n"); + } + } + if ( absolute ) { + tick = 0; } tickQueue.insert(pair(tick+(uint32_t)when, handler)); From 3e5537e3213d2c6d85ec783314b07cbf8efa4137 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 18:47:51 -0500 Subject: [PATCH 347/472] autoSyndrome: made it use EventManager. --- plugins/autoSyndrome.cpp | 151 ++++++++++++--------------------------- 1 file changed, 47 insertions(+), 104 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 1298764de..0f8b1ced3 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -2,6 +2,11 @@ #include "Export.h" #include "DataDefs.h" #include "Core.h" + +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/Maps.h" + #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" @@ -18,8 +23,6 @@ #include "df/general_ref.h" #include "df/general_ref_type.h" #include "df/general_ref_unit_workerst.h" -#include "modules/Maps.h" -#include "modules/Job.h" #include #include @@ -92,15 +95,12 @@ Next, start a new fort in a new world, build a duck workshop, then have someone */ const int32_t ticksPerYear = 403200; -int32_t lastRun = 0; -unordered_map prevJobs; -unordered_map jobWorkers; bool enabled = true; DFHACK_PLUGIN("autoSyndrome"); command_result autoSyndrome(color_ostream& out, vector& parameters); -int32_t processJob(color_ostream& out, int32_t id); +void processJob(color_ostream& out, void* jobPtr); int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { @@ -123,6 +123,9 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); + EventManager::EventHandler handle(processJob); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); return CR_OK; } @@ -130,85 +133,6 @@ DFhackCExport command_result plugin_shutdown(color_ostream& out) { return CR_OK; } -DFhackCExport command_result plugin_onupdate(color_ostream& out) { - if ( !enabled ) - return CR_OK; - if(DFHack::Maps::IsValid() == false) { - return CR_OK; - } - - //don't run more than once per tick - int32_t time = (*df::global::cur_year)*ticksPerYear + (*df::global::cur_year_tick); - if ( time <= lastRun ) - return CR_OK; - lastRun = time; - - //keep track of all queued jobs. When one completes (and is not cancelled), check if it's a boiling rock job, and if so, give the worker the appropriate syndrome - unordered_map jobs; - df::job_list_link* link = &df::global::world->job_list; - for( ; link != NULL; link = link->next ) { - df::job* item = link->item; - if ( item == NULL ) - continue; - //-1 usually means it hasn't been assigned yet. - if ( item->completion_timer < 0 ) - continue; - //if the completion timer is more than one, then the job will never disappear next tick unless it's cancelled - if ( item->completion_timer > 1 ) - continue; - - //only consider jobs that have been started - int32_t workerId = -1; - for ( size_t a = 0; a < item->references.size(); a++ ) { - if ( item->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) - continue; - if ( workerId != -1 ) { - out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); - } - workerId = ((df::general_ref_unit_workerst*)item->references[a])->unit_id; - } - if ( workerId == -1 ) - continue; - - jobs[item->id] = item; - jobWorkers[item->id] = workerId; - } - - //if it's not on the job list anymore, and its completion timer was 0, then it probably finished and was not cancelled, so process the job. - for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* completion = (*i).second; - if ( jobs.find(id) != jobs.end() ) - continue; - if ( completion->completion_timer > 0 ) - continue; - if ( processJob(out, id) < 0 ) { - //enabled = false; - return CR_FAILURE; - } - } - - //delete obselete job copies - for ( unordered_map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* oldJob = (*i).second; - DFHack::Job::deleteJobStruct(oldJob); - if ( jobs.find(id) == jobs.end() ) - jobWorkers.erase(id); - } - prevJobs.clear(); - - //make copies of the jobs we looked at this tick in case they disappear next frame. - for ( unordered_map::iterator i = jobs.begin(); i != jobs.end(); i++ ) { - int32_t id = (*i).first; - df::job* oldJob = (*i).second; - df::job* jobCopy = DFHack::Job::cloneJobStruct(oldJob); - prevJobs[id] = jobCopy; - } - - return CR_OK; -} - /*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { return CR_OK; }*/ @@ -217,6 +141,7 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; + bool wasEnabled = enabled; if ( parameters.size() == 1 ) { if ( parameters[0] == "enable" ) { enabled = true; @@ -232,22 +157,31 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { } out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + if ( enabled == wasEnabled ) + return CR_OK; + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + if ( enabled ) { + EventManager::EventHandler handle(processJob); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + } else { + EventManager::unregisterAll(me); + } return CR_OK; } -int32_t processJob(color_ostream& out, int32_t jobId) { - df::job* job = prevJobs[jobId]; +void processJob(color_ostream& out, void* jobPtr) { + df::job* job = (df::job*)jobPtr; if ( job == NULL ) { - out.print("Error %s line %d: couldn't find job %d.\n", __FILE__, __LINE__, jobId); - return -1; + out.print("Error %s line %d: null job.\n", __FILE__, __LINE__); + return; } + if ( job->completion_timer > 0 ) + return; if ( job->job_type != df::job_type::CustomReaction ) - return 0; + return; - //find the custom reaction raws and see if we have any special tags there - //out.print("job: \"%s\"\n", job->reaction_name.c_str()); - df::reaction* reaction = NULL; for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { df::reaction* candidate = df::global::world->raws.reactions[a]; @@ -258,18 +192,27 @@ int32_t processJob(color_ostream& out, int32_t jobId) { } if ( reaction == NULL ) { out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); - return -1; + return; } - - if ( jobWorkers.find(jobId) == jobWorkers.end() ) { - out.print("%s, line %d: couldn't find worker for job %d.\n", __FILE__, __LINE__, jobId); - return -1; + + int32_t workerId = -1; + for ( size_t a = 0; a < job->references.size(); a++ ) { + if ( job->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)job->references[a])->unit_id; + if (workerId == -1) { + out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); + continue; + } } - int32_t workerId = jobWorkers[jobId]; + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); if ( workerIndex < 0 ) { out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); - return -1; + return; } df::unit* unit = df::global::world->units.all[workerIndex]; df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; @@ -333,7 +276,7 @@ int32_t processJob(color_ostream& out, int32_t jobId) { if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); - return -1; + return; } for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { if ( creature_name != *syndrome->syn_affected_creature_1[c] ) @@ -357,13 +300,13 @@ int32_t processJob(color_ostream& out, int32_t jobId) { continue; } if ( giveSyndrome(out, workerId, syndrome) < 0 ) - return -1; + return; } } if ( !foundIt ) - return 0; + return; - return -2; + return; } /* From 75db99a3c79c6f410c8db3e13117ff9ef5911422 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 15 Dec 2012 18:49:45 -0500 Subject: [PATCH 348/472] autoSyndrome: deleted an unused constant. --- plugins/autoSyndrome.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 0f8b1ced3..f2c86bdfd 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -94,7 +94,6 @@ reaction_duck Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. */ -const int32_t ticksPerYear = 403200; bool enabled = true; DFHACK_PLUGIN("autoSyndrome"); From 8bf359ba028093e8187c78ab3bec592e1519deb7 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 15 Dec 2012 21:18:06 -0600 Subject: [PATCH 349/472] Autolabor: add labor for ivory & horn crafts (yawn) --- plugins/autolabor.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 49c2e4174..3d0af7289 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -886,7 +886,9 @@ private: case df::item_type::BOULDER: return df::unit_labor::STONE_CRAFT; case df::item_type::NONE: - if (j->material_category.bits.bone) + if (j->material_category.bits.bone || + j->material_category.bits.horn || + j->material_category.bits.tooth) return df::unit_labor::BONE_CARVE; else { From 4ac6d9c0c39bffe1a16bd3ae638650047f6a4c3b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 16 Dec 2012 00:03:26 -0600 Subject: [PATCH 350/472] Autolabor: add a number of destroy furniture labors (all "haul furniture") --- plugins/autolabor.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 3d0af7289..a2bdb4c9f 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -853,7 +853,24 @@ private: return df::unit_labor::PLANT; case df::building_type::Trap: return df::unit_labor::MECHANIC; + case df::building_type::Chair: + case df::building_type::Bed: + case df::building_type::Table: + case df::building_type::Coffin: + case df::building_type::Door: + case df::building_type::Floodgate: + case df::building_type::Box: + case df::building_type::Weaponrack: + case df::building_type::Armorstand: + case df::building_type::Cabinet: + case df::building_type::Statue: + case df::building_type::WindowGlass: + case df::building_type::WindowGem: case df::building_type::Cage: + case df::building_type::NestBox: + case df::building_type::TractionBench: + case df::building_type::Slab: + case df::building_type::Chain: return df::unit_labor::HAUL_FURNITURE; } From 3951d4d204ddaeaad7f393fadae3a98f27c69c4d Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 15:39:39 -0500 Subject: [PATCH 351/472] EventManager: made it safe to register/unregister while events are being triggered. --- library/modules/EventManager.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 553a69668..d8ea0d5de 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -173,13 +173,14 @@ static void manageJobInitiatedEvent(color_ostream& out) { if ( lastJobId+1 == *df::global::job_next_id ) { return; //no new jobs } + multimap copy(handlers[EventType::JOB_INITIATED].begin(), handlers[EventType::JOB_INITIATED].end()); for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { if ( link->item == NULL ) continue; if ( link->item->id <= lastJobId ) continue; - for ( multimap::iterator i = handlers[EventType::JOB_INITIATED].begin(); i != handlers[EventType::JOB_INITIATED].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)link->item); } } @@ -193,6 +194,7 @@ static void manageJobCompletedEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::JOB_COMPLETED].begin(), handlers[EventType::JOB_COMPLETED].end()); map nowJobs; for ( df::job_list_link* link = &df::global::world->job_list; link != NULL; link = link->next ) { if ( link->item == NULL ) @@ -205,7 +207,7 @@ static void manageJobCompletedEvent(color_ostream& out) { continue; //recently finished or cancelled job! - for ( multimap::iterator j = handlers[EventType::JOB_COMPLETED].begin(); j != handlers[EventType::JOB_COMPLETED].end(); j++ ) { + for ( auto j = copy.begin(); j != copy.end(); j++ ) { (*j).second.eventHandler(out, (void*)(*i).second); } } @@ -238,6 +240,7 @@ static void manageUnitDeathEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::UNIT_DEATH].begin(), handlers[EventType::UNIT_DEATH].end()); for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { df::unit* unit = df::global::world->units.active[a]; if ( unit->counters.death_id == -1 ) { @@ -248,7 +251,7 @@ static void manageUnitDeathEvent(color_ostream& out) { if ( livingUnits.find(unit->id) == livingUnits.end() ) continue; - for ( auto i = handlers[EventType::UNIT_DEATH].begin(); i != handlers[EventType::UNIT_DEATH].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)unit->id); } livingUnits.erase(unit->id); @@ -264,6 +267,7 @@ static void manageItemCreationEvent(color_ostream& out) { return; } + multimap copy(handlers[EventType::ITEM_CREATED].begin(), handlers[EventType::ITEM_CREATED].end()); size_t index = df::item::binsearch_index(df::global::world->items.all, nextItem, false); for ( size_t a = index; a < df::global::world->items.all.size(); a++ ) { df::item* item = df::global::world->items.all[a]; @@ -279,7 +283,7 @@ static void manageItemCreationEvent(color_ostream& out) { //spider webs don't count if ( item->flags.bits.spider_web ) continue; - for ( auto i = handlers[EventType::ITEM_CREATED].begin(); i != handlers[EventType::ITEM_CREATED].end(); i++ ) { + for ( auto i = copy.begin(); i != copy.end(); i++ ) { (*i).second.eventHandler(out, (void*)item->id); } } From 78aab90f3aa8a11bcdf741328684d8ed4fec4481 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 16:27:08 -0500 Subject: [PATCH 352/472] EventManager: whitespace. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index d8ea0d5de..187a95819 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -188,7 +188,6 @@ static void manageJobInitiatedEvent(color_ostream& out) { lastJobId = *df::global::job_next_id - 1; } - static void manageJobCompletedEvent(color_ostream& out) { if ( handlers[EventType::JOB_COMPLETED].empty() ) { return; From 01e5e938257ada98c2582b4b11ab84b8cb0f5035 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:26:50 -0500 Subject: [PATCH 353/472] Renamed Maps::canWalkBetween to Maps::canPathBetween and added Maps::canWalkBetween, which does what it says. --- library/LuaApi.cpp | 2 +- library/include/modules/Maps.h | 1 + library/modules/Maps.cpp | 46 +++++++++++++++++- plugins/devel/CMakeLists.txt | 1 + plugins/devel/walkBetween.cpp | 87 ++++++++++++++++++++++++++++++++++ plugins/fix-armory.cpp | 2 +- 6 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 plugins/devel/walkBetween.cpp diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 0151ed404..720ccd21f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1309,7 +1309,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, enableBlockUpdates), WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), - WRAPM(Maps, canWalkBetween), + WRAPM(Maps, canPathBetween), WRAPM(Maps, spawnFlow), WRAPN(hasTileAssignment, hasTileAssignment), WRAPN(getTileAssignment, getTileAssignment), diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 632e8ec13..2655268d7 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -308,6 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); +DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); } } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 482b950ba..0db69ea82 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -521,7 +521,7 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } -bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +bool Maps::canPathBetween(df::coord pos1, df::coord pos2) { auto block1 = getTileBlock(pos1); auto block2 = getTileBlock(pos2); @@ -535,6 +535,50 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return tile1 && tile1 == tile2; } +bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +{ + bool b = canPathBetween(pos1, pos2); + if ( !b ) + return false; + + int32_t dx = pos1.x-pos2.x; + int32_t dy = pos1.y-pos2.y; + int32_t dz = pos1.z-pos2.z; + + if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 ) + return false; + + if ( dz == 0 ) + return true; + + df::tiletype* type1 = Maps::getTileType(pos1); + df::tiletype* type2 = Maps::getTileType(pos2); + + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1); + df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); + + if ( pos2.z < pos1.z ) { + df::tiletype_shape temp = shape1; + shape1 = shape2; + shape2 = temp; + } + if ( dx == 0 && dy == 0 ) { + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) + return true; + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN ) + return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN ) + return true; + return false; + } + + //diagonal up: has to be a ramp + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) + return true; + + return false; +} + #define COPY(a,b) memcpy(&a,&b,sizeof(a)) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..41fcd8130 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -21,3 +21,4 @@ DFHACK_PLUGIN(vshook vshook.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() +DFHACK_PLUGIN(walkBetween walkBetween.cpp) diff --git a/plugins/devel/walkBetween.cpp b/plugins/devel/walkBetween.cpp new file mode 100644 index 000000000..9dafe4bb8 --- /dev/null +++ b/plugins/devel/walkBetween.cpp @@ -0,0 +1,87 @@ + +#include "Core.h" +#include +#include +#include + +// DF data structure definition headers +#include "DataDefs.h" +#include "df/world.h" + +#include "modules/Gui.h" +#include "modules/Maps.h" + +using namespace DFHack; +using namespace df::enums; + + + +command_result walkBetween (color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case +DFHACK_PLUGIN("walkBetween"); + +// Mandatory init function. If you have some global state, create it here. +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "walkBetween", "Do nothing, look pretty.", + walkBetween, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +// Whatever you put here will be done in each game step. Don't abuse it. +// It's optional, so you can just comment it out like this if you don't need it. +/* +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + // whetever. You don't need to suspend DF execution here. + return CR_OK; +} +*/ + +df::coord prev; + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result walkBetween (color_ostream &out, std::vector & parameters) +{ + df::coord bob = Gui::getCursorPos(); + out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canWalkBetween(prev, bob), Maps::canPathBetween(prev,bob)); + + prev = bob; + return CR_OK; +} diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index b937d40e8..e54639960 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -475,7 +475,7 @@ static bool try_store_item(df::building *target, df::item *item) df::coord tpos(target->centerx, target->centery, target->z); df::coord ipos = Items::getPosition(item); - if (!Maps::canWalkBetween(tpos, ipos)) + if (!Maps::canPathBetween(tpos, ipos)) return false; // Check if the target has enough space left From d2be8f18e131ac5d2c763703b8fe62c53a420e51 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:30:35 -0500 Subject: [PATCH 354/472] canWalkBetween: forgot a case with stairs. --- library/modules/Maps.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 0db69ea82..4b468defc 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -569,6 +569,8 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return true; if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_UPDOWN ) return true; + if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN ) + return true; return false; } From 1a6a09281b8b3e5d6666bec376bfe526d91b47a3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 16 Dec 2012 23:37:15 -0500 Subject: [PATCH 355/472] canWalkBetween: forgot a case with ramps. --- library/modules/Maps.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 4b468defc..ff02bcf60 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -551,17 +551,18 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) if ( dz == 0 ) return true; + if ( pos2.z < pos1.z ) { + df::coord temp = pos1; + pos1 = pos2; + pos2 = temp; + } + df::tiletype* type1 = Maps::getTileType(pos1); df::tiletype* type2 = Maps::getTileType(pos2); df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type1); df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); - if ( pos2.z < pos1.z ) { - df::tiletype_shape temp = shape1; - shape1 = shape2; - shape2 = temp; - } if ( dx == 0 && dy == 0 ) { if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) return true; @@ -575,8 +576,12 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) } //diagonal up: has to be a ramp - if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) - return true; + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) { + df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); + df::tiletype* typeUp = Maps::getTileType(up); + df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); + return shapeUp == tiletype_shape::RAMP_TOP; + } return false; } From 22837af8d7440f625e90d46714120c66643c508f Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 17 Dec 2012 00:25:14 -0500 Subject: [PATCH 356/472] canWalkBetween: fixed bug involving ramps. --- library/modules/Maps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index ff02bcf60..f736d385a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -576,7 +576,7 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) } //diagonal up: has to be a ramp - if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP ) { + if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) { df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); df::tiletype* typeUp = Maps::getTileType(up); df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); From a9fec84c72bfd795c2927eba3525fb58628792f5 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 18 Dec 2012 16:23:02 -0600 Subject: [PATCH 357/472] Autolabor: add paved roads. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index a2bdb4c9f..df1b1ddba 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -775,6 +775,7 @@ private: case df::building_type::Bridge: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: + case df::building_type::RoadPaved: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 555c754636aa8825a7561f634e7fa45528d20df3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 18 Dec 2012 18:34:38 -0500 Subject: [PATCH 358/472] EventManager: added construction and building events. --- library/include/modules/EventManager.h | 2 + library/modules/EventManager.cpp | 118 +++++++++++++++++++++++-- plugins/devel/eventExample.cpp | 14 +++ 3 files changed, 127 insertions(+), 7 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index c1c09da7d..1c610564e 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -17,6 +17,8 @@ namespace DFHack { JOB_COMPLETED, UNIT_DEATH, ITEM_CREATED, + BUILDING, + CONSTRUCTION, EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 187a95819..f2d7e0261 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -4,6 +4,8 @@ #include "modules/Job.h" #include "modules/World.h" +#include "df/building.h" +#include "df/construction.h" #include "df/global_objects.h" #include "df/item.h" #include "df/job.h" @@ -11,9 +13,10 @@ #include "df/unit.h" #include "df/world.h" -//#include #include -//#include +#include +#include + using namespace std; using namespace DFHack; using namespace EventManager; @@ -21,11 +24,13 @@ using namespace EventManager; /* * TODO: * error checking + * consider a typedef instead of a struct for EventHandler **/ //map > tickQueue; multimap tickQueue; +//TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; @@ -96,13 +101,31 @@ static void manageJobInitiatedEvent(color_ostream& out); static void manageJobCompletedEvent(color_ostream& out); static void manageUnitDeathEvent(color_ostream& out); static void manageItemCreationEvent(color_ostream& out); +static void manageBuildingEvent(color_ostream& out); +static void manageConstructionEvent(color_ostream& out); +//tick event static uint32_t lastTick = 0; + +//job initiated static int32_t lastJobId = -1; -static map prevJobs; -static set livingUnits; + +//job completed +static unordered_map prevJobs; + +//unit death +static unordered_set livingUnits; + +//item creation static int32_t nextItem; +//building +static int32_t nextBuilding; +static unordered_set buildings; + +//construction +static unordered_set constructions; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; @@ -114,6 +137,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.clear(); livingUnits.clear(); nextItem = -1; + nextBuilding = -1; + buildings.clear(); + constructions.clear(); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -126,6 +152,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); nextItem = *df::global::item_next_id; + nextBuilding = *df::global::building_next_id; + constructions.insert(df::global::world->constructions.begin(), df::global::world->constructions.end()); } } @@ -144,6 +172,8 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageJobCompletedEvent(out); manageUnitDeathEvent(out); manageItemCreationEvent(out); + manageBuildingEvent(out); + manageConstructionEvent(out); return; } @@ -201,7 +231,7 @@ static void manageJobCompletedEvent(color_ostream& out) { nowJobs[link->item->id] = link->item; } - for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { if ( nowJobs.find((*i).first) != nowJobs.end() ) continue; @@ -212,13 +242,13 @@ static void manageJobCompletedEvent(color_ostream& out) { } //erase old jobs, copy over possibly altered jobs - for ( map::iterator i = prevJobs.begin(); i != prevJobs.end(); i++ ) { + for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { Job::deleteJobStruct((*i).second); } prevJobs.clear(); //create new jobs - for ( map::iterator j = nowJobs.begin(); j != nowJobs.end(); j++ ) { + for ( auto j = nowJobs.begin(); j != nowJobs.end(); j++ ) { /*map::iterator i = prevJobs.find((*j).first); if ( i != prevJobs.end() ) { continue; @@ -289,3 +319,77 @@ static void manageItemCreationEvent(color_ostream& out) { nextItem = *df::global::item_next_id; } +static void manageBuildingEvent(color_ostream& out) { + /* + * TODO: could be faster + * consider looking at jobs: building creation / destruction + **/ + if ( handlers[EventType::ITEM_CREATED].empty() ) + return; + + multimap copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); + //first alert people about new buildings + for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); + if ( index == -1 ) { + out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + } + buildings.insert(a); + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler bob = (*b).second; + bob.eventHandler(out, (void*)a); + } + } + nextBuilding = *df::global::building_next_id; + + //now alert people about destroyed buildings + unordered_set toDelete; + for ( auto a = buildings.begin(); a != buildings.end(); a++ ) { + int32_t id = *a; + int32_t index = df::building::binsearch_index(df::global::world->buildings.all,id); + if ( index != -1 ) + continue; + toDelete.insert(id); + + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler bob = (*b).second; + bob.eventHandler(out, (void*)id); + } + } + + for ( auto a = toDelete.begin(); a != toDelete.end(); a++ ) { + int32_t id = *a; + buildings.erase(id); + } +} + +static void manageConstructionEvent(color_ostream& out) { + if ( handlers[EventType::CONSTRUCTION].empty() ) + return; + + unordered_set constructionsNow(df::global::world->constructions.begin(), df::global::world->constructions.end()); + + multimap copy(handlers[EventType::CONSTRUCTION].begin(), handlers[EventType::CONSTRUCTION].end()); + for ( auto a = constructions.begin(); a != constructions.end(); a++ ) { + df::construction* construction = *a; + if ( constructionsNow.find(construction) != constructionsNow.end() ) + continue; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)construction); + } + } + + for ( auto a = constructionsNow.begin(); a != constructionsNow.end(); a++ ) { + df::construction* construction = *a; + if ( constructions.find(construction) != constructions.end() ) + continue; + for ( auto b = copy.begin(); b != copy.end(); b++ ) { + EventHandler handle = (*b).second; + handle.eventHandler(out, (void*)construction); + } + } + + constructions.clear(); + constructions.insert(constructionsNow.begin(), constructionsNow.end()); +} diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 2099be110..d93396d29 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -21,6 +21,8 @@ void jobCompleted(color_ostream& out, void* job); void timePassed(color_ostream& out, void* ptr); void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); +void building(color_ostream& out, void* ptr); +void construction(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); @@ -36,7 +38,10 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler timeHandler(timePassed); EventManager::EventHandler deathHandler(unitDeath); EventManager::EventHandler itemHandler(itemCreate); + EventManager::EventHandler buildingHandler(building); + EventManager::EventHandler constructionHandler(construction); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); + EventManager::unregisterAll(me); EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); @@ -46,6 +51,8 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerTick(timeHandler, 8, me); EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); out.print("Events registered.\n"); return CR_OK; } @@ -77,3 +84,10 @@ void itemCreate(color_ostream& out, void* ptr) { out.print("Item created: %d, %s, at (%d,%d,%d)\n", (int32_t)(ptr), ENUM_KEY_STR(item_type, type).c_str(), pos.x, pos.y, pos.z); } +void building(color_ostream& out, void* ptr) { + out.print("Building created/destroyed: %d\n", (int32_t)ptr); +} + +void construction(color_ostream& out, void* ptr) { + out.print("Construction created/destroyed: 0x%X\n", ptr); +} From a93c0223a2999944b788092791df342d9131b6e3 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 18 Dec 2012 20:28:30 -0500 Subject: [PATCH 359/472] EventManager: unstable. Temp commit. --- library/include/modules/Buildings.h | 4 ++ library/include/modules/Constructions.h | 2 + library/modules/Buildings.cpp | 82 ++++++++++++++++++++++++- library/modules/Constructions.cpp | 5 ++ library/modules/EventManager.cpp | 22 +++++-- 5 files changed, 110 insertions(+), 5 deletions(-) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 266aadcb8..5f3a2b48d 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -25,7 +25,9 @@ distribution. #pragma once #include "Export.h" #include "DataDefs.h" +#include "Types.h" #include "df/building.h" +#include "df/building_type.h" #include "df/civzone_type.h" #include "df/furnace_type.h" #include "df/workshop_type.h" @@ -178,5 +180,7 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector +#include +#include +#include #include +#include #include -#include using namespace std; +#include "ColorText.h" #include "VersionInfo.h" #include "MemAccess.h" #include "Types.h" @@ -77,6 +82,14 @@ using df::global::building_next_id; using df::global::process_jobs; using df::building_def; +struct CoordHash { + size_t operator()(const df::coord pos) const { + return pos.x*65537 + pos.y*17 + pos.z; + } +}; + +static unordered_map locationToBuilding; + static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) { if (!extent.extents) @@ -222,6 +235,30 @@ df::building *Buildings::findAtTile(df::coord pos) if (!occ || !occ->bits.building) return NULL; + auto a = locationToBuilding.find(pos); + if ( a == locationToBuilding.end() ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all, id); + if ( index == -1 ) { + cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all[index]; + if (!building->isSettingOccupancy()) + return NULL; + + if (building->room.extents && building->isExtentShaped()) + { + auto etile = getExtentTile(building->room, pos); + if (!etile || !*etile) + return NULL; + } + return building; + + /* auto &vec = df::building::get_vector(); for (size_t i = 0; i < vec.size(); i++) { @@ -246,6 +283,7 @@ df::building *Buildings::findAtTile(df::coord pos) } return NULL; + */ } bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) @@ -1063,3 +1101,45 @@ bool Buildings::deconstruct(df::building *bld) return true; } +void Buildings::updateBuildings(color_ostream& out, void* ptr) { + static unordered_map corner1; + static unordered_map corner2; + out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); + int32_t id = (int32_t)ptr; + + if ( corner1.find(id) == corner1.end() ) { + //new building: mark stuff + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id); + if ( index == -1 ) { + out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id); + exit(1); + } + df::building* building = df::global::world->buildings.all[index]; + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); + df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); + + corner1[id] = p1; + corner2[id] = p2; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,building->z); + locationToBuilding[pt] = id; + } + } + } else { + //existing building: destroy it + df::coord p1 = corner1[id]; + df::coord p2 = corner2[id]; + + for ( int32_t x = p1.x; x <= p2.x; x++ ) { + for ( int32_t y = p1.y; y <= p2.y; y++ ) { + df::coord pt(x,y,p1.z); + locationToBuilding.erase(pt); + } + } + + corner1.erase(id); + corner2.erase(id); + } +} diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 16c1f1b89..2153ce6a2 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -169,3 +169,8 @@ bool Constructions::designateRemove(df::coord pos, bool *immediate) return false; } + + +void Constructions::updateConstructions(color_ostream& out, void* ptr) { + //do stuff +} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index f2d7e0261..b057adb01 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -1,5 +1,7 @@ #include "Core.h" #include "Console.h" +#include "modules/Buildings.h" +#include "modules/Constructions.h" #include "modules/EventManager.h" #include "modules/Job.h" #include "modules/World.h" @@ -127,6 +129,16 @@ static unordered_set buildings; static unordered_set constructions; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { + static bool doOnce = false; + if ( !doOnce ) { + //TODO: put this somewhere else + doOnce = true; + EventHandler buildingHandler(Buildings::updateBuildings); + EventHandler constructionHandler(Constructions::updateConstructions); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); + DFHack::EventManager::registerListener(EventType::CONSTRUCTION, constructionHandler, NULL); + out.print("Registered listeners.\n %d", __LINE__); + } if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; lastJobId = -1; @@ -151,9 +163,9 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event tickQueue.insert(newTickQueue.begin(), newTickQueue.end()); - nextItem = *df::global::item_next_id; - nextBuilding = *df::global::building_next_id; - constructions.insert(df::global::world->constructions.begin(), df::global::world->constructions.end()); + nextItem = 0; + nextBuilding = 0; + lastTick = 0; } } @@ -324,7 +336,7 @@ static void manageBuildingEvent(color_ostream& out) { * TODO: could be faster * consider looking at jobs: building creation / destruction **/ - if ( handlers[EventType::ITEM_CREATED].empty() ) + if ( handlers[EventType::BUILDING].empty() ) return; multimap copy(handlers[EventType::BUILDING].begin(), handlers[EventType::BUILDING].end()); @@ -361,6 +373,8 @@ static void manageBuildingEvent(color_ostream& out) { int32_t id = *a; buildings.erase(id); } + + out.print("Sent building event.\n %d", __LINE__); } static void manageConstructionEvent(color_ostream& out) { From 7972902c81408526d77821d31bb12d355f85a8ed Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 19 Dec 2012 20:30:37 -0500 Subject: [PATCH 360/472] stepBetween: named a few things better, and fixed a lot. --- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 99 ++++++++++++++++--- plugins/devel/CMakeLists.txt | 2 +- .../{walkBetween.cpp => stepBetween.cpp} | 12 +-- 4 files changed, 95 insertions(+), 20 deletions(-) rename plugins/devel/{walkBetween.cpp => stepBetween.cpp} (87%) diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 2655268d7..29d3d69ba 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -309,7 +309,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); -DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); +DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); } } #endif diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index f736d385a..6517331ee 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -30,10 +30,12 @@ distribution. #include #include #include +#include using namespace std; #include "modules/Maps.h" #include "modules/MapCache.h" +#include "ColorText.h" #include "Error.h" #include "VersionInfo.h" #include "MemAccess.h" @@ -59,6 +61,7 @@ using namespace std; #include "df/z_level_flags.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/building_type.h" using namespace DFHack; using namespace df::enums; @@ -535,28 +538,35 @@ bool Maps::canPathBetween(df::coord pos1, df::coord pos2) return tile1 && tile1 == tile2; } -bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +bool Maps::canStepBetween(df::coord pos1, df::coord pos2) { - bool b = canPathBetween(pos1, pos2); - if ( !b ) - return false; - - int32_t dx = pos1.x-pos2.x; - int32_t dy = pos1.y-pos2.y; - int32_t dz = pos1.z-pos2.z; + color_ostream& out = Core::getInstance().getConsole(); + int32_t dx = pos2.x-pos1.x; + int32_t dy = pos2.y-pos1.y; + int32_t dz = pos2.z-pos1.z; if ( dx*dx > 1 || dy*dy > 1 || dz*dz > 1 ) return false; - if ( dz == 0 ) - return true; - if ( pos2.z < pos1.z ) { df::coord temp = pos1; pos1 = pos2; pos2 = temp; } + df::map_block* block1 = getTileBlock(pos1); + df::map_block* block2 = getTileBlock(pos2); + + if ( !block1 || !block2 ) + return false; + + if ( !index_tile(block1->walkable,pos1) || !index_tile(block2->walkable,pos2) ) { + return false; + } + + if ( dz == 0 ) + return true; + df::tiletype* type1 = Maps::getTileType(pos1); df::tiletype* type2 = Maps::getTileType(pos2); @@ -564,6 +574,11 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) df::tiletype_shape shape2 = ENUM_ATTR(tiletype,shape,*type2); if ( dx == 0 && dy == 0 ) { + //check for forbidden hatches and floors and such + df::enums::tile_building_occ::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building; + if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored ) + return false; + if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) return true; if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == tiletype_shape::STAIR_DOWN ) @@ -572,15 +587,75 @@ bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) return true; if ( shape1 == tiletype_shape::STAIR_UP && shape2 == tiletype_shape::STAIR_DOWN ) return true; + if ( shape1 == tiletype_shape::RAMP && shape2 == tiletype_shape::RAMP_TOP ) { + //it depends + //there has to be a wall next to the ramp + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp + + //there has to be an unforbidden hatch above the ramp + if ( index_tile(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic ) + return false; + //note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy + df::building* building = Buildings::findAtTile(pos2); + if ( building == NULL ) { + out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n"; + return false; + } + if ( building->getType() != df::enums::building_type::Hatch ) { + return false; + } + return true; + } return false; } //diagonal up: has to be a ramp if ( shape1 == tiletype_shape::RAMP /*&& shape2 == tiletype_shape::RAMP*/ ) { df::coord up = df::coord(pos1.x,pos1.y,pos1.z+1); + bool foundWall = false; + for ( int32_t x = -1; x <= 1; x++ ) { + for ( int32_t y = -1; y <= 1; y++ ) { + if ( x == 0 && y == 0 ) + continue; + df::tiletype* type = Maps::getTileType(df::coord(pos1.x+x,pos1.y+y,pos1.z)); + df::tiletype_shape shape1 = ENUM_ATTR(tiletype,shape,*type); + if ( shape1 == tiletype_shape::WALL ) { + foundWall = true; + x = 2; + break; + } + } + } + if ( !foundWall ) + return false; //unusable ramp df::tiletype* typeUp = Maps::getTileType(up); df::tiletype_shape shapeUp = ENUM_ATTR(tiletype,shape,*typeUp); - return shapeUp == tiletype_shape::RAMP_TOP; + if ( shapeUp != tiletype_shape::RAMP_TOP ) + return false; + + df::map_block* blockUp = getTileBlock(up); + if ( !blockUp ) + return false; + + df::enums::tile_building_occ::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building; + if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable ) + return false; + return true; } return false; diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 41fcd8130..8b0778f86 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -21,4 +21,4 @@ DFHACK_PLUGIN(vshook vshook.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() -DFHACK_PLUGIN(walkBetween walkBetween.cpp) +DFHACK_PLUGIN(stepBetween stepBetween.cpp) diff --git a/plugins/devel/walkBetween.cpp b/plugins/devel/stepBetween.cpp similarity index 87% rename from plugins/devel/walkBetween.cpp rename to plugins/devel/stepBetween.cpp index 9dafe4bb8..c1bc3fa1a 100644 --- a/plugins/devel/walkBetween.cpp +++ b/plugins/devel/stepBetween.cpp @@ -16,19 +16,19 @@ using namespace df::enums; -command_result walkBetween (color_ostream &out, std::vector & parameters); +command_result stepBetween (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. // The name string provided must correspond to the filename - skeleton.plug.so or skeleton.plug.dll in this case -DFHACK_PLUGIN("walkBetween"); +DFHACK_PLUGIN("stepBetween"); // Mandatory init function. If you have some global state, create it here. DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { // Fill the command list with your commands. commands.push_back(PluginCommand( - "walkBetween", "Do nothing, look pretty.", - walkBetween, false, /* true means that the command can't be used from non-interactive user interface */ + "stepBetween", "Do nothing, look pretty.", + stepBetween, false, /* true means that the command can't be used from non-interactive user interface */ // Extended help string. Used by CR_WRONG_USAGE and the help command: " This command does nothing at all.\n" )); @@ -77,10 +77,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::coord prev; // A command! It sits around and looks pretty. And it's nice and friendly. -command_result walkBetween (color_ostream &out, std::vector & parameters) +command_result stepBetween (color_ostream &out, std::vector & parameters) { df::coord bob = Gui::getCursorPos(); - out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canWalkBetween(prev, bob), Maps::canPathBetween(prev,bob)); + out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canPathBetween(prev,bob)); prev = bob; return CR_OK; From 786149a630d70bc194efa961aff28d7df003ab74 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 21 Dec 2012 00:42:15 +0200 Subject: [PATCH 361/472] Added new event to eventful: onWorkshopFillSidebarMenu --- Lua API.rst | 1 + plugins/eventful.cpp | 22 +++++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/Lua API.rst b/Lua API.rst index 7315f63a0..4a203d7bd 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2969,6 +2969,7 @@ List of events 4. onProjItemCheckImpact(projectile,somebool) - is called when projectile hits something 5. onProjUnitCheckMovement(projectile) - is called when projectile moves 6. onProjUnitCheckImpact(projectile,somebool) - is called when projectile hits something +6. onWorkshopFillSidebarMenu(workshop,callnative) - is called when viewing a workshop in 'q' mode, to populate reactions, usefull for custom viewscreens for shops Examples -------- diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 02df231c4..5597dcadc 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -5,6 +5,9 @@ #include #include + +#include "df/building_workshopst.h" + #include "df/unit.h" #include "df/item.h" #include "df/item_actual.h" @@ -84,6 +87,7 @@ static bool is_lua_hook(const std::string &name) /* * Hooks */ +static void handle_fillsidebar(color_ostream &out,df::building_workshopst*,bool *call_native){}; static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag , std::vector *out_items,bool *call_native){}; @@ -93,6 +97,8 @@ static void handle_projitem_cm(color_ostream &out,df::proj_itemst*){}; static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){}; static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){}; +DEFINE_LUA_EVENT_2(onWorkshopFillSidebarMenu, handle_fillsidebar, df::building_workshopst*,bool* ); + DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t ); //projectiles @@ -102,6 +108,7 @@ DEFINE_LUA_EVENT_2(onProjUnitCheckImpact, handle_projunit_ci, df::proj_unitst*,b DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* ); DFHACK_PLUGIN_LUA_EVENTS { + DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu), DFHACK_LUA_EVENT(onReactionComplete), DFHACK_LUA_EVENT(onItemContaminateWound), DFHACK_LUA_EVENT(onProjItemCheckImpact), @@ -110,7 +117,19 @@ DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onProjUnitCheckMovement), DFHACK_LUA_END }; - +struct workshop_hook : df::building_workshopst{ + typedef df::building_workshopst interpose_base; + DEFINE_VMETHOD_INTERPOSE(void,fillSidebarMenu,()) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + bool call_native=true; + onWorkshopFillSidebarMenu(out,this,&call_native); + if(call_native) + INTERPOSE_NEXT(fillSidebarMenu)(); + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, fillSidebarMenu); struct product_hook : item_product { typedef item_product interpose_base; @@ -246,6 +265,7 @@ static bool find_reactions(color_ostream &out) static void enable_hooks(bool enable) { + INTERPOSE_HOOK(workshop_hook,fillSidebarMenu).apply(enable); INTERPOSE_HOOK(item_hooks,contaminateWound).apply(enable); INTERPOSE_HOOK(proj_unit_hook,checkImpact).apply(enable); INTERPOSE_HOOK(proj_unit_hook,checkMovement).apply(enable); From bb3a491d6891b6c60b349328b98d5e5451b40214 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 21 Dec 2012 14:00:50 +0400 Subject: [PATCH 362/472] Implement a per-save lua init script. --- Lua API.html | 27 +++++++++++++++++++++++++- Lua API.rst | 25 ++++++++++++++++++++++++ library/LuaTools.cpp | 12 ++++++++++++ library/lua/dfhack.lua | 43 ++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 104 insertions(+), 3 deletions(-) diff --git a/Lua API.html b/Lua API.html index aa41b8b63..e5318165d 100644 --- a/Lua API.html +++ b/Lua API.html @@ -404,7 +404,10 @@ ul.auto-toc {
                                                                              • sort
                                                                              • -
                                                                              • Scripts
                                                                              • +
                                                                              • Scripts +
                                                                              • The current version of DFHack has extensive support for @@ -1034,6 +1037,9 @@ can be omitted.

                                                                              • dfhack.getHackPath()

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

                                                                              • +
                                                                              • dfhack.getSavePath()

                                                                                +

                                                                                Returns the path to the current save directory, or nil if no save loaded.

                                                                                +
                                                                              • dfhack.getTickCount()

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

                                                                              • @@ -3064,6 +3070,25 @@ The name argument should be the name stem, as

                                                                                Note that this function lets errors propagate to the caller.

                                                                                +
                                                                                +

                                                                                Save init script

                                                                                +

                                                                                If a save directory contains a file called raw/init.lua, it is +automatically loaded and executed every time the save is loaded. It +can also define the following functions to be called by dfhack:

                                                                                +
                                                                                  +
                                                                                • function onStateChange(op) ... end

                                                                                  +

                                                                                  Automatically called from the regular onStateChange event as long +as the save is still loaded. This avoids the need to install a hook +into the global dfhack.onStateChange table, with associated +cleanup concerns.

                                                                                  +
                                                                                • +
                                                                                • function onUnload() ... end

                                                                                  +

                                                                                  Called when the save containing the script is unloaded. This function +should clean up any global hooks installed by the script.

                                                                                  +
                                                                                • +
                                                                                +

                                                                                Within the init script, the path to the save directory is available as SAVE_PATH.

                                                                                +
                                                                                diff --git a/Lua API.rst b/Lua API.rst index eaef74997..0ec464f76 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -741,6 +741,10 @@ can be omitted. Returns the dfhack directory path, i.e. ``".../df/hack/"``. +* ``dfhack.getSavePath()`` + + Returns the path to the current save directory, or *nil* if no save loaded. + * ``dfhack.getTickCount()`` Returns the tick count in ms, exactly as DF ui uses. @@ -2993,3 +2997,24 @@ from other scripts) in any context, via the same function the core uses: The ``name`` argument should be the name stem, as would be used on the command line. Note that this function lets errors propagate to the caller. + +Save init script +================ + +If a save directory contains a file called ``raw/init.lua``, it is +automatically loaded and executed every time the save is loaded. It +can also define the following functions to be called by dfhack: + +* ``function onStateChange(op) ... end`` + + Automatically called from the regular onStateChange event as long + as the save is still loaded. This avoids the need to install a hook + into the global ``dfhack.onStateChange`` table, with associated + cleanup concerns. + +* ``function onUnload() ... end`` + + Called when the save containing the script is unloaded. This function + should clean up any global hooks installed by the script. + +Within the init script, the path to the save directory is available as ``SAVE_PATH``. diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index c052b88aa..392659bc8 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1551,6 +1551,10 @@ void DFHack::Lua::Notification::bind(lua_State *state, const char *name) void OpenDFHackApi(lua_State *state); +namespace DFHack { namespace Lua { namespace Core { + static void InitCoreContext(); +}}} + lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) { if (!state) @@ -1654,6 +1658,10 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_dup(state); lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); + // Init core-context specific stuff before loading dfhack.lua + if (IsCoreContext(state)) + Lua::Core::InitCoreContext(); + // load dfhack.lua Require(out, state, "dfhack"); @@ -1829,8 +1837,12 @@ void DFHack::Lua::Core::Init(color_ostream &out) State = luaL_newstate(); + // Calls InitCoreContext after checking IsCoreContext Lua::Open(out, State); +} +static void Lua::Core::InitCoreContext() +{ lua_newtable(State); lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 3f57e5722..1354701ff 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -328,9 +328,11 @@ end -- Command scripts -dfhack.internal.scripts = dfhack.internal.scripts or {} +local internal = dfhack.internal -local scripts = dfhack.internal.scripts +internal.scripts = internal.scripts or {} + +local scripts = internal.scripts local hack_path = dfhack.getHackPath() function dfhack.run_script(name,...) @@ -349,5 +351,42 @@ function dfhack.run_script(name,...) return f(...) end +-- Per-save init file + +function dfhack.getSavePath() + if dfhack.isWorldLoaded() then + return dfhack.getDFPath() .. '/data/save/' .. df.global.world.cur_savegame.save_dir + end +end + +if dfhack.is_core_context then + dfhack.onStateChange.DFHACK_PER_SAVE = function(op) + if op == SC_WORLD_LOADED or op == SC_WORLD_UNLOADED then + if internal.save_init then + if internal.save_init.onUnload then + safecall(internal.save_init.onUnload) + end + internal.save_init = nil + end + + local path = dfhack.getSavePath() + + if path and op == SC_WORLD_LOADED then + local env = setmetatable({ SAVE_PATH = path }, { __index = base_env }) + local f,perr = loadfile(path..'/raw/init.lua', 't', env) + if f == nil then + if not string.match(perr, 'No such file or directory') then + dfhack.printerr(perr) + end + elseif safecall(f) then + internal.save_init = env + end + end + elseif internal.save_init and internal.save_init.onStateChange then + safecall(internal.save_init.onStateChange, op) + end + end +end + -- Feed the table back to the require() mechanism. return dfhack From a4dc79565a95bf6e73e0145e28b03e646c051730 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 22 Dec 2012 00:13:07 -0500 Subject: [PATCH 363/472] AutoSyndrome: allowed for triggering DFHack commands from in game reactions. --- plugins/autoSyndrome.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index f2c86bdfd..6e5b7f636 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -234,12 +234,32 @@ void processJob(color_ostream& out, void* jobPtr) { //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - if ( inorganic->material.heat.boiling_point > 10000 ) - continue; + if ( inorganic->material.heat.boiling_point > 10000 ) { + //continue; + } for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool foundCommand = false; + string commandStr; + vector args; + for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { + std::string* clazz = syndrome->syn_class[c]; + out.print("Class = %s\n", clazz->c_str()); + if ( foundCommand ) { + if ( commandStr == "" ) + commandStr = *clazz; + else + args.push_back(*clazz); + } else if ( *clazz == "command" ) { + foundCommand = true; + } + } + if ( commandStr != "" ) { + out.print("Running command thingy."); + Core::getInstance().runCommand(out, commandStr, args); + } //check that the syndrome applies to that guy /* * If there is no affected class or affected creature, then anybody who isn't immune is fair game. From 6d4c00374852cf2e95f8376693978a128af1f461 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 27 Dec 2012 02:52:54 -0600 Subject: [PATCH 364/472] Autolabor: fix dig-from-below bug regaring mining jobs, add overallocation detection and remediation, fix fishing and hunting --- plugins/autolabor.cpp | 63 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 13 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index df1b1ddba..85ef7076b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -17,9 +17,14 @@ #include "modules/Units.h" #include "modules/World.h" +#include "modules/Maps.h" +#include "modules/MapCache.h" +#include "modules/Items.h" // DF data structure definition headers #include "DataDefs.h" +#include + #include #include #include @@ -62,11 +67,6 @@ #include #include -#include - -#include "modules/MapCache.h" -#include "modules/Items.h" - using std::string; using std::endl; using namespace DFHack; @@ -367,6 +367,8 @@ struct labor_info PersistentDataItem config; int active_dwarfs; + int idle_dwarfs; + int busy_dwarfs; int priority() { return config.ival(1); } void set_priority(int priority) { config.ival(1) = priority; } @@ -1566,7 +1568,12 @@ private: for (int y = 0; y < 16; y++) { if (bl->designation[x][y].bits.hidden) - continue; + { + df::coord p = bl->map_pos; + df::coord c(p.x, p.y, p.z-1); + if (Maps::getTileDesignation(c)->bits.hidden) + continue; + } df::tile_dig_designation dig = bl->designation[x][y].bits.dig; if (dig != df::enums::tile_dig_designation::No) @@ -1799,7 +1806,17 @@ private: out.print ("Dwarf %s is disabled, will not be assigned labors\n", dwarf->dwarf->name.first_name.c_str()); } else + { + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (dwarf->dwarf->status.labors[l]) + labor_infos[l].idle_dwarfs++; + } + state = IDLE; + } } else { @@ -1817,6 +1834,8 @@ private: if (labor != df::unit_labor::NONE) { dwarf->using_labor = labor; + labor_infos[labor].busy_dwarfs++; + if (!dwarf->dwarf->status.labors[labor] && print_debug) { out.print("AUTOLABOR: dwarf %s (id %d) is doing job %s(%d) but is not enabled for labor %s(%d).\n", @@ -1950,7 +1969,7 @@ public: if (l == df::unit_labor::NONE) continue; - labor_infos[l].active_dwarfs = 0; + labor_infos[l].active_dwarfs = labor_infos[l].busy_dwarfs = labor_infos[l].idle_dwarfs = 0; } // scan for specific buildings of interest @@ -2000,6 +2019,7 @@ public: labor_needed[df::unit_labor::HAUL_STONE] += world->stockpile.num_jobs[1]; labor_needed[df::unit_labor::HAUL_WOOD] += world->stockpile.num_jobs[2]; labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[3]; + labor_needed[df::unit_labor::HAUL_ITEM] += world->stockpile.num_jobs[4]; labor_needed[df::unit_labor::HAUL_BODY] += world->stockpile.num_jobs[5]; labor_needed[df::unit_labor::HAUL_FOOD] += world->stockpile.num_jobs[6]; labor_needed[df::unit_labor::HAUL_REFUSE] += world->stockpile.num_jobs[7]; @@ -2014,11 +2034,11 @@ public: // add fishing & hunting - if (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) - labor_needed[df::unit_labor::FISH] ++; + labor_needed[df::unit_labor::FISH] = + (isOptionEnabled(CF_ALLOW_FISHING) && has_fishery) ? 1 : 0; - if (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) - labor_needed[df::unit_labor::HUNT] ++; + labor_needed[df::unit_labor::HUNT] = + (isOptionEnabled(CF_ALLOW_HUNTING) && has_butchers) ? 1 : 0; /* add animal trainers */ for (auto a = df::global::ui->equipment.training_assignments.begin(); @@ -2029,12 +2049,21 @@ public: // note: this doesn't test to see if the trainer is actually needed, and thus will overallocate trainers. bleah. } + /* adjust for over/under */ + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + labor_needed[l] = std::min(labor_needed[l], + std::max(1, (std::max(labor_infos[l].busy_dwarfs, labor_needed[l] - labor_infos[l].idle_dwarfs)))); + } + if (print_debug) { for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - out.print ("labor_needed [%s] = %d, outside = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, - labor_outside[i->first]); + out.print ("labor_needed [%s] = %d, outside = %d, idle = %d\n", ENUM_KEY_STR(unit_labor, i->first).c_str(), i->second, + labor_outside[i->first], labor_infos[i->first].idle_dwarfs); } } @@ -2054,6 +2083,14 @@ public: } } + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0) + pq.push(make_pair(std::min(labor_infos[l].time_since_last_assigned()/12, 25), l)); + } + if (print_debug) out.print("available count = %d, distinct labors needed = %d\n", available_dwarfs.size(), pq.size()); From 6ab8c8c30ebf64a549ca16ba6149a404f77feca8 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 28 Dec 2012 07:58:29 -0600 Subject: [PATCH 365/472] Autolabor: change fishery build labor to CLEAN_FISH to control random acts of fish extermination; add build labor for wells. --- plugins/autolabor.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 85ef7076b..e589b4b58 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -635,7 +635,7 @@ static df::unit_labor workshop_build_labor[] = /* Leatherworks */ df::unit_labor::LEATHER, /* Tanners */ df::unit_labor::TANNER, /* Clothiers */ df::unit_labor::CLOTHESMAKER, - /* Fishery */ df::unit_labor::FISH, + /* Fishery */ df::unit_labor::CLEAN_FISH, /* Still */ df::unit_labor::BREWER, /* Loom */ df::unit_labor::WEAVER, /* Quern */ df::unit_labor::MILLER, @@ -778,6 +778,7 @@ private: case df::building_type::ArcheryTarget: case df::building_type::WaterWheel: case df::building_type::RoadPaved: + case df::building_type::Well: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 0572e87d7b14c0be2982ce39bbde6b93f237390d Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 31 Dec 2012 13:50:44 -0500 Subject: [PATCH 366/472] SkyEternal: allocates new z-levels of sky as needed, or on request. --- plugins/CMakeLists.txt | 1 + plugins/skyEternal.cpp | 152 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 plugins/skyEternal.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..aa4f95056 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -127,6 +127,7 @@ if (BUILD_SUPPORTED) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) + DFHACK_PLUGIN(skyEternal skyEternal.cpp) endif() diff --git a/plugins/skyEternal.cpp b/plugins/skyEternal.cpp new file mode 100644 index 000000000..04a176a8d --- /dev/null +++ b/plugins/skyEternal.cpp @@ -0,0 +1,152 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/World.h" + +#include "df/construction.h" +#include "df/game_mode.h" +#include "df/map_block.h" +#include "df/world.h" +#include "df/z_level_flags.h" + +#include +#include +#include + +using namespace std; + +using namespace DFHack; +using namespace df::enums; + +command_result skyEternal (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("skyEternal"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "skyEternal", + "Creates new sky levels on request, or as you construct up.", + skyEternal, false, + "Usage:\n" + " skyEternal\n" + " creates one more z-level\n" + " skyEternal [n]\n" + " creates n more z-level(s)\n" + " skyEternal enable\n" + " enables monitoring of constructions\n" + " skyEternal disable\n" + " disable monitoring of constructions\n" + "\n" + "If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +static size_t constructionSize = 0; +static bool enabled = false; + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + if ( !enabled ) + return CR_OK; + if ( !Core::getInstance().isMapLoaded() ) + return CR_OK; + { + t_gamemodes mode; + if ( !Core::getInstance().getWorld()->ReadGameMode(mode) ) + return CR_FAILURE; + if ( mode.g_mode != df::enums::game_mode::DWARF ) + return CR_OK; + } + + if ( df::global::world->constructions.size() == constructionSize ) + return CR_OK; + int32_t zNow = df::global::world->map.z_count_block; + vector vec; + for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) { + df::construction* construct = df::global::world->constructions[a]; + if ( construct->pos.z+2 < zNow ) + continue; + skyEternal(out, vec); + zNow = df::global::world->map.z_count_block; + ///break; + } + constructionSize = df::global::world->constructions.size(); + + return CR_OK; +} + +command_result skyEternal (color_ostream &out, std::vector & parameters) +{ + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + if ( parameters.size() == 0 ) { + vector vec; + vec.push_back("1"); + return skyEternal(out, vec); + } + if (parameters[0] == "enable") { + enabled = true; + return CR_OK; + } + if (parameters[0] == "disable") { + enabled = false; + constructionSize = 0; + return CR_OK; + } + int32_t howMany = 0; + howMany = atoi(parameters[0].c_str()); + df::world* world = df::global::world; + CoreSuspender suspend; + int32_t x_count_block = world->map.x_count_block; + int32_t y_count_block = world->map.y_count_block; + for ( int32_t count = 0; count < howMany; count++ ) { + //change the size of the pointer stuff + int32_t z_count_block = world->map.z_count_block; + df::map_block**** block_index = world->map.block_index; + for ( int32_t a = 0; a < x_count_block; a++ ) { + for ( int32_t b = 0; b < y_count_block; b++ ) { + df::map_block** column = new df::map_block*[z_count_block+1]; + memcpy(column, block_index[a][b], z_count_block*sizeof(df::map_block*)); + column[z_count_block] = NULL; + delete[] block_index[a][b]; + block_index[a][b] = column; + } + } + df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; + memcpy(flags, world->map.z_level_flags, z_count_block*sizeof(df::z_level_flags)); + flags[z_count_block].whole = 0; + flags[z_count_block].bits.update = 1; + world->map.z_count_block++; + world->map.z_count++; + } + + return CR_OK; +} From 3a24565728b6a676b14b107b6ef97588b58cacc1 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 15:12:45 -0600 Subject: [PATCH 367/472] Autolabor: add construction labor for hatch. --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index e589b4b58..bfe435b1d 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -875,6 +875,7 @@ private: case df::building_type::TractionBench: case df::building_type::Slab: case df::building_type::Chain: + case df::building_type::Hatch: return df::unit_labor::HAUL_FURNITURE; } From d50aa24ebfe187f8acbb523764006e7ea23b612e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 16:35:09 -0600 Subject: [PATCH 368/472] Autolabor: fix idle stepdown (seems to work much better), change some scoring weights, change autolabor list output to include busy and idle counters --- plugins/autolabor.cpp | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index bfe435b1d..5157bda61 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2056,8 +2056,14 @@ public: { if (l == df::unit_labor::NONE) continue; - labor_needed[l] = std::min(labor_needed[l], - std::max(1, (std::max(labor_infos[l].busy_dwarfs, labor_needed[l] - labor_infos[l].idle_dwarfs)))); + if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) + { + if (print_debug) + out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), + labor_infos[l].busy_dwarfs, + labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); + labor_needed[l] = labor_infos[l].busy_dwarfs; + } } if (print_debug) @@ -2089,7 +2095,8 @@ public: { if (l == df::unit_labor::NONE) continue; - if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0) + if (labor_infos[l].idle_dwarfs == 0 && labor_infos[l].busy_dwarfs > 0 && + (labor_infos[l].maximum_dwarfs() == 0 || labor_needed[l] < labor_infos[l].maximum_dwarfs())) pq.push(make_pair(std::min(labor_infos[l].time_since_last_assigned()/12, 25), l)); } @@ -2160,9 +2167,9 @@ public: score += 500; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) - score += 3000; + score += 5000; if (d->has_children && labor_outside[labor]) - score -= 10000; + score -= 15000; if (d->armed && labor_outside[labor]) score += 5000; if (d->state == BUSY) @@ -2295,7 +2302,10 @@ void print_labor (df::unit_labor labor, color_ostream &out) out << ' '; out << "priority " << labor_infos[labor].priority() << ", maximum " << labor_infos[labor].maximum_dwarfs() - << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs (" + << labor_infos[labor].busy_dwarfs << " busy, " + << labor_infos[labor].idle_dwarfs << " idle)" + << endl; } df::unit_labor lookup_labor_by_name (std::string& name) From 41615d044616b0f0790f8b950d1f7e18674deb2c Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 1 Jan 2013 17:53:24 -0600 Subject: [PATCH 369/472] Autolabor: adjust idle clawback to deal with "pickup equipment" for miners. --- plugins/autolabor.cpp | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 5157bda61..7068ad5fb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1809,14 +1809,6 @@ private: } else { - FOR_ENUM_ITEMS(unit_labor, l) - { - if (l == df::unit_labor::NONE) - continue; - if (dwarf->dwarf->status.labors[l]) - labor_infos[l].idle_dwarfs++; - } - state = IDLE; } } @@ -1836,7 +1828,6 @@ private: if (labor != df::unit_labor::NONE) { dwarf->using_labor = labor; - labor_infos[labor].busy_dwarfs++; if (!dwarf->dwarf->status.labors[labor] && print_debug) { @@ -1850,6 +1841,18 @@ private: dwarf->state = state; + FOR_ENUM_ITEMS(unit_labor, l) + { + if (l == df::unit_labor::NONE) + continue; + if (dwarf->dwarf->status.labors[l]) + if (state == IDLE) + labor_infos[l].idle_dwarfs++; + else if (state == BUSY) + labor_infos[l].busy_dwarfs++; + } + + if (print_debug) out.print("Dwarf \"%s\": state %s %d\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state], dwarf->clear_all); @@ -2058,11 +2061,15 @@ public: continue; if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) { + int clawback = labor_infos[l].busy_dwarfs; + if (clawback == 0 && labor_needed[l] > 0) + clawback = 1; + if (print_debug) out.print("reducing labor %s to %d (%d needed, %d busy, %d idle)\n", ENUM_KEY_STR(unit_labor, l).c_str(), - labor_infos[l].busy_dwarfs, + clawback, labor_needed[l], labor_infos[l].busy_dwarfs, labor_infos[l].idle_dwarfs); - labor_needed[l] = labor_infos[l].busy_dwarfs; + labor_needed[l] = clawback; } } From 4e99841862735d8544bcade9f99010c02ac741c5 Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 1 Jan 2013 22:22:31 -0500 Subject: [PATCH 370/472] EventManager: made Buildings module keep track of buildings so that it can do findAtTile in constant time. --- library/include/modules/Buildings.h | 1 + library/include/modules/Constructions.h | 2 -- library/modules/Buildings.cpp | 14 +++++++++++--- library/modules/Constructions.cpp | 5 ----- library/modules/EventManager.cpp | 12 +++++++----- 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index 5f3a2b48d..4e162be69 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -181,6 +181,7 @@ DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector corner1; +static unordered_map corner2; + +void Buildings::clearBuildings(color_ostream& out) { + corner1.clear(); + corner2.clear(); + locationToBuilding.clear(); +} + void Buildings::updateBuildings(color_ostream& out, void* ptr) { - static unordered_map corner1; - static unordered_map corner2; - out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); + //out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); int32_t id = (int32_t)ptr; if ( corner1.find(id) == corner1.end() ) { diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 2153ce6a2..16c1f1b89 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -169,8 +169,3 @@ bool Constructions::designateRemove(df::coord pos, bool *immediate) return false; } - - -void Constructions::updateConstructions(color_ostream& out, void* ptr) { - //do stuff -} diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b057adb01..24bd6aeec 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -134,10 +134,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event //TODO: put this somewhere else doOnce = true; EventHandler buildingHandler(Buildings::updateBuildings); - EventHandler constructionHandler(Constructions::updateConstructions); DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); - DFHack::EventManager::registerListener(EventType::CONSTRUCTION, constructionHandler, NULL); - out.print("Registered listeners.\n %d", __LINE__); + //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { lastTick = 0; @@ -152,6 +150,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextBuilding = -1; buildings.clear(); constructions.clear(); + + Buildings::clearBuildings(out); } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -344,7 +344,9 @@ static void manageBuildingEvent(color_ostream& out) { for ( int32_t a = nextBuilding; a < *df::global::building_next_id; a++ ) { int32_t index = df::building::binsearch_index(df::global::world->buildings.all, a); if ( index == -1 ) { - out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + //out.print("%s, line %d: Couldn't find new building with id %d.\n", __FILE__, __LINE__, a); + //the tricky thing is that when the game first starts, it's ok to skip buildings, but otherwise, if you skip buildings, something is probably wrong. TODO: make this smarter + continue; } buildings.insert(a); for ( auto b = copy.begin(); b != copy.end(); b++ ) { @@ -374,7 +376,7 @@ static void manageBuildingEvent(color_ostream& out) { buildings.erase(id); } - out.print("Sent building event.\n %d", __LINE__); + //out.print("Sent building event.\n %d", __LINE__); } static void manageConstructionEvent(color_ostream& out) { From b320fb25f3ff04458f007f4fa2986cd6f57b384e Mon Sep 17 00:00:00 2001 From: expwnent Date: Tue, 1 Jan 2013 23:56:31 -0500 Subject: [PATCH 371/472] AutoSyndrome: added smart arguments for location, worker id, and reaction id. --- plugins/autoSyndrome.cpp | 34 +++++++++++++++++++++++++++++----- plugins/devel/CMakeLists.txt | 1 + plugins/devel/printArgs.cpp | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 plugins/devel/printArgs.cpp diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 6e5b7f636..c18055455 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -180,7 +180,7 @@ void processJob(color_ostream& out, void* jobPtr) { if ( job->job_type != df::job_type::CustomReaction ) return; - + df::reaction* reaction = NULL; for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { df::reaction* candidate = df::global::world->raws.reactions[a]; @@ -250,14 +250,38 @@ void processJob(color_ostream& out, void* jobPtr) { if ( foundCommand ) { if ( commandStr == "" ) commandStr = *clazz; - else - args.push_back(*clazz); - } else if ( *clazz == "command" ) { + else { + stringstream bob; + if ( *clazz == "\\LOCATION" ) { + bob << job->pos.x; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.y; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.z; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + } else if ( *clazz == "\\WORKER_ID" ) { + bob << workerId; + args.push_back(bob.str()); + } else if ( *clazz == "\\REACTION_INDEX" ) { + bob << reaction->index; + args.push_back(bob.str()); + } else { + args.push_back(*clazz); + } + } + } else if ( *clazz == "\\COMMAND" ) { foundCommand = true; } } if ( commandStr != "" ) { - out.print("Running command thingy."); Core::getInstance().runCommand(out, commandStr, args); } //check that the syndrome applies to that guy diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 80d627fa9..c57596e9c 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,6 +19,7 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) +DFHACK_PLUGIN(printArgs printArgs.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/printArgs.cpp b/plugins/devel/printArgs.cpp new file mode 100644 index 000000000..051c7b1dc --- /dev/null +++ b/plugins/devel/printArgs.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include + +using namespace DFHack; +using namespace std; + +command_result printArgs (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("printArgs"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "printArgs", "Print the arguments given.", + printArgs, false + )); + return CR_OK; +} + +command_result printArgs (color_ostream &out, std::vector & parameters) +{ + for ( size_t a = 0; a < parameters.size(); a++ ) { + out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl; + } + return CR_OK; +} From c3b2ae21377037dff478d4dbd3ef0c6d89a6b405 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 11:07:56 -0500 Subject: [PATCH 372/472] EventManager: allowed plugins to specify how often they need events to be checked, in the event that monitoring is necessary. --- library/include/modules/EventManager.h | 4 +- library/modules/EventManager.cpp | 67 ++++++++++++++++++++++---- plugins/devel/eventExample.cpp | 12 ++--- 3 files changed, 66 insertions(+), 17 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1c610564e..a06439fcd 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -37,9 +37,9 @@ namespace DFHack { } }; - DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); - DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); void onStateChange(color_ostream& out, state_change_event event); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 24bd6aeec..2725d80a3 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -34,11 +34,16 @@ multimap tickQueue; //TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; +multimap pluginFrequencies[EventType::EVENT_MAX]; +map eventFrequency[EventType::EVENT_MAX]; +uint32_t eventLastTick[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; -void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { handlers[e].insert(pair(plugin, handler)); + eventFrequency[e][freq]++; + pluginFrequencies[e].insert(pair(plugin, freq)); } void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { @@ -59,7 +64,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug return; } -void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { if ( (*i).first != plugin ) break; @@ -69,6 +74,16 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl break; } } + if ( eventFrequency[e].find(freq) == eventFrequency[e].end() ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + return; + } + eventFrequency[e][freq]--; + if ( eventFrequency[e][freq] == 0 ) { + eventFrequency[e].erase(eventFrequency[e].find(freq)); + } else if ( eventFrequency[e][freq] < 0 ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + } return; } @@ -95,6 +110,21 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { handlers[a].erase(plugin); } + + for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { + for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { + if ( (*b).first != plugin ) + continue; + int32_t freq = (*b).second; + eventFrequency[a][freq]--; + if ( eventFrequency[a][freq] < 0 ) { + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + eventFrequency[a].erase(eventFrequency[a].find(freq)); + } else if ( eventFrequency[a][freq] == 0 ) { + eventFrequency[a].erase(eventFrequency[a].find(freq)); + } + } + } return; } @@ -134,7 +164,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event //TODO: put this somewhere else doOnce = true; EventHandler buildingHandler(Buildings::updateBuildings); - DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, 100, NULL); //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -175,17 +205,36 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { } uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); + if ( tick <= lastTick ) return; lastTick = tick; manageTickEvent(out); - manageJobInitiatedEvent(out); - manageJobCompletedEvent(out); - manageUnitDeathEvent(out); - manageItemCreationEvent(out); - manageBuildingEvent(out); - manageConstructionEvent(out); + if ( tick - eventLastTick[EventType::JOB_INITIATED] >= (*eventFrequency[EventType::JOB_INITIATED].begin()).first ) { + manageJobInitiatedEvent(out); + eventLastTick[EventType::JOB_INITIATED] = tick; + } + if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= (*eventFrequency[EventType::JOB_COMPLETED].begin()).first ) { + manageJobCompletedEvent(out); + eventLastTick[EventType::JOB_COMPLETED] = tick; + } + if ( tick - eventLastTick[EventType::UNIT_DEATH] >= (*eventFrequency[EventType::UNIT_DEATH].begin()).first ) { + manageUnitDeathEvent(out); + eventLastTick[EventType::UNIT_DEATH] = tick; + } + if ( tick - eventLastTick[EventType::ITEM_CREATED] >= (*eventFrequency[EventType::ITEM_CREATED].begin()).first ) { + manageItemCreationEvent(out); + eventLastTick[EventType::ITEM_CREATED] = tick; + } + if ( tick - eventLastTick[EventType::BUILDING] >= (*eventFrequency[EventType::BUILDING].begin()).first ) { + manageBuildingEvent(out); + eventLastTick[EventType::BUILDING] = tick; + } + if ( tick - eventLastTick[EventType::CONSTRUCTION] >= (*eventFrequency[EventType::CONSTRUCTION].begin()).first ) { + manageConstructionEvent(out); + eventLastTick[EventType::CONSTRUCTION] = tick; + } return; } diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index d93396d29..e6a3dd22f 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -43,16 +43,16 @@ command_result eventExample(color_ostream& out, vector& parameters) { Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, 10, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, 5, me); EventManager::registerTick(timeHandler, 1, me); EventManager::registerTick(timeHandler, 2, me); EventManager::registerTick(timeHandler, 4, me); EventManager::registerTick(timeHandler, 8, me); - EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); - EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); - EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); - EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); + EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, 500, me); + EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); out.print("Events registered.\n"); return CR_OK; } From f680ef7ee34da948e39bcdf35824da1c661856ec Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 11:30:06 -0500 Subject: [PATCH 373/472] Made git ignore vim swap files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9f2b009c6..041624475 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ dfhack/python/dist build/CPack*Config.cmake /cmakeall.bat + +# swap files for vim +*.swp From 5e2877be23a01c9817a6c626a826c1919522ccdb Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 13:44:56 -0500 Subject: [PATCH 374/472] AutoSyndrome: added options for worker only (vs all in building), allow multiple targets, and allow multiple syndromes. --- plugins/autoSyndrome.cpp | 210 +++++++++++++++++++++++++-------------- 1 file changed, 138 insertions(+), 72 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index c18055455..2c8023035 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -7,6 +7,7 @@ #include "modules/Job.h" #include "modules/Maps.h" +#include "df/building.h" #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" @@ -21,6 +22,7 @@ #include "df/ui.h" #include "df/unit.h" #include "df/general_ref.h" +#include "df/general_ref_building_holderst.h" #include "df/general_ref_type.h" #include "df/general_ref_unit_workerst.h" @@ -124,7 +126,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); EventManager::EventHandler handle(processJob); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, 5, me); return CR_OK; } @@ -162,13 +164,81 @@ command_result autoSyndrome(color_ostream& out, vector& parameters) { Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); if ( enabled ) { EventManager::EventHandler handle(processJob); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, 5, me); } else { EventManager::unregisterAll(me); } return CR_OK; } +bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) { + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = caste->caste_id; + //check that the syndrome applies to that guy + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0; + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return false; + } + for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature[c] ) + continue; + if ( *syndrome->syn_affected_caste[c] == "ALL" || + *syndrome->syn_affected_caste[c] == creature_caste ) { + applies = true; + break; + } + } + for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature[c] ) + continue; + if ( *syndrome->syn_immune_caste[c] == "ALL" || + *syndrome->syn_immune_caste[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + return false; + } + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return false; + return true; +} + void processJob(color_ostream& out, void* jobPtr) { df::job* job = (df::job*)jobPtr; if ( job == NULL ) { @@ -195,13 +265,13 @@ void processJob(color_ostream& out, void* jobPtr) { } int32_t workerId = -1; - for ( size_t a = 0; a < job->references.size(); a++ ) { - if ( job->references[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) continue; if ( workerId != -1 ) { out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); } - workerId = ((df::general_ref_unit_workerst*)job->references[a])->unit_id; + workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id; if (workerId == -1) { out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); continue; @@ -214,13 +284,32 @@ void processJob(color_ostream& out, void* jobPtr) { return; } df::unit* unit = df::global::world->units.all[workerIndex]; - df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; - df::caste_raw* caste = creature->caste[unit->caste]; - std::string& creature_name = creature->creature_id; - std::string& creature_caste = caste->caste_id; + //find the building that made it + int32_t buildingId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER ) + continue; + if ( buildingId != -1 ) { + out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__); + } + buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id; + if (buildingId == -1) { + out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__); + continue; + } + } + df::building* building; + { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId); + if ( index == -1 ) { + out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId); + return; + } + building = df::global::world->buildings.all[index]; + } //find all of the products it makes. Look for a stone with a low boiling point. - bool foundIt = false; + bool appliedSomething = false; for ( size_t a = 0; a < reaction->products.size(); a++ ) { df::reaction_product_type type = reaction->products[a]->getType(); //out.print("type = %d\n", (int32_t)type); @@ -234,23 +323,34 @@ void processJob(color_ostream& out, void* jobPtr) { //must be a boiling rock syndrome df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; - if ( inorganic->material.heat.boiling_point > 10000 ) { - //continue; + if ( inorganic->material.heat.boiling_point > 9000 ) { + continue; } for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool workerOnly = false; + bool allowMultipleSyndromes = false; + bool allowMultipleTargets = false; bool foundCommand = false; string commandStr; vector args; for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { std::string* clazz = syndrome->syn_class[c]; - out.print("Class = %s\n", clazz->c_str()); if ( foundCommand ) { - if ( commandStr == "" ) - commandStr = *clazz; - else { + if ( commandStr == "" ) { + if ( *clazz == "\\WORKER_ONLY" ) { + workerOnly = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_SYNDROMES" ) { + allowMultipleSyndromes = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { + allowMultipleTargets = true; + } + else { + commandStr = *clazz; + } + } else { stringstream bob; if ( *clazz == "\\LOCATION" ) { bob << job->pos.x; @@ -284,70 +384,36 @@ void processJob(color_ostream& out, void* jobPtr) { if ( commandStr != "" ) { Core::getInstance().runCommand(out, commandStr, args); } - //check that the syndrome applies to that guy - /* - * If there is no affected class or affected creature, then anybody who isn't immune is fair game. - * - * Otherwise, it works like this: - * add all the affected class creatures - * remove all the immune class creatures - * add all the affected creatures - * remove all the immune creatures - * you're affected if and only if you're in the remaining list after all of that - **/ - bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature_1.size() == 0; - for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { - if ( applies ) - break; - for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { - if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { - applies = true; - break; - } - } - } - for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { - if ( !applies ) - break; - for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { - if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { - applies = false; - break; - } - } - } - if ( syndrome->syn_affected_creature_1.size() != syndrome->syn_affected_creature_2.size() ) { - out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); - return; + //only one syndrome per reaction will be applied, unless multiples are allowed. + if ( appliedSomething && !allowMultipleSyndromes ) + continue; + + if ( maybeApply(out, syndrome, workerId, unit) ) { + appliedSomething = true; + continue; } - for ( size_t c = 0; c < syndrome->syn_affected_creature_1.size(); c++ ) { - if ( creature_name != *syndrome->syn_affected_creature_1[c] ) + + if ( workerOnly ) + continue; + + //now try applying it to everybody inside the building + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit->pos.z != building->z ) continue; - if ( *syndrome->syn_affected_creature_2[c] == "ALL" || - *syndrome->syn_affected_creature_2[c] == creature_caste ) { - applies = true; - break; - } - } - for ( size_t c = 0; c < syndrome->syn_immune_creature_1.size(); c++ ) { - if ( creature_name != *syndrome->syn_immune_creature_1[c] ) + if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) + continue; + if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 ) continue; - if ( *syndrome->syn_immune_creature_2[c] == "ALL" || - *syndrome->syn_immune_creature_2[c] == creature_caste ) { - applies = false; - break; + if ( maybeApply(out, syndrome, unit->id, unit) ) { + appliedSomething = true; + if ( !allowMultipleTargets ) + break; } } - if ( !applies ) { - continue; - } - if ( giveSyndrome(out, workerId, syndrome) < 0 ) - return; } } - if ( !foundIt ) - return; return; } From 38ef75418ae5147dcc84ed2f808f8eb0b76850a6 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 14:09:16 -0500 Subject: [PATCH 375/472] AutoSyndrome: added an option to delete boiling rocks as they are created (on by default). --- library/xml | 2 +- plugins/autoSyndrome.cpp | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 2c8023035..2fa9fb681 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -11,6 +11,8 @@ #include "df/caste_raw.h" #include "df/creature_raw.h" #include "df/global_objects.h" +#include "df/item.h" +#include "df/item_boulderst.h" #include "df/job.h" #include "df/job_type.h" #include "df/reaction.h" @@ -334,6 +336,7 @@ void processJob(color_ostream& out, void* jobPtr) { bool allowMultipleSyndromes = false; bool allowMultipleTargets = false; bool foundCommand = false; + bool destroyRock = true; string commandStr; vector args; for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { @@ -346,6 +349,8 @@ void processJob(color_ostream& out, void* jobPtr) { allowMultipleSyndromes = true; } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { allowMultipleTargets = true; + } else if ( *clazz == "\\PRESERVE_ROCK" ) { + destroyRock = false; } else { commandStr = *clazz; @@ -385,13 +390,35 @@ void processJob(color_ostream& out, void* jobPtr) { Core::getInstance().runCommand(out, commandStr, args); } + if ( destroyRock ) { + //find the rock and kill it before it can boil and cause problems and ugliness + for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) { + df::item* item = df::global::world->items.all[c]; + if ( item->pos.z != building->z ) + continue; + if ( item->pos.x < building->x1 || item->pos.x > building->x2 ) + continue; + if ( item->pos.y < building->y1 || item->pos.y > building->y2 ) + continue; + if ( item->getType() != df::enums::item_type::BOULDER ) + continue; + //make sure it's the right type of boulder + df::item_boulderst* boulder = (df::item_boulderst*)item; + if ( boulder->mat_index != bob->mat_index ) + continue; + + boulder->flags.bits.garbage_collect = true; + boulder->flags.bits.forbid = true; + boulder->flags.bits.hidden = true; + } + } + //only one syndrome per reaction will be applied, unless multiples are allowed. if ( appliedSomething && !allowMultipleSyndromes ) continue; if ( maybeApply(out, syndrome, workerId, unit) ) { appliedSomething = true; - continue; } if ( workerOnly ) From 8b5e847dfa5060cc05d8f8993d5d95a3f277343f Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 2 Jan 2013 23:43:38 +0200 Subject: [PATCH 376/472] New event for eventful. postWorkshopFillSidebarMenu for tweaking the sidebar menu on workshops. --- Lua API.rst | 3 ++- plugins/eventful.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Lua API.rst b/Lua API.rst index 95ce5923e..1cc730b8a 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2973,7 +2973,8 @@ List of events 4. onProjItemCheckImpact(projectile,somebool) - is called when projectile hits something 5. onProjUnitCheckMovement(projectile) - is called when projectile moves 6. onProjUnitCheckImpact(projectile,somebool) - is called when projectile hits something -6. onWorkshopFillSidebarMenu(workshop,callnative) - is called when viewing a workshop in 'q' mode, to populate reactions, usefull for custom viewscreens for shops +7. onWorkshopFillSidebarMenu(workshop,callnative) - is called when viewing a workshop in 'q' mode, to populate reactions, usefull for custom viewscreens for shops +8. postWorkshopFillSidebarMenu(workshop) - is called after calling (or not) native fillSidebarMenu(). Usefull for job button tweaking (e.g. adding custom reactions) Examples -------- diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index 5597dcadc..00b764ad1 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -88,6 +88,7 @@ static bool is_lua_hook(const std::string &name) * Hooks */ static void handle_fillsidebar(color_ostream &out,df::building_workshopst*,bool *call_native){}; +static void handle_postfillsidebar(color_ostream &out,df::building_workshopst*){}; static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag , std::vector *out_items,bool *call_native){}; @@ -98,6 +99,7 @@ static void handle_projunit_ci(color_ostream &out,df::proj_unitst*,bool){}; static void handle_projunit_cm(color_ostream &out,df::proj_unitst*){}; DEFINE_LUA_EVENT_2(onWorkshopFillSidebarMenu, handle_fillsidebar, df::building_workshopst*,bool* ); +DEFINE_LUA_EVENT_1(postWorkshopFillSidebarMenu, handle_postfillsidebar, df::building_workshopst*); DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); DEFINE_LUA_EVENT_5(onItemContaminateWound, handle_contaminate_wound, df::item_actual*,df::unit* , df::unit_wound* , uint8_t , int16_t ); @@ -109,6 +111,7 @@ DEFINE_LUA_EVENT_1(onProjUnitCheckMovement, handle_projunit_cm, df::proj_unitst* DFHACK_PLUGIN_LUA_EVENTS { DFHACK_LUA_EVENT(onWorkshopFillSidebarMenu), + DFHACK_LUA_EVENT(postWorkshopFillSidebarMenu), DFHACK_LUA_EVENT(onReactionComplete), DFHACK_LUA_EVENT(onItemContaminateWound), DFHACK_LUA_EVENT(onProjItemCheckImpact), @@ -127,6 +130,7 @@ struct workshop_hook : df::building_workshopst{ onWorkshopFillSidebarMenu(out,this,&call_native); if(call_native) INTERPOSE_NEXT(fillSidebarMenu)(); + postWorkshopFillSidebarMenu(out,this); } }; IMPLEMENT_VMETHOD_INTERPOSE(workshop_hook, fillSidebarMenu); From 03650dbdbdc2a3fff47df77dfd8be17b1c5201d5 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 17:32:11 -0500 Subject: [PATCH 377/472] True transformation. Temp commit. --- plugins/CMakeLists.txt | 1 + plugins/trueTransformation.cpp | 85 ++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 plugins/trueTransformation.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1c89d9ab7..077f0e4ad 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,6 +132,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) endif() diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp new file mode 100644 index 000000000..6cd6f16ea --- /dev/null +++ b/plugins/trueTransformation.cpp @@ -0,0 +1,85 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +using namespace DFHack; + +DFHACK_PLUGIN("trueTransformation"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "skeleton", "Do nothing, look pretty.", + skeleton, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " This command does nothing at all.\n" + "Example:\n" + " skeleton\n" + " Does nothing.\n" + )); + return CR_OK; +} + +// This is called right before the plugin library is removed from memory. +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + // You *MUST* kill all threads you created before this returns. + // If everything fails, just return CR_FAILURE. Your plugin will be + // in a zombie state, but things won't crash. + return CR_OK; +} + +// Called to notify the plugin about important state changes. +// Invoked with DF suspended, and always before the matching plugin_onupdate. +// More event codes may be added in the future. +/* +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_GAME_LOADED: + // initialize from the world just loaded + break; + case SC_GAME_UNLOADED: + // cleanup + break; + default: + break; + } + return CR_OK; +} +*/ + +// Whatever you put here will be done in each game step. Don't abuse it. +// It's optional, so you can just comment it out like this if you don't need it. +/* +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + // whetever. You don't need to suspend DF execution here. + return CR_OK; +} +*/ + +// A command! It sits around and looks pretty. And it's nice and friendly. +command_result skeleton (color_ostream &out, std::vector & parameters) +{ + // It's nice to print a help message you get invalid options + // from the user instead of just acting strange. + // This can be achieved by adding the extended help string to the + // PluginCommand registration as show above, and then returning + // CR_WRONG_USAGE from the function. The same string will also + // be used by 'help your-command'. + if (!parameters.empty()) + return CR_WRONG_USAGE; + // Commands are called from threads other than the DF one. + // Suspend this thread until DF has time for us. If you + // use CoreSuspender, it'll automatically resume DF when + // execution leaves the current scope. + CoreSuspender suspend; + // Actually do something here. Yay. + out.print("Hello! I do nothing, remember?\n"); + // Give control back to DF. + return CR_OK; +} From 4e4e382b8f7e359fef6723bebf4cd3e36a578aac Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 18:30:15 -0500 Subject: [PATCH 378/472] EventManager: added syndrome event. --- library/include/modules/EventManager.h | 9 ++++++++ library/modules/EventManager.cpp | 32 ++++++++++++++++++++++++++ library/xml | 2 +- plugins/devel/eventExample.cpp | 10 +++++++- 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index a06439fcd..40d6603a7 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -19,6 +19,7 @@ namespace DFHack { ITEM_CREATED, BUILDING, CONSTRUCTION, + SYNDROME, EVENT_MAX }; } @@ -36,6 +37,14 @@ namespace DFHack { return !( *this == handle); } }; + + struct SyndromeData { + int32_t unitId; + int32_t syndromeIndex; + SyndromeData(int32_t unitId_in, int32_t syndromeIndex_in): unitId(unitId_in), syndromeIndex(syndromeIndex_in) { + + } + }; DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 2725d80a3..6a355aa36 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -13,6 +13,7 @@ #include "df/job.h" #include "df/job_list_link.h" #include "df/unit.h" +#include "df/unit_syndrome.h" #include "df/world.h" #include @@ -135,6 +136,7 @@ static void manageUnitDeathEvent(color_ostream& out); static void manageItemCreationEvent(color_ostream& out); static void manageBuildingEvent(color_ostream& out); static void manageConstructionEvent(color_ostream& out); +static void manageSyndromeEvent(color_ostream& out); //tick event static uint32_t lastTick = 0; @@ -235,6 +237,10 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageConstructionEvent(out); eventLastTick[EventType::CONSTRUCTION] = tick; } + if ( tick - eventLastTick[EventType::SYNDROME] >= (*eventFrequency[EventType::SYNDROME].begin()).first ) { + manageSyndromeEvent(out); + eventLastTick[EventType::SYNDROME] = tick; + } return; } @@ -458,3 +464,29 @@ static void manageConstructionEvent(color_ostream& out) { constructions.clear(); constructions.insert(constructionsNow.begin(), constructionsNow.end()); } + +static void manageSyndromeEvent(color_ostream& out) { + if ( handlers[EventType::SYNDROME].empty() ) + return; + + multimap copy(handlers[EventType::SYNDROME].begin(), handlers[EventType::SYNDROME].end()); + for ( auto a = df::global::world->units.active.begin(); a != df::global::world->units.active.end(); a++ ) { + df::unit* unit = *a; + if ( unit->flags1.bits.dead ) + continue; + for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { + df::unit_syndrome* syndrome = unit->syndromes.active[b]; + uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; + out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); + if ( startTime < eventLastTick[EventType::SYNDROME] ) + continue; + + SyndromeData data(unit->id, b); + for ( auto c = copy.begin(); c != copy.end(); c++ ) { + EventHandler handle = (*c).second; + handle.eventHandler(out, (void*)&data); + } + } + } +} + diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index e6a3dd22f..aaaa35a09 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -23,10 +23,10 @@ void unitDeath(color_ostream& out, void* ptr); void itemCreate(color_ostream& out, void* ptr); void building(color_ostream& out, void* ptr); void construction(color_ostream& out, void* ptr); +void syndrome(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); - DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("eventExample", "Sets up a few event triggers.",eventExample)); return CR_OK; @@ -40,6 +40,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler itemHandler(itemCreate); EventManager::EventHandler buildingHandler(building); EventManager::EventHandler constructionHandler(construction); + EventManager::EventHandler syndromeHandler(syndrome); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); @@ -53,6 +54,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); out.print("Events registered.\n"); return CR_OK; } @@ -91,3 +93,9 @@ void building(color_ostream& out, void* ptr) { void construction(color_ostream& out, void* ptr) { out.print("Construction created/destroyed: 0x%X\n", ptr); } + +void syndrome(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); +} + From d4afa4b6e45c3b8b76e0c1d551ce566b71607dd7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:21:25 -0500 Subject: [PATCH 379/472] True Transformation: turn into something temporarily, then permanently transform into another creature. If you try to do that with a syndrome, you can't do it recursively. --- plugins/trueTransformation.cpp | 133 +++++++++++++++++---------------- 1 file changed, 69 insertions(+), 64 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6cd6f16ea..fd1e1a61b 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -5,81 +5,86 @@ #include "Export.h" #include "PluginManager.h" +#include "modules/EventManager.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/syndrome.h" +#include "df/unit.h" +#include "df/unit_syndrome.h" +#include "df/world.h" + +#include + using namespace DFHack; +using namespace std; DFHACK_PLUGIN("trueTransformation"); +void syndromeHandler(color_ostream& out, void* ptr); + DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "skeleton", "Do nothing, look pretty.", - skeleton, false, /* true means that the command can't be used from non-interactive user interface */ - // Extended help string. Used by CR_WRONG_USAGE and the help command: - " This command does nothing at all.\n" - "Example:\n" - " skeleton\n" - " Does nothing.\n" - )); - return CR_OK; -} + EventManager::EventHandler syndrome(syndromeHandler); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, 1, me); -// This is called right before the plugin library is removed from memory. -DFhackCExport command_result plugin_shutdown ( color_ostream &out ) -{ - // You *MUST* kill all threads you created before this returns. - // If everything fails, just return CR_FAILURE. Your plugin will be - // in a zombie state, but things won't crash. return CR_OK; } -// Called to notify the plugin about important state changes. -// Invoked with DF suspended, and always before the matching plugin_onupdate. -// More event codes may be added in the future. -/* -DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) -{ - switch (event) { - case SC_GAME_LOADED: - // initialize from the world just loaded - break; - case SC_GAME_UNLOADED: - // cleanup - break; - default: - break; +void syndromeHandler(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); + + int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); + if ( index < 0 ) { + out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); + return; } - return CR_OK; -} -*/ + df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; + df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; + + bool foundIt = false; + int32_t raceId = -1; + df::creature_raw* creatureRaw = NULL; + int32_t casteId = -1; + for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) { + if ( *syndrome->syn_class[a] == "\\PERMANENT" ) { + foundIt = true; + } + if ( foundIt && raceId == -1 ) { + //find the race with the name + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) { + df::creature_raw* creature = df::global::world->raws.creatures.all[b]; + if ( creature->creature_id != name ) + continue; + raceId = b; + creatureRaw = creature; + break; + } + continue; + } + if ( foundIt && raceId != -1 ) { + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) { + df::caste_raw* caste = creatureRaw->caste[b]; + if ( caste->caste_id != name ) + continue; + casteId = b; + break; + } + break; + } + } + out.print("foundIt = %d, raceId = %d, casteId = %d\n", (int32_t)foundIt, raceId, casteId); + if ( !foundIt || raceId == -1 || casteId == -1 ) + return; -// Whatever you put here will be done in each game step. Don't abuse it. -// It's optional, so you can just comment it out like this if you don't need it. -/* -DFhackCExport command_result plugin_onupdate ( color_ostream &out ) -{ - // whetever. You don't need to suspend DF execution here. - return CR_OK; + unit->enemy.normal_race = raceId; + unit->enemy.normal_caste = casteId; + out.print("Did the thing.\n"); + //that's it! } -*/ -// A command! It sits around and looks pretty. And it's nice and friendly. -command_result skeleton (color_ostream &out, std::vector & parameters) -{ - // It's nice to print a help message you get invalid options - // from the user instead of just acting strange. - // This can be achieved by adding the extended help string to the - // PluginCommand registration as show above, and then returning - // CR_WRONG_USAGE from the function. The same string will also - // be used by 'help your-command'. - if (!parameters.empty()) - return CR_WRONG_USAGE; - // Commands are called from threads other than the DF one. - // Suspend this thread until DF has time for us. If you - // use CoreSuspender, it'll automatically resume DF when - // execution leaves the current scope. - CoreSuspender suspend; - // Actually do something here. Yay. - out.print("Hello! I do nothing, remember?\n"); - // Give control back to DF. - return CR_OK; -} From 6d2773856a22f3ec4fabe3047222a6fce02d6931 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:23:40 -0500 Subject: [PATCH 380/472] EventManager: fixed a few things. --- library/modules/EventManager.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 6a355aa36..b3f22b109 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -159,6 +159,7 @@ static unordered_set buildings; //construction static unordered_set constructions; +static bool gameLoaded; void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; @@ -184,6 +185,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event constructions.clear(); Buildings::clearBuildings(out); + gameLoaded = false; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -198,11 +200,12 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextItem = 0; nextBuilding = 0; lastTick = 0; + gameLoaded = true; } } void DFHack::EventManager::manageEvents(color_ostream& out) { - if ( !Core::getInstance().isWorldLoaded() ) { + if ( !gameLoaded ) { return; } uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear @@ -478,7 +481,7 @@ static void manageSyndromeEvent(color_ostream& out) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); - if ( startTime < eventLastTick[EventType::SYNDROME] ) + if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; SyndromeData data(unit->id, b); From a8b0a7e6958be517ea487202ca0e81b2edf9cf0e Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:25:24 -0500 Subject: [PATCH 381/472] EventManager: got rid of silly print statement. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b3f22b109..8611417b0 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -480,7 +480,6 @@ static void manageSyndromeEvent(color_ostream& out) { for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; - out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; From 5865579b23a0f28e86ddf344084a84a469233e34 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:26:37 -0500 Subject: [PATCH 382/472] EventManager: got rid of print statement. --- library/modules/EventManager.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index b3f22b109..8611417b0 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -480,7 +480,6 @@ static void manageSyndromeEvent(color_ostream& out) { for ( size_t b = 0; b < unit->syndromes.active.size(); b++ ) { df::unit_syndrome* syndrome = unit->syndromes.active[b]; uint32_t startTime = syndrome->year*ticksPerYear + syndrome->year_time; - out.print("start time = %d, time = %d\n", startTime, eventLastTick[EventType::SYNDROME]); if ( startTime <= eventLastTick[EventType::SYNDROME] ) continue; From 285ee3b399f6505b67ce305f952cf52e6f2b46fd Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 19:45:08 -0500 Subject: [PATCH 383/472] True transformation: got rid of debug print statements. --- plugins/trueTransformation.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index fd1e1a61b..6da6b7e74 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -78,13 +78,11 @@ void syndromeHandler(color_ostream& out, void* ptr) { break; } } - out.print("foundIt = %d, raceId = %d, casteId = %d\n", (int32_t)foundIt, raceId, casteId); if ( !foundIt || raceId == -1 || casteId == -1 ) return; unit->enemy.normal_race = raceId; unit->enemy.normal_caste = casteId; - out.print("Did the thing.\n"); //that's it! } From 123f34bf85f6eb3b9331f563b3d4abd4438047f9 Mon Sep 17 00:00:00 2001 From: expwnent Date: Wed, 2 Jan 2013 21:06:48 -0500 Subject: [PATCH 384/472] Ignore swap files. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9f2b009c6..b4a578ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ dfhack/python/dist build/CPack*Config.cmake /cmakeall.bat + +# vim swap files +*.swp From 796e387398b98b1ef8904d8ab7a6b49dbda28096 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 3 Jan 2013 12:24:32 -0600 Subject: [PATCH 385/472] Add a guard against an invalid item pointer, and don't try to deduce construction labor for item_lost jobs --- plugins/autolabor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 7068ad5fb..ff0670b9b 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -665,7 +665,7 @@ static df::building* get_building_from_job(df::job* j) static df::unit_labor construction_build_labor (df::item* i) { MaterialInfo matinfo; - if (matinfo.decode(i)) + if (i && matinfo.decode(i)) { if (matinfo.material->flags.is_set(df::material_flags::IS_METAL)) return df::unit_labor::METAL_CRAFT; @@ -754,6 +754,9 @@ private: public: df::unit_labor get_labor(df::job* j) { + if (j->flags.bits.item_lost) + return df::unit_labor::NONE; + df::building* bld = get_building_from_job (j); switch (bld->getType()) { From c22c4d009c94e9187faa2f685d7e57ca929585b7 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 3 Jan 2013 20:30:51 +0200 Subject: [PATCH 386/472] Added convenience functions to eventful (see Lua API.rst) --- Lua API.rst | 12 +++++ plugins/lua/eventful.lua | 109 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index 1cc730b8a..a6bdca4ef 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2976,6 +2976,13 @@ List of events 7. onWorkshopFillSidebarMenu(workshop,callnative) - is called when viewing a workshop in 'q' mode, to populate reactions, usefull for custom viewscreens for shops 8. postWorkshopFillSidebarMenu(workshop) - is called after calling (or not) native fillSidebarMenu(). Usefull for job button tweaking (e.g. adding custom reactions) +Functions +--------- + +1. registerReaction(reaction_name,callback) - simplified way of using onReactionComplete, the callback is function (same params as event) +2. removeNative(shop_name) - removes native choice list from the building +3. addReactionToShop(reaction_name,shop_name) - add a custom reaction to the building + Examples -------- Spawn dragon breath on each item attempt to contaminate wound: @@ -3003,6 +3010,11 @@ Granade example: dfhack.maps.spawnFlow(projectile.cur_pos,6,0,0,50000) end +Integrated tannery: +:: + b=require "plugins.eventful" + b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") + ======= Scripts ======= diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index 18d9fd7a0..c610fa87c 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -1,7 +1,112 @@ local _ENV = mkmodule('plugins.eventful') --[[ - Native events: -TODO + + --]] +local function getShopName(btype,bsubtype,bcustom) + local typenames_shop={[df.workshop_type.Carpenters]="CARPENTERS",[df.workshop_type.Farmers]="FARMERS", + [df.workshop_type.Masons]="MASONS",[df.workshop_type.Craftsdwarfs]="CRAFTSDWARFS", + [df.workshop_type.Jewelers]="JEWELERS",[df.workshop_type.MetalsmithsForge]="METALSMITHSFORGE", + [df.workshop_type.MagmaForge]="MAGMAFORGE",[df.workshop_type.Bowyers]="BOWYERS", + [df.workshop_type.Mechanics]="MECHANICS",[df.workshop_type.Siege]="SIEGE", + [df.workshop_type.Butchers]="BUTCHERS",[df.workshop_type.Leatherworks]="LEATHERWORKS", + [df.workshop_type.Tanners]="TANNERS",[df.workshop_type.Clothiers]="CLOTHIERS", + [df.workshop_type.Fishery]="FISHERY",[df.workshop_type.Still]="STILL", + [df.workshop_type.Loom]="LOOM",[df.workshop_type.Quern]="QUERN", + [df.workshop_type.Kennels]="KENNELS",[df.workshop_type.Ashery]="ASHERY", + [df.workshop_type.Kitchen]="KITCHEN",[df.workshop_type.Dyers]="DYERS", + [df.workshop_type.Tool]="TOOL",[df.workshop_type.Millstone]="MILLSTONE", + } + local typenames_furnace={[df.furnace_type.WoodFurnace]="WOOD_FURNACE",[df.furnace_type.Smelter]="SMELTER", + [df.furnace_type.GlassFurnace]="GLASS_FURNACE",[df.furnace_type.MagmaSmelter]="MAGMA_SMELTER", + [df.furnace_type.MagmaGlassFurnace]="MAGMA_GLASS_FURNACE",[df.furnace_type.MagmaKiln]="MAGMA_KILN", + [df.furnace_type.Kiln]="KILN"} + if btype==df.building_type.Workshop then + if typenames_shop[bsubtype]~=nil then + return typenames_shop[bsubtype] + else + return nil --todo add custom (not very useful) + end + elseif btype==df.building_type.Furnace then + if typenames_furnace[bsubtype]~=nil then + return typenames_furnace[bsubtype] + else + return nil --todo add custom (not very useful) + end + end +end +_registeredStuff={} +local function unregall(state) + if state==SC_WORLD_UNLOADED then + onReactionComplete._library=nil + postWorkshopFillSidebarMenu._library=nil + dfhack.onStateChange.eventful= nil + _registeredStuff={} + end +end +local function onReact(reaction,unit,input_items,input_reagents,output_items,call_native) + if _registeredStuff.reactionCallbacks and _registeredStuff.reactionCallbacks[reaction.code] then + _registeredStuff.reactionCallbacks[reaction.code](reaction,unit,input_items,input_reagents,output_items,call_native) + end +end +local function onPostSidebar(workshop) + local shop_id=getShopName(workshop:getType(),workshop:getSubtype(),workshop:getCustomType()) + if shop_id then + if _registeredStuff.shopNonNative and _registeredStuff.shopNonNative[shop_id] then + if _registeredStuff.shopNonNative[shop_id].all then + --[[for _,button in ipairs(df.global.ui_sidebar_menus.workshop_job.choices_all) do + button.is_hidden=true + end]] + df.global.ui_sidebar_menus.workshop_job.choices_visible:resize(0) + else + --todo by name + end + end + + if _registeredStuff.reactionToShop and _registeredStuff.reactionToShop[shop_id] then + for _,reaction_name in ipairs(_registeredStuff.reactionToShop[shop_id]) do + local new_button=df.interface_button_building_new_jobst:new() + --new_button.hotkey_id=--todo get hotkey + new_button.is_hidden=false + new_button.building=workshop + new_button.job_type=df.job_type.CustomReaction --could be used for other stuff too i guess... + new_button.reaction_name=reaction_name + new_button.is_custom=true + local wjob=df.global.ui_sidebar_menus.workshop_job + wjob.choices_all:insert("#",new_button) + wjob.choices_visible:insert("#",new_button) + end + end + end +end + +function registerReaction(reaction_name,callback) + _registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {} + _registeredStuff.reactionCallbacks[reaction_name]=callback + onReactionComplete._library=onReact + dfhack.onStateChange.eventful=unregall +end + +function removeNative(shop_name,name) + _registeredStuff.shopNonNative=_registeredStuff.shopNonNative or {} + local shops=_registeredStuff.shopNonNative + shops[shop_name]=shops[shop_name] or {} + if name~=nil then + table.insert(shops[shop_name],name) + else + shops[shop_name].all=true + end + postWorkshopFillSidebarMenu._library=onPostSidebar + dfhack.onStateChange.eventful=unregall +end + +function addReactionToShop(reaction_name,shop_name) + _registeredStuff.reactionToShop=_registeredStuff.reactionToShop or {} + local shops=_registeredStuff.reactionToShop + shops[shop_name]=shops[shop_name] or {} + table.insert(shops[shop_name],reaction_name) + postWorkshopFillSidebarMenu._library=onPostSidebar + dfhack.onStateChange.eventful=unregall +end return _ENV \ No newline at end of file From 910e398a7bb3d7deb7f13cdbcd74acef6325a7d7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 15:52:56 -0500 Subject: [PATCH 387/472] EventManager: added invasion event. --- library/include/modules/EventManager.h | 1 + library/modules/EventManager.cpp | 27 ++++++++++++++++++++++++++ plugins/devel/eventExample.cpp | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 40d6603a7..1f7758ddf 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -20,6 +20,7 @@ namespace DFHack { BUILDING, CONSTRUCTION, SYNDROME, + INVASION, EVENT_MAX }; } diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 8611417b0..77248038f 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -12,6 +12,7 @@ #include "df/item.h" #include "df/job.h" #include "df/job_list_link.h" +#include "df/ui.h" #include "df/unit.h" #include "df/unit_syndrome.h" #include "df/world.h" @@ -137,6 +138,7 @@ static void manageItemCreationEvent(color_ostream& out); static void manageBuildingEvent(color_ostream& out); static void manageConstructionEvent(color_ostream& out); static void manageSyndromeEvent(color_ostream& out); +static void manageInvasionEvent(color_ostream& out); //tick event static uint32_t lastTick = 0; @@ -161,6 +163,9 @@ static unordered_set buildings; static unordered_set constructions; static bool gameLoaded; +//invasion +static int32_t nextInvasion; + void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event event) { static bool doOnce = false; if ( !doOnce ) { @@ -186,6 +191,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event Buildings::clearBuildings(out); gameLoaded = false; + nextInvasion = -1; } else if ( event == DFHack::SC_MAP_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); @@ -200,6 +206,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event nextItem = 0; nextBuilding = 0; lastTick = 0; + nextInvasion = df::global::ui->invasions.next_id; gameLoaded = true; } } @@ -244,6 +251,10 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { manageSyndromeEvent(out); eventLastTick[EventType::SYNDROME] = tick; } + if ( tick - eventLastTick[EventType::INVASION] >= (*eventFrequency[EventType::INVASION].begin()).first ) { + manageInvasionEvent(out); + eventLastTick[EventType::INVASION] = tick; + } return; } @@ -492,3 +503,19 @@ static void manageSyndromeEvent(color_ostream& out) { } } +static void manageInvasionEvent(color_ostream& out) { + if ( handlers[EventType::INVASION].empty() ) + return; + + multimap copy(handlers[EventType::INVASION].begin(), handlers[EventType::INVASION].end()); + + if ( df::global::ui->invasions.next_id <= nextInvasion ) + return; + nextInvasion = df::global::ui->invasions.next_id; + + for ( auto a = copy.begin(); a != copy.end(); a++ ) { + EventHandler handle = (*a).second; + handle.eventHandler(out, (void*)nextInvasion); + } +} + diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index aaaa35a09..3bc84879a 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -24,6 +24,7 @@ void itemCreate(color_ostream& out, void* ptr); void building(color_ostream& out, void* ptr); void construction(color_ostream& out, void* ptr); void syndrome(color_ostream& out, void* ptr); +void invasion(color_ostream& out, void* ptr); command_result eventExample(color_ostream& out, vector& parameters); @@ -41,6 +42,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::EventHandler buildingHandler(building); EventManager::EventHandler constructionHandler(construction); EventManager::EventHandler syndromeHandler(syndrome); + EventManager::EventHandler invasionHandler(invasion); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); @@ -55,6 +57,7 @@ command_result eventExample(color_ostream& out, vector& parameters) { EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); + EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, 1, me); out.print("Events registered.\n"); return CR_OK; } @@ -99,3 +102,7 @@ void syndrome(color_ostream& out, void* ptr) { out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); } +void invasion(color_ostream& out, void* ptr) { + out.print("New invasion! %d\n", (int32_t)ptr); +} + From e3ca612ed55c4b99e464fd170f9999fdb7f22173 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 3 Jan 2013 23:21:57 +0200 Subject: [PATCH 388/472] many improvements to advfort: fixed being able to engrave soft materials, started using ui_buttons for better workshop functionality, started working on manual item assignment --- scripts/gui/advfort.lua | 223 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 216 insertions(+), 7 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 70a702768..11c335637 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -17,6 +17,7 @@ local dialog=require 'gui.dialogs' local buildings=require 'dfhack.buildings' local bdialog=require 'gui.buildings' local workshopJobs=require 'dfhack.workshops' +local utils=require 'utils' local tile_attrs = df.tiletype.attrs @@ -211,10 +212,19 @@ function IsConstruct(args) return false, "Can only do it on constructions" end end +function SameSquare(args) + local pos1=args.pos + local pos2=args.from_pos + if pos1.x==pos2.x and pos1.y==pos2.y and pos1.z==pos2.z then + return true + else + return false, "Can only do it on same square" + end +end function IsHardMaterial(args) local tt=dfhack.maps.getTileType(args.pos) local mat=tile_attrs[tt].material - local hard_materials={df.tiletype_material.STONE,df.tiletype_material.FEATURE, + local hard_materials=makeset{df.tiletype_material.STONE,df.tiletype_material.FEATURE, df.tiletype_material.LAVA_STONE,df.tiletype_material.MINERAL,df.tiletype_material.FROZEN_LIQUID,} if hard_materials[mat] then return true @@ -440,6 +450,155 @@ function EnumItems(args) end return ret end +jobitemEditor=defclass(jobitemEditor,gui.FramedScreen) +jobitemEditor.ATTRS{ + frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, + allow_add=false, + allow_remove=false, + allow_any_item=false, + job=DEFAULT_NIL, + items=DEFAULT_NIL, +} +function jobitemEditor:init(args) + --self.job=args.job + if self.job==nil then qerror("This screen must have job target") end + if self.items==nil then qerror("This screen must have item list") end + local itemChoices={} + table.insert(itemChoices,{text=""}) + for k,v in pairs(self.items) do + table.insert(itemChoices,{item=v,text=dfhack.items.getDescription(v, 0)}) + end + self.itemChoices=itemChoices + self:addviews{ + wid.Label{ + view_id = 'label', + text = args.prompt, + text_pen = args.text_pen, + frame = { l = 0, t = 0 }, + }, + wid.List{ + view_id = 'itemList', + frame = { l = 0, t = 2 ,b=2}, + on_submit = self:callback("selectJobItem") + }, + wid.Label{ + frame = { b=1,l=1}, + text ={{text= ": cancel", + key = "LEAVESCREEN", + on_activate= self:callback("dismiss") + }, + { + gap=3, + text= ": accept", + key = "SEC_SELECT", + on_activate= self:callback("commit"), + enabled=self:callback("jobValid") + }, + { + gap=3, + text= ": add", + key = "CUSTOM_A", + enabled=false, + --on_activate= self:callback("commit") + }, + { + gap=3, + text= ": remove", + key = "CUSTOM_R", + enabled=false, + --on_activate= self:callback("commit") + },} + }, + } + self.assigned={} + self:fill(self.job) +end + +function jobitemEditor:fill(job) + local choices={} + for item_id, trg_job_item in ipairs(job.job_items) do + local str="" + if self.assigned[item_id] ~=nil then + str=dfhack.items.getDescription(self.assigned[item_id],0) + end + local text={string.format("%3d:",item_id)} + if self.assigned[item_id]~=nil then + for subid,assigned_item in ipairs(self.assigned[item_id]) do + table.insert(text," "..dfhack.items.getDescription(assigned_item,0)) + table.insert(text,"\n") + end + else + table.insert(text,"") + end + table.insert(choices,{text=text, + job_item=trg_job_item,job_item_idx=item_id}) + end + self.choices=choices + self.subviews.itemList:setChoices(choices) +end +function jobitemEditor:countMatched(job_item_idx,job_item) + local sum=0 + if self.assigned[job_item_idx]==nil then return false end + for k,v in pairs(self.assigned[job_item_idx]) do + sum=sum+v:getTotalDimension() + end + return job_item.quantity<=sum +end +function jobitemEditor:jobValid() + for k,v in pairs(self.job.job_items) do + if not self:countMatched(k,v) then + return false + end + end + return true +end +function jobitemEditor:itemChosen(job_choice,index,choice) + if choice.item==nil then + self.assigned[job_choice.job_item_idx]=nil + else + self.assigned[job_choice.job_item_idx]=self.assigned[job_choice.job_item_idx] or {} + table.insert(self.assigned[job_choice.job_item_idx],choice.item) + if not self:countMatched(job_choice.job_item_idx,job_choice.job_item) then + self:selectJobItem(nil,job_choice) + end + end + self:fill(self.job) +end +function jobitemEditor:selectJobItem(index,choice) + local filtered_items={} + for k,v in pairs(self.itemChoices) do + if (v.text=="" or isSuitableItem(choice.job_item,v.item)) and (v.item==nil or not self:isAssigned(v.item)) then + table.insert(filtered_items,v) + end + end + dialog.showListPrompt("Item choice", "Choose item:", nil, filtered_items, self:callback("itemChosen",choice), nil,nil,true) --custom item choice dialog req +end +function jobitemEditor:isAssigned(item) + for k,v in pairs(self.assigned) do + for subid, aitem in pairs(v) do + if aitem.id==item.id then + return true + end + end + end +end +function jobitemEditor:commit() + + for job_item_id,v in pairs(self.assigned) do + for sub_id,cur_item in pairs(v) do + self.job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_item_id}) + end + end + local uncollected = getItemsUncollected(job) + if #uncollected == 0 then + self.job.flags.working=true + else + uncollected[1].is_fetching=1 + self.job.flags.fetching=true + end + self:dismiss() +end function AssignJobItems(args) if settings.df_assign then --use df default logic and hope that it would work @@ -450,6 +609,16 @@ function AssignJobItems(args) local its=EnumItems{pos=args.from_pos,unit=args.unit, inv={[df.unit_inventory_item.T_mode.Hauled]=settings.check_inv,[df.unit_inventory_item.T_mode.Worn]=settings.check_inv, [df.unit_inventory_item.T_mode.Weapon]=settings.check_inv,},deep=true} + --[[ job item editor... + jobitemEditor{job=args.job,items=its}:show() + local ok=job.flags.working or job.flags.fetching + if not ok then + return ok, "Stuff" + else + return ok + end + --]] + -- [=[ --[[while(#job.items>0) do --clear old job items job.items[#job.items-1]:delete() job.items:erase(#job.items-1) @@ -496,6 +665,7 @@ function AssignJobItems(args) --todo set working for workshops if items are present, else set fetching (and at least one item to is_fetching=1) --job.flags.working=true return true + --]=] end function AssignJobToBuild(args) local bld=dfhack.buildings.findAtTile(args.pos) @@ -554,9 +724,9 @@ function ContinueJob(unit) end actions={ - {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMat}}, - {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMat}}, - {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMat}}, + {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMaterial}}, + {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMaterial}}, + {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMaterial,SameSquare}}, --{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work?? {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, @@ -745,8 +915,47 @@ function usetool:openSiegeWindow(building) require("gui.dialogs").showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, dfhack.curry(siegeWeaponActionChosen,building)) end +function usetool:onWorkShopButtonClicked(building,index,choice) + local adv=df.global.world.units.active[0] + if df.interface_button_building_new_jobst:is_instance(choice.button) then + choice.button:click() + if #building.jobs>0 then + local job=building.jobs[#building.jobs-1] + AssignUnitToJob(job,adv,adv.pos) + AssignJobItems{job=job,from_pos=adv.pos,pos=adv.pos,unit=adv} + end + elseif df.interface_button_building_category_selectorst:is_instance(choice.button) or + df.interface_button_building_material_selectorst:is_instance(choice.button) then + choice.button:click() + self:openShopWindowButtoned(building,true) + end + printall(choice) +end +function usetool:openShopWindowButtoned(building,no_reset) + local wui=df.global.ui_sidebar_menus.workshop_job + if not no_reset then + wui:assign{category_id=-1,mat_type=-1,mat_index=-1} + for k,v in pairs(wui.material_category) do + wui.material_category[k]=false + end + end + building:fillSidebarMenu() + + local list={} + for id,choice in pairs(wui.choices_visible) do + table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) + end + if #list ==0 then + self:openShopWindow(building) + return + --qerror("No jobs for this workshop") + end + require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,list,self:callback("onWorkShopButtonClicked",building) + ,nil, nil,true) +end function usetool:openShopWindow(building) local adv=df.global.world.units.active[0] + local filter_pile=workshopJobs.getJobs(building:getType(),building:getSubtype(),building:getCustomType()) if filter_pile then local state={unit=adv,from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z} @@ -788,11 +997,11 @@ MODES={ }, [df.building_type.Workshop]={ name="Workshop menu", - input=usetool.openShopWindow, + input=usetool.openShopWindowButtoned, }, [df.building_type.Furnace]={ name="Workshop menu", - input=usetool.openShopWindow, + input=usetool.openShopWindowButtoned, }, [df.building_type.SiegeEngine]={ name="Siege menu", @@ -809,7 +1018,7 @@ function usetool:shopMode(enable,mode,building) end function usetool:shopInput(keys) if keys[keybinds.workshop.key] then - self:openShopWindow(self.in_shop) + self:openShopWindowButtoned(self.in_shop) end end function usetool:fieldInput(keys) From 179c7ae32a706defd9564ea7dd3f7e9cf92b1ec3 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 3 Jan 2013 23:53:42 +0200 Subject: [PATCH 389/472] advfort: reset using df button. --- scripts/gui/advfort.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 11c335637..5dc3643b7 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -934,10 +934,16 @@ end function usetool:openShopWindowButtoned(building,no_reset) local wui=df.global.ui_sidebar_menus.workshop_job if not no_reset then + --[[ manual reset incase the df-one does not exist? wui:assign{category_id=-1,mat_type=-1,mat_index=-1} for k,v in pairs(wui.material_category) do wui.material_category[k]=false end + ]]-- + building:fillSidebarMenu() + if #wui.choices_all>0 then + wui.choices_all[#wui.choices_all-1]:click() + end end building:fillSidebarMenu() From 8ffafd8a26082e1796cb16bbd5d191736170cd9c Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 4 Jan 2013 00:48:24 +0200 Subject: [PATCH 390/472] advfort: reverted to old way of reseting the menu, added setup for race/civ/group ids and main.fortress_entity. --- scripts/gui/advfort.lua | 50 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 5dc3643b7..ed043c2d8 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -929,21 +929,23 @@ function usetool:onWorkShopButtonClicked(building,index,choice) choice.button:click() self:openShopWindowButtoned(building,true) end - printall(choice) end + function usetool:openShopWindowButtoned(building,no_reset) + self:setupFields() local wui=df.global.ui_sidebar_menus.workshop_job if not no_reset then - --[[ manual reset incase the df-one does not exist? + -- [[ manual reset incase the df-one does not exist? wui:assign{category_id=-1,mat_type=-1,mat_index=-1} for k,v in pairs(wui.material_category) do wui.material_category[k]=false end - ]]-- - building:fillSidebarMenu() + --]] + --[[building:fillSidebarMenu() if #wui.choices_all>0 then wui.choices_all[#wui.choices_all-1]:click() end + --]] end building:fillSidebarMenu() @@ -951,7 +953,8 @@ function usetool:openShopWindowButtoned(building,no_reset) for id,choice in pairs(wui.choices_visible) do table.insert(list,{text=utils.call_with_string(choice,"getLabel"),button=choice}) end - if #list ==0 then + if #list ==0 and not no_reset then + print("Fallback") self:openShopWindow(building) return --qerror("No jobs for this workshop") @@ -1027,6 +1030,43 @@ function usetool:shopInput(keys) self:openShopWindowButtoned(self.in_shop) end end +function advGlobalPos() + local wd=df.global.world.world_data + return wd.adv_region_x*16+wd.adv_emb_x,wd.adv_region_y*16+wd.adv_emb_y +end +function inSite() + local tx,ty=advGlobalPos() + for k,v in pairs(df.global.world.world_data.sites) do + local tp={v.pos.x,v.pos.y} + if tx>=tp[1]*16+v.rgn_min_x and tx<=tp[1]*16+v.rgn_max_x and + ty>=tp[2]*16+v.rgn_min_y and ty<=tp[2]*16+v.rgn_max_y then + return v + end + end +end +function usetool:setupFields() + local adv=df.global.world.units.active[0] + local civ_id=df.global.world.units.active[0].civ_id + local ui=df.global.ui + ui.civ_id = civ_id + ui.main.fortress_entity=df.historical_entity.find(civ_id) + ui.race_id=adv.race + local nem=dfhack.units.getNemesis(adv) + if nem then + local links=nem.figure.entity_links + for _,link in ipairs(links) do + local hist_entity=df.historical_entity.find(link.entity_id) + if hist_entity and hist_entity.type==df.historical_entity_type.SiteGovernment then + ui.group_id=link.entity_id + break + end + end + end + local site= inSite() + if site then + ui.site_id=site.id + end +end function usetool:fieldInput(keys) local adv=df.global.world.units.active[0] local cur_mode=actions[(mode or 0)+1] From 9e74ae58f25db9380bfa27e4cd1fa01eab173215 Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 19:07:05 -0500 Subject: [PATCH 391/472] EventManager: Fixed a problem with deregistering event frequencies. --- library/modules/EventManager.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 77248038f..6df6106de 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -120,13 +120,23 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { int32_t freq = (*b).second; eventFrequency[a][freq]--; if ( eventFrequency[a][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); + Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister: %d, %d.\n", __FILE__, __LINE__, a, freq); eventFrequency[a].erase(eventFrequency[a].find(freq)); } else if ( eventFrequency[a][freq] == 0 ) { eventFrequency[a].erase(eventFrequency[a].find(freq)); } } } + //now delete the frequencies from the thing + for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { + for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { + if ( (*b).first != plugin ) + continue; + pluginFrequencies[a].erase(b); + a--; + break; + } + } return; } From 715f191c264009c3a30b89e1011620b930ebaa6a Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 19:31:29 -0500 Subject: [PATCH 392/472] EventManager: made the frequency part of EventHandler. --- library/include/modules/EventManager.h | 9 ++-- library/modules/EventManager.cpp | 74 ++++++++------------------ plugins/devel/eventExample.cpp | 34 ++++++------ 3 files changed, 45 insertions(+), 72 deletions(-) diff --git a/library/include/modules/EventManager.h b/library/include/modules/EventManager.h index 1f7758ddf..9cca6e0e0 100644 --- a/library/include/modules/EventManager.h +++ b/library/include/modules/EventManager.h @@ -27,12 +27,13 @@ namespace DFHack { struct EventHandler { void (*eventHandler)(color_ostream&, void*); //called when the event happens + int32_t freq; - EventHandler(void (*eventHandlerIn)(color_ostream&, void*)): eventHandler(eventHandlerIn) { + EventHandler(void (*eventHandlerIn)(color_ostream&, void*), int32_t freqIn): eventHandler(eventHandlerIn), freq(freqIn) { } bool operator==(EventHandler& handle) const { - return eventHandler == handle.eventHandler; + return eventHandler == handle.eventHandler && freq == handle.freq; } bool operator!=(EventHandler& handle) const { return !( *this == handle); @@ -47,9 +48,9 @@ namespace DFHack { } }; - DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); + DFHACK_EXPORT void registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute=false); - DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin); + DFHACK_EXPORT void unregister(EventType::EventType e, EventHandler handler, Plugin* plugin); DFHACK_EXPORT void unregisterAll(Plugin* plugin); void manageEvents(color_ostream& out); void onStateChange(color_ostream& out, state_change_event event); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index 6df6106de..ff48263fc 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -36,16 +36,12 @@ multimap tickQueue; //TODO: consider unordered_map of pairs, or unordered_map of unordered_set, or whatever multimap handlers[EventType::EVENT_MAX]; -multimap pluginFrequencies[EventType::EVENT_MAX]; -map eventFrequency[EventType::EVENT_MAX]; uint32_t eventLastTick[EventType::EVENT_MAX]; const uint32_t ticksPerYear = 403200; -void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { +void DFHack::EventManager::registerListener(EventType::EventType e, EventHandler handler, Plugin* plugin) { handlers[e].insert(pair(plugin, handler)); - eventFrequency[e][freq]++; - pluginFrequencies[e].insert(pair(plugin, freq)); } void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plugin* plugin, bool absolute) { @@ -66,7 +62,7 @@ void DFHack::EventManager::registerTick(EventHandler handler, int32_t when, Plug return; } -void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, int32_t freq, Plugin* plugin) { +void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handler, Plugin* plugin) { for ( multimap::iterator i = handlers[e].find(plugin); i != handlers[e].end(); i++ ) { if ( (*i).first != plugin ) break; @@ -76,16 +72,6 @@ void DFHack::EventManager::unregister(EventType::EventType e, EventHandler handl break; } } - if ( eventFrequency[e].find(freq) == eventFrequency[e].end() ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); - return; - } - eventFrequency[e][freq]--; - if ( eventFrequency[e][freq] == 0 ) { - eventFrequency[e].erase(eventFrequency[e].find(freq)); - } else if ( eventFrequency[e][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister.\n", __FILE__, __LINE__); - } return; } @@ -112,31 +98,6 @@ void DFHack::EventManager::unregisterAll(Plugin* plugin) { for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { handlers[a].erase(plugin); } - - for ( size_t a = 0; a < (size_t)EventType::EVENT_MAX; a++ ) { - for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { - if ( (*b).first != plugin ) - continue; - int32_t freq = (*b).second; - eventFrequency[a][freq]--; - if ( eventFrequency[a][freq] < 0 ) { - Core::getInstance().getConsole().print("%s, line %d: Error: incorrect frequency on deregister: %d, %d.\n", __FILE__, __LINE__, a, freq); - eventFrequency[a].erase(eventFrequency[a].find(freq)); - } else if ( eventFrequency[a][freq] == 0 ) { - eventFrequency[a].erase(eventFrequency[a].find(freq)); - } - } - } - //now delete the frequencies from the thing - for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { - for ( auto b = pluginFrequencies[a].begin(); b != pluginFrequencies[a].end(); b++ ) { - if ( (*b).first != plugin ) - continue; - pluginFrequencies[a].erase(b); - a--; - break; - } - } return; } @@ -181,8 +142,8 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event if ( !doOnce ) { //TODO: put this somewhere else doOnce = true; - EventHandler buildingHandler(Buildings::updateBuildings); - DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, 100, NULL); + EventHandler buildingHandler(Buildings::updateBuildings, 100); + DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); //out.print("Registered listeners.\n %d", __LINE__); } if ( event == DFHack::SC_MAP_UNLOADED ) { @@ -231,37 +192,48 @@ void DFHack::EventManager::manageEvents(color_ostream& out) { if ( tick <= lastTick ) return; lastTick = tick; + + int32_t eventFrequency[EventType::EVENT_MAX]; + for ( size_t a = 0; a < EventType::EVENT_MAX; a++ ) { + int32_t min = 1000000000; + for ( auto b = handlers[a].begin(); b != handlers[a].end(); b++ ) { + EventHandler bob = (*b).second; + if ( bob.freq < min ) + min = bob.freq; + } + eventFrequency[a] = min; + } manageTickEvent(out); - if ( tick - eventLastTick[EventType::JOB_INITIATED] >= (*eventFrequency[EventType::JOB_INITIATED].begin()).first ) { + if ( tick - eventLastTick[EventType::JOB_INITIATED] >= eventFrequency[EventType::JOB_INITIATED] ) { manageJobInitiatedEvent(out); eventLastTick[EventType::JOB_INITIATED] = tick; } - if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= (*eventFrequency[EventType::JOB_COMPLETED].begin()).first ) { + if ( tick - eventLastTick[EventType::JOB_COMPLETED] >= eventFrequency[EventType::JOB_COMPLETED] ) { manageJobCompletedEvent(out); eventLastTick[EventType::JOB_COMPLETED] = tick; } - if ( tick - eventLastTick[EventType::UNIT_DEATH] >= (*eventFrequency[EventType::UNIT_DEATH].begin()).first ) { + if ( tick - eventLastTick[EventType::UNIT_DEATH] >= eventFrequency[EventType::UNIT_DEATH] ) { manageUnitDeathEvent(out); eventLastTick[EventType::UNIT_DEATH] = tick; } - if ( tick - eventLastTick[EventType::ITEM_CREATED] >= (*eventFrequency[EventType::ITEM_CREATED].begin()).first ) { + if ( tick - eventLastTick[EventType::ITEM_CREATED] >= eventFrequency[EventType::ITEM_CREATED] ) { manageItemCreationEvent(out); eventLastTick[EventType::ITEM_CREATED] = tick; } - if ( tick - eventLastTick[EventType::BUILDING] >= (*eventFrequency[EventType::BUILDING].begin()).first ) { + if ( tick - eventLastTick[EventType::BUILDING] >= eventFrequency[EventType::BUILDING] ) { manageBuildingEvent(out); eventLastTick[EventType::BUILDING] = tick; } - if ( tick - eventLastTick[EventType::CONSTRUCTION] >= (*eventFrequency[EventType::CONSTRUCTION].begin()).first ) { + if ( tick - eventLastTick[EventType::CONSTRUCTION] >= eventFrequency[EventType::CONSTRUCTION] ) { manageConstructionEvent(out); eventLastTick[EventType::CONSTRUCTION] = tick; } - if ( tick - eventLastTick[EventType::SYNDROME] >= (*eventFrequency[EventType::SYNDROME].begin()).first ) { + if ( tick - eventLastTick[EventType::SYNDROME] >= eventFrequency[EventType::SYNDROME] ) { manageSyndromeEvent(out); eventLastTick[EventType::SYNDROME] = tick; } - if ( tick - eventLastTick[EventType::INVASION] >= (*eventFrequency[EventType::INVASION].begin()).first ) { + if ( tick - eventLastTick[EventType::INVASION] >= eventFrequency[EventType::INVASION] ) { manageInvasionEvent(out); eventLastTick[EventType::INVASION] = tick; } diff --git a/plugins/devel/eventExample.cpp b/plugins/devel/eventExample.cpp index 3bc84879a..3a2970c5b 100644 --- a/plugins/devel/eventExample.cpp +++ b/plugins/devel/eventExample.cpp @@ -34,30 +34,30 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector& parameters) { - EventManager::EventHandler initiateHandler(jobInitiated); - EventManager::EventHandler completeHandler(jobCompleted); - EventManager::EventHandler timeHandler(timePassed); - EventManager::EventHandler deathHandler(unitDeath); - EventManager::EventHandler itemHandler(itemCreate); - EventManager::EventHandler buildingHandler(building); - EventManager::EventHandler constructionHandler(construction); - EventManager::EventHandler syndromeHandler(syndrome); - EventManager::EventHandler invasionHandler(invasion); + EventManager::EventHandler initiateHandler(jobInitiated, 10); + EventManager::EventHandler completeHandler(jobCompleted, 5); + EventManager::EventHandler timeHandler(timePassed, 1); + EventManager::EventHandler deathHandler(unitDeath, 500); + EventManager::EventHandler itemHandler(itemCreate, 1000); + EventManager::EventHandler buildingHandler(building, 500); + EventManager::EventHandler constructionHandler(construction, 100); + EventManager::EventHandler syndromeHandler(syndrome, 1); + EventManager::EventHandler invasionHandler(invasion, 1000); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("eventExample"); EventManager::unregisterAll(me); - EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, 10, me); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, 5, me); + EventManager::registerListener(EventManager::EventType::JOB_INITIATED, initiateHandler, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, completeHandler, me); EventManager::registerTick(timeHandler, 1, me); EventManager::registerTick(timeHandler, 2, me); EventManager::registerTick(timeHandler, 4, me); EventManager::registerTick(timeHandler, 8, me); - EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, 500, me); - EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, 1000, me); - EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, 500, me); - EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, 100, me); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, 1, me); - EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, 1, me); + EventManager::registerListener(EventManager::EventType::UNIT_DEATH, deathHandler, me); + EventManager::registerListener(EventManager::EventType::ITEM_CREATED, itemHandler, me); + EventManager::registerListener(EventManager::EventType::BUILDING, buildingHandler, me); + EventManager::registerListener(EventManager::EventType::CONSTRUCTION, constructionHandler, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndromeHandler, me); + EventManager::registerListener(EventManager::EventType::INVASION, invasionHandler, me); out.print("Events registered.\n"); return CR_OK; } From ec03d567d28bd6fd5d4f656d7d42e3c2e1e246de Mon Sep 17 00:00:00 2001 From: expwnent Date: Thu, 3 Jan 2013 22:47:27 -0500 Subject: [PATCH 393/472] EventManager: use WORLD_LOADED instead of MAP_LOADED. --- library/modules/EventManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index ff48263fc..3af096004 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -146,7 +146,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event DFHack::EventManager::registerListener(EventType::BUILDING, buildingHandler, NULL); //out.print("Registered listeners.\n %d", __LINE__); } - if ( event == DFHack::SC_MAP_UNLOADED ) { + if ( event == DFHack::SC_WORLD_UNLOADED ) { lastTick = 0; lastJobId = -1; for ( auto i = prevJobs.begin(); i != prevJobs.end(); i++ ) { @@ -163,7 +163,7 @@ void DFHack::EventManager::onStateChange(color_ostream& out, state_change_event Buildings::clearBuildings(out); gameLoaded = false; nextInvasion = -1; - } else if ( event == DFHack::SC_MAP_LOADED ) { + } else if ( event == DFHack::SC_WORLD_LOADED ) { uint32_t tick = DFHack::World::ReadCurrentYear()*ticksPerYear + DFHack::World::ReadCurrentTick(); multimap newTickQueue; From 9404267c1f9b97daef23efb9f65b4e91d20e1fdb Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 4 Jan 2013 13:14:20 -0600 Subject: [PATCH 394/472] Autolabor: Tell DF to immediately process jobs after each run. --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index ff0670b9b..d96baec9e 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2259,6 +2259,8 @@ public: } } + *df::global::process_jobs = true; + print_debug = 0; } From 7debd3d983a92dad224dd49574936b413e8cf581 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 4 Jan 2013 13:20:28 -0600 Subject: [PATCH 395/472] Autofarm: correct incorrect logic for determining if a farm is surface or subterranean --- scripts/autofarm.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 6a7635b90..e1dc62ae3 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -102,9 +102,9 @@ class AutoFarm farms_u = [] df.world.buildings.other[:FARM_PLOT].each { |f| if (f.flags.exists) - outside = df.map_designation_at(f.centerx,f.centery,f.z).outside - farms_s.push(f) if outside - farms_u.push(f) unless outside + underground = df.map_designation_at(f.centerx,f.centery,f.z).subterranean + farms_s.push(f) unless underground + farms_u.push(f) if underground end } From 4d731d0c2831000e528d06b429d6e873a129279c Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 5 Jan 2013 01:41:44 +0200 Subject: [PATCH 396/472] advfort: added buildings with width/height/direction (TODO better gui) added farming (planting/harvesting) added clean job --- scripts/gui/advfort.lua | 96 +++++++++++++++++++++++++++++++++++------ 1 file changed, 83 insertions(+), 13 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index ed043c2d8..c65e20473 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -70,7 +70,7 @@ function showHelp() table.insert(helptext,NEWLINE) table.insert(helptext,NEWLINE) Disclaimer(helptext) - require("gui.dialogs").showMessage("Help!?!",helptext) + dialog.showMessage("Help!?!",helptext) end --[[ low level job management ]]-- function getLastJobLink() @@ -302,17 +302,52 @@ function AssignBuildingRef(args) bld.jobs:insert("#",args.job) return true end +function chooseBuildingWidthHeightDir(args) --TODO nicer selection dialog + local btype=df.building_type + local area=makeset{"w","h"} + local all=makeset{"w","h","d"} + local needs={[btype.FarmPlot]=area,[btype.Bridge]=all, + [btype.RoadDirt]=area,[btype.RoadPaved]=area,[btype.ScrewPump]=makeset{"d"}, + [btype.AxleHorizontal]=makeset{"w","h"},[btype.WaterWheel]=makeset{"d"},[btype.Rollers]=makeset{"d"}} + local myneeds=needs[args.type] + if myneeds==nil then return end + if args.width==nil and myneeds.w then + --args.width=3 + dialog.showInputPrompt("Building size:", "Input building width:", nil, "1", + function(txt) args.width=tonumber(txt);BuildingChosen(args) end) + return true + end + if args.height==nil and myneeds.h then + --args.height=4 + dialog.showInputPrompt("Building size:", "Input building height:", nil, "1", + function(txt) args.height=tonumber(txt);BuildingChosen(args) end) + return true + end + if args.direction==nil and myneeds.d then + --args.direction=0--? + dialog.showInputPrompt("Building size:", "Input building direction:", nil, "0", + function(txt) args.direction=tonumber(txt);BuildingChosen(args) end) + return true + end + return false + --width = ..., height = ..., direction = ... +end function BuildingChosen(inp_args,type_id,subtype_id,custom_id) - local args={} - args.type=type_id - args.subtype=subtype_id - args.custom=custom_id - args.pos=inp_args.pos + local args=inp_args or {} + + args.type=type_id or args.type + args.subtype=subtype_id or args.subtype + args.custom=custom_id or args.custom_id + if inp_args then + args.pos=inp_args.pos or args.pos + end + if chooseBuildingWidthHeightDir(args) then + return + end --if settings.build_by_items then -- args.items=itemsAtPos(inp_args.from_pos) --end - --printall(args.items) buildings.constructBuilding(args) end @@ -337,6 +372,7 @@ function isSuitableItem(job_item,item) --todo butcher test if job_item.item_type~=-1 then if item:getType()~= job_item.item_type then + return false, "type" elseif job_item.item_subtype~=-1 then if item:getSubtype()~=job_item.item_subtype then @@ -638,7 +674,7 @@ function AssignJobItems(args) --end if (item_counts[job_id]>0 and item_suitable) or settings.build_by_items then - cur_item.flags.in_job=true + --cur_item.flags.in_job=true job.items:insert("#",{new=true,item=cur_item,role=df.job_item_ref.T_role.Reagent,job_item_idx=job_id}) item_counts[job_id]=item_counts[job_id]-cur_item:getTotalDimension() --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),item_counts[job_id])) @@ -739,12 +775,13 @@ actions={ --{"Diagnose Patient" ,df.job_type.DiagnosePatient,{IsUnit},{SetPatientRef}}, --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, {"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, - {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant}}, + {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant,SameSquare}}, {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild,{NoConstructedBuilding}}, + {"Clean" ,df.job_type.Clean,{}}, } @@ -909,10 +946,10 @@ function usetool:openPutWindow(building) for k,v in pairs(items) do table.insert(choices,{text=dfhack.items.getDescription(v,0),item=v}) end - require("gui.dialogs").showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) + dialog.showListPrompt("Item choice", "Choose item to put into:", COLOR_WHITE,choices,function (idx,choice) putItemToBuilding(building,choice.item) end) end function usetool:openSiegeWindow(building) - require("gui.dialogs").showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, + dialog.showListPrompt("Engine job choice", "Choose what to do:",COLOR_WHITE,{"Turn","Load","Fire"}, dfhack.curry(siegeWeaponActionChosen,building)) end function usetool:onWorkShopButtonClicked(building,index,choice) @@ -959,7 +996,7 @@ function usetool:openShopWindowButtoned(building,no_reset) return --qerror("No jobs for this workshop") end - require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,list,self:callback("onWorkShopButtonClicked",building) + dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,list,self:callback("onWorkShopButtonClicked",building) ,nil, nil,true) end function usetool:openShopWindow(building) @@ -973,12 +1010,41 @@ function usetool:openShopWindow(building) for k,v in pairs(filter_pile) do table.insert(choices,{job_id=0,text=v.name:lower(),filter=v}) end - require("gui.dialogs").showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) + dialog.showListPrompt("Workshop job choice", "Choose what to make",COLOR_WHITE,choices,dfhack.curry(onWorkShopJobChosen,state) ,nil, nil,true) else qerror("No jobs for this workshop") end end +function usetool:farmPlot(building) + local adv=df.global.world.units.active[0] + local do_harvest=false + for id, con_item in pairs(building.contained_items) do + if con_item.use_mode==2 and con_item.item:getType()==df.item_type.PLANT then + if same_xyz(adv.pos,con_item.item.pos) then + do_harvest=true + end + end + end + --check if there tile is without plantseeds,add job + + local args={unit=adv,pos=adv.pos,from_pos=adv.pos, + } + if not do_harvest then + local seedjob={items={{quantity=1,item_type=df.item_type.SEEDS}}} + args.job_type=df.job_type.PlantSeeds + args.pre_actions={dfhack.curry(setFiltersUp,seedjob)} + args.post_actions={AssignBuildingRef} + + else + args.job_type=df.job_type.HarvestPlants + args.post_actions={AssignBuildingRef} + end + local job,msg=makeJob(args) + if job==nil then + print(msg) + end +end MODES={ [df.building_type.Table]={ --todo filters... name="Put items", @@ -1016,6 +1082,10 @@ MODES={ name="Siege menu", input=usetool.openSiegeWindow, }, + [df.building_type.FarmPlot]={ + name="Plant/Harvest", + input=usetool.farmPlot, + } } function usetool:shopMode(enable,mode,building) self.subviews.shopLabel.visible=enable From bc5cdf88777e6655857317b60ee2ff655dafb298 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 11:37:12 -0500 Subject: [PATCH 397/472] Auto syndrome: got rid of allow multiple syndromes option that didn't make any sense, and made sure that if allowing multiple targets it doesn't attach the syndrome to the worker twice. --- plugins/autoSyndrome.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 2fa9fb681..574bcf853 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -119,7 +119,7 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorunits.all[workerIndex]; + df::unit* worker = df::global::world->units.all[workerIndex]; //find the building that made it int32_t buildingId = -1; for ( size_t a = 0; a < job->general_refs.size(); a++ ) { @@ -333,7 +333,6 @@ void processJob(color_ostream& out, void* jobPtr) { //add each syndrome to the guy who did the job df::syndrome* syndrome = inorganic->material.syndrome[b]; bool workerOnly = false; - bool allowMultipleSyndromes = false; bool allowMultipleTargets = false; bool foundCommand = false; bool destroyRock = true; @@ -345,8 +344,6 @@ void processJob(color_ostream& out, void* jobPtr) { if ( commandStr == "" ) { if ( *clazz == "\\WORKER_ONLY" ) { workerOnly = true; - } else if ( *clazz == "\\ALLOW_MULTIPLE_SYNDROMES" ) { - allowMultipleSyndromes = true; } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { allowMultipleTargets = true; } else if ( *clazz == "\\PRESERVE_ROCK" ) { @@ -414,10 +411,10 @@ void processJob(color_ostream& out, void* jobPtr) { } //only one syndrome per reaction will be applied, unless multiples are allowed. - if ( appliedSomething && !allowMultipleSyndromes ) + if ( appliedSomething && !allowMultipleTargets ) continue; - if ( maybeApply(out, syndrome, workerId, unit) ) { + if ( maybeApply(out, syndrome, workerId, worker) ) { appliedSomething = true; } @@ -427,6 +424,8 @@ void processJob(color_ostream& out, void* jobPtr) { //now try applying it to everybody inside the building for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { df::unit* unit = df::global::world->units.active[a]; + if ( unit == worker ) + continue; if ( unit->pos.z != building->z ) continue; if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) From 5fc466ef7e4967c883efd23678860ddb76b4b0b1 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 12:50:10 -0500 Subject: [PATCH 398/472] Work now: also update dig on pause. --- plugins/workNow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/workNow.cpp b/plugins/workNow.cpp index 906df33dc..5058259f5 100644 --- a/plugins/workNow.cpp +++ b/plugins/workNow.cpp @@ -42,6 +42,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan return CR_OK; *df::global::process_jobs = true; + *df::global::process_dig = true; return CR_OK; } From e1ad933068994c3441fa3e7bafed3dcbbfba1666 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 12:58:06 -0500 Subject: [PATCH 399/472] True transformation: make it compile. --- plugins/trueTransformation.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6da6b7e74..6c4245da8 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -25,9 +25,9 @@ void syndromeHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - EventManager::EventHandler syndrome(syndromeHandler); + EventManager::EventHandler syndrome(syndromeHandler, 1); Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, 1, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); return CR_OK; } From 838e13a80efaa54a8f0c5b80cf3596e0fd69397b Mon Sep 17 00:00:00 2001 From: Warmist Date: Sat, 5 Jan 2013 20:06:22 +0200 Subject: [PATCH 400/472] Make monarch script, for changing who is king/queen --- scripts/make-monarch.lua | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 scripts/make-monarch.lua diff --git a/scripts/make-monarch.lua b/scripts/make-monarch.lua new file mode 100644 index 000000000..253179680 --- /dev/null +++ b/scripts/make-monarch.lua @@ -0,0 +1,31 @@ +--set target unit as king/queen +local unit=dfhack.gui.getSelectedUnit() +if not unit then qerror("No unit selected") end +local newfig=dfhack.units.getNemesis(unit).figure +local my_entity=df.historical_entity.find(df.global.ui.civ_id) +local monarch_id +for k,v in pairs(my_entity.positions.own) do + if v.code=="MONARCH" then + monarch_id=v.id + break + end +end +if not monarch_id then qerror("No monarch found!") end +local old_id +for pos_id,v in pairs(my_entity.positions.assignments) do + if v.position_id==monarch_id then + old_id=v.histfig + v.histfig=newfig.id + local oldfig=df.historical_figure.find(old_id) + + for k,v in pairs(oldfig.entity_links) do + if df.histfig_entity_link_positionst:is_instance(v) and v.assignment_id==pos_id and v.entity_id==df.global.ui.civ_id then + oldfig.entity_links:erase(k) + break + end + end + newfig.entity_links:insert("#",{new=df.histfig_entity_link_positionst,entity_id=df.global.ui.civ_id, + link_strength=100,assignment_id=pos_id,start_year=df.global.cur_year}) + break + end +end From 9eafc7443d2f2fb23af5c194b8f3d4b658dabcb0 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 13:15:10 -0500 Subject: [PATCH 401/472] Update submodules. --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 22b01b80a..fbf671a7d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 +Subproject commit fbf671a7d5aacb41cb44059eb16a1ee9cad419be From cf3ac485184d4b0bd18eb19ea91fb88ec8d791d7 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 13:35:49 -0500 Subject: [PATCH 402/472] Merge in autoSyndrome, trueTransformation, ... --- .gitignore | 2 +- plugins/CMakeLists.txt | 2 + plugins/autoSyndrome.cpp | 477 +++++++++++++++++++++++++++++++++ plugins/devel/CMakeLists.txt | 1 + plugins/devel/printArgs.cpp | 32 +++ plugins/trueTransformation.cpp | 88 ++++++ 6 files changed, 601 insertions(+), 1 deletion(-) create mode 100644 plugins/autoSyndrome.cpp create mode 100644 plugins/devel/printArgs.cpp create mode 100644 plugins/trueTransformation.cpp diff --git a/.gitignore b/.gitignore index 041624475..b4a578ec0 100644 --- a/.gitignore +++ b/.gitignore @@ -58,5 +58,5 @@ build/CPack*Config.cmake /cmakeall.bat -# swap files for vim +# vim swap files *.swp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 1c89d9ab7..a2f99c206 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -132,6 +132,8 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(versionosd versionosd.cpp) DFHACK_PLUGIN(misery misery.cpp) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) + DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) + DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) endif() diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp new file mode 100644 index 000000000..884c7749b --- /dev/null +++ b/plugins/autoSyndrome.cpp @@ -0,0 +1,477 @@ +#include "PluginManager.h" +#include "Export.h" +#include "DataDefs.h" +#include "Core.h" + +#include "modules/EventManager.h" +#include "modules/Job.h" +#include "modules/Maps.h" + +#include "df/building.h" +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/item_boulderst.h" +#include "df/job.h" +#include "df/job_type.h" +#include "df/reaction.h" +#include "df/reaction_product.h" +#include "df/reaction_product_type.h" +#include "df/reaction_product_itemst.h" +#include "df/syndrome.h" +#include "df/unit_syndrome.h" +#include "df/ui.h" +#include "df/unit.h" +#include "df/general_ref.h" +#include "df/general_ref_building_holderst.h" +#include "df/general_ref_type.h" +#include "df/general_ref_unit_workerst.h" + +#include +#include +#include +#include + +using namespace std; +using namespace DFHack; + +/* +Example usage: + +////////////////////////////////////////////// +//In file inorganic_duck.txt +inorganic_stone_duck + +[OBJECT:INORGANIC] + +[INORGANIC:DUCK_ROCK] +[USE_MATERIAL_TEMPLATE:STONE_TEMPLATE] +[STATE_NAME_ADJ:ALL_SOLID:drakium][DISPLAY_COLOR:0:7:0][TILE:'.'] +[IS_STONE] +[SOLID_DENSITY:1][MELTING_POINT:25000] +[BOILING_POINT:9999] //This is the critical line: boiling point must be <= 10000 +[SYNDROME] + [SYN_NAME:Chronic Duck Syndrome] + [CE_BODY_TRANSFORMATION:PROB:100:START:0] + [CE:CREATURE:BIRD_DUCK:MALE] //even though we don't have SYN_INHALED, the plugin will add it +/////////////////////////////////////////////// +//In file building_duck.txt +building_duck + +[OBJECT:BUILDING] + +[BUILDING_WORKSHOP:DUCK_WORKSHOP] + [NAME:Duck Workshop] + [NAME_COLOR:7:0:1] + [DIM:1:1] + [WORK_LOCATION:1:1] + [BLOCK:1:0:0:0] + [TILE:0:1:236] + [COLOR:0:1:0:0:1] + [TILE:1:1:' '] + [COLOR:1:1:0:0:0] + [TILE:2:1:8] + [COLOR:2:1:0:0:1] + [TILE:3:1:8] + [COLOR:3:2:0:4:1] + [BUILD_ITEM:1:NONE:NONE:NONE:NONE] + [BUILDMAT] + [WORTHLESS_STONE_ONLY] + [CAN_USE_ARTIFACT] +/////////////////////////////////////////////// +//In file reaction_duck.txt +reaction_duck + +[OBJECT:REACTION] + +[REACTION:DUCKIFICATION] +[NAME:become a duck] +[BUILDING:DUCK_WORKSHOP:NONE] +[PRODUCT:100:100:STONE:NO_SUBTYPE:STONE:DUCK_ROCK] +////////////////////////////////////////////// +//Add the following lines to your entity in entity_default.txt (or wherever it is) + [PERMITTED_BUILDING:DUCK_WORKSHOP] + [PERMITTED_REACTION:DUCKIFICATION] +////////////////////////////////////////////// + +Next, start a new fort in a new world, build a duck workshop, then have someone become a duck. +*/ + +bool enabled = true; + +DFHACK_PLUGIN("autoSyndrome"); + +command_result autoSyndrome(color_ostream& out, vector& parameters); +void processJob(color_ostream& out, void* jobPtr); +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome); + +DFhackCExport command_result plugin_init(color_ostream& out, vector &commands) { + commands.push_back(PluginCommand("autoSyndrome", "Automatically give units syndromes when they complete jobs, as configured in the raw files.\n", &autoSyndrome, false, + "autoSyndrome:\n" + " autoSyndrome 0 //disable\n" + " autoSyndrome 1 //enable\n" + " autoSyndrome disable //disable\n" + " autoSyndrome enable //enable\n" + "\n" + "autoSyndrome looks for recently completed jobs matching certain conditions, and if it finds one, then it will give the dwarf that finished that job the syndrome specified in the raw files.\n" + "\n" + "Requirements:\n" + " 1) The job must be a custom reaction.\n" + " 2) The job must produce a stone of some inorganic material.\n" + " 3) The stone must have a boiling temperature less than or equal to 9000.\n" + "\n" + "When these conditions are met, the unit that completed the job will immediately become afflicted with all applicable syndromes associated with the inorganic material of the stone, or stones. It should correctly check for whether the creature or caste is affected or immune, and it should also correctly account for affected and immune creature classes.\n" + "Multiple syndromes per stone, or multiple boiling rocks produced with the same reaction should work fine.\n" + )); + + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + EventManager::EventHandler handle(processJob, 5); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream& out) { + return CR_OK; +} + +/*DFhackCExport command_result plugin_onstatechange(color_ostream& out, state_change_event e) { + return CR_OK; +}*/ + +command_result autoSyndrome(color_ostream& out, vector& parameters) { + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + + bool wasEnabled = enabled; + if ( parameters.size() == 1 ) { + if ( parameters[0] == "enable" ) { + enabled = true; + } else if ( parameters[0] == "disable" ) { + enabled = false; + } else { + int32_t a = atoi(parameters[0].c_str()); + if ( a < 0 || a > 1 ) + return CR_WRONG_USAGE; + + enabled = (bool)a; + } + } + + out.print("autoSyndrome is %s\n", enabled ? "enabled" : "disabled"); + if ( enabled == wasEnabled ) + return CR_OK; + + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("autoSyndrome"); + if ( enabled ) { + EventManager::EventHandler handle(processJob, 5); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + } else { + EventManager::unregisterAll(me); + } + return CR_OK; +} + +bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df::unit* unit) { + df::creature_raw* creature = df::global::world->raws.creatures.all[unit->race]; + df::caste_raw* caste = creature->caste[unit->caste]; + std::string& creature_name = creature->creature_id; + std::string& creature_caste = caste->caste_id; + //check that the syndrome applies to that guy + /* + * If there is no affected class or affected creature, then anybody who isn't immune is fair game. + * + * Otherwise, it works like this: + * add all the affected class creatures + * remove all the immune class creatures + * add all the affected creatures + * remove all the immune creatures + * you're affected if and only if you're in the remaining list after all of that + **/ + bool applies = syndrome->syn_affected_class.size() == 0 && syndrome->syn_affected_creature.size() == 0; + for ( size_t c = 0; c < syndrome->syn_affected_class.size(); c++ ) { + if ( applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_affected_class[c] == *caste->creature_class[d] ) { + applies = true; + break; + } + } + } + for ( size_t c = 0; c < syndrome->syn_immune_class.size(); c++ ) { + if ( !applies ) + break; + for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { + if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { + applies = false; + break; + } + } + } + + if ( syndrome->syn_affected_creature.size() != syndrome->syn_affected_caste.size() ) { + out.print("%s, line %d: different affected creature/caste sizes.\n", __FILE__, __LINE__); + return false; + } + for ( size_t c = 0; c < syndrome->syn_affected_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_affected_creature[c] ) + continue; + if ( *syndrome->syn_affected_caste[c] == "ALL" || + *syndrome->syn_affected_caste[c] == creature_caste ) { + applies = true; + break; + } + } + for ( size_t c = 0; c < syndrome->syn_immune_creature.size(); c++ ) { + if ( creature_name != *syndrome->syn_immune_creature[c] ) + continue; + if ( *syndrome->syn_immune_caste[c] == "ALL" || + *syndrome->syn_immune_caste[c] == creature_caste ) { + applies = false; + break; + } + } + if ( !applies ) { + return false; + } + if ( giveSyndrome(out, workerId, syndrome) < 0 ) + return false; + return true; +} + +void processJob(color_ostream& out, void* jobPtr) { + df::job* job = (df::job*)jobPtr; + if ( job == NULL ) { + out.print("Error %s line %d: null job.\n", __FILE__, __LINE__); + return; + } + if ( job->completion_timer > 0 ) + return; + + if ( job->job_type != df::job_type::CustomReaction ) + return; + + df::reaction* reaction = NULL; + for ( size_t a = 0; a < df::global::world->raws.reactions.size(); a++ ) { + df::reaction* candidate = df::global::world->raws.reactions[a]; + if ( candidate->code != job->reaction_name ) + continue; + reaction = candidate; + break; + } + if ( reaction == NULL ) { + out.print("%s, line %d: could not find reaction \"%s\".\n", __FILE__, __LINE__, job->reaction_name.c_str() ); + return; + } + + int32_t workerId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + continue; + if ( workerId != -1 ) { + out.print("%s, line %d: Found two workers on the same job.\n", __FILE__, __LINE__); + } + workerId = ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id; + if (workerId == -1) { + out.print("%s, line %d: invalid worker.\n", __FILE__, __LINE__); + continue; + } + } + + int32_t workerIndex = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( workerIndex < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return; + } + df::unit* worker = df::global::world->units.all[workerIndex]; + //find the building that made it + int32_t buildingId = -1; + for ( size_t a = 0; a < job->general_refs.size(); a++ ) { + if ( job->general_refs[a]->getType() != df::enums::general_ref_type::BUILDING_HOLDER ) + continue; + if ( buildingId != -1 ) { + out.print("%s, line %d: Found two buildings for the same job.\n", __FILE__, __LINE__); + } + buildingId = ((df::general_ref_building_holderst*)job->general_refs[a])->building_id; + if (buildingId == -1) { + out.print("%s, line %d: invalid building.\n", __FILE__, __LINE__); + continue; + } + } + df::building* building; + { + int32_t index = df::building::binsearch_index(df::global::world->buildings.all, buildingId); + if ( index == -1 ) { + out.print("%s, line %d: error: couldn't find building %d.\n", __FILE__, __LINE__, buildingId); + return; + } + building = df::global::world->buildings.all[index]; + } + + //find all of the products it makes. Look for a stone with a low boiling point. + bool appliedSomething = false; + for ( size_t a = 0; a < reaction->products.size(); a++ ) { + df::reaction_product_type type = reaction->products[a]->getType(); + //out.print("type = %d\n", (int32_t)type); + if ( type != df::enums::reaction_product_type::item ) + continue; + df::reaction_product_itemst* bob = (df::reaction_product_itemst*)reaction->products[a]; + //out.print("item_type = %d\n", (int32_t)bob->item_type); + if ( bob->item_type != df::enums::item_type::BOULDER ) + continue; + //for now don't worry about subtype + + //must be a boiling rock syndrome + df::inorganic_raw* inorganic = df::global::world->raws.inorganics[bob->mat_index]; + if ( inorganic->material.heat.boiling_point > 9000 ) { + continue; + } + + for ( size_t b = 0; b < inorganic->material.syndrome.size(); b++ ) { + //add each syndrome to the guy who did the job + df::syndrome* syndrome = inorganic->material.syndrome[b]; + bool workerOnly = false; + bool allowMultipleTargets = false; + bool foundCommand = false; + bool destroyRock = true; + string commandStr; + vector args; + for ( size_t c = 0; c < syndrome->syn_class.size(); c++ ) { + std::string* clazz = syndrome->syn_class[c]; + if ( foundCommand ) { + if ( commandStr == "" ) { + if ( *clazz == "\\WORKER_ONLY" ) { + workerOnly = true; + } else if ( *clazz == "\\ALLOW_MULTIPLE_TARGETS" ) { + allowMultipleTargets = true; + } else if ( *clazz == "\\PRESERVE_ROCK" ) { + destroyRock = false; + } + else { + commandStr = *clazz; + } + } else { + stringstream bob; + if ( *clazz == "\\LOCATION" ) { + bob << job->pos.x; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.y; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + + bob << job->pos.z; + args.push_back(bob.str()); + bob.str(""); + bob.clear(); + } else if ( *clazz == "\\WORKER_ID" ) { + bob << workerId; + args.push_back(bob.str()); + } else if ( *clazz == "\\REACTION_INDEX" ) { + bob << reaction->index; + args.push_back(bob.str()); + } else { + args.push_back(*clazz); + } + } + } else if ( *clazz == "\\COMMAND" ) { + foundCommand = true; + } + } + if ( commandStr != "" ) { + Core::getInstance().runCommand(out, commandStr, args); + } + + if ( destroyRock ) { + //find the rock and kill it before it can boil and cause problems and ugliness + for ( size_t c = 0; c < df::global::world->items.all.size(); c++ ) { + df::item* item = df::global::world->items.all[c]; + if ( item->pos.z != building->z ) + continue; + if ( item->pos.x < building->x1 || item->pos.x > building->x2 ) + continue; + if ( item->pos.y < building->y1 || item->pos.y > building->y2 ) + continue; + if ( item->getType() != df::enums::item_type::BOULDER ) + continue; + //make sure it's the right type of boulder + df::item_boulderst* boulder = (df::item_boulderst*)item; + if ( boulder->mat_index != bob->mat_index ) + continue; + + boulder->flags.bits.garbage_collect = true; + boulder->flags.bits.forbid = true; + boulder->flags.bits.hidden = true; + } + } + + //only one syndrome per reaction will be applied, unless multiples are allowed. + if ( appliedSomething && !allowMultipleTargets ) + continue; + + if ( maybeApply(out, syndrome, workerId, worker) ) { + appliedSomething = true; + } + + if ( workerOnly ) + continue; + + //now try applying it to everybody inside the building + for ( size_t a = 0; a < df::global::world->units.active.size(); a++ ) { + df::unit* unit = df::global::world->units.active[a]; + if ( unit == worker ) + continue; + if ( unit->pos.z != building->z ) + continue; + if ( unit->pos.x < building->x1 || unit->pos.x > building->x2 ) + continue; + if ( unit->pos.y < building->y1 || unit->pos.y > building->y2 ) + continue; + if ( maybeApply(out, syndrome, unit->id, unit) ) { + appliedSomething = true; + if ( !allowMultipleTargets ) + break; + } + } + } + } + + return; +} + +/* + * Heavily based on https://gist.github.com/4061959/ + **/ +int32_t giveSyndrome(color_ostream& out, int32_t workerId, df::syndrome* syndrome) { + int32_t index = df::unit::binsearch_index(df::global::world->units.all, workerId); + if ( index < 0 ) { + out.print("%s line %d: Couldn't find unit %d.\n", __FILE__, __LINE__, workerId); + return -1; + } + df::unit* unit = df::global::world->units.all[index]; + + df::unit_syndrome* unitSyndrome = new df::unit_syndrome(); + unitSyndrome->type = syndrome->id; + unitSyndrome->year = 0; + unitSyndrome->year_time = 0; + unitSyndrome->ticks = 1; + unitSyndrome->unk1 = 1; + unitSyndrome->flags = 0; //typecast + + for ( size_t a = 0; a < syndrome->ce.size(); a++ ) { + df::unit_syndrome::T_symptoms* symptom = new df::unit_syndrome::T_symptoms(); + symptom->unk1 = 0; + symptom->unk2 = 0; + symptom->ticks = 1; + symptom->flags = 2; //TODO: ??? + unitSyndrome->symptoms.push_back(symptom); + } + unit->syndromes.active.push_back(unitSyndrome); + return 0; +} + diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index c2d67c628..8a1edb9cc 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -19,6 +19,7 @@ DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) DFHACK_PLUGIN(eventExample eventExample.cpp) +DFHACK_PLUGIN(printArgs printArgs.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/printArgs.cpp b/plugins/devel/printArgs.cpp new file mode 100644 index 000000000..051c7b1dc --- /dev/null +++ b/plugins/devel/printArgs.cpp @@ -0,0 +1,32 @@ + +#include "Console.h" +#include "Core.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include + +using namespace DFHack; +using namespace std; + +command_result printArgs (color_ostream &out, std::vector & parameters); + +DFHACK_PLUGIN("printArgs"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "printArgs", "Print the arguments given.", + printArgs, false + )); + return CR_OK; +} + +command_result printArgs (color_ostream &out, std::vector & parameters) +{ + for ( size_t a = 0; a < parameters.size(); a++ ) { + out << "Argument " << (a+1) << ": \"" << parameters[a] << "\"" << endl; + } + return CR_OK; +} diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp new file mode 100644 index 000000000..6c4245da8 --- /dev/null +++ b/plugins/trueTransformation.cpp @@ -0,0 +1,88 @@ + +#include "Core.h" +#include "Console.h" +#include "DataDefs.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/EventManager.h" + +#include "df/caste_raw.h" +#include "df/creature_raw.h" +#include "df/syndrome.h" +#include "df/unit.h" +#include "df/unit_syndrome.h" +#include "df/world.h" + +#include + +using namespace DFHack; +using namespace std; + +DFHACK_PLUGIN("trueTransformation"); + +void syndromeHandler(color_ostream& out, void* ptr); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + EventManager::EventHandler syndrome(syndromeHandler, 1); + Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); + + return CR_OK; +} + +void syndromeHandler(color_ostream& out, void* ptr) { + EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; + //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); + + int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); + if ( index < 0 ) { + out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); + return; + } + df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; + df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; + + bool foundIt = false; + int32_t raceId = -1; + df::creature_raw* creatureRaw = NULL; + int32_t casteId = -1; + for ( size_t a = 0; a < syndrome->syn_class.size(); a++ ) { + if ( *syndrome->syn_class[a] == "\\PERMANENT" ) { + foundIt = true; + } + if ( foundIt && raceId == -1 ) { + //find the race with the name + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < df::global::world->raws.creatures.all.size(); b++ ) { + df::creature_raw* creature = df::global::world->raws.creatures.all[b]; + if ( creature->creature_id != name ) + continue; + raceId = b; + creatureRaw = creature; + break; + } + continue; + } + if ( foundIt && raceId != -1 ) { + string& name = *syndrome->syn_class[a]; + for ( size_t b = 0; b < creatureRaw->caste.size(); b++ ) { + df::caste_raw* caste = creatureRaw->caste[b]; + if ( caste->caste_id != name ) + continue; + casteId = b; + break; + } + break; + } + } + if ( !foundIt || raceId == -1 || casteId == -1 ) + return; + + unit->enemy.normal_race = raceId; + unit->enemy.normal_caste = casteId; + //that's it! +} + From 4920293c2db40454c101bdfcb524aaad6816d37d Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:30:48 -0500 Subject: [PATCH 403/472] Infinite sky: get it to compile. --- plugins/skyEternal.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/skyEternal.cpp b/plugins/skyEternal.cpp index 04a176a8d..3ab353b4f 100644 --- a/plugins/skyEternal.cpp +++ b/plugins/skyEternal.cpp @@ -80,7 +80,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; { t_gamemodes mode; - if ( !Core::getInstance().getWorld()->ReadGameMode(mode) ) + if ( !World::ReadGameMode(mode) ) return CR_FAILURE; if ( mode.g_mode != df::enums::game_mode::DWARF ) return CR_OK; From 151ff0f29659db1b9d39e480a98e6bf674db77ef Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:34:33 -0500 Subject: [PATCH 404/472] Infinite sky: rename from sky eternal. --- plugins/CMakeLists.txt | 2 +- plugins/{skyEternal.cpp => infiniteSky.cpp} | 22 ++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) rename plugins/{skyEternal.cpp => infiniteSky.cpp} (90%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6b8986dc3..22be1c555 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -135,7 +135,7 @@ if (BUILD_SUPPORTED) #DFHACK_PLUGIN(dfstream dfstream.cpp LINK_LIBRARIES clsocket dfhack-tinythread) DFHACK_PLUGIN(autoSyndrome autoSyndrome.cpp) DFHACK_PLUGIN(trueTransformation trueTransformation.cpp) - DFHACK_PLUGIN(skyEternal skyEternal.cpp) + DFHACK_PLUGIN(infiniteSky infiniteSky.cpp) endif() diff --git a/plugins/skyEternal.cpp b/plugins/infiniteSky.cpp similarity index 90% rename from plugins/skyEternal.cpp rename to plugins/infiniteSky.cpp index 3ab353b4f..f8690b5d0 100644 --- a/plugins/skyEternal.cpp +++ b/plugins/infiniteSky.cpp @@ -22,24 +22,24 @@ using namespace std; using namespace DFHack; using namespace df::enums; -command_result skyEternal (color_ostream &out, std::vector & parameters); +command_result infiniteSky (color_ostream &out, std::vector & parameters); -DFHACK_PLUGIN("skyEternal"); +DFHACK_PLUGIN("infiniteSky"); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( - "skyEternal", + "infiniteSky", "Creates new sky levels on request, or as you construct up.", - skyEternal, false, + infiniteSky, false, "Usage:\n" - " skyEternal\n" + " infiniteSky\n" " creates one more z-level\n" - " skyEternal [n]\n" + " infiniteSky [n]\n" " creates n more z-level(s)\n" - " skyEternal enable\n" + " infiniteSky enable\n" " enables monitoring of constructions\n" - " skyEternal disable\n" + " infiniteSky disable\n" " disable monitoring of constructions\n" "\n" "If construction monitoring is enabled, then the plugin will automatically create new sky z-levels as you construct upward.\n" @@ -94,7 +94,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) df::construction* construct = df::global::world->constructions[a]; if ( construct->pos.z+2 < zNow ) continue; - skyEternal(out, vec); + infiniteSky(out, vec); zNow = df::global::world->map.z_count_block; ///break; } @@ -103,14 +103,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -command_result skyEternal (color_ostream &out, std::vector & parameters) +command_result infiniteSky (color_ostream &out, std::vector & parameters) { if ( parameters.size() > 1 ) return CR_WRONG_USAGE; if ( parameters.size() == 0 ) { vector vec; vec.push_back("1"); - return skyEternal(out, vec); + return infiniteSky(out, vec); } if (parameters[0] == "enable") { enabled = true; From 4d57a053fc9ed9772e3ee20d541e571e7ca3329e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 16:51:29 -0500 Subject: [PATCH 405/472] Infinite sky: added helpful print statements. --- plugins/infiniteSky.cpp | 54 ++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index f8690b5d0..b86ea867a 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -71,6 +71,7 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan static size_t constructionSize = 0; static bool enabled = false; +void doInfiniteSky(color_ostream& out, int32_t howMany); DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { @@ -89,12 +90,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if ( df::global::world->constructions.size() == constructionSize ) return CR_OK; int32_t zNow = df::global::world->map.z_count_block; - vector vec; for ( size_t a = constructionSize; a < df::global::world->constructions.size(); a++ ) { df::construction* construct = df::global::world->constructions[a]; if ( construct->pos.z+2 < zNow ) continue; - infiniteSky(out, vec); + doInfiniteSky(out, 1); zNow = df::global::world->map.z_count_block; ///break; } @@ -103,26 +103,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -command_result infiniteSky (color_ostream &out, std::vector & parameters) -{ - if ( parameters.size() > 1 ) - return CR_WRONG_USAGE; - if ( parameters.size() == 0 ) { - vector vec; - vec.push_back("1"); - return infiniteSky(out, vec); - } - if (parameters[0] == "enable") { - enabled = true; - return CR_OK; - } - if (parameters[0] == "disable") { - enabled = false; - constructionSize = 0; - return CR_OK; - } - int32_t howMany = 0; - howMany = atoi(parameters[0].c_str()); +void doInfiniteSky(color_ostream& out, int32_t howMany) { df::world* world = df::global::world; CoreSuspender suspend; int32_t x_count_block = world->map.x_count_block; @@ -138,6 +119,9 @@ command_result infiniteSky (color_ostream &out, std::vector & para column[z_count_block] = NULL; delete[] block_index[a][b]; block_index[a][b] = column; + + //deal with map_block_column stuff even though it'd probably be fine + } } df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; @@ -148,5 +132,31 @@ command_result infiniteSky (color_ostream &out, std::vector & para world->map.z_count++; } +} + +command_result infiniteSky (color_ostream &out, std::vector & parameters) +{ + if ( parameters.size() > 1 ) + return CR_WRONG_USAGE; + if ( parameters.size() == 0 ) { + vector vec; + vec.push_back("1"); + return infiniteSky(out, vec); + } + if (parameters[0] == "enable") { + enabled = true; + out.print("Construction monitoring enabled.\n"); + return CR_OK; + } + if (parameters[0] == "disable") { + enabled = false; + out.print("Construction monitoring disabled.\n"); + constructionSize = 0; + return CR_OK; + } + int32_t howMany = 0; + howMany = atoi(parameters[0].c_str()); + out.print("InfiniteSky: creating %d new z-levels of sky.\n", howMany); + doInfiniteSky(out, howMany); return CR_OK; } From 86c3c385bd3895c8a29ac1da36deaecd4834790d Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 5 Jan 2013 16:27:57 -0600 Subject: [PATCH 406/472] Autolabor: exclude hauling labors from clawback --- plugins/autolabor.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index d96baec9e..788247352 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -2062,6 +2062,8 @@ public: { if (l == df::unit_labor::NONE) continue; + if (l >= df::unit_labor::HAUL_STONE && l <= df::unit_labor::HAUL_ANIMAL) + continue; if (labor_infos[l].idle_dwarfs > 0 && labor_needed[l] > labor_infos[l].busy_dwarfs) { int clawback = labor_infos[l].busy_dwarfs; From 47b20ea301c267d69e4271691b21593acbd3193e Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 17:32:23 -0500 Subject: [PATCH 407/472] Infinite sky: added glyphs and made it print status when no arguments given. --- plugins/infiniteSky.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index b86ea867a..1c99df6d0 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -10,6 +10,7 @@ #include "df/construction.h" #include "df/game_mode.h" #include "df/map_block.h" +#include "df/map_block_column.h" #include "df/world.h" #include "df/z_level_flags.h" @@ -114,14 +115,32 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) { df::map_block**** block_index = world->map.block_index; for ( int32_t a = 0; a < x_count_block; a++ ) { for ( int32_t b = 0; b < y_count_block; b++ ) { - df::map_block** column = new df::map_block*[z_count_block+1]; - memcpy(column, block_index[a][b], z_count_block*sizeof(df::map_block*)); - column[z_count_block] = NULL; + df::map_block** blockColumn = new df::map_block*[z_count_block+1]; + memcpy(blockColumn, block_index[a][b], z_count_block*sizeof(df::map_block*)); + blockColumn[z_count_block] = NULL; delete[] block_index[a][b]; - block_index[a][b] = column; + block_index[a][b] = blockColumn; //deal with map_block_column stuff even though it'd probably be fine - + df::map_block_column* column = world->map.column_index[a][b]; + if ( !column ) { + out.print("%s, line %d: column is null (%d, %d).\n", __FILE__, __LINE__, a, b); + continue; + } + df::map_block_column::T_unmined_glyphs* glyphs = new df::map_block_column::T_unmined_glyphs; + glyphs->x[0] = 0; + glyphs->x[1] = 1; + glyphs->x[2] = 2; + glyphs->x[3] = 3; + glyphs->y[0] = 0; + glyphs->y[1] = 0; + glyphs->y[2] = 0; + glyphs->y[3] = 0; + glyphs->tile[0] = 'e'; + glyphs->tile[1] = 'x'; + glyphs->tile[2] = 'p'; + glyphs->tile[3] = '^'; + column->unmined_glyphs.push_back(glyphs); } } df::z_level_flags* flags = new df::z_level_flags[z_count_block+1]; @@ -139,9 +158,8 @@ command_result infiniteSky (color_ostream &out, std::vector & para if ( parameters.size() > 1 ) return CR_WRONG_USAGE; if ( parameters.size() == 0 ) { - vector vec; - vec.push_back("1"); - return infiniteSky(out, vec); + out.print("Construction monitoring is %s.\n", enabled ? "enabled" : "disabled"); + return CR_OK; } if (parameters[0] == "enable") { enabled = true; @@ -156,7 +174,7 @@ command_result infiniteSky (color_ostream &out, std::vector & para } int32_t howMany = 0; howMany = atoi(parameters[0].c_str()); - out.print("InfiniteSky: creating %d new z-levels of sky.\n", howMany); + out.print("InfiniteSky: creating %d new z-level%s of sky.\n", howMany, howMany == 1 ? "" : "s" ); doInfiniteSky(out, howMany); return CR_OK; } From f8abd5c595e5fa25b210258279478ef06830b9c6 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 18:09:50 -0500 Subject: [PATCH 408/472] StepBetween: renamed stuff back to the way it was. Thought I had already done that. --- library/LuaApi.cpp | 2 +- library/include/modules/Maps.h | 2 +- library/modules/Maps.cpp | 2 +- plugins/devel/stepBetween.cpp | 2 +- plugins/fix-armory.cpp | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 720ccd21f..0151ed404 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1309,7 +1309,7 @@ static const LuaWrapper::FunctionReg dfhack_maps_module[] = { WRAPM(Maps, enableBlockUpdates), WRAPM(Maps, getGlobalInitFeature), WRAPM(Maps, getLocalInitFeature), - WRAPM(Maps, canPathBetween), + WRAPM(Maps, canWalkBetween), WRAPM(Maps, spawnFlow), WRAPN(hasTileAssignment, hasTileAssignment), WRAPN(getTileAssignment, getTileAssignment), diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 29d3d69ba..265c89a9f 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -308,7 +308,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); -DFHACK_EXPORT bool canPathBetween(df::coord pos1, df::coord pos2); +DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); } } diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 6517331ee..5c0608765 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -524,7 +524,7 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } -bool Maps::canPathBetween(df::coord pos1, df::coord pos2) +bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) { auto block1 = getTileBlock(pos1); auto block2 = getTileBlock(pos2); diff --git a/plugins/devel/stepBetween.cpp b/plugins/devel/stepBetween.cpp index c1bc3fa1a..32d5be31b 100644 --- a/plugins/devel/stepBetween.cpp +++ b/plugins/devel/stepBetween.cpp @@ -80,7 +80,7 @@ df::coord prev; command_result stepBetween (color_ostream &out, std::vector & parameters) { df::coord bob = Gui::getCursorPos(); - out.print("(%d,%d,%d), (%d,%d,%d): canWalkBetween = %d, canPathBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canPathBetween(prev,bob)); + out.print("(%d,%d,%d), (%d,%d,%d): canStepBetween = %d, canWalkBetween = %d\n", prev.x, prev.y, prev.z, bob.x, bob.y, bob.z, Maps::canStepBetween(prev, bob), Maps::canWalkBetween(prev,bob)); prev = bob; return CR_OK; diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index e54639960..b937d40e8 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -475,7 +475,7 @@ static bool try_store_item(df::building *target, df::item *item) df::coord tpos(target->centerx, target->centery, target->z); df::coord ipos = Items::getPosition(item); - if (!Maps::canPathBetween(tpos, ipos)) + if (!Maps::canWalkBetween(tpos, ipos)) return false; // Check if the target has enough space left From 87d79b1119db81f2cee9576ecc36ccae6abc2195 Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 6 Jan 2013 18:58:58 +0200 Subject: [PATCH 409/472] advfort: is sand bearing fix for glass making, is corpse fix for butchering (todo dissasemble and see how it works really) --- scripts/gui/advfort.lua | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index c65e20473..b737be235 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -390,6 +390,12 @@ function isSuitableItem(job_item,item) end end end + if job_item.flags1.sand_bearing and not item:isSandBearing() then + return false,"not sand bearing" + end + if job_item.flags1.butcherable and not (item:getType()== df.item_type.CORPSE or item:getType()==CORPSEPIECE) then + return false,"not butcherable" + end local matinfo=dfhack.matinfo.decode(item) --print(matinfo:getCraftClass()) --print("Matching ",item," vs ",job_item) @@ -695,8 +701,9 @@ function AssignJobItems(args) if #uncollected == 0 then job.flags.working=true else - uncollected[1].is_fetching=1 job.flags.fetching=true + uncollected[1].is_fetching=1 + end --todo set working for workshops if items are present, else set fetching (and at least one item to is_fetching=1) --job.flags.working=true @@ -1041,8 +1048,11 @@ function usetool:farmPlot(building) args.post_actions={AssignBuildingRef} end local job,msg=makeJob(args) - if job==nil then + if not job then print(msg) + else + --print(AssignJobItems(args)) --WHY U NO WORK? + end end MODES={ From 553a31226604b1813d6611791ab4060dc2be484b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 6 Jan 2013 10:59:20 -0600 Subject: [PATCH 410/472] Autolabor: add deconstruct bridge labor --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 788247352..b95550ec1 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -851,6 +851,7 @@ private: case df::building_type::TradeDepot: case df::building_type::Construction: case df::building_type::Wagon: + case df::building_type::Bridge: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); From 1990e2caf7e590dc4c0b532c0fd5e4ddf84ab742 Mon Sep 17 00:00:00 2001 From: Warmist Date: Mon, 7 Jan 2013 02:01:59 +0200 Subject: [PATCH 411/472] Small fix to adv site creation, now it's really centered around you --- plugins/lua/dfusion/adv_tools.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/lua/dfusion/adv_tools.lua b/plugins/lua/dfusion/adv_tools.lua index 95ace5d38..a17898808 100644 --- a/plugins/lua/dfusion/adv_tools.lua +++ b/plugins/lua/dfusion/adv_tools.lua @@ -116,8 +116,8 @@ end menu:add("Log adventurers position",log_pos) function addSite(x,y,rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y,civ_id,name,sitetype) if x==nil or y==nil then - x=df.global.world.map.region_x/16 - y=df.global.world.map.region_y/16 + x=(df.global.world.map.region_x+1)/16 + y=(df.global.world.map.region_y+1)/16 end if name==nil then name=dfhack.lineedit("Site name:")or "Hacked site" @@ -125,8 +125,8 @@ function addSite(x,y,rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y,civ_id,name,sitetyp if sitetype==nil then sitetype=tonumber(dfhack.lineedit("Site type (numeric):")) or 7 end - rgn_max_x=rgn_max_x or df.global.world.map.region_x%16 - rgn_max_y=rgn_max_y or df.global.world.map.region_y%16 + rgn_max_x=rgn_max_x or (df.global.world.map.region_x+1)%16 + rgn_max_y=rgn_max_y or (df.global.world.map.region_y+1)%16 rgn_min_y=rgn_min_y or rgn_max_y rgn_min_x=rgn_min_x or rgn_max_x print("Region:",rgn_max_x,rgn_min_x,rgn_max_y,rgn_min_y) From 6fd9ce339d657ce00e45dc0414f703e95d609b0c Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 7 Jan 2013 12:09:39 -0500 Subject: [PATCH 412/472] Autosyndrome: fixed the rules on when syndromes apply. --- plugins/autoSyndrome.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 884c7749b..79e8b35ec 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -184,8 +184,8 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df * * Otherwise, it works like this: * add all the affected class creatures - * remove all the immune class creatures * add all the affected creatures + * remove all the immune class creatures * remove all the immune creatures * you're affected if and only if you're in the remaining list after all of that **/ @@ -206,7 +206,7 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df for ( size_t d = 0; d < caste->creature_class.size(); d++ ) { if ( *syndrome->syn_immune_class[c] == *caste->creature_class[d] ) { applies = false; - break; + return false; } } } @@ -230,7 +230,7 @@ bool maybeApply(color_ostream& out, df::syndrome* syndrome, int32_t workerId, df if ( *syndrome->syn_immune_caste[c] == "ALL" || *syndrome->syn_immune_caste[c] == creature_caste ) { applies = false; - break; + return false; } } if ( !applies ) { From 0b6858c7ffdc5dd36c991d7e2fec4c528d7d0753 Mon Sep 17 00:00:00 2001 From: expwnent Date: Mon, 7 Jan 2013 12:36:11 -0500 Subject: [PATCH 413/472] Update news. --- NEWS | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS b/NEWS index ed6290261..5aec19361 100644 --- a/NEWS +++ b/NEWS @@ -3,6 +3,8 @@ DFHack future Internals: - support for displaying active keybindings properly. - support for reusable widgets in lua screen library. + - Maps::canStepBetween: returns whether you can walk between two tiles in one step. + - EventManager: monitors various in game events centrally so that individual plugins don't have to monitor the same things redundantly. Notable bugfixes: - autobutcher can be re-enabled again after being stopped. - stopped Dwarf Manipulator from unmasking vampires. @@ -59,6 +61,11 @@ DFHack future saving you from having to trawl through long lists of materials each time you place one. Dfusion plugin: Reworked to make use of lua modules, now all the scripts can be used from other scripts. + Auto syndrome plugin: a way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. + Infinite sky plugin: create new z-levels automatically or on request. + True transformation plugin: a better way of doing permanent transformations that allows later transformations. + Work now plugin: makes the game assign jobs every time you pause. + DFHack v0.34.11-r2 From 7c1fb39ec0f09cc4ec2fa3b7727c04db9b648817 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 9 Jan 2013 10:19:43 -0600 Subject: [PATCH 414/472] No need for these to be separate variables --- CMakeLists.txt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 50d50d18d..0d8813635 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,14 +58,10 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac endif() # set up versioning. -set(DF_VERSION_MAJOR "0") -set(DF_VERSION_MINOR "34") -set(DF_VERSION_PATCH "11") -set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}") - +set(DF_VERSION "0.34.11") SET(DFHACK_RELEASE "r2" CACHE STRING "Current release revision.") -set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-${DFHACK_RELEASE}") +set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}") ## where to install things (after the build is done, classic 'make install' or package structure) From 99ec3d9841206bf95493da484bf19c964c99b4fa Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 13 Jan 2013 22:07:10 +0200 Subject: [PATCH 415/472] gm-editor: Fixed bug with arrays with number indexes. --- scripts/gui/gm-editor.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 6f3683c55..269d09c31 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -165,7 +165,7 @@ function GmEditorUi:editSelected(index,choice) --print(type(trg.target[trg.keys[trg.selected]]),trg.target[trg.keys[trg.selected]]._kind or "") local trg_type=type(trg.target[trg_key]) if trg_type=='number' or trg_type=='string' then --ugly TODO: add metatable get selected - dialog.showInputPrompt(trg_key,"Enter new value:",COLOR_WHITE, + dialog.showInputPrompt(tostring(trg_key),"Enter new value:",COLOR_WHITE, tostring(trg.target[trg_key]),self:callback("commitEdit",trg_key)) elseif trg_type=='boolean' then From 44662de6010183bf266464c1a91810dfdbeb29e8 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sun, 13 Jan 2013 15:28:17 -0500 Subject: [PATCH 416/472] Infinite sky: bug with z level flags. --- plugins/infiniteSky.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/infiniteSky.cpp b/plugins/infiniteSky.cpp index 1c99df6d0..8750e5435 100644 --- a/plugins/infiniteSky.cpp +++ b/plugins/infiniteSky.cpp @@ -149,6 +149,8 @@ void doInfiniteSky(color_ostream& out, int32_t howMany) { flags[z_count_block].bits.update = 1; world->map.z_count_block++; world->map.z_count++; + delete[] world->map.z_level_flags; + world->map.z_level_flags = flags; } } From 90a62a82f744213d3ceccf5ecc7d6c754f841733 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 14 Jan 2013 12:12:56 -0600 Subject: [PATCH 417/472] Autolabor: add screw pump build labor --- plugins/autolabor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index b95550ec1..975ac1942 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -782,6 +782,7 @@ private: case df::building_type::WaterWheel: case df::building_type::RoadPaved: case df::building_type::Well: + case df::building_type::ScrewPump: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) From 0073c1bec2e73650e74f33c627aaf749e89123b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 15 Jan 2013 23:16:15 +0100 Subject: [PATCH 418/472] Track xml and stonesense --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 55cb62822..e87942b50 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 55cb628224f9da7d39e88e62a312d877aeed537f +Subproject commit e87942b50481280c027d8b5f64d452c09e3be095 diff --git a/plugins/stonesense b/plugins/stonesense index cb97cf308..8d3533329 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd +Subproject commit 8d35333298ceed10b089d12e730e451d427e616d From e06b6904f1fc06a3d02737c8da75e934ffb8c838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 15 Jan 2013 23:41:43 +0100 Subject: [PATCH 419/472] Small fix to sync dfhack with the structures. --- library/modules/Materials.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index e15e7e83d..17823f5f1 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -766,7 +766,7 @@ bool Materials::ReadCreatureTypesEx (void) for(size_t k = 0; k < sizecolormod;k++) { // color mod [0] -> color list - auto & indexes = colorings[k]->color_indexes; + auto & indexes = colorings[k]->pattern_index; size_t sizecolorlist = indexes.size(); caste.ColorModifier[k].colorlist.resize(sizecolorlist); for(size_t l = 0; l < sizecolorlist; l++) From 8741983aaa9ef166234b4a7de7011e6596ebebc9 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 14:59:39 +1300 Subject: [PATCH 420/472] Fix for gcc errors. It seems calls to base class members in a templated class must be fully template qualified. --- plugins/search.cpp | 48 +++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index cc1d410d3..53c45806d 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -376,10 +376,10 @@ protected: auto list = getLayerList(screen); if (!list->active) { - if (is_valid()) + if (this->is_valid()) { - clear_search(); - reset_all(); + this->clear_search(); + this->reset_all(); } return false; @@ -388,24 +388,24 @@ protected: return true; } - virtual void do_search() + virtual void do_search() { - search_generic::do_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::do_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } - - int32_t *get_viewscreen_cursor() + + int32_t *get_viewscreen_cursor() { - auto list = getLayerList(viewscreen); + auto list = getLayerList(this->viewscreen); return &list->cursor; } virtual void clear_search() { - search_generic::clear_search(); - auto list = getLayerList(viewscreen); - list->num_entries = get_primary_list()->size(); + search_generic::clear_search(); + auto list = getLayerList(this->viewscreen); + list->num_entries = this->get_primary_list()->size(); } private: @@ -458,7 +458,7 @@ protected: virtual void clear_search() { - if (saved_list1.size() > 0) + if (this->saved_list1.size() > 0) { do_pre_incremental_search(); restore_secondary_values(); @@ -471,24 +471,24 @@ protected: virtual bool is_match(T &a, T &b) = 0; virtual bool is_match(vector &a, vector &b) = 0; - + void do_pre_incremental_search() { PARENT::do_pre_incremental_search(); if (read_only) return; - bool list_has_been_sorted = (primary_list->size() == reference_list.size() - && !is_match(*primary_list, reference_list)); + bool list_has_been_sorted = (this->primary_list->size() == reference_list.size() + && !is_match(*this->primary_list, reference_list)); for (size_t i = 0; i < saved_indexes.size(); i++) { int adjusted_item_index = i; if (list_has_been_sorted) { - for (size_t j = 0; j < primary_list->size(); j++) + for (size_t j = 0; j < this->primary_list->size(); j++) { - if (is_match((*primary_list)[j], reference_list[i])) + if (is_match((*this->primary_list)[j], reference_list[i])) { adjusted_item_index = j; break; @@ -503,14 +503,14 @@ protected: void clear_viewscreen_vectors() { - search_generic::clear_viewscreen_vectors(); + search_generic::clear_viewscreen_vectors(); saved_indexes.clear(); clear_secondary_viewscreen_vectors(); } void add_to_filtered_list(size_t i) { - search_generic::add_to_filtered_list(i); + search_generic::add_to_filtered_list(i); add_to_filtered_secondary_lists(i); if (!read_only) saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed @@ -519,12 +519,12 @@ protected: virtual void do_post_search() { if (!read_only) - reference_list = *primary_list; + reference_list = *this->primary_list; } void save_original_values() { - search_generic::save_original_values(); + search_generic::save_original_values(); save_secondary_values(); } }; @@ -556,7 +556,7 @@ protected: virtual void do_post_init() { - search_multicolumn_modifiable::do_post_init(); + search_multicolumn_modifiable::do_post_init(); secondary_list = get_secondary_list(); } From 4257c9fe84c5a38ddc6cae3d3a789296210b1abe Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:14:25 +1300 Subject: [PATCH 421/472] Ignore vermin in animals screen search --- plugins/search.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/plugins/search.cpp b/plugins/search.cpp index 53c45806d..dac627b68 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -291,6 +291,11 @@ protected: } + virtual bool is_valid_for_search(size_t index) + { + return true; + } + // The actual sort virtual void do_search() { @@ -311,6 +316,9 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (!is_valid_for_search(i)) + continue; + T element = saved_list1[i]; string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) @@ -697,6 +705,11 @@ private: return viewscreen->mode == T_mode::List; } + bool is_valid_for_search(size_t i) + { + return is_vermin_s[i] == 0; + } + void save_secondary_values() { is_vermin_s = *is_vermin; From ed0baa3f6964b881e3303e3fa5d159e0d2d2857c Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 6 Jan 2013 18:32:49 +1300 Subject: [PATCH 422/472] Restore accidentally removed priority of unit screen search hook's input check over manipulator plugin. --- plugins/search.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index dac627b68..8d0805947 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -969,8 +969,9 @@ private: } }; -IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); - +typedef generic_search_hook unitlist_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search // From 309f1625667c7f8c758e1f7e70fc26957eaf52ab Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 18:54:35 +0400 Subject: [PATCH 423/472] Fix line endings again. --- plugins/search.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 8d0805947..c4884d1a9 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -969,8 +969,8 @@ private: } }; -typedef generic_search_hook unitlist_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +typedef generic_search_hook unitlist_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search @@ -1292,7 +1292,7 @@ IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // // START: Announcement list search // -class annoucnement_search : public search_generic +class annoucnement_search : public search_generic { public: void render() const From 14d41b8edac56a97480fbf671000a55710ba4112 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 18:55:05 +0400 Subject: [PATCH 424/472] Update search to the newer structure definitions. --- plugins/search.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index c4884d1a9..37fcb5826 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1303,19 +1303,19 @@ public: private: int32_t *get_viewscreen_cursor() { - return &viewscreen->anon_3; + return &viewscreen->sel_idx; } - virtual vector *get_primary_list() + virtual vector *get_primary_list() { - return &viewscreen->anon_4; + return &viewscreen->reports; } private: - string get_element_description(void *element) const + string get_element_description(df::report *element) const { - return ((df::report *) element)->text; + return element->text; } }; From 597074498fd1d0d51e9f8ae7e9cbd82bed34e7ef Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:30:11 +0400 Subject: [PATCH 425/472] Fix various issues with updated search. - Priority conflict with tweak military-stable-assign. - The noble screen misbehaves if only one list item is left. - Noble screen search string not reset after Enter/Esc. --- plugins/search.cpp | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 37fcb5826..85334940d 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -91,7 +91,15 @@ public: return false; if (!can_init(screen)) + { + if (is_valid()) + { + clear_search(); + reset_all(); + } + return false; + } if (!is_valid()) { @@ -296,6 +304,11 @@ protected: return true; } + virtual bool force_in_search(size_t index) + { + return false; + } + // The actual sort virtual void do_search() { @@ -316,6 +329,12 @@ protected: string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) { + if (force_in_search(i)) + { + add_to_filtered_list(i); + continue; + } + if (!is_valid_for_search(i)) continue; @@ -383,15 +402,7 @@ protected: { auto list = getLayerList(screen); if (!list->active) - { - if (this->is_valid()) - { - this->clear_search(); - this->reset_all(); - } - return false; - } return true; } @@ -655,6 +666,10 @@ template V generic_search_hook ::module; template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) +#define IMPLEMENT_HOOKS_PRIO(screen, module, prio) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, 100); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, 100) // // END: Generic Search functionality @@ -1197,7 +1212,7 @@ public: } }; -IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); +IMPLEMENT_HOOKS_PRIO(df::viewscreen_layer_militaryst, military_search, 100); // // END: Military screen search @@ -1349,6 +1364,11 @@ public: print_search_option(2, 23); } + bool force_in_search(size_t index) + { + return index == 0; // Leave Vacant + } + bool can_init(df::viewscreen_layer_noblelistst *screen) { if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) @@ -1488,11 +1508,6 @@ public: { return search_twocolumn_modifiable::can_init(screen); } - else if (is_valid()) - { - clear_search(); - reset_all(); - } return false; } From e1a2e6ece40d367325789b44857f6b58f1b0f460 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:42:41 +0400 Subject: [PATCH 426/472] Block and grey out the trade screen actions when search is active. After actually trying the search in game, it is obvious that clearing search upon pressing the trade button is confusing, because if you don't pay enough attention, it looks exactly like as if the trade actually happened. --- Readme.rst | 9 ++++++--- plugins/search.cpp | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Readme.rst b/Readme.rst index 24be76f96..236eafb01 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2105,7 +2105,9 @@ directly to the main dwarf mode screen. Search ====== -The search plugin adds search to the Stocks, Trading, Stockpile and Unit List screens. +The search plugin adds search to the Stocks, Animals, Trading, Stockpile, +Noble (assignment candidates), Military (position candidates), Burrows +(unit list), Rooms, Announcements, Job List and Unit List screens. .. image:: images/search.png @@ -2125,8 +2127,9 @@ Leaving any screen automatically clears the filter. In the Trade screen, the actual trade will always only act on items that are actually visible in the list; the same effect applies to the Trade -Value numbers displayed by the screen. Because of this, pressing the 't' -key while search is active clears the search instead of executing the trade. +Value numbers displayed by the screen. Because of this, the 't' key is +blocked while search is active, so you have to reset the filters first. +Pressing Alt-C will clear both search strings. In the stockpile screen the option only appears if the cursor is in the rightmost list: diff --git a/plugins/search.cpp b/plugins/search.cpp index 85334940d..d7b2694bf 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -57,6 +57,27 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) x += text.length(); } +void make_text_dim(int x1, int x2, int y) +{ + for (int x = x1; x <= x2; x++) + { + Screen::Pen pen = Screen::readTile(x,y); + + if (pen.valid()) + { + if (pen.fg != 0) + { + if (pen.fg == 7) + pen.adjust(0,true); + else + pen.bold = 0; + } + + Screen::paintTile(pen,x,y); + } + } +} + static bool is_live_screen(const df::viewscreen *screen) { for (df::viewscreen *cur = &gview->view; cur; cur = cur->child) @@ -1015,13 +1036,8 @@ private: { // Block the keys if were searching if (!search_string.empty()) - { input->clear(); - // Send a force clear to other search class too - input->insert(interface_key::CUSTOM_ALT_C); - } - clear_search_for_trade(); return false; } else if (input->count(interface_key::CUSTOM_ALT_C)) @@ -1048,6 +1064,12 @@ public: virtual void render() const { print_search_option(2, 26); + + if (!search_string.empty()) + { + make_text_dim(2, 37, 22); + make_text_dim(42, gps->dimx-2, 22); + } } private: @@ -1081,6 +1103,12 @@ public: virtual void render() const { print_search_option(42, 26); + + if (!search_string.empty()) + { + make_text_dim(2, 37, 22); + make_text_dim(42, gps->dimx-2, 22); + } } private: From 1a3987bab3bca16ffc71d8e72e731ad34338e10f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 16 Jan 2013 20:42:55 +0400 Subject: [PATCH 427/472] Proper line breaks in the NEWS file. --- NEWS | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index f2a547e10..d9c184832 100644 --- a/NEWS +++ b/NEWS @@ -64,11 +64,14 @@ DFHack future Reworked to make use of lua modules, now all the scripts can be used from other scripts. New Eventful plugin: A collection of lua events, that will allow new ways to interact with df world. - Auto syndrome plugin: a way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. - Infinite sky plugin: create new z-levels automatically or on request. - True transformation plugin: a better way of doing permanent transformations that allows later transformations. - Work now plugin: makes the game assign jobs every time you pause. - + Auto syndrome plugin: + A way of automatically applying boiling rock syndromes and calling dfhack commands controlled by raws. + Infinite sky plugin: + Create new z-levels automatically or on request. + True transformation plugin: + A better way of doing permanent transformations that allows later transformations. + Work now plugin: + Makes the game assign jobs every time you pause. DFHack v0.34.11-r2 From 675e92f350773b733ca8a1159d8cd37eaf61d35f Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Tue, 22 Jan 2013 16:34:51 -0600 Subject: [PATCH 428/472] Autolabor: add build floor grate labor, add unbuild screwpump labor, protect pump operators, do not clear labors on already busy dwarfs --- plugins/autolabor.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 975ac1942..15e390b22 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -810,6 +810,7 @@ private: case df::building_type::TractionBench: case df::building_type::Slab: case df::building_type::Chain: + case df::building_type::GrateFloor: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: @@ -853,6 +854,7 @@ private: case df::building_type::Construction: case df::building_type::Wagon: case df::building_type::Bridge: + case df::building_type::ScrewPump: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); @@ -2178,7 +2180,10 @@ public: } int score = skill_level * 1000 - (d->high_skill - skill_level) * 2000 + (xp / (skill_level + 5) * 10); if (d->dwarf->status.labors[labor]) - score += 500; + if (labor == df::unit_labor::OPERATE_PUMP) + score += 50000; + else + score += 1000; if (default_labor_infos[labor].tool != TOOL_NONE && d->has_tool[default_labor_infos[labor].tool]) score += 5000; @@ -2219,7 +2224,7 @@ public: (*bestdwarf)->dwarf->military.pickup_flags.bits.update = true; } } - else + else if ((*bestdwarf)->state == IDLE) (*bestdwarf)->clear_labor(l); } From 0e384ada753f364a47e818c830c7419bdf659cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 5 Feb 2013 05:34:34 +0100 Subject: [PATCH 429/472] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e87942b50..a29068467 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e87942b50481280c027d8b5f64d452c09e3be095 +Subproject commit a290684672102222da7b284424036a60c0ac56f9 diff --git a/plugins/stonesense b/plugins/stonesense index 8d3533329..3877374ff 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 8d35333298ceed10b089d12e730e451d427e616d +Subproject commit 3877374ff5618629ce0fcc3339ec0cf57dd4572e From e707d4552f0d62cb6b0de1678e635a20a6c40729 Mon Sep 17 00:00:00 2001 From: Warmist Date: Tue, 5 Feb 2013 07:21:44 +0200 Subject: [PATCH 430/472] advfort.lua: small bugfix for butcher. --- scripts/gui/advfort.lua | 70 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index b737be235..f58735361 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -11,6 +11,7 @@ use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, workshop={key="CHANGETAB",desc="Show building menu"}, } + local gui = require 'gui' local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' @@ -393,7 +394,7 @@ function isSuitableItem(job_item,item) if job_item.flags1.sand_bearing and not item:isSandBearing() then return false,"not sand bearing" end - if job_item.flags1.butcherable and not (item:getType()== df.item_type.CORPSE or item:getType()==CORPSEPIECE) then + if job_item.flags1.butcherable and not (item:getType()== df.item_type.CORPSE or item:getType()==df.item_type.CORPSEPIECE) then return false,"not butcherable" end local matinfo=dfhack.matinfo.decode(item) @@ -1023,6 +1024,61 @@ function usetool:openShopWindow(building) qerror("No jobs for this workshop") end end +function usetool:armCleanTrap(building) + local adv=df.global.world.units.active[0] + --[[ + Lever, + PressurePlate, + CageTrap, + StoneFallTrap, + WeaponTrap, + TrackStop + --]] + if building.state==0 then + --CleanTrap + --[[ LoadCageTrap, + LoadStoneTrap, + LoadWeaponTrap, + ]] + + local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.CleanTrap} + if building.trap_type==df.trap_type.CageTrap then + args.job_type=df.job_type.LoadCageTrap + local job_filter={items={{quantity=1,item_type=df.item_type.CAGE}} } + args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + + elseif building.trap_type==df.trap_type.StoneFallTrap then + args.job_type=df.job_type.LoadStoneTrap + local job_filter={items={{quantity=1,item_type=df.item_type.BOULDER}} } + args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + elseif building.trap_type==df.trap_type.WeaponTrap then + qerror("TODO") + else + return + end + local job,msg=makeJob(args) + if not job then + print(msg) + end + end +end +function usetool:hiveActions(building) + local adv=df.global.world.units.active[0] + local args={unit=adv,post_actions={AssignBuildingRef,AssignJobItems},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.InstallColonyInHive} + local job_filter={items={{quantity=1,item_type=df.item_type.VERMIN}} } + args.pre_actions={dfhack.curry(setFiltersUp,job_filter)} + local job,msg=makeJob(args) + if not job then + print(msg) + end + --InstallColonyInHive, + --CollectHiveProducts, +end +function usetool:operatePump(building) + + local adv=df.global.world.units.active[0] + makeJob{unit=adv,post_actions={AssignBuildingRef},pos=adv.pos,from_pos=adv.pos,job_type=df.job_type.OperatePump} +end function usetool:farmPlot(building) local adv=df.global.world.units.active[0] local do_harvest=false @@ -1095,6 +1151,18 @@ MODES={ [df.building_type.FarmPlot]={ name="Plant/Harvest", input=usetool.farmPlot, + }, + [df.building_type.ScrewPump]={ + name="Operate Pump", + input=usetool.operatePump, + }, + [df.building_type.Trap]={ + name="Arm/Clean Trap", + input=usetool.armCleanTrap, + }, + [df.building_type.Hive]={ + name="Hive actions", + input=usetool.hiveActions, } } function usetool:shopMode(enable,mode,building) From f90737e2747e37c72ab0dd79fd4d34417856aaeb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 10 Feb 2013 15:26:48 +0400 Subject: [PATCH 431/472] Add more comments to the vmethod interpose implementation. --- library/VTableInterpose.cpp | 55 ++++++++++++++- library/include/VTableInterpose.h | 109 ++++++++++++++++++------------ 2 files changed, 117 insertions(+), 47 deletions(-) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 3f9423b45..0db5ac03c 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -39,15 +39,54 @@ using namespace DFHack; /* * Code for accessing method pointers directly. Very compiler-specific. + * + * Pointers to methods in C++ are conceptually similar to pointers to + * functions, but with some complications. Specifically, the target of + * such pointer can be either: + * + * - An ordinary non-virtual method, in which case the pointer behaves + * not much differently from a simple function pointer. + * - A virtual method, in which case calling the pointer must emulate + * an ordinary call to that method, i.e. fetch the real code address + * from the vtable at the appropriate index. + * + * This means that pointers to virtual methods actually have to encode + * the relevant vtable index value in some way. Also, since these two + * types of pointers cannot be distinguished by data type and differ + * only in value, any sane compiler would ensure that any non-virtual + * method that can potentially be called via a pointer uses the same + * parameter passing rules as an equivalent virtual method, so that + * the same parameter passing code would work with both types of pointer. + * + * This means that with a few small low-level compiler-specific wrappers + * to access the data inside such pointers it is possible to: + * + * - Convert a non-virtual method pointer into a code address that + * can be directly put into a vtable. + * - Convert a pointer taken out of a vtable into a fake non-virtual + * method pointer that can be used to easily call the original + * vmethod body. + * - Extract a vtable index out of a virtual method pointer. + * + * Taken together, these features allow delegating all the difficult + * and fragile tasks like passing parameters and calculating the + * vtable index to the C++ compiler. */ #if defined(_MSC_VER) +// MSVC may use up to 3 different representations +// based on context, but adding the /vmg /vmm options +// forces it to stick to this one. It can accomodate +// multiple, but not virtual inheritance. struct MSVC_MPTR { void *method; intptr_t this_shift; }; +// Debug builds sometimes use additional thunks that +// just jump to the real one, presumably to attach some +// additional debug info. static uint32_t *follow_jmp(void *ptr) { uint8_t *p = (uint8_t*)ptr; @@ -56,10 +95,10 @@ static uint32_t *follow_jmp(void *ptr) { switch (*p) { - case 0xE9: + case 0xE9: // jmp near rel32 p += 5 + *(int32_t*)(p+1); break; - case 0xEB: + case 0xEB: // jmp short rel8 p += 2 + *(int8_t*)(p+1); break; default: @@ -120,8 +159,10 @@ void DFHack::addr_to_method_pointer_(void *pptr, void *addr) #elif defined(__GXX_ABI_VERSION) +// GCC seems to always use this structure - possibly unless +// virtual inheritance is involved, but that's irrelevant. struct GCC_MPTR { - intptr_t method; + intptr_t method; // Code pointer or tagged vtable offset intptr_t this_shift; }; @@ -254,6 +295,14 @@ VMethodInterposeLinkBase::VMethodInterposeLinkBase(virtual_identity *host, int v { if (vmethod_idx < 0 || interpose_method == NULL) { + /* + * A failure here almost certainly means a problem in one + * of the pointer-to-method access wrappers above: + * + * - vmethod_idx comes from vmethod_pointer_to_idx_ + * - interpose_method comes from method_pointer_to_addr_ + */ + fprintf(stderr, "Bad VMethodInterposeLinkBase arguments: %d %08x\n", vmethod_idx, unsigned(interpose_method)); fflush(stderr); diff --git a/library/include/VTableInterpose.h b/library/include/VTableInterpose.h index bd3c23036..8e2541c55 100644 --- a/library/include/VTableInterpose.h +++ b/library/include/VTableInterpose.h @@ -28,6 +28,58 @@ distribution. namespace DFHack { + /* VMethod interpose API. + + This API allows replacing an entry in the original vtable + with code defined by DFHack, while retaining ability to + call the original code. The API can be safely used from + plugins, and multiple hooks for the same vmethod are + automatically chained (subclass before superclass; at same + level highest priority called first; undefined order otherwise). + + Usage: + + struct my_hack : df::someclass { + typedef df::someclass interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) { + // If needed by the code, claim the suspend lock. + // DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK! + // CoreSuspendClaimer suspend; + ... + INTERPOSE_NEXT(foo)(arg) // call the original + ... + } + }; + + IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); + or + IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); + + void init() { + if (!INTERPOSE_HOOK(my_hack, foo).apply()) + error(); + } + + void shutdown() { + INTERPOSE_HOOK(my_hack, foo).remove(); + } + + Important caveat: + + This will NOT intercept calls to the superclass vmethod + from overriding vmethod bodies in subclasses, i.e. whenever + DF code contains something like this, the call to "superclass::foo()" + doesn't actually use vtables, and thus will never trigger any hooks: + + class superclass { virtual foo() { ... } }; + class subclass : superclass { virtual foo() { ... superclass::foo(); ... } }; + + The only workaround is to implement and apply a second hook for subclass::foo, + and repeat that for any other subclasses and sub-subclasses that override this + vmethod. + */ + template struct StaticAssert; template<> struct StaticAssert {}; @@ -81,43 +133,6 @@ namespace DFHack return addr_to_method_pointer

                                                                                (identity.get_vmethod_ptr(idx)); } - /* VMethod interpose API. - - This API allows replacing an entry in the original vtable - with code defined by DFHack, while retaining ability to - call the original code. The API can be safely used from - plugins, and multiple hooks for the same vmethod are - automatically chained (subclass before superclass; at same - level highest priority called first; undefined order otherwise). - - Usage: - - struct my_hack : df::someclass { - typedef df::someclass interpose_base; - - DEFINE_VMETHOD_INTERPOSE(void, foo, (int arg)) { - // If needed by the code, claim the suspend lock. - // DO NOT USE THE USUAL CoreSuspender, OR IT WILL DEADLOCK! - // CoreSuspendClaimer suspend; - ... - INTERPOSE_NEXT(foo)(arg) // call the original - ... - } - }; - - IMPLEMENT_VMETHOD_INTERPOSE(my_hack, foo); - or - IMPLEMENT_VMETHOD_INTERPOSE_PRIO(my_hack, foo, priority); - - void init() { - if (!INTERPOSE_HOOK(my_hack, foo).apply()) - error(); - } - - void shutdown() { - INTERPOSE_HOOK(my_hack, foo).remove(); - } - */ #define DEFINE_VMETHOD_INTERPOSE(rtype, name, args) \ typedef rtype (interpose_base::*interpose_ptr_##name)args; \ @@ -142,18 +157,21 @@ namespace DFHack friend class virtual_identity; virtual_identity *host; // Class with the vtable - int vmethod_idx; + int vmethod_idx; // Index of the interposed method in the vtable void *interpose_method; // Pointer to the code of the interposing method - void *chain_mptr; // Pointer to the chain field below - int priority; + void *chain_mptr; // Pointer to the chain field in the subclass below + int priority; // Higher priority hooks are called earlier - bool applied; - void *saved_chain; // Previous pointer to the code - VMethodInterposeLinkBase *next, *prev; // Other hooks for the same method + bool applied; // True if this hook is currently applied + void *saved_chain; // Pointer to the code of the original vmethod or next hook - // inherited vtable members + // Chain of hooks within the same host + VMethodInterposeLinkBase *next, *prev; + // Subclasses that inherit this topmost hook directly std::set child_hosts; + // Hooks within subclasses that branch off this topmost hook std::set child_next; + // (See the cpp file for a more detailed description of these links) void set_chain(void *chain); void on_host_delete(virtual_identity *host); @@ -172,6 +190,9 @@ namespace DFHack template class VMethodInterposeLink : public VMethodInterposeLinkBase { public: + // Exactly the same as the saved_chain field of superclass, + // but converted to the appropriate pointer-to-method type. + // Kept up to date via the chain_mptr pointer. Ptr chain; operator Ptr () { return chain; } From 39dbaf743abbbbd9ddc91cced6d942020d89dfa9 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 13:54:49 +0400 Subject: [PATCH 432/472] Add a script to fix cloth stockpiles by patching memory objects. This patching needs to be done every time raws are reloaded. --- dfhack.init-example | 3 ++ scripts/fix/cloth-stockpile.lua | 80 +++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+) create mode 100644 scripts/fix/cloth-stockpile.lua diff --git a/dfhack.init-example b/dfhack.init-example index 1a5aee48f..ddf93de16 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -144,6 +144,9 @@ tweak military-training # write the correct season to gamelog on world load soundsense-season +# patch the material objects in memory to fix cloth stockpiles +fix/cloth-stockpile enable + ####################################################### # Apply binary patches at runtime # # # diff --git a/scripts/fix/cloth-stockpile.lua b/scripts/fix/cloth-stockpile.lua new file mode 100644 index 000000000..7da5d583c --- /dev/null +++ b/scripts/fix/cloth-stockpile.lua @@ -0,0 +1,80 @@ +-- Fixes cloth/thread stockpiles by correcting material object data. + +local raws = df.global.world.raws +local organic_types = raws.mat_table.organic_types +local organic_indexes = raws.mat_table.organic_indexes + +local function verify(category,idx,vtype,vidx) + if idx == -1 then + -- Purely for reporting reasons + return true + end + local tvec = organic_types[category] + if idx < 0 or idx >= #tvec or tvec[idx] ~= vtype then + return false + end + return organic_indexes[category][idx] == vidx +end + +local patched_cnt = 0 +local mat_cnt = 0 + +function patch_material(mat,mat_type,mat_index) + local idxarr = mat.food_mat_index + + -- These refer to fish/eggs, i.e. castes and not materials + idxarr[1] = -1 + idxarr[2] = -1 + idxarr[3] = -1 + + for i = 0,#idxarr-1 do + if not verify(i,idxarr[i],mat_type,mat_index) then + idxarr[i] = -1 + patched_cnt = patched_cnt+1 + end + end + + mat_cnt = mat_cnt + 1 +end + +function patch_materials() + patched_cnt = 0 + mat_cnt = 0 + + print('Fixing cloth stockpile handling (bug 5739)...') + + for i,v in ipairs(raws.inorganics) do + patch_material(v.material, 0, i) + end + + for i,v in ipairs(raws.creatures.all) do + for j,m in ipairs(v.material) do + patch_material(m, 19+j, i) + end + end + + for i,v in ipairs(raws.plants.all) do + for j,m in ipairs(v.material) do + patch_material(m, 419+j, i) + end + end + + print('Patched '..patched_cnt..' bad references in '..mat_cnt..' materials.') +end + +local args = {...} + +if args[1] == 'enable' then + dfhack.onStateChange[_ENV] = function(sc) + if sc == SC_WORLD_LOADED then + patch_materials() + end + end + if dfhack.isWorldLoaded() then + patch_materials() + end +elseif args[1] == 'disable' then + dfhack.onStateChange[_ENV] = nil +else + patch_materials() +end From a17760af4f806d66a07308e4053ff6455a0af377 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 14:45:24 +0400 Subject: [PATCH 433/472] Communicate the Plugin pointer to the plugin in a decent sort of way. --- library/PluginManager.cpp | 6 ++++-- library/include/PluginManager.h | 3 ++- plugins/autoSyndrome.cpp | 3 +-- plugins/trueTransformation.cpp | 3 +-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 86bab66cd..d5636109b 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -212,9 +212,10 @@ bool Plugin::load(color_ostream &con) } const char ** plug_name =(const char ** ) LookupPlugin(plug, "name"); const char ** plug_version =(const char ** ) LookupPlugin(plug, "version"); - if(!plug_name || !plug_version) + Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self"); + if(!plug_name || !plug_version || !plug_self) { - con.printerr("Plugin %s has no name or version.\n", filename.c_str()); + con.printerr("Plugin %s has no name, version or self pointer.\n", filename.c_str()); ClosePlugin(plug); RefAutolock lock(access); state = PS_BROKEN; @@ -229,6 +230,7 @@ bool Plugin::load(color_ostream &con) state = PS_BROKEN; return false; } + *plug_self = this; RefAutolock lock(access); plugin_init = (command_result (*)(color_ostream &, std::vector &)) LookupPlugin(plug, "plugin_init"); if(!plugin_init) diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 62a195867..46a46f2df 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -247,7 +247,8 @@ namespace DFHack /// You have to have this in every plugin you write - just once. Ideally on top of the main file. #define DFHACK_PLUGIN(plugin_name) \ DFhackDataExport const char * version = DFHACK_VERSION;\ - DFhackDataExport const char * name = plugin_name; + DFhackDataExport const char * name = plugin_name;\ + DFhackDataExport Plugin *plugin_self = NULL; #define DFHACK_PLUGIN_LUA_COMMANDS \ DFhackCExport const DFHack::CommandReg plugin_lua_commands[] = diff --git a/plugins/autoSyndrome.cpp b/plugins/autoSyndrome.cpp index 79e8b35ec..3cee68589 100644 --- a/plugins/autoSyndrome.cpp +++ b/plugins/autoSyndrome.cpp @@ -126,9 +126,8 @@ DFhackCExport command_result plugin_init(color_ostream& out, vectorgetPluginByName("autoSyndrome"); EventManager::EventHandler handle(processJob, 5); - EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, me); + EventManager::registerListener(EventManager::EventType::JOB_COMPLETED, handle, plugin_self); return CR_OK; } diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 6c4245da8..1e3403fc5 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -26,8 +26,7 @@ void syndromeHandler(color_ostream& out, void* ptr); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { EventManager::EventHandler syndrome(syndromeHandler, 1); - Plugin* me = Core::getInstance().getPluginManager()->getPluginByName("trueTransformation"); - EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, me); + EventManager::registerListener(EventManager::EventType::SYNDROME, syndrome, plugin_self); return CR_OK; } From 9ca435544ea49c6e2dbd62f4761c6bb87e34342e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Feb 2013 15:03:15 +0400 Subject: [PATCH 434/472] Nuke unsafe behavior in Buildings::findAtTile from orbit. --- library/modules/Buildings.cpp | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 13d983855..ab70944b7 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -249,31 +249,21 @@ df::building *Buildings::findAtTile(df::coord pos) if (!occ || !occ->bits.building) return NULL; - auto a = locationToBuilding.find(pos); - if ( a == locationToBuilding.end() ) { - cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all, id); - if ( index == -1 ) { - cerr << __FILE__ << ", " << __LINE__ << ": can't find building at " << pos.x << ", " << pos.y << ", " <buildings.all[index]; - if (!building->isSettingOccupancy()) - return NULL; - - if (building->room.extents && building->isExtentShaped()) + // Try cache lookup in case it works: + auto cached = locationToBuilding.find(pos); + if (cached != locationToBuilding.end()) { - auto etile = getExtentTile(building->room, pos); - if (!etile || !*etile) - return NULL; + auto building = df::building::find(cached->second); + + if (building && building->z == pos.z && + building->isSettingOccupancy() && + containsTile(building, pos, false)) + { + return building; + } } - return building; - /* - //old method: brute-force + // The authentic method, i.e. how the game generally does this: auto &vec = df::building::get_vector(); for (size_t i = 0; i < vec.size(); i++) { @@ -298,7 +288,6 @@ df::building *Buildings::findAtTile(df::coord pos) } return NULL; - */ } bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) From 27f5dc76318227f838ba40117d6ce4c57b526d83 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:07:54 -0600 Subject: [PATCH 435/472] Autolabor: add in the rest of the building construct and deconstruct labors. Also handle wood crafts at the craftdwarf's shop. --- plugins/autolabor.cpp | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 15e390b22..987d343ce 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -760,6 +760,8 @@ private: df::building* bld = get_building_from_job (j); switch (bld->getType()) { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; @@ -783,6 +785,10 @@ private: case df::building_type::RoadPaved: case df::building_type::Well: case df::building_type::ScrewPump: + case df::building_type::Wagon: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::Windmill: { df::building_actual* b = (df::building_actual*) bld; if (b->design && !b->design->flags.bits.designed) @@ -811,12 +817,27 @@ private: case df::building_type::Slab: case df::building_type::Chain: case df::building_type::GrateFloor: + case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateWall: return df::unit_labor::HAUL_FURNITURE; case df::building_type::Trap: case df::building_type::GearAssembly: case df::building_type::AxleHorizontal: case df::building_type::AxleVertical: + case df::building_type::Rollers: return df::unit_labor::MECHANIC; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; } debug ("AUTOLABOR: Cannot deduce labor for construct building job of type %s\n", @@ -837,6 +858,8 @@ private: switch (bld->getType()) { + case df::building_type::Hive: + return df::unit_labor::BEEKEEPING; case df::building_type::Workshop: { df::building_workshopst* ws = (df::building_workshopst*) bld; @@ -855,6 +878,13 @@ private: case df::building_type::Wagon: case df::building_type::Bridge: case df::building_type::ScrewPump: + case df::building_type::ArcheryTarget: + case df::building_type::RoadPaved: + case df::building_type::Shop: + case df::building_type::Support: + case df::building_type::WaterWheel: + case df::building_type::Well: + case df::building_type::Windmill: { df::building_actual* b = (df::building_actual*) bld; return construction_build_labor(b->contained_items[0]->item); @@ -863,6 +893,10 @@ private: case df::building_type::FarmPlot: return df::unit_labor::PLANT; case df::building_type::Trap: + case df::building_type::AxleHorizontal: + case df::building_type::AxleVertical: + case df::building_type::GearAssembly: + case df::building_type::Rollers: return df::unit_labor::MECHANIC; case df::building_type::Chair: case df::building_type::Bed: @@ -883,7 +917,21 @@ private: case df::building_type::Slab: case df::building_type::Chain: case df::building_type::Hatch: + case df::building_type::BarsFloor: + case df::building_type::BarsVertical: + case df::building_type::GrateFloor: + case df::building_type::GrateWall: return df::unit_labor::HAUL_FURNITURE; + case df::building_type::AnimalTrap: + return df::unit_labor::TRAPPER; + case df::building_type::Civzone: + case df::building_type::Nest: + case df::building_type::RoadDirt: + case df::building_type::Stockpile: + case df::building_type::Weapon: + return df::unit_labor::NONE; + case df::building_type::SiegeEngine: + return df::unit_labor::SIEGECRAFT; } debug ("AUTOLABOR: Cannot deduce labor for destroy building job of type %s\n", @@ -924,6 +972,8 @@ private: debug ("AUTOLABOR: Cannot deduce labor for make crafts job (not bone)\n"); return df::unit_labor::NONE; } + case df::item_type::WOOD: + return df::unit_labor::WOOD_CRAFT; default: debug ("AUTOLABOR: Cannot deduce labor for make crafts job, item type %s\n", ENUM_KEY_STR(item_type, jobitem).c_str()); From ff0012c91f0ca671a603cf4f19e3c45039b3725e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:33:32 -0600 Subject: [PATCH 436/472] Move new autolabor to autolabor2 in devel. --- plugins/devel/CMakeLists.txt | 1 + .../{autolabor.cpp => devel/autolabor2.cpp} | 27 ++++++++++--------- 2 files changed, 15 insertions(+), 13 deletions(-) rename plugins/{autolabor.cpp => devel/autolabor2.cpp} (99%) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..8eee29659 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) +DFHACK_PLUGIN(autolabor2 autolabor2.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/autolabor.cpp b/plugins/devel/autolabor2.cpp similarity index 99% rename from plugins/autolabor.cpp rename to plugins/devel/autolabor2.cpp index 987d343ce..c06e5ea73 100644 --- a/plugins/autolabor.cpp +++ b/plugins/devel/autolabor2.cpp @@ -96,8 +96,8 @@ enum ConfigFlags { command_result autolabor (color_ostream &out, std::vector & parameters); // A plugin must be able to return its name and version. -// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case -DFHACK_PLUGIN("autolabor"); +// The name string provided must correspond to the filename - autolabor2.plug.so or autolabor2.plug.dll in this case +DFHACK_PLUGIN("autolabor2"); static void generate_labor_to_skill_map(); @@ -1478,31 +1478,32 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector \n" + " autolabor2 max \n" " Set max number of dwarves assigned to a labor.\n" - " autolabor max none\n" + " autolabor2 max none\n" " Unrestrict the number of dwarves assigned to a labor.\n" - " autolabor priority \n" + " autolabor2 priority \n" " Change the assignment priority of a labor (default is 100)\n" - " autolabor reset \n" + " autolabor2 reset \n" " Return a labor to the default handling.\n" - " autolabor reset-all\n" + " autolabor2 reset-all\n" " Return all labors to the default handling.\n" - " autolabor list\n" + " autolabor2 list\n" " List current status of all labors.\n" - " autolabor status\n" + " autolabor2 status\n" " Show basic status information.\n" "Function:\n" " When enabled, autolabor periodically checks your dwarves and enables or\n" " disables labors. Generally, each dwarf will be assigned exactly one labor.\n" " Warning: autolabor will override any manual changes you make to labors\n" - " while it is enabled.\n" + " while it is enabled. Do not try to run both autolabor and autolabor2 at\n" + " the same time." )); generate_labor_to_skill_map(); From f9a3450acae6d65007cbaa1b51e49a637089b547 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:34:39 -0600 Subject: [PATCH 437/472] Reinstate old autolabor in its original place. --- plugins/autolabor.cpp | 1669 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1669 insertions(+) create mode 100644 plugins/autolabor.cpp diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp new file mode 100644 index 000000000..e5047b434 --- /dev/null +++ b/plugins/autolabor.cpp @@ -0,0 +1,1669 @@ +// This is a generic plugin that does nothing useful apart from acting as an example... of a plugin that does nothing :D + +// some headers required for a plugin. Nothing special, just the basics. +#include "Core.h" +#include +#include +#include + +#include +#include + +#include "modules/Units.h" +#include "modules/World.h" + +// DF data structure definition headers +#include "DataDefs.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "modules/MapCache.h" +#include "modules/Items.h" + +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; +using df::global::ui; +using df::global::world; + +#define ARRAY_COUNT(array) (sizeof(array)/sizeof((array)[0])) + +/* + * Autolabor module for dfhack + * + * The idea behind this module is to constantly adjust labors so that the right dwarves + * are assigned to new tasks. The key is that, for almost all labors, once a dwarf begins + * a job it will finish that job even if the associated labor is removed. Thus the + * strategy is to frequently decide, for each labor, which dwarves should possibly take + * a new job for that labor if it comes in and which shouldn't, and then set the labors + * appropriately. The updating should happen as often as can be reasonably done without + * causing lag. + * + * The obvious thing to do is to just set each labor on a single idle dwarf who is best + * suited to doing new jobs of that labor. This works in a way, but it leads to a lot + * of idle dwarves since only one dwarf will be dispatched for each labor in an update + * cycle, and dwarves that finish tasks will wait for the next update before being + * dispatched. An improvement is to also set some labors on dwarves that are currently + * doing a job, so that they will immediately take a new job when they finish. The + * details of which dwarves should have labors set is mostly a heuristic. + * + * A complication to the above simple scheme is labors that have associated equipment. + * Enabling/disabling these labors causes dwarves to change equipment, and disabling + * them in the middle of a job may cause the job to be abandoned. Those labors + * (mining, hunting, and woodcutting) need to be handled carefully to minimize churn. + */ + +static int enable_autolabor = 0; + +static bool print_debug = 0; + +static std::vector state_count(5); + +static PersistentDataItem config; + +enum ConfigFlags { + CF_ENABLED = 1, +}; + + +// Here go all the command declarations... +// mostly to allow having the mandatory stuff on top of the file and commands on the bottom +command_result autolabor (color_ostream &out, std::vector & parameters); + +// A plugin must be able to return its name and version. +// The name string provided must correspond to the filename - autolabor.plug.so or autolabor.plug.dll in this case +DFHACK_PLUGIN("autolabor"); + +static void generate_labor_to_skill_map(); + +enum labor_mode { + DISABLE, + HAULERS, + AUTOMATIC, +}; + +enum dwarf_state { + // Ready for a new task + IDLE, + + // Busy with a useful task + BUSY, + + // In the military, can't work + MILITARY, + + // Child or noble, can't work + CHILD, + + // Doing something that precludes working, may be busy for a while + OTHER +}; + +const int NUM_STATE = 5; + +static const char *state_names[] = { + "IDLE", + "BUSY", + "MILITARY", + "CHILD", + "OTHER", +}; + +static const dwarf_state dwarf_states[] = { + BUSY /* CarveFortification */, + BUSY /* DetailWall */, + BUSY /* DetailFloor */, + BUSY /* Dig */, + BUSY /* CarveUpwardStaircase */, + BUSY /* CarveDownwardStaircase */, + BUSY /* CarveUpDownStaircase */, + BUSY /* CarveRamp */, + BUSY /* DigChannel */, + BUSY /* FellTree */, + BUSY /* GatherPlants */, + BUSY /* RemoveConstruction */, + BUSY /* CollectWebs */, + BUSY /* BringItemToDepot */, + BUSY /* BringItemToShop */, + OTHER /* Eat */, + OTHER /* GetProvisions */, + OTHER /* Drink */, + OTHER /* Drink2 */, + OTHER /* FillWaterskin */, + OTHER /* FillWaterskin2 */, + OTHER /* Sleep */, + BUSY /* CollectSand */, + BUSY /* Fish */, + BUSY /* Hunt */, + OTHER /* HuntVermin */, + BUSY /* Kidnap */, + BUSY /* BeatCriminal */, + BUSY /* StartingFistFight */, + BUSY /* CollectTaxes */, + BUSY /* GuardTaxCollector */, + BUSY /* CatchLiveLandAnimal */, + BUSY /* CatchLiveFish */, + BUSY /* ReturnKill */, + BUSY /* CheckChest */, + BUSY /* StoreOwnedItem */, + BUSY /* PlaceItemInTomb */, + BUSY /* StoreItemInStockpile */, + BUSY /* StoreItemInBag */, + BUSY /* StoreItemInHospital */, + BUSY /* StoreItemInChest */, + BUSY /* StoreItemInCabinet */, + BUSY /* StoreWeapon */, + BUSY /* StoreArmor */, + BUSY /* StoreItemInBarrel */, + BUSY /* StoreItemInBin */, + BUSY /* SeekArtifact */, + BUSY /* SeekInfant */, + OTHER /* AttendParty */, + OTHER /* GoShopping */, + OTHER /* GoShopping2 */, + BUSY /* Clean */, + OTHER /* Rest */, + BUSY /* PickupEquipment */, + BUSY /* DumpItem */, + OTHER /* StrangeMoodCrafter */, + OTHER /* StrangeMoodJeweller */, + OTHER /* StrangeMoodForge */, + OTHER /* StrangeMoodMagmaForge */, + OTHER /* StrangeMoodBrooding */, + OTHER /* StrangeMoodFell */, + OTHER /* StrangeMoodCarpenter */, + OTHER /* StrangeMoodMason */, + OTHER /* StrangeMoodBowyer */, + OTHER /* StrangeMoodTanner */, + OTHER /* StrangeMoodWeaver */, + OTHER /* StrangeMoodGlassmaker */, + OTHER /* StrangeMoodMechanics */, + BUSY /* ConstructBuilding */, + BUSY /* ConstructDoor */, + BUSY /* ConstructFloodgate */, + BUSY /* ConstructBed */, + BUSY /* ConstructThrone */, + BUSY /* ConstructCoffin */, + BUSY /* ConstructTable */, + BUSY /* ConstructChest */, + BUSY /* ConstructBin */, + BUSY /* ConstructArmorStand */, + BUSY /* ConstructWeaponRack */, + BUSY /* ConstructCabinet */, + BUSY /* ConstructStatue */, + BUSY /* ConstructBlocks */, + BUSY /* MakeRawGlass */, + BUSY /* MakeCrafts */, + BUSY /* MintCoins */, + BUSY /* CutGems */, + BUSY /* CutGlass */, + BUSY /* EncrustWithGems */, + BUSY /* EncrustWithGlass */, + BUSY /* DestroyBuilding */, + BUSY /* SmeltOre */, + BUSY /* MeltMetalObject */, + BUSY /* ExtractMetalStrands */, + BUSY /* PlantSeeds */, + BUSY /* HarvestPlants */, + BUSY /* TrainHuntingAnimal */, + BUSY /* TrainWarAnimal */, + BUSY /* MakeWeapon */, + BUSY /* ForgeAnvil */, + BUSY /* ConstructCatapultParts */, + BUSY /* ConstructBallistaParts */, + BUSY /* MakeArmor */, + BUSY /* MakeHelm */, + BUSY /* MakePants */, + BUSY /* StudWith */, + BUSY /* ButcherAnimal */, + BUSY /* PrepareRawFish */, + BUSY /* MillPlants */, + BUSY /* BaitTrap */, + BUSY /* MilkCreature */, + BUSY /* MakeCheese */, + BUSY /* ProcessPlants */, + BUSY /* ProcessPlantsBag */, + BUSY /* ProcessPlantsVial */, + BUSY /* ProcessPlantsBarrel */, + BUSY /* PrepareMeal */, + BUSY /* WeaveCloth */, + BUSY /* MakeGloves */, + BUSY /* MakeShoes */, + BUSY /* MakeShield */, + BUSY /* MakeCage */, + BUSY /* MakeChain */, + BUSY /* MakeFlask */, + BUSY /* MakeGoblet */, + BUSY /* MakeInstrument */, + BUSY /* MakeToy */, + BUSY /* MakeAnimalTrap */, + BUSY /* MakeBarrel */, + BUSY /* MakeBucket */, + BUSY /* MakeWindow */, + BUSY /* MakeTotem */, + BUSY /* MakeAmmo */, + BUSY /* DecorateWith */, + BUSY /* MakeBackpack */, + BUSY /* MakeQuiver */, + BUSY /* MakeBallistaArrowHead */, + BUSY /* AssembleSiegeAmmo */, + BUSY /* LoadCatapult */, + BUSY /* LoadBallista */, + BUSY /* FireCatapult */, + BUSY /* FireBallista */, + BUSY /* ConstructMechanisms */, + BUSY /* MakeTrapComponent */, + BUSY /* LoadCageTrap */, + BUSY /* LoadStoneTrap */, + BUSY /* LoadWeaponTrap */, + BUSY /* CleanTrap */, + BUSY /* CastSpell */, + BUSY /* LinkBuildingToTrigger */, + BUSY /* PullLever */, + BUSY /* BrewDrink */, + BUSY /* ExtractFromPlants */, + BUSY /* ExtractFromRawFish */, + BUSY /* ExtractFromLandAnimal */, + BUSY /* TameVermin */, + BUSY /* TameAnimal */, + BUSY /* ChainAnimal */, + BUSY /* UnchainAnimal */, + BUSY /* UnchainPet */, + BUSY /* ReleaseLargeCreature */, + BUSY /* ReleasePet */, + BUSY /* ReleaseSmallCreature */, + BUSY /* HandleSmallCreature */, + BUSY /* HandleLargeCreature */, + BUSY /* CageLargeCreature */, + BUSY /* CageSmallCreature */, + BUSY /* RecoverWounded */, + BUSY /* DiagnosePatient */, + BUSY /* ImmobilizeBreak */, + BUSY /* DressWound */, + BUSY /* CleanPatient */, + BUSY /* Surgery */, + BUSY /* Suture */, + BUSY /* SetBone */, + BUSY /* PlaceInTraction */, + BUSY /* DrainAquarium */, + BUSY /* FillAquarium */, + BUSY /* FillPond */, + BUSY /* GiveWater */, + BUSY /* GiveFood */, + BUSY /* GiveWater2 */, + BUSY /* GiveFood2 */, + BUSY /* RecoverPet */, + BUSY /* PitLargeAnimal */, + BUSY /* PitSmallAnimal */, + BUSY /* SlaughterAnimal */, + BUSY /* MakeCharcoal */, + BUSY /* MakeAsh */, + BUSY /* MakeLye */, + BUSY /* MakePotashFromLye */, + BUSY /* FertilizeField */, + BUSY /* MakePotashFromAsh */, + BUSY /* DyeThread */, + BUSY /* DyeCloth */, + BUSY /* SewImage */, + BUSY /* MakePipeSection */, + BUSY /* OperatePump */, + OTHER /* ManageWorkOrders */, + OTHER /* UpdateStockpileRecords */, + OTHER /* TradeAtDepot */, + BUSY /* ConstructHatchCover */, + BUSY /* ConstructGrate */, + BUSY /* RemoveStairs */, + BUSY /* ConstructQuern */, + BUSY /* ConstructMillstone */, + BUSY /* ConstructSplint */, + BUSY /* ConstructCrutch */, + BUSY /* ConstructTractionBench */, + BUSY /* CleanSelf */, + BUSY /* BringCrutch */, + BUSY /* ApplyCast */, + BUSY /* CustomReaction */, + BUSY /* ConstructSlab */, + BUSY /* EngraveSlab */, + BUSY /* ShearCreature */, + BUSY /* SpinThread */, + BUSY /* PenLargeAnimal */, + BUSY /* PenSmallAnimal */, + BUSY /* MakeTool */, + BUSY /* CollectClay */, + BUSY /* InstallColonyInHive */, + BUSY /* CollectHiveProducts */, + OTHER /* CauseTrouble */, + OTHER /* DrinkBlood */, + OTHER /* ReportCrime */, + OTHER /* ExecuteCriminal */, + BUSY /* TrainAnimal */, + BUSY /* CarveTrack */, + BUSY /* PushTrackVehicle */, + BUSY /* PlaceTrackVehicle */, + BUSY /* StoreItemInVehicle */ +}; + +struct labor_info +{ + PersistentDataItem config; + + bool is_exclusive; + int active_dwarfs; + + labor_mode mode() { return (labor_mode) config.ival(0); } + void set_mode(labor_mode mode) { config.ival(0) = mode; } + + int minimum_dwarfs() { return config.ival(1); } + void set_minimum_dwarfs(int minimum_dwarfs) { config.ival(1) = minimum_dwarfs; } + + int maximum_dwarfs() { return config.ival(2); } + void set_maximum_dwarfs(int maximum_dwarfs) { config.ival(2) = maximum_dwarfs; } + +}; + +struct labor_default +{ + labor_mode mode; + bool is_exclusive; + int minimum_dwarfs; + int maximum_dwarfs; + int active_dwarfs; +}; + +static int hauler_pct = 33; + +static std::vector labor_infos; + +static const struct labor_default default_labor_infos[] = { + /* MINE */ {AUTOMATIC, true, 2, 200, 0}, + /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, + /* CLEAN */ {HAULERS, false, 1, 200, 0}, + /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, + /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, + /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, + /* MASON */ {AUTOMATIC, false, 1, 200, 0}, + /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, + /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, + /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, + /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, + /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, + /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, + /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, + /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, + /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, + /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, + /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, + /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, + /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, + /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, + /* MILK */ {AUTOMATIC, false, 1, 200, 0}, + /* COOK */ {AUTOMATIC, false, 1, 200, 0}, + /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, + /* FISH */ {AUTOMATIC, false, 1, 1, 0}, + /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, + /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, + /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, + /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, + /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, + /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, + /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* DYER */ {AUTOMATIC, false, 1, 200, 0}, + /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, + /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, + /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, + /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, + /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, + /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, + /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, + /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) + /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, + /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} +}; + +static const int responsibility_penalties[] = { + 0, /* LAW_MAKING */ + 0, /* LAW_ENFORCEMENT */ + 3000, /* RECEIVE_DIPLOMATS */ + 0, /* MEET_WORKERS */ + 1000, /* MANAGE_PRODUCTION */ + 3000, /* TRADE */ + 1000, /* ACCOUNTING */ + 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ + 0, /* MAKE_INTRODUCTIONS */ + 0, /* MAKE_PEACE_AGREEMENTS */ + 0, /* MAKE_TOPIC_AGREEMENTS */ + 0, /* COLLECT_TAXES */ + 0, /* ESCORT_TAX_COLLECTOR */ + 0, /* EXECUTIONS */ + 0, /* TAME_EXOTICS */ + 0, /* RELIGION */ + 0, /* ATTACK_ENEMIES */ + 0, /* PATROL_TERRITORY */ + 0, /* MILITARY_GOALS */ + 0, /* MILITARY_STRATEGY */ + 0, /* UPGRADE_SQUAD_EQUIPMENT */ + 0, /* EQUIPMENT_MANIFESTS */ + 0, /* SORT_AMMUNITION */ + 0, /* BUILD_MORALE */ + 5000 /* HEALTH_MANAGEMENT */ +}; + +struct dwarf_info_t +{ + int highest_skill; + int total_skill; + int mastery_penalty; + int assigned_jobs; + dwarf_state state; + bool has_exclusive_labor; + int noble_penalty; // penalty for assignment due to noble status + bool medical; // this dwarf has medical responsibility + bool trader; // this dwarf has trade responsibility + bool diplomacy; // this dwarf meets with diplomats + int single_labor; // this dwarf will be exclusively assigned to one labor (-1/NONE for none) +}; + +static bool isOptionEnabled(unsigned flag) +{ + return config.isValid() && (config.ival(0) & flag) != 0; +} + +static void setOptionEnabled(ConfigFlags flag, bool on) +{ + if (!config.isValid()) + return; + + if (on) + config.ival(0) |= flag; + else + config.ival(0) &= ~flag; +} + +static void cleanup_state() +{ + labor_infos.clear(); +} + +static void reset_labor(df::unit_labor labor) +{ + labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); + labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); + labor_infos[labor].set_mode(default_labor_infos[labor].mode); +} + +static void init_state() +{ + config = World::GetPersistentData("autolabor/config"); + if (config.isValid() && config.ival(0) == -1) + config.ival(0) = 0; + + enable_autolabor = isOptionEnabled(CF_ENABLED); + + if (!enable_autolabor) + return; + + auto cfg_haulpct = World::GetPersistentData("autolabor/haulpct"); + if (cfg_haulpct.isValid()) + { + hauler_pct = cfg_haulpct.ival(0); + } + else + { + hauler_pct = 33; + } + + // Load labors from save + labor_infos.resize(ARRAY_COUNT(default_labor_infos)); + + std::vector items; + World::GetPersistentData(&items, "autolabor/labors/", true); + + for (auto p = items.begin(); p != items.end(); p++) + { + string key = p->key(); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); + if (labor >= 0 && labor <= labor_infos.size()) + { + labor_infos[labor].config = *p; + labor_infos[labor].is_exclusive = default_labor_infos[labor].is_exclusive; + labor_infos[labor].active_dwarfs = 0; + } + } + + // Add default labors for those not in save + for (int i = 0; i < ARRAY_COUNT(default_labor_infos); i++) { + if (labor_infos[i].config.isValid()) + continue; + + std::stringstream name; + name << "autolabor/labors/" << i; + + 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; + reset_labor((df::unit_labor) i); + } + + generate_labor_to_skill_map(); + +} + +static df::job_skill labor_to_skill[ENUM_LAST_ITEM(unit_labor) + 1]; + +static void generate_labor_to_skill_map() +{ + // Generate labor -> skill mapping + + for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) + labor_to_skill[i] = job_skill::NONE; + + FOR_ENUM_ITEMS(job_skill, skill) + { + int labor = ENUM_ATTR(job_skill, labor, skill); + if (labor != unit_labor::NONE) + { + /* + assert(labor >= 0); + assert(labor < ARRAY_COUNT(labor_to_skill)); + */ + + labor_to_skill[labor] = skill; + } + } + +} + + +static void enable_plugin(color_ostream &out) +{ + if (!config.isValid()) + { + config = World::AddPersistentData("autolabor/config"); + config.ival(0) = 0; + } + + setOptionEnabled(CF_ENABLED, true); + enable_autolabor = true; + out << "Enabling the plugin." << endl; + + cleanup_state(); + init_state(); +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + // initialize labor infos table from default table + if(ARRAY_COUNT(default_labor_infos) != ENUM_LAST_ITEM(unit_labor) + 1) + return CR_FAILURE; + + // Fill the command list with your commands. + commands.push_back(PluginCommand( + "autolabor", "Automatically manage dwarf labors.", + autolabor, false, /* true means that the command can't be used from non-interactive user interface */ + // Extended help string. Used by CR_WRONG_USAGE and the help command: + " autolabor enable\n" + " autolabor disable\n" + " Enables or disables the plugin.\n" + " autolabor []\n" + " Set number of dwarves assigned to a labor.\n" + " autolabor haulers\n" + " Set a labor to be handled by hauler dwarves.\n" + " autolabor disable\n" + " Turn off autolabor for a specific labor.\n" + " autolabor reset\n" + " Return a labor to the default handling.\n" + " autolabor reset-all\n" + " Return all labors to the default handling.\n" + " autolabor list\n" + " List current status of all labors.\n" + " autolabor status\n" + " Show basic status information.\n" + "Function:\n" + " When enabled, autolabor periodically checks your dwarves and enables or\n" + " disables labors. It tries to keep as many dwarves as possible busy but\n" + " also tries to have dwarves specialize in specific skills.\n" + " Warning: autolabor will override any manual changes you make to labors\n" + " while it is enabled.\n" + "Examples:\n" + " autolabor MINE 2\n" + " Keep at least 2 dwarves with mining enabled.\n" + " autolabor CUT_GEM 1 1\n" + " Keep exactly 1 dwarf with gemcutting enabled.\n" + " autolabor FEED_WATER_CIVILIANS haulers\n" + " Have haulers feed and water wounded dwarves.\n" + " autolabor CUTWOOD disable\n" + " Turn off autolabor for wood cutting.\n" + )); + + init_state(); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + cleanup_state(); + + return CR_OK; +} + +// sorting objects +struct dwarfinfo_sorter +{ + dwarfinfo_sorter(std::vector & info):dwarf_info(info){}; + bool operator() (int i,int j) + { + if (dwarf_info[i].state == IDLE && dwarf_info[j].state != IDLE) + return true; + if (dwarf_info[i].state != IDLE && dwarf_info[j].state == IDLE) + return false; + return dwarf_info[i].mastery_penalty > dwarf_info[j].mastery_penalty; + }; + std::vector & dwarf_info; +}; +struct laborinfo_sorter +{ + bool operator() (int i,int j) + { + return labor_infos[i].mode() < labor_infos[j].mode(); + }; +}; + +struct values_sorter +{ + values_sorter(std::vector & values):values(values){}; + bool operator() (int i,int j) + { + return values[i] > values[j]; + }; + std::vector & values; +}; + + +static void assign_labor(unit_labor::unit_labor labor, + int n_dwarfs, + std::vector& dwarf_info, + bool trader_requested, + std::vector& dwarfs, + bool has_butchers, + bool has_fishery, + color_ostream& out) +{ + df::job_skill skill = labor_to_skill[labor]; + + if (labor_infos[labor].mode() != AUTOMATIC) + return; + + int best_dwarf = 0; + int best_value = -10000; + + std::vector values(n_dwarfs); + std::vector candidates; + std::map dwarf_skill; + std::vector previously_enabled(n_dwarfs); + + auto mode = labor_infos[labor].mode(); + + // Find candidate dwarfs, and calculate a preference value for each dwarf + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state == CHILD) + continue; + if (dwarf_info[dwarf].state == MILITARY) + continue; + if (dwarf_info[dwarf].trader && trader_requested) + continue; + if (dwarf_info[dwarf].diplomacy) + continue; + + if (labor_infos[labor].is_exclusive && dwarf_info[dwarf].has_exclusive_labor) + continue; + + int value = dwarf_info[dwarf].mastery_penalty; + + if (skill != job_skill::NONE) + { + int skill_level = 0; + int skill_experience = 0; + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s < dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + if ((*s)->id == skill) + { + skill_level = (*s)->rating; + skill_experience = (*s)->experience; + break; + } + } + + dwarf_skill[dwarf] = skill_level; + + value += skill_level * 100; + value += skill_experience / 20; + if (skill_level > 0 || skill_experience > 0) + value += 200; + if (skill_level >= 15) + value += 1000 * (skill_level - 14); + } + else + { + dwarf_skill[dwarf] = 0; + } + + if (dwarfs[dwarf]->status.labors[labor]) + { + value += 5; + if (labor_infos[labor].is_exclusive) + value += 350; + } + + // bias by happiness + + value += dwarfs[dwarf]->status.happiness; + + values[dwarf] = value; + + candidates.push_back(dwarf); + + } + + // Sort candidates by preference value + values_sorter ivs(values); + std::sort(candidates.begin(), candidates.end(), ivs); + + // Disable the labor on everyone + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if (dwarf_info[dwarf].state == CHILD) + continue; + + previously_enabled[dwarf] = dwarfs[dwarf]->status.labors[labor]; + dwarfs[dwarf]->status.labors[labor] = false; + } + + int min_dwarfs = labor_infos[labor].minimum_dwarfs(); + int max_dwarfs = labor_infos[labor].maximum_dwarfs(); + + // Special - don't assign hunt without a butchers, or fish without a fishery + if (unit_labor::HUNT == labor && !has_butchers) + min_dwarfs = max_dwarfs = 0; + if (unit_labor::FISH == labor && !has_fishery) + min_dwarfs = max_dwarfs = 0; + + bool want_idle_dwarf = true; + if (state_count[IDLE] < 2) + want_idle_dwarf = false; + + /* + * Assign dwarfs to this labor. We assign at least the minimum number of dwarfs, in + * order of preference, and then assign additional dwarfs that meet any of these conditions: + * - The dwarf is idle and there are no idle dwarves assigned to this labor + * - The dwarf has nonzero skill associated with the labor + * - The labor is mining, hunting, or woodcutting and the dwarf currently has it enabled. + * We stop assigning dwarfs when we reach the maximum allowed. + * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs + * (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted. + * Military and children/nobles will not have labors assigned. + * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. + */ + for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) + { + int dwarf = candidates[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + bool preferred_dwarf = false; + if (want_idle_dwarf && dwarf_info[dwarf].state == IDLE) + preferred_dwarf = true; + if (dwarf_skill[dwarf] > 0) + preferred_dwarf = true; + if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) + preferred_dwarf = true; + if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) + preferred_dwarf = true; + if (dwarf_info[dwarf].trader && trader_requested) + continue; + if (dwarf_info[dwarf].diplomacy) + continue; + + if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) + continue; + + if (!dwarfs[dwarf]->status.labors[labor]) + dwarf_info[dwarf].assigned_jobs++; + + dwarfs[dwarf]->status.labors[labor] = true; + + if (labor_infos[labor].is_exclusive) + { + dwarf_info[dwarf].has_exclusive_labor = true; + // all the exclusive labors require equipment so this should force the dorf to reequip if needed + dwarfs[dwarf]->military.pickup_flags.bits.update = 1; + } + + if (print_debug) + out.print("Dwarf %i \"%s\" assigned %s: value %i %s %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str(), values[dwarf], dwarf_info[dwarf].trader ? "(trader)" : "", dwarf_info[dwarf].diplomacy ? "(diplomacy)" : ""); + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + labor_infos[labor].active_dwarfs++; + + if (dwarf_info[dwarf].state == IDLE) + want_idle_dwarf = false; + } +} + + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + cleanup_state(); + init_state(); + break; + case SC_MAP_UNLOADED: + cleanup_state(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + static int step_count = 0; + // check run conditions + if(!world || !world->map.block_index || !enable_autolabor) + { + // give up if we shouldn't be running' + return CR_OK; + } + + if (++step_count < 60) + return CR_OK; + step_count = 0; + + uint32_t race = ui->race_id; + uint32_t civ = ui->civ_id; + + std::vector dwarfs; + + bool has_butchers = false; + bool has_fishery = false; + bool trader_requested = false; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = trader_requested || depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + + for (int i = 0; i < world->units.active.size(); ++i) + { + df::unit* cre = world->units.active[i]; + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + dwarfs.push_back(cre); + } + } + + int n_dwarfs = dwarfs.size(); + + if (n_dwarfs == 0) + return CR_OK; + + std::vector dwarf_info(n_dwarfs); + + // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + dwarf_info[dwarf].single_labor = -1; + + if (dwarfs[dwarf]->status.souls.size() <= 0) + continue; + + // compute noble penalty + + int noble_penalty = 0; + + df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + for (int n = 0; n < 25; n++) + if (position->responsibilities[n]) + noble_penalty += responsibility_penalties[n]; + + if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT]) + dwarf_info[dwarf].medical = true; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + dwarf_info[dwarf].trader = true; + + } + + } + + dwarf_info[dwarf].noble_penalty = noble_penalty; + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarfs[dwarf]; + bool p2 = act->person2 == dwarfs[dwarf]; + + if (p1 || p2) + { + dwarf_info[dwarf].diplomacy = true; + if (print_debug) + out.print("Dwarf %i \"%s\" has a meeting, will be cleared of all labors\n", dwarf, dwarfs[dwarf]->name.first_name.c_str()); + break; + } + } + + for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) + { + df::job_skill skill = (*s)->id; + + df::job_skill_class skill_class = ENUM_ATTR(job_skill, type, skill); + + int skill_level = (*s)->rating; + int skill_experience = (*s)->experience; + + // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) + + if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical) + continue; + + if (dwarf_info[dwarf].highest_skill < skill_level) + dwarf_info[dwarf].highest_skill = skill_level; + dwarf_info[dwarf].total_skill += skill_level; + } + } + + // Calculate a base penalty for using each dwarf for a task he isn't good at. + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; + dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; + dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) + dwarf_info[dwarf].mastery_penalty -= 100; + } + } + + // Find the activity state for each dwarf. It's important to get this right - a dwarf who we think is IDLE but + // can't work will gum everything up. In the future I might add code to auto-detect slacker dwarves. + + state_count.clear(); + state_count.resize(NUM_STATE); + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + bool is_on_break = false; + + for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarfs[dwarf]->profession == profession::BABY || + dwarfs[dwarf]->profession == profession::CHILD || + dwarfs[dwarf]->profession == profession::DRUNK) + { + dwarf_info[dwarf].state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarfs[dwarf]->profession)) + dwarf_info[dwarf].state = MILITARY; + else if (dwarfs[dwarf]->job.current_job == NULL) + { + if (is_on_break) + dwarf_info[dwarf].state = OTHER; + else if (dwarfs[dwarf]->specific_refs.size() > 0) + dwarf_info[dwarf].state = OTHER; + else + dwarf_info[dwarf].state = IDLE; + } + else + { + int job = dwarfs[dwarf]->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + dwarf_info[dwarf].state = dwarf_states[job]; + else + { + out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); + dwarf_info[dwarf].state = OTHER; + } + } + + state_count[dwarf_info[dwarf].state]++; + + if (print_debug) + out.print("Dwarf %i \"%s\": penalty %i, state %s\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), dwarf_info[dwarf].mastery_penalty, state_names[dwarf_info[dwarf].state]); + } + + std::vector labors; + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + labor_infos[labor].active_dwarfs = 0; + + labors.push_back(labor); + } + laborinfo_sorter lasorter; + std::sort(labors.begin(), labors.end(), lasorter); + + // Handle DISABLED skills (just bookkeeping) + for (auto lp = labors.begin(); lp != labors.end(); ++lp) + { + auto labor = *lp; + + if (labor_infos[labor].mode() != DISABLE) + continue; + + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if ((dwarf_info[dwarf].trader && trader_requested) || + dwarf_info[dwarf].diplomacy) + { + dwarfs[dwarf]->status.labors[labor] = false; + } + + if (dwarfs[dwarf]->status.labors[labor]) + { + if (labor_infos[labor].is_exclusive) + dwarf_info[dwarf].has_exclusive_labor = true; + + dwarf_info[dwarf].assigned_jobs++; + } + } + } + + // Handle all skills except those marked HAULERS + + for (auto lp = labors.begin(); lp != labors.end(); ++lp) + { + auto labor = *lp; + + assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); + } + + // Set about 1/3 of the dwarfs as haulers. The haulers have all HAULER labors enabled. Having a lot of haulers helps + // make sure that hauling jobs are handled quickly rather than building up. + + int num_haulers = state_count[IDLE] + state_count[BUSY] * hauler_pct / 100; + + if (num_haulers < 1) + num_haulers = 1; + + std::vector hauler_ids; + for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) + { + if ((dwarf_info[dwarf].trader && trader_requested) || + dwarf_info[dwarf].diplomacy) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + if (labor_infos[labor].mode() != HAULERS) + continue; + dwarfs[dwarf]->status.labors[labor] = false; + } + continue; + } + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + hauler_ids.push_back(dwarf); + } + dwarfinfo_sorter sorter(dwarf_info); + // Idle dwarves come first, then we sort from least-skilled to most-skilled. + std::sort(hauler_ids.begin(), hauler_ids.end(), sorter); + + // don't set any haulers if everyone is off drinking or something + if (hauler_ids.size() == 0) { + num_haulers = 0; + } + + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + if (labor_infos[labor].mode() != HAULERS) + continue; + + for (int i = 0; i < num_haulers; i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + dwarfs[dwarf]->status.labors[labor] = true; + dwarf_info[dwarf].assigned_jobs++; + + if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) + labor_infos[labor].active_dwarfs++; + + if (print_debug) + out.print("Dwarf %i \"%s\" assigned %s: hauler\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), ENUM_KEY_STR(unit_labor, labor).c_str()); + } + + for (int i = num_haulers; i < hauler_ids.size(); i++) + { + assert(i < hauler_ids.size()); + + int dwarf = hauler_ids[i]; + + assert(dwarf >= 0); + assert(dwarf < n_dwarfs); + + dwarfs[dwarf]->status.labors[labor] = false; + } + } + + print_debug = 0; + + return CR_OK; +} + +void print_labor (df::unit_labor labor, color_ostream &out) +{ + string labor_name = ENUM_KEY_STR(unit_labor, labor); + out << labor_name << ": "; + for (int i = 0; i < 20 - (int)labor_name.length(); i++) + out << ' '; + if (labor_infos[labor].mode() == DISABLE) + out << "disabled" << endl; + else + { + if (labor_infos[labor].mode() == HAULERS) + out << "haulers"; + else + out << "minimum " << labor_infos[labor].minimum_dwarfs() << ", maximum " << labor_infos[labor].maximum_dwarfs(); + out << ", currently " << labor_infos[labor].active_dwarfs << " dwarfs" << endl; + } +} + + +command_result autolabor (color_ostream &out, std::vector & parameters) +{ + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()) { + out.printerr("World is not loaded: please load a game first.\n"); + return CR_FAILURE; + } + + if (parameters.size() == 1 && + (parameters[0] == "0" || parameters[0] == "enable" || + parameters[0] == "1" || parameters[0] == "disable")) + { + bool enable = (parameters[0] == "1" || parameters[0] == "enable"); + if (enable && !enable_autolabor) + { + enable_plugin(out); + } + else if(!enable && enable_autolabor) + { + enable_autolabor = false; + setOptionEnabled(CF_ENABLED, false); + + out << "The plugin is disabled." << endl; + } + + return CR_OK; + } + else if (parameters.size() == 2 && parameters[0] == "haulpct") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + int pct = atoi (parameters[1].c_str()); + hauler_pct = pct; + return CR_OK; + } + else if (parameters.size() == 2 || parameters.size() == 3) + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + df::unit_labor labor = unit_labor::NONE; + + FOR_ENUM_ITEMS(unit_labor, test_labor) + { + if (parameters[0] == ENUM_KEY_STR(unit_labor, test_labor)) + labor = test_labor; + } + + if (labor == unit_labor::NONE) + { + out.printerr("Could not find labor %s.\n", parameters[0].c_str()); + return CR_WRONG_USAGE; + } + + if (parameters[1] == "haulers") + { + labor_infos[labor].set_mode(HAULERS); + print_labor(labor, out); + return CR_OK; + } + if (parameters[1] == "disable") + { + labor_infos[labor].set_mode(DISABLE); + print_labor(labor, out); + return CR_OK; + } + if (parameters[1] == "reset") + { + reset_labor(labor); + print_labor(labor, out); + return CR_OK; + } + + int minimum = atoi (parameters[1].c_str()); + int maximum = 200; + if (parameters.size() == 3) + maximum = atoi (parameters[2].c_str()); + + if (maximum < minimum || maximum < 0 || minimum < 0) + { + out.printerr("Syntax: autolabor []\n", maximum, minimum); + return CR_WRONG_USAGE; + } + + labor_infos[labor].set_minimum_dwarfs(minimum); + labor_infos[labor].set_maximum_dwarfs(maximum); + labor_infos[labor].set_mode(AUTOMATIC); + print_labor(labor, out); + + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "reset-all") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + for (int i = 0; i < labor_infos.size(); i++) + { + reset_labor((df::unit_labor) i); + } + out << "All labors reset." << endl; + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + bool need_comma = 0; + for (int i = 0; i < NUM_STATE; i++) + { + if (state_count[i] == 0) + continue; + if (need_comma) + out << ", "; + out << state_count[i] << ' ' << state_names[i]; + need_comma = 1; + } + out << endl; + + if (parameters[0] == "list") + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + print_labor(labor, out); + } + } + + return CR_OK; + } + else if (parameters.size() == 1 && parameters[0] == "debug") + { + if (!enable_autolabor) + { + out << "Error: The plugin is not enabled." << endl; + return CR_FAILURE; + } + + print_debug = 1; + + return CR_OK; + } + else + { + out.print("Automatically assigns labors to dwarves.\n" + "Activate with 'autolabor 1', deactivate with 'autolabor 0'.\n" + "Current state: %d.\n", enable_autolabor); + + return CR_OK; + } +} + +struct StockpileInfo { + df::building_stockpilest* sp; + int size; + int free; + int x1, x2, y1, y2, z; + +public: + StockpileInfo(df::building_stockpilest *sp_) : sp(sp_) + { + MapExtras::MapCache mc; + + z = sp_->z; + x1 = sp_->room.x; + x2 = sp_->room.x + sp_->room.width; + y1 = sp_->room.y; + y2 = sp_->room.y + sp_->room.height; + int e = 0; + size = 0; + free = 0; + for (int y = y1; y < y2; y++) + for (int x = x1; x < x2; x++) + if (sp_->room.extents[e++] == 1) + { + size++; + DFCoord cursor (x,y,z); + uint32_t blockX = x / 16; + uint32_t tileX = x % 16; + uint32_t blockY = y / 16; + uint32_t tileY = y % 16; + MapExtras::Block * b = mc.BlockAt(cursor/16); + if(b && b->is_valid()) + { + auto &block = *b->getRaw(); + df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + if (!occ.bits.item) + free++; + } + } + } + + bool isFull() { return free == 0; } + + bool canHold(df::item *i) + { + return false; + } + + bool inStockpile(df::item *i) + { + df::item *container = Items::getContainer(i); + if (container) + return inStockpile(container); + + if (i->pos.z != z) return false; + if (i->pos.x < x1 || i->pos.x >= x2 || + i->pos.y < y1 || i->pos.y >= y2) return false; + int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; + return sp->room.extents[e] == 1; + } + + int getId() { return sp->id; } +}; + +static int stockcheck(color_ostream &out, vector & parameters) +{ + int count = 0; + + std::vector stockpiles; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (building_type::Stockpile == type) + { + df::building_stockpilest *sp = virtual_cast(build); + StockpileInfo *spi = new StockpileInfo(sp); + stockpiles.push_back(spi); + } + + } + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); + F(spider_web); F(owned); F(in_job); +#undef F + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG + + df::item_type typ = item->getType(); + if (typ != item_type::MEAT && + typ != item_type::FISH && + typ != item_type::FISH_RAW && + typ != item_type::PLANT && + typ != item_type::CHEESE && + typ != item_type::FOOD && + typ != item_type::EGG) + continue; + + df::item *container = 0; + df::unit *holder = 0; + df::building *building = 0; + + for (size_t i = 0; i < item->general_refs.size(); i++) + { + df::general_ref *ref = item->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + container = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + + df::item *nextcontainer = container; + df::item *lastcontainer = 0; + + while(nextcontainer) { + df::item *thiscontainer = nextcontainer; + nextcontainer = 0; + for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) + { + df::general_ref *ref = thiscontainer->general_refs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + lastcontainer = nextcontainer = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + } + + if (holder) + continue; // carried items do not rot as far as i know + + if (building) { + df::building_type btype = building->getType(); + if (btype == building_type::TradeDepot || + btype == building_type::Wagon) + continue; // items in trade depot or the embark wagon do not rot + + if (typ == item_type::EGG && btype ==building_type::NestBox) + continue; // eggs in nest box do not rot + } + + int canHoldCount = 0; + StockpileInfo *current = 0; + + for (int idx = 0; idx < stockpiles.size(); idx++) + { + StockpileInfo *spi = stockpiles[idx]; + if (spi->canHold(item)) canHoldCount++; + if (spi->inStockpile(item)) current=spi; + } + + if (current) + continue; + + count++; + + } + + return count; +} From 148a37b2e4aebe1ec8225d54765aecc495fee81b Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 13:55:28 -0600 Subject: [PATCH 438/472] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 506ab1e68..76fb647a4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 506ab1e68d1522e2f282f134176b7da774f6a73c +Subproject commit 76fb647a42ba2064d11f2a0be7bf04f6e3622bc5 From e35a1c772062241138b9814823b1be65123a0769 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Wed, 13 Feb 2013 16:00:09 -0600 Subject: [PATCH 439/472] Correct autolabor2 for changes in structures. --- plugins/devel/autolabor2.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/autolabor2.cpp b/plugins/devel/autolabor2.cpp index c06e5ea73..0d5196a77 100644 --- a/plugins/devel/autolabor2.cpp +++ b/plugins/devel/autolabor2.cpp @@ -1858,7 +1858,7 @@ private: state = OTHER; else if (dwarf->dwarf->burrows.size() > 0) state = OTHER; // dwarfs assigned to burrows are treated as if permanently busy - else if (dwarf->dwarf->status2.able_grasp_impair == 0) + else if (dwarf->dwarf->status2.limbs_grasp_count == 0) { state = OTHER; // dwarfs unable to grasp are incapable of nearly all labors dwarf->clear_all = true; From a8f5e54e37dab4495d6888fb7245b8b55173ad88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 14 Feb 2013 09:53:14 +0100 Subject: [PATCH 440/472] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 76fb647a4..c7e2c28fe 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 76fb647a42ba2064d11f2a0be7bf04f6e3622bc5 +Subproject commit c7e2c28febd6dca06ff7e9951090982fbbee12b5 diff --git a/plugins/stonesense b/plugins/stonesense index cb97cf308..37f6e626b 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd +Subproject commit 37f6e626b054571b72535e2ac0ee3957e07432f1 From 177e45bdd83fe923ffc85205e19f071b6bc8f464 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 12:49:57 +0400 Subject: [PATCH 441/472] Improve fix/cloth-stockpile performance by 30% by reducing garbage. --- scripts/fix/cloth-stockpile.lua | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/scripts/fix/cloth-stockpile.lua b/scripts/fix/cloth-stockpile.lua index 7da5d583c..45e5fcc43 100644 --- a/scripts/fix/cloth-stockpile.lua +++ b/scripts/fix/cloth-stockpile.lua @@ -1,8 +1,16 @@ -- Fixes cloth/thread stockpiles by correcting material object data. local raws = df.global.world.raws -local organic_types = raws.mat_table.organic_types -local organic_indexes = raws.mat_table.organic_indexes + +-- Cache references to vectors in lua tables for a speed-up +local organic_types = {} +for i,v in ipairs(raws.mat_table.organic_types) do + organic_types[i] = v +end +local organic_indexes = {} +for i,v in ipairs(raws.mat_table.organic_indexes) do + organic_indexes[i] = v +end local function verify(category,idx,vtype,vidx) if idx == -1 then From 7cbd201f318cd3067f0f00ac77a5bbe18dcebfa7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 13:07:27 +0400 Subject: [PATCH 442/472] Nuke the third exit(1) and change building caching code to make more sense. --- library/modules/Buildings.cpp | 39 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index ab70944b7..f2312b05d 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -1114,18 +1114,21 @@ void Buildings::clearBuildings(color_ostream& out) { locationToBuilding.clear(); } -void Buildings::updateBuildings(color_ostream& out, void* ptr) { - //out.print("Updating buildings, %s %d\n", __FILE__, __LINE__); +void Buildings::updateBuildings(color_ostream& out, void* ptr) +{ int32_t id = (int32_t)ptr; - - if ( corner1.find(id) == corner1.end() ) { - //new building: mark stuff - int32_t index = df::building::binsearch_index(df::global::world->buildings.all, id); - if ( index == -1 ) { - out.print("%s, line %d: Couldn't find new building id=%d.\n", __FILE__, __LINE__, id); - exit(1); - } - df::building* building = df::global::world->buildings.all[index]; + auto building = df::building::find(id); + + if (building) + { + // Already cached -> weird, so bail out + if (corner1.count(id)) + return; + // Civzones cannot be cached because they can + // overlap each other and normal buildings. + if (!building->isSettingOccupancy()) + return; + df::coord p1(min(building->x1, building->x2), min(building->y1,building->y2), building->z); df::coord p2(max(building->x1, building->x2), max(building->y1,building->y2), building->z); @@ -1135,18 +1138,24 @@ void Buildings::updateBuildings(color_ostream& out, void* ptr) { for ( int32_t x = p1.x; x <= p2.x; x++ ) { for ( int32_t y = p1.y; y <= p2.y; y++ ) { df::coord pt(x,y,building->z); - locationToBuilding[pt] = id; + if (containsTile(building, pt, false)) + locationToBuilding[pt] = id; } } - } else { + } + else if (corner1.count(id)) + { //existing building: destroy it df::coord p1 = corner1[id]; df::coord p2 = corner2[id]; - + for ( int32_t x = p1.x; x <= p2.x; x++ ) { for ( int32_t y = p1.y; y <= p2.y; y++ ) { df::coord pt(x,y,p1.z); - locationToBuilding.erase(pt); + + auto cur = locationToBuilding.find(pt); + if (cur != locationToBuilding.end() && cur->second == id) + locationToBuilding.erase(cur); } } From 8de172f1c868f1ead74330954dc8a3f94ca0e3e1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 14 Feb 2013 13:12:23 +0400 Subject: [PATCH 443/472] Binsearch in units.active can't possibly work, ever. --- plugins/trueTransformation.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/trueTransformation.cpp b/plugins/trueTransformation.cpp index 1e3403fc5..4527871d9 100644 --- a/plugins/trueTransformation.cpp +++ b/plugins/trueTransformation.cpp @@ -35,12 +35,12 @@ void syndromeHandler(color_ostream& out, void* ptr) { EventManager::SyndromeData* data = (EventManager::SyndromeData*)ptr; //out.print("Syndrome started: unit %d, syndrome %d.\n", data->unitId, data->syndromeIndex); - int32_t index = df::unit::binsearch_index(df::global::world->units.active, data->unitId); - if ( index < 0 ) { + df::unit* unit = df::unit::find(data->unitId); + if (!unit) { out.print("%s, line %d: couldn't find unit.\n", __FILE__, __LINE__); return; } - df::unit* unit = df::global::world->units.active[index]; + df::unit_syndrome* unit_syndrome = unit->syndromes.active[data->syndromeIndex]; df::syndrome* syndrome = df::global::world->raws.syndromes.all[unit_syndrome->type]; From 84f2ee75614fdad0486ae662b597cd4c760554db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Thu, 14 Feb 2013 12:17:23 +0100 Subject: [PATCH 444/472] Update readme html --- Readme.html | 191 ++++++++++++++++++++++++++++++++++------------------ 1 file changed, 125 insertions(+), 66 deletions(-) diff --git a/Readme.html b/Readme.html index 54deb013f..d9a3d0602 100644 --- a/Readme.html +++ b/Readme.html @@ -3,13 +3,13 @@ - + DFHack Readme

                                                                                on_submit:Enter key callback; if specified, the list reacts to the key and calls it as on_submit(index,choice).
                                                                                on_submit2:Shift-Enter key callback; if specified, the list reacts to the key +and calls it as on_submit2(index,choice).
                                                                                row_height:Height of every row in text lines.
                                                                                icon_width:If not nil, the specified number of character columns @@ -2908,6 +2911,9 @@ with the following fields:

                                                                              • list:submit()

                                                                                Call the on_submit callback, as if the Enter key was handled.

                                                                              • +
                                                                              • list:submit2()

                                                                                +

                                                                                Call the on_submit2 callback, as if the Shift-Enter key was handled.

                                                                                +
                                                                              • diff --git a/Lua API.rst b/Lua API.rst index 4087ff0aa..d42a348e4 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2777,6 +2777,8 @@ It has the following attributes: :on_select: Selection change callback; called as ``on_select(index,choice)``. :on_submit: Enter key callback; if specified, the list reacts to the key and calls it as ``on_submit(index,choice)``. +:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key + and calls it as ``on_submit2(index,choice)``. :row_height: Height of every row in text lines. :icon_width: If not *nil*, the specified number of character columns are reserved to the left of the list item for the icons. @@ -2826,6 +2828,10 @@ The list supports the following methods: Call the ``on_submit`` callback, as if the Enter key was handled. +* ``list:submit2()`` + + Call the ``on_submit2`` callback, as if the Shift-Enter key was handled. + FilteredList class ------------------ diff --git a/Readme.html b/Readme.html index cdc4dd631..deea72bef 100644 --- a/Readme.html +++ b/Readme.html @@ -3045,11 +3045,11 @@ the job material first using job as described in workflow documentation above. In this manner, this feature can be used for troubleshooting jobs that don't match the right constraints.

                                                                                images/workflow-new1.png -

                                                                                After selecting one of the presented outputs, the interface proceeds to the +

                                                                                If you select one of the outputs with Enter, the matching constraint is simply +added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range.

                                                                                images/workflow-new2.png -

                                                                                If you don't need advanced settings, you can just press 'y' to confirm creation.

                                                                                gui/assign-rack

                                                                                diff --git a/Readme.rst b/Readme.rst index b9844debd..a214a6ecb 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2309,15 +2309,13 @@ can be used for troubleshooting jobs that don't match the right constraints. .. image:: images/workflow-new1.png -After selecting one of the presented outputs, the interface proceeds to the +If you select one of the outputs with Enter, the matching constraint is simply +added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range. .. image:: images/workflow-new2.png -If you don't need advanced settings, you can just press 'y' to confirm creation. - - gui/assign-rack =============== diff --git a/images/workflow-new1.png b/images/workflow-new1.png index 25b498bca23326f9e5ea05471dfcdb275f4e4734..50d0e1f421ac9de1abf87545c8d7ade31bd4cd4a 100644 GIT binary patch literal 6674 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 Q!oMj@&RZH+8M@v5A9!#IKL7v# literal 5662 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 X3+v6wDh=8C=tWR0Y|S5=xySw=1!(tQ diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 0a79b4c3e..fb9b8fd63 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -152,7 +152,9 @@ ListBox.ATTRS{ with_filter = false, cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, - on_select = DEFAULT_NIL + on_select = DEFAULT_NIL, + on_select2 = DEFAULT_NIL, + select2_hint = DEFAULT_NIL, } function ListBox:preinit(info) @@ -168,6 +170,16 @@ function ListBox:init(info) list_widget = widgets.FilteredList end + local on_submit2 + if self.select2_hint or self.on_select2 then + on_submit2 = function(sel, obj) + self:dismiss() + if self.on_select2 then self.on_select2(sel, obj) end + local cb = obj.on_select2 + if cb then cb(obj, sel) end + end + end + self:addviews{ list_widget{ view_id = 'list', @@ -182,11 +194,19 @@ function ListBox:init(info) local cb = obj.on_select or obj[2] if cb then cb(obj, sel) end end, + on_submit2 = on_submit2, frame = { l = 0, r = 0 }, } } end +function ListBox:onRenderFrame(dc,rect) + ListBox.super.onRenderFrame(self,dc,rect) + if self.select2_hint then + dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY) + end +end + function ListBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) local list = self.subviews.list diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 67090e114..145300c59 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -384,6 +384,7 @@ List.ATTRS{ inactive_pen = DEFAULT_NIL, on_select = DEFAULT_NIL, on_submit = DEFAULT_NIL, + on_submit2 = DEFAULT_NIL, row_height = 1, scroll_keys = STANDARDSCROLL, icon_width = DEFAULT_NIL, @@ -542,10 +543,19 @@ function List:submit() end end +function List:submit2() + if self.on_submit2 and #self.choices > 0 then + self.on_submit2(self:getSelected()) + end +end + function List:onInput(keys) if self.on_submit and keys.SELECT then self:submit() return true + elseif self.on_submit2 and keys.SEC_SELECT then + self:submit2() + return true else for k,v in pairs(self.scroll_keys) do if keys[k] then @@ -608,6 +618,11 @@ function FilteredList:init(info) return info.on_submit(self:getSelected()) end end + if info.on_submit2 then + self.list.on_submit2 = function() + return info.on_submit2(self:getSelected()) + end + end self.not_found = Label{ visible = false, text = info.not_found_label or 'No matches', @@ -634,6 +649,10 @@ function FilteredList:submit() return self.list:submit() end +function FilteredList:submit2() + return self.list:submit2() +end + function FilteredList:canSubmit() return not self.not_found.visible end diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 80c05d296..a387e64b9 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -552,18 +552,22 @@ function JobConstraints:onNewConstraint() table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) end - dlg.showListPrompt( - 'New limit', - 'Select one of the possible outputs:', - COLOR_WHITE, - choices, - function(idx,item) + dlg.ListBox{ + frame_title = 'New limit', + text = 'Select one of the possible outputs:', + text_pen = COLOR_WHITE, + choices = choices, + on_select = function(idx,item) + self:saveConstraint(item.obj) + end, + select2_hint = 'Advanced', + on_select2 = function(idx,item) NewConstraint{ constraint = item.obj, on_submit = self:callback('saveConstraint') }:show() - end - ) + end, + }:show() end function JobConstraints:onDeleteConstraint() From 7f23c1f55f1e743c27cdbc49e87f54786717c5f8 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 17:16:32 +0200 Subject: [PATCH 237/472] Added ability to look in advfort, moved to tiletypes and fixed most of predicates --- scripts/gui/advfort.lua | 107 +++++++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 46 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 5cb0deabd..8dee2e276 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -4,6 +4,8 @@ local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' local buildings=require 'dfhack.buildings' +local tile_attrs = df.tiletype.attrs + mode=mode or 0 keybinds={ key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, @@ -125,51 +127,54 @@ function makeset(args) end return tbl end +function NotConstruct(args) + local tt=dfhack.maps.getTileType(args.pos) + if tile_attrs[tt].material~=df.tiletype_material.CONSTRUCTION and dfhack.buildings.findAtTile(args.pos)==nil then + return true + else + return false, "Can only do it on non constructions" + end +end function IsConstruct(args) local tt=dfhack.maps.getTileType(args.pos) - local cwalls=makeset{ df.tiletype.ConstructedWallRD2, df.tiletype.ConstructedWallR2D, df.tiletype.ConstructedWallR2U, df.tiletype.ConstructedWallRU2, - df.tiletype.ConstructedWallL2U, df.tiletype.ConstructedWallLU2, df.tiletype.ConstructedWallL2D, df.tiletype.ConstructedWallLD2, - df.tiletype.ConstructedWallLRUD, df.tiletype.ConstructedWallRUD, df.tiletype.ConstructedWallLRD, df.tiletype.ConstructedWallLRU, - df.tiletype.ConstructedWallLUD, df.tiletype.ConstructedWallRD, df.tiletype.ConstructedWallRU, df.tiletype.ConstructedWallLU, - df.tiletype.ConstructedWallLD, df.tiletype.ConstructedWallUD, df.tiletype.ConstructedWallLR,} - if cwalls[tt] or dfhack.buildings.findAtTile(args.pos) then + if tile_attrs[tt].material==df.tiletype_material.CONSTRUCTION or dfhack.buildings.findAtTile(args.pos) then return true else return false, "Can only do it on constructions" end end +function IsHardMaterial(args) + local tt=dfhack.maps.getTileType(args.pos) + local mat=tile_attrs[tt].material + local hard_materials={df.tiletype_material.STONE,df.tiletype_material.FEATURE, + df.tiletype_material.LAVA_STONE,df.tiletype_material.MINERAL,df.tiletype_material.FROZEN_LIQUID,} + if hard_materials[mat] then + return true + else + return false, "Can only do it on hard materials" + end +end +function IsStairs(args) + local tt=dfhack.maps.getTileType(args.pos) + local shape=tile_attrs[tt].shape + if shape==df.tiletype_shape.STAIR_UP or shape==df.tiletype_shape.STAIR_DOWN or shape==df.tiletype_shape.STAIR_UPDOWN or shape==df.tiletype_shape.RAMP then + return true + else + return false,"Can only do it on stairs/ramps" + end +end +function IsFloor(args) + local tt=dfhack.maps.getTileType(args.pos) + local shape=tile_attrs[tt].shape + if shape==df.tiletype_shape.FLOOR or shape==df.tiletype_shape.BOULDER or shape==df.tiletype_shape.PEBBLES then + return true + else + return false,"Can only do it on floors" + end +end function IsWall(args) local tt=dfhack.maps.getTileType(args.pos) - local walls=makeset{df.tiletype.StoneWallWorn1, df.tiletype.StoneWallWorn2, df.tiletype.StoneWallWorn3, df.tiletype.StoneWall, - df.tiletype.SoilWall, df.tiletype.LavaWallSmoothRD2, df.tiletype.LavaWallSmoothR2D, df.tiletype.LavaWallSmoothR2U, df.tiletype.LavaWallSmoothRU2, - df.tiletype.LavaWallSmoothL2U, df.tiletype.LavaWallSmoothLU2, df.tiletype.LavaWallSmoothL2D, df.tiletype.LavaWallSmoothLD2, df.tiletype.LavaWallSmoothLRUD, - df.tiletype.LavaWallSmoothRUD, df.tiletype.LavaWallSmoothLRD, df.tiletype.LavaWallSmoothLRU, df.tiletype.LavaWallSmoothLUD, df.tiletype.LavaWallSmoothRD, - df.tiletype.LavaWallSmoothRU, df.tiletype.LavaWallSmoothLU, df.tiletype.LavaWallSmoothLD, df.tiletype.LavaWallSmoothUD, df.tiletype.LavaWallSmoothLR, - df.tiletype.FeatureWallSmoothRD2, df.tiletype.FeatureWallSmoothR2D, df.tiletype.FeatureWallSmoothR2U, df.tiletype.FeatureWallSmoothRU2, - df.tiletype.FeatureWallSmoothL2U, df.tiletype.FeatureWallSmoothLU2, df.tiletype.FeatureWallSmoothL2D, df.tiletype.FeatureWallSmoothLD2, - df.tiletype.FeatureWallSmoothLRUD, df.tiletype.FeatureWallSmoothRUD, df.tiletype.FeatureWallSmoothLRD, df.tiletype.FeatureWallSmoothLRU, - df.tiletype.FeatureWallSmoothLUD, df.tiletype.FeatureWallSmoothRD, df.tiletype.FeatureWallSmoothRU, df.tiletype.FeatureWallSmoothLU, - df.tiletype.FeatureWallSmoothLD, df.tiletype.FeatureWallSmoothUD, df.tiletype.FeatureWallSmoothLR, df.tiletype.StoneWallSmoothRD2, - df.tiletype.StoneWallSmoothR2D, df.tiletype.StoneWallSmoothR2U, df.tiletype.StoneWallSmoothRU2, df.tiletype.StoneWallSmoothL2U, - df.tiletype.StoneWallSmoothLU2, df.tiletype.StoneWallSmoothL2D, df.tiletype.StoneWallSmoothLD2, df.tiletype.StoneWallSmoothLRUD, - df.tiletype.StoneWallSmoothRUD, df.tiletype.StoneWallSmoothLRD, df.tiletype.StoneWallSmoothLRU, df.tiletype.StoneWallSmoothLUD, - df.tiletype.StoneWallSmoothRD, df.tiletype.StoneWallSmoothRU, df.tiletype.StoneWallSmoothLU, df.tiletype.StoneWallSmoothLD, - df.tiletype.StoneWallSmoothUD, df.tiletype.StoneWallSmoothLR, df.tiletype.LavaWallWorn1, df.tiletype.LavaWallWorn2, df.tiletype.LavaWallWorn3, - df.tiletype.LavaWall, df.tiletype.FeatureWallWorn1, df.tiletype.FeatureWallWorn2, df.tiletype.FeatureWallWorn3, df.tiletype.FeatureWall, - df.tiletype.FrozenWallWorn1, df.tiletype.FrozenWallWorn2, df.tiletype.FrozenWallWorn3, df.tiletype.FrozenWall, df.tiletype.MineralWallSmoothRD2, - df.tiletype.MineralWallSmoothR2D, df.tiletype.MineralWallSmoothR2U, df.tiletype.MineralWallSmoothRU2, df.tiletype.MineralWallSmoothL2U, - df.tiletype.MineralWallSmoothLU2, df.tiletype.MineralWallSmoothL2D, df.tiletype.MineralWallSmoothLD2, df.tiletype.MineralWallSmoothLRUD, - df.tiletype.MineralWallSmoothRUD, df.tiletype.MineralWallSmoothLRD, df.tiletype.MineralWallSmoothLRU, df.tiletype.MineralWallSmoothLUD, - df.tiletype.MineralWallSmoothRD, df.tiletype.MineralWallSmoothRU, df.tiletype.MineralWallSmoothLU, df.tiletype.MineralWallSmoothLD, - df.tiletype.MineralWallSmoothUD, df.tiletype.MineralWallSmoothLR, df.tiletype.MineralWallWorn1, df.tiletype.MineralWallWorn2, - df.tiletype.MineralWallWorn3, df.tiletype.MineralWall, df.tiletype.FrozenWallSmoothRD2, df.tiletype.FrozenWallSmoothR2D, - df.tiletype.FrozenWallSmoothR2U, df.tiletype.FrozenWallSmoothRU2, df.tiletype.FrozenWallSmoothL2U, df.tiletype.FrozenWallSmoothLU2, - df.tiletype.FrozenWallSmoothL2D, df.tiletype.FrozenWallSmoothLD2, df.tiletype.FrozenWallSmoothLRUD, df.tiletype.FrozenWallSmoothRUD, - df.tiletype.FrozenWallSmoothLRD, df.tiletype.FrozenWallSmoothLRU, df.tiletype.FrozenWallSmoothLUD, df.tiletype.FrozenWallSmoothRD, - df.tiletype.FrozenWallSmoothRU, df.tiletype.FrozenWallSmoothLU, df.tiletype.FrozenWallSmoothLD, df.tiletype.FrozenWallSmoothUD, - df.tiletype.FrozenWallSmoothLR, - } - if walls[tt] then + if tile_attrs[tt].shape==df.tiletype_shape.WALL then return true else return false, "Can only do it on walls" @@ -177,19 +182,24 @@ function IsWall(args) end function IsTree(args) local tt=dfhack.maps.getTileType(args.pos) - if tt==24 then + if tile_attrs[tt].shape==df.tiletype_shape.TREE then return true else return false, "Can only do it on trees" end - -end -function IsWater(args) - return true end function IsPlant(args) + local tt=dfhack.maps.getTileType(args.pos) + if tile_attrs[tt].shape==df.tiletype_shape.PLANT then + return true + else + return false, "Can only do it on plants" + end +end +function IsWater(args) return true end + function IsUnit(args) local pos=args.pos for k,v in pairs(df.global.world.units.active) do @@ -212,6 +222,9 @@ function AssignBuildingRef(args) local bld=dfhack.buildings.findAtTile(args.pos) args.job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=bld.id}) bld.jobs:insert("#",args.job) +end +function AssignItems(items,args) + end --[[ building submodule... ]]-- function DialogBuildingChoose(on_select, on_cancel) @@ -291,10 +304,10 @@ function ContinueJob(unit) end dig_modes={ - {"CarveFortification" ,df.job_type.CarveFortification,{IsWall}}, - {"DetailWall" ,df.job_type.DetailWall,{IsWall}}, - {"DetailFloor" ,df.job_type.DetailFloor}, - --{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work?? + {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMat}}, + {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMat}}, + {"DetailFloor" ,df.job_type.DetailFloor,{IsFloor,IsHardMat}}, + --{"CarveTrack" ,df.job_type.CarveTrack}, -- does not work?? {"Dig" ,df.job_type.Dig,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveUpwardStaircase" ,df.job_type.CarveUpwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING),IsWall}}, {"CarveDownwardStaircase",df.job_type.CarveDownwardStaircase,{MakePredicateWieldsItem(df.job_skill.MINING)}}, @@ -310,6 +323,7 @@ dig_modes={ {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, + {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, } @@ -358,7 +372,8 @@ MOVEMENT_KEYS = { ALLOWED_KEYS={ A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, - A_MOVE_UP_AUX=true + A_MOVE_UP_AUX=true,A_LOOK=true,CURSOR_DOWN=true,CURSOR_UP=true,CURSOR_LEFT=true,CURSOR_RIGHT=true, + CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true } function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} From 2d4fc739ed450c80305c24140d6db02b8d3e3387 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 17:16:58 +0200 Subject: [PATCH 238/472] Removed console spam --- scripts/gui/advfort.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 8dee2e276..5fbce9395 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -409,7 +409,7 @@ function usetool:onInput(keys) local cur_mode=dig_modes[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do - print(code) + --print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} From 5b53f07905b0b3caea076feeb66e2b7ecbae287b Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 17:24:45 +0200 Subject: [PATCH 239/472] Real construction removal added. --- scripts/gui/advfort.lua | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 5fbce9395..608ddfd3b 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -273,6 +273,15 @@ function BuildingChosen(st_pos,pos,index) end --[[ end of buildings ]]-- +function RemoveConstruction(args) + local bld=dfhack.buildings.findAtTile(args.pos) + if bld==nil then + MakeJob(args.unit,args.pos,df.job_type.RemoveConstruction,args.old_pos) + else + bld:queueDestroy() + AssignUnitToJob(bld.jobs[0],args.unit,args.old_pos) + end +end function AssignJobToBuild(args) local bld=dfhack.buildings.findAtTile(args.pos) if bld~=nil then @@ -320,7 +329,7 @@ dig_modes={ --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, --{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant}}, - {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, + {"RemoveConstruction" ,RemoveConstruction,{IsConstruct}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, From 3858fb320dce64a40306270dd9139a647e4a51d5 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 17:50:22 +0200 Subject: [PATCH 240/472] in Advfort- splint removeconstruction into building/construction removal. --- scripts/gui/advfort.lua | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 608ddfd3b..523230a08 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -135,9 +135,15 @@ function NotConstruct(args) return false, "Can only do it on non constructions" end end +function IsBuilding(args) + if dfhack.buildings.findAtTile(args.pos) then + return true + end + return false, "Can only do it on buildings" +end function IsConstruct(args) local tt=dfhack.maps.getTileType(args.pos) - if tile_attrs[tt].material==df.tiletype_material.CONSTRUCTION or dfhack.buildings.findAtTile(args.pos) then + if tile_attrs[tt].material==df.tiletype_material.CONSTRUCTION then return true else return false, "Can only do it on constructions" @@ -273,15 +279,19 @@ function BuildingChosen(st_pos,pos,index) end --[[ end of buildings ]]-- -function RemoveConstruction(args) +function RemoveBuilding(args) local bld=dfhack.buildings.findAtTile(args.pos) - if bld==nil then - MakeJob(args.unit,args.pos,df.job_type.RemoveConstruction,args.old_pos) - else + if bld~=nil then bld:queueDestroy() - AssignUnitToJob(bld.jobs[0],args.unit,args.old_pos) + for k,v in ipairs(bld.jobs) do + if v.job_type==df.job_type.DestroyBuilding then + AssignUnitToJob(v,args.unit,args.old_pos) + return + end + end end end + function AssignJobToBuild(args) local bld=dfhack.buildings.findAtTile(args.pos) if bld~=nil then @@ -329,10 +339,11 @@ dig_modes={ --{"Surgery" ,df.job_type.Surgery,{IsUnit},{SetPatientRef}}, --{"TameAnimal" ,df.job_type.TameAnimal,{IsUnit},{SetCreatureRef}}, {"GatherPlants" ,df.job_type.GatherPlants,{IsPlant}}, - {"RemoveConstruction" ,RemoveConstruction,{IsConstruct}}, + {"RemoveConstruction" ,df.job_type.RemoveConstruction,{IsConstruct}}, + {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, - {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, + {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, } From d7f7538d0148e8a731089034f431dfcd80c3a268 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 29 Nov 2012 10:33:18 +0100 Subject: [PATCH 241/472] ruby: fix Pointer assignment --- plugins/ruby/ruby-autogen-defs.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index 4148659a6..a3e810178 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -308,7 +308,7 @@ module DFHack DFHack.memory_write_int32(@_memaddr, v) end when nil; DFHack.memory_write_int32(@_memaddr, 0) - else _get._set(v) + else @_tg._at(_getp)._set(v) end end From 184082b379c67989c55d5454cc3a204b40d71360 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 29 Nov 2012 17:11:16 +0100 Subject: [PATCH 242/472] scripts/lever: fix for links to cage/support --- scripts/lever.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/scripts/lever.rb b/scripts/lever.rb index 59196f7d2..43aa29b04 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -47,9 +47,12 @@ def lever_descr(bld, idx=nil) }.flatten.each { |r| # linked building description tg = r.building_tg - state = tg.gate_flags.closed ? 'closed' : 'opened' - state << ', closing' if tg.gate_flags.closing - state << ', opening' if tg.gate_flags.opening + state = '' + if tg.respond_to?(:gate_flags) + state << (tg.gate_flags.closed ? 'closed' : 'opened') + state << ", closing (#{tg.timer})" if tg.gate_flags.closing + state << ", opening (#{tg.timer})" if tg.gate_flags.opening + end ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") From f617d2844b2bf2796f064811c650e0761d72a713 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 18:33:04 +0200 Subject: [PATCH 243/472] Fixed look exit and allow skip messages --- scripts/gui/advfort.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 523230a08..011ed1a68 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -393,7 +393,7 @@ ALLOWED_KEYS={ A_MOVE_N=true,A_MOVE_S=true,A_MOVE_W=true,A_MOVE_E=true,A_MOVE_NW=true, A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, A_MOVE_UP_AUX=true,A_LOOK=true,CURSOR_DOWN=true,CURSOR_UP=true,CURSOR_LEFT=true,CURSOR_RIGHT=true, - CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true + CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true,A_CLEAR_ANNOUNCEMENTS=true, } function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} @@ -413,7 +413,11 @@ end function usetool:onInput(keys) if keys.LEAVESCREEN then - self:dismiss() + if df.global.cursor.x~=-30000 then + self:sendInputToParent("LEAVESCREEN") + else + self:dismiss() + end elseif keys[keybinds.key_next.key] then mode=(mode+1)%#dig_modes elseif keys[keybinds.key_prev.key] then From 346b402a0f18833e0e76a8ca135d39d5bb11f5b7 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 18:49:36 +0200 Subject: [PATCH 244/472] Allow look up/down, unsuspend job if pressing wait with job. --- scripts/gui/advfort.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 011ed1a68..4e620a405 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -309,9 +309,16 @@ function AssignJobToBuild(args) DialogBuildingChoose(dfhack.curry(BuildingChosen,args.old_pos,args.pos)) end end +function CancelJob(unit) + local c_job=unit.job.current_job + if c_job then + unit.job.current_job =nil --todo add real cancelation + end +end function ContinueJob(unit) local c_job=unit.job.current_job if c_job then + c_job.flags.suspend=false for k,v in pairs(c_job.items) do if v.is_fetching==1 then unit.path.dest:assign(v.item.pos) @@ -394,6 +401,7 @@ ALLOWED_KEYS={ A_MOVE_NE=true,A_MOVE_SW=true,A_MOVE_SE=true,A_STANCE=true,SELECT=true,A_MOVE_DOWN_AUX=true, A_MOVE_UP_AUX=true,A_LOOK=true,CURSOR_DOWN=true,CURSOR_UP=true,CURSOR_LEFT=true,CURSOR_RIGHT=true, CURSOR_UPLEFT=true,CURSOR_UPRIGHT=true,CURSOR_DOWNLEFT=true,CURSOR_DOWNRIGHT=true,A_CLEAR_ANNOUNCEMENTS=true, + CURSOR_UP_Z=true,CURSOR_DOWN_Z=true, } function moddedpos(pos,delta) return {x=pos.x+delta[1],y=pos.y+delta[2],z=pos.z+delta[3]} @@ -411,12 +419,13 @@ function usetool:onHelp() showHelp() end function usetool:onInput(keys) - + local adv=df.global.world.units.active[0] if keys.LEAVESCREEN then if df.global.cursor.x~=-30000 then self:sendInputToParent("LEAVESCREEN") else self:dismiss() + CancelJob(adv) end elseif keys[keybinds.key_next.key] then mode=(mode+1)%#dig_modes @@ -426,14 +435,14 @@ function usetool:onInput(keys) --elseif keys.A_LOOK then -- self:sendInputToParent("A_LOOK") elseif keys[keybinds.key_continue.key] then - ContinueJob(df.global.world.units.active[0]) + ContinueJob(adv) self:sendInputToParent("A_WAIT") else - local adv=df.global.world.units.active[0] + local cur_mode=dig_modes[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do - --print(code) + print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} From 471d15ba4cd0ea3676d8bc0dd1d96de8e55d0886 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 20:49:16 +0200 Subject: [PATCH 245/472] New building selection dialog. --- library/lua/gui/buildings.lua | 268 ++++++++++++++++++++++++++++++++++ scripts/gui/advfort.lua | 57 ++------ 2 files changed, 280 insertions(+), 45 deletions(-) create mode 100644 library/lua/gui/buildings.lua diff --git a/library/lua/gui/buildings.lua b/library/lua/gui/buildings.lua new file mode 100644 index 000000000..98bee3be5 --- /dev/null +++ b/library/lua/gui/buildings.lua @@ -0,0 +1,268 @@ +-- Stock dialog for selecting buildings + +local _ENV = mkmodule('gui.buildings') + +local gui = require('gui') +local widgets = require('gui.widgets') +local dlg = require('gui.dialogs') +local utils = require('utils') + +ARROW = string.char(26) + +WORKSHOP_ABSTRACT={ + [df.building_type.Civzone]=true,[df.building_type.Stockpile]=true, +} +WORKSHOP_SPECIAL={ + [df.building_type.Workshop]=true,[df.building_type.Furnace]=true,[df.building_type.Trap]=true, + [df.building_type.Construction]=true +} +BuildingDialog = defclass(BuildingDialog, gui.FramedScreen) + +BuildingDialog.focus_path = 'BuildingDialog' + +BuildingDialog.ATTRS{ + prompt = 'Type or select a building from this list', + frame_style = gui.GREY_LINE_FRAME, + frame_inset = 1, + frame_title = 'Select Building', + -- new attrs + none_caption = 'none', + hide_none = false, + use_abstract = true, + use_workshops = true, + use_tool_workshop=true, + use_furnace = true, + use_construction = true, + use_trap = true, + use_custom = true, + building_filter = DEFAULT_NIL, + on_select = DEFAULT_NIL, + on_cancel = DEFAULT_NIL, + on_close = DEFAULT_NIL, +} + +function BuildingDialog:init(info) + self:addviews{ + widgets.Label{ + text = { + self.prompt, '\n\n', + 'Category: ', { text = self:cb_getfield('context_str'), pen = COLOR_CYAN } + }, + text_pen = COLOR_WHITE, + frame = { l = 0, t = 0 }, + }, + widgets.Label{ + view_id = 'back', + visible = false, + text = { { key = 'LEAVESCREEN', text = ': Back' } }, + frame = { r = 0, b = 0 }, + auto_width = true, + }, + widgets.FilteredList{ + view_id = 'list', + not_found_label = 'No matching buildings', + frame = { l = 0, r = 0, t = 4, b = 2 }, + icon_width = 2, + on_submit = self:callback('onSubmitItem'), + }, + widgets.Label{ + text = { { + key = 'SELECT', text = ': Select', + disabled = function() return not self.subviews.list:canSubmit() end + } }, + frame = { l = 0, b = 0 }, + } + } + self:initBuiltinMode() +end + +function BuildingDialog:getWantedFrameSize(rect) + return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8) +end + +function BuildingDialog:onDestroy() + if self.on_close then + self.on_close() + end +end + +function BuildingDialog:initBuiltinMode() + local choices = {} + if not self.hide_none then + table.insert(choices, { text = self.none_caption, type_id = -1, subtype_id = -1, custom_id=-1}) + end + + if self.use_workshops then + table.insert(choices, { + icon = ARROW, text = 'workshop', key = 'CUSTOM_SHIFT_W', + cb = self:callback('initWorkshopMode') + }) + end + if self.use_furnace then + table.insert(choices, { + icon = ARROW, text = 'furnaces', key = 'CUSTOM_SHIFT_F', + cb = self:callback('initFurnaceMode') + }) + end + if self.use_trap then + table.insert(choices, { + icon = ARROW, text = 'traps', key = 'CUSTOM_SHIFT_T', + cb = self:callback('initTrapMode') + }) + end + if self.use_construction then + table.insert(choices, { + icon = ARROW, text = 'constructions', key = 'CUSTOM_SHIFT_C', + cb = self:callback('initConstructionMode') + }) + end + if self.use_custom then + table.insert(choices, { + icon = ARROW, text = 'custom workshop', key = 'CUSTOM_SHIFT_U', + cb = self:callback('initCustomMode') + }) + end + + + for i=0,df.building_type._last_item do + if (not WORKSHOP_ABSTRACT[i] or self.use_abstract)and not WORKSHOP_SPECIAL[i] then + self:addBuilding(choices, df.building_type[i], i, -1,-1,nil) + end + end + + self:pushContext('Any building', choices) +end + +function BuildingDialog:initWorkshopMode() + local choices = {} + + for i=0,df.workshop_type._last_item do + if i~=df.workshop_type.Custom and (i~=df.workshop_type.Tool or self.use_tool_workshop) then + self:addBuilding(choices, df.workshop_type[i], df.building_type.Workshop, i,-1,nil) + end + end + + self:pushContext('Workshops', choices) +end +function BuildingDialog:initTrapMode() + local choices = {} + + for i=0,df.trap_type._last_item do + self:addBuilding(choices, df.trap_type[i], df.building_type.Trap, i,-1,nil) + end + + self:pushContext('Traps', choices) +end + +function BuildingDialog:initConstructionMode() + local choices = {} + + for i=0,df.construction_type._last_item do + self:addBuilding(choices, df.construction_type[i], df.building_type.Construction, i,-1,nil) + end + + self:pushContext('Constructions', choices) +end + +function BuildingDialog:initFurnaceMode() + local choices = {} + + for i=0,df.furnace_type._last_item do + self:addBuilding(choices, df.furnace_type[i], df.building_type.Furnace, i,-1,nil) + end + + self:pushContext('Furnaces', choices) +end + +function BuildingDialog:initCustomMode() + local choices = {} + local raws=df.global.world.raws.buildings.all + for k,v in pairs(raws) do + self:addBuilding(choices, v.name, df.building_type.Workshop,df.workshop_type.Custom,v.id,v) + end + + self:pushContext('Custom workshops', choices) +end + +function BuildingDialog:addBuilding(choices, name,type_id, subtype_id, custom_id, parent) + -- Check the filter + if self.building_filter and not self.building_filter(name,type_id,subtype_id,custom_id, parent) then + return + end + + table.insert(choices, { + text = name:lower(), + customshop = parent, + type_id = type_id, subtype_id = subtype_id, custom_id=custom_id + }) +end + +function BuildingDialog:pushContext(name, choices) + if not self.back_stack then + self.back_stack = {} + self.subviews.back.visible = false + else + table.insert(self.back_stack, { + context_str = self.context_str, + all_choices = self.subviews.list:getChoices(), + edit_text = self.subviews.list:getFilter(), + selected = self.subviews.list:getSelected(), + }) + self.subviews.back.visible = true + end + + self.context_str = name + self.subviews.list:setChoices(choices, 1) +end + +function BuildingDialog:onGoBack() + local save = table.remove(self.back_stack) + self.subviews.back.visible = (#self.back_stack > 0) + + self.context_str = save.context_str + self.subviews.list:setChoices(save.all_choices) + self.subviews.list:setFilter(save.edit_text, save.selected) +end + +function BuildingDialog:submitBuilding(type_id,subtype_id,custom_id,choice,index) + self:dismiss() + + if self.on_select then + self.on_select(type_id,subtype_id,custom_id,choice,index) + end +end + +function BuildingDialog:onSubmitItem(idx, item) + if item.cb then + item:cb(idx) + else + self:submitBuilding(item.type_id, item.subtype_id,item.custom_id,item,idx) + end +end + +function BuildingDialog:onInput(keys) + if keys.LEAVESCREEN or keys.LEAVESCREEN_ALL then + if self.subviews.back.visible and not keys.LEAVESCREEN_ALL then + self:onGoBack() + else + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end + else + self:inputToSubviews(keys) + end +end + +function showBuildingPrompt(title, prompt, on_select, on_cancel, build_filter) + BuildingDialog{ + frame_title = title, + prompt = prompt, + building_filter = build_filter, + on_select = on_select, + on_cancel = on_cancel, + }:show() +end + +return _ENV diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 4e620a405..7348e3d55 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -3,6 +3,7 @@ local gui = require 'gui' local wid=require 'gui.widgets' local dialog=require 'gui.dialogs' local buildings=require 'dfhack.buildings' +local bdialog=require 'gui.buildings' local tile_attrs = df.tiletype.attrs @@ -11,9 +12,9 @@ keybinds={ key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, key_prev={key="CUSTOM_SHIFT_R",desc="Previous job in the list"}, key_continue={key="A_WAIT",desc="Continue job if available"}, -key_down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"},--does not work? +key_down_alt1={key="CUSTOM_CTRL_D",desc="Use job down"}, key_down_alt2={key="CURSOR_DOWN_Z_AUX",desc="Use job down"}, -key_up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, --does not work? +key_up_alt1={key="CUSTOM_CTRL_E",desc="Use job up"}, key_up_alt2={key="CURSOR_UP_Z_AUX",desc="Use job up"}, key_use_same={key="A_MOVE_SAME_SQUARE",desc="Use job at the tile you are standing"}, } @@ -233,49 +234,15 @@ function AssignItems(items,args) end --[[ building submodule... ]]-- -function DialogBuildingChoose(on_select, on_cancel) - blist={} - for i=df.building_type._first_item,df.building_type._last_item do - table.insert(blist,df.building_type[i]) - end - dialog.showListPrompt("Building list", "Choose building:", COLOR_WHITE, blist, on_select, on_cancel, nil, true) -end -function DialogSubtypeChoose(subtype,on_select, on_cancel) - blist={} - for i=subtype._first_item,subtype._last_item do - table.insert(blist,subtype[i]) - end - dialog.showListPrompt("Subtype", "Choose subtype:", COLOR_WHITE, blist, on_select, on_cancel, nil, true) -end ---workshop, furnaces, traps -invalid_buildings={} -function SubtypeChosen(args,index) - args.subtype=index-1 - buildings.constructBuilding(args) -end -function BuildingChosen(st_pos,pos,index) - local b_type=index-2 + +function BuildingChosen(inp_args,type_id,subtype_id,custom_id) local args={} - args.type=b_type - args.pos=pos - args.items=itemsAtPos(st_pos) - if invalid_buildings[b_type] then - return - elseif b_type==df.building_type.Construction then - DialogSubtypeChoose(df.construction_type,dfhack.curry(SubtypeChosen,args)) - return - elseif b_type==df.building_type.Furnace then - DialogSubtypeChoose(df.furnace_type,dfhack.curry(SubtypeChosen,args)) - return - elseif b_type==df.building_type.Trap then - DialogSubtypeChoose(df.trap_type,dfhack.curry(SubtypeChosen,args)) - return - elseif b_type==df.building_type.Workshop then - DialogSubtypeChoose(df.workshop_type,dfhack.curry(SubtypeChosen,args)) - return - else - buildings.constructBuilding(args) - end + args.type=type_id + args.subtype=subtype_id + args.custom=custom_id + args.pos=inp_args.pos + args.items=itemsAtPos(inp_args.old_pos) + buildings.constructBuilding(args) end --[[ end of buildings ]]-- @@ -306,7 +273,7 @@ function AssignJobToBuild(args) end else - DialogBuildingChoose(dfhack.curry(BuildingChosen,args.old_pos,args.pos)) + bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true}:show() end end function CancelJob(unit) From 49de3fe237e89d7d459ac7e0809cab32644dcd2e Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 29 Nov 2012 20:50:02 +0200 Subject: [PATCH 246/472] Removed spam yet again... --- scripts/gui/advfort.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 7348e3d55..9116126fb 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -409,7 +409,7 @@ function usetool:onInput(keys) local cur_mode=dig_modes[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do - print(code) + --print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} From c31b320722b5946b14674774aec610a8706881ea Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 30 Nov 2012 00:02:03 +0200 Subject: [PATCH 247/472] Building now respects required items. --- scripts/gui/advfort.lua | 159 +++++++++++++++++++++++++++++++--------- 1 file changed, 125 insertions(+), 34 deletions(-) diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 9116126fb..e7ab494a5 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -6,7 +6,18 @@ local buildings=require 'dfhack.buildings' local bdialog=require 'gui.buildings' local tile_attrs = df.tiletype.attrs - +settings={build_by_items=false,check_inv=false,df_assign=true} +for k,v in ipairs({...}) do + if v=="-c" or v=="--cheat" then + settings.build_by_items=true + settings.df_assign=false + elseif v=="-i" or v=="--inventory" then + settings.check_inv=true + settings.df_assign=false + elseif v=="-a" or v=="--nodfassign" then + settings.df_assign=false + end +end mode=mode or 0 keybinds={ key_next={key="CUSTOM_SHIFT_T",desc="Next job in the list"}, @@ -61,19 +72,29 @@ function AddNewJob(job) nl.item=job job.list_link=nl end -function MakeJob(unit,pos,job_type,unit_pos,post_actions) +function MakeJob(args) local nj=df.job:new() nj.id=df.global.job_next_id df.global.job_next_id=df.global.job_next_id+1 --nj.flags.special=true - nj.job_type=job_type + nj.job_type=args.job_type nj.completion_timer=-1 - --nj.unk4a=12 - --nj.unk4b=0 - nj.pos:assign(pos) - AssignUnitToJob(nj,unit,unit_pos) - for k,v in ipairs(post_actions or {}) do - v{job=nj,pos=pos,old_pos=unit_pos,unit=unit} + + nj.pos:assign(args.pos) + args.job=nj + local failed=false + for k,v in ipairs(args.pre_actions or {}) do + local ok,msg=v(args) + if not ok then + failed=true + return false,msg + end + end + if not failed then + AssignUnitToJob(nj,args.unit,args.from_pos) + end + for k,v in ipairs(args.post_actions or {}) do + v(args) end AddNewJob(nj) return nj @@ -121,6 +142,7 @@ function MakePredicateWieldsItem(item_skill) end return pred end + function makeset(args) local tbl={} for k,v in pairs(args) do @@ -230,10 +252,6 @@ function AssignBuildingRef(args) args.job.general_refs:insert("#",{new=df.general_ref_building_holderst,building_id=bld.id}) bld.jobs:insert("#",args.job) end -function AssignItems(items,args) - -end ---[[ building submodule... ]]-- function BuildingChosen(inp_args,type_id,subtype_id,custom_id) local args={} @@ -241,40 +259,94 @@ function BuildingChosen(inp_args,type_id,subtype_id,custom_id) args.subtype=subtype_id args.custom=custom_id args.pos=inp_args.pos - args.items=itemsAtPos(inp_args.old_pos) + --if settings.build_by_items then + -- args.items=itemsAtPos(inp_args.from_pos) + --end + --printall(args.items) buildings.constructBuilding(args) end ---[[ end of buildings ]]-- + function RemoveBuilding(args) local bld=dfhack.buildings.findAtTile(args.pos) if bld~=nil then bld:queueDestroy() for k,v in ipairs(bld.jobs) do if v.job_type==df.job_type.DestroyBuilding then - AssignUnitToJob(v,args.unit,args.old_pos) - return + AssignUnitToJob(v,args.unit,args.from_pos) + return true end end + return false,"Building removal job failed to be created" + else + return false,"No building to remove" end end - +function AssignJobItems(args) + if settings.df_assign then + return true + end + --printall(args) + local job=args.job + local its=itemsAtPos(args.from_pos) + if settings.check_inv then + for k,v in pairs(args.unit.inventory) do + table.insert(its,v.item) + end + end + --[[while(#job.items>0) do --clear old job items + job.items[#job.items-1]:delete() + job.items:erase(#job.items-1) + end]] + local used_item_id={} + for job_id, trg_job_item in ipairs(job.job_items) do + for _,cur_item in pairs(its) do + if not used_item_id[cur_item.id] then + if (trg_job_item.quantity>0 and dfhack.job.isSuitableItem(trg_job_item, cur_item:getType(), cur_item:getSubtype()) and + dfhack.job.isSuitableMaterial(trg_job_item, cur_item:getMaterial(), cur_item:getMaterialIndex())) or settings.build_by_items then + job.items:insert("#",{new=true,item=cur_item,role=2,job_item_idx=job_id}) + trg_job_item.quantity=trg_job_item.quantity-1 + --print(string.format("item added, job_item_id=%d, item %s, quantity left=%d",job_id,tostring(cur_item),trg_job_item.quantity)) + used_item_id[cur_item.id]=true + end + end + end + end + --[[print("+============+") + printall(job.items) + print("-============-") + printall(job.job_items) + print("+============+")]]-- + if not settings.build_by_items then + for job_id, trg_job_item in ipairs(job.job_items) do + if trg_job_item.quantity>0 then + return false, "Not enough items for this job" + end + end + end + return true +end function AssignJobToBuild(args) local bld=dfhack.buildings.findAtTile(args.pos) + args.job_type=df.job_type.ConstructBuilding if bld~=nil then if #bld.jobs>0 then - AssignUnitToJob(bld.jobs[0],args.unit,args.old_pos) - else - local jb=MakeJob(args.unit,args.pos,df.job_type.ConstructBuilding,args.old_pos,{AssignBuildingRef}) - local its=itemsAtPos(args.old_pos) - for k,v in pairs(its) do - jb.items:insert("#",{new=true,item=v,role=2}) + args.job=bld.jobs[0] + local ok,msg=AssignJobItems(args) + if not ok then + return false,msg + else + AssignUnitToJob(bld.jobs[0],args.unit,args.from_pos) --todo check if correct job type end - + else + args.pre_action=AssignJobItems + local ok,msg=MakeJob(args) + return ok,msg end else bdialog.BuildingDialog{on_select=dfhack.curry(BuildingChosen,args),hide_none=true}:show() end + return true end function CancelJob(unit) local c_job=unit.job.current_job @@ -317,12 +389,17 @@ dig_modes={ {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, + --{"ConstructBlocks" ,df.job_type.ConstructBlocks,{},{SetBuildingRef},{AssignJobItems}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, + --{"LoadCageTrap" ,df.job_type.LoadCageTrap,{},{SetBuildingRef},{AssignJobItems}}, } usetool=defclass(usetool,gui.Screen) +function usetool:getShopMode() + return "In shop" +end function usetool:getModeName() local adv=df.global.world.units.active[0] if adv.job.current_job then @@ -332,11 +409,19 @@ function usetool:getModeName() end end + function usetool:init(args) self:addviews{ wid.Label{ + view_id="main_label", frame = {xalign=0,yalign=0}, text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getModeName,self)},{gap=1,key=keybinds.key_next.key}} + }, + wid.Label{ + view_id="shop_label", + visible=false, + frame = {xalign=0,yalign=0}, + text={{key=keybinds.key_prev.key},{gap=1,text=dfhack.curry(usetool.getShopMode,self),pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}},{gap=1,key=keybinds.key_next.key}} } } end @@ -412,7 +497,7 @@ function usetool:onInput(keys) --print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], - old_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z}} + from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_action=cur_mode[4],pre_action=cur_mode[5],job_type=cur_mode[2],screen=self} if code=="SELECT" then if df.global.cursor.x~=-30000 then state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} @@ -422,25 +507,31 @@ function usetool:onInput(keys) end for _,p in pairs(cur_mode[3] or {}) do - local t,v=p(state) - if t==false then - dfhack.gui.showAnnouncement(v,5,1) + local ok,msg=p(state) + if ok==false then + dfhack.gui.showAnnouncement(msg,5,1) failed=true end end + if not failed then + local ok,msg if type(cur_mode[2])=="function" then - cur_mode[2](state) + ok,msg=cur_mode[2](state) else - MakeJob(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) + ok,msg=MakeJob(state) + --(adv,moddedpos(adv.pos,MOVEMENT_KEYS[code]),cur_mode[2],adv.pos,cur_mode[4]) + end if code=="SELECT" then self:sendInputToParent("LEAVESCREEN") end - - self:sendInputToParent("A_WAIT") - + if ok then + self:sendInputToParent("A_WAIT") + else + dfhack.gui.showAnnouncement(msg,5,1) + end end return code end From 0fb5ad7729831796f5fb459d6e7a8ba76107c261 Mon Sep 17 00:00:00 2001 From: Warmist Date: Fri, 30 Nov 2012 01:17:01 +0200 Subject: [PATCH 248/472] Some readme and bugfixes --- Readme.rst | 11 ++++++++++- scripts/gui/advfort.lua | 36 +++++++++++++++++++++++++++++------- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/Readme.rst b/Readme.rst index 7d715077a..415f1a0e0 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2354,7 +2354,16 @@ gui/advfort This script allows to perform jobs in adventure mode. For more complete help press '?' while script is running. It's most confortable to use this as a -keybinding. (e.g. keybinding set Ctrl-T gui/advfort) +keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments: + +* -a or --nodfassign - uses different method to assign items. + +* -i or --inventory - checks inventory for possible items to use in the job. + +* -c or --cheat - relaxes item requirements for buildings (e.g. walls from bones). + implies -a + + gui/gm-editor ============= diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index e7ab494a5..e62088dd9 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -339,7 +339,7 @@ function AssignJobToBuild(args) AssignUnitToJob(bld.jobs[0],args.unit,args.from_pos) --todo check if correct job type end else - args.pre_action=AssignJobItems + args.pre_actions={AssignJobItems} local ok,msg=MakeJob(args) return ok,msg end @@ -367,7 +367,32 @@ function ContinueJob(unit) unit.path.dest:assign(c_job.pos) end end - +function AddItemRefMason(args) + --printall(args) + args.job.job_items:insert("#",{new=true,mat_type=0,quantity=1}) + return true +end +workshops={ + [df.workshop_type.Mason]={ + common={item_type=df.item_type.BOULDER,vector_id=df.job_item_vector_id.BOULDER, mat_type=0,flags2={non_economic=true,},flags3={hard=true}}, + [df.job_type.ConstructArmorStand]={{}}, + [df.job_type.ConstructBlocks]={{}}, + [df.job_type.ConstructThrone]={{}}, + [df.job_type.ConstructCoffin]={{}}, + [df.job_type.ConstructDoor]={{}}, + [df.job_type.ConstructFloodgate]={{}}, + [df.job_type.ConstructHatchCover]={{}}, + [df.job_type.ConstructGrate]={{}}, + [df.job_type.ConstructCabinet]={{}}, + [df.job_type.ConstructCofer]={{}}, + [df.job_type.ConstructStatue]={{}}, + [df.job_type.ConstructSlab]={{}}, + [df.job_type.ConstructTable]={{}}, + [df.job_type.ConstructWeaponRack]={{}}, + [df.job_type.ConstructQuern]={{}}, + [df.job_type.ConstructMillstone]={{}}, + }, + } dig_modes={ {"CarveFortification" ,df.job_type.CarveFortification,{IsWall,IsHardMat}}, {"DetailWall" ,df.job_type.DetailWall,{IsWall,IsHardMat}}, @@ -389,7 +414,7 @@ dig_modes={ {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, --{"HandleLargeCreature" ,df.job_type.HandleLargeCreature,{isUnit},{SetCreatureRef}}, {"Build" ,AssignJobToBuild}, - --{"ConstructBlocks" ,df.job_type.ConstructBlocks,{},{SetBuildingRef},{AssignJobItems}}, + --{"ConstructBlocks" ,df.job_type.ConstructBlocks,{},{AssignBuildingRef},{AddItemRefMason,AssignJobItems}}, {"RemoveStairs" ,df.job_type.RemoveStairs,{IsStairs,NotConstruct}}, --{"LoadCageTrap" ,df.job_type.LoadCageTrap,{},{SetBuildingRef},{AssignJobItems}}, @@ -426,7 +451,6 @@ function usetool:init(args) } end function usetool:onRenderBody(dc) - self:renderParent() end function usetool:onIdle() @@ -490,14 +514,13 @@ function usetool:onInput(keys) ContinueJob(adv) self:sendInputToParent("A_WAIT") else - local cur_mode=dig_modes[(mode or 0)+1] local failed=false for code,_ in pairs(keys) do --print(code) if MOVEMENT_KEYS[code] then local state={unit=adv,pos=moddedpos(adv.pos,MOVEMENT_KEYS[code]),dir=MOVEMENT_KEYS[code], - from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_action=cur_mode[4],pre_action=cur_mode[5],job_type=cur_mode[2],screen=self} + from_pos={x=adv.pos.x,y=adv.pos.y, z=adv.pos.z},post_action=cur_mode[4],pre_actions=cur_mode[5],job_type=cur_mode[2],screen=self} if code=="SELECT" then if df.global.cursor.x~=-30000 then state.pos={x=df.global.cursor.x,y=df.global.cursor.y,z=df.global.cursor.z} @@ -541,7 +564,6 @@ function usetool:onInput(keys) end end end - end end usetool():show() \ No newline at end of file From f8dea0e9f95c9d763a03b0b672495b3f9a9a6196 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 29 Nov 2012 18:19:53 -0600 Subject: [PATCH 249/472] Autofarm: only try to plant things that have seeds. --- scripts/autofarm.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 098466745..18f5a9aeb 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -18,10 +18,11 @@ class AutoFarm end def is_plantable (plant) + has_seed = plant.flags[:SEED] season = df.cur_season harvest = df.cur_season_tick + plant.growdur * 10 will_finish = harvest < 10080 - can_plant = plant.flags[season] + can_plant = has_seed && plant.flags[season] can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) can_plant end From 2cb594ba8990e062e7f353d38811b218f631866b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Nov 2012 14:48:05 +0400 Subject: [PATCH 250/472] Tweak the workflow lua api: include history in output of listConstraints. --- plugins/lua/workflow.lua | 3 +- plugins/workflow.cpp | 98 +++++++++++++++++++++++++--------------- 2 files changed, 63 insertions(+), 38 deletions(-) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index 19ca0a84a..b3bcf3d08 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -8,10 +8,11 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) - * listConstraints([job]) -> {...} + * listConstraints([job[,with_history]]) -> {{...},...} * findConstraint(token) -> {...} or nil * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false + * getCountHistory(token) -> {{...},...} or nil --]] diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 05fdca55b..12174af5c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -291,6 +291,15 @@ int ProtectedJob::cur_tick_idx = 0; typedef std::map, bool> TMaterialCache; +static const size_t MAX_HISTORY_SIZE = 28; + +enum HistoryItem { + HIST_COUNT = 0, + HIST_AMOUNT, + HIST_INUSE_COUNT, + HIST_INUSE_AMOUNT +}; + struct ItemConstraint { PersistentDataItem config; PersistentDataItem history; @@ -360,36 +369,29 @@ public: size_t history_size() { return history.data_size() / hist_entry_size; } - size_t history_base(int idx) { + int history_value(int idx, HistoryItem item) { size_t hsize = history_size(); - return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size; - } - int history_count(int idx) { - return history.get_int28(history_base(idx) + 0*int28_size); - } - int history_amount(int idx) { - return history.get_int28(history_base(idx) + 1*int28_size); - } - int history_inuse_count(int idx) { - return history.get_int28(history_base(idx) + 2*int28_size); - } - int history_inuse_amount(int idx) { - return history.get_int28(history_base(idx) + 3*int28_size); + size_t base = ((history.ival(0)+1+idx) % hsize) * hist_entry_size; + return history.get_int28(base + item*int28_size); } + int history_count(int idx) { return history_value(idx, HIST_COUNT); } + int history_amount(int idx) { return history_value(idx, HIST_AMOUNT); } + int history_inuse_count(int idx) { return history_value(idx, HIST_INUSE_COUNT); } + int history_inuse_amount(int idx) { return history_value(idx, HIST_INUSE_AMOUNT); } void updateHistory() { size_t buffer_size = history_size(); - if (buffer_size < 28) - history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size); + if (buffer_size < MAX_HISTORY_SIZE && size_t(history.ival(0)+1) == buffer_size) + history.ensure_data(hist_entry_size*++buffer_size); history.ival(0) = (history.ival(0)+1) % buffer_size; size_t base = history.ival(0) * hist_entry_size; - history.set_int28(base + 0*int28_size, item_count); - history.set_int28(base + 1*int28_size, item_amount); - history.set_int28(base + 2*int28_size, item_inuse_count); - history.set_int28(base + 3*int28_size, item_inuse_amount); + history.set_int28(base + HIST_COUNT*int28_size, item_count); + history.set_int28(base + HIST_AMOUNT*int28_size, item_amount); + history.set_int28(base + HIST_INUSE_COUNT*int28_size, item_inuse_count); + history.set_int28(base + HIST_INUSE_AMOUNT*int28_size, item_inuse_amount); } }; @@ -1384,6 +1386,25 @@ static void setEnabled(color_ostream &out, bool enable) } } +static void push_count_history(lua_State *L, ItemConstraint *icv) +{ + size_t hsize = icv->history_size(); + + lua_createtable(L, hsize, 0); + + for (size_t i = 0; i < hsize; i++) + { + lua_createtable(L, 0, 4); + + Lua::SetField(L, icv->history_amount(i), -1, "cur_amount"); + Lua::SetField(L, icv->history_count(i), -1, "cur_count"); + Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount"); + Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count"); + + lua_rawseti(L, -2, i+1); + } +} + static void push_constraint(lua_State *L, ItemConstraint *cv) { lua_newtable(L); @@ -1430,19 +1451,31 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) lua_newtable(L); + bool resumed = false, want_resumed = false; + for (size_t i = 0, j = 0; i < cv->jobs.size(); i++) { if (!cv->jobs[i]->isLive()) continue; Lua::PushDFObject(L, cv->jobs[i]->actual_job); lua_rawseti(L, -2, ++j); + + if (cv->jobs[i]->want_resumed) { + want_resumed = true; + resumed = resumed || cv->jobs[i]->isActuallyResumed(); + } } lua_setfield(L, ctable, "jobs"); + + if (want_resumed && !resumed) + Lua::SetField(L, true, ctable, "is_delayed"); } static int listConstraints(lua_State *L) { + lua_settop(L, 2); auto job = Lua::CheckDFObject(L, 1); + bool with_history = lua_toboolean(L, 2); lua_pushnil(L); @@ -1467,6 +1500,13 @@ static int listConstraints(lua_State *L) for (size_t i = 0; i < vec.size(); i++) { push_constraint(L, vec[i]); + + if (with_history) + { + push_count_history(L, vec[i]); + lua_setfield(L, -2, "history"); + } + lua_rawseti(L, -2, i+1); } @@ -1525,23 +1565,7 @@ static int getCountHistory(lua_State *L) ItemConstraint *icv = get_constraint(out, token, NULL, false); if (icv) - { - size_t hsize = icv->history_size(); - - lua_createtable(L, hsize, 0); - - for (int i = hsize-1; i >= 0; i--) - { - lua_createtable(L, 0, 4); - - Lua::SetField(L, icv->history_amount(i), -1, "cur_amount"); - Lua::SetField(L, icv->history_count(i), -1, "cur_count"); - Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount"); - Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count"); - - lua_rawseti(L, -2, hsize-i); // reverse order - } - } + push_count_history(L, icv); else lua_pushnil(L); From 9ed7a562025aa036ea144f24950db02e94a58e85 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 30 Nov 2012 15:50:35 +0100 Subject: [PATCH 251/472] deathcause: handle non-dead units --- scripts/deathcause.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/deathcause.rb b/scripts/deathcause.rb index 0ed54d81a..ab3e44a39 100644 --- a/scripts/deathcause.rb +++ b/scripts/deathcause.rb @@ -25,12 +25,19 @@ else puts "Not a historical figure, cannot death find info" else events = df.world.history.events + found = false (0...events.length).reverse_each { |i| - if events[i].kind_of?(DFHack::HistoryEventHistFigureDiedst) and events[i].victim_hf == hf - display_death_event(events[i]) + e = events[i] + if e.kind_of?(DFHack::HistoryEventHistFigureDiedst) and e.victim_hf == hf + display_death_event(e) + found = true break end } + if not found + u = item.unit_tg + puts "#{u.name} is not dead yet !" if u and not u.flags1.dead + end end end From 0bfe006016d76206c9a05f4c8c0d5aede3dd748e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Nov 2012 19:10:17 +0400 Subject: [PATCH 252/472] Try to reimplement the inventory monitor by falconne in lua. For no other reason than to provide a complete example of lua interface for a native plugin :) TODO: paint the graph in the right pane. --- Lua API.rst | 11 + library/lua/gui.lua | 10 +- library/lua/gui/widgets.lua | 41 +++- scripts/gui/workflow.lua | 410 +++++++++++++++++++++++++++++++++--- 4 files changed, 430 insertions(+), 42 deletions(-) diff --git a/Lua API.rst b/Lua API.rst index d42a348e4..714a41bfb 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2710,6 +2710,16 @@ containing newlines, or a table with the following possible fields: Specifies a pen to paint as one tile before the main part of the token. +* ``token.width = ...`` + + If specified either as a value or a callback, the text field is padded + or truncated to the specified number. + +* ``token.pad_char = '?'`` + + If specified together with ``width``, the padding area is filled with + this character instead of just being skipped over. + * ``token.key = '...'`` Specifies the keycode associated with the token. The string description @@ -2842,6 +2852,7 @@ In addition to passing through all attributes supported by List, it supports: :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. +:edit_below: If true, the edit field is placed below the list instead of above. :not_found_label: Specifies the text of the label shown when no items match the filter. The list choices may include the following attributes: diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 2145cfad1..99bf9263c 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -112,10 +112,14 @@ function inset_frame(rect, inset, gap) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) end -function compute_frame_body(wavail, havail, spec, inset, gap) +function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame) gap = gap or 0 local l,t,r,b = parse_inset(inset) - local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) + local xgap,ygap = 0,0 + if inner_frame then + xgap,ygap = gap*2+l+r, gap*2+t+b + end + local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap) local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) return rect, body end @@ -623,7 +627,7 @@ end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height local fw, fh = self:getWantedFrameSize(parent_rect) - return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) + return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true) end function FramedScreen:onRenderFrame(dc, rect) diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 145300c59..ebbffbbbd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -60,6 +60,7 @@ Panel = defclass(Panel, Widget) Panel.ATTRS { on_render = DEFAULT_NIL, + on_layout = DEFAULT_NIL, } function Panel:init(args) @@ -70,6 +71,10 @@ function Panel:onRenderBody(dc) if self.on_render then self.on_render(dc) end end +function Panel:postComputeFrame(body) + if self.on_layout then self.on_layout(body) end +end + ----------- -- Pages -- ----------- @@ -242,7 +247,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end if token.text or token.key then - local text = getval(token.text) or '' + local text = ''..(getval(token.text) or '') local keypen if dc then @@ -256,7 +261,23 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end end - x = x + #text + local width = getval(token.width) + local padstr + if width then + x = x + width + if #text > width then + text = string.sub(text,1,width) + else + if token.pad_char then + padstr = string.rep(token.pad_char,width-#text) + end + if dc and token.rjustify then + if padstr then dc:string(padstr) else dc:advance(width-#text) end + end + end + else + x = x + #text + end if token.key then local keystr = gui.getKeyDisplay(token.key) @@ -281,6 +302,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) dc:string(text) end end + + if width and dc and not token.rjustify then + if padstr then dc:string(padstr) else dc:advance(width-#text) end + end end token.x2 = x @@ -591,10 +616,14 @@ end FilteredList = defclass(FilteredList, Widget) +FilteredList.ATTRS { + edit_below = false, +} + function FilteredList:init(info) self.edit = EditField{ text_pen = info.edit_pen or info.cursor_pen, - frame = { l = info.icon_width, t = 0 }, + frame = { l = info.icon_width, t = 0, h = 1 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), } @@ -608,6 +637,10 @@ function FilteredList:init(info) scroll_keys = info.scroll_keys, icon_width = info.icon_width, } + if self.edit_below then + self.edit.frame = { l = info.icon_width, b = 0, h = 1 } + self.list.frame = { t = 0, b = 2 } + end if info.on_select then self.list.on_select = function() return info.on_select(self:getSelected()) @@ -627,7 +660,7 @@ function FilteredList:init(info) visible = false, text = info.not_found_label or 'No matches', text_pen = COLOR_LIGHTRED, - frame = { l = info.icon_width, t = 2 }, + frame = { l = info.icon_width, t = self.list.frame.t }, } self:addviews{ self.edit, self.list, self.not_found } self:setChoices(info.choices, info.selected) diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index a387e64b9..9a45f6554 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -66,20 +66,64 @@ function is_caste_mat(iobj) end function describe_material(iobj) - local matline = 'any material' + local matflags = utils.list_bitfield_flags(iobj.mat_mask) + if #matflags > 0 then + matflags = 'any '..table.concat(matflags, '/') + else + matflags = nil + end + if is_caste_mat(iobj) then - matline = 'no material' + return 'no material' elseif (iobj.mat_type or -1) >= 0 then local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + local matline if info then matline = info:toString() else matline = iobj.mat_type..':'..iobj.mat_index end + return matline, matflags + else + return matflags or 'any material' + end +end + +function current_stock(iobj) + if iobj.goal_by_count then + return iobj.cur_count + else + return iobj.cur_amount end - return matline end +function if_by_count(iobj,bc,ba) + if iobj.goal_by_count then + return bc + else + return ba + end +end + +function compute_trend(history,field) + local count = #history + if count == 0 then + return 0 + end + local sumX,sumY,sumXY,sumXX = 0,0,0,0 + for i,v in ipairs(history) do + sumX = sumX + i + sumY = sumY + v[field] + sumXY = sumXY + i*v[field] + sumXX = sumXX + i*i + end + return (count * sumXY - sumX * sumY) / (count * sumXX - sumX * sumX) +end + +------------------------ +-- RANGE EDITOR GROUP -- +------------------------ + local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } RangeEditor = defclass(RangeEditor, widgets.Label) @@ -162,6 +206,10 @@ function RangeEditor:onIncRange(field, delta) self.save_cb(cons) end +--------------------------- +-- NEW CONSTRAINT DIALOG -- +--------------------------- + NewConstraint = defclass(NewConstraint, gui.FramedScreen) NewConstraint.focus_path = 'workflow/new' @@ -177,7 +225,7 @@ NewConstraint.ATTRS { } function NewConstraint:init(args) - self.constraint = args.constraint or {} + self.constraint = args.constraint or { item_type = -1 } rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false }) local matlist = {} @@ -202,8 +250,16 @@ function NewConstraint:init(args) frame = { l = 1, t = 2, w = 26 }, text = { 'Type: ', - { pen = COLOR_LIGHTCYAN, - text = function() return describe_item_type(self.constraint) end }, + { pen = function() + if self:isValid() then return COLOR_LIGHTCYAN else return COLOR_LIGHTRED end + end, + text = function() + if self:isValid() then + return describe_item_type(self.constraint) + else + return 'item not set' + end + end }, NEWLINE, ' ', { key = 'CUSTOM_T', text = ': Select, ', on_activate = self:callback('chooseType') }, @@ -277,6 +333,7 @@ function NewConstraint:init(args) { key = 'LEAVESCREEN', text = ': Cancel, ', on_activate = self:callback('dismiss') }, { key = 'MENU_CONFIRM', key_sep = ': ', + enabled = self:callback('isValid'), text = function() if self.is_existing then return 'Update' else return 'Create new' end end, @@ -295,9 +352,17 @@ function NewConstraint:postinit() self:onChange() end +function NewConstraint:isValid() + return self.constraint.item_type >= 0 +end + function NewConstraint:onChange() local token = workflow.constraintToToken(self.constraint) - local out = workflow.findConstraint(token) + local out + + if self:isValid() then + out = workflow.findConstraint(token) + end if out then self.constraint = out @@ -390,6 +455,288 @@ function NewConstraint:onRangeChange() cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) end +------------------------------ +-- GLOBAL CONSTRAINT SCREEN -- +------------------------------ + +ConstraintList = defclass(ConstraintList, gui.FramedScreen) + +ConstraintList.focus_path = 'workflow/list' + +ConstraintList.ATTRS { + frame_title = 'Workflow Status', + frame_inset = 0, + frame_background = COLOR_BLACK, + frame_style = gui.BOUNDARY_FRAME, +} + +function ConstraintList:init(args) + local fwidth_cb = self:cb_getfield('fwidth') + + self.fwidth = 20 + self.sort_by_severity = false + + self:addviews{ + widgets.Panel{ + frame = { w = 31, r = 0, h = 6, t = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + enabled = self:callback('isAnySelected'), + text = { + { text = function() + local cur = self:getCurConstraint() + if cur then + return string.format( + 'Currently %d (%d in use)', + current_stock(cur), + if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) + ) + else + return 'No constraint selected' + end + end } + } + }, + RangeEditor{ + frame = { l = 0, t = 2 }, + enabled = self:callback('isAnySelected'), + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), + }, + } + }, + widgets.Widget{ + active = false, + frame = { w = 1, r = 31 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Widget{ + active = false, + frame = { w = 31, r = 0, h = 1, t = 6 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Panel{ + frame = { l = 0, r = 32 }, + frame_inset = 1, + on_layout = function(body) + self.fwidth = body.width - (12+1+1+7+1+1+1+7) + end, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + text_pen = COLOR_CYAN, + text = { + { text = 'Item', width = 12 }, ' ', + { text = 'Material etc', width = fwidth_cb }, ' ', + { text = 'Stock / Limit' }, + } + }, + widgets.FilteredList{ + view_id = 'list', + frame = { t = 2, b = 2 }, + edit_below = true, + not_found_label = 'No matching constraints', + edit_pen = COLOR_LIGHTCYAN, + text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, + cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + }, + widgets.Label{ + frame = { b = 0, h = 1 }, + text = { + { key = 'CUSTOM_SHIFT_A', text = ': Add', + on_activate = self:callback('onNewConstraint') }, ', ', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', + on_activate = self:callback('onDeleteConstraint') }, ', ', + { key = 'CUSTOM_SHIFT_O', text = ': Severity Order', + on_activate = self:callback('onSwitchSort'), + pen = function() + if self.sort_by_severity then + return COLOR_LIGHTCYAN + else + return COLOR_WHITE + end + end }, ', ', + { key = 'CUSTOM_SHIFT_S', text = ': Search', + on_activate = function() + self.subviews.list.edit.active = not self.subviews.list.edit.active + end, + pen = function() + if self.subviews.list.edit.active then + return COLOR_LIGHTCYAN + else + return COLOR_WHITE + end + end } + } + } + } + }, + } + + self.subviews.list.edit.active = false + + self:initListChoices() +end + +function stock_trend_color(cons) + local stock = current_stock(cons) + if stock >= cons.goal_value - cons.goal_gap then + return COLOR_LIGHTGREEN, 0 + elseif stock <= cons.goal_gap then + return COLOR_LIGHTRED, 4 + elseif stock >= cons.goal_value - 2*cons.goal_gap then + return COLOR_GREEN, 1 + elseif stock <= 2*cons.goal_gap then + return COLOR_RED, 3 + else + local trend = if_by_count(cons, cons.trend_count, cons.trend_amount) + if trend > 0.3 then + return COLOR_GREEN, 1 + elseif trend < -0.3 then + return COLOR_RED, 3 + else + return COLOR_GREY, 2 + end + end +end + +function ConstraintList:initListChoices(clist, sel_token) + clist = clist or workflow.listConstraints(nil, true) + + local fwidth_cb = self:cb_getfield('fwidth') + local choices = {} + + for i,cons in ipairs(clist) do + cons.trend_count = compute_trend(cons.history, 'cur_count') + cons.trend_amount = compute_trend(cons.history, 'cur_amount') + + local itemstr = describe_item_type(cons) + local matstr,matflagstr = describe_material(cons) + if matflagstr then + matstr = matflagstr .. ' ' .. matstr + end + + if cons.min_quality > 0 or cons.is_local then + local lst = {} + if cons.is_local then + table.insert(lst, 'local') + end + if cons.min_quality > 0 then + table.insert(lst, string.lower(df.item_quality[cons.min_quality])) + end + matstr = matstr .. ' ('..table.concat(lst,',')..')' + end + + local goal_color = COLOR_GREY + if #cons.jobs == 0 then + goal_color = COLOR_RED + elseif cons.is_delayed then + goal_color = COLOR_YELLOW + end + + table.insert(choices, { + text = { + { text = itemstr, width = 12, pad_char = ' ' }, ' ', + { text = matstr, width = fwidth_cb, pad_char = ' ' }, ' ', + { text = curry(current_stock,cons), width = 7, rjustify = true, + pen = function() return { fg = stock_trend_color(cons) } end }, + { text = curry(if_by_count,cons,'S','I'), gap = 1, + pen = { fg = COLOR_GREY } }, + { text = function() return cons.goal_value end, gap = 1, + pen = { fg = goal_color } } + }, + severity = select(2, stock_trend_color(cons)), + search_key = itemstr .. ' | ' .. matstr, + token = cons.token, + obj = cons + }) + end + + self:setChoices(choices, sel_token) +end + +function ConstraintList:isAnySelected() + return self.subviews.list:getSelected() ~= nil +end + +function ConstraintList:getCurConstraint() + local selidx,selobj = self.subviews.list:getSelected() + if selobj then return selobj.obj end +end + +function ConstraintList:onSwitchSort() + self.sort_by_severity = not self.sort_by_severity + self:setChoices(self.subviews.list:getChoices()) +end + +function ConstraintList:setChoices(choices, sel_token) + if self.sort_by_severity then + table.sort(choices, function(a,b) + return a.severity > b.severity + or (a.severity == b.severity and + current_stock(a.obj)/a.obj.goal_value < current_stock(b.obj)/b.obj.goal_value) + end) + else + table.sort(choices, function(a,b) return a.search_key < b.search_key end) + end + + local selidx = nil + if sel_token then + selidx = utils.linear_index(choices, sel_token, 'token') + end + + local list = self.subviews.list + local filter = list:getFilter() + + list:setChoices(choices, selidx) + + if filter ~= '' then + list:setFilter(filter, selidx) + + if selidx and list:getSelected() ~= selidx then + list:setFilter('', selidx) + end + end +end + +function ConstraintList:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + else + ConstraintList.super.onInput(self, keys) + end +end + +function ConstraintList:onNewConstraint() + NewConstraint{ + on_submit = self:callback('saveConstraint') + }:show() +end + +function ConstraintList:saveConstraint(cons) + local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices(nil, out.token) +end + +function ConstraintList:onDeleteConstraint() + local cons = self:getCurConstraint() + dlg.showYesNoPrompt( + 'Delete Constraint', + 'Really delete the current constraint?', + COLOR_YELLOW, + function() + workflow.deleteConstraint(cons.token) + self:initListChoices() + end + ) +end + +------------------------------- +-- WORKSHOP JOB INFO OVERLAY -- +------------------------------- + JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) JobConstraints.focus_path = 'workflow/job' @@ -480,24 +827,12 @@ function JobConstraints:initListChoices(clist, sel_token) end itemstr = itemstr .. ' ('..table.concat(lst,',')..')' end - local matstr = describe_material(cons) - local matflagstr = '' - local matflags = utils.list_bitfield_flags(cons.mat_mask) - if #matflags > 0 then - matflags[1] = 'any '..matflags[1] - if matstr == 'any material' then - matstr = table.concat(matflags, ', ') - matflags = {} - end - end - if #matflags > 0 then - matflagstr = table.concat(matflags, ', ') - end + local matstr,matflagstr = describe_material(cons) table.insert(choices, { text = { goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, - ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr + ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', (matflagstr or '') }, token = cons.token, obj = cons @@ -593,20 +928,25 @@ function JobConstraints:onInput(keys) end end -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") -end +local args = {...} -local job = dfhack.gui.getSelectedJob() +if args[1] == 'list' then + check_enabled(function() ConstraintList{}:show() end) +else + if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") + end -check_enabled(function() - check_repeat(job, function() - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() - end) -end) + local job = dfhack.gui.getSelectedJob() + check_enabled(function() + check_repeat(job, function() + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() + end) + end) +end From 9e30bf0dffcffc428556d49dd4117bc5e94b6a5f Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 11:05:37 -0600 Subject: [PATCH 253/472] Autofarm: use player's actual seed stocks as basis for plantable seeds, instead of player entity's hypothetically plantable seeds. Avoids designating a plot for planting with seeds the player doesn't have. --- scripts/autofarm.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 18f5a9aeb..9d39bc29a 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -29,7 +29,18 @@ class AutoFarm def find_plantable_plants plantable = {} - for i in 0..df.ui.tasks.known_plants.length-1 + counts = {} + + df.world.items.other[:SEEDS].each { |i| + if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && + !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && + !i.flags.trader && !i.flags.in_building && !i.flags.construction && + !i.flags.artifact1 && plantable.has_key? (i.mat_index)) + counts[i.mat_index] = counts[i.mat_index] + i.stack_size + end + } + + counts.keys.each { |i| if df.ui.tasks.known_plants[i] plant = df.world.raws.plants.all[i] if is_plantable(plant) @@ -37,7 +48,8 @@ class AutoFarm plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0) end end - end + } + return plantable end From eecb6048002187202bff3ef2991d88d62ac6b37a Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 11:23:58 -0600 Subject: [PATCH 254/472] Sync submodules --- library/xml | 2 +- plugins/stonesense | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index e06438924..42e26b368 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e06438924929a8ecab751c0c233dad5767e91f7e +Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc diff --git a/plugins/stonesense b/plugins/stonesense index 75df76626..cb97cf308 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 75df766263b23182820a1e07b330e64f87d5c9b7 +Subproject commit cb97cf308c6e09638c0de94894473c9bd0f561fd From 63e4c16aa17a1cd7b677468fb55acb0981a83d4e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:22:29 -0600 Subject: [PATCH 255/472] Sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 42e26b368..22b01b80a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc +Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 From 384a667e9740d78a1895625e400d53d06b7f0c28 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:22:58 -0600 Subject: [PATCH 256/472] Assorted progress on new autolabor. Still lots of issues. --- plugins/autolabor.cpp | 189 ++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 110 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 45ddda2e9..89115366a 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -8,6 +8,9 @@ #include #include +#include +#include +#include #include "modules/Units.h" #include "modules/World.h" @@ -721,7 +724,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) static df::building* get_building_from_job(df::job* j) { - for (auto r = j->references.begin(); r != j->references.end(); r++) + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) { if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) { @@ -762,12 +765,17 @@ static df::job_skill workshop_build_labor[] = /* Tool */ (df::job_skill) -1 }; -static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labor &labor) +static df::unit_labor find_job_labor(df::job* j) { + df::job_skill skill; + df::unit_labor labor; + + labor = df::unit_labor::NONE; switch (j->job_type) { case df::job_type::ConstructBuilding: + case df::job_type::DestroyBuilding: { df::building* bld = get_building_from_job (j); switch (bld->getType()) @@ -779,6 +787,7 @@ static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labo } } break; + case df::job_type::CustomReaction: for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) { @@ -803,6 +812,7 @@ static void find_job_skill_labor(df::job* j, df::job_skill &skill, df::unit_labo { } + return labor; } class AutoLaborManager { @@ -840,7 +850,7 @@ private: int pick_count; int axe_count; - std::vector jobs; + std::map labor_needed; std::vector dwarf_info; std::deque idle_dwarfs; @@ -930,7 +940,7 @@ private: #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); #undef F for (int i = 0; i < world->items.all.size(); ++i) @@ -957,9 +967,6 @@ private: void collect_job_list() { - std::vector repeating_jobs; - - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) { df::job* j = jll->item; @@ -971,24 +978,21 @@ private: int worker = -1; - for (int r = 0; r < j->references.size(); ++r) - if (j->references[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->references[r]))->unit_id; + for (int r = 0; r < j->general_refs.size(); ++r) + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; if (worker != -1) continue; - if (j->flags.bits.repeat) - repeating_jobs.push_back(j); - else - jobs.push_back(j); - } + df::unit_labor labor = find_job_labor (j); - if (print_debug) - out.print("%d repeating jobs, %d nonrepeating jobs\n", repeating_jobs.size(), jobs.size()); + if (print_debug) + out.print ("Job requiring labor %d found\n", labor); - for (int i = 0; i < repeating_jobs.size(); i++) - jobs.push_back(repeating_jobs[i]); + if (labor != df::unit_labor::NONE) + labor_needed[labor]++; + } } @@ -1150,37 +1154,6 @@ private: } } - void assign_one_dwarf (df::job_skill skill, df::unit_labor labor) - { - if (labor == df::unit_labor::NONE) - return; - - std::deque::iterator bestdwarf = idle_dwarfs.begin(); - - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; - - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) - { - dwarf_info_t* d = (*k); - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } - } - } - - if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); - (*bestdwarf)->set_labor(labor); - - idle_dwarfs.erase(bestdwarf); - } - public: void process() { @@ -1196,6 +1169,8 @@ public: count_tools(); + // create job entries for designation + // collect current job list collect_job_list(); @@ -1204,82 +1179,76 @@ public: collect_dwarf_list(); - // assign jobs requiring skill to idle dwarfs + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count + // and remove the idle dwarf from the idle list - int jobs_to_assign = jobs.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); - - if (print_debug) - out.print ("Assign idle (skilled): %d\n", jobs_to_assign); - - std::vector jobs2; - - for (int i = 0; i < jobs_to_assign; ++i) + for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) { - df::job* j = jobs[i]; - - df::job_skill skill; - df::unit_labor labor; - - find_job_skill_labor(j, skill, labor); - - if(print_debug) - out.print("Job skill = %d labor = %d\n", skill, labor); - - if (labor == -1) - out.print("Invalid labor for job (%d)\n", j->job_type); - - if (skill == df::job_skill::NONE) - jobs2.push_back(j); - else - assign_one_dwarf(skill, labor); + FOR_ENUM_ITEMS(unit_labor, l) + { + if ((*i)->dwarf->status.labors[l]) + if (labor_needed[l] > 0) + { + if (print_debug) + out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + labor_needed[l]--; + idle_dwarfs.erase(i); // remove from idle list + break; + } else { + (*i)->dwarf->status.labors[l] = false; + } + } } - // assign mining jobs to idle dwarfs - - int dig_max = dig_count; - if (dig_max > pick_count) - dig_max = pick_count; - - jobs_to_assign = dig_max - jobs2.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); - - if (print_debug) - out.print ("Assign idle (mining): %d\n", jobs_to_assign); - - for (int i = 0; i < jobs_to_assign; ++i) + priority_queue> pq; + + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) { - df::job_skill skill = df::job_skill::MINING; - df::unit_labor labor = df::unit_labor::MINE; - - assign_one_dwarf(skill, labor); + if (i->second > 0) + pq.push(make_pair(i->second, i->first)); } + + while (!idle_dwarfs.empty() && !pq.empty()) + { + df::unit_labor labor = pq.top().second; + int remaining = pq.top().first; + df::job_skill skill = labor_to_skill[labor]; - // now assign unskilled jobs to idle dwarfs + if (print_debug) + out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - jobs_to_assign = jobs2.size(); - if (jobs_to_assign > idle_dwarfs.size()) - jobs_to_assign = idle_dwarfs.size(); + std::deque::iterator bestdwarf = idle_dwarfs.begin(); - if (print_debug) - out.print ("Assign idle (unskilled): %d\n", jobs_to_assign); + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; - for (int i = 0; i < jobs_to_assign; ++i) - { - df::job* j = jobs2[i]; + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); - df::job_skill skill; - df::unit_labor labor; + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } - find_job_skill_labor(j, skill, labor); - assign_one_dwarf(skill, labor); + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); + + idle_dwarfs.erase(bestdwarf); + pq.pop(); + if (--remaining) + pq.push(make_pair(remaining, labor)); } print_debug = 0; - } + } }; From 021d08970922f897b8d37c117524418b91c62041 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:25:19 -0600 Subject: [PATCH 257/472] sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 42e26b368..22b01b80a 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc +Subproject commit 22b01b80ad1f0e82c609dec56f09be1a46788921 From e5f509a994d7c071d84595bd8d01f5ce78196dca Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 20:51:40 -0600 Subject: [PATCH 258/472] autofarm: sync with changes to structures for df-item.xml --- scripts/autofarm.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 3839a7ca9..6a7635b90 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -35,7 +35,7 @@ class AutoFarm if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact1 && plantable.has_key? (i.mat_index)) + !i.flags.artifact) counts[i.mat_index] = counts[i.mat_index] + i.stack_size end } From 05dce0d2f173b452faceef23b5ef33b33a07f6bd Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Fri, 30 Nov 2012 21:24:18 -0600 Subject: [PATCH 259/472] Fix inadvertently prematurely terminated block comment. --- plugins/lua/workflow.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index b3bcf3d08..563e83297 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -8,7 +8,7 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) - * listConstraints([job[,with_history]]) -> {{...},...} + * listConstraints([job[,with_history] ]) -> {{...},...} * findConstraint(token) -> {...} or nil * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false From afb6d8ef799cef5ad493554a335a8290fbcb93cf Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sat, 1 Dec 2012 02:26:06 -0600 Subject: [PATCH 260/472] Autolabor: improve (hopefully) the functionality for mapping jobs to labors. Still some gaps to fill, but not many. --- plugins/autolabor.cpp | 1473 +++++++++++++++++++++++++++-------------- 1 file changed, 980 insertions(+), 493 deletions(-) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 89115366a..5736480fb 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -45,7 +45,11 @@ #include #include #include +#include +#include #include +#include +#include #include @@ -515,27 +519,120 @@ static const int responsibility_penalties[] = { struct dwarf_info_t { - df::unit* dwarf; + df::unit* dwarf; dwarf_state state; - bool clear_all; + bool clear_all; - bool has_axe; - bool has_pick; - bool has_crossbow; + bool has_axe; + bool has_pick; + bool has_crossbow; - dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} + dwarf_info_t(df::unit* dw) : dwarf(dw), clear_all(0), has_axe(0), has_pick(0), has_crossbow(0), state(OTHER) {} - void set_labor(df::unit_labor labor) - { - dwarf->status.labors[labor] = true; - if ((labor == df::unit_labor::MINE && !has_pick) || - (labor == df::unit_labor::CUTWOOD && !has_axe) || - (labor == df::unit_labor::HUNT && !has_crossbow)) - dwarf->military.pickup_flags.bits.update = 1; - } + void set_labor(df::unit_labor labor) + { + dwarf->status.labors[labor] = true; + if ((labor == df::unit_labor::MINE && !has_pick) || + (labor == df::unit_labor::CUTWOOD && !has_axe) || + (labor == df::unit_labor::HUNT && !has_crossbow)) + dwarf->military.pickup_flags.bits.update = 1; + } }; +static df::unit_labor hauling_labor_map[] = + { + df::unit_labor::HAUL_ITEM, /* BAR */ + df::unit_labor::HAUL_ITEM, /* SMALLGEM */ + df::unit_labor::HAUL_ITEM, /* BLOCKS */ + df::unit_labor::HAUL_ITEM, /* ROUGH */ + df::unit_labor::HAUL_STONE, /* BOULDER */ + df::unit_labor::HAUL_WOOD, /* WOOD */ + df::unit_labor::HAUL_FURNITURE, /* DOOR */ + df::unit_labor::HAUL_FURNITURE, /* FLOODGATE */ + df::unit_labor::HAUL_FURNITURE, /* BED */ + df::unit_labor::HAUL_FURNITURE, /* CHAIR */ + df::unit_labor::HAUL_ITEM, /* CHAIN */ + df::unit_labor::HAUL_ITEM, /* FLASK */ + df::unit_labor::HAUL_ITEM, /* GOBLET */ + df::unit_labor::HAUL_ITEM, /* INSTRUMENT */ + df::unit_labor::HAUL_ITEM, /* TOY */ + df::unit_labor::HAUL_FURNITURE, /* WINDOW */ + df::unit_labor::HAUL_ANIMAL, /* CAGE */ + df::unit_labor::HAUL_ITEM, /* BARREL */ + df::unit_labor::HAUL_ITEM, /* BUCKET */ + df::unit_labor::HAUL_ANIMAL, /* ANIMALTRAP */ + df::unit_labor::HAUL_FURNITURE, /* TABLE */ + df::unit_labor::HAUL_FURNITURE, /* COFFIN */ + df::unit_labor::HAUL_FURNITURE, /* STATUE */ + df::unit_labor::HAUL_BODY, /* CORPSE */ + df::unit_labor::HAUL_ITEM, /* WEAPON */ + df::unit_labor::HAUL_ITEM, /* ARMOR */ + df::unit_labor::HAUL_ITEM, /* SHOES */ + df::unit_labor::HAUL_ITEM, /* SHIELD */ + df::unit_labor::HAUL_ITEM, /* HELM */ + df::unit_labor::HAUL_ITEM, /* GLOVES */ + df::unit_labor::HAUL_FURNITURE, /* BOX */ + df::unit_labor::HAUL_ITEM, /* BIN */ + df::unit_labor::HAUL_FURNITURE, /* ARMORSTAND */ + df::unit_labor::HAUL_FURNITURE, /* WEAPONRACK */ + df::unit_labor::HAUL_FURNITURE, /* CABINET */ + df::unit_labor::HAUL_ITEM, /* FIGURINE */ + df::unit_labor::HAUL_ITEM, /* AMULET */ + df::unit_labor::HAUL_ITEM, /* SCEPTER */ + df::unit_labor::HAUL_ITEM, /* AMMO */ + df::unit_labor::HAUL_ITEM, /* CROWN */ + df::unit_labor::HAUL_ITEM, /* RING */ + df::unit_labor::HAUL_ITEM, /* EARRING */ + df::unit_labor::HAUL_ITEM, /* BRACELET */ + df::unit_labor::HAUL_ITEM, /* GEM */ + df::unit_labor::HAUL_FURNITURE, /* ANVIL */ + df::unit_labor::HAUL_BODY, /* CORPSEPIECE */ + df::unit_labor::HAUL_REFUSE, /* REMAINS */ + df::unit_labor::HAUL_FOOD, /* MEAT */ + df::unit_labor::HAUL_FOOD, /* FISH */ + df::unit_labor::HAUL_FOOD, /* FISH_RAW */ + df::unit_labor::HAUL_REFUSE, /* VERMIN */ + df::unit_labor::HAUL_ITEM, /* PET */ + df::unit_labor::HAUL_FOOD, /* SEEDS */ + df::unit_labor::HAUL_FOOD, /* PLANT */ + df::unit_labor::HAUL_ITEM, /* SKIN_TANNED */ + df::unit_labor::HAUL_FOOD, /* LEAVES */ + df::unit_labor::HAUL_ITEM, /* THREAD */ + df::unit_labor::HAUL_ITEM, /* CLOTH */ + df::unit_labor::HAUL_ITEM, /* TOTEM */ + df::unit_labor::HAUL_ITEM, /* PANTS */ + df::unit_labor::HAUL_ITEM, /* BACKPACK */ + df::unit_labor::HAUL_ITEM, /* QUIVER */ + df::unit_labor::HAUL_FURNITURE, /* CATAPULTPARTS */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAPARTS */ + df::unit_labor::HAUL_FURNITURE, /* SIEGEAMMO */ + df::unit_labor::HAUL_FURNITURE, /* BALLISTAARROWHEAD */ + df::unit_labor::HAUL_FURNITURE, /* TRAPPARTS */ + df::unit_labor::HAUL_FURNITURE, /* TRAPCOMP */ + df::unit_labor::HAUL_FOOD, /* DRINK */ + df::unit_labor::HAUL_FOOD, /* POWDER_MISC */ + df::unit_labor::HAUL_FOOD, /* CHEESE */ + df::unit_labor::HAUL_FOOD, /* FOOD */ + df::unit_labor::HAUL_FOOD, /* LIQUID_MISC */ + df::unit_labor::HAUL_ITEM, /* COIN */ + df::unit_labor::HAUL_FOOD, /* GLOB */ + df::unit_labor::HAUL_STONE, /* ROCK */ + df::unit_labor::HAUL_FURNITURE, /* PIPE_SECTION */ + df::unit_labor::HAUL_FURNITURE, /* HATCH_COVER */ + df::unit_labor::HAUL_FURNITURE, /* GRATE */ + df::unit_labor::HAUL_FURNITURE, /* QUERN */ + df::unit_labor::HAUL_FURNITURE, /* MILLSTONE */ + df::unit_labor::HAUL_ITEM, /* SPLINT */ + df::unit_labor::HAUL_ITEM, /* CRUTCH */ + df::unit_labor::HAUL_FURNITURE, /* TRACTION_BENCH */ + df::unit_labor::HAUL_ITEM, /* ORTHOPEDIC_CAST */ + df::unit_labor::HAUL_ITEM, /* TOOL */ + df::unit_labor::HAUL_FURNITURE, /* SLAB */ + df::unit_labor::HAUL_FOOD, /* EGG */ + df::unit_labor::HAUL_ITEM, /* BOOK */ + }; + static bool isOptionEnabled(unsigned flag) { return config.isValid() && (config.ival(0) & flag) != 0; @@ -724,531 +821,921 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) static df::building* get_building_from_job(df::job* j) { - for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) - { - if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) - { - int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; - df::building* bld = binsearch_in_vector(world->buildings.all, id); - return bld; - } - } - return 0; + for (auto r = j->general_refs.begin(); r != j->general_refs.end(); r++) + { + if ((*r)->getType() == df::general_ref_type::BUILDING_HOLDER) + { + int32_t id = ((df::general_ref_building_holderst*)(*r))->building_id; + df::building* bld = binsearch_in_vector(world->buildings.all, id); + return bld; + } + } + return 0; } -static df::job_skill workshop_build_labor[] = + + +static df::unit_labor workshop_build_labor[] = +{ + /* Carpenters */ df::unit_labor::CARPENTER, + /* Farmers */ df::unit_labor::HERBALIST, + /* Masons */ df::unit_labor::MASON, + /* Craftsdwarfs */ df::unit_labor::STONE_CRAFT, + /* Jewelers */ df::unit_labor::CUT_GEM, + /* MetalsmithsForge */ df::unit_labor::METAL_CRAFT, + /* MagmaForge */ df::unit_labor::METAL_CRAFT, + /* Bowyers */ df::unit_labor::BOWYER, + /* Mechanics */ df::unit_labor::MECHANIC, + /* Siege */ df::unit_labor::SIEGECRAFT, + /* Butchers */ df::unit_labor::BUTCHER, + /* Leatherworks */ df::unit_labor::LEATHER, + /* Tanners */ df::unit_labor::TANNER, + /* Clothiers */ df::unit_labor::CLOTHESMAKER, + /* Fishery */ df::unit_labor::FISH, + /* Still */ df::unit_labor::BREWER, + /* Loom */ df::unit_labor::WEAVER, + /* Quern */ df::unit_labor::MILLER, + /* Kennels */ df::unit_labor::ANIMALTRAIN, + /* Kitchen */ df::unit_labor::COOK, + /* Ashery */ df::unit_labor::LYE_MAKING, + /* Dyers */ df::unit_labor::DYER, + /* Millstone */ df::unit_labor::MILLER, + /* Custom */ df::unit_labor::NONE, + /* Tool */ df::unit_labor::NONE +}; + +class jlfunc +{ + public: + virtual df::unit_labor get_labor(df::job* j) = 0; +}; + +class jlfunc_const : public jlfunc +{ + private: + df::unit_labor labor; + public: + df::unit_labor get_labor(df::job* j) + { + return labor; + } + jlfunc_const(df::unit_labor l) : labor(l) {}; +}; + +class jlfunc_hauling : public jlfunc { - /* Carpenters */ df::job_skill::CARPENTRY, - /* Farmers */ df::job_skill::PROCESSPLANTS, - /* Masons */ df::job_skill::MASONRY, - /* Craftsdwarfs */ df::job_skill::STONECRAFT, - /* Jewelers */ df::job_skill::CUTGEM, - /* MetalsmithsForge */ df::job_skill::METALCRAFT, - /* MagmaForge */ df::job_skill::METALCRAFT, - /* Bowyers */ df::job_skill::BOWYER, - /* Mechanics */ df::job_skill::MECHANICS, - /* Siege */ df::job_skill::SIEGECRAFT, - /* Butchers */ df::job_skill::BUTCHER, - /* Leatherworks */ df::job_skill::LEATHERWORK, - /* Tanners */ df::job_skill::TANNER, - /* Clothiers */ df::job_skill::CLOTHESMAKING, - /* Fishery */ df::job_skill::FISH, - /* Still */ df::job_skill::BREWING, - /* Loom */ df::job_skill::WEAVING, - /* Quern */ df::job_skill::MILLING, - /* Kennels */ df::job_skill::ANIMALTRAIN, - /* Kitchen */ df::job_skill::COOK, - /* Ashery */ df::job_skill::LYE_MAKING, - /* Dyers */ df::job_skill::DYER, - /* Millstone */ df::job_skill::MILLING, - /* Custom */ (df::job_skill) -1, - /* Tool */ (df::job_skill) -1 +public: + df::unit_labor get_labor(df::job* j) + { + df::item* item = j->items[0]->item; + return hauling_labor_map[item->getType()]; + } + jlfunc_hauling() {}; +}; + +class jlfunc_construct_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + switch (bld->getType()) + { + case df::building_type::Workshop: + df::building_workshopst* ws = (df::building_workshopst*) bld; + if (ws->type == df::workshop_type::Custom) + { + df::building_def* def = df::building_def::find(ws->custom_type); + return def->build_labors[0]; + } + else + return workshop_build_labor[ws->type]; + + break; + } + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_construct_bld() {} +}; + +class jlfunc_destroy_bld : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job (j); + df::building_type type = bld->getType(); + + // FIXME + return df::unit_labor::NONE; + } + jlfunc_destroy_bld() {} }; +class jlfunc_make : public jlfunc +{ +private: + df::unit_labor metaltype; +public: + df::unit_labor get_labor(df::job* j) + { + df::building* bld = get_building_from_job(j); + if (bld->getType() == df::building_type::Workshop) + { + df::workshop_type type = ((df::building_workshopst*)(bld))->type; + switch (type) + { + case df::workshop_type::Craftsdwarfs: + { + df::item_type jobitem = j->job_items[0]->item_type; + switch (jobitem) + { + case df::item_type::BOULDER: + return df::unit_labor::STONE_CRAFT; + case df::item_type::NONE: + if (j->material_category.bits.bone) + return df::unit_labor::BONE_CARVE; + else + return df::unit_labor::NONE; //FIXME + default: + return df::unit_labor::NONE; //FIXME + } + } + case df::workshop_type::Masons: + return df::unit_labor::MASON; + case df::workshop_type::Carpenters: + return df::unit_labor::CARPENTER; + case df::workshop_type::Leatherworks: + return df::unit_labor::LEATHER; + case df::workshop_type::Clothiers: + return df::unit_labor::CLOTHESMAKER; + case df::workshop_type::MagmaForge: + case df::workshop_type::MetalsmithsForge: + return metaltype; + default: + return df::unit_labor::NONE; // FIXME + } + } + else if (bld->getType() == df::building_type::Furnace) + { + df::furnace_type type = ((df::building_furnacest*)(bld))->type; + switch (type) + { + case df::furnace_type::MagmaGlassFurnace: + case df::furnace_type::GlassFurnace: + return df::unit_labor::GLASSMAKER; + default: + return df::unit_labor::NONE; // FIXME + } + } + + return df::unit_labor::NONE; // FIXME + } + + jlfunc_make (df::unit_labor mt) : metaltype(mt) {} +}; + +class jlfunc_custom : public jlfunc +{ +public: + df::unit_labor get_labor(df::job* j) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + df::unit_labor labor = ENUM_ATTR(job_skill, labor, skill); + return labor; + } + } + return df::unit_labor::NONE; + } + jlfunc_custom() {} +}; + +static map jlf_cache; + +jlfunc* jlf_const(df::unit_labor l) { + jlfunc* jlf; + if (jlf_cache.count(l) == 0) + { + jlf = jlf_const(l); + jlf_cache[l] = jlf; + } + else + jlf = jlf_cache[l]; + + return jlf; +} + +static jlfunc* jlf_no_labor = jlf_const(df::unit_labor::NONE); +static jlfunc* jlf_hauling = new jlfunc_hauling(); +static jlfunc* jlf_make_furniture = new jlfunc_make(df::unit_labor::FORGE_FURNITURE); +static jlfunc* jlf_make_object = new jlfunc_make(df::unit_labor::METAL_CRAFT); +static jlfunc* jlf_make_armor = new jlfunc_make(df::unit_labor::FORGE_ARMOR); +static jlfunc* jlf_make_weapon = new jlfunc_make(df::unit_labor::FORGE_WEAPON); + +static jlfunc* job_to_labor_table[] = { + jlf_const(df::unit_labor::DETAIL) /* CarveFortification */, + jlf_const(df::unit_labor::DETAIL) /* DetailWall */, + jlf_const(df::unit_labor::DETAIL) /* DetailFloor */, + jlf_const(df::unit_labor::MINE) /* Dig */, + jlf_const(df::unit_labor::MINE) /* CarveUpwardStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveDownwardStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveUpDownStaircase */, + jlf_const(df::unit_labor::MINE) /* CarveRamp */, + jlf_const(df::unit_labor::MINE) /* DigChannel */, + jlf_const(df::unit_labor::CUTWOOD) /* FellTree */, + jlf_const(df::unit_labor::HERBALIST) /* GatherPlants */, + jlf_no_labor /* RemoveConstruction */, + jlf_const(df::unit_labor::WEAVER) /* CollectWebs */, + jlf_no_labor /* BringItemToDepot */, + jlf_no_labor /* BringItemToShop */, + jlf_no_labor /* Eat */, + jlf_no_labor /* GetProvisions */, + jlf_no_labor /* Drink */, + jlf_no_labor /* Drink2 */, + jlf_no_labor /* FillWaterskin */, + jlf_no_labor /* FillWaterskin2 */, + jlf_no_labor /* Sleep */, + jlf_const(df::unit_labor::GLASSMAKER) /* CollectSand */, + jlf_const(df::unit_labor::FISH) /* Fish */, + jlf_const(df::unit_labor::HUNT) /* Hunt */, + jlf_no_labor /* HuntVermin */, + jlf_no_labor /* Kidnap */, + jlf_no_labor /* BeatCriminal */, + jlf_no_labor /* StartingFistFight */, + jlf_no_labor /* CollectTaxes */, + jlf_no_labor /* GuardTaxCollector */, + jlf_const(df::unit_labor::HUNT) /* CatchLiveLandAnimal */, + jlf_const(df::unit_labor::FISH) /* CatchLiveFish */, + jlf_no_labor /* ReturnKill */, + jlf_no_labor /* CheckChest */, + jlf_no_labor /* StoreOwnedItem */, + jlf_const(df::unit_labor::HAUL_BODY) /* PlaceItemInTomb */, + jlf_hauling /* StoreItemInStockpile */, + jlf_hauling /* StoreItemInBag */, + jlf_hauling /* StoreItemInHospital */, + jlf_hauling /* StoreItemInChest */, + jlf_hauling /* StoreItemInCabinet */, + jlf_hauling /* StoreWeapon */, + jlf_hauling /* StoreArmor */, + jlf_hauling /* StoreItemInBarrel */, + jlf_hauling /* StoreItemInBin */, + jlf_no_labor /* SeekArtifact */, + jlf_no_labor /* SeekInfant */, + jlf_no_labor /* AttendParty */, + jlf_no_labor /* GoShopping */, + jlf_no_labor /* GoShopping2 */, + jlf_const(df::unit_labor::CLEAN) /* Clean */, + jlf_no_labor /* Rest */, + jlf_no_labor /* PickupEquipment */, + jlf_hauling /* DumpItem */, + jlf_no_labor /* StrangeMoodCrafter */, + jlf_no_labor /* StrangeMoodJeweller */, + jlf_no_labor /* StrangeMoodForge */, + jlf_no_labor /* StrangeMoodMagmaForge */, + jlf_no_labor /* StrangeMoodBrooding */, + jlf_no_labor /* StrangeMoodFell */, + jlf_no_labor /* StrangeMoodCarpenter */, + jlf_no_labor /* StrangeMoodMason */, + jlf_no_labor /* StrangeMoodBowyer */, + jlf_no_labor /* StrangeMoodTanner */, + jlf_no_labor /* StrangeMoodWeaver */, + jlf_no_labor /* StrangeMoodGlassmaker */, + jlf_no_labor /* StrangeMoodMechanics */, + new jlfunc_construct_bld() /* ConstructBuilding */, + jlf_make_furniture /* ConstructDoor */, + jlf_make_furniture /* ConstructFloodgate */, + jlf_make_furniture /* ConstructBed */, + jlf_make_furniture /* ConstructThrone */, + jlf_make_furniture /* ConstructCoffin */, + jlf_make_furniture /* ConstructTable */, + jlf_make_furniture /* ConstructChest */, + jlf_make_furniture /* ConstructBin */, + jlf_make_furniture /* ConstructArmorStand */, + jlf_make_furniture /* ConstructWeaponRack */, + jlf_make_furniture /* ConstructCabinet */, + jlf_make_furniture /* ConstructStatue */, + jlf_make_furniture /* ConstructBlocks */, + jlf_const(df::unit_labor::GLASSMAKER) /* MakeRawGlass */, + jlf_make_object /* MakeCrafts */, + jlf_const(df::unit_labor::METAL_CRAFT) /* MintCoins */, + jlf_const(df::unit_labor::CUT_GEM) /* CutGems */, + jlf_const(df::unit_labor::CUT_GEM) /* CutGlass */, + jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGems */, + jlf_const(df::unit_labor::ENCRUST_GEM) /* EncrustWithGlass */, + new jlfunc_destroy_bld() /* DestroyBuilding */, + jlf_const(df::unit_labor::SMELT) /* SmeltOre */, + jlf_const(df::unit_labor::SMELT) /* MeltMetalObject */, + jlf_const(df::unit_labor::EXTRACT_STRAND) /* ExtractMetalStrands */, + jlf_const(df::unit_labor::PLANT) /* PlantSeeds */, + jlf_const(df::unit_labor::PLANT) /* HarvestPlants */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainHuntingAnimal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainWarAnimal */, + jlf_make_weapon /* MakeWeapon */, + jlf_make_furniture /* ForgeAnvil */, + jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructCatapultParts */, + jlf_const(df::unit_labor::SIEGECRAFT) /* ConstructBallistaParts */, + jlf_make_armor /* MakeArmor */, + jlf_make_armor /* MakeHelm */, + jlf_make_armor /* MakePants */, + jlf_make_object /* StudWith */, + jlf_const(df::unit_labor::BUTCHER) /* ButcherAnimal */, + jlf_const(df::unit_labor::CLEAN_FISH) /* PrepareRawFish */, + jlf_const(df::unit_labor::MILLER) /* MillPlants */, + jlf_const(df::unit_labor::TRAPPER) /* BaitTrap */, + jlf_const(df::unit_labor::MILK) /* MilkCreature */, + jlf_const(df::unit_labor::MAKE_CHEESE) /* MakeCheese */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlants */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBag */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsVial */, + jlf_const(df::unit_labor::PROCESS_PLANT) /* ProcessPlantsBarrel */, + jlf_const(df::unit_labor::COOK) /* PrepareMeal */, + jlf_const(df::unit_labor::WEAVER) /* WeaveCloth */, + jlf_make_armor /* MakeGloves */, + jlf_make_armor /* MakeShoes */, + jlf_make_armor /* MakeShield */, + jlf_make_furniture /* MakeCage */, + jlf_make_object /* MakeChain */, + jlf_make_object /* MakeFlask */, + jlf_make_object /* MakeGoblet */, + jlf_make_object/* MakeInstrument */, + jlf_make_object/* MakeToy */, + jlf_const(df::unit_labor::TRAPPER) /* MakeAnimalTrap */, + jlf_make_furniture /* MakeBarrel */, + jlf_make_furniture /* MakeBucket */, + jlf_make_furniture /* MakeWindow */, + jlf_const(df::unit_labor::BONE_CARVE) /* MakeTotem */, + jlf_make_weapon /* MakeAmmo */, + jlf_make_object /* DecorateWith */, + jlf_make_object /* MakeBackpack */, + jlf_make_armor /* MakeQuiver */, + jlf_make_weapon /* MakeBallistaArrowHead */, + jlf_const(df::unit_labor::SIEGECRAFT) /* AssembleSiegeAmmo */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadCatapult */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* LoadBallista */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* FireCatapult */, + jlf_const(df::unit_labor::SIEGEOPERATE) /* FireBallista */, + jlf_const(df::unit_labor::MECHANIC) /* ConstructMechanisms */, + jlf_const(df::unit_labor::MECHANIC) /* MakeTrapComponent */, + jlf_const(df::unit_labor::MECHANIC) /* LoadCageTrap */, + jlf_const(df::unit_labor::MECHANIC) /* LoadStoneTrap */, + jlf_const(df::unit_labor::MECHANIC) /* LoadWeaponTrap */, + jlf_const(df::unit_labor::MECHANIC) /* CleanTrap */, + jlf_no_labor /* CastSpell */, + jlf_const(df::unit_labor::MECHANIC) /* LinkBuildingToTrigger */, + jlf_no_labor /* PullLever */, + jlf_const(df::unit_labor::BREWER) /* BrewDrink */, + jlf_const(df::unit_labor::HERBALIST) /* ExtractFromPlants */, + jlf_const(df::unit_labor::DISSECT_FISH) /* ExtractFromRawFish */, + jlf_const(df::unit_labor::DISSECT_VERMIN) /* ExtractFromLandAnimal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TameVermin */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TameAnimal */, + jlf_no_labor /* ChainAnimal */, + jlf_no_labor /* UnchainAnimal */, + jlf_no_labor /* UnchainPet */, + jlf_no_labor /* ReleaseLargeCreature */, + jlf_no_labor /* ReleasePet */, + jlf_no_labor /* ReleaseSmallCreature */, + jlf_no_labor /* HandleSmallCreature */, + jlf_no_labor /* HandleLargeCreature */, + jlf_no_labor /* CageLargeCreature */, + jlf_no_labor /* CageSmallCreature */, + jlf_const(df::unit_labor::RECOVER_WOUNDED) /* RecoverWounded */, + jlf_const(df::unit_labor::DIAGNOSE) /* DiagnosePatient */, + jlf_const(df::unit_labor::BONE_SETTING) /* ImmobilizeBreak */, + jlf_const(df::unit_labor::DRESSING_WOUNDS) /* DressWound */, + jlf_const(df::unit_labor::CLEAN) /* CleanPatient */, + jlf_const(df::unit_labor::SURGERY) /* Surgery */, + jlf_const(df::unit_labor::SUTURING) /* Suture */, + jlf_const(df::unit_labor::BONE_SETTING) /* SetBone */, + jlf_const(df::unit_labor::BONE_SETTING) /* PlaceInTraction */, + jlf_no_labor /* DrainAquarium */, + jlf_no_labor /* FillAquarium */, + jlf_no_labor /* FillPond */, + jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveWater */, + jlf_const(df::unit_labor::FEED_WATER_CIVILIANS) /* GiveFood */, + jlf_no_labor /* GiveWater2 */, + jlf_no_labor /* GiveFood2 */, + jlf_no_labor /* RecoverPet */, + jlf_no_labor /* PitLargeAnimal */, + jlf_no_labor /* PitSmallAnimal */, + jlf_const(df::unit_labor::BUTCHER) /* SlaughterAnimal */, + jlf_const(df::unit_labor::BURN_WOOD) /* MakeCharcoal */, + jlf_const(df::unit_labor::BURN_WOOD) /* MakeAsh */, + jlf_const(df::unit_labor::LYE_MAKING) /* MakeLye */, + jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromLye */, + jlf_const(df::unit_labor::PLANT) /* FertilizeField */, + jlf_const(df::unit_labor::POTASH_MAKING) /* MakePotashFromAsh */, + jlf_const(df::unit_labor::DYER) /* DyeThread */, + jlf_const(df::unit_labor::DYER) /* DyeCloth */, + jlf_make_object /* SewImage */, + jlf_make_furniture /* MakePipeSection */, + jlf_const(df::unit_labor::OPERATE_PUMP) /* OperatePump */, + jlf_no_labor /* ManageWorkOrders */, + jlf_no_labor /* UpdateStockpileRecords */, + jlf_no_labor /* TradeAtDepot */, + jlf_make_furniture /* ConstructHatchCover */, + jlf_make_furniture /* ConstructGrate */, + jlf_const(df::unit_labor::MINE) /* RemoveStairs */, + jlf_make_furniture /* ConstructQuern */, + jlf_make_furniture /* ConstructMillstone */, + jlf_make_object /* ConstructSplint */, + jlf_make_object /* ConstructCrutch */, + jlf_const(df::unit_labor::MECHANIC) /* ConstructTractionBench */, + jlf_no_labor /* CleanSelf */, + jlf_no_labor /* BringCrutch */, + jlf_const(df::unit_labor::BONE_SETTING) /* ApplyCast */, + new jlfunc_custom() /* CustomReaction */, + jlf_make_furniture /* ConstructSlab */, + jlf_const(df::unit_labor::STONE_CRAFT) /* EngraveSlab */, + jlf_const(df::unit_labor::SHEARER) /* ShearCreature */, + jlf_const(df::unit_labor::SPINNER) /* SpinThread */, + jlf_no_labor /* PenLargeAnimal */, + jlf_no_labor /* PenSmallAnimal */, + jlf_make_furniture /* MakeTool */, + jlf_const(df::unit_labor::POTTERY) /* CollectClay */, + jlf_const(df::unit_labor::BEEKEEPING) /* InstallColonyInHive */, + jlf_const(df::unit_labor::BEEKEEPING) /* CollectHiveProducts */, + jlf_no_labor /* CauseTrouble */, + jlf_no_labor /* DrinkBlood */, + jlf_no_labor /* ReportCrime */, + jlf_no_labor /* ExecuteCriminal */, + jlf_const(df::unit_labor::ANIMALTRAIN) /* TrainAnimal */, + jlf_const(df::unit_labor::DETAIL) /* CarveTrack */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PushTrackVehicle */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* PlaceTrackVehicle */, + jlf_const(df::unit_labor::PUSH_HAUL_VEHICLE) /* StoreItemInVehicle */ +}; + + static df::unit_labor find_job_labor(df::job* j) { - df::job_skill skill; - df::unit_labor labor; - - labor = df::unit_labor::NONE; - - switch (j->job_type) - { - case df::job_type::ConstructBuilding: - case df::job_type::DestroyBuilding: - { - df::building* bld = get_building_from_job (j); - switch (bld->getType()) - { - case df::building_type::Workshop: - df::building_workshopst* ws = (df::building_workshopst*) bld; - skill = workshop_build_labor[ws->type]; - break; - } - } - break; - - case df::job_type::CustomReaction: - for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) - { - if ((*r)->code == j->reaction_name) - { - skill = (*r)->skill; - break; - } - } - break; - default: - skill = ENUM_ATTR(job_type, skill, j->job_type); - } - - if (skill != df::job_skill::NONE) - labor = ENUM_ATTR(job_skill, labor, skill); - - if (labor == df::unit_labor::NONE) - labor = ENUM_ATTR(job_type, labor, j->job_type); - - if (labor == -1) - { - } - - return labor; + if (j->job_type == df::job_type::CustomReaction) + { + for (auto r = world->raws.reactions.begin(); r != world->raws.reactions.end(); r++) + { + if ((*r)->code == j->reaction_name) + { + df::job_skill skill = (*r)->skill; + return ENUM_ATTR(job_skill, labor, skill); + } + } + return df::unit_labor::NONE; + } + + df::job_skill skill; + df::unit_labor labor; + skill = ENUM_ATTR(job_type, skill, j->job_type); + if (skill != df::job_skill::NONE) + labor = ENUM_ATTR(job_skill, labor, skill); + else + labor = ENUM_ATTR(job_type, labor, j->job_type); + + if (labor == df::unit_labor::NONE) + labor = job_to_labor_table[j->job_type]->get_labor(j); + + return labor; } class AutoLaborManager { - color_ostream& out; + color_ostream& out; public: - AutoLaborManager(color_ostream& o) : out(o) - { - } + AutoLaborManager(color_ostream& o) : out(o) + { + } - ~AutoLaborManager() - { - for (std::vector::iterator i = dwarf_info.begin(); - i != dwarf_info.end(); i++) - delete (*i); - } + ~AutoLaborManager() + { + for (std::vector::iterator i = dwarf_info.begin(); + i != dwarf_info.end(); i++) + delete (*i); + } - dwarf_info_t* add_dwarf(df::unit* u) - { - dwarf_info_t* dwarf = new dwarf_info_t(u); - dwarf_info.push_back(dwarf); - return dwarf; - } + dwarf_info_t* add_dwarf(df::unit* u) + { + dwarf_info_t* dwarf = new dwarf_info_t(u); + dwarf_info.push_back(dwarf); + return dwarf; + } private: - bool has_butchers; - bool has_fishery; - bool trader_requested; + bool has_butchers; + bool has_fishery; + bool trader_requested; - int dig_count; - int tree_count; - int plant_count; - int detail_count; - int pick_count; - int axe_count; + int dig_count; + int tree_count; + int plant_count; + int detail_count; + int pick_count; + int axe_count; - std::map labor_needed; - std::vector dwarf_info; - std::deque idle_dwarfs; + std::map labor_needed; + std::vector dwarf_info; + std::deque idle_dwarfs; private: - void scan_buildings() - { - for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) - { - df::building *build = *b; - auto type = build->getType(); - if (building_type::Workshop == type) - { - df::workshop_type subType = (df::workshop_type)build->getSubtype(); - if (workshop_type::Butchers == subType) - has_butchers = true; - if (workshop_type::Fishery == subType) - has_fishery = true; - } - else if (building_type::TradeDepot == type) - { - df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; - if (print_debug) - { - if (trader_requested) - out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); - else - out.print("Trade depot found but trader is not requested.\n"); - } - } - } - } - - void count_map_designations() - { - dig_count = 0; - tree_count = 0; - plant_count = 0; - detail_count = 0; - - for (int i = 0; i < world->map.map_blocks.size(); ++i) - { - df::map_block* bl = world->map.map_blocks[i]; - - if (!bl->flags.bits.designated) - continue; - - if (print_debug) - out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); - - for (int x = 0; x < 16; x++) - for (int y = 0; y < 16; y++) - { - df::tile_dig_designation dig = bl->designation[x][y].bits.dig; - if (dig != df::enums::tile_dig_designation::No) - { - df::tiletype tt = bl->tiletype[x][y]; - df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); - switch (tts) - { - case df::enums::tiletype_shape::TREE: - tree_count++; break; - case df::enums::tiletype_shape::SHRUB: - plant_count++; break; - default: - dig_count++; break; - } - } - if (bl->designation[x][y].bits.smooth != 0) - detail_count++; - } - } - - if (print_debug) - out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); - - } - - void count_tools() - { - pick_count = 0; - axe_count = 0; - - df::item_flags bad_flags; - bad_flags.whole = 0; + void scan_buildings() + { + for (auto b = world->buildings.all.begin(); b != world->buildings.all.end(); b++) + { + df::building *build = *b; + auto type = build->getType(); + if (building_type::Workshop == type) + { + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) + has_butchers = true; + if (workshop_type::Fishery == subType) + has_fishery = true; + } + else if (building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->trade_flags.bits.trader_requested; + if (print_debug) + { + if (trader_requested) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + else + out.print("Trade depot found but trader is not requested.\n"); + } + } + } + } + + void count_map_designations() + { + dig_count = 0; + tree_count = 0; + plant_count = 0; + detail_count = 0; + + for (int i = 0; i < world->map.map_blocks.size(); ++i) + { + df::map_block* bl = world->map.map_blocks[i]; + + if (!bl->flags.bits.designated) + continue; + + if (print_debug) + out.print ("block with designations found: %d, %d, %d\n", bl->map_pos.x, bl->map_pos.y, bl->map_pos.z); + + for (int x = 0; x < 16; x++) + for (int y = 0; y < 16; y++) + { + df::tile_dig_designation dig = bl->designation[x][y].bits.dig; + if (dig != df::enums::tile_dig_designation::No) + { + df::tiletype tt = bl->tiletype[x][y]; + df::tiletype_shape tts = ENUM_ATTR(tiletype, shape, tt); + switch (tts) + { + case df::enums::tiletype_shape::TREE: + tree_count++; break; + case df::enums::tiletype_shape::SHRUB: + plant_count++; break; + default: + dig_count++; break; + } + } + if (bl->designation[x][y].bits.smooth != 0) + detail_count++; + } + } + + if (print_debug) + out.print("Dig count = %d, Cut tree count = %d, gather plant count = %d, detail count = %d\n", dig_count, tree_count, plant_count, detail_count); + + } + + void count_tools() + { + pick_count = 0; + axe_count = 0; + + df::item_flags bad_flags; + bad_flags.whole = 0; #define F(x) bad_flags.bits.x = true; - F(dump); F(forbid); F(garbage_collect); - F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact); + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact); #undef F - for (int i = 0; i < world->items.all.size(); ++i) - { - df::item* item = world->items.all[i]; - if (item->flags.whole & bad_flags.whole) - continue; - - if (!item->isWeapon()) - continue; - - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - if (weaponsk == df::job_skill::AXE) - axe_count++; - else if (weaponsk == df::job_skill::MINING) - pick_count++; - } - - if (print_debug) - out.print("Axes = %d, picks = %d\n", axe_count, pick_count); - - } - - void collect_job_list() - { - for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) - { - df::job* j = jll->item; - if (!j) - continue; - - if (j->flags.bits.suspend) - continue; - - int worker = -1; - - for (int r = 0; r < j->general_refs.size(); ++r) - if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) - worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; - - if (worker != -1) - continue; - - df::unit_labor labor = find_job_labor (j); - - if (print_debug) - out.print ("Job requiring labor %d found\n", labor); - - if (labor != df::unit_labor::NONE) - labor_needed[labor]++; - } - - } - - void collect_dwarf_list() - { - - for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) - { - df::unit* cre = *u; - - if (Units::isCitizen(cre)) - { - if (cre->burrows.size() > 0) - continue; // dwarfs assigned to burrows are skipped entirely - - dwarf_info_t* dwarf = add_dwarf(cre); - - df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); - for (int i = 0; i < hf->entity_links.size(); i++) - { - df::histfig_entity_link* hfelink = hf->entity_links.at(i); - if (hfelink->getType() == df::histfig_entity_link_type::POSITION) - { - df::histfig_entity_link_positionst *epos = - (df::histfig_entity_link_positionst*) hfelink; - df::historical_entity* entity = df::historical_entity::find(epos->entity_id); - if (!entity) - continue; - df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); - if (!assignment) - continue; - df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); - if (!position) - continue; - - if (position->responsibilities[df::entity_position_responsibility::TRADE]) - if (trader_requested) - dwarf->clear_all = true; - } - - } - - // identify dwarfs who are needed for meetings and mark them for exclusion - - for (int i = 0; i < ui->activities.size(); ++i) - { - df::activity_info *act = ui->activities[i]; - if (!act) continue; - bool p1 = act->person1 == dwarf->dwarf; - bool p2 = act->person2 == dwarf->dwarf; - - if (p1 || p2) - { - dwarf->clear_all = true; - if (print_debug) - out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); - break; - } - } - - // Find the activity state for each dwarf-> - - bool is_on_break = false; - dwarf_state state = OTHER; - - for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) - { - if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) - is_on_break = true; - } - - if (dwarf->dwarf->profession == profession::BABY || - dwarf->dwarf->profession == profession::CHILD || - dwarf->dwarf->profession == profession::DRUNK) - { - state = CHILD; - } - else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) - state = MILITARY; - else if (dwarf->dwarf->job.current_job == NULL) - { - if (is_on_break) - state = OTHER; - else if (dwarf->dwarf->specific_refs.size() > 0) - state = OTHER; - else - state = IDLE; - } - else - { - int job = dwarf->dwarf->job.current_job->job_type; - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - state = dwarf_states[job]; - else - { - out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); - state = OTHER; - } - } - - dwarf->state = state; - - if (print_debug) - out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); - - // check if dwarf has an axe, pick, or crossbow - - for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) - { - df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; - if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) - { - df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; - df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; - df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; - if (weaponsk == df::job_skill::AXE) - { - dwarf->has_axe = 1; - if (state != IDLE) - axe_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); - } - else if (weaponsk == df::job_skill::MINING) - { - dwarf->has_pick = 1; - if (state != IDLE) - pick_count--; - if (print_debug) - out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); - } - else if (rangesk == df::job_skill::CROSSBOW) - { - dwarf->has_crossbow = 1; - if (print_debug) - out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); - } - } - } - - // clear labors if currently idle - - if (state == IDLE || dwarf->clear_all) - { - FOR_ENUM_ITEMS(unit_labor, labor) - { - if (labor == unit_labor::NONE) - continue; - - dwarf->dwarf->status.labors[labor] = false; - } - } - - if (state == IDLE && !dwarf->clear_all) - idle_dwarfs.push_back(dwarf); - - } - - } - } + for (int i = 0; i < world->items.all.size(); ++i) + { + df::item* item = world->items.all[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + if (!item->isWeapon()) + continue; + + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)item)->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + if (weaponsk == df::job_skill::AXE) + axe_count++; + else if (weaponsk == df::job_skill::MINING) + pick_count++; + } -public: - void process() - { - // scan for specific buildings of interest + if (print_debug) + out.print("Axes = %d, picks = %d\n", axe_count, pick_count); + + } + + void collect_job_list() + { + for (df::job_list_link* jll = world->job_list.next; jll; jll = jll->next) + { + df::job* j = jll->item; + if (!j) + continue; + + if (j->flags.bits.suspend) + continue; + + int worker = -1; + + for (int r = 0; r < j->general_refs.size(); ++r) + if (j->general_refs[r]->getType() == df::general_ref_type::UNIT_WORKER) + worker = ((df::general_ref_unit_workerst *)(j->general_refs[r]))->unit_id; + + if (worker != -1) + continue; + + df::unit_labor labor = find_job_labor (j); + + if (print_debug) + out.print ("Job requiring labor %d found\n", labor); + + if (labor != df::unit_labor::NONE) + labor_needed[labor]++; + } + + } + + void collect_dwarf_list() + { + + for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u) + { + df::unit* cre = *u; + + if (Units::isCitizen(cre)) + { + if (cre->burrows.size() > 0) + continue; // dwarfs assigned to burrows are skipped entirely + + dwarf_info_t* dwarf = add_dwarf(cre); + + df::historical_figure* hf = df::historical_figure::find(dwarf->dwarf->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) + { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) + { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + if (trader_requested) + dwarf->clear_all = true; + } + + } + + // identify dwarfs who are needed for meetings and mark them for exclusion + + for (int i = 0; i < ui->activities.size(); ++i) + { + df::activity_info *act = ui->activities[i]; + if (!act) continue; + bool p1 = act->person1 == dwarf->dwarf; + bool p2 = act->person2 == dwarf->dwarf; + + if (p1 || p2) + { + dwarf->clear_all = true; + if (print_debug) + out.print("Dwarf \"%s\" has a meeting, will be cleared of all labors\n", dwarf->dwarf->name.first_name.c_str()); + break; + } + } + + // Find the activity state for each dwarf-> + + bool is_on_break = false; + dwarf_state state = OTHER; + + for (auto p = dwarf->dwarf->status.misc_traits.begin(); p < dwarf->dwarf->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + is_on_break = true; + } + + if (dwarf->dwarf->profession == profession::BABY || + dwarf->dwarf->profession == profession::CHILD || + dwarf->dwarf->profession == profession::DRUNK) + { + state = CHILD; + } + else if (ENUM_ATTR(profession, military, dwarf->dwarf->profession)) + state = MILITARY; + else if (dwarf->dwarf->job.current_job == NULL) + { + if (is_on_break) + state = OTHER; + else if (dwarf->dwarf->specific_refs.size() > 0) + state = OTHER; + else + state = IDLE; + } + else + { + int job = dwarf->dwarf->job.current_job->job_type; + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + state = dwarf_states[job]; + else + { + out.print("Dwarf \"%s\" has unknown job %i\n", dwarf->dwarf->name.first_name.c_str(), job); + state = OTHER; + } + } + + dwarf->state = state; + + if (print_debug) + out.print("Dwarf \"%s\": state %s\n", dwarf->dwarf->name.first_name.c_str(), state_names[dwarf->state]); + + // check if dwarf has an axe, pick, or crossbow + + for (int j = 0; j < dwarf->dwarf->inventory.size(); j++) + { + df::unit_inventory_item* ui = dwarf->dwarf->inventory[j]; + if (ui->mode == df::unit_inventory_item::Weapon && ui->item->isWeapon()) + { + df::itemdef_weaponst* weapondef = ((df::item_weaponst*)(ui->item))->subtype; + df::job_skill weaponsk = (df::job_skill) weapondef->skill_melee; + df::job_skill rangesk = (df::job_skill) weapondef->skill_ranged; + if (weaponsk == df::job_skill::AXE) + { + dwarf->has_axe = 1; + if (state != IDLE) + axe_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an axe\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (weaponsk == df::job_skill::MINING) + { + dwarf->has_pick = 1; + if (state != IDLE) + pick_count--; + if (print_debug) + out.print("Dwarf \"%s\" has an pick\n", dwarf->dwarf->name.first_name.c_str()); + } + else if (rangesk == df::job_skill::CROSSBOW) + { + dwarf->has_crossbow = 1; + if (print_debug) + out.print("Dwarf \"%s\" has a crossbow\n", dwarf->dwarf->name.first_name.c_str()); + } + } + } + + // clear labors if currently idle + + if (state == IDLE || dwarf->clear_all) + { + FOR_ENUM_ITEMS(unit_labor, labor) + { + if (labor == unit_labor::NONE) + continue; + + dwarf->dwarf->status.labors[labor] = false; + } + } + + if (state == IDLE && !dwarf->clear_all) + idle_dwarfs.push_back(dwarf); + + } - scan_buildings(); + } + } - // count number of squares designated for dig, wood cutting, detailing, and plant harvesting +public: + void process() + { + // scan for specific buildings of interest - count_map_designations(); + scan_buildings(); - // count number of picks and axes available for use + // count number of squares designated for dig, wood cutting, detailing, and plant harvesting - count_tools(); + count_map_designations(); - // create job entries for designation + // count number of picks and axes available for use - // collect current job list + count_tools(); - collect_job_list(); + // create job entries for designation - // collect list of dwarfs + // collect current job list - collect_dwarf_list(); + collect_job_list(); - // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count - // and remove the idle dwarf from the idle list + // collect list of dwarfs - for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) - { - FOR_ENUM_ITEMS(unit_labor, l) - { - if ((*i)->dwarf->status.labors[l]) - if (labor_needed[l] > 0) - { - if (print_debug) - out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); - labor_needed[l]--; - idle_dwarfs.erase(i); // remove from idle list - break; - } else { - (*i)->dwarf->status.labors[l] = false; - } - } - } + collect_dwarf_list(); - priority_queue> pq; - - for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) - { - if (i->second > 0) - pq.push(make_pair(i->second, i->first)); - } - - while (!idle_dwarfs.empty() && !pq.empty()) - { - df::unit_labor labor = pq.top().second; - int remaining = pq.top().first; - df::job_skill skill = labor_to_skill[labor]; + // match idle dwarfs to need list - if an idle dwarf is assigned to that labor, then yay, decrement the need count + // and remove the idle dwarf from the idle list - if (print_debug) - out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); + for (auto i = idle_dwarfs.begin(); i != idle_dwarfs.end(); i++) + { + FOR_ENUM_ITEMS(unit_labor, l) + { + if ((*i)->dwarf->status.labors[l]) + if (labor_needed[l] > 0) + { + if (print_debug) + out.print("assign \"%s\" labor %d (carried through)\n", (*i)->dwarf->name.first_name.c_str(), l); + labor_needed[l]--; + idle_dwarfs.erase(i); // remove from idle list + break; + } else { + (*i)->dwarf->status.labors[l] = false; + } + } + } - std::deque::iterator bestdwarf = idle_dwarfs.begin(); + priority_queue> pq; + + for (auto i = labor_needed.begin(); i != labor_needed.end(); i++) + { + if (i->second > 0) + pq.push(make_pair(i->second, i->first)); + } + + while (!idle_dwarfs.empty() && !pq.empty()) + { + df::unit_labor labor = pq.top().second; + int remaining = pq.top().first; + df::job_skill skill = labor_to_skill[labor]; - if (skill != df::job_skill::NONE) - { - int best_skill_level = -1; + if (print_debug) + out.print("labor %d skill %d remaining %d\n", labor, skill, remaining); - for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) - { - dwarf_info_t* d = (*k); - int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + std::deque::iterator bestdwarf = idle_dwarfs.begin(); - if (skill_level > best_skill_level) - { - bestdwarf = k; - best_skill_level = skill_level; - } - } - } + if (skill != df::job_skill::NONE) + { + int best_skill_level = -1; + + for (std::deque::iterator k = idle_dwarfs.begin(); k != idle_dwarfs.end(); k++) + { + dwarf_info_t* d = (*k); + int skill_level = Units::getEffectiveSkill(d->dwarf, skill); + + if (skill_level > best_skill_level) + { + bestdwarf = k; + best_skill_level = skill_level; + } + } + } - if (print_debug) - out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); - (*bestdwarf)->set_labor(labor); + if (print_debug) + out.print("assign \"%s\" labor %d\n", (*bestdwarf)->dwarf->name.first_name.c_str(), labor); + (*bestdwarf)->set_labor(labor); - idle_dwarfs.erase(bestdwarf); - pq.pop(); - if (--remaining) - pq.push(make_pair(remaining, labor)); - } + idle_dwarfs.erase(bestdwarf); + pq.pop(); + if (--remaining) + pq.push(make_pair(remaining, labor)); + } - print_debug = 0; + print_debug = 0; - } + } }; @@ -1284,9 +1771,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; step_count = 0; - AutoLaborManager alm(out); + AutoLaborManager alm(out); - alm.process(); + alm.process(); return CR_OK; From 58239e97ed2372c5110db5684a4c228bcb5ebf95 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 1 Dec 2012 16:50:03 +0400 Subject: [PATCH 261/472] Implement the history graph in the workflow status screen. --- Lua API.html | 14 +- Lua API.rst | 2 + Readme.html | 27 +++- Readme.rst | 33 ++++- dfhack.init-example | 1 + images/workflow-new1.png | Bin 6674 -> 6775 bytes images/workflow-new2.png | Bin 6862 -> 7793 bytes images/workflow-status.png | Bin 0 -> 5118 bytes images/workflow.png | Bin 4931 -> 5779 bytes library/lua/gui/widgets.lua | 19 ++- scripts/gui/workflow.lua | 248 ++++++++++++++++++++++++------------ 11 files changed, 248 insertions(+), 96 deletions(-) create mode 100644 images/workflow-status.png diff --git a/Lua API.html b/Lua API.html index f42905d01..f14239a10 100644 --- a/Lua API.html +++ b/Lua API.html @@ -2787,6 +2787,14 @@ before rendering the token.

                                                                              • token.tile = pen

                                                                                Specifies a pen to paint as one tile before the main part of the token.

                                                                              • +
                                                                              • token.width = ...

                                                                                +

                                                                                If specified either as a value or a callback, the text field is padded +or truncated to the specified number.

                                                                                +
                                                                              • +
                                                                              • token.pad_char = '?'

                                                                                +

                                                                                If specified together with width, the padding area is filled with +this character instead of just being skipped over.

                                                                                +
                                                                              • token.key = '...'

                                                                                Specifies the keycode associated with the token. The string description of the key binding is added to the text content of the token.

                                                                                @@ -2848,7 +2856,9 @@ this may be extended with mouse click support.

                                                                              • icon_pen:Default pen for icons.
                                                                                on_select:Selection change callback; called as on_select(index,choice).
                                                                                on_select:Selection change callback; called as on_select(index,choice). +This is also called with nil arguments if setChoices is called +with an empty list.
                                                                                on_submit:Enter key callback; if specified, the list reacts to the key and calls it as on_submit(index,choice).
                                                                                edit_pen:If specified, used instead of cursor_pen for the edit field.
                                                                                edit_below:If true, the edit field is placed below the list instead of above.
                                                                                not_found_label:
                                                                                 Specifies the text of the label shown when no items match the filter.