z_mirror/Krastorio2

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

Caleb HeuerAutomatically enable debug mode if debug adapter is being used76f4399d

real-fusion-reactor
18.2 KiB543 linesraw
1local flib_position = require("__flib__.position")
2local flib_table = require("__flib__.table")
3
4local data_util = {}
5
6local DEBUG = __DebugAdapter ~= nil
7-- DEBUG = true
8
9--- Prints the given error message to the log, or invokes a script error if in debug mode.
10--- @param message string
11function data_util.error(message)
12  if DEBUG then
13    error(message)
14  else
15    local output = "\n      KRASTORIO 2 ERROR: " .. message
16    local i = 2
17    while true do
18      local info = debug.getinfo(i, "nSl")
19      if not info then
20        break
21      end
22
23      output = output .. "\n        " .. info.source .. ":" .. info.currentline
24      if info.name then
25        output = output .. " in function '" .. info.name .. "'"
26      else
27        output = output .. " in main chunk"
28      end
29
30      i = i + 1
31    end
32    log(output)
33  end
34end
35
36--- Calls `data_util.error` with the given `message` if `obj` is falsey.
37--- @param obj any?
38--- @param message string
39function data_util.assert(obj, message)
40  if not obj then
41    data_util.error(message)
42  end
43end
44
45--- Overwrites or adds ammo effects for the given technology.
46--- @param tech data.TechnologyPrototype
47--- @param effects data.Modifier[]
48function data_util.update_ammo_effects(tech, effects)
49  if not tech.effects then
50    tech.effects = {}
51  end
52
53  for _, updated_effect in pairs(effects) do
54    local exists = false
55    for _, effect in pairs(tech.effects) do
56      if
57        effect.type == updated_effect.type
58        and (
59          (effect.ammo_category ~= nil and effect.ammo_category == updated_effect.ammo_category)
60          or (effect.turret_id ~= nil and effect.turret_id == updated_effect.turret_id)
61        )
62      then
63        effect.modifier = updated_effect.modifier --- @diagnostic disable-line:assign-type-mismatch
64        exists = true
65        break
66      end
67    end
68    if not exists then
69      table.insert(
70        tech.effects,
71        { type = updated_effect.type, ammo_category = updated_effect.ammo_category, modifier = updated_effect.modifier } --- @diagnostic disable-line:assign-type-mismatch
72      )
73    end
74  end
75end
76
77--- @param technology_name data.TechnologyID
78--- @param new_effect data.Modifier
79function data_util.add_effect(technology_name, new_effect)
80  local technology = data.raw.technology[technology_name]
81  if not technology then
82    data_util.error("Technology " .. technology_name .. " does not exist.")
83    return
84  end
85  local effects = technology.effects
86  if not effects or not next(effects) then
87    technology.effects = { new_effect }
88    return
89  end
90
91  local found = false
92  for _, effect in pairs(effects) do
93    if effect.type == new_effect.type then
94      -- TODO: Update for new effect types.
95      if effect.type == "gun-speed" then
96        if effect.ammo_category == new_effect.ammo_category and effect.modifier == new_effect.modifier then
97          found = true
98          break
99        end
100      elseif effect.type == "ammo-damage" then
101        if effect.ammo_category == new_effect.ammo_category and effect.modifier == new_effect.modifier then
102          found = true
103          break
104        end
105      elseif effect.type == "give-item" then
106        if effect.item == new_effect.item and effect.count == new_effect.count then
107          found = true
108          break
109        end
110      elseif effect.type == "turret-attack" then
111        if effect.turret_id == new_effect.turret_id and effect.modifier == new_effect.modifier then
112          found = true
113          break
114        end
115      elseif effect.type == "unlock-recipe" then
116        if effect.recipe == new_effect.recipe then
117          found = true
118          break
119        end
120      elseif effect.type == "nothing" then
121        if effect.effect_description == new_effect.effect_description then
122          found = true
123          break
124        end
125      else
126        if effect.modifier == new_effect.modifier then
127          found = true
128          break
129        end
130      end
131    end
132  end
133
134  if not found then
135    table.insert(effects, new_effect)
136  end
137end
138
139--- Adds the given recipe as an unlock of the given technology.
140--- @param technology_name data.TechnologyID
141--- @param recipe_name data.RecipeID
142function data_util.add_recipe_unlock(technology_name, recipe_name)
143  local technology = data.raw.technology[technology_name]
144  if not technology then
145    data_util.error("Technology " .. technology_name .. " does not exist.")
146    return
147  end
148  for _, effect in pairs(technology.effects) do
149    if effect.type == "unlock-recipe" and effect.recipe == recipe_name then
150      data_util.error("Technology " .. technology_name .. " already unlocks recipe " .. recipe_name .. ".")
151      return
152    end
153  end
154  table.insert(technology.effects, { type = "unlock-recipe", recipe = recipe_name })
155end
156
157--- Removes the given recipe from the unlocks of the given technology.
158--- @param technology_name data.TechnologyID
159--- @param recipe_name data.RecipeID
160function data_util.remove_recipe_unlock(technology_name, recipe_name)
161  local technology = data.raw.technology[technology_name]
162  if not technology then
163    data_util.error("Technology " .. technology_name .. " does not exist.")
164    return
165  end
166  for i, effect in pairs(technology.effects) do
167    if effect.type == "unlock-recipe" and effect.recipe == recipe_name then
168      table.remove(technology.effects, i)
169      return
170    end
171  end
172  data_util.error("Technology " .. technology_name .. " does not unlock recipe " .. recipe_name .. ".")
173end
174
175--- Adds the given prerequisite to the technology.
176--- @param technology_id data.TechnologyID
177--- @param prerequisite_id data.TechnologyID
178function data_util.add_prerequisite(technology_id, prerequisite_id)
179  local technology = data.raw.technology[technology_id]
180  if not technology then
181    data_util.error("Technology " .. technology_id .. " does not exist.")
182    return
183  end
184  local prerequisite = data.raw.technology[prerequisite_id]
185  if not prerequisite then
186    data_util.error("Technology prerequisite " .. prerequisite_id .. " does not exist.")
187    return
188  end
189  if not technology.prerequisites then
190    technology.prerequisites = { prerequisite_id }
191    return
192  end
193  if flib_table.find(technology.prerequisites, prerequisite_id) then
194    data_util.error("Technology " .. technology_id .. " already has prerequisite " .. prerequisite_id)
195    return
196  end
197  table.insert(technology.prerequisites, prerequisite_id)
198end
199
200--- Removes the given prerequisite from the technology.
201--- @param technology_id data.TechnologyID
202--- @param prerequisite_id data.TechnologyID
203function data_util.remove_prerequisite(technology_id, prerequisite_id)
204  local technology = data.raw.technology[technology_id]
205  if not technology then
206    data_util.error("Technology " .. technology_id .. " does not exist.")
207    return
208  end
209  local prerequisite = data.raw.technology[prerequisite_id]
210  if not prerequisite then
211    data_util.error("Technology prerequisite " .. prerequisite_id .. " does not exist.")
212    return
213  end
214  local i = flib_table.find(technology.prerequisites or {}, prerequisite_id)
215  if not i then
216    data_util.error("Technology " .. technology_id .. " does not have prerequisite " .. prerequisite_id)
217    return
218  end
219  table.remove(technology.prerequisites, i)
220end
221
222--- Converts one research unit ingredient to another, updating science pack prerequisites if needed.
223--- @param technology_id data.TechnologyID
224--- @param from_id data.ItemID
225--- @param to_id data.ItemID
226function data_util.convert_research_unit_ingredient(technology_id, from_id, to_id)
227  local technology = data.raw.technology[technology_id]
228  if not technology then
229    data_util.error("Technology " .. technology_id .. " does not exist.")
230    return
231  end
232  if not technology.unit or not technology.unit.ingredients then
233    data_util.error("Technology " .. technology_id .. " has no research unit ingredients.")
234    return
235  end
236  local converted = false
237  for _, ingredient in pairs(technology.unit.ingredients) do
238    if ingredient[1] == from_id then
239      converted = true
240      ingredient[1] = to_id
241    elseif ingredient[1] == to_id then
242      data_util.error("Technology " .. technology_id .. " already has research unit ingredient " .. to_id)
243      return
244    end
245  end
246  if not converted then
247    data_util.error("Technology " .. technology_id .. " does not have research unit ingredient " .. from_id)
248    return
249  end
250  -- XXX: Hardcoding the science pack name like this feels bad, but it's what the old code did...
251  local from_index = flib_table.find(technology.prerequisites, from_id)
252  if from_index then
253    table.remove(technology.prerequisites, from_index)
254  end
255  local new_tech = data.raw.technology[to_id]
256  if not new_tech then
257    return
258  end
259  if not technology.prerequisites then
260    technology.prerequisites = { to_id }
261    return
262  end
263  if not flib_table.find(technology.prerequisites, to_id) then
264    table.insert(technology.prerequisites, to_id)
265  end
266end
267
268--- Adds the given research ingredient to the technology.
269--- @param technology_id data.TechnologyID
270--- @param ingredient_id data.ItemID
271--- @param ingredient_count uint? Defaults to 1.
272function data_util.add_research_unit_ingredient(technology_id, ingredient_id, ingredient_count)
273  ingredient_count = ingredient_count or 1
274  local technology = data.raw.technology[technology_id]
275  if not technology then
276    data_util.error("Technology " .. technology_id .. " does not exist.")
277    return
278  end
279  if technology.research_trigger then
280    data_util.error("Technology " .. technology_id .. " has a research trigger, so cannot have ingredients.")
281    return
282  end
283  local unit = technology.unit
284  if not unit then
285    data_util.error("Technology " .. technology_id .. " has no research unit.")
286    return
287  end
288  for _, ingredient in pairs(technology.unit.ingredients) do
289    if ingredient[1] == ingredient_id then
290      data_util.error("Technology " .. technology_id .. " already has research unit ingredient" .. ingredient_id)
291      return
292    end
293  end
294  table.insert(technology.unit.ingredients, { ingredient_id, ingredient_count })
295end
296
297--- Removes the given research ingredient from the technology.
298--- @param technology_id data.TechnologyID
299--- @param ingredient_id data.ItemID
300function data_util.remove_research_unit_ingredient(technology_id, ingredient_id)
301  local technology = data.raw.technology[technology_id]
302  if not technology then
303    data_util.error("Technology " .. technology_id .. " does not exist.")
304    return
305  end
306  if not technology.unit then
307    data_util.error("Technology " .. technology_id .. " has no research units.")
308    return
309  end
310  for i, ingredient in pairs(technology.unit.ingredients) do
311    if ingredient[1] == ingredient_id then
312      table.remove(technology.unit.ingredients, i)
313      return
314    end
315  end
316  data_util.error("Technology " .. technology_id .. " does not have research unit ingredient " .. ingredient_id .. ".")
317end
318
319--- Returns a technology that unlocks the given recipe. The technology returned will be the one that was added earliest.
320--- @param recipe_id data.RecipeID
321--- @return data.TechnologyPrototype?
322function data_util.get_technology_for_recipe(recipe_id)
323  for _, technology in pairs(data.raw.technology) do
324    for _, effect in pairs(technology.effects or {}) do
325      if effect.type == "unlock-recipe" and effect.recipe == recipe_id then
326        return technology
327      end
328    end
329  end
330end
331
332--- Converts a furnace prototype into an assembling machine prototype.
333--- @param furnace_name data.EntityID
334--- @return data.AssemblingMachinePrototype?
335function data_util.furnace_to_assembler(furnace_name)
336  local furnace = data.raw.furnace[furnace_name]
337  if not furnace then
338    data_util.error("Furnace " .. furnace_name .. " does not exist.")
339    return
340  end
341
342  local assembler = table.deepcopy(furnace) --[[@as data.AssemblingMachinePrototype]]
343  assembler.type = "assembling-machine"
344  assembler.source_inventory_size = nil --- @diagnostic disable-line
345  assembler.energy_source.emissions_per_minute = { pollution = 2 }
346  assembler.energy_usage = "0.2MW"
347  data.raw.furnace[furnace_name] = nil
348  data:extend({ assembler })
349  return assembler
350end
351
352--- Returns a copy of the given prototype's icons in the standard format.
353--- @param prototype k2.PrototypeWithIcons
354--- @return data.IconData[]
355function data_util.get_icons(prototype)
356  local icons = prototype.icons -- LuaLS isn't inferring the dropped optional unless we assign it to a local first.
357  if icons then
358    return table.deepcopy(icons)
359  end
360  return { { icon = prototype.icon, icon_size = prototype.icon_size } }
361end
362
363--- Converts the given prototype's icons to the standard format and returns a reference to them.
364--- The game will throw an error if you provide an empty icons array, so if using the function, care must be taken to
365--- ensure that at least one icon layer exists at tne end of loading.
366--- @param prototype k2.PrototypeWithIcons
367--- @return data.IconData[]
368function data_util.standardize_icons(prototype)
369  if not prototype.icons then
370    prototype.icons = {}
371    if prototype.icon then
372      table.insert(prototype.icons, { icon = prototype.icon, icon_size = prototype.icon_size })
373      prototype.icon = nil
374      prototype.icon_size = nil
375    end
376  end
377  return prototype.icons
378end
379
380--- Overrides the prototype's icon.
381--- @param prototype k2.PrototypeWithIcons
382--- @param icon data.FileName
383--- @param icon_size double?
384function data_util.set_icon(prototype, icon, icon_size)
385  data_util.assert(prototype, "Provided prototype was nil.")
386  if prototype.icons then
387    prototype.icons = nil
388  end
389  prototype.icon = icon
390  prototype.icon_size = icon_size
391end
392
393--- Overrides the prototype's icons array.
394--- @param prototype k2.PrototypeWithIcons
395--- @param icons data.IconData[]
396function data_util.set_icons(prototype, icons)
397  data_util.assert(prototype, "Provided prototype was nil.")
398  prototype.icons = icons
399  prototype.icon = nil
400  prototype.icon_size = nil
401end
402
403--- Fields are analagous to `data.IconData`.
404--- @class k2.IconTransformOptions
405--- @field shift Vector?
406--- @field scale double?
407
408--- Applies the given transformations to each layer in the icons array in-place and returns the array.
409--- @param icons data.IconData[]
410--- @param transforms k2.IconTransformOptions
411--- @return data.IconData[]
412function data_util.transform_icons(icons, transforms)
413  for _, icon in pairs(icons) do
414    if transforms.shift then
415      icon.shift = flib_position.add((icon.shift or { 0, 0 }), transforms.shift)
416    end
417    if transforms.scale then
418      icon.scale = (icon.scale or 1) * transforms.scale
419    end
420  end
421  return icons
422end
423
424--- Adds or replaces the recipe's ingredient matching the given name.
425--- @param recipe_name data.RecipeID
426--- @param old_ingredient_name data.FluidID|data.ItemID
427--- @param new_ingredient data.IngredientPrototype
428function data_util.add_or_replace_ingredient(recipe_name, old_ingredient_name, new_ingredient)
429  local recipe = data.raw.recipe[recipe_name]
430  if not recipe then
431    data_util.error("Recipe " .. recipe_name .. " does not exist.")
432    return
433  end
434  if not recipe.ingredients then
435    recipe.ingredients = { new_ingredient }
436    return
437  end
438  for i, ingredient in pairs(recipe.ingredients) do
439    if ingredient.name == old_ingredient_name then
440      recipe.ingredients[i] = new_ingredient
441      return
442    end
443  end
444  table.insert(recipe.ingredients, new_ingredient)
445end
446
447--- Converts the given ingredient to the new ingredient, carrying over other properties.
448--- @param recipe_name data.RecipeID
449--- @param old_ingredient_name data.FluidID|data.ItemID
450--- @param new_ingredient_name data.FluidID|data.ItemID
451function data_util.convert_ingredient(recipe_name, old_ingredient_name, new_ingredient_name)
452  local recipe = data.raw.recipe[recipe_name]
453  if not recipe then
454    data_util.error("Recipe " .. recipe_name .. " does not exist.")
455    return
456  end
457  if not recipe.ingredients then
458    data_util.error("Recipe " .. recipe_name .. " has no ingredients.")
459    return
460  end
461  for _, ingredient in pairs(recipe.ingredients) do
462    if ingredient.name == old_ingredient_name then
463      ingredient.name = new_ingredient_name
464      break
465    end
466  end
467end
468
469--- Remove the given ingredient from the recipe.
470--- @param recipe_name data.RecipeID
471--- @param ingredient_name data.FluidID|data.ItemID
472function data_util.remove_ingredient(recipe_name, ingredient_name)
473  local recipe = data.raw.recipe[recipe_name]
474  if not recipe then
475    data_util.error("Recipe " .. recipe_name .. " does not exist.")
476    return
477  end
478  if not recipe.ingredients then
479    data_util.error("Recipe " .. recipe_name .. " has no ingredients.")
480    return
481  end
482  for i, ingredient in pairs(recipe.ingredients) do
483    if ingredient.name == ingredient_name then
484      table.remove(recipe.ingredients, i)
485      return
486    end
487  end
488  data_util.error("Recipe " .. recipe_name .. " does not have ingredient " .. ingredient_name .. ".")
489end
490
491--- Adds or replaces the recipe's product matching the given name.
492--- @param recipe_name data.RecipeID
493--- @param old_product_name data.FluidID|data.ItemID
494--- @param new_product data.ProductPrototype
495function data_util.add_or_replace_product(recipe_name, old_product_name, new_product)
496  local recipe = data.raw.recipe[recipe_name]
497  if not recipe then
498    data_util.error("Recipe " .. recipe_name .. " does not exist.")
499    return
500  end
501  if not recipe.results then
502    recipe.results = { new_product }
503    return
504  end
505  for i, product in pairs(recipe.results) do
506    if product.name == old_product_name then
507      recipe.results[i] = new_product
508      return
509    end
510  end
511  table.insert(recipe.results, new_product)
512end
513
514--- @param lab_name data.EntityID
515--- @param input_name data.ItemID
516function data_util.remove_lab_input(lab_name, input_name)
517  local lab = data.raw.lab[lab_name]
518  data_util.assert(lab, "Lab " .. lab_name .. " does not exist.")
519  local inputs = lab.inputs
520  data_util.assert(inputs, "Lab inputs are nil.")
521  for i = 1, #inputs do
522    if inputs[i] == input_name then
523      table.remove(inputs, i)
524      return
525    end
526  end
527  data_util.error("Lab " .. lab_name .. " does not contain input " .. input_name .. ".")
528end
529
530--- @param energy_source data.EnergySource
531--- @param category_name data.FuelCategoryID
532function data_util.add_fuel_category(energy_source, category_name)
533  if energy_source.type ~= "burner" then
534    data_util.error("Energy source must be a burner to add a fuel category.")
535    return
536  end
537  if not energy_source.fuel_categories then
538    energy_source.fuel_categories = {}
539  end
540  table.insert(energy_source.fuel_categories, category_name)
541end
542
543return data_util