From 27bdc9f2df02ccd566eddc5fe1ffa2a09d7e6143 Mon Sep 17 00:00:00 2001 From: Warmist Date: Thu, 23 Aug 2012 21:38:38 +0300 Subject: [PATCH 001/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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/389] 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 43532e4871255d7bdb3696ce32ebe0f776844c3e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Mon, 1 Oct 2012 17:48:47 -0500 Subject: [PATCH 023/389] 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 923ea3f4b0a2c435399dd967d4736e2ec626344f Mon Sep 17 00:00:00 2001 From: Warmist Date: Sun, 7 Oct 2012 20:44:18 +0300 Subject: [PATCH 024/389] 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 025/389] 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: Wed, 17 Oct 2012 19:33:20 +0300 Subject: [PATCH 026/389] 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 da92fb9a1c8dfd373ac9c0781729fe722009fdcb Mon Sep 17 00:00:00 2001 From: Warmist Date: Wed, 17 Oct 2012 21:43:44 +0300 Subject: [PATCH 027/389] 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 01:38:08 +1300 Subject: [PATCH 028/389] Fix autobutcher resume --- plugins/zone.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index c496f49b6..8e73ba34b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -3411,7 +3411,6 @@ command_result start_autobutcher(color_ostream &out) 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 +3419,7 @@ 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 fd60db44ab415c0e59266380133c64ef3230b660 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 20 Oct 2012 01:38:28 +1300 Subject: [PATCH 029/389] Search plugin, early work. Unit and stocks screen. --- plugins/CMakeLists.txt | 1 + plugins/search.cpp | 327 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 328 insertions(+) create mode 100644 plugins/search.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 91d578215..fc3d0743e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -117,6 +117,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(regrass regrass.cpp) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp) + DFHACK_PLUGIN(search search.cpp) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) diff --git a/plugins/search.cpp b/plugins/search.cpp new file mode 100644 index 000000000..6a5a713c9 --- /dev/null +++ b/plugins/search.cpp @@ -0,0 +1,327 @@ +// Dwarf Manipulator - a Therapist-style labor editor + +#include +#include +#include +#include + +#include + +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_storesst.h" +#include "df/interface_key.h" + +using std::set; +using std::vector; +using std::string; + +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; + + +void OutputString(int8_t color, int &x, int y, const std::string &text) +{ + Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); + x += text.length(); +} + +struct search_struct +{ + static string search_string; + static bool entry_mode; + + void print_search_option(int &x) + { + OutputString(12, x, gps->dimy - 2, "s"); + OutputString(15, x, gps->dimy - 2, ": Search"); + if (search_string.length() > 0 || entry_mode) + OutputString(15, x, gps->dimy - 2, ": " + search_string); + if (entry_mode) + OutputString(12, x, gps->dimy - 2, "_"); + } + + bool process_input(df::interface_key select_key, set *input, bool &string_changed) + { + bool key_processed = true; + string_changed = false; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += 32 + last_token - interface_key::STRING_A032; + string_changed = true; + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + string_changed = true; + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + entry_mode = false; + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) + { + entry_mode = false; + key_processed = false; + } + } + else if (input->count(select_key)) + { + entry_mode = true; + } + else + { + key_processed = false; + } + + return key_processed; + } +}; + +string search_struct::search_string = ""; +bool search_struct::entry_mode = false; + + +struct stocks_search : df::viewscreen_storesst, search_struct +{ + typedef df::viewscreen_storesst interpose_base; + + static vector saved_items; + + static void reset_search() + { + entry_mode = false; + search_string = ""; + saved_items.clear(); + } + + void clear_search() + { + if (saved_items.size() > 0) + { + items = saved_items; + } + } + + + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + int x = 1; + print_search_option(x); + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (in_group_mode) + { + INTERPOSE_NEXT(feed)(input); + return; + } + + + bool string_changed = false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !in_right_list) + { + saved_items.clear(); + entry_mode = false; + if (search_string.length() > 0) + string_changed = true; + INTERPOSE_NEXT(feed)(input); + } + else + { + if (!process_input(interface_key::CUSTOM_S, input, string_changed) && !entry_mode) + { + INTERPOSE_NEXT(feed)(input); + if (in_group_mode) + { + clear_search(); + reset_search(); + } + } + } + + if (string_changed) + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + if (saved_items.size() == 0 && items.size() > 0) + { + saved_items = items; + } + items.clear(); + + for (int i = 0; i < saved_items.size(); i++ ) + { + string search_string_l = toLower(search_string); + string desc = Items::getDescription(saved_items[i], 0, true); + if (desc.find(search_string_l) != string::npos) + { + items.push_back(saved_items[i]); + } + } + + item_cursor = 0; + } + } + +}; + +vector stocks_search::saved_items; + + +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, feed); +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, render); + + + + +struct unitlist_search : df::viewscreen_unitlistst, search_struct +{ + typedef df::viewscreen_unitlistst interpose_base; + + static vector saved_units; + static vector saved_jobs; + + + static void reset_search() + { + entry_mode = false; + search_string = ""; + saved_units.clear(); + saved_jobs.clear(); + } + + void clear_search() + { + if (saved_units.size() > 0) + { + units[page] = saved_units; + jobs[page] = saved_jobs; + } + } + + void do_search() + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + while(1) + { + if (saved_units.size() == 0) + { + saved_units = units[page]; + saved_jobs = jobs[page]; + } + units[page].clear(); + jobs[page].clear(); + + for (int i = 0; i < saved_units.size(); i++ ) + { + df::unit *unit = saved_units[i]; + string search_string_l = toLower(search_string); + string name = toLower(Translation::TranslateName(Units::getVisibleName(unit), false)); + if (name.find(search_string_l) != string::npos) + { + units[page].push_back(unit); + jobs[page].push_back(saved_jobs[i]); + } + } + + if (units[page].size() > 0) + { + cursor_pos[page] = 0; + break; + } + + search_string.erase(search_string.length()-1); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + bool string_changed = false; + if (!process_input(interface_key::CUSTOM_S, input, string_changed)) + { + if (!entry_mode) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + clear_search(); + reset_search(); + } + INTERPOSE_NEXT(feed)(input); + } + } + else if (string_changed) + do_search(); + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + INTERPOSE_NEXT(render)(); + + if (units[page].size()) + { + int x = 28; + print_search_option(x); + } + } + +}; + +vector unitlist_search::saved_units; +vector unitlist_search::saved_jobs; + + +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, render); + + + + +DFHACK_PLUGIN("search"); + + +DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) +{ + if (!gps || !INTERPOSE_HOOK(unitlist_search, feed).apply() || !INTERPOSE_HOOK(unitlist_search, render).apply() + || !INTERPOSE_HOOK(stocks_search, feed).apply() || !INTERPOSE_HOOK(stocks_search, render).apply()) + out.printerr("Could not insert Search hooks!\n"); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + INTERPOSE_HOOK(unitlist_search, feed).remove(); + INTERPOSE_HOOK(unitlist_search, render).remove(); + INTERPOSE_HOOK(stocks_search, feed).remove(); + INTERPOSE_HOOK(stocks_search, render).remove(); + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) +{ + unitlist_search::reset_search(); + return CR_OK; +} \ No newline at end of file From c5b38a24eb86e9ec28eb290ad2aa3a0a2b5781e4 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 21 Oct 2012 14:18:29 +1300 Subject: [PATCH 030/389] Refactoring to use templates --- plugins/search.cpp | 576 +++++++++++++++++++++++++-------------------- 1 file changed, 316 insertions(+), 260 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 6a5a713c9..e911a73be 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -20,283 +20,338 @@ using namespace df::enums; using df::global::gps; - void OutputString(int8_t color, int &x, int y, const std::string &text) { Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); x += text.length(); } -struct search_struct + +// +// START: Base Search functionality +// +template +struct search_parent { - static string search_string; - static bool entry_mode; - - void print_search_option(int &x) - { - OutputString(12, x, gps->dimy - 2, "s"); - OutputString(15, x, gps->dimy - 2, ": Search"); - if (search_string.length() > 0 || entry_mode) - OutputString(15, x, gps->dimy - 2, ": " + search_string); - if (entry_mode) - OutputString(12, x, gps->dimy - 2, "_"); - } - - bool process_input(df::interface_key select_key, set *input, bool &string_changed) - { - bool key_processed = true; - string_changed = false; - - if (entry_mode) - { - df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) - { - search_string += 32 + last_token - interface_key::STRING_A032; - string_changed = true; - } - else if (last_token == interface_key::STRING_A000) - { - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - string_changed = true; - } - } - else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) - { - entry_mode = false; - } - else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) - { - entry_mode = false; - key_processed = false; - } - } - else if (input->count(select_key)) - { - entry_mode = true; - } - else - { - key_processed = false; - } - - return key_processed; - } + vector *sort_list1; + vector *sort_list2; + int *cursor_pos; + char select_key; + const S *viewscreen; + + bool valid; + bool entry_mode; + bool redo_search; + string search_string; + vector saved_list1; + vector saved_list2; + + df::interface_key select_token; + const int ascii_to_enum_offset; + + search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0') + { + reset_all(); + } + + virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + { + this->cursor_pos = cursor_pos; + this->sort_list1 = sort_list1; + this->sort_list2 = sort_list2; + this->select_key = select_key; + select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + valid = true; + } + + void reset_search() + { + entry_mode = false; + search_string = ""; + saved_list1.clear(); + saved_list2.clear(); + } + + void reset_all() + { + reset_search(); + valid = false; + sort_list1 = NULL; + sort_list2 = NULL; + viewscreen = NULL; + select_key = 's'; + } + + void clear_search() + { + if (saved_list1.size() > 0) + { + *sort_list1 = saved_list1; + if (sort_list2 != NULL) + *sort_list2 = saved_list2; + } + } + + void do_search() + { + if (search_string.length() == 0) + { + clear_search(); + return; + } + + if (saved_list1.size() == 0) + { + saved_list1 = *sort_list1; + if (sort_list2 != NULL) + saved_list2 = *sort_list2; + } + sort_list1->clear(); + if (sort_list2 != NULL) + sort_list2->clear(); + + string search_string_l = toLower(search_string); + for (int i = 0; i < saved_list1.size(); i++ ) + { + T *element = saved_list1[i]; + string desc = toLower(get_element_description(element)); + if (desc.find(search_string_l) != string::npos) + { + sort_list1->push_back(element); + if (sort_list2 != NULL) + sort_list2->push_back(saved_list2[i]); + } + } + + *cursor_pos = 0; + } + + virtual bool process_input(const set *input) + { + bool key_processed = true; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += last_token - ascii_to_enum_offset; + do_search(); + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + do_search(); + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + entry_mode = false; + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) + || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + entry_mode = false; + key_processed = false; + } + } + else if (input->count(select_token)) + { + entry_mode = true; + } + else + { + key_processed = false; + } + + return key_processed; + } + + + virtual string get_element_description(T *element) const = 0; + virtual void render () const = 0; + + virtual void do_post_update_check() + { + if (redo_search) + { + do_search(); + redo_search = false; + } + } + + void print_search_option(int x) const + { + OutputString((entry_mode) ? 4 : 12, x, gps->dimy - 2, string(1, select_key)); + OutputString((entry_mode) ? 10 : 15, x, gps->dimy - 2, ": Search"); + if (search_string.length() > 0 || entry_mode) + OutputString(15, x, gps->dimy - 2, ": " + search_string); + if (entry_mode) + OutputString(10, x, gps->dimy - 2, "_"); + } }; -string search_struct::search_string = ""; -bool search_struct::entry_mode = false; - -struct stocks_search : df::viewscreen_storesst, search_struct +template +struct search_hook : T { - typedef df::viewscreen_storesst interpose_base; - - static vector saved_items; - - static void reset_search() - { - entry_mode = false; - search_string = ""; - saved_items.clear(); - } - - void clear_search() - { - if (saved_items.size() > 0) - { - items = saved_items; - } - } - - - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - int x = 1; - print_search_option(x); - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - if (in_group_mode) - { - INTERPOSE_NEXT(feed)(input); - return; - } - - - bool string_changed = false; - - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !in_right_list) - { - saved_items.clear(); - entry_mode = false; - if (search_string.length() > 0) - string_changed = true; - INTERPOSE_NEXT(feed)(input); - } - else - { - if (!process_input(interface_key::CUSTOM_S, input, string_changed) && !entry_mode) - { - INTERPOSE_NEXT(feed)(input); - if (in_group_mode) - { - clear_search(); - reset_search(); - } - } - } - - if (string_changed) - { - if (search_string.length() == 0) - { - clear_search(); - return; - } - - if (saved_items.size() == 0 && items.size() > 0) - { - saved_items = items; - } - items.clear(); - - for (int i = 0; i < saved_items.size(); i++ ) - { - string search_string_l = toLower(search_string); - string desc = Items::getDescription(saved_items[i], 0, true); - if (desc.find(search_string_l) != string::npos) - { - items.push_back(saved_items[i]); - } - } - - item_cursor = 0; - } - } - + typedef T interpose_base; + + static V module; + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + module.init(this); + if (!module.process_input(input)) + { + INTERPOSE_NEXT(feed)(input); + module.do_post_update_check(); + } + + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + module.init(this); + INTERPOSE_NEXT(render)(); + module.render(); + } }; -vector stocks_search::saved_items; - +template V search_hook ::module; -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, feed); -IMPLEMENT_VMETHOD_INTERPOSE(stocks_search, render); +// +// END: Base Search functionality +// -struct unitlist_search : df::viewscreen_unitlistst, search_struct +// +// START: Stocks screen search +// +struct stocks_search : search_parent { - typedef df::viewscreen_unitlistst interpose_base; - - static vector saved_units; - static vector saved_jobs; - - - static void reset_search() - { - entry_mode = false; - search_string = ""; - saved_units.clear(); - saved_jobs.clear(); - } - - void clear_search() - { - if (saved_units.size() > 0) - { - units[page] = saved_units; - jobs[page] = saved_jobs; - } - } - - void do_search() - { - if (search_string.length() == 0) - { - clear_search(); - return; - } - - while(1) - { - if (saved_units.size() == 0) - { - saved_units = units[page]; - saved_jobs = jobs[page]; - } - units[page].clear(); - jobs[page].clear(); - - for (int i = 0; i < saved_units.size(); i++ ) - { - df::unit *unit = saved_units[i]; - string search_string_l = toLower(search_string); - string name = toLower(Translation::TranslateName(Units::getVisibleName(unit), false)); - if (name.find(search_string_l) != string::npos) - { - units[page].push_back(unit); - jobs[page].push_back(saved_jobs[i]); - } - } - - if (units[page].size() > 0) - { - cursor_pos[page] = 0; - break; - } - - search_string.erase(search_string.length()-1); - } - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) - { - bool string_changed = false; - if (!process_input(interface_key::CUSTOM_S, input, string_changed)) - { - if (!entry_mode) - { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) - { - clear_search(); - reset_search(); - } - INTERPOSE_NEXT(feed)(input); - } - } - else if (string_changed) - do_search(); - } - - DEFINE_VMETHOD_INTERPOSE(void, render, ()) - { - INTERPOSE_NEXT(render)(); - - if (units[page].size()) - { - int x = 28; - print_search_option(x); - } - } + virtual void render() const + { + if (!viewscreen->in_group_mode) + print_search_option(1); + else + { + int x = 1; + OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); + } + } + + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + virtual bool process_input(const set *input) + { + if (viewscreen->in_group_mode) + return false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + saved_list1.clear(); + entry_mode = false; + if (search_string.length() > 0) + redo_search = true; + + return false; + } + else + return search_parent::process_input(input) || entry_mode; + + return true; + } + + virtual void do_post_update_check() + { + if (viewscreen->in_group_mode) + { + clear_search(); + reset_search(); + } + else + search_parent::do_post_update_check(); + } + + virtual void init(df::viewscreen_storesst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->item_cursor, &screen->items); + } + } }; -vector unitlist_search::saved_units; -vector unitlist_search::saved_jobs; +typedef search_hook stocks_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); + +// +// END: Stocks screen search +// -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search, render); +// +// START: Unit screen search +// +struct unitlist_search : search_parent +{ + virtual void render() const + { + print_search_option(28); + } + + virtual string get_element_description(df::unit *element) const + { + return Translation::TranslateName(Units::getVisibleName(element), false); + } + + virtual bool process_input(const set *input) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + if (!entry_mode) + { + clear_search(); + reset_search(); + return false; + } + } + else + return search_parent::process_input(input) || entry_mode; + + return true; + } + + virtual void init(df::viewscreen_unitlistst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + } + } +}; + +typedef search_hook unitlist_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); + +// +// END: Unit screen search +// DFHACK_PLUGIN("search"); @@ -304,8 +359,8 @@ DFHACK_PLUGIN("search"); DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - if (!gps || !INTERPOSE_HOOK(unitlist_search, feed).apply() || !INTERPOSE_HOOK(unitlist_search, render).apply() - || !INTERPOSE_HOOK(stocks_search, feed).apply() || !INTERPOSE_HOOK(stocks_search, render).apply()) + if (!gps || !INTERPOSE_HOOK(unitlist_search_hook, feed).apply() || !INTERPOSE_HOOK(unitlist_search_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; @@ -313,15 +368,16 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Sun, 21 Oct 2012 13:42:55 +0300 Subject: [PATCH 031/389] 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 032/389] 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 bf01ecd206f4f3522d944162a1e806b7b45272a9 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 21 Oct 2012 21:32:01 +1300 Subject: [PATCH 033/389] Added Trade screen --- plugins/search.cpp | 365 ++++++++++++++++++++++++++++++--------------- 1 file changed, 245 insertions(+), 120 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index e911a73be..294ebe7d2 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -9,6 +9,7 @@ #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_tradegoodsst.h" #include "df/interface_key.h" using std::set; @@ -31,25 +32,93 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) // START: Base Search functionality // template -struct search_parent +class search_parent { - vector *sort_list1; - vector *sort_list2; - int *cursor_pos; - char select_key; +public: + void reset_all() + { + reset_search(); + valid = false; + sort_list1 = NULL; + sort_list2 = NULL; + viewscreen = NULL; + select_key = 's'; + } + + virtual bool process_input(set *input) + { + if (lock != NULL && lock != this) + return false; + + if (!should_check_input(input)) + return false; + + bool key_processed = true; + + if (entry_mode) + { + df::interface_key last_token = *input->rbegin(); + if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) + { + search_string += last_token - ascii_to_enum_offset; + do_search(); + } + else if (last_token == interface_key::STRING_A000) + { + if (search_string.length() > 0) + { + search_string.erase(search_string.length()-1); + do_search(); + } + } + else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) + { + end_entry_mode(); + } + else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) + || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + { + end_entry_mode(); + key_processed = false; + } + } + else if (input->count(select_token)) + { + start_entry_mode(); + } + else if (input->count((df::interface_key) (select_token + shift_offset))) + { + clear_search(); + } + else + { + key_processed = false; + } + + return key_processed || entry_mode; + } + + virtual void do_post_update_check() + { + if (redo_search) + { + do_search(); + redo_search = false; + } + } + + static search_parent *lock; + +protected: const S *viewscreen; + vector saved_list1; + vector saved_list2; bool valid; - bool entry_mode; bool redo_search; string search_string; - vector saved_list1; - vector saved_list2; - df::interface_key select_token; - const int ascii_to_enum_offset; - - search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0') + search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') { reset_all(); } @@ -63,23 +132,30 @@ struct search_parent select_token = (df::interface_key) (ascii_to_enum_offset + select_key); valid = true; } + + bool is_entry_mode() + { + return entry_mode; + } + + void start_entry_mode() + { + entry_mode = true; + lock = this; + } - void reset_search() + void end_entry_mode() { entry_mode = false; - search_string = ""; - saved_list1.clear(); - saved_list2.clear(); + lock = NULL; } - void reset_all() + void reset_search() { - reset_search(); - valid = false; - sort_list1 = NULL; - sort_list2 = NULL; - viewscreen = NULL; - select_key = 's'; + end_entry_mode(); + search_string = ""; + saved_list1.clear(); + saved_list2.clear(); } void clear_search() @@ -90,6 +166,7 @@ struct search_parent if (sort_list2 != NULL) *sort_list2 = saved_list2; } + search_string = ""; } void do_search() @@ -126,75 +203,45 @@ struct search_parent *cursor_pos = 0; } - virtual bool process_input(const set *input) + virtual bool should_check_input(set *input) { - bool key_processed = true; - - if (entry_mode) - { - df::interface_key last_token = *input->rbegin(); - if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) - { - search_string += last_token - ascii_to_enum_offset; - do_search(); - } - else if (last_token == interface_key::STRING_A000) - { - if (search_string.length() > 0) - { - search_string.erase(search_string.length()-1); - do_search(); - } - } - else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) - { - entry_mode = false; - } - else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) - || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) - { - entry_mode = false; - key_processed = false; - } - } - else if (input->count(select_token)) - { - entry_mode = true; - } - else - { - key_processed = false; - } - - return key_processed; + return true; } - - virtual string get_element_description(T *element) const = 0; - virtual void render () const = 0; - - virtual void do_post_update_check() + void print_search_option(int x, int y = -1) const { - if (redo_search) - { - do_search(); - redo_search = false; - } - } + if (y == -1) + y = gps->dimy - 2; - void print_search_option(int x) const - { - OutputString((entry_mode) ? 4 : 12, x, gps->dimy - 2, string(1, select_key)); - OutputString((entry_mode) ? 10 : 15, x, gps->dimy - 2, ": Search"); + OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); + OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); if (search_string.length() > 0 || entry_mode) - OutputString(15, x, gps->dimy - 2, ": " + search_string); + OutputString(15, x, y, ": " + search_string); if (entry_mode) - OutputString(10, x, gps->dimy - 2, "_"); + OutputString(10, x, y, "_"); } + + virtual string get_element_description(T *element) const = 0; + virtual void render () const = 0; + +private: + vector *sort_list1; + vector *sort_list2; + int *cursor_pos; + char select_key; + + bool entry_mode; + + df::interface_key select_token; + const int ascii_to_enum_offset; + const int shift_offset; + }; +template search_parent *search_parent ::lock = NULL; -template + +template struct search_hook : T { typedef T interpose_base; @@ -220,7 +267,7 @@ struct search_hook : T } }; -template V search_hook ::module; +template V search_hook ::module; // @@ -232,8 +279,10 @@ template V search_hook ::module; // // START: Stocks screen search // -struct stocks_search : search_parent +class stocks_search : public search_parent { +public: + virtual void render() const { if (!viewscreen->in_group_mode) @@ -245,31 +294,6 @@ struct stocks_search : search_parent } } - virtual string get_element_description(df::item *element) const - { - return Items::getDescription(element, 0, true); - } - - virtual bool process_input(const set *input) - { - if (viewscreen->in_group_mode) - return false; - - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) - { - saved_list1.clear(); - entry_mode = false; - if (search_string.length() > 0) - redo_search = true; - - return false; - } - else - return search_parent::process_input(input) || entry_mode; - - return true; - } - virtual void do_post_update_check() { if (viewscreen->in_group_mode) @@ -290,6 +314,30 @@ struct stocks_search : search_parent } } + +private: + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + virtual bool should_check_input(set *input) + { + if (viewscreen->in_group_mode) + return false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + saved_list1.clear(); + end_entry_mode(); + if (search_string.length() > 0) + redo_search = true; + + return false; + } + + return true; + } }; @@ -306,51 +354,120 @@ IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // START: Unit screen search // -struct unitlist_search : search_parent +class unitlist_search : public search_parent { +public: + virtual void render() const { print_search_option(28); } + virtual void init(df::viewscreen_unitlistst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + } + } + +private: virtual string get_element_description(df::unit *element) const { return Translation::TranslateName(Units::getVisibleName(element), false); } - virtual bool process_input(const set *input) + virtual bool should_check_input(set *input) { if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) { - if (!entry_mode) + if (!is_entry_mode()) { clear_search(); reset_search(); - return false; } + else + input->clear(); + + return false; } - else - return search_parent::process_input(input) || entry_mode; return true; } - virtual void init(df::viewscreen_unitlistst *screen) +}; + +typedef search_hook unitlist_search_hook; +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); + +// +// END: Unit screen search +// + + +// +// START: Trade screen search +// +class trade_search_base : public search_parent +{ + +private: + virtual string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } +}; + + +class trade_search_merc : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(2, 26); + } + + virtual void init(df::viewscreen_tradegoodsst *screen) { if (!valid) { viewscreen = screen; - search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); + search_parent::init(&screen->trader_cursor, &screen->trader_items, NULL, 'q'); } } }; -typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); +typedef search_hook trade_search_merc_hook; +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); + + +class trade_search_fort : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(42, 26); + } + + virtual void init(df::viewscreen_tradegoodsst *screen) + { + if (!valid) + { + viewscreen = screen; + search_parent::init(&screen->broker_cursor, &screen->broker_items, NULL, 'w'); + } + } +}; + +typedef search_hook trade_search_fort_hook; +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); +IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // -// END: Unit screen search +// END: Trade screen search // @@ -360,6 +477,8 @@ 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()) out.printerr("Could not insert Search hooks!\n"); @@ -370,6 +489,10 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { INTERPOSE_HOOK(unitlist_search_hook, feed).remove(); INTERPOSE_HOOK(unitlist_search_hook, render).remove(); + INTERPOSE_HOOK(trade_search_merc_hook, feed).remove(); + INTERPOSE_HOOK(trade_search_merc_hook, render).remove(); + INTERPOSE_HOOK(trade_search_fort_hook, feed).remove(); + INTERPOSE_HOOK(trade_search_fort_hook, render).remove(); INTERPOSE_HOOK(stocks_search_hook, feed).remove(); INTERPOSE_HOOK(stocks_search_hook, render).remove(); return CR_OK; @@ -378,6 +501,8 @@ 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(); return CR_OK; } From f501ae074870ffd629a9e086d2acdc0026bd5d66 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 01:09:10 +1300 Subject: [PATCH 034/389] Bug fix on unit sort --- plugins/search.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 294ebe7d2..9a9a28c40 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -385,7 +385,7 @@ private: if (!is_entry_mode()) { clear_search(); - reset_search(); + reset_all(); } else input->clear(); From dfa3a520fd2e7243413d5dc38d253fd17e072c1e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 21 Oct 2012 16:34:13 -0500 Subject: [PATCH 035/389] 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 c433a8eeff35911af1073beac87adbc7aab375c8 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 02:42:17 +1300 Subject: [PATCH 036/389] Better handling of Trade screen. Tracks marked items and handles re-orders is sort plugin is used to sort filtered list. --- plugins/search.cpp | 88 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 73 insertions(+), 15 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 9a9a28c40..81785d914 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -31,7 +31,7 @@ void OutputString(int8_t color, int &x, int y, const std::string &text) // // START: Base Search functionality // -template +template class search_parent { public: @@ -43,6 +43,7 @@ public: sort_list2 = NULL; viewscreen = NULL; select_key = 's'; + track_secondary_values = false; } virtual bool process_input(set *input) @@ -111,11 +112,13 @@ public: protected: const S *viewscreen; - vector saved_list1; - vector saved_list2; + vector saved_list1, reference_list; + vector saved_list2; + vector saved_indexes; bool valid; bool redo_search; + bool track_secondary_values; string search_string; search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') @@ -123,13 +126,14 @@ protected: reset_all(); } - virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') { this->cursor_pos = cursor_pos; this->sort_list1 = sort_list1; this->sort_list2 = sort_list2; this->select_key = select_key; select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + track_secondary_values = false; valid = true; } @@ -156,6 +160,43 @@ protected: search_string = ""; saved_list1.clear(); saved_list2.clear(); + reference_list.clear(); + saved_indexes.clear(); + } + + void update_secondary_values() + { + if (sort_list2 != NULL && track_secondary_values) + { + bool list_has_been_sorted = (sort_list1->size() == reference_list.size() + && *sort_list1 != reference_list); + + for (int 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++) + { + if ((*sort_list1)[j] == reference_list[i]) + { + adjusted_item_index = j; + break; + } + } + } + + saved_list2[saved_indexes[i]] = (*sort_list2)[adjusted_item_index]; + } + saved_indexes.clear(); + } + } + + //Used to work out if filtered list has been sorted after filtering + void store_reference_values() + { + if (track_secondary_values) + reference_list = *sort_list1; } void clear_search() @@ -164,8 +205,12 @@ protected: { *sort_list1 = saved_list1; if (sort_list2 != NULL) + { + update_secondary_values(); *sort_list2 = saved_list2; + } } + store_reference_values(); search_string = ""; } @@ -183,23 +228,35 @@ protected: if (sort_list2 != NULL) saved_list2 = *sort_list2; } + else + update_secondary_values(); + sort_list1->clear(); if (sort_list2 != NULL) + { sort_list2->clear(); + saved_indexes.clear(); + } string search_string_l = toLower(search_string); for (int i = 0; i < saved_list1.size(); i++ ) { - T *element = saved_list1[i]; + T element = saved_list1[i]; string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) { sort_list1->push_back(element); if (sort_list2 != NULL) + { sort_list2->push_back(saved_list2[i]); + if (track_secondary_values) + saved_indexes.push_back(i); + } } } + store_reference_values(); + *cursor_pos = 0; } @@ -221,12 +278,12 @@ protected: OutputString(10, x, y, "_"); } - virtual string get_element_description(T *element) const = 0; + virtual string get_element_description(T element) const = 0; virtual void render () const = 0; private: - vector *sort_list1; - vector *sort_list2; + vector *sort_list1; + vector *sort_list2; int *cursor_pos; char select_key; @@ -237,7 +294,6 @@ private: const int shift_offset; }; - template search_parent *search_parent ::lock = NULL; @@ -279,7 +335,7 @@ template V search_hook ::module; // // START: Stocks screen search // -class stocks_search : public search_parent +class stocks_search : public search_parent { public: @@ -354,7 +410,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); // // START: Unit screen search // -class unitlist_search : public search_parent +class unitlist_search : public search_parent { public: @@ -410,7 +466,7 @@ IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); // // START: Trade screen search // -class trade_search_base : public search_parent +class trade_search_base : public search_parent { private: @@ -434,7 +490,8 @@ public: if (!valid) { viewscreen = screen; - search_parent::init(&screen->trader_cursor, &screen->trader_items, NULL, 'q'); + search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); + track_secondary_values = true; } } }; @@ -457,7 +514,8 @@ public: if (!valid) { viewscreen = screen; - search_parent::init(&screen->broker_cursor, &screen->broker_items, NULL, 'w'); + search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); + track_secondary_values = true; } } }; @@ -505,4 +563,4 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch trade_search_fort_hook::module.reset_all(); stocks_search_hook::module.reset_all(); return CR_OK; -} +} \ No newline at end of file From 2fcceaa65e8f3ff72c70af319fff09330b8baa8e Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Mon, 22 Oct 2012 21:44:02 +1300 Subject: [PATCH 037/389] Set priority over manipulator plugin Include animal type is search --- plugins/search.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 81785d914..6b9a94947 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,9 +7,10 @@ #include -#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_tradegoodsst.h" +#include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" using std::set; @@ -431,12 +432,16 @@ public: private: virtual string get_element_description(df::unit *element) const { - return Translation::TranslateName(Units::getVisibleName(element), false); + string desc = Translation::TranslateName(Units::getVisibleName(element), false); + if (viewscreen->page == 1) + desc += Units::getProfessionName(element); + + return desc; } virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) { if (!is_entry_mode()) { @@ -455,14 +460,22 @@ private: }; typedef search_hook unitlist_search_hook; -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, feed); -IMPLEMENT_VMETHOD_INTERPOSE(unitlist_search_hook, render); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, feed, 100); +IMPLEMENT_VMETHOD_INTERPOSE_PRIO(unitlist_search_hook, render, 100); // // END: Unit screen search // +// +// TODO: Animals screen search +// + +// +// END: Animals screen search +// + // // START: Trade screen search // From 94898a73fb0be419c93b2237eae197e34dbea50d Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Tue, 23 Oct 2012 21:20:51 +1300 Subject: [PATCH 038/389] Add Search plugin comments --- plugins/search.cpp | 66 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 12 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 6b9a94947..fdc788955 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -1,5 +1,3 @@ -// Dwarf Manipulator - a Therapist-style labor editor - #include #include #include @@ -7,7 +5,7 @@ #include -#include "df/viewscreen_petst.h" +//#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" @@ -22,20 +20,35 @@ using namespace df::enums; using df::global::gps; +/* +Search Plugin + +A plugin that adds a "Search" hotkey to some screens (Units, Trade and Stocks) +that allows filtering of the list items by a typed query. + +Works by manipulating the vector(s) that the list based viewscreens use to store +their items. When a search is started the plugin saves the original vectors and +with each keystroke creates a new filtered vector off the saves for the screen +to use. +*/ + + void OutputString(int8_t color, int &x, int y, const std::string &text) { Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); x += text.length(); } - // // START: Base Search functionality // + +// Parent class that does most of the work template class search_parent { public: + // Called each time you enter or leave a searchable screen. Resets everything. void reset_all() { reset_search(); @@ -47,11 +60,15 @@ public: track_secondary_values = false; } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { + // If the page has two search options (Trade screen), only allow one to operate + // at a time if (lock != NULL && lock != this) return false; + // Allows custom preprocessing for each screen if (!should_check_input(input)) return false; @@ -59,14 +76,18 @@ public: if (entry_mode) { + // Query typing mode + df::interface_key last_token = *input->rbegin(); if (last_token >= interface_key::STRING_A032 && last_token <= interface_key::STRING_A126) { + // Standard character search_string += last_token - ascii_to_enum_offset; do_search(); } else if (last_token == interface_key::STRING_A000) { + // Backspace if (search_string.length() > 0) { search_string.erase(search_string.length()-1); @@ -75,31 +96,40 @@ public: } else if (input->count(interface_key::SELECT) || input->count(interface_key::LEAVESCREEN)) { + // ENTER or ESC: leave typing mode end_entry_mode(); } else if (input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN) || input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT)) { + // Arrow key pressed. Leave entry mode and allow screen to process key end_entry_mode(); key_processed = false; } } + // Not in query typing mode else if (input->count(select_token)) { + // Hotkey pressed, enter typing mode start_entry_mode(); } else if (input->count((df::interface_key) (select_token + shift_offset))) { + // Shift + Hotkey pressed, clear query clear_search(); } else { + // Not a key for us, pass it on to the screen key_processed = false; } - return key_processed || entry_mode; + return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode } + // Called if the search should be redone after the screen processes the keystroke. + // Used by the stocks screen where changing categories should redo the search on + // the new category. virtual void do_post_update_check() { if (redo_search) @@ -165,6 +195,10 @@ protected: saved_indexes.clear(); } + // If the second vector is editable (i.e. Trade screen vector used for marking). then it may + // have been edited while the list was filtered. We have to update the original unfiltered + // list with these values. Uses a stored reference vector to determine if the list has been + // reordered after filtering, in which case indexes must be remapped. void update_secondary_values() { if (sort_list2 != NULL && track_secondary_values) @@ -193,13 +227,14 @@ protected: } } - //Used to work out if filtered list has been sorted after filtering + // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering void store_reference_values() { if (track_secondary_values) reference_list = *sort_list1; } + // Shortcut to clear the search immediately void clear_search() { if (saved_list1.size() > 0) @@ -215,6 +250,7 @@ protected: search_string = ""; } + // The actual sort void do_search() { if (search_string.length() == 0) @@ -225,13 +261,15 @@ protected: if (saved_list1.size() == 0) { + // On first run, save the original list saved_list1 = *sort_list1; if (sort_list2 != NULL) saved_list2 = *sort_list2; } else - update_secondary_values(); + update_secondary_values(); // Update original list with any modified values + // Clear viewscreen vectors sort_list1->clear(); if (sort_list2 != NULL) { @@ -251,12 +289,12 @@ protected: { sort_list2->push_back(saved_list2[i]); if (track_secondary_values) - saved_indexes.push_back(i); + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed } } } - store_reference_values(); + store_reference_values(); //Keep a copy, in case user sorts new list *cursor_pos = 0; } @@ -266,6 +304,7 @@ protected: return true; } + // Display hotkey message void print_search_option(int x, int y = -1) const { if (y == -1) @@ -297,7 +336,7 @@ private: }; template search_parent *search_parent ::lock = NULL; - +// Parent struct for the hooks template struct search_hook : T { @@ -355,6 +394,7 @@ public: { if (viewscreen->in_group_mode) { + // Disable search if item lists are grouped clear_search(); reset_search(); } @@ -385,6 +425,7 @@ private: if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) { + // Redo search if category changes saved_list1.clear(); end_entry_mode(); if (search_string.length() > 0) @@ -434,7 +475,7 @@ private: { string desc = Translation::TranslateName(Units::getVisibleName(element), false); if (viewscreen->page == 1) - desc += Units::getProfessionName(element); + desc += Units::getProfessionName(element); // Check animal type too return desc; } @@ -445,11 +486,12 @@ private: { if (!is_entry_mode()) { + // Changing screens, reset search clear_search(); reset_all(); } else - input->clear(); + input->clear(); // Ignore cursor keys when typing return false; } From 2d83b4fa39f58a52104206e4ce732dc094abca5c Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 24 Oct 2012 14:07:46 -0500 Subject: [PATCH 039/389] 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 040/389] 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 041/389] 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 042/389] 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 043/389] 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 044/389] 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 045/389] 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 046/389] 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 047/389] 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 048/389] 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 049/389] 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 050/389] 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 051/389] 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 052/389] 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 053/389] 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 054/389] 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 055/389] 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 056/389] 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 057/389] 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
      -
    • lair
    • -
    • mode
    • +
    • Mode switch and reclaim
    • -
    • Visualizer and data export
        -
      • ssense / stonesense
      • -
      • mapexport
      • -
      • dwarfexport
      • +
      • Visualizer and data export
      • -
      • Job management
          -
        • job
        • -
        • job-material
        • -
        • job-duplicate
        • -
        • workflow
            -
          • Function
          • -
          • Constraint examples
          • +
          • Job management
          • -
          • Fortress activity management
              -
            • seedwatch
            • -
            • zone
                -
              • Usage with single units
              • -
              • Usage with filters
              • -
              • Mass-renaming
              • -
              • Cage zones
              • -
              • Examples
              • +
              • Fortress activity management
              • -
              • Other
              • -
              • Scripts
                  -
                • fix/*
                • -
                • gui/*
                • -
                • quicksave
                • -
                • setfps
                • -
                • siren
                • -
                • growcrops
                • -
                • removebadthoughts
                • -
                • slayrace
                • -
                • magmasource
                • -
                • digfort
                • -
                • superdwarf
                • -
                • drainaquifer
                • -
                • deathcause
                • +
                • Scripts
                • -
                • In-game interface 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 159/389] 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 160/389] 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 161/389] 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 162/389] 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 163/389] 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 164/389] 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 165/389] 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 166/389] 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 167/389] 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 168/389] 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 169/389] 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 170/389] 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 171/389] 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 172/389] 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 173/389] 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 174/389] 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 175/389] 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 176/389] 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 177/389] 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 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 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 205/389] 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 206/389] 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 207/389] 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 208/389] 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 209/389] 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 210/389] 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 211/389] 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 212/389] 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 213/389] 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 214/389] 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 215/389] 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 216/389] 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 217/389] 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 218/389] 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 219/389] 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 220/389] 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 221/389] 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 222/389] 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 223/389] 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 224/389] 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 225/389] 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 226/389] 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 227/389] 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 228/389] 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 229/389] 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 230/389] 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 231/389] 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 232/389] 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 233/389] 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 234/389] 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 235/389] 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 236/389] 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 237/389] 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 238/389] 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 239/389] 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 240/389] 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 241/389] 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 242/389] 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 243/389] 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 244/389] 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 245/389] 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 246/389] 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 247/389] 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 248/389] 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 249/389] 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 250/389] 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 251/389] 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 252/389] 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 253/389] 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 254/389] 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 255/389] 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 256/389] 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 257/389] 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 258/389] 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 259/389] 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 260/389] 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 261/389] 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 262/389] 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 263/389] 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 264/389] 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 265/389] 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 266/389] 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 267/389] 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 268/389] 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 269/389] 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 270/389] 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 271/389] 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 272/389] 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 273/389] 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 274/389] 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 275/389] 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 276/389] 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 277/389] 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 278/389] 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 279/389] 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 280/389] 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 281/389] 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 282/389] 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 283/389] 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 284/389] 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 285/389] 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 286/389] 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 287/389] 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 288/389] 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 289/389] 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 290/389] 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 291/389] 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 292/389] 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 293/389] 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 294/389] 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 295/389] 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 296/389] 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 297/389] 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 298/389] 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 299/389] 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 300/389] 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 301/389] 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 302/389] 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 303/389] 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 304/389] 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 305/389] 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 306/389] 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 307/389] 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 308/389] 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 309/389] 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 310/389] 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 311/389] 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 312/389] 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 313/389] 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 314/389] 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 315/389] 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 316/389] 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 317/389] 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 318/389] 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 319/389] 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 320/389] 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 321/389] 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 322/389] 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 323/389] 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 324/389] 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 325/389] 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 326/389] 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 327/389] 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 328/389] 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 329/389] 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 330/389] 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 331/389] 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 332/389] 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 333/389] 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 334/389] 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 335/389] 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 336/389] 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 337/389] 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 338/389] 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 339/389] 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 91ee8ac020eafbda852014f59fa38f0854b14737 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Thu, 27 Dec 2012 22:23:13 +1300 Subject: [PATCH 340/389] Refactor search to handle more types of screens cleanly. Added search to screens: * Animals * Military positions assignment * Announcements * Room list * Job list * Burrow assignment --- plugins/search.cpp | 1535 +++++++++++++++++++++++++++++++++----------- 1 file changed, 1172 insertions(+), 363 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index cf1798dcf..d3983f45b 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -5,14 +5,28 @@ #include -//#include "df/viewscreen_petst.h" +#include "df/viewscreen_announcelistst.h" +#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" #include "df/viewscreen_layer_stockpilest.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_buildinglistst.h" +#include "df/viewscreen_joblistst.h" #include "df/interface_key.h" #include "df/interfacest.h" #include "df/layer_object_listst.h" +#include "df/job.h" +#include "df/report.h" +#include "modules/Job.h" +#include "df/global_objects.h" +#include "df/viewscreen_dwarfmodest.h" +#include "modules/Gui.h" +#include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/unit_misc_trait.h" using std::set; using std::vector; @@ -51,25 +65,56 @@ static bool is_live_screen(const df::viewscreen *screen) return false; } +static string get_unit_description(df::unit *unit) +{ + string desc; + auto name = Units::getVisibleName(unit); + if (name->has_name) + desc = Translation::TranslateName(name, false); + desc += ", " + Units::getProfessionName(unit); // Check animal type too + + return desc; +} + + // -// START: Base Search functionality +// START: Generic Search functionality // -// Parent class that does most of the work -template -class search_parent +template +class search_generic { public: + bool init(S *screen) + { + if (screen != viewscreen && !reset_on_change()) + return false; + + if (!can_init(screen)) + return false; + + if (!is_valid()) + { + this->viewscreen = screen; + this->cursor_pos = get_viewscreen_cursor(); + this->primary_list = get_primary_list(); + this->select_key = get_search_select_key(); + select_token = (df::interface_key) (ascii_to_enum_offset + select_key); + valid = true; + do_post_init(); + } + + return true; + } + // Called each time you enter or leave a searchable screen. Resets everything. - void reset_all() + virtual void reset_all() { reset_search(); valid = false; - sort_list1 = NULL; - sort_list2 = NULL; + primary_list = NULL; viewscreen = NULL; select_key = 's'; - track_secondary_values = false; } bool reset_on_change() @@ -153,48 +198,35 @@ public: return key_processed || entry_mode; // Only pass unrecognized keys down if not in typing mode } - // Called if the search should be redone after the screen processes the keystroke. - // Used by the stocks screen where changing categories should redo the search on - // the new category. - virtual void do_post_update_check() + // Called after a keystroke has been processed + virtual void do_post_input_feed() { - if (redo_search) - { - do_search(); - redo_search = false; - } } - static search_parent *lock; + static search_generic *lock; protected: - const S *viewscreen; - vector saved_list1, reference_list; - vector saved_list2; - vector *sort_list2; - vector saved_indexes; - - bool redo_search; - bool track_secondary_values; - string search_string; + virtual string get_element_description(T element) const = 0; + virtual void render() const = 0; + virtual int32_t *get_viewscreen_cursor() = 0; + virtual vector *get_primary_list() = 0; - search_parent() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') + search_generic() : ascii_to_enum_offset(interface_key::STRING_A048 - '0'), shift_offset('A' - 'a') { reset_all(); } - virtual void init(int *cursor_pos, vector *sort_list1, vector *sort_list2 = NULL, char select_key = 's') + virtual bool can_init(S *screen) + { + return true; + } + + virtual void do_post_init() { - this->cursor_pos = cursor_pos; - this->sort_list1 = sort_list1; - this->sort_list2 = sort_list2; - this->select_key = select_key; - select_token = (df::interface_key) (ascii_to_enum_offset + select_key); - track_secondary_values = false; - valid = true; + } - bool is_entry_mode() + bool in_entry_mode() { return entry_mode; } @@ -204,84 +236,59 @@ protected: entry_mode = true; lock = this; } - + void end_entry_mode() { entry_mode = false; lock = NULL; } - void reset_search() + virtual char get_search_select_key() + { + return 's'; + } + + virtual void reset_search() { end_entry_mode(); search_string = ""; saved_list1.clear(); - saved_list2.clear(); - reference_list.clear(); - saved_indexes.clear(); } - // If the second vector is editable (i.e. Trade screen vector used for marking). then it may - // have been edited while the list was filtered. We have to update the original unfiltered - // list with these values. Uses a stored reference vector to determine if the list has been - // reordered after filtering, in which case indexes must be remapped. - void update_secondary_values() + // Shortcut to clear the search immediately + virtual void clear_search() { - if (sort_list2 != NULL && track_secondary_values) + if (saved_list1.size() > 0) { - bool list_has_been_sorted = (sort_list1->size() == reference_list.size() - && *sort_list1 != reference_list); + *primary_list = saved_list1; + saved_list1.clear(); + } + search_string = ""; + } - 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 < sort_list1->size(); j++) - { - if ((*sort_list1)[j] == reference_list[i]) - { - adjusted_item_index = j; - break; - } - } - } + virtual void save_original_values() + { + saved_list1 = *primary_list; + } + + virtual void do_pre_incremental_search() + { - update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); - } - saved_indexes.clear(); - } } - virtual void update_saved_secondary_list_item(size_t i, size_t j) + virtual void clear_viewscreen_vectors() { - saved_list2[i] = (*sort_list2)[j]; + primary_list->clear(); } - // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering - void store_reference_values() + virtual void add_to_filtered_list(size_t i) { - if (track_secondary_values) - reference_list = *sort_list1; + primary_list->push_back(saved_list1[i]); } - // Shortcut to clear the search immediately - virtual void clear_search() + virtual void do_post_search() { - if (saved_list1.size() > 0) - { - *sort_list1 = saved_list1; - if (sort_list2 != NULL) - { - update_secondary_values(); - *sort_list2 = saved_list2; - } - saved_list1.clear(); - saved_list2.clear(); - } - store_reference_values(); - search_string = ""; } // The actual sort @@ -294,22 +301,12 @@ protected: } if (saved_list1.size() == 0) - { // On first run, save the original list - saved_list1 = *sort_list1; - if (sort_list2 != NULL) - saved_list2 = *sort_list2; - } + save_original_values(); else - update_secondary_values(); // Update original list with any modified values + do_pre_incremental_search(); - // Clear viewscreen vectors - sort_list1->clear(); - if (sort_list2 != NULL) - { - sort_list2->clear(); - saved_indexes.clear(); - } + clear_viewscreen_vectors(); string search_string_l = toLower(search_string); for (size_t i = 0; i < saved_list1.size(); i++ ) @@ -318,17 +315,11 @@ protected: string desc = toLower(get_element_description(element)); if (desc.find(search_string_l) != string::npos) { - sort_list1->push_back(element); - if (sort_list2 != NULL) - { - sort_list2->push_back(saved_list2[i]); - if (track_secondary_values) - saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed - } + add_to_filtered_list(i); } } - store_reference_values(); //Keep a copy, in case user sorts new list + do_post_search(); if (cursor_pos) *cursor_pos = 0; @@ -354,11 +345,13 @@ protected: OutputString(10, x, y, "_"); } - virtual string get_element_description(T element) const = 0; - virtual void render () const = 0; + S *viewscreen; + vector saved_list1, reference_list, *primary_list; + + //bool redo_search; + string search_string; private: - vector *sort_list1; int *cursor_pos; char select_key; bool valid; @@ -369,430 +362,1241 @@ private: const int shift_offset; }; -template search_parent *search_parent ::lock = NULL; -// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V -// but different static modules -template -struct search_hook : T -{ - typedef T interpose_base; +template search_generic *search_generic ::lock = NULL; - static V module; - DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) +// Search class helper for layered screens +template +class layered_search : public search_generic +{ +protected: + virtual bool can_init(S *screen) { - if (!module.init(this)) + auto list = getLayerList(screen); + if (!list->active) { - INTERPOSE_NEXT(feed)(input); - return; - } + if (is_valid()) + { + clear_search(); + reset_all(); + } - if (!module.process_input(input)) - { - INTERPOSE_NEXT(feed)(input); - module.do_post_update_check(); + return false; } + return true; } - DEFINE_VMETHOD_INTERPOSE(void, render, ()) + virtual void do_search() { - bool ok = module.init(this); - INTERPOSE_NEXT(render)(); - if (ok) - module.render(); + search_generic::do_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } + + int32_t *get_viewscreen_cursor() + { + auto list = getLayerList(viewscreen); + return &list->cursor; } -}; - -template V search_hook ::module; + virtual void clear_search() + { + search_generic::clear_search(); + auto list = getLayerList(viewscreen); + list->num_entries = get_primary_list()->size(); + } -// -// END: Base Search functionality -// +private: + static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer) + { + return virtual_cast(vector_get(layer->layer_objects, LIST_ID)); + } +}; -// -// START: Stocks screen search -// -class stocks_search : public search_parent +// Parent class for screens that have more than one primary list to synchronise +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable_generic : public PARENT { -public: +protected: + vector reference_list; + vector saved_indexes; + bool read_only; - virtual void render() const + virtual void update_saved_secondary_list_item(size_t i, size_t j) = 0; + virtual void save_secondary_values() = 0; + virtual void clear_secondary_viewscreen_vectors() = 0; + virtual void add_to_filtered_secondary_lists(size_t i) = 0; + virtual void clear_secondary_saved_lists() = 0; + virtual void reset_secondary_viewscreen_vectors() = 0; + virtual void restore_secondary_values() = 0; + + virtual void do_post_init() { - if (!viewscreen->in_group_mode) - print_search_option(2); - else - { - auto dim = Screen::getWindowSize(); - int x = 2, y = dim.y - 2; - OutputString(15, x, y, "Tab to enable Search"); - } + // If true, secondary list isn't modifiable so don't bother synchronising values + read_only = false; } - virtual void do_post_update_check() + void reset_all() { - if (viewscreen->in_group_mode) - { - // Disable search if item lists are grouped - clear_search(); - reset_search(); - } - else - search_parent::do_post_update_check(); + PARENT::reset_all(); + reference_list.clear(); + saved_indexes.clear(); + reset_secondary_viewscreen_vectors(); } - bool init(df::viewscreen_storesst *screen) + void reset_search() { - if (screen != viewscreen && !reset_on_change()) - return false; + PARENT::reset_search(); + reference_list.clear(); + saved_indexes.clear(); + clear_secondary_saved_lists(); + } - if (!is_valid()) + virtual void clear_search() + { + if (saved_list1.size() > 0) { - viewscreen = screen; - search_parent::init(&screen->item_cursor, &screen->items); + do_pre_incremental_search(); + restore_secondary_values(); } - - return true; + clear_secondary_saved_lists(); + PARENT::clear_search(); + do_post_search(); } + virtual bool is_match(T &a, T &b) = 0; -private: - virtual string get_element_description(df::item *element) const + virtual bool is_match(vector &a, vector &b) = 0; + + void do_pre_incremental_search() { - return Items::getDescription(element, 0, true); - } + PARENT::do_pre_incremental_search(); + if (read_only) + return; - virtual bool should_check_input(set *input) - { - if (viewscreen->in_group_mode) - return false; + bool list_has_been_sorted = (primary_list->size() == reference_list.size() + && !is_match(*primary_list, reference_list)); - if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + for (size_t i = 0; i < saved_indexes.size(); i++) { - // Redo search if category changes - saved_list1.clear(); - end_entry_mode(); - if (search_string.length() > 0) - redo_search = true; + int adjusted_item_index = i; + if (list_has_been_sorted) + { + for (size_t j = 0; j < primary_list->size(); j++) + { + if (is_match((*primary_list)[j], reference_list[i])) + { + adjusted_item_index = j; + break; + } + } + } - return false; + update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); } + saved_indexes.clear(); + } - return true; + void 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); + add_to_filtered_secondary_lists(i); + if (!read_only) + saved_indexes.push_back(i); // Used to map filtered indexes back to original, if needed + } -typedef search_hook stocks_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stocks_search_hook, render); + virtual void do_post_search() + { + if (!read_only) + reference_list = *primary_list; + } -// -// END: Stocks screen search -// + void save_original_values() + { + search_generic::save_original_values(); + save_secondary_values(); + } +}; +// This basic match function is separated out from the generic multi column class, because the +// pets screen, which uses a union in its primary list, will cause a compile failure is this +// match function exists in the generic class +template < class S, class T, class PARENT = search_generic > +class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic +{ + bool is_match(T &a, T &b) + { + return a == b; + } + bool is_match(vector &a, vector &b) + { + return a == b; + } +}; -// -// START: Unit screen search -// -class unitlist_search : public search_parent +// General class for screens that have only one secondary list to keep in sync +template < class S, class T, class V, class PARENT = search_generic > +class search_twocolumn_modifiable : public search_multicolumn_modifiable { public: +protected: + virtual vector * get_secondary_list() = 0; - virtual void render() const + virtual void do_post_init() { - print_search_option(28); + search_multicolumn_modifiable::do_post_init(); + secondary_list = get_secondary_list(); } - bool init(df::viewscreen_unitlistst *screen) + void save_secondary_values() { - if (screen != viewscreen && !reset_on_change()) - return false; - - if (!is_valid()) - { - viewscreen = screen; - search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); - } + saved_secondary_list = *secondary_list; + } - return true; + void reset_secondary_viewscreen_vectors() + { + secondary_list = NULL; } -private: - virtual string get_element_description(df::unit *element) const + virtual void update_saved_secondary_list_item(size_t i, size_t j) { - string desc = Translation::TranslateName(Units::getVisibleName(element), false); - desc += ", " + Units::getProfessionName(element); // Check animal type too + saved_secondary_list[i] = (*secondary_list)[j]; + } - return desc; + void clear_secondary_viewscreen_vectors() + { + secondary_list->clear(); } - virtual bool should_check_input(set *input) + void add_to_filtered_secondary_lists(size_t i) { - 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()) - { - // Changing screens, reset search - clear_search(); - reset_all(); - } - else - input->clear(); // Ignore cursor keys when typing + secondary_list->push_back(saved_secondary_list[i]); + } - return false; - } + void clear_secondary_saved_lists() + { + saved_secondary_list.clear(); + } - return true; + void restore_secondary_values() + { + *secondary_list = saved_secondary_list; } + vector *secondary_list, saved_secondary_list; }; -typedef 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 -// - - -// -// TODO: Animals screen search -// - -// -// END: Animals screen search -// -// -// START: Trade screen search -// -class trade_search_base : public search_parent +// Parent struct for the hooks, use optional param D to generate multiple search classes in the same +// viewscreen but different static modules +template +struct generic_search_hook : T { + typedef T interpose_base; -private: - virtual string get_element_description(df::item *element) const - { - return Items::getDescription(element, 0, true); - } + static V module; - virtual bool should_check_input(set *input) + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - if (is_entry_mode()) + if (!module.init(this)) + { + INTERPOSE_NEXT(feed)(input); + return; + } + + if (!module.process_input(input)) + { + INTERPOSE_NEXT(feed)(input); + module.do_post_input_feed(); + } + + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + bool ok = module.init(this); + INTERPOSE_NEXT(render)(); + if (ok) + module.render(); + } +}; + +template V generic_search_hook ::module; + + +// Hook definition helpers +#define IMPLEMENT_HOOKS_WITH_ID(screen, module, id) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + +#define IMPLEMENT_HOOKS(screen, module) \ + typedef generic_search_hook module##_hook; \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, feed); \ + template<> IMPLEMENT_VMETHOD_INTERPOSE(module##_hook, render) + + +// +// END: Generic Search functionality +// + + +// +// START: Animal screen search +// +typedef df::viewscreen_petst::T_animal T_animal; +typedef df::viewscreen_petst::T_mode T_mode; + +class pets_search : public search_multicolumn_modifiable_generic +{ +public: + void render() const + { + if (viewscreen->mode == T_mode::List) + print_search_option(25, 4); + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->animal; + } + + virtual void do_post_init() + { + is_vermin = &viewscreen->is_vermin; + pet_info = &viewscreen->pet_info; + is_tame = &viewscreen->is_tame; + is_adopting = &viewscreen->is_adopting; + } + + string get_element_description(df::viewscreen_petst::T_animal element) const + { + return get_unit_description(element.unit); + } + + bool should_check_input() + { + return viewscreen->mode == T_mode::List; + } + + void save_secondary_values() + { + is_vermin_s = *is_vermin; + pet_info_s = *pet_info; + is_tame_s = *is_tame; + is_adopting_s = *is_adopting; + } + + void reset_secondary_viewscreen_vectors() + { + is_vermin = NULL; + pet_info = NULL; + is_tame = NULL; + is_adopting = NULL; + } + + void update_saved_secondary_list_item(size_t i, size_t j) + { + is_vermin_s[i] = (*is_vermin)[j]; + pet_info_s[i] = (*pet_info)[j]; + is_tame_s[i] = (*is_tame)[j]; + is_adopting_s[i] = (*is_adopting)[j]; + } + + void clear_secondary_viewscreen_vectors() + { + is_vermin->clear(); + pet_info->clear(); + is_tame->clear(); + is_adopting->clear(); + } + + void add_to_filtered_secondary_lists(size_t i) + { + is_vermin->push_back(is_vermin_s[i]); + pet_info->push_back(pet_info_s[i]); + is_tame->push_back(is_tame_s[i]); + is_adopting->push_back(is_adopting_s[i]); + } + + void clear_secondary_saved_lists() + { + is_vermin_s.clear(); + pet_info_s.clear(); + is_tame_s.clear(); + is_adopting_s.clear(); + } + + void restore_secondary_values() + { + *is_vermin = is_vermin_s; + *pet_info = pet_info_s; + *is_tame = is_tame_s; + *is_adopting = is_adopting_s; + } + + bool is_match(T_animal &a, T_animal &b) + { + return a.unit == b.unit; + } + + bool is_match(vector &a, vector &b) + { + for (size_t i = 0; i < a.size(); i++) + { + if (!is_match(a[i], b[i])) + return false; + } + + return true; + } + + std::vector *is_vermin, is_vermin_s; + std::vector *pet_info, pet_info_s; + std::vector *is_tame, is_tame_s; + std::vector *is_adopting, is_adopting_s; +}; + +IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search); + +// +// END: Animal screen search +// + + + +// +// START: Stocks screen search +// +class stocks_search : public search_generic +{ +public: + + void render() const + { + if (!viewscreen->in_group_mode) + print_search_option(2); + else + { + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); + } + } + + bool process_input(set *input) + { + if (viewscreen->in_group_mode) + return false; + + redo_search = false; + + if ((input->count(interface_key::CURSOR_UP) || input->count(interface_key::CURSOR_DOWN)) && !viewscreen->in_right_list) + { + // Redo search if category changes + saved_list1.clear(); + end_entry_mode(); + if (search_string.length() > 0) + redo_search = true; + + return false; + } + + return search_generic::process_input(input); + } + + virtual void do_post_input_feed() + { + if (viewscreen->in_group_mode) + { + // Disable search if item lists are grouped + clear_search(); + reset_search(); + } + else if (redo_search) + { + do_search(); + redo_search = false; + } + } + +private: + int32_t *get_viewscreen_cursor() + { + return &viewscreen->item_cursor; + } + + virtual vector *get_primary_list() + { + return &viewscreen->items; + } + + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool redo_search; +}; + + +IMPLEMENT_HOOKS(df::viewscreen_storesst, stocks_search); + +// +// END: Stocks screen search +// + + +// +// START: Unit screen search +// +class unitlist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(28); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + static string get_non_work_description(df::unit *unit) + { + for (auto p = unit->status.misc_traits.begin(); p < unit->status.misc_traits.end(); p++) + { + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) + { + int i = (*p)->value; + return ".on break"; + } + } + + if (unit->profession == profession::BABY || + unit->profession == profession::CHILD || + unit->profession == profession::DRUNK) + { + return ""; + } + + if (ENUM_ATTR(profession, military, unit->profession)) + return ".military"; + + return ".idle.no job"; + } + + string get_element_description(df::unit *unit) const + { + string desc = get_unit_description(unit); + if (!unit->job.current_job) + { + desc += get_non_work_description(unit); + } + + return desc; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!in_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) + { + if (!in_entry_mode()) + { + // Changing screens, reset search + clear_search(); + reset_all(); + } + else + input->clear(); // Ignore cursor keys when typing + + return false; + } + + return true; + } + + vector *get_secondary_list() + { + return &viewscreen->jobs[viewscreen->page]; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos[viewscreen->page]; + } + + vector *get_primary_list() + { + return &viewscreen->units[viewscreen->page]; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_unitlistst, unitlist_search); + +// +// END: Unit screen search +// + + +// +// START: Trade screen search +// +class trade_search_base : public search_twocolumn_modifiable +{ + +private: + string get_element_description(df::item *element) const + { + return Items::getDescription(element, 0, true); + } + + bool should_check_input(set *input) + { + if (in_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(); + // 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)) + { + clear_search_for_trade(); return true; + } + + return true; + } + + void clear_search_for_trade() + { + // Trying to trade, reset search + clear_search(); + reset_all(); + } +}; + + +class trade_search_merc : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(2, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->trader_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->trader_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->trader_items; + } + + char get_search_select_key() + { + return 'q'; + } +}; + +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_merc, 1); + + +class trade_search_fort : public trade_search_base +{ +public: + virtual void render() const + { + print_search_option(42, 26); + } + +private: + vector *get_secondary_list() + { + return &viewscreen->broker_selected; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->broker_cursor; + } + + vector *get_primary_list() + { + return &viewscreen->broker_items; + } + + char get_search_select_key() + { + return 'w'; + } +}; - 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(); +IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_tradegoodsst, trade_search_fort, 2); - // Trying to trade, reset search - clear_search(); - reset_all(); +// +// END: Trade screen search +// - return false; - } - return true; +// +// START: Stockpile screen search +// +typedef layered_search stocks_layer; +class stockpile_search : public search_twocolumn_modifiable +{ +public: + void update_saved_secondary_list_item(size_t i, size_t j) + { + *saved_secondary_list[i] = *(*secondary_list)[j]; + } + + string get_element_description(string *element) const + { + return *element; + } + + void render() const + { + print_search_option(51, 23); + } + + vector *get_primary_list() + { + return &viewscreen->item_names; } + + vector *get_secondary_list() + { + return &viewscreen->item_status; + } + + + }; +IMPLEMENT_HOOKS(df::viewscreen_layer_stockpilest, stockpile_search); -class trade_search_merc : public trade_search_base +// +// END: Stockpile screen search +// + + +// +// START: Military screen search +// +class military_search : public layered_search { public: - virtual void render() const + + string get_element_description(df::unit *element) const { - print_search_option(2, 26); + return get_unit_description(element); } - bool init(df::viewscreen_tradegoodsst *screen) + void render() const { - if (screen != viewscreen && !reset_on_change()) + print_search_option(52, 22); + } + + char get_search_select_key() + { + return 'q'; + } + + bool can_init(df::viewscreen_layer_militaryst *screen) + { + if (screen->page != df::viewscreen_layer_militaryst::Positions) return false; - if (!is_valid()) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->positions.candidates; + } + + bool should_check_input(set *input) + { + if (input->count(interface_key::SELECT) && !in_entry_mode() && !search_string.empty()) { - viewscreen = screen; - search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); - track_secondary_values = true; + // About to make an assignment, so restore original list (it will be changed by the game) + int32_t *cursor = get_viewscreen_cursor(); + df::unit *selected_unit = get_primary_list()->at(*cursor); + clear_search(); + + for (*cursor = 0; *cursor < get_primary_list()->size(); (*cursor)++) + { + if (get_primary_list()->at(*cursor) == selected_unit) + break; + } + + reset_all(); } return true; } }; -typedef search_hook trade_search_merc_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_merc_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_layer_militaryst, military_search); +// +// END: Military screen search +// -class trade_search_fort : public trade_search_base + +// +// START: Room list search +// +static map< df::building_type, vector > room_quality_names; +static int32_t room_value_bounds[] = {1, 100, 250, 500, 1000, 1500, 2500, 10000}; + +class roomlist_search : public search_twocolumn_modifiable { public: - virtual void render() const + void render() const { - print_search_option(42, 26); + print_search_option(2, 23); } - bool init(df::viewscreen_tradegoodsst *screen) +private: + void do_post_init() { - if (screen != viewscreen && !reset_on_change()) - return false; + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } - if (!is_valid()) + string get_element_description(df::building *bld) const + { + bool is_ownable_room = (bld->is_room && room_quality_names.find(bld->getType()) != room_quality_names.end()); + + string desc; + desc.reserve(100); + if (bld->owner) + desc += get_unit_description(bld->owner); + else if (is_ownable_room) + desc += "no owner"; + + desc += "."; + + if (is_ownable_room) + { + int32_t value = bld->getRoomValue(NULL); + vector *names = &room_quality_names[bld->getType()]; + string *room_name = &names->at(0); + for (int i = 1; i < 8; i++) + { + if (room_value_bounds[i] > value) + break; + room_name = &names->at(i); + } + + desc += *room_name; + } + else { - viewscreen = screen; - search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); - track_secondary_values = true; + string name; + bld->getName(&name); + if (!name.empty()) + { + desc += name; + } } + + return desc; + } - return true; + vector *get_secondary_list() + { + return &viewscreen->room_value; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor; + } + + vector *get_primary_list() + { + return &viewscreen->buildings; } }; -typedef search_hook trade_search_fort_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_buildinglistst, roomlist_search); // -// END: Trade screen search +// END: Room list search // + // -// START: Stockpile screen search +// START: Announcement list search // - -class stockpile_search : public search_parent +class annoucnement_search : public search_generic { public: - void update_saved_secondary_list_item(size_t i, size_t j) + void render() const { - *saved_list2[i] = *(*sort_list2)[j]; + print_search_option(2, gps->dimy - 3); } - string get_element_description(string *element) const +private: + int32_t *get_viewscreen_cursor() { - return *element; + return &viewscreen->anon_3; } - void render() const + virtual vector *get_primary_list() + { + return &viewscreen->anon_4; + } + + +private: + string get_element_description(void *element) const + { + return ((df::report *) element)->text; + } +}; + + +IMPLEMENT_HOOKS(df::viewscreen_announcelistst, annoucnement_search); + +// +// END: Announcement list search +// + + +// +// START: Nobles search list +// +typedef df::viewscreen_layer_noblelistst::T_candidates T_candidates; + +class nobles_search : public layered_search +{ +public: + + string get_element_description(T_candidates *element) const { - print_search_option(2, 25); + if (!element->unit) + return ""; + + return get_unit_description(element->unit); } - static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx) + void render() const { - return virtual_cast(vector_get(layer->layer_objects,idx)); + print_search_option(2, 23); } - bool init(df::viewscreen_layer_stockpilest *screen) + bool can_init(df::viewscreen_layer_noblelistst *screen) { - if (screen != viewscreen && !reset_on_change()) + if (screen->mode != df::viewscreen_layer_noblelistst::Appoint) return false; - auto list3 = getLayerList(screen, 2); - if (!list3->active) + return layered_search::can_init(screen); + } + + vector *get_primary_list() + { + return &viewscreen->candidates; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_layer_noblelistst, nobles_search); + +// +// END: Nobles search list +// + + +// +// START: Job list search +// +void get_job_details(string &desc, df::job *job) +{ + string job_name = ENUM_KEY_STR(job_type,job->job_type); + for (size_t i = 0; i < job_name.length(); i++) + { + char c = job_name[i]; + if (c >= 'A' && c <= 'Z') + desc += " "; + desc += c; + } + desc += "."; + + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); + + MaterialInfo mat(job); + if (itype == item_type::FOOD) + mat.decode(-1); + + if (mat.isValid() || job->material_category.whole) + { + desc += mat.toString(); + desc += "."; + if (job->material_category.whole) { - if (is_valid()) - { - clear_search(); - reset_all(); - } + desc += bitfield_to_string(job->material_category); + desc += "."; + } + } - return false; + if (!job->reaction_name.empty()) + { + for (size_t i = 0; i < job->reaction_name.length(); i++) + { + if (job->reaction_name[i] == '_') + desc += " "; + else + desc += job->reaction_name[i]; } - if (!is_valid()) + desc += "."; + } + + if (job->flags.bits.suspend) + desc += "suspended."; +} + +class joblist_search : public search_twocolumn_modifiable +{ +public: + void render() const + { + print_search_option(2); + } + +private: + void do_post_init() + { + search_twocolumn_modifiable::do_post_init(); + read_only = true; + } + + string get_element_description(df::job *element) const + { + if (!element) + return "no job.idle"; + + string desc; + desc.reserve(100); + get_job_details(desc, element); + df::unit *worker = DFHack::Job::getWorker(element); + if (worker) + desc += get_unit_description(worker); + else + desc += "Inactive"; + + return desc; + } + + vector *get_secondary_list() + { + return &viewscreen->units; + } + + int32_t *get_viewscreen_cursor() + { + return &viewscreen->cursor_pos; + } + + vector *get_primary_list() + { + return &viewscreen->jobs; + } +}; + +IMPLEMENT_HOOKS(df::viewscreen_joblistst, joblist_search); + +// +// END: Job list search +// + + +// +// START: Burrow assignment search +// +using df::global::ui; + +class burrow_search : public search_twocolumn_modifiable +{ +public: + bool can_init(df::viewscreen_dwarfmodest *screen) + { + if (ui->main.mode == df::ui_sidebar_mode::Burrows && ui->burrows.in_add_units_mode) + { + return search_twocolumn_modifiable::can_init(screen); + } + else if (is_valid()) { - viewscreen = screen; - search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status); - track_secondary_values = true; + clear_search(); + reset_all(); } - return true; + return false; + } + + string get_element_description(df::unit *element) const + { + return get_unit_description(element); + } + + void render() const + { + auto dims = Gui::getDwarfmodeViewDims(); + int left_margin = dims.menu_x1 + 1; + int x = left_margin; + int y = 23; + + print_search_option(x, y); + } + + vector *get_primary_list() + { + return &ui->burrows.list_units; } - void do_search() + vector *get_secondary_list() { - search_parent::do_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.sel_units; } - void clear_search() + virtual int32_t * get_viewscreen_cursor() { - search_parent::clear_search(); - auto list3 = getLayerList(viewscreen, 2); - list3->num_entries = viewscreen->item_names.size(); + return &ui->burrows.unit_cursor_pos; + } + + + bool should_check_input(set *input) + { + if (input->count(interface_key::SECONDSCROLL_UP) || input->count(interface_key::SECONDSCROLL_DOWN) + || input->count(interface_key::SECONDSCROLL_PAGEUP) || input->count(interface_key::SECONDSCROLL_PAGEDOWN)) + { + end_entry_mode(); + return false; + } + + return true; } }; -typedef search_hook stockpile_search_hook; -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed); -template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render); +IMPLEMENT_HOOKS(df::viewscreen_dwarfmodest, burrow_search); // -// END: Stockpile screen search +// END: Burrow assignment search // + DFHACK_PLUGIN("search"); +#define SEARCH_HOOKS \ + HOOK_ACTION(unitlist_search_hook) \ + HOOK_ACTION(roomlist_search_hook) \ + HOOK_ACTION(trade_search_merc_hook) \ + HOOK_ACTION(trade_search_fort_hook) \ + HOOK_ACTION(stocks_search_hook) \ + HOOK_ACTION(pets_search_hook) \ + HOOK_ACTION(military_search_hook) \ + HOOK_ACTION(nobles_search_hook) \ + HOOK_ACTION(annoucnement_search_hook) \ + HOOK_ACTION(joblist_search_hook) \ + HOOK_ACTION(burrow_search_hook) \ + HOOK_ACTION(stockpile_search_hook) DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { - 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() || - !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() || - !INTERPOSE_HOOK(stockpile_search_hook, feed).apply() || - !INTERPOSE_HOOK(stockpile_search_hook, render).apply()) +#define HOOK_ACTION(hook) \ + !INTERPOSE_HOOK(hook, feed).apply() || \ + !INTERPOSE_HOOK(hook, render).apply() || + + if (!gps || !gview || SEARCH_HOOKS 0) out.printerr("Could not insert Search hooks!\n"); +#undef HOOK_ACTION + + const string a[] = {"Meager Quarters", "Modest Quarters", "Quarters", "Decent Quarters", "Fine Quarters", "Great Bedroom", "Grand Bedroom", "Royal Bedroom"}; + room_quality_names[df::building_type::Bed] = vector(a, a + 8); + + const string b[] = {"Meager Dining Room", "Modest Dining Room", "Dining Room", "Decent Dining Room", "Fine Dining Room", "Great Dining Room", "Grand Dining Room", "Royal Dining Room"}; + room_quality_names[df::building_type::Table] = vector(b, b + 8); + + const string c[] = {"Meager Office", "Modest Office", "Office", "Decent Office", "Splendid Office", "Throne Room", "Opulent Throne Room", "Royal Throne Room"}; + room_quality_names[df::building_type::Chair] = vector(c, c + 8); + + const string d[] = {"Grave", "Servants Burial Chamber", "Burial Chamber", "Tomb", "Fine Tomb", "Mausoleum", "Grand Mausoleum", "Royal Mausoleum"}; + room_quality_names[df::building_type::Coffin] = vector(d, d + 8); + return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { - INTERPOSE_HOOK(unitlist_search_hook, feed).remove(); - INTERPOSE_HOOK(unitlist_search_hook, render).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_merc_hook, render).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, feed).remove(); - INTERPOSE_HOOK(trade_search_fort_hook, render).remove(); - INTERPOSE_HOOK(stocks_search_hook, feed).remove(); - INTERPOSE_HOOK(stocks_search_hook, render).remove(); - INTERPOSE_HOOK(stockpile_search_hook, feed).remove(); - INTERPOSE_HOOK(stockpile_search_hook, render).remove(); +#define HOOK_ACTION(hook) \ + INTERPOSE_HOOK(hook, feed).remove(); \ + INTERPOSE_HOOK(hook, render).remove(); + + SEARCH_HOOKS + +#undef HOOK_ACTION + return CR_OK; } DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_change_event event ) { +#define HOOK_ACTION(hook) hook::module.reset_on_change(); + switch (event) { case SC_VIEWSCREEN_CHANGED: - 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(); - stockpile_search_hook::module.reset_on_change(); + SEARCH_HOOKS break; default: @@ -800,4 +1604,9 @@ DFhackCExport command_result plugin_onstatechange ( color_ostream &out, state_ch } return CR_OK; + +#undef HOOK_ACTION } + +#undef IMPLEMENT_HOOKS +#undef SEARCH_HOOKS \ No newline at end of file From bc5cdf88777e6655857317b60ee2ff655dafb298 Mon Sep 17 00:00:00 2001 From: expwnent Date: Sat, 5 Jan 2013 11:37:12 -0500 Subject: [PATCH 341/389] 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 342/389] 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 343/389] 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 344/389] 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 345/389] 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 346/389] 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 347/389] 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 348/389] 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 349/389] 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 350/389] 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 351/389] 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 352/389] 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 353/389] 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 354/389] 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 355/389] 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 356/389] 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 357/389] 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 358/389] 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 359/389] 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 360/389] 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 361/389] 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 362/389] 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 363/389] 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 364/389] 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 365/389] 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 366/389] 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 367/389] 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 368/389] 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 369/389] 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 370/389] 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 371/389] 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 372/389] 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 373/389] 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 374/389] 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 375/389] 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 376/389] 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 377/389] 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 378/389] 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 379/389] 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 380/389] 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 381/389] 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 382/389] 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 383/389] 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 384/389] 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 385/389] 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 386/389] 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 387/389] 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 388/389] 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 178/389] 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 179/389] 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 180/389] 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 181/389] 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 182/389] 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 183/389] 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 184/389] 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 185/389] 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 186/389] 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 187/389] 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 188/389] 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 189/389] 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 190/389] 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 abe027c940ec3efcea7955d45708d2c7e8916295 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Fri, 30 Nov 2012 22:44:05 +1300 Subject: [PATCH 191/389] Copy changes from ag fork --- plugins/search.cpp | 154 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 120 insertions(+), 34 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index fdc788955..a14397fba 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) { @@ -206,12 +225,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]) { @@ -245,6 +264,9 @@ protected: update_secondary_values(); *sort_list2 = saved_list2; } + + saved_list1.clear(); + saved_list2.clear(); } store_reference_values(); search_string = ""; @@ -278,7 +300,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)); @@ -307,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"); @@ -346,7 +369,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 +385,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,11 +411,12 @@ public: virtual void render() const { if (!viewscreen->in_group_mode) - print_search_option(1); + print_search_option(2); else { - int x = 1; - 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"); } } @@ -402,13 +432,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; } @@ -440,8 +475,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 @@ -461,28 +496,33 @@ 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; } 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()) { @@ -502,8 +542,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 @@ -529,6 +569,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,20 +603,25 @@ 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; } }; 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 @@ -564,20 +632,25 @@ 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; } }; 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 +662,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 || !gview || + !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 +691,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_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: + break; + } + return CR_OK; -} \ No newline at end of file +} From 2cb594ba8990e062e7f353d38811b218f631866b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 30 Nov 2012 14:48:05 +0400 Subject: [PATCH 192/389] 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 210c1650ecb19a9407779b37ff334f28dca859a7 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sat, 1 Dec 2012 01:01:04 +1300 Subject: [PATCH 193/389] Add stockpile screen searching capability --- plugins/search.cpp | 122 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 13 deletions(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index a14397fba..cf1798dcf 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -7,10 +7,12 @@ //#include "df/viewscreen_petst.h" #include "df/viewscreen_storesst.h" +#include "df/viewscreen_layer_stockpilest.h" #include "df/viewscreen_tradegoodsst.h" #include "df/viewscreen_unitlistst.h" #include "df/interface_key.h" #include "df/interfacest.h" +#include "df/layer_object_listst.h" using std::set; using std::vector; @@ -79,6 +81,11 @@ public: return true; } + bool is_valid() + { + return valid; + } + // A new keystroke is received in a searchable screen virtual bool process_input(set *input) { @@ -164,9 +171,9 @@ protected: const S *viewscreen; vector saved_list1, reference_list; vector saved_list2; + vector *sort_list2; vector saved_indexes; - bool valid; bool redo_search; bool track_secondary_values; string search_string; @@ -240,12 +247,17 @@ protected: } } - saved_list2[saved_indexes[i]] = (*sort_list2)[adjusted_item_index]; + update_saved_secondary_list_item(saved_indexes[i], adjusted_item_index); } saved_indexes.clear(); } } + virtual void update_saved_secondary_list_item(size_t i, size_t j) + { + saved_list2[i] = (*sort_list2)[j]; + } + // Store a copy of filtered list, used later to work out if filtered list has been sorted after filtering void store_reference_values() { @@ -254,7 +266,7 @@ protected: } // Shortcut to clear the search immediately - void clear_search() + virtual void clear_search() { if (saved_list1.size() > 0) { @@ -273,7 +285,7 @@ protected: } // The actual sort - void do_search() + virtual void do_search() { if (search_string.length() == 0) { @@ -318,7 +330,8 @@ protected: store_reference_values(); //Keep a copy, in case user sorts new list - *cursor_pos = 0; + if (cursor_pos) + *cursor_pos = 0; } virtual bool should_check_input(set *input) @@ -346,10 +359,9 @@ protected: private: vector *sort_list1; - vector *sort_list2; int *cursor_pos; char select_key; - + bool valid; bool entry_mode; df::interface_key select_token; @@ -359,7 +371,8 @@ private: }; template search_parent *search_parent ::lock = NULL; -// Parent struct for the hooks +// Parent struct for the hooks, use optional param D to generate multiple classes with same T & V +// but different static modules template struct search_hook : T { @@ -437,7 +450,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->item_cursor, &screen->items); @@ -501,7 +514,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->cursor_pos[viewscreen->page], &screen->units[viewscreen->page], &screen->jobs[viewscreen->page]); @@ -608,7 +621,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->trader_cursor, &screen->trader_items, &screen->trader_selected, 'q'); @@ -637,7 +650,7 @@ public: if (screen != viewscreen && !reset_on_change()) return false; - if (!valid) + if (!is_valid()) { viewscreen = screen; search_parent::init(&screen->broker_cursor, &screen->broker_items, &screen->broker_selected, 'w'); @@ -657,6 +670,84 @@ template<> IMPLEMENT_VMETHOD_INTERPOSE(trade_search_fort_hook, render); // +// +// START: Stockpile screen search +// + +class stockpile_search : public search_parent +{ +public: + void update_saved_secondary_list_item(size_t i, size_t j) + { + *saved_list2[i] = *(*sort_list2)[j]; + } + + string get_element_description(string *element) const + { + return *element; + } + + void render() const + { + print_search_option(2, 25); + } + + static df::layer_object_listst *getLayerList(const df::viewscreen_layer *layer, int idx) + { + return virtual_cast(vector_get(layer->layer_objects,idx)); + } + + bool init(df::viewscreen_layer_stockpilest *screen) + { + if (screen != viewscreen && !reset_on_change()) + return false; + + auto list3 = getLayerList(screen, 2); + if (!list3->active) + { + if (is_valid()) + { + clear_search(); + reset_all(); + } + + return false; + } + + if (!is_valid()) + { + viewscreen = screen; + search_parent::init(&list3->cursor, &screen->item_names, &screen->item_status); + track_secondary_values = true; + } + + return true; + } + + void do_search() + { + search_parent::do_search(); + auto list3 = getLayerList(viewscreen, 2); + list3->num_entries = viewscreen->item_names.size(); + } + + void clear_search() + { + search_parent::clear_search(); + auto list3 = getLayerList(viewscreen, 2); + list3->num_entries = viewscreen->item_names.size(); + } +}; + +typedef search_hook stockpile_search_hook; +template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, feed); +template<> IMPLEMENT_VMETHOD_INTERPOSE(stockpile_search_hook, render); + +// +// END: Stockpile screen search +// + + DFHACK_PLUGIN("search"); @@ -670,7 +761,9 @@ DFhackCExport command_result plugin_init ( color_ostream &out, vector Date: Fri, 30 Nov 2012 15:50:35 +0100 Subject: [PATCH 194/389] 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 195/389] 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 196/389] 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 197/389] 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 198/389] 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 199/389] 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 200/389] 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 201/389] 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 202/389] 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 203/389] 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 204/389] 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.