z_mirror/RecipeBook

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

Caleb HeuerCheck force validity before indexingbcd2b2e

trunk
8.6 KiB281 linesraw
1local flib_table = require("__flib__.table")
2local util = require("scripts.util")
3
4--- @type table<uint, table<SpritePath, boolean>>
5local entries = {}
6
7--- @param prototype GenericPrototype
8--- @param force_index uint
9local function research(prototype, force_index)
10  assert(prototype, "Prototype was nil")
11  local path = util.get_path(prototype)
12  local researched = flib_table.get_or_insert(entries, force_index, {})
13
14  if not util.should_include(prototype) then
15    return
16  end
17  if researched[path] then
18    return
19  end
20  researched[path] = true
21
22  if prototype.object_name == "LuaTilePrototype" then
23    local fluid = prototype.fluid
24    if fluid then
25      for _, offshore_pump in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "offshore-pump" } })) do
26        if researched[util.get_path(offshore_pump)] then
27          research(fluid, force_index)
28          break
29        end
30      end
31    end
32  elseif prototype.object_name == "LuaTechnologyPrototype" then
33    for _, effect in pairs(prototype.effects) do
34      if effect.type == "unlock-recipe" then
35        research(prototypes.recipe[effect.recipe], force_index)
36      end
37    end
38  elseif prototype.object_name == "LuaRecipePrototype" then
39    if prototype.unlock_results then
40      for _, product in pairs(prototype.products) do
41        if product.type ~= "research-progress" then
42          research(prototypes[product.type][product.name], force_index)
43        end
44      end
45    end
46  elseif prototype.object_name == "LuaItemPrototype" then
47    for _, product in pairs(prototype.rocket_launch_products) do
48      if product.type ~= "research-progress" then
49        research(prototypes[product.type][product.name], force_index)
50      end
51    end
52    local burnt_result = prototype.burnt_result
53    if burnt_result then
54      research(burnt_result, force_index)
55    end
56    local place_result = prototype.place_result
57    if place_result then
58      research(place_result, force_index)
59    end
60    local place_as_tile_result = prototype.place_as_tile_result
61    if place_as_tile_result then
62      research(place_as_tile_result.result, force_index)
63    end
64    local place_as_equipment_result = prototype.place_as_equipment_result
65    if place_as_equipment_result then
66      research(place_as_equipment_result, force_index)
67    end
68    local spoil_result = prototype.spoil_result
69    if spoil_result then
70      research(spoil_result, force_index)
71    end
72    -- TODO: Spoil trigger results
73  elseif prototype.object_name == "LuaEntityPrototype" then
74    local mineable_properties = prototype.mineable_properties
75    if mineable_properties then
76      for _, product in pairs(mineable_properties.products or {}) do
77        if product.type ~= "research-progress" then
78          research(prototypes[product.type][product.name], force_index)
79        end
80      end
81    end
82    if prototype.type == "boiler" then
83      for _, fluidbox in pairs(prototype.fluidbox_prototypes) do
84        -- TODO: Are multiple outputs possible?
85        if fluidbox.production_type == "output" and fluidbox.filter then
86          research(fluidbox.filter, force_index)
87        end
88      end
89    end
90    if prototype.type == "mining-drill" then
91      --- @type string|boolean?
92      local filter
93      for _, fluidbox_prototype in pairs(prototype.fluidbox_prototypes) do
94        local production_type = fluidbox_prototype.production_type
95        if production_type ~= "output" then
96          filter = fluidbox_prototype.filter and fluidbox_prototype.filter.name or true
97          break
98        end
99      end
100      local resource_categories = prototype.resource_categories or {}
101      for _, resource in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "resource" } })) do
102        local mineable = resource.mineable_properties
103        local required_fluid = mineable.required_fluid
104        if
105          resource_categories[resource.resource_category]
106          and (not required_fluid or filter == true or filter == required_fluid)
107        then
108          research(resource, force_index)
109        end
110      end
111    end
112    if prototype.type == "offshore-pump" then
113      for _, tile in pairs(prototypes.tile) do
114        if tile.fluid and researched[util.get_path(tile)] then
115          research(tile.fluid, force_index)
116        end
117      end
118    end
119  end
120end
121
122--- @param e EventData.on_research_finished
123local function on_research_finished(e)
124  research(e.research.prototype, e.research.force.index)
125end
126
127--- @param surface LuaSurface
128local function research_surface(surface)
129  local autoplace_settings = surface.map_gen_settings.autoplace_settings
130  local entity_settings = autoplace_settings.entity
131  if entity_settings then
132    for entity_name in pairs(entity_settings.settings or {}) do
133      local entity = prototypes.entity[entity_name]
134      -- TODO: Check cases where you can hand-mine but don't have any mining drills that can mine.
135      if entity and entity.type ~= "resource" then
136        for _, force in pairs(storage.forces) do
137          if force.valid then
138            research(entity, force.index)
139          end
140        end
141      end
142    end
143  end
144  local tile_settings = autoplace_settings.tile
145  if tile_settings then
146    for tile_name in pairs(tile_settings.settings or {}) do
147      local tile = prototypes.tile[tile_name]
148      if tile then
149        for _, force in pairs(storage.forces) do
150          if force.valid then
151            research(tile, force.index)
152          end
153        end
154      end
155    end
156  end
157end
158
159--- @param e EventData.on_surface_created
160local function on_surface_created(e)
161  local surface = game.get_surface(e.surface_index)
162  if not surface then
163    return
164  end
165  research_surface(surface)
166end
167
168--- @param force LuaForce
169local function rebuild_force(force)
170  local force_index = force.index
171  -- Gather-able items
172  for _, entity in
173    pairs(prototypes.get_entity_filtered({
174      --- @diagnostic disable-next-line unused-fields
175      { filter = "type", type = "tree" },
176      --- @diagnostic disable-next-line unused-fields
177      { filter = "type", type = "fish" },
178    }))
179  do
180    research(entity, force_index)
181  end
182  -- Technologies
183  for _, technology in pairs(force.technologies) do
184    if technology.researched then
185      research(technology.prototype, force_index)
186    end
187  end
188  -- Recipes (some may be enabled without technologies)
189  for _, recipe in pairs(force.recipes) do
190    -- Some recipes will be enabled from the start, but will only be craftable in machines
191    if recipe.enabled and not (recipe.prototype.enabled and recipe.prototype.hidden_from_player_crafting) then
192      research(recipe.prototype, force_index)
193    end
194  end
195  -- Characters
196  --- @diagnostic disable-next-line unused-fields
197  for _, character in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "character" } })) do
198    research(character, force_index)
199  end
200end
201
202local function rebuild()
203  if not storage.forces or not storage.surfaces then
204    return
205  end
206  for _, surface in pairs(storage.surfaces) do
207    if surface.valid then
208      research_surface(surface)
209    end
210  end
211  for _, force in pairs(storage.forces or {}) do
212    if force.valid then
213      rebuild_force(force)
214    end
215  end
216end
217
218--- @param e EventData.on_force_created
219local function on_force_created(e)
220  if not storage.forces then
221    return
222  end
223  storage.forces[e.force.index] = e.force
224  rebuild_force(e.force)
225end
226
227local function setup_cache()
228  storage.forces = {}
229  for _, force in pairs(game.forces) do
230    storage.forces[force.index] = force
231  end
232  storage.surfaces = {}
233  for _, surface in pairs(game.surfaces) do
234    storage.surfaces[surface.index] = surface
235  end
236end
237
238--- @class Researched
239local M = {}
240
241function M.on_init()
242  setup_cache()
243  rebuild()
244end
245
246M.on_load = rebuild
247
248function M.on_configuration_changed()
249  setup_cache()
250  rebuild()
251end
252
253M.events = {
254  [defines.events.on_force_created] = on_force_created,
255  -- [defines.events.on_forces_merged] = on_forces_merged, -- TODO:
256  [defines.events.on_research_finished] = on_research_finished,
257  [defines.events.on_surface_created] = on_surface_created,
258  -- [defines.events.on_surface_deleted] = on_surface_destroyed, -- TODO:
259}
260
261--- @param prototype GenericPrototype
262--- @param force_index uint
263--- @return boolean
264function M.is(prototype, force_index)
265  local force_entries = entries[force_index]
266  if not force_entries then
267    return false
268  end
269  return force_entries[util.get_path(prototype)] or false
270end
271
272return M
273
274--[[
275  Database topology
276  - Build per-player search tree depending on their preferences
277  - Track researched objects with a simple map<SpritePath, boolean>
278  - Memoize results as they are browsed
279  - Consider pregenerating expensive lists - or maybe just pregen everything like in RB3?
280]]
281--