z_mirror/RecipeBook

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

Caleb HeuerCheck fluid energy source fluidbox filter for burned in3fc4959

trunk
22.7 KiB729 linesraw
1local flib_math = require("__flib__.math")
2local flib_table = require("__flib__.table")
3
4local util = require("scripts.util")
5
6--- @alias DatabaseIDType
7--- | "entity"
8--- | "equipment"
9--- | "fluid"
10--- | "item"
11--- | "recipe"
12--- | "technology"
13--- | "tile"
14
15--- @class DatabaseID
16--- @field type DatabaseIDType
17--- @field name string
18--- @field amount number?
19--- @field amount_min number?
20--- @field amount_max number?
21--- @field time number?
22--- @field probability number?
23--- @field temperature number?
24--- @field minimum_temperature number?
25--- @field maximum_temperature number?
26--- @field extra_count_fraction number?
27
28--- @class Collectors
29local collectors = {}
30
31--- @param prototype LuaRecipePrototype
32--- @return DatabaseID[]
33function collectors.ingredients(prototype)
34  local output = {}
35
36  for _, ingredient in pairs(prototype.ingredients) do
37    output[#output + 1] = {
38      type = ingredient.type,
39      name = ingredient.name,
40      amount = ingredient.amount,
41      minimum_temperature = ingredient.minimum_temperature,
42      maximum_temperature = ingredient.maximum_temperature,
43    }
44  end
45
46  return output
47end
48
49--- @param prototype LuaRecipePrototype
50--- @return DatabaseID[]
51function collectors.products(prototype)
52  local output = {}
53
54  for _, product in pairs(prototype.products) do
55    if product.type ~= "research-progress" then
56      output[#output + 1] = {
57        type = product.type,
58        name = product.name,
59        amount = product.amount,
60        amount_min = product.amount_min,
61        amount_max = product.amount_max,
62        temperature = product.temperature,
63        probability = product.probability,
64        extra_count_fraction = product.extra_count_fraction,
65      }
66    end
67  end
68
69  return output
70end
71
72--- @param prototype LuaRecipePrototype
73--- @return DatabaseID[]
74function collectors.made_in(prototype)
75  local output = util.unique_id_array()
76
77  --- @diagnostic disable-next-line unused-fields
78  for _, character in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "character" } })) do
79    if character.crafting_categories[prototype.category] then
80      output[#output + 1] = {
81        type = "entity",
82        name = character.name,
83        time = prototype.energy,
84      }
85    end
86  end
87
88  local item_ingredients = flib_table.reduce(prototype.ingredients, function(accumulator, ingredient)
89    return accumulator + (ingredient.type == "item" and 1 or 0)
90  end, 0) --[[@as integer]]
91
92  for _, crafter in
93    pairs(prototypes.get_entity_filtered({
94      --- @diagnostic disable-next-line unused-fields
95      { filter = "crafting-category", crafting_category = prototype.category },
96    }))
97  do
98    local ingredient_count = crafter.ingredient_count
99    if ingredient_count == 0 or ingredient_count >= item_ingredients then
100      output[#output + 1] = {
101        type = "entity",
102        name = crafter.name,
103        time = prototype.energy / crafter.get_crafting_speed(), -- TODO: Quality
104      }
105    end
106  end
107
108  return output
109end
110
111--- @param prototype LuaFluidPrototype|LuaItemPrototype
112--- @return DatabaseID[]
113function collectors.gathered_from(prototype)
114  local output = util.unique_id_array()
115
116  if prototype.object_name == "LuaFluidPrototype" then
117    for tile_name, tile in pairs(prototypes.tile) do
118      if tile.fluid == prototype then
119        output[#output + 1] = { type = "tile", name = tile_name }
120      end
121    end
122  end
123
124  local type = util.object_name_to_type[prototype.object_name]
125
126  for entity_name, entity in pairs(util.get_natural_entities()) do
127    if prototype.name ~= entity_name then
128      local mineable_properties = entity.mineable_properties
129      if mineable_properties.minable then
130        for _, product in pairs(mineable_properties.products or {}) do
131          if product.type == type and product.name == prototype.name then
132            output[#output + 1] = { type = "entity", name = entity_name }
133          end
134        end
135      end
136    end
137  end
138
139  return output
140end
141
142--- @type table<string, string[]>
143local spoilage_from_cache = {}
144for item_name, item in pairs(prototypes.item) do
145  local spoil_result = item.spoil_result
146  if spoil_result then
147    table.insert(
148      flib_table.get_or_insert(spoilage_from_cache, spoil_result.name, util.unique_id_array()),
149      { type = "item", name = item_name }
150    )
151  end
152end
153
154--- @param prototype LuaItemPrototype
155--- @return DatabaseID[]
156function collectors.spoilage_from(prototype)
157  return spoilage_from_cache[prototype.name] or {}
158end
159
160--- @param prototype LuaItemPrototype
161--- @return DatabaseID[]
162function collectors.rocket_launch_products(prototype)
163  local output = util.unique_id_array()
164
165  for _, product in pairs(prototype.rocket_launch_products) do
166    if product.type ~= "research-progress" then
167      output[#output + 1] = {
168        type = product.type,
169        name = product.name,
170        amount = product.amount,
171        amount_min = product.amount_min,
172        amount_max = product.amount_max,
173        temperature = product.temperature,
174        probability = product.probability,
175      }
176    end
177  end
178
179  return output
180end
181
182--- @param prototype LuaItemPrototype
183--- @return DatabaseID[]
184function collectors.rocket_launch_product_of(prototype)
185  local output = util.unique_id_array()
186
187  --- @diagnostic disable-next-line unused-fields
188  for _, other_item in pairs(prototypes.get_item_filtered({ { filter = "has-rocket-launch-products" } })) do
189    for _, product in pairs(other_item.rocket_launch_products) do
190      if product.name == prototype.name then
191        output[#output + 1] = { type = "item", name = other_item.name }
192        break
193      end
194    end
195  end
196
197  return output
198end
199
200--- @param prototype LuaFluidPrototype
201--- @return DatabaseID[]
202function collectors.generated_by(prototype)
203  local output = util.unique_id_array()
204
205  for _, boiler_prototype in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "boiler" } })) do
206    local generates = collectors.generated_fluid(boiler_prototype)
207    if generates and generates.name == prototype.name then
208      output[#output + 1] = {
209        type = "entity",
210        name = boiler_prototype.name,
211        temperature = boiler_prototype.target_temperature,
212      }
213    end
214  end
215
216  return output
217end
218
219--- @param prototype LuaFluidPrototype
220--- @return DatabaseID[]
221function collectors.mining_fluid_for(prototype)
222  local output = util.unique_id_array()
223
224  for resource_name, resource in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "resource" } })) do
225    local mineable = resource.mineable_properties
226    if mineable.minable and mineable.required_fluid == prototype.name then
227      output[#output + 1] = { type = "entity", name = resource_name }
228    end
229  end
230
231  return output
232end
233
234--- @param prototype LuaEntityPrototype
235--- @return DatabaseID?
236function collectors.generated_fluid(prototype)
237  if prototype.type ~= "boiler" then
238    return
239  end
240
241  for _, fluidbox in pairs(prototype.fluidbox_prototypes) do
242    if fluidbox.production_type == "output" and fluidbox.filter then
243      return { type = "fluid", name = fluidbox.filter.name, temperature = prototype.target_temperature }
244    end
245  end
246end
247
248--- @param prototype LuaEntityPrototype
249--- @param grouped_item LuaItemPrototype?
250--- @return DatabaseID[]
251function collectors.place_result_of(prototype, grouped_item)
252  local output = util.unique_id_array()
253
254  for _, item_definition in pairs(prototype.items_to_place_this or {}) do
255    if not grouped_item or grouped_item.name ~= item_definition.name then
256      output[#output + 1] = { type = "item", name = item_definition.name, amount = item_definition.count }
257    end
258  end
259
260  return output
261end
262
263--- @param prototype LuaEntityPrototype
264--- @return DatabaseID[]
265function collectors.mined_by(prototype)
266  local output = util.unique_id_array()
267
268  local required_fluid = prototype.mineable_properties.required_fluid
269  local resource_category = prototype.resource_category
270  for _, drill in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "mining-drill" } })) do
271    if drill.resource_categories[resource_category] and (not required_fluid or drill.fluidbox_prototypes[1]) then
272      output[#output + 1] = { type = "entity", name = drill.name }
273    end
274  end
275
276  return output
277end
278
279--- @param prototype LuaFluidPrototype|LuaItemPrototype
280--- @param grouped_recipe LuaRecipePrototype?
281--- @return DatabaseID[]
282function collectors.alternative_recipes(prototype, grouped_recipe)
283  local output = util.unique_id_array()
284  if prototype.object_name == "LuaFluidPrototype" then
285    for _, recipe in
286      pairs(prototypes.get_recipe_filtered({
287        --- @diagnostic disable-next-line unused-fields
288        { filter = "has-product-fluid", elem_filters = { { filter = "name", name = prototype.name } } },
289      }))
290    do
291      if recipe ~= grouped_recipe then
292        local id = { type = "recipe", name = recipe.name }
293        for _, product in pairs(recipe.products) do
294          if product.name == prototype.name and product.temperature then
295            id.temperature = product.temperature
296            break
297          end
298        end
299        output[#output + 1] = id
300      end
301    end
302  else
303    for _, recipe in
304      pairs(prototypes.get_recipe_filtered({
305        --- @diagnostic disable-next-line unused-fields
306        { filter = "has-product-item", elem_filters = { { filter = "name", name = prototype.name } } },
307      }))
308    do
309      if recipe ~= grouped_recipe then
310        output[#output + 1] = { type = "recipe", name = recipe.name }
311      end
312    end
313  end
314
315  return output
316end
317
318--- @param prototype LuaFluidPrototype|LuaItemPrototype
319--- @param grouped_recipe LuaRecipePrototype?
320--- @return DatabaseID[]
321function collectors.used_in(prototype, grouped_recipe)
322  local output = util.unique_id_array()
323
324  if prototype.object_name == "LuaFluidPrototype" then
325    for _, recipe in
326      pairs(prototypes.get_recipe_filtered({
327        --- @diagnostic disable-next-line unused-fields
328        { filter = "has-ingredient-fluid", elem_filters = { { filter = "name", name = prototype.name } } },
329      }))
330    do
331      if recipe ~= grouped_recipe then
332        local id = { type = "recipe", name = recipe.name }
333        for _, ingredient in pairs(recipe.ingredients) do
334          -- minimum_temperature and maximum_temperature are mutually inclusive.
335          if ingredient.name == prototype.name and ingredient.minimum_temperature then
336            id.minimum_temperature = ingredient.minimum_temperature
337            id.maximum_temperature = ingredient.maximum_temperature
338            break
339          end
340        end
341        output[#output + 1] = id
342      end
343    end
344  else
345    for _, recipe in
346      pairs(prototypes.get_recipe_filtered({
347        --- @diagnostic disable-next-line unused-fields
348        { filter = "has-ingredient-item", elem_filters = { { filter = "name", name = prototype.name } } },
349      }))
350    do
351      if recipe ~= grouped_recipe then
352        output[#output + 1] = { type = "recipe", name = recipe.name }
353      end
354    end
355  end
356
357  return output
358end
359
360--- @param prototype LuaFluidPrototype|LuaItemPrototype
361--- @return DatabaseID[]
362function collectors.burned_in(prototype)
363  local output = util.unique_id_array()
364
365  if prototype.object_name == "LuaFluidPrototype" then
366    --- @diagnostic disable-next-line unused-fields
367    for entity_name, entity in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "generator" } })) do
368      local fluid_box = entity.fluidbox_prototypes[1]
369      if
370        (fluid_box.filter and fluid_box.filter.name == prototype.name)
371        or (not fluid_box.filter and prototype.fuel_value > 0)
372      then
373        output[#output + 1] = { type = "entity", name = entity_name }
374      end
375    end
376    --- @diagnostic disable-next-line unused-fields
377    for entity_name, entity in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "boiler" } })) do
378      for _, fluidbox in pairs(entity.fluidbox_prototypes) do
379        if
380          (fluidbox.production_type == "input" or fluidbox.production_type == "input-output")
381          and fluidbox.filter
382          and fluidbox.filter.name == prototype.name
383        then
384          output[#output + 1] = { type = "entity", name = entity_name }
385        end
386      end
387    end
388    --- @diagnostic disable-next-line unused-fields
389    for entity_name, entity in pairs(prototypes.get_entity_filtered({ { filter = "building" } })) do
390      local fluid_energy_source_prototype = entity.fluid_energy_source_prototype
391      if fluid_energy_source_prototype then
392        local filter = fluid_energy_source_prototype.fluid_box.filter
393        if
394          (not filter and (not fluid_energy_source_prototype.burns_fluid or prototype.fuel_value > 0))
395          or filter == prototype
396        then
397          output[#output + 1] = { type = "entity", name = entity_name }
398        end
399      end
400    end
401  else
402    if prototype.fuel_value == 0 then
403      return output
404    end
405    local fuel_category = prototype.fuel_category
406    for entity_name, entity_prototype in pairs(prototypes.entity) do
407      local burner = entity_prototype.burner_prototype
408      if burner and burner.fuel_categories[fuel_category] then
409        output[#output + 1] = { type = "entity", name = entity_name }
410      end
411    end
412    for equipment_name, equipment_prototype in pairs(prototypes.equipment) do
413      local burner = equipment_prototype.burner_prototype
414      if burner and burner.fuel_categories[fuel_category] then
415        output[#output + 1] = { type = "equipment", name = equipment_name }
416      end
417    end
418  end
419
420  return output
421end
422
423--- @param prototype LuaEntityPrototype
424--- @return DatabaseID[]
425function collectors.can_mine(prototype)
426  local output = util.unique_id_array()
427
428  --- @type string|boolean?
429  local filter
430  for _, fluidbox_prototype in pairs(prototype.fluidbox_prototypes) do
431    local production_type = fluidbox_prototype.production_type
432    if production_type ~= "output" then
433      filter = fluidbox_prototype.filter and fluidbox_prototype.filter.name or true
434      break
435    end
436  end
437  local resource_categories = prototype.resource_categories or {}
438  for _, resource in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "resource" } })) do
439    local mineable = resource.mineable_properties
440    local required_fluid = mineable.required_fluid
441    if
442      resource_categories[resource.resource_category]
443      and (not required_fluid or filter == true or filter == required_fluid)
444    then
445      output[#output + 1] = { type = "entity", name = resource.name }
446    end
447  end
448
449  return output
450end
451
452--- @param prototype LuaEntityPrototype
453--- @return DatabaseID[]
454function collectors.can_burn(prototype)
455  local output = util.unique_id_array()
456
457  local burner = prototype.burner_prototype
458  if burner then
459    for category in pairs(burner.fuel_categories) do
460      for item_name in
461        --- @diagnostic disable-next-line unused-fields
462        pairs(prototypes.get_item_filtered({ { filter = "fuel-category", ["fuel-category"] = category } }))
463      do
464        output[#output + 1] = { type = "item", name = item_name }
465      end
466    end
467  end
468  local fluid_energy_source_prototype = prototype.fluid_energy_source_prototype
469  if fluid_energy_source_prototype then
470    local filter = fluid_energy_source_prototype.fluid_box.filter
471    if filter then
472      output[#output + 1] = { type = "fluid", name = filter.name }
473    else
474      for fluid_name in
475        --- @diagnostic disable-next-line unused-fields
476        pairs(prototypes.get_fluid_filtered({ { filter = "fuel-value", comparison = ">", value = 0 } }))
477      do
478        output[#output + 1] = { type = "fluid", name = fluid_name }
479      end
480    end
481  end
482  if prototype.type == "generator" then
483    local fluid_box = prototype.fluidbox_prototypes[1]
484    if fluid_box.filter then
485      output[#output + 1] = { type = "fluid", name = fluid_box.filter.name }
486    else
487      for fluid_name in
488        --- @diagnostic disable-next-line unused-fields
489        pairs(prototypes.get_fluid_filtered({ { filter = "fuel-value", comparison = ">", value = 0 } }))
490      do
491        output[#output + 1] = { type = "fluid", name = fluid_name }
492      end
493    end
494  end
495
496  return output
497end
498
499local yields = {
500  ["fish"] = true,
501  ["resource"] = true,
502  ["simple-entity"] = true,
503  ["tree"] = true,
504}
505
506--- @param prototype LuaEntityPrototype
507--- @param grouped_item LuaItemPrototype?
508--- @return DatabaseID[]
509function collectors.yields(prototype, grouped_item)
510  local output = util.unique_id_array()
511
512  if not yields[prototype.type] then
513    return output
514  end
515
516  local mineable_properties = prototype.mineable_properties
517  if not mineable_properties or not mineable_properties.minable then
518    return output
519  end
520
521  local products = mineable_properties.products
522  if not products then
523    return output
524  end
525
526  if not (#products == 1 and grouped_item and products[1].type == "item" and products[1].name == grouped_item.name) then
527    local products = mineable_properties.products
528    if products then
529      for _, product in pairs(products) do
530        if product.type ~= "research-progress" then
531          output[#output + 1] = {
532            type = product.type,
533            name = product.name,
534            amount = product.amount,
535            amount_min = product.amount_min,
536            amount_max = product.amount_max,
537            temperature = product.temperature,
538            probability = product.probability,
539          }
540        end
541      end
542    end
543  end
544
545  return output
546end
547
548--- @param prototype LuaRecipePrototype
549--- @return DatabaseID[]
550function collectors.unlocked_by(prototype)
551  local output = util.unique_id_array()
552
553  if not prototype.unlock_results or prototype.enabled then
554    return output
555  end
556
557  for technology_name in
558    --- @diagnostic disable-next-line unused-fields
559    pairs(prototypes.get_technology_filtered({ { filter = "unlocks-recipe", recipe = prototype.name } }))
560  do
561    output[#output + 1] = { type = "technology", name = technology_name }
562  end
563
564  return output
565end
566
567local crafting_entities = {
568  ["assembling-machine"] = true,
569  ["furnace"] = true,
570  ["rocket-silo"] = true,
571  ["character"] = true,
572}
573
574--- @param prototype LuaEntityPrototype
575--- @return DatabaseID[]
576function collectors.can_craft(prototype)
577  local output = util.unique_id_array()
578
579  if not crafting_entities[prototype.type] then
580    return {}
581  end
582
583  --- @type RecipePrototypeFilter[]
584  local filters = {}
585  for category in pairs(prototype.crafting_categories) do
586    filters[#filters + 1] = { filter = "category", category = category }
587  end
588  for _, recipe in pairs(prototypes.get_recipe_filtered(filters)) do
589    if not recipe.parameter then
590      local item_ingredients = 0
591      for _, ingredient in pairs(recipe.ingredients) do
592        if ingredient.type == "item" then
593          item_ingredients = item_ingredients + 1
594        end
595      end
596      local ingredient_count = prototype.ingredient_count
597      if not ingredient_count or ingredient_count >= item_ingredients then
598        output[#output + 1] = { type = "recipe", name = recipe.name }
599      end
600    end
601  end
602
603  return output
604end
605
606--- @param prototype LuaEntityPrototype
607--- @return DatabaseID[]
608function collectors.can_extract_from(prototype)
609  local output = util.unique_id_array()
610
611  if prototype.type ~= "offshore-pump" then
612    return output
613  end
614
615  for _, tile_prototype in pairs(prototypes.tile) do
616    if tile_prototype.fluid then
617      output[#output + 1] = { type = "tile", name = tile_prototype.name }
618    end
619  end
620
621  return output
622end
623
624--- @param prototype LuaTilePrototype
625--- @return DatabaseID[]
626function collectors.source_of(prototype)
627  local output = util.unique_id_array()
628  local fluid = prototype.fluid
629  if fluid then
630    output[#output + 1] = { type = "fluid", name = fluid.name }
631  end
632  return output
633end
634
635--- @param prototype LuaTilePrototype
636--- @return DatabaseID[]
637function collectors.extracted_by(prototype)
638  local output = util.unique_id_array()
639
640  if not prototype.fluid then
641    return output
642  end
643
644  for _, offshore_pump in pairs(prototypes.get_entity_filtered({ { filter = "type", type = "offshore-pump" } })) do
645    output[#output + 1] = { type = "entity", name = offshore_pump.name }
646  end
647
648  return output
649end
650
651--- @param prototype LuaEntityPrototype
652--- @return DatabaseID?
653function collectors.material_consumption(prototype)
654  if prototype.type == "boiler" and prototype.boiler_mode == "output-to-separate-pipe" then
655    local input_fluid_box = prototype.fluidbox_prototypes[1]
656    local input_filter = input_fluid_box.filter
657    if input_filter then
658      local minimum_temperature = input_fluid_box.minimum_temperature or input_filter.default_temperature
659      local flow_per_tick = (prototype.target_temperature - minimum_temperature) * input_filter.heat_capacity
660      return { type = "fluid", name = input_filter.name, amount = prototype.get_max_energy_usage() / flow_per_tick * 60 } -- TODO: Quality
661    end
662  elseif prototype.type == "generator" then
663    local fluid_box = prototype.fluidbox_prototypes[1]
664    local filter = fluid_box.filter
665    if filter then
666      return {
667        type = "fluid",
668        name = filter.name,
669        amount = prototype.fluid_usage_per_tick * 60,
670        maximum_temperature = prototype.maximum_temperature,
671      }
672    end
673  end
674end
675
676--- @param prototype LuaEntityPrototype
677--- @return DatabaseID?
678function collectors.material_production(prototype)
679  if prototype.type == "boiler" and prototype.boiler_mode == "output-to-separate-pipe" then
680    local input_fluid_box = prototype.fluidbox_prototypes[1]
681    local input_filter = input_fluid_box.filter
682    if input_filter then
683      local output_fluid_box = prototype.fluidbox_prototypes[2]
684      local output_filter = output_fluid_box.filter
685      if output_filter then
686        local minimum_temperature = input_fluid_box.minimum_temperature or input_filter.default_temperature
687        local flow_per_tick = (prototype.target_temperature - minimum_temperature) * input_filter.heat_capacity
688        return {
689          type = "fluid",
690          name = output_filter.name,
691          amount = prototype.get_max_energy_usage() / flow_per_tick * 60, -- TODO: Quality
692          temperature = prototype.target_temperature,
693        }
694      end
695    end
696  end
697end
698
699--- @param prototype LuaEntityPrototype
700--- @return {min: double, max: double}?
701function collectors.power_consumption(prototype)
702  local electric_energy_source_prototype = prototype.electric_energy_source_prototype --[[@as LuaElectricEnergySourcePrototype]]
703  if not electric_energy_source_prototype then
704    return
705  end
706
707  -- local added_emissions = 0
708  local max_energy_usage = prototype.get_max_energy_usage() or 0 -- TODO: Quality
709  if max_energy_usage > 0 and max_energy_usage < flib_math.max_int53 then
710    local drain = electric_energy_source_prototype.drain
711    if max_energy_usage ~= drain then
712      max_energy_usage = max_energy_usage + drain
713    end
714    return { min = drain * 60, max = max_energy_usage * 60 }
715  end
716end
717
718--- @param prototype LuaEntityPrototype
719--- @return double?
720function collectors.power_production(prototype)
721  local output = prototype.get_max_power_output() -- TODO: Quality
722  if not output then
723    return
724  end
725
726  return output * 60
727end
728
729return collectors