z_mirror/RecipeBook

git clone https://git.raiguard.me/z_mirror/RecipeBook

Caleb HeuerFix clobbering of a generic `id` tag namea949b6c

trunk
18.9 KiB596 linesraw
1local flib_gui = require("__flib__.gui")
2local flib_gui_templates = require("__flib__.gui-templates")
3local flib_technology = require("__flib__.technology")
4
5local gui_util = require("scripts.gui.util")
6local info_description = require("scripts.gui.info-description")
7local info_section = require("scripts.gui.info-section")
8
9local collectors = require("scripts.database.collectors")
10local grouped = require("scripts.database.grouped")
11local researched = require("scripts.database.researched")
12local util = require("scripts.util")
13
14--- @class InfoPane
15--- @field context MainGuiContext
16--- @field title_label LuaGuiElement
17--- @field type_label LuaGuiElement
18--- @field content_pane LuaGuiElement
19local info_pane = {}
20local mt = { __index = info_pane }
21script.register_metatable("info_pane", mt)
22
23--- @type function?
24info_pane.on_result_clicked = nil
25
26--- @param parent LuaGuiElement
27--- @param context MainGuiContext
28function info_pane.build(parent, context)
29  local frame = parent.add({
30    type = "frame",
31    style = "inside_shallow_frame",
32    direction = "vertical",
33  })
34  frame.style.width = (40 * 10) + 24 + 12
35
36  local subheader = frame.add({ type = "frame", style = "subheader_frame" })
37  local title_label = subheader.add({
38    type = "sprite-button",
39    name = "page_header_title",
40    style = "rb_subheader_caption_button",
41    caption = { "gui.rb-welcome-title" },
42  })
43  subheader.add({ type = "empty-widget", style = "flib_horizontal_pusher" })
44  local type_label = subheader.add({
45    type = "label",
46    name = "page_header_type_label",
47    style = "rb_info_label",
48  })
49  type_label.style.right_margin = 8
50
51  local content_pane = frame.add({
52    type = "scroll-pane",
53    style = "rb_info_scroll_pane",
54    horizontal_scroll_policy = "never",
55    vertical_scroll_policy = "always",
56  })
57
58  -- Will be deleted when the first page is shown
59  content_pane.add({
60    type = "label",
61    name = "welcome_label",
62    caption = { "gui.rb-welcome-text" },
63  }).style.single_line =
64    false
65
66  local self = {
67    context = context,
68    title_label = title_label,
69    type_label = type_label,
70    content_pane = content_pane,
71  }
72  setmetatable(self, mt)
73  return self
74end
75
76--- @param prototype GenericPrototype
77--- @return boolean? updated
78function info_pane:show(prototype)
79  local profiler = game.create_profiler()
80
81  -- Subheader
82  do
83    local title_label = self.title_label
84    title_label.caption = { "", "      ", prototype.localised_name } --- @diagnostic disable-line:assign-type-mismatch
85    title_label.sprite = util.get_path(prototype)
86    local style = "rb_subheader_caption_button"
87    -- TODO: Technology runtime enable/disable
88    if prototype.hidden_in_factoriopedia then
89      style = "rb_subheader_caption_button_hidden"
90    elseif not researched.is(prototype, self.context.player.force_index) then
91      style = "rb_subheader_caption_button_unresearched"
92    end
93    title_label.style = style
94    title_label.style.horizontally_squashable = true
95  end
96
97  local prototype_path = util.get_path(prototype)
98  --- @type DatabaseID
99  local prototype_id = { type = util.object_name_to_type[prototype.object_name], name = prototype.name }
100
101  -- TODO: Refactor this trash
102  local recipe = prototype.object_name == "LuaRecipePrototype" and prototype --[[@as LuaRecipePrototype]]
103    or (self.context.grouping_mode == "all" and grouped.recipe[prototype_path] or nil)
104  local item = prototype.object_name == "LuaItemPrototype" and prototype --[[@as LuaItemPrototype]]
105    or (
106      self.context.grouping_mode == "all"
107        and grouped.material[prototype_path]
108        and grouped.material[prototype_path].object_name == "LuaItemPrototype"
109        and grouped.material[prototype_path]
110      or nil
111    )
112  local entity = prototype.object_name == "LuaEntityPrototype" and prototype --[[@as LuaEntityPrototype]]
113    or (self.context.grouping_mode ~= "none" and grouped.entity[prototype_path] or nil)
114
115  --- @type LuaEntityPrototype?
116  local grouped_entity
117  --- @type LuaEquipmentPrototype?
118  local grouped_equipment
119  --- @type LuaRecipePrototype?
120  local grouped_recipe
121  --- @type LuaTilePrototype?
122  local grouped_tile
123  if self.context.grouping_mode == "all" then
124    grouped_recipe = grouped.recipe[prototype_path]
125  end
126  if self.context.grouping_mode ~= "none" then
127    grouped_entity = grouped.entity[prototype_path]
128    grouped_equipment = grouped.equipment[prototype_path]
129    grouped_tile = grouped.tile[prototype_path]
130  end
131
132  do
133    --- @type LocalisedString
134    local type_caption = { "" }
135    --- @param sub_prototype GenericPrototype
136    local function add_type_locale(sub_prototype)
137      type_caption[#type_caption + 1] = gui_util.type_locale[sub_prototype.object_name] --- @diagnostic disable-line:assign-type-mismatch
138      type_caption[#type_caption + 1] = "/"
139    end
140
141    if self.context.grouping_mode == "all" then
142      local grouped_recipe = grouped.recipe[prototype_path]
143      if grouped_recipe then
144        add_type_locale(grouped_recipe)
145      end
146    end
147    add_type_locale(prototype)
148    if grouped_entity then
149      add_type_locale(grouped_entity)
150    end
151    if grouped_equipment then
152      add_type_locale(grouped_equipment)
153    end
154    if grouped_tile then
155      add_type_locale(grouped_tile)
156    end
157    type_caption[#type_caption] = nil
158    self.type_label.caption = type_caption
159  end
160
161  -- Contents
162
163  local force_index = self.context.player.force_index
164
165  --- @param id DatabaseID
166  --- @param holder LuaGuiElement
167  --- @return LuaGuiElement
168  local function make_list_box_item(id, holder)
169    local prototype = util.get_prototype(id)
170    local style = "rb_list_box_item"
171    if prototype.hidden_in_factoriopedia then
172      style = "rb_list_box_item_hidden"
173    elseif not researched.is(prototype, force_index) then
174      style = "rb_list_box_item_unresearched"
175    end
176
177    return holder.add({
178      type = "sprite-button",
179      style = style,
180      sprite = id.type .. "/" .. id.name,
181      caption = { "", "      ", gui_util.format_caption(id) },
182      tooltip = gui_util.format_tooltip(id),
183      elem_tooltip = { type = id.type, name = id.name },
184      tags = flib_gui.format_handlers({ [defines.events.on_gui_click] = info_pane.on_result_clicked }),
185    })
186  end
187
188  --- @param id DatabaseID
189  --- @param holder LuaGuiElement
190  --- @return LuaGuiElement
191  local function make_slot_button(id, holder)
192    local prototype = util.get_prototype(id)
193    local style = "flib_slot_button_default"
194    if prototype.hidden_in_factoriopedia then
195      style = "flib_slot_button_grey"
196    elseif not researched.is(prototype, force_index) then
197      style = "flib_slot_button_red"
198    end
199
200    local button_holder = holder.add({ type = "empty-widget", style = "rb_slot_button_holder" })
201
202    local bottom_remark, top_remark = gui_util.format_slot_button_remarks(id)
203
204    local button = button_holder.add({
205      type = "sprite-button",
206      style = style,
207      sprite = id.type .. "/" .. id.name,
208      tooltip = { "", { "gui.rb-control-hint" }, "\n", { "gui.rb-pipette-control-hint" } },
209      elem_tooltip = { type = id.type, name = id.name },
210      tags = flib_gui.format_handlers({ [defines.events.on_gui_click] = info_pane.on_result_clicked }),
211    })
212
213    if bottom_remark then
214      button_holder.add({
215        type = "label",
216        style = "rb_slot_remark_label",
217        caption = bottom_remark,
218        ignored_by_interaction = true,
219      })
220    end
221    if top_remark then
222      button_holder.add({
223        type = "label",
224        style = "rb_slot_remark_label_top",
225        caption = top_remark,
226        ignored_by_interaction = true,
227      })
228    end
229
230    return button
231  end
232
233  local content_pane = self.content_pane
234  content_pane.clear()
235  content_pane.scroll_to_top()
236  if content_pane.welcome_label then
237    content_pane.welcome_label.destroy()
238  end
239
240  local general_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
241  if self.context.player.mod_settings["rb-show-mod-owners"].value then
242    general_desc:add_prototype_history(prototype)
243  end
244  general_desc:add_description(prototype)
245  if recipe then
246    general_desc:add_recipe_properties(recipe)
247  end
248  if prototype.object_name == "LuaItemPrototype" then
249    general_desc:add_item_properties(prototype)
250  end
251  if prototype.object_name == "LuaFluidPrototype" then
252    general_desc:add_fluid_properties(prototype)
253  end
254  if entity then
255    general_desc:add_entity_properties(entity)
256  end
257  if item then
258    local place_result = item.place_result
259    if place_result and place_result ~= grouped_entity then
260      general_desc:add_id_row(
261        { "description.rb-place-result" },
262        { type = "entity", name = place_result.name },
263        force_index
264      )
265    end
266  end
267  general_desc:finalize()
268
269  if prototype.object_name == "LuaItemPrototype" then
270    local spoil_ticks = prototype.get_spoil_ticks()
271    if spoil_ticks > 0 then
272      local spoil_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
273      spoil_desc:add_category_header("tooltip-category-spoilable", { "tooltip-category.spoilable" })
274
275      spoil_desc:add_generic_row({ "description.spoil-time" }, gui_util.format_spoil_time(spoil_ticks))
276      local spoil_result = prototype.spoil_result
277      if spoil_result then
278        spoil_desc:add_id_row({ "description.spoil-result" }, { type = "item", name = spoil_result.name }, force_index)
279      end
280      -- TODO: Trigger spoil effect garbage
281    end
282  end
283
284  if entity then
285    local consumption_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
286    local consumption_id = collectors.material_consumption(entity)
287    if consumption_id then
288      consumption_desc:add_consumption(consumption_id)
289    end
290    consumption_desc:finalize()
291
292    local power_consumption_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
293    local power_consumption = collectors.power_consumption(entity)
294    if power_consumption then
295      power_consumption_desc:add_category_header(
296        "tooltip-category-electricity",
297        { "", { "tooltip-category.consumes" }, " ", { "tooltip-category.electricity" } }
298      )
299      if power_consumption.min == power_consumption.max then
300        power_consumption_desc:add_generic_row(
301          { "description.energy-consumption" },
302          gui_util.format_power(power_consumption.max)
303        )
304      else
305        power_consumption_desc:add_generic_row(
306          { "description.max-energy-consumption" },
307          gui_util.format_power(power_consumption.max)
308        )
309        power_consumption_desc:add_generic_row(
310          { "description.min-energy-consumption" },
311          gui_util.format_power(power_consumption.min)
312        )
313      end
314    end
315    power_consumption_desc:finalize()
316
317    local power_production_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
318    local power_production = collectors.power_production(entity)
319    if power_production then
320      power_production_desc:add_category_header(
321        "tooltip-category-electricity",
322        { "", { "tooltip-category.generates" }, " ", { "tooltip-category.electricity" } }
323      )
324      power_production_desc:add_generic_row(
325        { "description.maximum-power-output" },
326        gui_util.format_power(power_production)
327      )
328    end
329    power_production_desc:finalize()
330
331    local production_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
332    local production_id = collectors.material_production(entity)
333    if production_id then
334      production_desc:add_production(production_id)
335    end
336    production_desc:finalize()
337
338    local vehicle_desc = info_description.new(content_pane, self.context, info_pane.on_result_clicked)
339    vehicle_desc:add_vehicle_properties(entity)
340    vehicle_desc:finalize()
341  end
342
343  local lists_everywhere = self.context.player.mod_settings["rb-lists-everywhere"].value --[[@as boolean]]
344  local grid_builder = lists_everywhere and make_list_box_item or make_slot_button
345  local grid_column_count = lists_everywhere and 1 or 10
346
347  if recipe then
348    info_section.build(
349      content_pane,
350      self.context,
351      { "factoriopedia.ingredients" },
352      collectors.ingredients(recipe),
353      { always_show = true, remark = gui_util.format_crafting_time(recipe.energy) },
354      make_list_box_item
355    )
356    info_section.build(
357      content_pane,
358      self.context,
359      { "factoriopedia.products" },
360      collectors.products(recipe),
361      { always_show = true },
362      make_list_box_item
363    )
364    info_section.build(
365      content_pane,
366      self.context,
367      { "factoriopedia.made-in" },
368      collectors.made_in(recipe),
369      { column_count = grid_column_count },
370      --- @param id DatabaseID
371      --- @param holder LuaGuiElement
372      --- @return LuaGuiElement
373      function(id, holder)
374        local elem = grid_builder(id, holder)
375        local id_prototype = util.get_prototype(id)
376        if id_prototype.type ~= "character" then
377          elem.tooltip = { "", { "gui.rb-control-hint" }, "\n", { "gui.rb-crafter-blueprint-control-hint" } }
378          gui_util.add_tag(elem, "rb_context_id", prototype_id)
379        end
380        return elem
381      end
382    )
383  end
384
385  if prototype.object_name == "LuaFluidPrototype" or prototype.object_name == "LuaItemPrototype" then
386    info_section.build(
387      content_pane,
388      self.context,
389      { "factoriopedia.gathered-from" },
390      collectors.gathered_from(prototype),
391      { column_count = grid_column_count },
392      grid_builder
393    )
394    info_section.build(
395      content_pane,
396      self.context,
397      grouped_recipe and { "factoriopedia.alternative-recipes" } or { "description.rb-recipes" },
398      collectors.alternative_recipes(prototype, grouped_recipe),
399      { column_count = grid_column_count },
400      grid_builder
401    )
402    info_section.build(
403      content_pane,
404      self.context,
405      { "factoriopedia.used-in" },
406      collectors.used_in(prototype, grouped_recipe),
407      { column_count = grid_column_count },
408      grid_builder
409    )
410  end
411
412  if prototype.object_name == "LuaItemPrototype" then
413    info_section.build(
414      content_pane,
415      self.context,
416      { "factoriopedia.spoilage-from" },
417      collectors.spoilage_from(prototype),
418      { column_count = grid_column_count },
419      grid_builder
420    )
421    info_section.build(
422      content_pane,
423      self.context,
424      { "description.rocket-launch-products" },
425      collectors.rocket_launch_products(prototype),
426      { column_count = grid_column_count },
427      grid_builder
428    )
429    info_section.build(
430      content_pane,
431      self.context,
432      { "description.rb-rocket-launch-product-of" },
433      collectors.rocket_launch_product_of(prototype),
434      { column_count = grid_column_count },
435      grid_builder
436    )
437  end
438
439  if prototype.object_name == "LuaFluidPrototype" then
440    info_section.build(
441      content_pane,
442      self.context,
443      { "factoriopedia.generated-by" },
444      collectors.generated_by(prototype),
445      { column_count = grid_column_count },
446      grid_builder
447    )
448    info_section.build(
449      content_pane,
450      self.context,
451      { "description.rb-mining-fluid-for" },
452      collectors.mining_fluid_for(prototype),
453      { column_count = grid_column_count },
454      grid_builder
455    )
456  end
457
458  if prototype.object_name == "LuaFluidPrototype" or prototype.object_name == "LuaItemPrototype" then
459    info_section.build(
460      content_pane,
461      self.context,
462      { "factoriopedia.burned-in" },
463      collectors.burned_in(prototype),
464      { column_count = grid_column_count },
465      grid_builder
466    )
467  end
468
469  if entity then
470    info_section.build(
471      content_pane,
472      self.context,
473      { "description.rb-place-result-of" },
474      collectors.place_result_of(entity, prototype.object_name == "LuaItemPrototype" and prototype or nil),
475      { column_count = grid_column_count },
476      grid_builder
477    )
478    info_section.build(
479      content_pane,
480      self.context,
481      { "description.mined-by" },
482      collectors.mined_by(entity),
483      { column_count = grid_column_count },
484      grid_builder
485    )
486    info_section.build(
487      content_pane,
488      self.context,
489      { "factoriopedia.can-mine" },
490      collectors.can_mine(entity),
491      { column_count = grid_column_count },
492      grid_builder
493    )
494    info_section.build(
495      content_pane,
496      self.context,
497      { "description.rb-can-burn" },
498      collectors.can_burn(entity),
499      { column_count = grid_column_count },
500      grid_builder
501    )
502    local item = prototype.object_name == "LuaItemPrototype" and prototype --[[@as LuaItemPrototype]]
503      or nil
504    info_section.build(
505      content_pane,
506      self.context,
507      { "factoriopedia.yields" },
508      collectors.yields(entity, item),
509      {},
510      make_list_box_item
511    )
512  end
513
514  if recipe then
515    info_section.build(
516      content_pane,
517      self.context,
518      { "factoriopedia.unlocked-by" },
519      collectors.unlocked_by(recipe),
520      { style = "rb_technology_slot_deep_frame", column_count = 5, always_show = true },
521      --- @param id DatabaseID
522      --- @param holder LuaGuiElement
523      function(id, holder)
524        local id_prototype = util.get_prototype(id)
525        if id_prototype.hidden_in_factoriopedia and not self.context.show_hidden then
526          return
527        end
528        local research_state
529        if not researched.is(id_prototype, force_index) then
530          research_state = flib_technology.research_state.not_available
531        else
532          research_state = flib_technology.research_state.researched
533        end
534        local technology = self.context.player.force.technologies[id.name]
535        local button = flib_gui_templates.technology_slot(
536          holder,
537          technology,
538          technology.level,
539          research_state,
540          info_pane.on_result_clicked
541        )
542        -- button.tooltip = { "", { "gui.rb-control-hint" }, "\n", { "gui.rb-technology-control-hint" } }
543        button.tooltip = { "gui.rb-control-hint" }
544
545        return button
546      end
547    )
548  end
549
550  if entity then
551    info_section.build(
552      content_pane,
553      self.context,
554      { "factoriopedia.can-craft" },
555      collectors.can_craft(entity),
556      { column_count = grid_column_count },
557      grid_builder
558    )
559    info_section.build(
560      content_pane,
561      self.context,
562      { "factoriopedia.can-extract-from" },
563      collectors.can_extract_from(entity),
564      { column_count = grid_column_count },
565      grid_builder
566    )
567  end
568
569  local tile = prototype.object_name == "LuaTilePrototype" and prototype --[[@as LuaTilePrototype]]
570    or (self.context.grouping_mode ~= "none" and grouped.tile[prototype_path] or nil)
571  if prototype.object_name == "LuaTilePrototype" then
572    info_section.build(
573      content_pane,
574      self.context,
575      { "factoriopedia.source-of" },
576      collectors.source_of(tile),
577      { column_count = grid_column_count },
578      grid_builder
579    )
580    info_section.build(
581      content_pane,
582      self.context,
583      { "factoriopedia.extracted-by" },
584      collectors.extracted_by(tile),
585      { column_count = grid_column_count },
586      grid_builder
587    )
588  end
589
590  profiler.stop()
591  log({ "", "[", util.get_path(prototype), "] ", profiler })
592
593  return true
594end
595
596return info_pane