diff --git a/changelog.txt b/changelog.txt index 67daa5c..edac17b 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,17 @@ --------------------------------------------------------------------------------------------------- +Version: 3.1.0 +Date: ??? + Changes: + - Updated to Factorio 2.1 + - Removed RECIPE.change_category, added add_category, remove_category, replace_category, has_category, and has_categories + - Updated annotations for emmylua integration + - Updated py.farm_speed and py.farm_speed_derived to use 0-indexed module slots with a building at native -100% speed + - Updated certain tests for new prototype properties + - Updated global checker + - Updated py.pipe_pictures to load graphics definitions directly from base instead of inferring them, allowing more to be used + - Fixed a potential crash when no tools are defined + - Added py.generate_alert and py.clear_alert to generate rendered and pinnable alerts for all players on a force +--------------------------------------------------------------------------------------------------- Version: 3.0.42 Date: 2026-05-27 Changes: diff --git a/data-final-fixes.lua b/data-final-fixes.lua index 372ddac..450155d 100644 --- a/data-final-fixes.lua +++ b/data-final-fixes.lua @@ -22,16 +22,7 @@ end if feature_flags.spoiling then local spoilage_loops = {} -- all known loops local spoilage_chains = {} -- all known - for _, prototype in pairs{ - "item", - "ammo", - "capsule", - "gun", - "module", - "tool", - "armor", - "repair-tool" - } do for _, spoiler in pairs(data.raw[prototype]) do + for _, prototype in pairs(defines.prototypes.item) do for _, spoiler in pairs(data.raw[prototype] or {}) do if spoiler.spoil_result then local spoil_result, spoil_chain, last_checked = find_base_spoil_result(spoiler, {}) if spoil_result then -- spoilage chain found, set accordingly @@ -113,7 +104,6 @@ local signal_recipes = { local create_signal_mode = settings.startup["pypp-extended-recipe-signals"].value for _, recipe in pairs(data.raw.recipe) do - recipe.always_show_products = true recipe.always_show_made_in = true if not recipe.maximum_productivity then recipe.maximum_productivity = 1000000 end -- Disable the max productivity cap @@ -194,7 +184,7 @@ if create_signal_mode then if #alternatives > 1 then for _, recipe in pairs(alternatives) do -- Skip recipe categories where signals aren't useful for any recipe - if (recipe.category and (recipe.category.name == "compost" or recipe.category.name == "py-barreling")) then + if (recipe:has_category("compost") or recipe:has_category("py-barreling")) then break end -- Determine amount of main product to display in signal name @@ -202,7 +192,7 @@ if create_signal_mode then local main_product_name = recipe:get_main_product(true).name for _, result in pairs(recipe.results) do if result.name == main_product_name then - if result.probability and result.probability < 1 then + if result.independent_probability and result.independent_probability < 1 then -- Some recipes have random amount for multiples, such as nuclear isotopes. local prob_amt = 0 if result.amount then @@ -210,7 +200,7 @@ if create_signal_mode then elseif result.amount_min and result.amount_max then prob_amt = (result.amount_min + result.amount_max) / 2 end - amt = result.probability * prob_amt + amt = result.independent_probability * prob_amt break elseif result.amount_min and result.amount_max then amt = (result.amount_min + result.amount_max) / 2 @@ -223,7 +213,6 @@ if create_signal_mode then end -- Inject recipe output into each localised name parameter, since native output display is not consistently shown if recipe.localised_name[1] == "?" then - recipe.show_amount_in_title = false for i, name in pairs(recipe.localised_name) do if i > 1 and amt ~= 1 then recipe.localised_name[i] = {"recipe-name.recipe-amount", tostring(amt), name} @@ -406,9 +395,9 @@ end for _, lab in pairs(data.raw.lab) do table.sort(lab.inputs, function(i1, i2) - local science_pack_a = data.raw.tool[i1] + local science_pack_a = ITEM(i1) if not science_pack_a then error("Missing science pack prototype " .. i1 .. " in lab " .. lab.name) end - local science_pack_b = data.raw.tool[i2] + local science_pack_b = ITEM(i2) if not science_pack_b then error("Missing science pack prototype " .. i2 .. " in lab " .. lab.name) end return science_pack_a.order < science_pack_b.order end) @@ -541,6 +530,10 @@ for _, bot_type in pairs {"construction-robot", "logistic-robot"} do end end +-- unhide the shortcut to toggle tall entities and make it available from the start +data.raw.shortcut["toggle-tall-entity-visibility"].hidden = nil +data.raw.shortcut["toggle-tall-entity-visibility"].technology_to_unlock = nil + -- Skip check if user has [declutter](https://mods.factorio.com/mod/declutter) mod which hides arbitrary techs -- Also skip check if user has autotech mod, since autotech runs after this check. if not (mods.declutter or mods.autotech) then @@ -556,4 +549,19 @@ if not (mods.declutter or mods.autotech) then end end +-- cleanup +if not mods.autotech then + for _, type in pairs(data.raw) do + for _, prototype in pairs(type) do + prototype.autotech_ignore = nil + prototype.autotech_always_available = nil + if prototype.results then + for _, result in pairs(prototype.results) do + result.autotech_is_not_primary_source = nil + end + end + end + end +end + if settings.startup["pypp-tests"].value then require "tests.data" end diff --git a/data-updates.lua b/data-updates.lua index ae8a13a..89bc1cb 100644 --- a/data-updates.lua +++ b/data-updates.lua @@ -22,8 +22,8 @@ local function set_underground_recipe(underground, belt, prev_underground, prev_ end end - if fluid and (RECIPE(underground).category or "crafting") == "crafting" then - RECIPE(underground):set_fields {category = "crafting-with-fluid"} + if fluid then + RECIPE(underground):remove_category("crafting"):add_category("crafting-with-fluid") end end @@ -37,10 +37,10 @@ set_underground_recipe("fast-underground-belt", "fast-transport-belt", "undergro set_underground_recipe("express-underground-belt", "express-transport-belt", "fast-underground-belt", "fast-transport-belt") local big_recipe_icons_blacklist = { - -- ["rc-mk01"] = true, - -- ["rc-mk02"] = true, - -- ["rc-mk03"] = true, - -- ["rc-mk04"] = true, + ["rc-mk01"] = true, + ["rc-mk02"] = true, + ["rc-mk03"] = true, + ["rc-mk04"] = true, } for _, prototype in pairs {"assembling-machine", "furnace", "container", "logistic-container"} do diff --git a/info.json b/info.json index 37fae80..f511520 100644 --- a/info.json +++ b/info.json @@ -1,14 +1,14 @@ { "name": "pypostprocessing", - "version": "3.0.42", - "factorio_version": "2.0", + "version": "3.1.0", + "factorio_version": "2.1", "title": "Pyanodons Post-processing", - "author": "Pyanodon, Shadowglass, LambdaLemon", + "author": "Pyanodon, Shadowglass, LambdaLemon, protocol_1903", "contact": "https://discord.gg/SBHM3h5Utj", "homepage": "https://mods.factorio.com/mods/pyanodon/pytech", "description": "Post-processing steps for Pyanodons modpack. Overhauls the technology tree to make sure prerequisites are set based on unlocked recipes.", "dependencies": [ - "base >= 2.0.48", + "base >= 2.1.0", "? quality", "? elevated-rails", "? space-age", diff --git a/lib/alerts.lua b/lib/alerts.lua new file mode 100644 index 0000000..83a6d9e --- /dev/null +++ b/lib/alerts.lua @@ -0,0 +1,92 @@ +-- functions and helpers for alerts, warnings, and errors + +---Draws a red error icon at the entity's position. +---@param entity LuaEntity +---@param sprite string +---@param time_to_live integer? default forever +---@param blink_interval integer? default 30 ticks +---@return LuaRenderObject +py.draw_error_sprite = function(entity, sprite, time_to_live, blink_interval) + return rendering.draw_sprite { + sprite = sprite, + x_scale = entity.prototype.alert_icon_scale or 0.5, + y_scale = entity.prototype.alert_icon_scale or 0.5, + target = entity, + surface = entity.surface, + time_to_live = time_to_live, + blink_interval = blink_interval or 30, + render_layer = "air-entity-info-icon" + } +end + +---Generates an error icon and alert at the entity's position, refreshing both until cancelled +---@param entity LuaEntity entity alert is tied to +---@param signal SignalID sprite of generated alert +---@param icon SpritePath sprite of rendered alert +---@param message LocalisedString message of alert +---@param show_on_map boolean whether to show alert on map +---@return uint alert_id unique identifier of this alert +py.generate_alert = function(entity, signal, icon, message, show_on_map) + if not entity or not entity.valid or not signal then return end + storage.alert_count = storage.alert_count + 1 + storage.alerts[storage.alert_count] = { + entity = entity, + surface = entity.surface, + force = entity.force, + signal = signal, + message = message, + show_on_map = show_on_map, + offset = game.tick % 600, + rendering = py.draw_error_sprite(entity, icon) + } + return storage.alert_count +end + +---Clears an alert from a force. Works if referenced entity is invalid +---@param alert_id uint +py.clear_alert = function(alert_id) + if not alert_id then return end + local alert_data = storage.alerts[alert_id] + if not alert_data then return end + if alert_data.entity and alert_data.entity.valid then + alert_data.entity.force.remove_alert{ + entity = alert_data.entity, + surface = alert_data.surface + } + alert_data.rendering.destroy() + else + for _, alert in pairs(alert_data.force.players[1].get_alerts{ + type = defines.alert_type.custom, + surface = alert_data.surface, + icon = alert_data.signal, + message = alert_data.message + }) do + alert_data.force.remove_alert(alert) + end + end + storage.alerts[alert_id] = nil +end + +py.on_event(defines.events.on_tick, function() + -- check stored alerts, update if required + local offset = game.tick % 600 + for i, alert_data in pairs(storage.alerts or {}) do + if alert_data.offset == offset then + if not alert_data.entity.valid then + storage.alerts[i] = nil + else + alert_data.entity.force.add_custom_alert( + alert_data.entity, + alert_data.signal, + alert_data.message, + alert_data.show_on_map + ) + end + end + end +end) + +py.on_event(py.events.on_init(), function() + storage.alerts = storage.alerts or {} + storage.alert_count = storage.alert_count or 0 +end) \ No newline at end of file diff --git a/lib/autorecipes.lua b/lib/autorecipes.lua index 50950fb..b23431e 100644 --- a/lib/autorecipes.lua +++ b/lib/autorecipes.lua @@ -2,7 +2,7 @@ --[[ py.autorecipes { -- is a function call can be many per file is the same as RECIPE{} that is used in the rest of pymods name = 'single-example', -- recipe name if in single recipe mode *@* - category = 'recipe-category', -- used in input recipe and output if outcategory not provided to set category + categories = {'recipe-category'}, -- used in input recipe and output if outcategory not provided to set category singlerecipe = false, --=true: its a single recipe done 1 machine. takes ingredients and outputs the results. --=false: creates 2 recipes. 1 with the ingredients as inputs and outputs an item. 2nd recipe takes the item in and outputs the results. module_limitations = "ulric", --adds the recipes to a modules allowed recipes table * subgroup = 'subgroup', -- sets the recipes subgroups for menu organizion @@ -18,7 +18,7 @@ py.autorecipes { -- is a function call can be many per file is the same as RECIP }, results = -- double duh, same as ingredients first time cant be empty or you get nothing { - {name='bones', amount = 'amount'*('*!*'),probability = 'probability'**, amount_min = 'amount_min'**'***', amount_max = 'amount_max'**'***'}, + {name='bones', amount = 'amount'*('*!*'),independent_probability = 'independent_probability'**, amount_min = 'amount_min'**'***', amount_max = 'amount_max'**'***'}, {'result'}, -- see above for details {'result'}, -- again not limited by this code to number of results }, @@ -86,12 +86,11 @@ local function modify_recipe_tables(item, items_table, previous_item_names, resu elseif type(item.fallback) == "table" and item.fallback.name then name = item.fallback.name item.name = name - if item.fallback.amount then - item.amount = item.fallback.amount - end + item.amount = item.fallback.amount elseif data.raw.fluid[barrel] then name = item.name end + item.fallback = nil -- remove unnecessary data if previous_item_names[name] ~= true then local item_type @@ -165,6 +164,8 @@ local function modify_recipe_tables(item, items_table, previous_item_names, resu for _, pre in pairs(items_table) do if pre.name == name then pre.amount = item.amount + pre.amount_min = nil + pre.amount_max = nil end end end @@ -183,6 +184,7 @@ local function modify_recipe_tables(item, items_table, previous_item_names, resu end return_item = {type = item_type, name = name, amount = amount} table.insert(result_table, return_item) + item.return_item = nil end if item.return_barrel then @@ -208,6 +210,7 @@ local function modify_recipe_tables(item, items_table, previous_item_names, resu end table.insert(result_table, barrels_to_return) ::already_had_barrel_result:: + item.return_barrel = nil end end @@ -243,7 +246,7 @@ local function recipe_item_builder(ingredients, results, previous_ingredients, p end ---Provides an interface to quickly build tiered recipes. See recipes-auto-brains.lua for an example ----@param params {name:RecipeID,category:RecipeCategoryID,subgroup:data.ItemSubGroupID,order:data.Order,main_product?:string,crafting_speed:double,allowed_module_categories:[data.ModuleCategoryID],number_icons:boolean,mats:[{name?:string,ingredients?:[data.IngredientPrototype],results?:[data.ProductPrototype],crafting_speed?:double,tech?:TechnologyID,icon?:data.FileName,icon_size?:integer,icons?:[data.IconData],main_product?:string}]} +---@param params {name:RecipeID,categories:RecipeCategoryID[],subgroup:data.ItemSubGroupID,order:data.Order,main_product?:string,crafting_speed:double,allowed_module_categories:[data.ModuleCategoryID],number_icons:boolean,mats:[{name?:string,ingredients?:[data.IngredientPrototype],results?:[data.ProductPrototype],crafting_speed?:double,tech?:TechnologyID,icon?:data.FileName,icon_size?:integer,icons?:[data.IconData],main_product?:string}]} py.autorecipes = function(params) local previous_ingredients = {} local previous_results = {} @@ -264,7 +267,7 @@ py.autorecipes = function(params) local recipe = RECIPE { type = "recipe", name = recipe_name, - category = params.category, + categories = params.categories, enabled = tier.tech == nil, energy_required = tier.crafting_speed or params.crafting_speed, ingredients = fixed_ingredients, @@ -274,7 +277,7 @@ py.autorecipes = function(params) allowed_module_categories = params.allowed_module_categories, icons = tier.icons, main_product = tier.main_product or params.main_product, - allow_productivity = params.category ~= "slaughterhouse", + allow_productivity = not table.any(params.categories, "slaughterhouse"), } if tier.tech then recipe:add_unlock(tier.tech) end if params.number_icons then -- add numbers to farming recipes so that they're not identical @@ -300,7 +303,7 @@ py.autorecipes = function(params) local scale = (data.raw.recipe[recipe_name].icons[1].scale or .5) / 2 table.insert( data.raw.recipe[recipe_name].icons, - {icon = "__pyalienlifegraphics__/graphics/icons/" .. i .. ".png", scale = scale, shift = {32 * scale, 32 * scale}, floating = true} + {icon = "__pyalienlifegraphics__/graphics/icons/" .. i .. ".png", scale = scale, shift = {36 * scale, 36 * scale}, floating = true} ) elseif tier.icon then data.raw.recipe[recipe_name].icon = tier.icon diff --git a/lib/control-stage.lua b/lib/control-stage.lua index dfbb664..aa01207 100644 --- a/lib/control-stage.lua +++ b/lib/control-stage.lua @@ -7,25 +7,7 @@ require "vector" require "smuggler" require "compound-entities" require "inventory" - ----Draws a red error icon at the entity's position. ----@param entity LuaEntity ----@param sprite string ----@param time_to_live integer? default forever ----@param blink_interval integer? default 30 ticks ----@return LuaRenderObject -py.draw_error_sprite = function(entity, sprite, time_to_live, blink_interval) - return rendering.draw_sprite { - sprite = sprite, - x_scale = entity.prototype.alert_icon_scale or 0.5, - y_scale = entity.prototype.alert_icon_scale or 0.5, - target = entity, - surface = entity.surface, - time_to_live = time_to_live, - blink_interval = blink_interval or 30, - render_layer = "air-entity-info-icon" - } -end +require "alerts" ---Creates a localised string tooltip for allowed modules. ---@param allowed_modules table diff --git a/lib/data-stage.lua b/lib/data-stage.lua index 25f345f..4cd1ea2 100644 --- a/lib/data-stage.lua +++ b/lib/data-stage.lua @@ -151,7 +151,7 @@ end function py.farm_speed(num_slots, desired_speed, module_bonus) module_bonus = module_bonus or 1 -- mk1 modules are 100% bonus speed * module_bonus. The farm itself then counts as much as one module - return desired_speed / (num_slots + 1 / module_bonus) / module_bonus + return desired_speed / (num_slots / module_bonus) / module_bonus end ---Returns the correct farm speed for a mk2+ farm based on the number of modules and the mk1 speed. @@ -165,10 +165,9 @@ function py.farm_speed_derived(num_slots, base_entity_name, base_module_bonus, t base_module_bonus = base_module_bonus or 1 local e = data.raw["assembling-machine"][base_entity_name] local mk1_slots = e.module_slots - local desired_mk1_speed = e.crafting_speed * (mk1_slots * base_module_bonus + 1) - local speed_improvement_ratio = num_slots / mk1_slots - this_bonus = this_bonus or speed_improvement_ratio * base_module_bonus - return (desired_mk1_speed * speed_improvement_ratio) / (num_slots + 1 / this_bonus) / base_module_bonus + local desired_mk1_speed = e.crafting_speed * mk1_slots * base_module_bonus + this_bonus = this_bonus or base_module_bonus * num_slots / mk1_slots + return desired_mk1_speed * this_bonus / (num_slots * base_module_bonus) end ---Returns a composite icon with a base icon and up to 4 child icons. diff --git a/lib/lib.lua b/lib/lib.lua index 59cf8c3..83bdbf6 100644 --- a/lib/lib.lua +++ b/lib/lib.lua @@ -212,6 +212,13 @@ local pyae_globals = { "Solar", "Wind", "Aerial", + "Tidal" +} + +local debugadapter_globals = { + -- data stage + "setfenv", + -- control stage } function py.has_any_py_mods() @@ -343,7 +350,8 @@ local global_vars = table.array_combine( pypp_globals, spidertron_enhancements_globals, pycp_globals, - pyae_globals + pyae_globals, + debugadapter_globals ) for _, var in pairs(global_vars) do diff --git a/lib/metas/entity.lua b/lib/metas/entity.lua index e3408c6..14f056f 100644 --- a/lib/metas/entity.lua +++ b/lib/metas/entity.lua @@ -1,14 +1,15 @@ +---@diagnostic disable-next-line: unresolved-require local collision_mask_util = require "__core__/lualib/collision-mask-util" local entity_types = defines.prototypes.entity ----@class data.EntityPrototype ----@field public standardize fun(self: data.EntityPrototype): data.EntityPrototype ----@field public add_flag fun(self: data.EntityPrototype, flag: string): data.EntityPrototype, boolean ----@field public remove_flag fun(self: data.EntityPrototype, flag: string): data.EntityPrototype, boolean ----@field public has_flag fun(self: data.EntityPrototype, flag: string): boolean +---@class pYdata.EntityPrototype:pYdata.AnyPrototype,data.EntityPrototype +---@operator call(string|pYdata.EntityPrototype|data.EntityPrototype): pYdata.EntityPrototype +---@field public standardize fun(self: pYdata.EntityPrototype): pYdata.EntityPrototype +---@field public add_flag fun(self: pYdata.EntityPrototype, flag: string): pYdata.EntityPrototype, boolean +---@field public remove_flag fun(self: pYdata.EntityPrototype, flag: string): pYdata.EntityPrototype, boolean +---@field public has_flag fun(self: pYdata.EntityPrototype, flag: string): boolean ENTITY = setmetatable({}, { - ---@param entity data.EntityPrototype __call = function(self, entity) local etype = type(entity) if etype == "string" then @@ -37,6 +38,8 @@ ENTITY = setmetatable({}, { end }) +---@diagnostic disable-next-line: missing-fields +---@type pYdata.EntityPrototype local metas = {} metas.standardize = function(self) @@ -58,10 +61,6 @@ metas.standardize = function(self) return self end ----@param self data.EntityPrototype ----@param flag string ----@return data.EntityPrototype self ----@return boolean success metas.add_flag = function(self, flag) self.flags = self.flags or {} for _, f in pairs(self.flags) do @@ -73,10 +72,6 @@ metas.add_flag = function(self, flag) return self, true -- flag added end ----@param self data.EntityPrototype ----@param flag string ----@return data.EntityPrototype self ----@return boolean success metas.remove_flag = function(self, flag) if not self.flags then return self, false end for i, f in pairs(self.flags) do @@ -88,9 +83,6 @@ metas.remove_flag = function(self, flag) return self, false -- could not find flag end ----@param self data.EntityPrototype ----@param flag string ----@return boolean has_flag metas.has_flag = function(self, flag) if not self.flags then return false end for _, f in pairs(self.flags) do diff --git a/lib/metas/fluid.lua b/lib/metas/fluid.lua index 84452d3..77f20ef 100644 --- a/lib/metas/fluid.lua +++ b/lib/metas/fluid.lua @@ -1,6 +1,6 @@ ----@class data.FluidPrototype +---@class pYdata.FluidPrototype:pYdata.AnyPrototype,data.FluidPrototype +---@operator call(string|pYdata.FluidPrototype|data.FluidPrototype): pYdata.FluidPrototype FLUID = setmetatable(data.raw.fluid, { - ---@param fluid data.FluidPrototype __call = function(self, fluid) local ftype = type(fluid) if ftype == "string" then @@ -16,6 +16,8 @@ FLUID = setmetatable(data.raw.fluid, { end }) +---@diagnostic disable-next-line: missing-fields +---@type pYdata.FluidPrototype local metas = {} return metas diff --git a/lib/metas/item.lua b/lib/metas/item.lua index 70860b7..8aa70d3 100644 --- a/lib/metas/item.lua +++ b/lib/metas/item.lua @@ -1,10 +1,11 @@ local item_prototypes = defines.prototypes.item ----@class data.ItemPrototype +---@class pYdata.ItemPrototype:pYdata.AnyPrototype,data.ItemPrototype +---@operator call(string|pYdata.ItemPrototype|data.ItemPrototype): pYdata.ItemPrototype ---@field public add_flag fun(self: data.ItemPrototype, flag: string): data.ItemPrototype, boolean ---@field public remove_flag fun(self: data.ItemPrototype, flag: string): data.ItemPrototype, boolean ---@field public has_flag fun(self: data.ItemPrototype, flag: string): boolean ----@field public spoil fun(self: data.ItemPrototype, spoil_result: (string | table), spoil_ticks: number): data.ItemPrototype, boolean +---@field public spoil fun(self: data.ItemPrototype, spoil_result: (string | table), spoil_ticks: uint): data.ItemPrototype, boolean ITEM = setmetatable({}, { ---@param item data.ItemPrototype __call = function(self, item) @@ -35,12 +36,10 @@ ITEM = setmetatable({}, { end }) +---@diagnostic disable-next-line: missing-fields +---@type pYdata.ItemPrototype local metas = {} ----@param self data.ItemPrototype ----@param flag string ----@return data.ItemPrototype self ----@return boolean success metas.add_flag = function(self, flag) self.flags = self.flags or {} for _, f in pairs(self.flags) do @@ -52,10 +51,6 @@ metas.add_flag = function(self, flag) return self, true -- flag added end ----@param self data.ItemPrototype ----@param flag string ----@return data.ItemPrototype self ----@return boolean success metas.remove_flag = function(self, flag) if not self.flags then return self, false end for i, f in pairs(self.flags) do @@ -67,9 +62,6 @@ metas.remove_flag = function(self, flag) return self, false -- could not find flag end ----@param self data.ItemPrototype ----@param flag string ----@return boolean has_flag metas.has_flag = function(self, flag) if not self.flags then return false end for _, f in pairs(self.flags) do @@ -102,11 +94,6 @@ py.spoil_triggers = { end } ----@param self data.ItemPrototype ----@param spoil_result string|data.SpoilToTriggerResult ----@param spoil_ticks int ----@return table self ----@return boolean success metas.spoil = function(self, spoil_result, spoil_ticks) if not feature_flags.spoiling then return self, false end -- spoilage is off if not spoil_ticks then error("No spoil ticks provided for item " .. self.name) end diff --git a/lib/metas/metas.lua b/lib/metas/metas.lua index 528262f..f0ff72f 100644 --- a/lib/metas/metas.lua +++ b/lib/metas/metas.lua @@ -9,14 +9,14 @@ local lib = { tile = require "tile" } ----@class data.AnyPrototype ----@field public copy fun(self: data.AnyPrototype, new_name: (string | fun(self: data.AnyPrototype): string)?): data.AnyPrototype ----@field public subgroup_order fun(self: data.AnyPrototype, subgroup: string, order: string): data.AnyPrototype ----@field public set_fields fun(self: data.AnyPrototype, fields: table): data.AnyPrototype ----@field public set fun(self: data.AnyPrototype, field: string, value: any): data.AnyPrototype ----@field public delete fun(self: data.AnyPrototype) ----@field public hide fun(self: data.AnyPrototype): data.AnyPrototype ----@field public unhide fun(self: data.AnyPrototype): data.AnyPrototype +---@class pYdata.AnyPrototype:data.AnyPrototype +---@field public copy fun(self: pYdata.AnyPrototype, new_name: (string | fun(self: pYdata.AnyPrototype): string)?): pYdata.AnyPrototype +---@field public subgroup_order fun(self: pYdata.AnyPrototype, subgroup: string, order: string): pYdata.AnyPrototype +---@field public set_fields fun(self: pYdata.AnyPrototype, fields: table): pYdata.AnyPrototype +---@field public set fun(self: pYdata.AnyPrototype, field: string, value: any): pYdata.AnyPrototype +---@field public delete fun(self: pYdata.AnyPrototype) +---@field public hide fun(self: pYdata.AnyPrototype): pYdata.AnyPrototype +---@field public unhide fun(self: pYdata.AnyPrototype): pYdata.AnyPrototype for _, meta in pairs(lib) do meta.copy = function(self, new_name) diff --git a/lib/metas/recipe.lua b/lib/metas/recipe.lua index d1c5236..7cf78b3 100644 --- a/lib/metas/recipe.lua +++ b/lib/metas/recipe.lua @@ -2,33 +2,41 @@ local table_insert = table.insert --unsafe functions overrides protections for ingredient/result existence +---@diagnostic disable-next-line: missing-fields +---@type pYdata.RecipePrototype local metas = {} ----@class data.RecipePrototype ----@field public standardize fun(self: data.RecipePrototype): data.RecipePrototype ----@field public add_unlock fun(self: data.RecipePrototype, technology_name: string | string[]): data.RecipePrototype, boolean ----@field public remove_unlock fun(self: data.RecipePrototype, technology_name: string | string[]): data.RecipePrototype, boolean ----@field public replace_unlock fun(self: data.RecipePrototype, technology_old: string | string[], technology_new: string | string[]): data.RecipePrototype, boolean ----@field public replace_ingredient fun(self: data.RecipePrototype, old_ingredient: string, new_ingredient: string | data.IngredientPrototype, new_amount: integer?): data.RecipePrototype, boolean ----@field public replace_ingredient_unsafe fun(self: data.RecipePrototype, old_ingredient: string, new_ingredient: string | data.IngredientPrototype, new_amount: integer?): data.RecipePrototype, boolean ----@field public add_ingredient fun(self: data.RecipePrototype, ingredient: data.IngredientPrototype): data.RecipePrototype, boolean ----@field public add_ingredient_unsafe fun(self: data.RecipePrototype, ingredient: data.IngredientPrototype): data.RecipePrototype, boolean ----@field public remove_ingredient fun(self: data.RecipePrototype, ingredient_name: string): data.RecipePrototype, integer ----@field public replace_result fun(self: data.RecipePrototype, old_result: string, new_result: string | data.ProductPrototype, new_amount: integer?): data.RecipePrototype, boolean ----@field public replace_result_unsafe fun(self: data.RecipePrototype, old_result: string, new_result: string | data.ProductPrototype, new_amount: integer?): data.RecipePrototype, boolean ----@field public add_result fun(self: data.RecipePrototype, result: data.ProductPrototype): data.RecipePrototype, boolean ----@field public remove_result fun(self: data.RecipePrototype, result_name: string): data.RecipePrototype, integer ----@field public clear_ingredients fun(self: data.RecipePrototype): data.RecipePrototype, boolean ----@field public multiply_result_amount fun(self: data.RecipePrototype, result_name: string, percent: number): data.RecipePrototype, boolean ----@field public multiply_ingredient_amount fun(self: data.RecipePrototype, ingredient_name: string, percent: number): data.RecipePrototype, boolean ----@field public add_result_amount fun(self: data.RecipePrototype, result_name: string, increase: number): data.RecipePrototype, boolean ----@field public add_ingredient_amount fun(self: data.RecipePrototype, ingredient_name: string, increase: number): data.RecipePrototype, boolean ----@field public set_result_amount fun(self: data.RecipePrototype, result_name: string, amount: number): data.RecipePrototype, boolean ----@field public set_ingredient_amount fun(self: data.RecipePrototype, ingredient_name: string, amount: number): data.RecipePrototype, boolean ----@field public get_main_product fun(self: data.RecipePrototype, allow_multi_product: boolean?): LuaItemPrototype?|LuaFluidPrototype? ----@field public get_icons fun(self: data.RecipePrototype): data.IconData[] +---@class pYdata.RecipePrototype:pYdata.AnyPrototype,data.RecipePrototype +---@operator call(string|pYdata.RecipePrototype|data.RecipePrototype): pYdata.RecipePrototype +---@field public standardize fun(self: pYdata.RecipePrototype): pYdata.RecipePrototype +---@field public add_unlock fun(self: pYdata.RecipePrototype, technology_name: string | string[]): pYdata.RecipePrototype, boolean +---@field public remove_unlock fun(self: pYdata.RecipePrototype, technology_name: string | string[]): pYdata.RecipePrototype, boolean +---@field public replace_unlock fun(self: pYdata.RecipePrototype, technology_old: string | string[], technology_new: string | string[]): pYdata.RecipePrototype, boolean +---@field public replace_ingredient fun(self: pYdata.RecipePrototype, old_ingredient: string, new_ingredient: string | data.IngredientPrototype, new_amount: integer?): pYdata.RecipePrototype, boolean +---@field public replace_ingredient_unsafe fun(self: pYdata.RecipePrototype, old_ingredient: string, new_ingredient: string | data.IngredientPrototype, new_amount: integer?): pYdata.RecipePrototype, boolean +---@field public add_ingredient fun(self: pYdata.RecipePrototype, ingredient: data.IngredientPrototype): pYdata.RecipePrototype, boolean +---@field public add_ingredient_unsafe fun(self: pYdata.RecipePrototype, ingredient: data.IngredientPrototype): pYdata.RecipePrototype, boolean +---@field public remove_ingredient fun(self: pYdata.RecipePrototype, ingredient_name: string): pYdata.RecipePrototype, integer +---@field public replace_result fun(self: pYdata.RecipePrototype, old_result: string, new_result: string | data.ProductPrototype, new_amount: integer?): pYdata.RecipePrototype, boolean +---@field public replace_result_unsafe fun(self: pYdata.RecipePrototype, old_result: string, new_result: string | data.ProductPrototype, new_amount: integer?): pYdata.RecipePrototype, boolean +---@field public add_result fun(self: pYdata.RecipePrototype, result: data.ProductPrototype): pYdata.RecipePrototype, boolean +---@field public remove_result fun(self: pYdata.RecipePrototype, result_name: string): pYdata.RecipePrototype, integer +---@field public clear_ingredients fun(self: pYdata.RecipePrototype): pYdata.RecipePrototype, boolean +---@field public clear_results fun(self: pYdata.RecipePrototype): pYdata.RecipePrototype, boolean +---@field public multiply_result_amount fun(self: pYdata.RecipePrototype, result_name: string, percent: number): pYdata.RecipePrototype, boolean +---@field public multiply_ingredient_amount fun(self: pYdata.RecipePrototype, ingredient_name: string, percent: number): pYdata.RecipePrototype, boolean +---@field public add_result_amount fun(self: pYdata.RecipePrototype, result_name: string, increase: number): pYdata.RecipePrototype, boolean +---@field public add_ingredient_amount fun(self: pYdata.RecipePrototype, ingredient_name: string, increase: number): pYdata.RecipePrototype, boolean +---@field public set_result_amount fun(self: pYdata.RecipePrototype, result_name: string, amount: number): pYdata.RecipePrototype, boolean +---@field public set_ingredient_amount fun(self: pYdata.RecipePrototype, ingredient_name: string, amount: number): pYdata.RecipePrototype, boolean +---@field public add_category fun(self: pYdata.RecipePrototype, category_name: data.RecipeCategoryID): pYdata.RecipePrototype, boolean +---@field public remove_category fun(self: pYdata.RecipePrototype, category_name: data.RecipeCategoryID): pYdata.RecipePrototype, boolean +---@field public replace_category fun(self: pYdata.RecipePrototype, old: data.RecipeCategoryID, new: data.RecipeCategoryID): pYdata.RecipePrototype, boolean +---@field public has_category fun(self: pYdata.RecipePrototype, category_name: data.RecipeCategoryID): boolean +---@field public has_categories fun(self: pYdata.RecipePrototype, category_name: data.RecipeCategoryID[], all?: boolean): boolean # Returns true if the recipe has any of the categories. all? categories must match to pass +---@field public get_main_product fun(self: pYdata.RecipePrototype, allow_multi_product: boolean?): LuaItemPrototype?|LuaFluidPrototype? +---@field public get_icons fun(self: pYdata.RecipePrototype): data.IconData[] RECIPE = setmetatable(data.raw.recipe, { - ---@param recipe data.RecipePrototype __call = function(self, recipe) local rtype = type(recipe) if rtype == "string" then @@ -80,10 +88,6 @@ py.allow_productivity = function(recipe_names) end end ----@param self data.RecipePrototype ----@param technology_name string|string[] ----@return data.RecipePrototype self ----@return boolean success metas.add_unlock = function(self, technology_name) if type(technology_name) == "table" then local success = true @@ -116,10 +120,6 @@ metas.add_unlock = function(self, technology_name) return self, true end ----@param self data.RecipePrototype ----@param technology_name string|string[] ----@return data.RecipePrototype self ----@return boolean success metas.remove_unlock = function(self, technology_name) if type(technology_name) == "table" then local success = true @@ -148,11 +148,6 @@ metas.remove_unlock = function(self, technology_name) return self, false -- recipe not part of tech end ----@param self data.RecipePrototype ----@param technology_old string|string[] ----@param technology_new string|string[] ----@return data.RecipePrototype self ----@return boolean success metas.replace_unlock = function(self, technology_old, technology_new) local _, success_remove = self:remove_unlock(technology_old) local _, success_add = self:add_unlock(technology_new) @@ -198,65 +193,37 @@ do return true -- must have been a success! cant early return on success because of possible repeated results end - ---@param self data.RecipePrototype - ---@param old_ingredient string - ---@param new_ingredient string|data.IngredientPrototype - ---@param new_amount? int - ---@return data.RecipePrototype self - ---@return boolean success metas.replace_ingredient = function(self, old_ingredient, new_ingredient, new_amount) self:standardize() local success = replacement_helper(self, self.ingredients, old_ingredient, new_ingredient, new_amount, false) return self, success end - ---@param self data.RecipePrototype - ---@param old_ingredient string - ---@param new_ingredient string|data.IngredientPrototype - ---@param new_amount? int - ---@return data.RecipePrototype self - ---@return boolean success metas.replace_ingredient_unsafe = function(self, old_ingredient, new_ingredient, new_amount) self:standardize() local success = replacement_helper(self, self.ingredients, old_ingredient, new_ingredient, new_amount, true) return self, success end - ---@param self data.RecipePrototype - ---@param old_result string - ---@param new_result string|data.ProductPrototype - ---@param new_amount? int - ---@return data.RecipePrototype self - ---@return boolean success metas.replace_result = function(self, old_result, new_result, new_amount) self:standardize() local success = replacement_helper(self, self.results, old_result, new_result, new_amount, false) if self.main_product == old_result then - self.main_product = type(new_result) == "string" and new_result or new_result[1] or new_result.name + self.main_product = type(new_result) == "string" and new_result or new_result.name end return self, success end - ---@param self data.RecipePrototype - ---@param old_result string - ---@param new_result string|data.ProductPrototype - ---@param new_amount? int - ---@return data.RecipePrototype self - ---@return boolean success metas.replace_result_unsafe = function(self, old_result, new_result, new_amount) self:standardize() local success = replacement_helper(self, self.results, old_result, new_result, new_amount, true) if self.main_product == old_result then - self.main_product = type(new_result) == "string" and new_result or new_result[1] or new_result.name + self.main_product = type(new_result) == "string" and new_result or new_result.name end return self, success end end ----@param self data.RecipePrototype ----@param ingredient data.IngredientPrototype ----@return data.RecipePrototype self ----@return boolean success metas.add_ingredient_unsafe = function(self, ingredient) self:standardize() -- Ensure that this ingredient does not already exist in this recipe. @@ -271,18 +238,14 @@ metas.add_ingredient_unsafe = function(self, ingredient) end end - if (not self.category or self.category == "crafting") and ingredient.type == "fluid" then - self.category = "crafting-with-fluid" + if ingredient.type == "fluid" and self:has_category("crafting") then + self:replace_category("crafting", "crafting-with-fluid") end table_insert(self.ingredients, ingredient) return self, true end ----@param self data.RecipePrototype ----@param ingredient data.IngredientPrototype ----@return data.RecipePrototype self ----@return boolean success metas.add_ingredient = function(self, ingredient) self:standardize() if not FLUID[ingredient.name] and not ITEM[ingredient.name] then @@ -293,20 +256,12 @@ metas.add_ingredient = function(self, ingredient) return metas.add_ingredient_unsafe(self, ingredient) end ----@param self data.RecipePrototype ----@param result data.ProductPrototype ----@return data.RecipePrototype self ----@return boolean success metas.add_result = function(self, result) self:standardize() table_insert(self.results, result) return self, true end ----@param self data.RecipePrototype ----@param ingredient_name string ----@return data.RecipePrototype self ----@return int amount_removed metas.remove_ingredient = function(self, ingredient_name) self:standardize() local amount_removed = 0 @@ -320,16 +275,12 @@ metas.remove_ingredient = function(self, ingredient_name) return self, amount_removed end ----@param self data.RecipePrototype ----@param result_name string ----@return data.RecipePrototype self ----@return int amount_removed metas.remove_result = function(self, result_name) self:standardize() local amount_removed = 0 self.results = table.filter(self.results, function(result) if result.name == result_name then - local amount = result.amount * (result.probability or 1) or (result.amount_min + result.amount_max) * (result.probability or 1) / 2 + local amount = result.amount * (result.independent_probability or 1) or (result.amount_min + result.amount_max) * (result.independent_probability or 1) / 2 amount_removed = amount_removed + amount return false end @@ -344,15 +295,10 @@ metas.clear_ingredients = function(self) end metas.clear_results = function(self) - self.ingredients = {} + self.results = {} return self, true -- impossible to fail end ----@param self data.RecipePrototype ----@param result_name string ----@param percent float ----@return data.RecipePrototype self ----@return boolean success metas.multiply_result_amount = function(self, result_name, percent) self:standardize() @@ -374,11 +320,6 @@ metas.multiply_result_amount = function(self, result_name, percent) return self, false -- could not find result end ----@param self data.RecipePrototype ----@param ingredient_name string ----@param percent float ----@return data.RecipePrototype self ----@return boolean success metas.multiply_ingredient_amount = function(self, ingredient_name, percent) self:standardize() @@ -397,11 +338,6 @@ metas.multiply_ingredient_amount = function(self, ingredient_name, percent) return self, false -- could not find ingredient end ----@param self data.RecipePrototype ----@param result_name string ----@param increase int ----@return data.RecipePrototype self ----@return boolean success metas.add_result_amount = function(self, result_name, increase) self:standardize() @@ -420,11 +356,6 @@ metas.add_result_amount = function(self, result_name, increase) return self, false -- could not find result end ----@param self data.RecipePrototype ----@param ingredient_name string ----@param increase int ----@return data.RecipePrototype self ----@return boolean success metas.add_ingredient_amount = function(self, ingredient_name, increase) self:standardize() @@ -443,46 +374,107 @@ metas.add_ingredient_amount = function(self, ingredient_name, increase) return self, false -- could not find ingredient end ----@param self data.RecipePrototype ----@param result_name string ----@param amount int ----@return data.RecipePrototype self ----@return boolean success metas.set_result_amount = function(self, result_name, amount) return self:replace_result(result_name, result_name, amount) end ----@param self data.RecipePrototype ----@param ingredient_name string ----@param amount int ----@return data.RecipePrototype self ----@return boolean success metas.set_ingredient_amount = function(self, ingredient_name, amount) return self:replace_ingredient(ingredient_name, ingredient_name, amount) end ----@param self data.RecipePrototype ----@param category_name string ----@return data.RecipePrototype self ----@return boolean success -metas.change_category = function(self, category_name) +metas.add_category = function(self, category_name) self:standardize() + self.categories = self.categories or {} -- not in self:standardize() because a recipe with no categories errors the game - if data.raw["recipe-category"][category_name] then - self.category = category_name + if not data.raw["recipe-category"][category_name] then + log("WARNING @ \'" .. self.name .. "\':add_category(): Category " .. category_name .. " not found") + return self, false -- category does not exist + else + for _, category in pairs(self.categories) do + if category == category_name then + return self, false -- category already set + end + end + self.categories[#self.categories+1] = category_name return self, true -- successful set + end +end + +metas.remove_category = function(self, category_name) + self:standardize() + + if not data.raw["recipe-category"][category_name] then + log("WARNING @ \'" .. self.name .. "\':remove_category(): Category " .. category_name .. " not found") + return self, false -- category does not exist else - log("WARNING @ \'" .. self.name .. "\':change_category(): Category " .. category_name .. " not found") + if category_name == "crafting" and (not self.categories or #self.categories == 0) then + return self, true -- fake positive if trying to remove 'category' with no categories because its the default + end + for i, category in pairs(self.categories or {}) do + if category == category_name then + table.remove(self.categories, i) + if #self.categories == 0 then self.categories = nil end -- remove categories if it is an empty table + return self, true -- successfully removed + end + end + return self, false -- category not found + end +end + +metas.replace_category = function(self, old, new) + self:standardize() + + if not data.raw["recipe-category"][old] then + log("WARNING @ \'" .. self.name .. "\':replace_category(): Category " .. old .. " not found") + return self, false -- category does not exist + elseif not data.raw["recipe-category"][new] then + log("WARNING @ \'" .. self.name .. "\':replace_category(): Category " .. new .. " not found") return self, false -- category does not exist + else + local _, success = self:remove_category(old) + if success then + return self:add_category(new) -- conditional on success of add_category + else + return self, false -- DNE, do not add + end end end +metas.has_category = function(self, category_name) + self:standardize() + + if not data.raw["recipe-category"][category_name] then + log("WARNING @ \'" .. self.name .. "\':replace_category(): Category " .. category_name .. " not found") + return false -- category does not exist + else + if category_name == "crafting" and (not self.categories or #self.categories == 0) then + return self, true -- fake positive if trying to remove 'category' with no categories because its the default + end + for _, category in pairs(self.categories or {}) do + if category == category_name then + return true -- category found + end + end + return false -- category not found + end +end + +metas.has_categories = function(self, categories, all) + self:standardize() + + for _, category in pairs(categories) do + if all and not self:has_category(category) then + return false -- all categories must be contained but this one was not + elseif not all and self:has_category(category) then + return true -- any categories must match and this one matched + end + end + return not not all -- all categories matched, or none did +end + --- Get the prototype for the main_product using the same logic the game uses. --- Set allow_multi_product to take the *first* result (not game behavior) instead of failing when a recipe has no main_product set but has multiple results. ---
Check https://lua-api.factorio.com/latest/prototypes/RecipePrototype.html#main_product for more details ----@param self data.RecipePrototype ----@param allow_multi_product boolean ----@return data.ItemPrototype|data.FluidPrototype? metas.get_main_product = function(self, allow_multi_product) self:standardize() local target, target_type = self.main_product, "item" @@ -504,16 +496,11 @@ metas.get_main_product = function(self, allow_multi_product) else -- or only result _, result = next(self.results) end - --[[@cast result data.ItemProductPrototype|data.ResearchProgressProductPrototype]] + --[[@cast result data.ItemProductPrototype]] -- Special modding funtimes case: invalid spec - if not (result.type == "research-progress" and result.research_item or result.name) then return end - if result.type ~= nil and (result.type ~= "item" and result.type ~= "fluid" and result.type ~= "research-progress") then return end + if not result.name then return end + if result.type ~= nil and result.type ~= "item" and result.type ~= "fluid" then return end target, target_type = result.name, result.type or target_type - -- Special case: type of research-progress uses an item prototype - if target_type == "research-progress" then - target = result.research_item - target_type = "item" - end -- Find our prototype :) for _, category in py.iter_prototype_categories(target_type) do local proto = category[target] @@ -538,8 +525,6 @@ end --- Returns the icons table a recipe would use (i.e. using the item icon if the recipe prototype has no .icons/.icon set). --- May error on malformed prototypes. ---
Check https://lua-api.factorio.com/latest/prototypes/RecipePrototype.html#icon for more details. ----@param self data.RecipePrototype ----@return data.IconData[] metas.get_icons = function(self) local icon = icons(self) if icon then return icon end diff --git a/lib/metas/technology.lua b/lib/metas/technology.lua index 430cbae..080ed92 100644 --- a/lib/metas/technology.lua +++ b/lib/metas/technology.lua @@ -1,12 +1,12 @@ ----@class data.TechnologyPrototype ----@field public standardize fun(): data.TechnologyPrototype ----@field public add_prereq fun(self, prereq_technology_name: data.TechnologyID): data.TechnologyPrototype, boolean ----@field public remove_prereq fun(self, prereq_technology_name: data.TechnologyID): data.TechnologyPrototype, boolean ----@field public replace_prereq fun(self, old: data.TechnologyID, new: data.TechnologyID): data.TechnologyPrototype, boolean ----@field public remove_pack fun(self, science_pack_name: data.ItemID): data.TechnologyPrototype, boolean ----@field public add_pack fun(self, science_pack_name: data.ItemID): data.TechnologyPrototype, boolean +---@class pYdata.TechnologyPrototype:pYdata.AnyPrototype,data.TechnologyPrototype +---@operator call(string|pYdata.TechnologyPrototype|data.TechnologyPrototype): pYdata.TechnologyPrototype +---@field public standardize fun(self: pYdata.TechnologyPrototype): pYdata.TechnologyPrototype +---@field public add_prereq fun(self: pYdata.TechnologyPrototype, prereq_technology_name: data.TechnologyID): pYdata.TechnologyPrototype, boolean +---@field public remove_prereq fun(self: pYdata.TechnologyPrototype, prereq_technology_name: data.TechnologyID): pYdata.TechnologyPrototype, boolean +---@field public replace_prereq fun(self: pYdata.TechnologyPrototype, old: data.TechnologyID, new: data.TechnologyID): pYdata.TechnologyPrototype, boolean +---@field public remove_pack fun(self: pYdata.TechnologyPrototype, science_pack_name: data.ItemID): pYdata.TechnologyPrototype, boolean +---@field public add_pack fun(self: pYdata.TechnologyPrototype, science_pack_name: data.ItemID): pYdata.TechnologyPrototype, boolean TECHNOLOGY = setmetatable(data.raw.technology, { - ---@param technology data.TechnologyPrototype __call = function(self, technology) local ttype = type(technology) if ttype == "string" then @@ -22,9 +22,12 @@ TECHNOLOGY = setmetatable(data.raw.technology, { end }) +---@diagnostic disable-next-line: missing-fields +---@type pYdata.TechnologyPrototype local metas = {} metas.standardize = function(self) + ---@diagnostic disable-next-line: assign-type-mismatch if not self.unit and not self.research_trigger then self.unit = {ingredients = {}} end self.prerequisites = self.prerequisites or {} @@ -33,10 +36,6 @@ metas.standardize = function(self) return self end ----@param self data.TechnologyPrototype ----@param prereq_technology_name data.TechnologyID ----@return data.TechnologyPrototype self ----@return boolean success metas.add_prereq = function(self, prereq_technology_name) local prereq_technology = data.raw.technology[prereq_technology_name] if not prereq_technology then @@ -57,10 +56,6 @@ metas.add_prereq = function(self, prereq_technology_name) return self, true -- add prereq succeeds end ----@param self data.TechnologyPrototype ----@param prereq_technology_name data.TechnologyID ----@return data.TechnologyPrototype self ----@return boolean success metas.remove_prereq = function(self, prereq_technology_name) self.prerequisites = self.prerequisites or {} @@ -75,11 +70,6 @@ metas.remove_prereq = function(self, prereq_technology_name) end --- Replace old prerequesite with the new one. Fails if the old one was not found. ----@param self data.TechnologyPrototype ----@param old data.TechnologyID ----@param new data.TechnologyID ----@return data.TechnologyPrototype self ----@return boolean success metas.replace_prereq = function(self, old, new) local _, success = self:remove_prereq(old) if success then @@ -89,10 +79,6 @@ metas.replace_prereq = function(self, old, new) end end ----@param self data.TechnologyPrototype ----@param science_pack_name data.ItemID ----@return data.TechnologyPrototype self ----@return boolean success metas.remove_pack = function(self, science_pack_name) if not self.unit then return self, true -- should it be true? false? @@ -109,15 +95,12 @@ metas.remove_pack = function(self, science_pack_name) end -- possible to add the same pack twice, should probably check for that ----@param self data.TechnologyPrototype ----@param science_pack_name data.ItemID ----@return data.TechnologyPrototype self ----@return boolean success metas.add_pack = function(self, science_pack_name) if self.research_trigger then error("WARNING @ \'" .. self.name .. "\':add_pack(): Attempted to add science packs to technology with research_trigger.") end + ---@diagnostic disable-next-line: assign-type-mismatch self.unit = self.unit or {ingredients = {}} for _, ingredient in pairs(self.unit.ingredients) do diff --git a/lib/metas/tile.lua b/lib/metas/tile.lua index 6a9d935..6827437 100644 --- a/lib/metas/tile.lua +++ b/lib/metas/tile.lua @@ -1,6 +1,6 @@ ----@class data.TilePrototype +---@class pYdata.TilePrototype:pYdata.AnyPrototype,data.TilePrototype +---@operator call(string|pYdata.TilePrototype|data.TilePrototype): pYdata.TilePrototype TILE = setmetatable(data.raw.tile, { - ---@param tile data.TilePrototype __call = function(self, tile) local ftype = type(tile) if ftype == "string" then @@ -16,6 +16,8 @@ TILE = setmetatable(data.raw.tile, { end }) +---@diagnostic disable-next-line: missing-fields +---@type pYdata.TilePrototype local metas = {} return metas diff --git a/lib/pipe-connections.lua b/lib/pipe-connections.lua index 496cb1c..7ca5e60 100644 --- a/lib/pipe-connections.lua +++ b/lib/pipe-connections.lua @@ -12,8 +12,8 @@ py.pipe_pictures = function(pictures, shift_north, shift_south, shift_west, shif { filename = "__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-N.png", priority = "extra-high", - width = 71, - height = 38, + width = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-N").width, + height = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-N").height, shift = shift_north, scale = 0.5 } or py.empty_image(), @@ -21,8 +21,8 @@ py.pipe_pictures = function(pictures, shift_north, shift_south, shift_west, shif { filename = "__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-S.png", priority = "extra-high", - width = 88, - height = 61, + width = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-S").width, + height = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-S").height, shift = shift_south, scale = 0.5 } or py.empty_image(), @@ -30,8 +30,8 @@ py.pipe_pictures = function(pictures, shift_north, shift_south, shift_west, shif { filename = "__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-W.png", priority = "extra-high", - width = 39, - height = 73, + width = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-W").width, + height = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-W").height, shift = shift_west, scale = 0.5 } or py.empty_image(), @@ -39,8 +39,8 @@ py.pipe_pictures = function(pictures, shift_north, shift_south, shift_west, shif { filename = "__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-E.png", priority = "extra-high", - width = 42, - height = 76, + width = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-E").width, + height = require("__base__/graphics/entity/" .. pictures .. "/" .. pictures .. "-pipe-E").height, shift = shift_east, scale = 0.5 } or py.empty_image() @@ -50,6 +50,7 @@ py.pipe_pictures = function(pictures, shift_north, shift_south, shift_west, shif new_pictures[direction].filename = image.filename new_pictures[direction].width = image.width new_pictures[direction].height = image.height + new_pictures[direction].size = image.size new_pictures[direction].priority = image.priority or new_pictures[direction].priority new_pictures[direction].scale = 1 or new_pictures[direction].scale end diff --git a/prototypes/fancy-module-slots.lua b/prototypes/fancy-module-slots.lua index 6aab672..629faaf 100644 --- a/prototypes/fancy-module-slots.lua +++ b/prototypes/fancy-module-slots.lua @@ -4,10 +4,10 @@ if mods["omnimatter_compression"] then return end -- custom module alt-mode draw positioning for prototype_name, inventory in pairs { ["mining-drill"] = defines.inventory.mining_drill_modules, - ["assembling-machine"] = defines.inventory.assembling_machine_modules, - ["furnace"] = defines.inventory.furnace_modules, + ["assembling-machine"] = defines.inventory.crafter_modules, + ["furnace"] = defines.inventory.crafter_modules, ["lab"] = defines.inventory.lab_modules, - ["rocket-silo"] = defines.inventory.rocket_silo_modules, + ["rocket-silo"] = defines.inventory.crafter_modules, ["beacon"] = defines.inventory.beacon_modules, } do for _, machine in pairs(data.raw[prototype_name] or {}) do diff --git a/prototypes/functions/compatibility/reverse-factory.lua b/prototypes/functions/compatibility/reverse-factory.lua index 72d8161..3a04d00 100644 --- a/prototypes/functions/compatibility/reverse-factory.lua +++ b/prototypes/functions/compatibility/reverse-factory.lua @@ -1,11 +1,10 @@ if mods["reverse-factory"] then - local cat = table.invert {"recycle-products", "recycle-intermediates", "recycle-with-fluids", "recycle-productivity"} + local whitelist = {"recycle-products", "recycle-intermediates", "recycle-with-fluids", "recycle-productivity"} for item_name, item in py.iter_prototypes("item") do local recipe_name = "rf-" .. item_name - if data.raw.recipe[recipe_name] and cat[data.raw.recipe[recipe_name].category] then - data.raw.recipe[recipe_name].unlock_results = false + if data.raw.recipe[recipe_name] and RECIPE(recipe_name):has_categories(whitelist) then if ITEM(item).hidden then data.raw.recipe[recipe_name].hidden = true end @@ -15,8 +14,7 @@ if mods["reverse-factory"] then for fluid_name, fluid in pairs(data.raw.fluid) do local recipe_name = "rf-" .. fluid_name - if data.raw.recipe[recipe_name] and cat[data.raw.recipe[recipe_name].category] then - data.raw.recipe[recipe_name].unlock_results = false + if data.raw.recipe[recipe_name] and RECIPE(recipe_name):has_categories(whitelist) then if fluid.hidden then data.raw.recipe[recipe_name].hidden = true end diff --git a/prototypes/functions/compatibility/transport-drones.lua b/prototypes/functions/compatibility/transport-drones.lua index b5989de..2ac5ee5 100644 --- a/prototypes/functions/compatibility/transport-drones.lua +++ b/prototypes/functions/compatibility/transport-drones.lua @@ -7,7 +7,7 @@ if mods["Transport_Drones"] then "fluid-depot" } data.raw.technology["transport-system"].prerequisites = nil - data.raw.recipe["road"].category = "crafting-with-fluid" + data.raw.recipe["road"].categories = {"crafting-with-fluid"} data.raw.recipe["road"].ingredients = data.raw.recipe["concrete"].ingredients TECHNOLOGY("transport-drone-capacity-1"):add_prereq("logistic-science-pack") TECHNOLOGY("transport-drone-speed-1"):add_prereq("logistic-science-pack") diff --git a/prototypes/functions/compatibility/yirailway.lua b/prototypes/functions/compatibility/yirailway.lua index 812c6ba..20c2261 100644 --- a/prototypes/functions/compatibility/yirailway.lua +++ b/prototypes/functions/compatibility/yirailway.lua @@ -4,7 +4,7 @@ if mods["yi_railway"] and mods["pyindustry"] then if recipe.subgroup and string.sub(recipe.subgroup, 1, 4) == "yir_" then if recipe.subgroup == "yir_locomotives_steam" then recipe.enabled = true - recipe.category = data.raw.recipe["locomotive"].category + recipe.categories = data.raw.recipe["locomotive"].categories recipe.group = data.raw.recipe["locomotive"].group recipe.energy_required = data.raw.recipe["locomotive"].energy_required recipe.ingredients = data.raw.recipe["locomotive"].ingredients -- I know what this does and I don't care @@ -29,7 +29,7 @@ if mods["yi_railway"] and mods["pyindustry"] then end elseif recipe.subgroup == "yir_locomotives_diesel" or recipe.subgroup == "yir_locomotives_nslong" then recipe.enabled = true - recipe.category = data.raw.recipe["mk02-locomotive"].category + recipe.categories = data.raw.recipe["mk02-locomotive"].categories recipe.group = data.raw.recipe["mk02-locomotive"].group recipe.energy_required = data.raw.recipe["mk02-locomotive"].energy_required recipe.ingredients = data.raw.recipe["mk02-locomotive"].ingredients @@ -52,7 +52,7 @@ if mods["yi_railway"] and mods["pyindustry"] then elseif recipe.subgroup == "yir_cargowagons" or recipe.subgroup == "yir_cargowagons_4A" or recipe.subgroup == "yir_cargowagons_2A2" then recipe.enabled = true - recipe.category = data.raw.recipe["cargo-wagon"].category + recipe.categories = data.raw.recipe["cargo-wagon"].categories recipe.group = data.raw.recipe["cargo-wagon"].group recipe.energy_required = data.raw.recipe["cargo-wagon"].energy_required recipe.ingredients = data.raw.recipe["cargo-wagon"].ingredients @@ -71,7 +71,7 @@ if mods["yi_railway"] and mods["pyindustry"] then ywagon.inventory_size = wagon.inventory_size elseif recipe.subgroup == "yir_tankwagons2a" and recipe.subgroup == "yir_fluidwagons_4A" then recipe.enabled = true - recipe.category = data.raw.recipe["fluid-wagon"].category + recipe.categories = data.raw.recipe["fluid-wagon"].categories recipe.group = data.raw.recipe["fluid-wagon"].group recipe.energy_required = data.raw.recipe["fluid-wagon"].energy_required recipe.ingredients = data.raw.recipe["fluid-wagon"].ingredients @@ -89,7 +89,6 @@ if mods["yi_railway"] and mods["pyindustry"] then ywagon.air_resistance = wagon.air_resistance ywagon.capacity = wagon.capacity else - recipe.unlock_results = false recipe.hidden = true if (recipe.results) then for i, v in ipairs(recipe.results[1]) do @@ -99,7 +98,6 @@ if mods["yi_railway"] and mods["pyindustry"] then end end elseif string.sub(recipe_name, 1, 4) == "yir_" and string.find(recipe_name, "pyvoid") == nil then - recipe.unlock_results = false recipe.hidden = true if (recipe.results) then for i, v in ipairs(recipe.results[1]) do diff --git a/prototypes/yafc.lua b/prototypes/yafc.lua index 726a4a4..e429011 100644 --- a/prototypes/yafc.lua +++ b/prototypes/yafc.lua @@ -161,7 +161,7 @@ if mods["pyalienlife"] then {type = "item", name = y[1], amount = 4} }, main_product = "nexelit-ore", - category = "dino-dig-site" + categories = {"dino-dig-site"} } end end @@ -188,7 +188,7 @@ if mods["pyalienlife"] then {type = "item", name = creature_name, amount = 1} }, main_product = "guano", - category = "biofluid" + categories = {"biofluid"} } end end diff --git a/tests/control.lua b/tests/control.lua index 8a0459d..4b76083 100644 --- a/tests/control.lua +++ b/tests/control.lua @@ -1,9 +1,8 @@ local total_string_count local function test_localised_strings() - local excluded_categories = {} local localised_strings = {} for _, recipe in pairs(prototypes.recipe) do - if not excluded_categories[recipe.category] and not recipe.hidden then table.insert(localised_strings, recipe.localised_name) end + if not recipe.hidden then table.insert(localised_strings, recipe.localised_name) end end local excluded_types = {} for _, category in pairs {"item", "fluid", "entity"} do diff --git a/tests/data.lua b/tests/data.lua index 09c97d2..380e602 100644 --- a/tests/data.lua +++ b/tests/data.lua @@ -23,7 +23,7 @@ local function test_entity_graphics() ["electric-energy-interface"] = {{"picture", "pictures", "animation", "animations"}}, ["electric-pole"] = {"pictures"}, ["fish"] = {"pictures"}, - ["generator"] = {"horizontal_animation", "vertical_animation"}, + ["generator"] = {"pictures"}, ["heat-interface"] = {"picture"}, ["heat-pipe"] = {"connection_sprites", "heat_glow_sprites"}, ["inserter"] = {"hand_base_picture", "hand_closed_picture", "hand_open_picture", "hand_base_shadow", "hand_closed_shadow", "hand_open_shadow"}, @@ -54,6 +54,7 @@ local function test_entity_graphics() ["solar-panel"] = {"picture"}, ["storage-tank"] = {"pictures"}, ["tree"] = {{"pictures", "variations"}}, + ["valve"] = {"animations"}, ["wall"] = {"pictures"}, } local excluded_types = { @@ -224,7 +225,6 @@ local function factoriopedia_recipes(check_absent_recipes) ["space-science-pack"] = 12, } local unlocks = {} - local barreling = {["py-barreling"] = true, ["py-unbarreling"] = true} for _, tech in pairs(data.raw["technology"]) do local unit_tech = table.deepcopy(tech) local science @@ -255,7 +255,7 @@ local function factoriopedia_recipes(check_absent_recipes) end end for _, modifier in pairs(tech.effects or {}) do - if modifier.type == "unlock-recipe" and not barreling[data.raw["recipe"][modifier.recipe].category] then + if modifier.type == "unlock-recipe" and not RECIPE(modifier.recipe):has_categories{"py-barreling", "py-unbarreling"} then if not science then for _, pack in pairs(unit_tech.unit.ingredients) do if pack[2] == 1 then @@ -271,7 +271,7 @@ local function factoriopedia_recipes(check_absent_recipes) end end for name, recipe in pairs(data.raw["recipe"]) do - if recipe.enabled ~= false and not recipe.category == "py-incineration" and not recipe.hidden then + if recipe.enabled ~= false and not RECIPE(name):has_category("py-incineration") and not recipe.hidden then for _, product in pairs(recipe.results) do unlocks[product.name] = unlocks[product.name] or {} table.insert(unlocks[product.name], {science = 0, recipe = name}) diff --git a/tests/scenario-tests.lua b/tests/scenario-tests.lua index 17f85f2..598f144 100644 --- a/tests/scenario-tests.lua +++ b/tests/scenario-tests.lua @@ -1,92 +1,93 @@ -local tests = {} - -local helper = require("scenario-helper") - -local turd_upgrade_names_table = { - "prototypes/upgrades/biofactory", - "prototypes/upgrades/compost", - "prototypes/upgrades/creature", - "prototypes/upgrades/incubator", - "prototypes/upgrades/slaughterhouse", - "prototypes/upgrades/arthurian", - "prototypes/upgrades/dhilmos", - "prototypes/upgrades/dingrits", - "prototypes/upgrades/korlex", - "prototypes/upgrades/fawogae", - "prototypes/upgrades/moss", - "prototypes/upgrades/scrondrix", - "prototypes/upgrades/vonix", - "prototypes/upgrades/yaedols", - "prototypes/upgrades/fwf", - "prototypes/upgrades/cadaveric", - "prototypes/upgrades/moondrop", - "prototypes/upgrades/auog", - "prototypes/upgrades/arqad", - "prototypes/upgrades/phadai", - "prototypes/upgrades/phagnot", - "prototypes/upgrades/sponge", - "prototypes/upgrades/tuuphra", - "prototypes/upgrades/ulric", - "prototypes/upgrades/vrauks", - "prototypes/upgrades/xyhiphoe", - "prototypes/upgrades/seaweed", - "prototypes/upgrades/atomizer", - "prototypes/upgrades/bioreactor", - "prototypes/upgrades/zungror", - "prototypes/upgrades/numal", - "prototypes/upgrades/data-array", - "prototypes/upgrades/xeno", - "prototypes/upgrades/fish", - "prototypes/upgrades/cottongut", - "prototypes/upgrades/guar", - "prototypes/upgrades/kicalk", - "prototypes/upgrades/rennea", - "prototypes/upgrades/navens", - "prototypes/upgrades/antelope", - "prototypes/upgrades/bhoddos", - "prototypes/upgrades/genlab", - "prototypes/upgrades/grod", - "prototypes/upgrades/research", - "prototypes/upgrades/yotoi", - "prototypes/upgrades/cridren", - "prototypes/upgrades/kmauts", - "prototypes/upgrades/trits", - "prototypes/upgrades/ralesia", - "prototypes/upgrades/mukmoux", - "prototypes/upgrades/simikmetalMK01", - "prototypes/upgrades/simikmetalMK02", - "prototypes/upgrades/simikmetalMK03", - "prototypes/upgrades/simikmetalMK04", - "prototypes/upgrades/simikmetalMK05", - "prototypes/upgrades/simikmetalMK06", - "prototypes/upgrades/sap", - "prototypes/upgrades/bioprinting", - "prototypes/upgrades/zipir", - "prototypes/upgrades/wpu", -} -local turds = {} -for i, t in pairs(turd_upgrade_names_table) do - turds[i] = require("__pyalienlife__/" .. t) -end - - -function tests.auog_turd_crash() - helper.select_turd("auog-upgrade", "glowing-mushrooms") - local auog_power_gen = game.surfaces.nauvis.create_entity{name = "generator-1", position = {0, 0}, force = game.player.force, player = game.player, raise_built = true} - helper.select_turd("auog-upgrade", "glowing-mushrooms") - helper.get_entity_at({0, 0}).destroy() - - return "Auog Glowing Mushrooms Turd" -end - -function tests.test_all_turds() - for _,turd in pairs(turds) do - for _,subtech in pairs(turd.sub_techs) do - helper.select_turd(turd.master_tech.name, subtech.name) - helper.select_turd(turd.master_tech.name, subtech.name) - end - end - return "Test all turds" -end - -return tests +if not script.active_mods.pyalienlife then return end +local tests = {} + +local helper = require("scenario-helper") + +local turd_upgrade_names_table = { + "prototypes/upgrades/biofactory", + "prototypes/upgrades/compost", + "prototypes/upgrades/creature", + "prototypes/upgrades/incubator", + "prototypes/upgrades/slaughterhouse", + "prototypes/upgrades/arthurian", + "prototypes/upgrades/dhilmos", + "prototypes/upgrades/dingrits", + "prototypes/upgrades/korlex", + "prototypes/upgrades/fawogae", + "prototypes/upgrades/moss", + "prototypes/upgrades/scrondrix", + "prototypes/upgrades/vonix", + "prototypes/upgrades/yaedols", + "prototypes/upgrades/fwf", + "prototypes/upgrades/cadaveric", + "prototypes/upgrades/moondrop", + "prototypes/upgrades/auog", + "prototypes/upgrades/arqad", + "prototypes/upgrades/phadai", + "prototypes/upgrades/phagnot", + "prototypes/upgrades/sponge", + "prototypes/upgrades/tuuphra", + "prototypes/upgrades/ulric", + "prototypes/upgrades/vrauks", + "prototypes/upgrades/xyhiphoe", + "prototypes/upgrades/seaweed", + "prototypes/upgrades/atomizer", + "prototypes/upgrades/bioreactor", + "prototypes/upgrades/zungror", + "prototypes/upgrades/numal", + "prototypes/upgrades/data-array", + "prototypes/upgrades/xeno", + "prototypes/upgrades/fish", + "prototypes/upgrades/cottongut", + "prototypes/upgrades/guar", + "prototypes/upgrades/kicalk", + "prototypes/upgrades/rennea", + "prototypes/upgrades/navens", + "prototypes/upgrades/antelope", + "prototypes/upgrades/bhoddos", + "prototypes/upgrades/genlab", + "prototypes/upgrades/grod", + "prototypes/upgrades/research", + "prototypes/upgrades/yotoi", + "prototypes/upgrades/cridren", + "prototypes/upgrades/kmauts", + "prototypes/upgrades/trits", + "prototypes/upgrades/ralesia", + "prototypes/upgrades/mukmoux", + "prototypes/upgrades/simikmetalMK01", + "prototypes/upgrades/simikmetalMK02", + "prototypes/upgrades/simikmetalMK03", + "prototypes/upgrades/simikmetalMK04", + "prototypes/upgrades/simikmetalMK05", + "prototypes/upgrades/simikmetalMK06", + "prototypes/upgrades/sap", + "prototypes/upgrades/bioprinting", + "prototypes/upgrades/zipir", + "prototypes/upgrades/wpu", +} +local turds = {} +for i, t in pairs(turd_upgrade_names_table) do + turds[i] = require("__pyalienlife__/" .. t) +end + + +function tests.auog_turd_crash() + helper.select_turd("auog-upgrade", "glowing-mushrooms") + local auog_power_gen = game.surfaces.nauvis.create_entity{name = "generator-1", position = {0, 0}, force = game.player.force, player = game.player, raise_built = true} + helper.select_turd("auog-upgrade", "glowing-mushrooms") + helper.get_entity_at({0, 0}).destroy() + + return "Auog Glowing Mushrooms Turd" +end + +function tests.test_all_turds() + for _,turd in pairs(turds) do + for _,subtech in pairs(turd.sub_techs) do + helper.select_turd(turd.master_tech.name, subtech.name) + helper.select_turd(turd.master_tech.name, subtech.name) + end + end + return "Test all turds" +end + +return tests