@@ -6,6 +6,55 @@ local widgets = require('gui.widgets')
66local repeatutil = require (" repeat-util" )
77local orders = require (' plugins.orders' )
88
9+ --- iterate over input materials of workshop with stockpile links
10+ --- @param workshop df.building_workshopst
11+ --- @param action fun ( item : df.item ): any
12+ local function for_inputs (workshop , action )
13+ if # workshop .profile .links .take_from_pile == 0 then
14+ dfhack .error (' workshop has no links' )
15+ else
16+ for _ , stockpile in ipairs (workshop .profile .links .take_from_pile ) do
17+ for _ , item in ipairs (dfhack .buildings .getStockpileContents (stockpile )) do
18+ if item :isAssignedToThisStockpile (stockpile .id ) then
19+ for _ , contained_item in ipairs (dfhack .items .getContainedItems (item )) do
20+ action (contained_item )
21+ end
22+ else
23+ action (item )
24+ end
25+ end
26+ end
27+ for _ , contained_item in ipairs (workshop .contained_items ) do
28+ if contained_item .use_mode == df .building_item_role_type .TEMP then
29+ action (contained_item .item )
30+ end
31+ end
32+ end
33+ end
34+
35+ --- choose random value based on positive integer weights
36+ --- @generic T
37+ --- @param choices table<T,integer>
38+ --- @return T
39+ function weightedChoice (choices )
40+ local sum = 0
41+ for _ , weight in pairs (choices ) do
42+ sum = sum + weight
43+ end
44+ if sum <= 0 then
45+ return nil
46+ end
47+ local random = math.random (sum )
48+ for choice , weight in pairs (choices ) do
49+ if random > weight then
50+ random = random - weight
51+ else
52+ return choice
53+ end
54+ end
55+ return nil -- never reached on well-formed input
56+ end
57+
958--- create a new linked job
1059--- @return df.job
1160function make_job ()
@@ -14,6 +63,61 @@ function make_job()
1463 return job
1564end
1665
66+ function assignToWorkshop (job , workshop )
67+ job .pos = xyz2pos (workshop .centerx , workshop .centery , workshop .z )
68+ dfhack .job .addGeneralRef (job , df .general_ref_type .BUILDING_HOLDER , workshop .id )
69+ workshop .jobs :insert (" #" , job )
70+ end
71+
72+ --- make totem at specified workshop
73+ --- @param unit df.unit
74+ --- @param workshop df.building_workshopst
75+ --- @return boolean
76+ function makeTotem (unit , workshop )
77+ local job = make_job ()
78+ job .job_type = df .job_type .MakeTotem
79+ job .mat_type = - 1
80+
81+ local jitem = df .job_item :new ()
82+ jitem .item_type = df .item_type .NONE -- the game seems to leave this uninitialized
83+ jitem .mat_type = - 1
84+ jitem .mat_index = - 1
85+ jitem .quantity = 1
86+ jitem .vector_id = df .job_item_vector_id .ANY_REFUSE
87+ jitem .flags1 .unrotten = true
88+ jitem .flags2 .totemable = true
89+ jitem .flags2 .body_part = true
90+ job .job_items .elements :insert (' #' , jitem )
91+
92+ assignToWorkshop (job , workshop )
93+ return dfhack .job .addWorker (job , unit )
94+ end
95+
96+ --- make totem at specified workshop
97+ --- @param unit df.unit
98+ --- @param workshop df.building_workshopst
99+ --- @return boolean
100+ function makeHornCrafts (unit , workshop )
101+ local job = make_job ()
102+ job .job_type = df .job_type .MakeCrafts
103+ job .mat_type = - 1
104+ job .material_category .horn = true
105+
106+ local jitem = df .job_item :new ()
107+ jitem .item_type = df .item_type .NONE -- the game seems to leave this uninitialized
108+ jitem .mat_type = - 1
109+ jitem .mat_index = - 1
110+ jitem .quantity = 1
111+ jitem .vector_id = df .job_item_vector_id .ANY_REFUSE
112+ jitem .flags1 .unrotten = true
113+ jitem .flags2 .horn = true
114+ jitem .flags2 .body_part = true
115+ job .job_items .elements :insert (' #' , jitem )
116+
117+ assignToWorkshop (job , workshop )
118+ return dfhack .job .addWorker (job , unit )
119+ end
120+
17121--- make bone crafts at specified workshop
18122--- @param unit df.unit
19123--- @param workshop df.building_workshopst
@@ -23,7 +127,6 @@ function makeBoneCraft(unit, workshop)
23127 job .job_type = df .job_type .MakeCrafts
24128 job .mat_type = - 1
25129 job .material_category .bone = true
26- job .pos = xyz2pos (workshop .centerx , workshop .centery , workshop .z )
27130
28131 local jitem = df .job_item :new ()
29132 jitem .item_type = df .item_type .NONE
@@ -36,8 +139,7 @@ function makeBoneCraft(unit, workshop)
36139 jitem .flags2 .body_part = true
37140 job .job_items .elements :insert (' #' , jitem )
38141
39- dfhack .job .addGeneralRef (job , df .general_ref_type .BUILDING_HOLDER , workshop .id )
40- workshop .jobs :insert (" #" , job )
142+ assignToWorkshop (job , workshop )
41143 return dfhack .job .addWorker (job , unit )
42144end
43145
@@ -49,7 +151,6 @@ function makeRockCraft(unit, workshop)
49151 local job = make_job ()
50152 job .job_type = df .job_type .MakeCrafts
51153 job .mat_type = 0
52- job .pos = xyz2pos (workshop .centerx , workshop .centery , workshop .z )
53154
54155 local jitem = df .job_item :new ()
55156 jitem .item_type = df .item_type .BOULDER
@@ -61,12 +162,27 @@ function makeRockCraft(unit, workshop)
61162 jitem .flags3 .hard = true
62163 job .job_items .elements :insert (' #' , jitem )
63164
64- dfhack .job .addGeneralRef (job , df .general_ref_type .BUILDING_HOLDER , workshop .id )
65- workshop .jobs :insert (" #" , job )
66-
165+ assignToWorkshop (job , workshop )
67166 return dfhack .job .addWorker (job , unit )
68167end
69168
169+ --- categorize and count crafting materials (for Craftsdwarf's workshop)
170+ --- @param tab table<string,integer>
171+ --- @param item df.item
172+ local function categorize_craft (tab ,item )
173+ if df .item_corpsepiecest :is_instance (item ) then
174+ if item .corpse_flags .bone then
175+ tab [' bone' ] = (tab [' bone' ] or 0 ) + item .material_amount .Bone
176+ elseif item .corpse_flags .skull then
177+ tab [' skull' ] = (tab [' skull' ] or 0 ) + 1
178+ elseif item .corpse_flags .horn then
179+ tab [' horn' ] = (tab [' horn' ] or 0 ) + item .material_amount .Horn
180+ end
181+ elseif df .item_boulderst :is_instance (item ) then
182+ tab [' boulder' ] = (tab [' boulder' ] or 0 ) + 1
183+ end
184+ end
185+
70186-- script logic
71187
72188local GLOBAL_KEY = ' idle-crafting'
@@ -180,6 +296,32 @@ function unitIsAvailable(unit)
180296 return true
181297end
182298
299+ --- select crafting job based on available resources
300+ --- @param workshop df.building_workshopst
301+ --- @return (fun ( unit : df.unit , workshop : df.building_workshopst ): boolean )?
302+ function select_crafting_job (workshop )
303+ local tab = {}
304+ for_inputs (workshop , curry (categorize_craft ,tab ))
305+ local blocked_labors = workshop .profile .blocked_labors
306+ if blocked_labors [STONE_CRAFT ] then
307+ tab [' boulder' ] = nil
308+ end
309+ if blocked_labors [BONE_CARVE ] then
310+ tab [' bone' ] = nil
311+ tab [' skull' ] = nil
312+ tab [' horn' ] = nil
313+ end
314+ local material = weightedChoice (tab )
315+ if material == ' bone' then return makeBoneCraft
316+ elseif material == ' skull' then return makeTotem
317+ elseif material == ' horn' then return makeHornCrafts
318+ elseif material == ' boulder' then return makeRockCraft
319+ else
320+ return nil
321+ end
322+ end
323+
324+
183325--- check if unit is ready and try to create a crafting job for it
184326--- @param workshop df.building_workshopst
185327--- @param idx integer " index of the unit's group"
@@ -200,19 +342,31 @@ local function processUnit(workshop, idx, unit_id)
200342 end
201343 -- We have an available unit
202344 local success = false
203- if workshop .profile .blocked_labors [STONE_CRAFT ] == false then
204- success = makeRockCraft (unit , workshop )
205- end
206- if not success and workshop .profile .blocked_labors [BONE_CARVE ] == false then
207- success = makeBoneCraft (unit , workshop )
345+ if # workshop .profile .links .take_from_pile == 0 then
346+ -- can we do something smarter here?
347+ if workshop .profile .blocked_labors [STONE_CRAFT ] == false then
348+ success = makeRockCraft (unit , workshop )
349+ end
350+ if not success and workshop .profile .blocked_labors [BONE_CARVE ] == false then
351+ success = makeBoneCraft (unit , workshop )
352+ end
353+ if not success then
354+ dfhack .printerr (' idle-crafting: profile allows neither bone carving nor stonecrafting' )
355+ end
356+ else
357+ local craftItem = select_crafting_job (workshop )
358+ if craftItem then
359+ success = craftItem (unit , workshop )
360+ else
361+ print (' idle-crafting: workshop has no usable materials in linked stockpiles' )
362+ failing [workshop .id ] = true
363+ end
208364 end
209365 if success then
210366 -- Why is the encoding still wrong, even when using df2console?
211367 print (' idle-crafting: assigned crafting job to ' .. dfhack .df2console (dfhack .units .getReadableName (unit )))
212368 watched [idx ][unit_id ] = nil
213369 allowed [workshop .id ] = df .global .world .frame_counter
214- else
215- dfhack .printerr (' idle-crafting: profile allows neither bone carving nor stonecrafting, disabling workshop' )
216370 end
217371 return true
218372end
0 commit comments