Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions changelog.txt
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
42 changes: 25 additions & 17 deletions data-final-fixes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -194,23 +184,23 @@ 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
local amt = 0
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
prob_amt = result.amount
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
Expand All @@ -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}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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
12 changes: 6 additions & 6 deletions data-updates.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
8 changes: 4 additions & 4 deletions info.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
92 changes: 92 additions & 0 deletions lib/alerts.lua
Original file line number Diff line number Diff line change
@@ -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)
21 changes: 12 additions & 9 deletions lib/autorecipes.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
},
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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 = {}
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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
Expand Down
20 changes: 1 addition & 19 deletions lib/control-stage.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>
Expand Down
Loading
Loading