@@ -170,14 +170,126 @@ local function get_farmplot_search_key(farmplot)
170170 return table.concat (result , ' ' )
171171end
172172
173+ --- @param siege_engine df.building_siegeenginest
174+ --- @return string
175+ local function siege_engine_type (siege_engine )
176+ if siege_engine .type == df .siegeengine_type .BoltThrower then
177+ return ' Bolt Thrower'
178+ end
179+ return df .siegeengine_type [siege_engine .type ]
180+ end
181+
182+ --- @param siege_engine df.building_siegeenginest
183+ --- @return string
184+ local function siege_engine_status (siege_engine )
185+ -- portions of return value with with underscores are to allow easier
186+ -- word-anchored matching even when the DFHack full-text search mode is
187+ -- enabled; e.g.
188+ -- - "loaded" would match "Loaded" and "Unloaded",
189+ -- - but "_loaded" would only match "_Loaded"
190+ local count = 0
191+ local count_all = siege_engine .type == df .siegeengine_type .BoltThrower
192+ for _ , building_item in ipairs (siege_engine .contained_items ) do
193+ if building_item .use_mode == df .building_item_role_type .TEMP then
194+ if not count_all then
195+ return ' Loaded _Loaded'
196+ end
197+ count = count + building_item .item :getStackSize ()
198+ end
199+ end
200+ if count_all and count > 0 then
201+ return (' %d bolts _%d_bolts' ):format (count , count )
202+ end
203+ return ' Unloaded'
204+ end
205+
206+ --- @param siege_engine df.building_siegeenginest
207+ --- @return string
208+ local function siege_engine_job_status (siege_engine )
209+ for _ , job in ipairs (siege_engine .jobs ) do
210+ if job .job_type == df .job_type .LoadCatapult
211+ or job .job_type == df .job_type .LoadBallista
212+ or job .job_type == df .job_type .LoadBoltThrower
213+ then
214+ if dfhack .job .getWorker (job ) ~= nil then
215+ return ' Loading'
216+ else
217+ return ' Inactive load task'
218+ end
219+ end
220+ local firing_bolt_thrower = job .job_type == df .job_type .FireBoltThrower
221+ local firing = job .job_type == df .job_type .FireCatapult
222+ or job .job_type == df .job_type .FireBallista
223+ or firing_bolt_thrower
224+ if firing then
225+ local unit = dfhack .job .getWorker (job )
226+ if unit == nil then
227+ return ' No operator'
228+ else
229+ --- @type integer ?, integer ?, integer ?
230+ local x , y , z = dfhack .units .getPosition (unit )
231+ -- DF shows "present" when the unit is inside the building's
232+ -- footprint (or, for bolt throwers, next to it); the unit does
233+ -- not need to be at the exact firing position tile (which
234+ -- varies based on siege engine type and direction)
235+ if x ~= nil and z == siege_engine .z then
236+ --- @cast y integer
237+ if firing_bolt_thrower then
238+ if siege_engine .x1 - 1 <= x and x <= siege_engine .x2 + 1
239+ and siege_engine .y1 - 1 <= y and y <= siege_engine .y2 + 1
240+ then
241+ return ' Operator present'
242+ end
243+ elseif dfhack .buildings .containsTile (siege_engine , x , y ) then
244+ return ' Operator present'
245+ end
246+ end
247+ return ' Operator assigned'
248+ end
249+ end
250+ end
251+ return ' '
252+ end
253+
254+ --- @param siege_engine df.building_siegeenginest
255+ --- @return string
256+ local function get_siege_engine_search_key (siege_engine )
257+ -- DF 53.05 Info window, Places tab, Siege Engines subtab shows this info:
258+ -- name: assigned name or siege engine type name
259+ -- status: "Unloaded", "Loaded", "<N> bolts"
260+ -- job status:
261+ -- - "Inactive load task" (load job unassigned),
262+ -- - "Loading" (load job assigned),
263+ -- - "No operator" (fire job unassigned),
264+ -- - "Operator present" (fire job assigned),
265+ -- - "Operator assigned" (fire job assigned, but not in position),
266+ -- - blank
267+ -- action: (icons) fire-at-will, practice, prepare-to-fire, keep-loaded, not-in-use
268+ -- These have associated text blurbs that are shown in the
269+ -- building info window, but those texts are not discoverable
270+ -- from the Info > Places > Siege engine list view.
271+ local result = {}
272+
273+ if # siege_engine .name ~= 0 then table.insert (result , siege_engine .name ) end
274+
275+ table.insert (result , siege_engine_type (siege_engine ))
276+
277+ table.insert (result , siege_engine_status (siege_engine ))
278+
279+ table.insert (result , siege_engine_job_status (siege_engine ))
280+
281+ return table.concat (result , ' ' )
282+ end
283+
173284-- ----------------------
174285-- PlacesOverlay
175286--
176287
177288PlacesOverlay = defclass (PlacesOverlay , sortoverlay .SortOverlay )
178289PlacesOverlay .ATTRS {
179290 desc = ' Adds search functionality to the places overview screens.' ,
180- default_pos = {x = 71 , y = 9 },
291+ default_pos = {x = 52 , y = 9 },
292+ version = 2 ,
181293 viewscreens = ' dwarfmode/Info' ,
182294 frame = {w = 40 , h = 6 }
183295}
@@ -205,6 +317,7 @@ function PlacesOverlay:init()
205317 self :register_handler (' STOCKPILES' , buildings .list [df .buildings_mode_type .STOCKPILES ], curry (sortoverlay .single_vector_search , {get_search_key_fn = get_stockpile_search_key }))
206318 self :register_handler (' WORKSHOPS' , buildings .list [df .buildings_mode_type .WORKSHOPS ], curry (sortoverlay .single_vector_search , {get_search_key_fn = get_workshop_search_key }))
207319 self :register_handler (' FARMPLOTS' , buildings .list [df .buildings_mode_type .FARMPLOTS ], curry (sortoverlay .single_vector_search , {get_search_key_fn = get_farmplot_search_key }))
320+ self :register_handler (' SIEGE_ENGINES' , buildings .list [df .buildings_mode_type .SIEGE_ENGINES ], curry (sortoverlay .single_vector_search , {get_search_key_fn = get_siege_engine_search_key }))
208321end
209322
210323function PlacesOverlay :get_key ()
0 commit comments