-- 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' 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"}, 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}}} 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(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=args.job_type nj.completion_timer=-1 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 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 and args.unit.body.weapon_bp==v.body_part_id then 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 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 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 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) if tile_attrs[tt].shape==df.tiletype_shape.WALL 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 tile_attrs[tt].shape==df.tiletype_shape.TREE then return true else return false, "Can only do it on trees" end 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 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 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 --if settings.build_by_items then -- args.items=itemsAtPos(inp_args.from_pos) --end --printall(args.items) buildings.constructBuilding(args) end 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.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 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_actions={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 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) return end end 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}}, {"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)}}, {"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}}, {"RemoveBuilding" ,RemoveBuilding,{IsBuilding}}, --{"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}}, } 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 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{ 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 function usetool:onRenderBody(dc) 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 }, 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 },--]] 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,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]} 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) 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 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(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 end end end usetool():show()