From aa5aacf7b2b98135cf7cc70c4e1f7a5756bb7437 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:08:04 -0700 Subject: [PATCH 1/9] initial commit, includes cache optimizations --- control.lua | 1 + scripts/slaughterhouse/slaughterhouse.lua | 260 ++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 scripts/slaughterhouse/slaughterhouse.lua diff --git a/control.lua b/control.lua index 64acbb73..1e819116 100644 --- a/control.lua +++ b/control.lua @@ -22,6 +22,7 @@ require "scripts.ocula.ocula" require "scripts.farming.farming" require "scripts.smart-farm.smart-farm" require "scripts.smart-farm.mega-farm" +require "scripts.slaughterhouse.slaughterhouse" require "scripts.worm.worm" require "scripts.turd.turd" require "scripts.vatbrain.vatbrain" diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua new file mode 100644 index 00000000..214058b1 --- /dev/null +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -0,0 +1,260 @@ +local animals = { + "auog", + "ulric", + "mukmoux", + "arthurian", + "cottongut", + "dhilmos", + "scrondrix", + "phadai", + "fish", + "phagnot", + "kmauts", + "dingrits", + "xeno", + "arqad", + "cridren", + "antelope", + "trits", + "vonix", + "vrauks", + "xyhiphoe", + "zipir", + "korlex", + "simik", +} +if script.active_mods["pyalternativeenergy"] then + table.insert(animals, "zungror") + table.insert(animals, "numal") +end +if script.active_mods["pystellarexpedition"] then + --table.insert(animals, 'tuls') + --table.insert(animals, 'riga') + table.insert(animals, "kakkalakki") +end + +--TODO circuit connections, parameters, quality + +local machines_with_gui = { + ["slaughterhouse-mk01"] = true, + ["slaughterhouse-mk02"] = true, + ["slaughterhouse-mk03"] = true, + ["slaughterhouse-mk04"] = true, + ["rc-mk01"] = true, + ["rc-mk02"] = true, + ["rc-mk03"] = true, + ["rc-mk04"] = true, +} + +-- cache crafting categories +local permitted_recipes = {} +for machine in pairs(machines_with_gui) do + for category in pairs(prototypes.entity[machine].crafting_categories) do + permitted_recipes[category] = {} + end +end +-- ignore parameters +permitted_recipes.parameters = nil + +-- cache permitted recipes +for category in pairs(permitted_recipes) do + for r, recipe in pairs(prototypes.get_recipe_filtered{{filter = "category", category = category}}) do + permitted_recipes[category][r] = recipe.subgroup.name + end +end + +py.on_event(defines.events.on_object_destroyed, function(event) + local unit_number = event.useful_id + if not unit_number or not storage.opened_slaughterhouses[unit_number] then return end + + storage.opened_slaughterhouses[unit_number] = nil + for _, player in pairs(game.players) do + local gui = player.gui.screen.slaughterhouse + if gui and gui.tags.entity == unit_number then gui.destroy() end + end +end) + +local alt_animals = { + zipir = "zipir1", + kakkalakki = "kakkalakki-f" +} + +local function get_animal_item(animal) + return alt_animals[animal] or animal +end + +local function build_animal_table(content_frame, player) + content_frame.clear() + local main_frame = content_frame.parent + main_frame.caption = main_frame.tags.caption + local animal_table = content_frame.add {type = "table", name = "s_table", column_count = 6} + for category in pairs(main_frame.tags.categories) do + for recipe, subgroup in pairs(permitted_recipes[category] or {}) do + if player.force.recipes[recipe].enabled then + for _, animal in pairs(animals) do + if subgroup:match(animal, 1, true) then + local name = "py_slaughterhouse_animal_" .. animal + if not animal_table[name] then + animal_table.add { + type = "choose-elem-button", + name = name, + elem_type = "item", + item = get_animal_item(animal), + style = "image_tab_slot", + tags = {animal = animal}, + locked = true + } + end + goto continue + end + end + game.print('ERROR: Recipe "' .. recipe .. '" has crafting category "' .. category .. '" but is unselectable in the GUI') + ::continue:: + end + end + end +end + +local function create_slaughterhouse_gui(player_index) + local player = game.get_player(player_index) + if not player then return end + local entity = player.opened + if not entity then return end + local name = entity.name == "entity-ghost" and entity.ghost_name or entity.name + if not machines_with_gui[name] or entity.get_recipe() then return end + local main_frame = player.gui.screen.add { + type = "frame", + name = "slaughterhouse", + direction = "vertical", + tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"slaughterhouse-gui." .. name}} + } + main_frame.force_auto_center() + player.opened = main_frame + local content_frame = main_frame.add {type = "frame", name = "content_frame", direction = "vertical", style = "inside_shallow_frame_with_padding"} + content_frame.style.vertically_stretchable = true + build_animal_table(content_frame, player) + storage.opened_slaughterhouses[entity.unit_number] = entity + script.register_on_object_destroyed(entity) + storage.watched_slaughterhouses[player_index] = nil + storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) +end + +py.on_event(py.events.on_gui_opened(), function(event) + local entity = event.entity + if not entity then return end + if not string.match(entity.name, "slaughterhouse%-") and not string.match(entity.name, "rc%-") then return end + + if entity.get_recipe() then + storage.watched_slaughterhouses[event.player_index] = entity + storage.watch_slaughterhouse = true + else + create_slaughterhouse_gui(event.player_index) + end +end) + +py.on_event(defines.events.on_gui_closed, function(event) + -- if not storage.watched_slaughterhouses then return end + local player = game.get_player(event.player_index) + if event.gui_type == defines.gui_type.custom then + local gui = player.gui.screen.slaughterhouse + if gui then gui.destroy() end + end + storage.watched_slaughterhouses[event.player_index] = nil + storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) +end) + +local function set_recipe(player, entity, recipe) + for item, count in pairs(entity.set_recipe(recipe)) do + count = count - player.insert {name = item, count = count} + if count > 0 then + player.surface.spill_item_stack {position = player.position, stack = {name = item, count = count}, enable_looted = true} + end + end + player.opened = entity +end + +gui_events[defines.events.on_gui_click]["py_slaughterhouse_animal_.+"] = function(event) + local player = game.get_player(event.player_index) + local element = event.element + local content_frame = element.parent.parent + local main_frame = content_frame.parent + local animal = element.tags.animal + content_frame.clear() + main_frame.caption = {"slaughterhouse-gui.select-recipe"} + local recipe_flow = content_frame.add {type = "flow", direction = "horizontal"} + recipe_flow.add {type = "sprite-button", name = "py_slaughterhouse_back", sprite = "utility/left_arrow"} + local recipe_table = recipe_flow.add {type = "table", column_count = 5} + local recipe_count, avalible_recipe = 0, nil + for category in pairs(main_frame.tags.categories) do + for recipe, subgroup in pairs(permitted_recipes[category] or {}) do + if subgroup:match(animal) and player.force.recipes[recipe].enabled then + recipe_count, avalible_recipe = recipe_count + 1, recipe + recipe_table.add { + type = "choose-elem-button", + name = "py_slaughterhouse_recipe_" .. recipe, + elem_type = "recipe", + recipe = recipe, + style = "slot_button", + tags = {recipe = recipe}, + locked = true + } + end + end + end + -- for _, recipe in pairs(player.force.recipes) do + -- if main_frame.tags.categories[recipe.category] and string.match(recipe.subgroup.name, animal) and recipe.enabled then + -- recipe_count, avalible_recipe = recipe_count + 1, recipe.name + -- recipe_table.add { + -- type = "choose-elem-button", + -- name = "py_slaughterhouse_recipe_" .. recipe.name, + -- elem_type = "recipe", + -- recipe = recipe.name, + -- style = "slot_button", + -- tags = {recipe = recipe.name}, + -- locked = true + -- } + -- end + -- end + + if recipe_count == 1 then + local entity = storage.opened_slaughterhouses[main_frame.tags.entity] + if not entity or not entity.valid then return end + set_recipe(player, entity, avalible_recipe) + end +end + +gui_events[defines.events.on_gui_click]["py_slaughterhouse_back"] = function(event) + local player = game.players[event.player_index] + local content_frame = event.element.parent.parent + build_animal_table(content_frame, player) +end + +gui_events[defines.events.on_gui_click]["py_slaughterhouse_recipe_.+"] = function(event) + local player = game.get_player(event.player_index) + local element = event.element + local main_frame = element.parent.parent.parent.parent + local entity = storage.opened_slaughterhouses[main_frame.tags.entity] + local recipe = element.tags.recipe + if not entity or not entity.valid then return end + set_recipe(player, entity, recipe) +end + +py.on_event(py.events.on_init(), function() + storage.watched_slaughterhouses = storage.watched_slaughterhouses or {} + storage.opened_slaughterhouses = storage.opened_slaughterhouses or {} +end) + +py.on_event(defines.events.on_tick, function() + if not storage.watch_slaughterhouse then return end + + for player_index, entity in pairs(storage.watched_slaughterhouses) do + if not entity.valid then + storage.watched_slaughterhouses[player_index] = nil + return + end + + if not entity.get_recipe() then + create_slaughterhouse_gui(player_index, entity) + end + end +end) \ No newline at end of file From 728774a9dfb7eb666add891fc7ddfa508fa0243b Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:14:17 -0700 Subject: [PATCH 2/9] allow normal assembler GUI if circuit set recipe is enabled --- scripts/slaughterhouse/slaughterhouse.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index 214058b1..e5cd3005 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -148,7 +148,10 @@ py.on_event(py.events.on_gui_opened(), function(event) storage.watched_slaughterhouses[event.player_index] = entity storage.watch_slaughterhouse = true else - create_slaughterhouse_gui(event.player_index) + local control_behavior = entity.get_control_behavior() + if not control_behavior or not control_behavior.circuit_set_recipe then + create_slaughterhouse_gui(event.player_index) + end end end) From e0482be4f38b3b2345ad6ed210de22c52911393a Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:20:31 -0700 Subject: [PATCH 3/9] generalize control behaviour check --- scripts/slaughterhouse/slaughterhouse.lua | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index e5cd3005..ef15283c 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -121,7 +121,8 @@ local function create_slaughterhouse_gui(player_index) local entity = player.opened if not entity then return end local name = entity.name == "entity-ghost" and entity.ghost_name or entity.name - if not machines_with_gui[name] or entity.get_recipe() then return end + local control_behavior = entity.get_control_behavior() + if not machines_with_gui[name] or entity.get_recipe() or (control_behavior and control_behavior.circuit_set_recipe) then return end local main_frame = player.gui.screen.add { type = "frame", name = "slaughterhouse", @@ -143,15 +144,13 @@ py.on_event(py.events.on_gui_opened(), function(event) local entity = event.entity if not entity then return end if not string.match(entity.name, "slaughterhouse%-") and not string.match(entity.name, "rc%-") then return end + local control_behavior = entity.get_control_behavior() - if entity.get_recipe() then + if entity.get_recipe() or control_behavior and control_behavior.circuit_set_recipe then storage.watched_slaughterhouses[event.player_index] = entity storage.watch_slaughterhouse = true else - local control_behavior = entity.get_control_behavior() - if not control_behavior or not control_behavior.circuit_set_recipe then - create_slaughterhouse_gui(event.player_index) - end + create_slaughterhouse_gui(event.player_index) end end) From 2817b1f8634ee6a4a1510040e1125c20f6621132 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:31:50 -0700 Subject: [PATCH 4/9] formatting --- scripts/slaughterhouse/slaughterhouse.lua | 316 +++++++++++----------- 1 file changed, 151 insertions(+), 165 deletions(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index ef15283c..5d19f774 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -1,49 +1,49 @@ +-- for full history reference: https://github.com/pyanodon/pyalienlife/commit/ed87228489c87e6c68993d78f09e76e21970302a + local animals = { - "auog", - "ulric", - "mukmoux", - "arthurian", - "cottongut", - "dhilmos", - "scrondrix", - "phadai", - "fish", - "phagnot", - "kmauts", - "dingrits", - "xeno", - "arqad", - "cridren", - "antelope", - "trits", - "vonix", - "vrauks", - "xyhiphoe", - "zipir", - "korlex", - "simik", + "auog", + "ulric", + "mukmoux", + "arthurian", + "cottongut", + "dhilmos", + "scrondrix", + "phadai", + "fish", + "phagnot", + "kmauts", + "dingrits", + "xeno", + "arqad", + "cridren", + "antelope", + "trits", + "vonix", + "vrauks", + "xyhiphoe", + "zipir", + "korlex", + "simik", } if script.active_mods["pyalternativeenergy"] then - table.insert(animals, "zungror") - table.insert(animals, "numal") + table.insert(animals, "zungror") + table.insert(animals, "numal") end if script.active_mods["pystellarexpedition"] then - --table.insert(animals, 'tuls') - --table.insert(animals, 'riga') - table.insert(animals, "kakkalakki") + --table.insert(animals, 'tuls') + --table.insert(animals, 'riga') + table.insert(animals, "kakkalakki") end ---TODO circuit connections, parameters, quality - local machines_with_gui = { - ["slaughterhouse-mk01"] = true, - ["slaughterhouse-mk02"] = true, - ["slaughterhouse-mk03"] = true, - ["slaughterhouse-mk04"] = true, - ["rc-mk01"] = true, - ["rc-mk02"] = true, - ["rc-mk03"] = true, - ["rc-mk04"] = true, + ["slaughterhouse-mk01"] = true, + ["slaughterhouse-mk02"] = true, + ["slaughterhouse-mk03"] = true, + ["slaughterhouse-mk04"] = true, + ["rc-mk01"] = true, + ["rc-mk02"] = true, + ["rc-mk03"] = true, + ["rc-mk04"] = true, } -- cache crafting categories @@ -64,14 +64,14 @@ for category in pairs(permitted_recipes) do end py.on_event(defines.events.on_object_destroyed, function(event) - local unit_number = event.useful_id - if not unit_number or not storage.opened_slaughterhouses[unit_number] then return end + local unit_number = event.useful_id + if not unit_number or not storage.opened_slaughterhouses[unit_number] then return end - storage.opened_slaughterhouses[unit_number] = nil - for _, player in pairs(game.players) do - local gui = player.gui.screen.slaughterhouse - if gui and gui.tags.entity == unit_number then gui.destroy() end - end + storage.opened_slaughterhouses[unit_number] = nil + for _, player in pairs(game.players) do + local gui = player.gui.screen.slaughterhouse + if gui and gui.tags.entity == unit_number then gui.destroy() end + end end) local alt_animals = { @@ -80,14 +80,14 @@ local alt_animals = { } local function get_animal_item(animal) - return alt_animals[animal] or animal + return alt_animals[animal] or animal end local function build_animal_table(content_frame, player) - content_frame.clear() - local main_frame = content_frame.parent - main_frame.caption = main_frame.tags.caption - local animal_table = content_frame.add {type = "table", name = "s_table", column_count = 6} + content_frame.clear() + local main_frame = content_frame.parent + main_frame.caption = main_frame.tags.caption + local animal_table = content_frame.add {type = "table", name = "s_table", column_count = 6} for category in pairs(main_frame.tags.categories) do for recipe, subgroup in pairs(permitted_recipes[category] or {}) do if player.force.recipes[recipe].enabled then @@ -102,7 +102,7 @@ local function build_animal_table(content_frame, player) item = get_animal_item(animal), style = "image_tab_slot", tags = {animal = animal}, - locked = true + locked = true } end goto continue @@ -116,147 +116,133 @@ local function build_animal_table(content_frame, player) end local function create_slaughterhouse_gui(player_index) - local player = game.get_player(player_index) - if not player then return end - local entity = player.opened - if not entity then return end + local player = game.get_player(player_index) + if not player then return end + local entity = player.opened + if not entity then return end local name = entity.name == "entity-ghost" and entity.ghost_name or entity.name - local control_behavior = entity.get_control_behavior() + local control_behavior = entity.get_control_behavior() if not machines_with_gui[name] or entity.get_recipe() or (control_behavior and control_behavior.circuit_set_recipe) then return end - local main_frame = player.gui.screen.add { - type = "frame", - name = "slaughterhouse", - direction = "vertical", - tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"slaughterhouse-gui." .. name}} - } - main_frame.force_auto_center() - player.opened = main_frame - local content_frame = main_frame.add {type = "frame", name = "content_frame", direction = "vertical", style = "inside_shallow_frame_with_padding"} - content_frame.style.vertically_stretchable = true - build_animal_table(content_frame, player) - storage.opened_slaughterhouses[entity.unit_number] = entity - script.register_on_object_destroyed(entity) - storage.watched_slaughterhouses[player_index] = nil - storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) + local main_frame = player.gui.screen.add { + type = "frame", + name = "slaughterhouse", + direction = "vertical", + tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"slaughterhouse-gui." .. name}} + } + main_frame.force_auto_center() + player.opened = main_frame + local content_frame = main_frame.add {type = "frame", name = "content_frame", direction = "vertical", style = "inside_shallow_frame_with_padding"} + content_frame.style.vertically_stretchable = true + build_animal_table(content_frame, player) + storage.opened_slaughterhouses[entity.unit_number] = entity + script.register_on_object_destroyed(entity) + storage.watched_slaughterhouses[player_index] = nil + storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) end py.on_event(py.events.on_gui_opened(), function(event) - local entity = event.entity - if not entity then return end - if not string.match(entity.name, "slaughterhouse%-") and not string.match(entity.name, "rc%-") then return end - local control_behavior = entity.get_control_behavior() - - if entity.get_recipe() or control_behavior and control_behavior.circuit_set_recipe then - storage.watched_slaughterhouses[event.player_index] = entity - storage.watch_slaughterhouse = true - else - create_slaughterhouse_gui(event.player_index) - end + local entity = event.entity + if not entity then return end + if not string.match(entity.name, "slaughterhouse%-") and not string.match(entity.name, "rc%-") then return end + local control_behavior = entity.get_control_behavior() + + if entity.get_recipe() or control_behavior and control_behavior.circuit_set_recipe then + storage.watched_slaughterhouses[event.player_index] = entity + storage.watch_slaughterhouse = true + else + create_slaughterhouse_gui(event.player_index) + end end) py.on_event(defines.events.on_gui_closed, function(event) - -- if not storage.watched_slaughterhouses then return end - local player = game.get_player(event.player_index) - if event.gui_type == defines.gui_type.custom then - local gui = player.gui.screen.slaughterhouse - if gui then gui.destroy() end - end - storage.watched_slaughterhouses[event.player_index] = nil - storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) + -- if not storage.watched_slaughterhouses then return end + local player = game.get_player(event.player_index) + if event.gui_type == defines.gui_type.custom then + local gui = player.gui.screen.slaughterhouse + if gui then gui.destroy() end + end + storage.watched_slaughterhouses[event.player_index] = nil + storage.watch_slaughterhouse = not not next(storage.watched_slaughterhouses) end) local function set_recipe(player, entity, recipe) - for item, count in pairs(entity.set_recipe(recipe)) do - count = count - player.insert {name = item, count = count} - if count > 0 then - player.surface.spill_item_stack {position = player.position, stack = {name = item, count = count}, enable_looted = true} - end - end - player.opened = entity + for item, count in pairs(entity.set_recipe(recipe)) do + count = count - player.insert {name = item, count = count} + if count > 0 then + player.surface.spill_item_stack {position = player.position, stack = {name = item, count = count}, enable_looted = true} + end + end + player.opened = entity end gui_events[defines.events.on_gui_click]["py_slaughterhouse_animal_.+"] = function(event) - local player = game.get_player(event.player_index) - local element = event.element - local content_frame = element.parent.parent - local main_frame = content_frame.parent - local animal = element.tags.animal - content_frame.clear() - main_frame.caption = {"slaughterhouse-gui.select-recipe"} - local recipe_flow = content_frame.add {type = "flow", direction = "horizontal"} - recipe_flow.add {type = "sprite-button", name = "py_slaughterhouse_back", sprite = "utility/left_arrow"} - local recipe_table = recipe_flow.add {type = "table", column_count = 5} - local recipe_count, avalible_recipe = 0, nil - for category in pairs(main_frame.tags.categories) do - for recipe, subgroup in pairs(permitted_recipes[category] or {}) do - if subgroup:match(animal) and player.force.recipes[recipe].enabled then - recipe_count, avalible_recipe = recipe_count + 1, recipe - recipe_table.add { - type = "choose-elem-button", - name = "py_slaughterhouse_recipe_" .. recipe, - elem_type = "recipe", - recipe = recipe, - style = "slot_button", - tags = {recipe = recipe}, - locked = true - } - end - end - end - -- for _, recipe in pairs(player.force.recipes) do - -- if main_frame.tags.categories[recipe.category] and string.match(recipe.subgroup.name, animal) and recipe.enabled then - -- recipe_count, avalible_recipe = recipe_count + 1, recipe.name - -- recipe_table.add { - -- type = "choose-elem-button", - -- name = "py_slaughterhouse_recipe_" .. recipe.name, - -- elem_type = "recipe", - -- recipe = recipe.name, - -- style = "slot_button", - -- tags = {recipe = recipe.name}, - -- locked = true - -- } - -- end - -- end + local player = game.get_player(event.player_index) + local element = event.element + local content_frame = element.parent.parent + local main_frame = content_frame.parent + local animal = element.tags.animal + content_frame.clear() + main_frame.caption = {"slaughterhouse-gui.select-recipe"} + local recipe_flow = content_frame.add {type = "flow", direction = "horizontal"} + recipe_flow.add {type = "sprite-button", name = "py_slaughterhouse_back", sprite = "utility/left_arrow"} + local recipe_table = recipe_flow.add {type = "table", column_count = 5} + local recipe_count, avalible_recipe = 0, nil + for category in pairs(main_frame.tags.categories) do + for recipe, subgroup in pairs(permitted_recipes[category] or {}) do + if subgroup:match(animal) and player.force.recipes[recipe].enabled then + recipe_count, avalible_recipe = recipe_count + 1, recipe + recipe_table.add { + type = "choose-elem-button", + name = "py_slaughterhouse_recipe_" .. recipe, + elem_type = "recipe", + recipe = recipe, + style = "slot_button", + tags = {recipe = recipe}, + locked = true + } + end + end + end - if recipe_count == 1 then - local entity = storage.opened_slaughterhouses[main_frame.tags.entity] - if not entity or not entity.valid then return end - set_recipe(player, entity, avalible_recipe) - end + if recipe_count == 1 then + local entity = storage.opened_slaughterhouses[main_frame.tags.entity] + if not entity or not entity.valid then return end + set_recipe(player, entity, avalible_recipe) + end end gui_events[defines.events.on_gui_click]["py_slaughterhouse_back"] = function(event) - local player = game.players[event.player_index] - local content_frame = event.element.parent.parent - build_animal_table(content_frame, player) + local player = game.players[event.player_index] + local content_frame = event.element.parent.parent + build_animal_table(content_frame, player) end gui_events[defines.events.on_gui_click]["py_slaughterhouse_recipe_.+"] = function(event) - local player = game.get_player(event.player_index) - local element = event.element - local main_frame = element.parent.parent.parent.parent - local entity = storage.opened_slaughterhouses[main_frame.tags.entity] - local recipe = element.tags.recipe - if not entity or not entity.valid then return end - set_recipe(player, entity, recipe) + local player = game.get_player(event.player_index) + local element = event.element + local main_frame = element.parent.parent.parent.parent + local entity = storage.opened_slaughterhouses[main_frame.tags.entity] + local recipe = element.tags.recipe + if not entity or not entity.valid then return end + set_recipe(player, entity, recipe) end py.on_event(py.events.on_init(), function() - storage.watched_slaughterhouses = storage.watched_slaughterhouses or {} - storage.opened_slaughterhouses = storage.opened_slaughterhouses or {} + storage.watched_slaughterhouses = storage.watched_slaughterhouses or {} + storage.opened_slaughterhouses = storage.opened_slaughterhouses or {} end) py.on_event(defines.events.on_tick, function() - if not storage.watch_slaughterhouse then return end + if not storage.watch_slaughterhouse then return end - for player_index, entity in pairs(storage.watched_slaughterhouses) do - if not entity.valid then - storage.watched_slaughterhouses[player_index] = nil - return - end + for player_index, entity in pairs(storage.watched_slaughterhouses) do + if not entity.valid then + storage.watched_slaughterhouses[player_index] = nil + return + end - if not entity.get_recipe() then - create_slaughterhouse_gui(player_index, entity) - end - end + if not entity.get_recipe() then + create_slaughterhouse_gui(player_index, entity) + end + end end) \ No newline at end of file From 6d77c7f71f248def6cfe75ceccbde4e4ace83652 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Sat, 17 Jan 2026 22:42:24 -0700 Subject: [PATCH 5/9] cache animal instead of subgroup --- scripts/slaughterhouse/slaughterhouse.lua | 47 +++++++++++------------ 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index 5d19f774..acd43830 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -59,7 +59,15 @@ permitted_recipes.parameters = nil -- cache permitted recipes for category in pairs(permitted_recipes) do for r, recipe in pairs(prototypes.get_recipe_filtered{{filter = "category", category = category}}) do - permitted_recipes[category][r] = recipe.subgroup.name + for _, animal in pairs(animals) do + if recipe.subgroup.name:match(animal) then + permitted_recipes[category][r] = animal + break + end + end + if not permitted_recipes[category][r] then + error("Could not find associated animal for recipe: " .. r) + end end end @@ -89,27 +97,18 @@ local function build_animal_table(content_frame, player) main_frame.caption = main_frame.tags.caption local animal_table = content_frame.add {type = "table", name = "s_table", column_count = 6} for category in pairs(main_frame.tags.categories) do - for recipe, subgroup in pairs(permitted_recipes[category] or {}) do - if player.force.recipes[recipe].enabled then - for _, animal in pairs(animals) do - if subgroup:match(animal, 1, true) then - local name = "py_slaughterhouse_animal_" .. animal - if not animal_table[name] then - animal_table.add { - type = "choose-elem-button", - name = name, - elem_type = "item", - item = get_animal_item(animal), - style = "image_tab_slot", - tags = {animal = animal}, - locked = true - } - end - goto continue - end - end - game.print('ERROR: Recipe "' .. recipe .. '" has crafting category "' .. category .. '" but is unselectable in the GUI') - ::continue:: + for recipe, animal in pairs(permitted_recipes[category] or {}) do + local name = "py_slaughterhouse_animal_" .. animal + if not animal_table[name] and player.force.recipes[recipe].enabled then + animal_table.add { + type = "choose-elem-button", + name = name, + elem_type = "item", + item = get_animal_item(animal), + style = "image_tab_slot", + tags = {animal = animal}, + locked = true + } end end end @@ -188,8 +187,8 @@ gui_events[defines.events.on_gui_click]["py_slaughterhouse_animal_.+"] = functio local recipe_table = recipe_flow.add {type = "table", column_count = 5} local recipe_count, avalible_recipe = 0, nil for category in pairs(main_frame.tags.categories) do - for recipe, subgroup in pairs(permitted_recipes[category] or {}) do - if subgroup:match(animal) and player.force.recipes[recipe].enabled then + for recipe, recipe_animal in pairs(permitted_recipes[category] or {}) do + if recipe_animal == animal and player.force.recipes[recipe].enabled then recipe_count, avalible_recipe = recipe_count + 1, recipe recipe_table.add { type = "choose-elem-button", From 00654b90a983f0af0cc5c28b00b1f3d601d398f6 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:42:30 -0700 Subject: [PATCH 6/9] remove logging, remove string.match uses, generalize naming such that it's easier to read out of context --- scripts/slaughterhouse/slaughterhouse.lua | 75 ++++++++++++----------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index acd43830..427f8428 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -1,6 +1,6 @@ -- for full history reference: https://github.com/pyanodon/pyalienlife/commit/ed87228489c87e6c68993d78f09e76e21970302a -local animals = { +local subgroups = { "auog", "ulric", "mukmoux", @@ -26,13 +26,13 @@ local animals = { "simik", } if script.active_mods["pyalternativeenergy"] then - table.insert(animals, "zungror") - table.insert(animals, "numal") + subgroups[#subgroups+1] = "zungror" + subgroups[#subgroups+1] = "numal" end if script.active_mods["pystellarexpedition"] then - --table.insert(animals, 'tuls') - --table.insert(animals, 'riga') - table.insert(animals, "kakkalakki") + -- subgroups[#subgroups+1] = "tuls" + -- subgroups[#subgroups+1] = "riga" + subgroups[#subgroups+1] = "kakkalakki" end local machines_with_gui = { @@ -59,14 +59,14 @@ permitted_recipes.parameters = nil -- cache permitted recipes for category in pairs(permitted_recipes) do for r, recipe in pairs(prototypes.get_recipe_filtered{{filter = "category", category = category}}) do - for _, animal in pairs(animals) do - if recipe.subgroup.name:match(animal) then - permitted_recipes[category][r] = animal + for _, subgroup in pairs(subgroups) do + if recipe.subgroup.name:match(subgroup) then + permitted_recipes[category][r] = subgroup break end end if not permitted_recipes[category][r] then - error("Could not find associated animal for recipe: " .. r) + error("Could not find associated subgroup for recipe: " .. r) end end end @@ -82,31 +82,31 @@ py.on_event(defines.events.on_object_destroyed, function(event) end end) -local alt_animals = { +local alt_items = { zipir = "zipir1", kakkalakki = "kakkalakki-f" } -local function get_animal_item(animal) - return alt_animals[animal] or animal +local function get_item_from_subgroup(subgroup) + return alt_items[subgroup] or subgroup end -local function build_animal_table(content_frame, player) +local function build_subgroup_table(content_frame, player) content_frame.clear() local main_frame = content_frame.parent main_frame.caption = main_frame.tags.caption - local animal_table = content_frame.add {type = "table", name = "s_table", column_count = 6} + local subgroup_table = content_frame.add {type = "table", name = "s_table", column_count = 6} for category in pairs(main_frame.tags.categories) do - for recipe, animal in pairs(permitted_recipes[category] or {}) do - local name = "py_slaughterhouse_animal_" .. animal - if not animal_table[name] and player.force.recipes[recipe].enabled then - animal_table.add { + for recipe, subgroup in pairs(permitted_recipes[category] or {}) do + local name = "py_recipe_gui_subgroup_" .. subgroup + if not subgroup_table[name] and player.force.recipes[recipe].enabled then + subgroup_table.add { type = "choose-elem-button", name = name, elem_type = "item", - item = get_animal_item(animal), + item = get_item_from_subgroup(subgroup), style = "image_tab_slot", - tags = {animal = animal}, + tags = {subgroup = subgroup}, locked = true } end @@ -114,7 +114,7 @@ local function build_animal_table(content_frame, player) end end -local function create_slaughterhouse_gui(player_index) +local function create_gui(player_index) local player = game.get_player(player_index) if not player then return end local entity = player.opened @@ -126,13 +126,13 @@ local function create_slaughterhouse_gui(player_index) type = "frame", name = "slaughterhouse", direction = "vertical", - tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"slaughterhouse-gui." .. name}} + tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"py-recipe-gui." .. name}} } main_frame.force_auto_center() player.opened = main_frame local content_frame = main_frame.add {type = "frame", name = "content_frame", direction = "vertical", style = "inside_shallow_frame_with_padding"} content_frame.style.vertically_stretchable = true - build_animal_table(content_frame, player) + build_subgroup_table(content_frame, player) storage.opened_slaughterhouses[entity.unit_number] = entity script.register_on_object_destroyed(entity) storage.watched_slaughterhouses[player_index] = nil @@ -142,14 +142,15 @@ end py.on_event(py.events.on_gui_opened(), function(event) local entity = event.entity if not entity then return end - if not string.match(entity.name, "slaughterhouse%-") and not string.match(entity.name, "rc%-") then return end + local name = entity.name == "entity-ghost" and entity.ghost_name or entity.name + if not machines_with_gui[name] then return end local control_behavior = entity.get_control_behavior() if entity.get_recipe() or control_behavior and control_behavior.circuit_set_recipe then storage.watched_slaughterhouses[event.player_index] = entity storage.watch_slaughterhouse = true else - create_slaughterhouse_gui(event.player_index) + create_gui(event.player_index) end end) @@ -174,25 +175,25 @@ local function set_recipe(player, entity, recipe) player.opened = entity end -gui_events[defines.events.on_gui_click]["py_slaughterhouse_animal_.+"] = function(event) +gui_events[defines.events.on_gui_click]["py_recipe_gui_subgroup_.+"] = function(event) local player = game.get_player(event.player_index) local element = event.element local content_frame = element.parent.parent local main_frame = content_frame.parent - local animal = element.tags.animal + local subgroup = element.tags.subgroup content_frame.clear() - main_frame.caption = {"slaughterhouse-gui.select-recipe"} + main_frame.caption = {"py-recipe-gui.select-recipe"} local recipe_flow = content_frame.add {type = "flow", direction = "horizontal"} - recipe_flow.add {type = "sprite-button", name = "py_slaughterhouse_back", sprite = "utility/left_arrow"} + recipe_flow.add {type = "sprite-button", name = "py_recipe_gui_back", sprite = "utility/left_arrow"} local recipe_table = recipe_flow.add {type = "table", column_count = 5} local recipe_count, avalible_recipe = 0, nil for category in pairs(main_frame.tags.categories) do - for recipe, recipe_animal in pairs(permitted_recipes[category] or {}) do - if recipe_animal == animal and player.force.recipes[recipe].enabled then + for recipe, recipe_subgroup in pairs(permitted_recipes[category] or {}) do + if recipe_subgroup == subgroup and player.force.recipes[recipe].enabled then recipe_count, avalible_recipe = recipe_count + 1, recipe recipe_table.add { type = "choose-elem-button", - name = "py_slaughterhouse_recipe_" .. recipe, + name = "py_recipe_gui_recipe_" .. recipe, elem_type = "recipe", recipe = recipe, style = "slot_button", @@ -210,13 +211,13 @@ gui_events[defines.events.on_gui_click]["py_slaughterhouse_animal_.+"] = functio end end -gui_events[defines.events.on_gui_click]["py_slaughterhouse_back"] = function(event) +gui_events[defines.events.on_gui_click]["py_recipe_gui_back"] = function(event) local player = game.players[event.player_index] local content_frame = event.element.parent.parent - build_animal_table(content_frame, player) + build_subgroup_table(content_frame, player) end -gui_events[defines.events.on_gui_click]["py_slaughterhouse_recipe_.+"] = function(event) +gui_events[defines.events.on_gui_click]["py_recipe_gui_recipe_.+"] = function(event) local player = game.get_player(event.player_index) local element = event.element local main_frame = element.parent.parent.parent.parent @@ -241,7 +242,7 @@ py.on_event(defines.events.on_tick, function() end if not entity.get_recipe() then - create_slaughterhouse_gui(player_index, entity) + create_gui(player_index, entity) end end end) \ No newline at end of file From 17cbf09aaf3676bac253a382b8a1d2bb93b38827 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:55:26 -0700 Subject: [PATCH 7/9] fix locale references --- locale/de/extras.cfg | 2 +- locale/en/extras.cfg | 2 +- locale/es-ES/extras.cfg | 2 +- locale/nl/extras.cfg | 2 +- locale/pl/extras.cfg | 2 +- locale/ru/extras.cfg | 2 +- locale/uk/extras.cfg | 2 +- locale/zh-CN/pyalienlife.cfg | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/locale/de/extras.cfg b/locale/de/extras.cfg index e83d7a75..a916e5e0 100644 --- a/locale/de/extras.cfg +++ b/locale/de/extras.cfg @@ -2,7 +2,7 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Wähle das nächste Opfer slaughterhouse-mk02=Wähle das nächste Opfer slaughterhouse-mk03=Wähle das nächste Opfer diff --git a/locale/en/extras.cfg b/locale/en/extras.cfg index 5680c065..b1827708 100644 --- a/locale/en/extras.cfg +++ b/locale/en/extras.cfg @@ -1,6 +1,6 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Choose your next victim slaughterhouse-mk02=Choose your next victim slaughterhouse-mk03=Choose your next victim diff --git a/locale/es-ES/extras.cfg b/locale/es-ES/extras.cfg index 0684e808..0e9fc0e3 100644 --- a/locale/es-ES/extras.cfg +++ b/locale/es-ES/extras.cfg @@ -1,6 +1,6 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Elige a tu próxima víctima slaughterhouse-mk02=Elige a tu próxima víctima slaughterhouse-mk03=Elige a tu próxima víctima diff --git a/locale/nl/extras.cfg b/locale/nl/extras.cfg index bc00f115..7faa9b4c 100644 --- a/locale/nl/extras.cfg +++ b/locale/nl/extras.cfg @@ -1,6 +1,6 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Kies je volgende slachtoffer slaughterhouse-mk02=Kies je volgende slachtoffer slaughterhouse-mk03=Kies je volgende slachtoffer diff --git a/locale/pl/extras.cfg b/locale/pl/extras.cfg index 4c611a64..8bbfc532 100644 --- a/locale/pl/extras.cfg +++ b/locale/pl/extras.cfg @@ -1,6 +1,6 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Wybierz swoją następną ofiarę slaughterhouse-mk02=Wybierz swoją następną ofiarę slaughterhouse-mk03=Wybierz swoją następną ofiarę diff --git a/locale/ru/extras.cfg b/locale/ru/extras.cfg index ab6927a6..900eb714 100644 --- a/locale/ru/extras.cfg +++ b/locale/ru/extras.cfg @@ -1,4 +1,4 @@ -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Выберите следующую жертву slaughterhouse-mk02=Выберите следующую жертву slaughterhouse-mk03=Выберите следующую жертву diff --git a/locale/uk/extras.cfg b/locale/uk/extras.cfg index e65e7d54..1f6959a4 100644 --- a/locale/uk/extras.cfg +++ b/locale/uk/extras.cfg @@ -1,6 +1,6 @@ [gui-game-finished] -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=Виберіть наступну жертву slaughterhouse-mk02=Виберіть наступну жертву slaughterhouse-mk03=Виберіть наступну жертву diff --git a/locale/zh-CN/pyalienlife.cfg b/locale/zh-CN/pyalienlife.cfg index 5ac997e7..b33bff7f 100644 --- a/locale/zh-CN/pyalienlife.cfg +++ b/locale/zh-CN/pyalienlife.cfg @@ -161,7 +161,7 @@ requestor_info=需求箱信息 provider=供应箱 provider_info=供应箱信息 -[slaughterhouse-gui] +[py-recipe-gui] slaughterhouse-mk01=有请下一位受害者 slaughterhouse-mk02=有请下一位受害者 slaughterhouse-mk03=有请下一位受害者 From fd5ecfd5df06db05e630a6ca89c6fcce03955f68 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Mon, 19 Jan 2026 19:00:42 -0700 Subject: [PATCH 8/9] ghost support --- scripts/slaughterhouse/slaughterhouse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index 427f8428..785da4b1 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -126,7 +126,7 @@ local function create_gui(player_index) type = "frame", name = "slaughterhouse", direction = "vertical", - tags = {entity = entity.unit_number, categories = entity.prototype.crafting_categories, caption = {"py-recipe-gui." .. name}} + tags = {entity = entity.unit_number, categories = (entity.name == "entity-ghost" and entity.ghost_prototype or entity.prototype).crafting_categories, caption = {"py-recipe-gui." .. name}} } main_frame.force_auto_center() player.opened = main_frame From 3880a82a53e740f73e8a6260609ec0a16ae3ab65 Mon Sep 17 00:00:00 2001 From: protocol_1903 <67478786+protocol-1903@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:11:34 -0700 Subject: [PATCH 9/9] remote interface --- scripts/slaughterhouse/slaughterhouse.lua | 83 +++++++++++++++++------ 1 file changed, 63 insertions(+), 20 deletions(-) diff --git a/scripts/slaughterhouse/slaughterhouse.lua b/scripts/slaughterhouse/slaughterhouse.lua index 785da4b1..63ffdb00 100644 --- a/scripts/slaughterhouse/slaughterhouse.lua +++ b/scripts/slaughterhouse/slaughterhouse.lua @@ -46,30 +46,78 @@ local machines_with_gui = { ["rc-mk04"] = true, } --- cache crafting categories +local alt_items = { + zipir = "zipir1", + kakkalakki = "kakkalakki-f" +} + local permitted_recipes = {} + +local function update_recipes() + for category in pairs(permitted_recipes) do + for r, recipe in pairs(prototypes.get_recipe_filtered{{filter = "category", category = category}}) do + for _, subgroup in pairs(subgroups) do + if recipe.subgroup.name:match(subgroup) then + permitted_recipes[category][r] = subgroup + break + end + end + if not permitted_recipes[category][r] then + error("Could not find associated subgroup for recipe: " .. r) + end + end + end +end + +remote.add_interface("py-recipe-gui", { + ---add a machine to use the custom recipe viewer. requires subgroups to be registered + ---@param machine data.EntityID + add_machine = function (machine) + local update = not machines_with_gui + machines_with_gui[machine] = true + for category in pairs(update and prototypes.entity[machine].crafting_categories or {}) do + permitted_recipes[category] = {} + end + permitted_recipes.parameters = nil + end, + ---remove a machine from the whitelist for the custom recipe viewer + ---@param machine data.EntityID + remove_machine = function (machine) + machines_with_gui[machine] = nil + end, + ---add a subgroup to use the custom recipe viewer. requires a compatible crafting machine + ---@param subgroup data.EntityID + add_subgroup = function (subgroup) + local update = not subgroups[subgroup] + subgroups[subgroup] = true + if update then update_recipes() end + end, + ---add a subgroup to use the custom recipe viewer + ---@param subgroup data.EntityID + remove_subgroup = function (subgroup) + subgroups[subgroup] = nil + end, + ---use an alternative item for the subgroup header icon. set to nil to remove + ---@param subgroup data.ItemSubGroupID + ---@param item data.ItemID + set_alt_item = function (subgroup, item) + alt_items[subgroup] = item + end, +}) + +-- TODO have AE/SE use remote interface + +-- cache crafting categories for machine in pairs(machines_with_gui) do for category in pairs(prototypes.entity[machine].crafting_categories) do permitted_recipes[category] = {} end end + -- ignore parameters permitted_recipes.parameters = nil --- cache permitted recipes -for category in pairs(permitted_recipes) do - for r, recipe in pairs(prototypes.get_recipe_filtered{{filter = "category", category = category}}) do - for _, subgroup in pairs(subgroups) do - if recipe.subgroup.name:match(subgroup) then - permitted_recipes[category][r] = subgroup - break - end - end - if not permitted_recipes[category][r] then - error("Could not find associated subgroup for recipe: " .. r) - end - end -end +update_recipes() py.on_event(defines.events.on_object_destroyed, function(event) local unit_number = event.useful_id @@ -82,11 +130,6 @@ py.on_event(defines.events.on_object_destroyed, function(event) end end) -local alt_items = { - zipir = "zipir1", - kakkalakki = "kakkalakki-f" -} - local function get_item_from_subgroup(subgroup) return alt_items[subgroup] or subgroup end