z_mirror/Krastorio2

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

Caleb HeuerRemove unused result_inventory_size from converted furnaces0eae335e

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