z_mirror/Krastorio2

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

Caleb HeuerChange to four-space indentation07409ca6

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