z_mirror/Krastorio2

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

Caleb HeuerFix early technology progression26da534e

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