From 4b3ca2360d58cae95a6b2f825d7c79ada4b5fe44 Mon Sep 17 00:00:00 2001 From: jodli Date: Sun, 28 Jun 2026 23:04:39 +0200 Subject: [PATCH 1/4] feat: add indestructible red creative wall The creative wall stays a normal destructible target (biters path to it, asteroids hit it) but is healed to full on every hit via a name-filtered on_entity_damaged handler, so it can never be destroyed. max_health is bumped high as defense-in-depth. The wall and its item icon are tinted red to match the mod's other creative entities. Not Space-Age gated. The on_entity_damaged handler is registered with event filters and excluded from the generic filterless event loop; Phase 3 (asteroid protection) will extend this same handler rather than registering a second one. --- changelog.txt | 1 + control.lua | 8 ++++++++ defines.lua | 3 +++ locale/en/base.cfg | 2 ++ prototypes/entity.lua | 46 +++++++++++++++++++++++++++++++++++++++++++ prototypes/item.lua | 24 ++++++++++++++++++++++ prototypes/recipe.lua | 8 ++++++++ scripts/events.lua | 15 ++++++++++++++ verify.py | 14 +++++++++++++ 9 files changed, 121 insertions(+) diff --git a/changelog.txt b/changelog.txt index 42d4673..d92a214 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,6 +5,7 @@ Date: ???? - Add an admin-only Surface tab. Admins can create a new blank surface by typing a name; the surface immediately appears in the Surface Cheats target list for every connected player allowed to see other surfaces. - Add space-platform creation to the Surface tab (Space Age only). Admins can type a name and pick the planet to orbit; the platform comes up with a hub already placed and its surface immediately appears in the Surface Cheats target list. - Add planet-surface creation to the Surface tab (Space Age only). Admins can pick a planet and create its surface; if the surface already exists the action is a harmless no-op, and any newly created surface immediately appears in the Surface Cheats target list. + - Add a creative wall: an indestructible red wall. Enemies still target and attack it, but it instantly heals back to full and can never be destroyed. It works on any surface, including space platforms, and is enabled by the normal Creative tools' recipe sweep. Changes: - Move the Surface Cheats menu from the Cheats tab into the Surface tab, grouping it with the surface-creation tools. Bugfixes: diff --git a/control.lua b/control.lua index 773d956..567a842 100644 --- a/control.lua +++ b/control.lua @@ -55,6 +55,13 @@ script.on_event(defines.events.on_player_main_inventory_changed, events.on_playe script.on_event(defines.events.on_entity_logistic_slot_changed, events.on_entity_logistic_slot_changed) script.on_event(defines.events.on_player_trash_inventory_changed, events.on_player_trash_inventory_changed) +-- on_entity_damaged is registered separately with event filters (the creative wall's name), so it +-- must be excluded from the generic filterless loop below - otherwise this loop would re-register +-- it without filters. Phase 3 will OR an impact damage-type filter into this same registration. +script.on_event(defines.events.on_entity_damaged, events.on_entity_damaged, { + { filter = "name", name = creative_mode_defines.names.entities.creative_wall }, +}) + -- Other events. local events_except_on_tick = {} for _, event in pairs(defines.events) do @@ -63,6 +70,7 @@ for _, event in pairs(defines.events) do and event ~= defines.events.on_player_main_inventory_changed and event ~= defines.events.on_entity_logistic_slot_changed and event ~= defines.events.on_player_trash_inventory_changed + and event ~= defines.events.on_entity_damaged then table.insert(events_except_on_tick, event) end diff --git a/defines.lua b/defines.lua index 25fe117..2cfd1ee 100644 --- a/defines.lua +++ b/defines.lua @@ -142,6 +142,7 @@ creative_mode_defines.names.entities = { alien_attractor_proxy_medium = creative_mode_defines.name_prefix .. "alien-attractor-proxy-medium", alien_attractor_proxy_large = creative_mode_defines.name_prefix .. "alien-attractor-proxy-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", + creative_wall = creative_mode_defines.name_prefix .. "creative-wall", } -- Technology names creative_mode_defines.names.technology = { @@ -209,6 +210,7 @@ creative_mode_defines.names.items = { alien_attractor_medium = creative_mode_defines.name_prefix .. "alien-attractor-medium", alien_attractor_large = creative_mode_defines.name_prefix .. "alien-attractor-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", + creative_wall = creative_mode_defines.name_prefix .. "creative-wall", super_speed_module = creative_mode_defines.name_prefix .. "super-speed-module", super_effectivity_module = creative_mode_defines.name_prefix .. "super-effectivity-module", super_productivity_module = creative_mode_defines.name_prefix .. "super-productivity-module", @@ -294,6 +296,7 @@ creative_mode_defines.names.recipes = { alien_attractor_medium = creative_mode_defines.name_prefix .. "alien-attractor-medium", alien_attractor_large = creative_mode_defines.name_prefix .. "alien-attractor-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", + creative_wall = creative_mode_defines.name_prefix .. "creative-wall", super_speed_module = creative_mode_defines.name_prefix .. "super-speed-module", super_effectivity_module = creative_mode_defines.name_prefix .. "super-effectivity-module", super_productivity_module = creative_mode_defines.name_prefix .. "super-productivity-module", diff --git a/locale/en/base.cfg b/locale/en/base.cfg index 6b4cf0a..6160ca2 100644 --- a/locale/en/base.cfg +++ b/locale/en/base.cfg @@ -48,6 +48,7 @@ creative-mod_alien-attractor-proxy-small=Small alien attractor creative-mod_alien-attractor-proxy-medium=Medium alien attractor creative-mod_alien-attractor-proxy-large=Large alien attractor creative-mod_super-beacon=Super beacon +creative-mod_creative-wall=Creative wall creative-mode_enemy-object=__1__ (Creative Mode) creative-mode_infinite-resource=__1__ (Infinite) @@ -99,6 +100,7 @@ creative-mod_passive-energy-void=Constantly absorbs exceeded energy from the net creative-mod_super-radar=Keeps the large area around it to be always revealed. Doesn't really need energy despite the warning signal.\n(Placing it before the sectors are generated may interrupt the generation process and cause the whole map to be blacked out temporarily.) creative-mod_super-radar-2=Keeps the extremely large area around it to be always revealed. Doesn't really need energy despite the warning signal.\n(Placing it before the sectors are generated may interrupt the generation process, causing lag and the whole map to be blacked out temporarily.\nUsually it takes a few minutes to generate sections for such a large area.) creative-mod_super-beacon=Super large effective area. Also accepts productivity modules. Doesn't really need energy despite the warning signal. +creative-mod_creative-wall=An indestructible wall that cannot be damaged or destroyed. Works on any surface, including space platforms. [technology-name] creative-mod_void-technology=Void Technology diff --git a/prototypes/entity.lua b/prototypes/entity.lua index f9efb00..63167a2 100644 --- a/prototypes/entity.lua +++ b/prototypes/entity.lua @@ -2898,6 +2898,52 @@ data:extend({ }, }) +-- Creative wall: a clone of the base stone-wall. It stays a normal *destructible* target so +-- biters path to it and asteroids hit it, but it is healed back to full on every hit by the +-- on_entity_damaged handler in scripts/events.lua, so it can never be destroyed. (Setting +-- destructible = false was rejected: it removes the entity from the targeting system entirely, +-- so nothing ever attacks it.) The very high max_health is defense-in-depth so no single hit +-- one-shots it before the heal fires. It is a general creative building block, usable on any +-- surface (including space platforms via the item's "spawnable" flag). Not Space-Age gated - +-- it has no Space Age data dependency, so it loads and is craftable everywhere. +local creative_wall = table.deepcopy(data.raw["wall"]["stone-wall"]) +creative_wall.name = creative_mode_defines.names.entities.creative_wall +creative_wall.flags = { "placeable-player", "player-creation" } +creative_wall.minable = { + mining_time = 0.2, + result = creative_mode_defines.names.items.creative_wall, +} +creative_wall.fast_replaceable_group = nil +creative_wall.se_allow_in_space = true +creative_wall.max_health = 1000000 +-- Tint the wall red to match the mod's other creative entities. Walls use a deeply nested +-- WallPictures structure, so recursively tint every non-shadow sprite layer. +local creative_entity_tint = { r = 1, g = 0.3, b = 0.3 } +local function apply_creative_tint(sprite) + if type(sprite) ~= "table" then + return + end + if sprite.layers then + for _, layer in pairs(sprite.layers) do + apply_creative_tint(layer) + end + return + end + if sprite.filename then + -- Leave shadows untinted; only tint the visible sprite layers. + if not sprite.draw_as_shadow then + sprite.tint = table.deepcopy(creative_entity_tint) + sprite.apply_runtime_tint = false + end + return + end + for _, value in pairs(sprite) do + apply_creative_tint(value) + end +end +apply_creative_tint(creative_wall.pictures) +data:extend({ creative_wall }) + -- Vanilla infinity chest already exists but has no description, so we add one. data.raw["infinity-container"]["infinity-chest"].localised_description = { "entity-description.infinity-chest" } -- It also needs it's gui mode set to match our setting. diff --git a/prototypes/item.lua b/prototypes/item.lua index 4006a86..89cbf6d 100644 --- a/prototypes/item.lua +++ b/prototypes/item.lua @@ -741,6 +741,30 @@ data:extend({ place_result = creative_mode_defines.names.entities.super_beacon, stack_size = 50, }, + { + -- Creative wall + type = "item", + name = creative_mode_defines.names.items.creative_wall, + icon_size = 64, + -- Tinted red to match the mod's other creative entities. + icons = { + { + icon = "__base__/graphics/icons/wall.png", + tint = { + r = 1, + g = 0.3, + b = 0.3, + }, + }, + }, + hidden = hidden, + subgroup = creative_mode_defines.names.item_subgroups.advanced, + order = "g", + place_result = creative_mode_defines.names.entities.creative_wall, + stack_size = 50, + -- "spawnable" lets the remote controller hold the item, so it can be placed in remote view (e.g. on space platforms). + flags = { "spawnable" }, + }, { -- Super speed module. type = "module", diff --git a/prototypes/recipe.lua b/prototypes/recipe.lua index 29b47c1..54a970e 100644 --- a/prototypes/recipe.lua +++ b/prototypes/recipe.lua @@ -459,6 +459,14 @@ data:extend({ results = { { type = "item", name = creative_mode_defines.names.items.super_beacon, amount = 1 } }, enabled = false, }, + { + -- Creative wall + type = "recipe", + name = creative_mode_defines.names.recipes.creative_wall, + ingredients = {}, + results = { { type = "item", name = creative_mode_defines.names.items.creative_wall, amount = 1 } }, + enabled = false, + }, { -- Super speed module type = "recipe", diff --git a/scripts/events.lua b/scripts/events.lua index e20804f..dc60434 100644 --- a/scripts/events.lua +++ b/scripts/events.lua @@ -1426,3 +1426,18 @@ end function events.on_player_trash_inventory_changed(data) handle_player_trash_inventory_changed(data) end + +-- The mod's single on_entity_damaged handler. It is registered in control.lua with combined +-- event filters (the creative wall's name, plus - once Phase 3 lands - impact damage) so it only +-- fires for the cases we care about. Branches: +-- * Creative wall: healed back to full on every hit so it can never be destroyed while still +-- being a normal attack target. Ungated - works on vanilla. +function events.on_entity_damaged(event) + local entity = event.entity + if not entity.valid then + return + end + if entity.name == creative_mode_defines.names.entities.creative_wall then + entity.health = entity.max_health + end +end diff --git a/verify.py b/verify.py index 3fe7f65..62ce7f0 100644 --- a/verify.py +++ b/verify.py @@ -402,6 +402,19 @@ def cmd_behavior(args: argparse.Namespace) -> int: "1", "create_planet_surface_no_duplicate", ), + # creative_wall_indestructible: place the creative wall, then deal a large damage hit. + # The wall stays a normal *destructible* target, but the mod's on_entity_damaged handler + # heals it back to full, so it survives and ends at max_health. (Damage is well below + # max_health, matching how the wall actually survives in play: it heals every hit.) + _assert_rcon( + sb, + '/c local surf = game.surfaces["nauvis"] ' + 'local e = surf.create_entity{name="creative-mod_creative-wall", position={200, 200}, force="player", raise_built=true} ' + 'e.damage(5000, game.forces.player, "impact") ' + "rcon.print(tostring(e.valid and e.destructible == true and e.health == e.max_health))", + "true", + "creative_wall_indestructible", + ), ] finally: _terminate_server(server) @@ -422,6 +435,7 @@ def cmd_behavior(args: argparse.Namespace) -> int: "create_planet_surface_exists", "create_planet_surface_noop", "create_planet_surface_no_duplicate", + "creative_wall_indestructible", ), results, ) From fff94c272a5e4c8b25cf1bfd98a9fa1a88de692a Mon Sep 17 00:00:00 2001 From: jodli Date: Sun, 28 Jun 2026 23:34:42 +0200 Subject: [PATCH 2/4] feat: add self-fueling creative thruster (Space Age) A clone of the vanilla thruster that keeps the normal performance curve but has its fuel + oxidizer topped off every tick by script, so a space platform travels at vanilla speed with no fuel chain to supply. Defined only when Space Age is present (its fluids are space-age-only); the entity is cloned in data-final-fixes where the vanilla thruster prototype is guaranteed to exist. The body and item icon are tinted red to match the mod's other creative (spawn/source) entities; exhaust flames/glow are left natural. --- .luacheckrc | 1 + changelog.txt | 1 + control.lua | 1 + data-final-fixes.lua | 63 +++++++++++++++++++++++++++++++++++ defines.lua | 3 ++ locale/en/base.cfg | 2 ++ prototypes/item.lua | 31 +++++++++++++++++ prototypes/recipe.lua | 15 +++++++++ scripts/creative-thruster.lua | 35 +++++++++++++++++++ scripts/events.lua | 1 + scripts/global-util.lua | 6 ++++ verify.py | 30 +++++++++++++++++ 12 files changed, 189 insertions(+) create mode 100644 scripts/creative-thruster.lua diff --git a/.luacheckrc b/.luacheckrc index 2ac2aac..7da9228 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -105,6 +105,7 @@ stds.creative_mod = { "creative_lab", "creative_mode_defines", "creative_provider_chest", + "creative_thruster", "duplicating_cargo_wagon", "duplicating_chest", "duplicating_chest_util", diff --git a/changelog.txt b/changelog.txt index d92a214..6812e4f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -6,6 +6,7 @@ Date: ???? - Add space-platform creation to the Surface tab (Space Age only). Admins can type a name and pick the planet to orbit; the platform comes up with a hub already placed and its surface immediately appears in the Surface Cheats target list. - Add planet-surface creation to the Surface tab (Space Age only). Admins can pick a planet and create its surface; if the surface already exists the action is a harmless no-op, and any newly created surface immediately appears in the Surface Cheats target list. - Add a creative wall: an indestructible red wall. Enemies still target and attack it, but it instantly heals back to full and can never be destroyed. It works on any surface, including space platforms, and is enabled by the normal Creative tools' recipe sweep. + - Add a creative thruster (Space Age only): a thruster that refuels itself every tick, keeping a space platform travelling at vanilla speed with no fuel or oxidizer chain to supply. Place it on a platform, set a route, and it travels with no fuel management. Changes: - Move the Surface Cheats menu from the Cheats tab into the Surface tab, grouping it with the surface-creation tools. Bugfixes: diff --git a/control.lua b/control.lua index 567a842..df6f9f6 100644 --- a/control.lua +++ b/control.lua @@ -6,6 +6,7 @@ require("scripts.cheats") require("scripts.configurable-super-boiler") require("scripts.creative-cargo-wagon") require("scripts.creative-chest-util") +require("scripts.creative-thruster") require("scripts.creative-lab") require("scripts.void-lab") require("scripts.duplicating-cargo-wagon") diff --git a/data-final-fixes.lua b/data-final-fixes.lua index 2c93c14..bd217e9 100644 --- a/data-final-fixes.lua +++ b/data-final-fixes.lua @@ -321,3 +321,66 @@ for _, name in pairs(finite_resource_names) do data:extend({ resource }) end + +-------------------------------------------------------------- + +-- Creative thruster: a clone of the vanilla Space Age thruster that keeps the normal performance +-- curve (so it produces real thrust at vanilla speed). Its fuel + oxidizer fluidboxes are topped +-- off every tick by scripts/creative-thruster.lua, so the platform travels with no fuel chain to +-- manage. Defined only when Space Age is present (its filtered fluidboxes reference the +-- thruster-fuel / thruster-oxidizer fluids, which only exist in the space-age mod). Done in +-- data-final-fixes so the vanilla "thruster" prototype is guaranteed to exist before we clone it. +if mods["space-age"] and data.raw["thruster"] and data.raw["thruster"]["thruster"] then + local creative_thruster = util.table.deepcopy(data.raw["thruster"]["thruster"]) + creative_thruster.name = creative_mode_defines.names.entities.creative_thruster + -- Keep the vanilla thruster's entity flags. ("spawnable" is an *item* flag and lives on the + -- creative thruster item, letting the remote controller hold it for placement in remote view.) + creative_thruster.flags = { + "placeable-neutral", + "placeable-player", + "player-creation", + "not-rotatable", + } + creative_thruster.minable = { + mining_time = 0.1, + result = creative_mode_defines.names.items.creative_thruster, + } + creative_thruster.fast_replaceable_group = nil + + -- Tint the thruster body red to match the mod's other creative (spawn/source) entities, the same + -- convention used by the creative wall. Only the static body plates (animation + integration + -- patch) are tinted; shadow, glow and additive-blend layers (the exhaust flames) are left natural + -- so the effect still reads correctly. + local creative_entity_tint = { r = 1, g = 0.3, b = 0.3 } + local function apply_creative_tint(sprite) + if type(sprite) ~= "table" then + return + end + if sprite.layers then + for _, layer in pairs(sprite.layers) do + apply_creative_tint(layer) + end + return + end + if sprite.filename then + local is_effect = sprite.draw_as_shadow + or sprite.draw_as_glow + or sprite.draw_as_light + or (sprite.blend_mode ~= nil and sprite.blend_mode ~= "normal") + if not is_effect then + sprite.tint = util.table.deepcopy(creative_entity_tint) + sprite.apply_runtime_tint = false + end + return + end + for _, value in pairs(sprite) do + apply_creative_tint(value) + end + end + if creative_thruster.graphics_set then + apply_creative_tint(creative_thruster.graphics_set.animation) + apply_creative_tint(creative_thruster.graphics_set.integration_patch) + end + + data:extend({ creative_thruster }) +end diff --git a/defines.lua b/defines.lua index 2cfd1ee..f02f8e4 100644 --- a/defines.lua +++ b/defines.lua @@ -143,6 +143,7 @@ creative_mode_defines.names.entities = { alien_attractor_proxy_large = creative_mode_defines.name_prefix .. "alien-attractor-proxy-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", creative_wall = creative_mode_defines.name_prefix .. "creative-wall", + creative_thruster = creative_mode_defines.name_prefix .. "creative-thruster", } -- Technology names creative_mode_defines.names.technology = { @@ -211,6 +212,7 @@ creative_mode_defines.names.items = { alien_attractor_large = creative_mode_defines.name_prefix .. "alien-attractor-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", creative_wall = creative_mode_defines.name_prefix .. "creative-wall", + creative_thruster = creative_mode_defines.name_prefix .. "creative-thruster", super_speed_module = creative_mode_defines.name_prefix .. "super-speed-module", super_effectivity_module = creative_mode_defines.name_prefix .. "super-effectivity-module", super_productivity_module = creative_mode_defines.name_prefix .. "super-productivity-module", @@ -297,6 +299,7 @@ creative_mode_defines.names.recipes = { alien_attractor_large = creative_mode_defines.name_prefix .. "alien-attractor-large", super_beacon = creative_mode_defines.name_prefix .. "super-beacon", creative_wall = creative_mode_defines.name_prefix .. "creative-wall", + creative_thruster = creative_mode_defines.name_prefix .. "creative-thruster", super_speed_module = creative_mode_defines.name_prefix .. "super-speed-module", super_effectivity_module = creative_mode_defines.name_prefix .. "super-effectivity-module", super_productivity_module = creative_mode_defines.name_prefix .. "super-productivity-module", diff --git a/locale/en/base.cfg b/locale/en/base.cfg index 6160ca2..5219b53 100644 --- a/locale/en/base.cfg +++ b/locale/en/base.cfg @@ -49,6 +49,7 @@ creative-mod_alien-attractor-proxy-medium=Medium alien attractor creative-mod_alien-attractor-proxy-large=Large alien attractor creative-mod_super-beacon=Super beacon creative-mod_creative-wall=Creative wall +creative-mod_creative-thruster=Creative thruster creative-mode_enemy-object=__1__ (Creative Mode) creative-mode_infinite-resource=__1__ (Infinite) @@ -101,6 +102,7 @@ creative-mod_super-radar=Keeps the large area around it to be always revealed. D creative-mod_super-radar-2=Keeps the extremely large area around it to be always revealed. Doesn't really need energy despite the warning signal.\n(Placing it before the sectors are generated may interrupt the generation process, causing lag and the whole map to be blacked out temporarily.\nUsually it takes a few minutes to generate sections for such a large area.) creative-mod_super-beacon=Super large effective area. Also accepts productivity modules. Doesn't really need energy despite the warning signal. creative-mod_creative-wall=An indestructible wall that cannot be damaged or destroyed. Works on any surface, including space platforms. +creative-mod_creative-thruster=A thruster that refuels itself every tick, keeping a space platform travelling at vanilla speed with no fuel or oxidizer chain to supply. [technology-name] creative-mod_void-technology=Void Technology diff --git a/prototypes/item.lua b/prototypes/item.lua index 89cbf6d..37c3dce 100644 --- a/prototypes/item.lua +++ b/prototypes/item.lua @@ -890,3 +890,34 @@ data:extend({ stack_size = 50, }, }) + +-- Creative thruster item. Defined only when Space Age is present, since its place_result entity +-- (the creative thruster) only exists with Space Age. +if mods["space-age"] then + data:extend({ + { + -- Creative thruster + type = "item", + name = creative_mode_defines.names.items.creative_thruster, + icon_size = 64, + -- Tinted red to match the mod's other creative entities. + icons = { + { + icon = "__space-age__/graphics/icons/thruster.png", + tint = { + r = 1, + g = 0.3, + b = 0.3, + }, + }, + }, + hidden = hidden, + subgroup = creative_mode_defines.names.item_subgroups.advanced, + order = "h", + place_result = creative_mode_defines.names.entities.creative_thruster, + stack_size = 50, + -- "spawnable" lets the remote controller hold the item, so it can be placed in remote view on space platforms. + flags = { "spawnable" }, + }, + }) +end diff --git a/prototypes/recipe.lua b/prototypes/recipe.lua index 54a970e..afed19b 100644 --- a/prototypes/recipe.lua +++ b/prototypes/recipe.lua @@ -552,3 +552,18 @@ data:extend({ enabled = false, }, }) + +-- Creative thruster recipe. Defined only when Space Age is present, since its result item only +-- exists with Space Age. It is auto-enabled at runtime by the creative_tools_recipes sweep. +if mods["space-age"] then + data:extend({ + { + -- Creative thruster + type = "recipe", + name = creative_mode_defines.names.recipes.creative_thruster, + ingredients = {}, + results = { { type = "item", name = creative_mode_defines.names.items.creative_thruster, amount = 1 } }, + enabled = false, + }, + }) +end diff --git a/scripts/creative-thruster.lua b/scripts/creative-thruster.lua new file mode 100644 index 0000000..55e5360 --- /dev/null +++ b/scripts/creative-thruster.lua @@ -0,0 +1,35 @@ +-- This file contains variables or functions that are related to the Creative Thruster in this mod. +if not creative_thruster then + creative_thruster = {} +end + +-- The creative thruster is cloned from the vanilla Space Age thruster, which has exactly two +-- filtered fluidboxes: fuel (thruster-fuel) and oxidizer (thruster-oxidizer), each with a volume +-- of 1000. The thruster entity does not expose a writable `fluidbox` property, so we top the two +-- fluids up via insert_fluid every tick (the engine clamps inserts at the per-fluid capacity). +local thruster_fluids = { + { name = "thruster-fuel", capacity = 1000 }, + { name = "thruster-oxidizer", capacity = 1000 }, +} + +-- Processes the table of creative_thruster in storage. +function creative_thruster.tick() + -- Loop through the table of creative thrusters and top off fuel + oxidizer to capacity, so the + -- thruster always has fuel and the platform travels with no fuel chain to supply. + -- Iterate backwards so table.remove of invalid entries doesn't skip elements. + for index = #storage.creative_mode.creative_thruster, 1, -1 do + local entity = storage.creative_mode.creative_thruster[index] + if entity.valid then + if not entity.to_be_deconstructed() then + for _, fluid in ipairs(thruster_fluids) do + local missing = fluid.capacity - entity.get_fluid_count(fluid.name) + if missing > 0 then + entity.insert_fluid({ name = fluid.name, amount = missing }) + end + end + end + else + table.remove(storage.creative_mode.creative_thruster, index) + end + end +end diff --git a/scripts/events.lua b/scripts/events.lua index dc60434..f79cea3 100644 --- a/scripts/events.lua +++ b/scripts/events.lua @@ -321,6 +321,7 @@ function events.on_tick() void_cargo_wagon.tick() fluid_void.tick() super_boiler.tick() + creative_thruster.tick() super_cooler.tick() configurable_super_boiler.tick() item_source.tick() diff --git a/scripts/global-util.lua b/scripts/global-util.lua index c05d448..ca70802 100644 --- a/scripts/global-util.lua +++ b/scripts/global-util.lua @@ -78,6 +78,9 @@ function global_util.initialize_or_update_global() if not storage.creative_mode.super_boiler then storage.creative_mode.super_boiler = {} end + if not storage.creative_mode.creative_thruster then + storage.creative_mode.creative_thruster = {} + end if not storage.creative_mode.super_cooler then storage.creative_mode.super_cooler = {} end @@ -712,6 +715,9 @@ local register_entity_look_up_functions = { [creative_mode_defines.names.entities.super_boiler] = function(entity) table.insert(storage.creative_mode.super_boiler, entity) end, + [creative_mode_defines.names.entities.creative_thruster] = function(entity) + table.insert(storage.creative_mode.creative_thruster, entity) + end, [creative_mode_defines.names.entities.super_cooler] = function(entity) table.insert(storage.creative_mode.super_cooler, entity) end, diff --git a/verify.py b/verify.py index 62ce7f0..0cb55f5 100644 --- a/verify.py +++ b/verify.py @@ -415,7 +415,35 @@ def cmd_behavior(args: argparse.Namespace) -> int: "true", "creative_wall_indestructible", ), + # creative_thruster_placed: place the creative thruster on the platform surface, with + # raise_built=true so the mod registers it into the tick loop. Stash the entity globally + # so a later command (after the server has ticked) can read its fluidboxes. + _assert_rcon( + sb, + '/c local s = nil for _, surf in pairs(game.surfaces) do if surf.platform and surf.platform.name == "cm_platform" then s = surf end end ' + 'local e = s.create_entity{name="creative-mod_creative-thruster", position=s.platform.hub.position, force="player", raise_built=true} ' + "storage = storage or {} storage.cm_verify_thruster = e " + "rcon.print(tostring(e ~= nil and e.valid))", + "true", + "creative_thruster_placed", + ), ] + # Let the server tick so the per-tick refill runs on the just-placed thruster. + time.sleep(1.0) + results.append( + # creative_thruster_refuels: the thruster's fuel + oxidizer start empty; the per-tick + # refill tops both up to capacity (1000 each), proving the platform can travel with no + # player-supplied fuel chain. + _assert_rcon( + sb, + "/c local e = storage.cm_verify_thruster " + "if not (e and e.valid) then rcon.print('no-entity') return end " + "rcon.print(tostring(e.get_fluid_count('thruster-fuel') >= 1000 " + "and e.get_fluid_count('thruster-oxidizer') >= 1000))", + "true", + "creative_thruster_refuels", + ) + ) finally: _terminate_server(server) @@ -436,6 +464,8 @@ def cmd_behavior(args: argparse.Namespace) -> int: "create_planet_surface_noop", "create_planet_surface_no_duplicate", "creative_wall_indestructible", + "creative_thruster_placed", + "creative_thruster_refuels", ), results, ) From fd881af48ffafb76a761cb61d42867027c4e3dcd Mon Sep 17 00:00:00 2001 From: jodli Date: Sun, 28 Jun 2026 23:50:17 +0200 Subject: [PATCH 3/4] fix: drop patchy thruster body tint, mark creative thruster by red icon only The thruster's animated body doesn't take a flat tint cleanly (only the integration patch picked it up, leaving a half-red look). The creative variant is now distinguished solely by its red item icon. --- data-final-fixes.lua | 40 ++++------------------------------------ 1 file changed, 4 insertions(+), 36 deletions(-) diff --git a/data-final-fixes.lua b/data-final-fixes.lua index bd217e9..ed420a2 100644 --- a/data-final-fixes.lua +++ b/data-final-fixes.lua @@ -346,41 +346,9 @@ if mods["space-age"] and data.raw["thruster"] and data.raw["thruster"]["thruster result = creative_mode_defines.names.items.creative_thruster, } creative_thruster.fast_replaceable_group = nil - - -- Tint the thruster body red to match the mod's other creative (spawn/source) entities, the same - -- convention used by the creative wall. Only the static body plates (animation + integration - -- patch) are tinted; shadow, glow and additive-blend layers (the exhaust flames) are left natural - -- so the effect still reads correctly. - local creative_entity_tint = { r = 1, g = 0.3, b = 0.3 } - local function apply_creative_tint(sprite) - if type(sprite) ~= "table" then - return - end - if sprite.layers then - for _, layer in pairs(sprite.layers) do - apply_creative_tint(layer) - end - return - end - if sprite.filename then - local is_effect = sprite.draw_as_shadow - or sprite.draw_as_glow - or sprite.draw_as_light - or (sprite.blend_mode ~= nil and sprite.blend_mode ~= "normal") - if not is_effect then - sprite.tint = util.table.deepcopy(creative_entity_tint) - sprite.apply_runtime_tint = false - end - return - end - for _, value in pairs(sprite) do - apply_creative_tint(value) - end - end - if creative_thruster.graphics_set then - apply_creative_tint(creative_thruster.graphics_set.animation) - apply_creative_tint(creative_thruster.graphics_set.integration_patch) - end - + -- The placed thruster keeps its vanilla graphics: its detailed animated body doesn't take a flat + -- tint cleanly (only the integration patch picked it up, leaving a patchy half-red look). The + -- creative variant is instead marked by its red item icon (see prototypes/item.lua), matching the + -- convention used for the mod's other creative entities. data:extend({ creative_thruster }) end From adb498c20a62c727db27539236af2913dd94c568 Mon Sep 17 00:00:00 2001 From: jodli Date: Mon, 29 Jun 2026 00:14:50 +0200 Subject: [PATCH 4/4] feat: add global asteroid spawning rate cheat (Space Age) Add an 'Asteroid spawning rate' numeric input to the Game Settings cheats submenu, exposing game.map_settings.asteroids.spawning_rate directly (a float multiplier, default 1; set 0 to stop all asteroid spawning). Modeled on the existing game_speed numeric cheat and clamped to >= 0. Applies game-wide with zero runtime cost - no event handler, no per-tick work. Gated to Space Age via get_player_can_access_function, so the row is hidden on vanilla. Replaces the originally planned per-surface on_entity_damaged asteroid-protection toggle, which playtesting showed was redundant (creative walls block asteroids fully; remove/kill all enemies clear them). --- changelog.txt | 1 + defines.lua | 5 +++++ locale/en/base.cfg | 3 +++ scripts/cheats.lua | 22 ++++++++++++++++++++++ scripts/gui-menu-cheats.lua | 12 ++++++++++++ verify.py | 19 +++++++++++++++++++ 6 files changed, 62 insertions(+) diff --git a/changelog.txt b/changelog.txt index 6812e4f..8710a54 100644 --- a/changelog.txt +++ b/changelog.txt @@ -7,6 +7,7 @@ Date: ???? - Add planet-surface creation to the Surface tab (Space Age only). Admins can pick a planet and create its surface; if the surface already exists the action is a harmless no-op, and any newly created surface immediately appears in the Surface Cheats target list. - Add a creative wall: an indestructible red wall. Enemies still target and attack it, but it instantly heals back to full and can never be destroyed. It works on any surface, including space platforms, and is enabled by the normal Creative tools' recipe sweep. - Add a creative thruster (Space Age only): a thruster that refuels itself every tick, keeping a space platform travelling at vanilla speed with no fuel or oxidizer chain to supply. Place it on a platform, set a route, and it travels with no fuel management. + - Add an "Asteroid spawning rate" input to the Game Settings cheats (Space Age only): a global multiplier for how often asteroids spawn around space platforms. Default is 1 (vanilla); set it to 0 to stop asteroids spawning entirely. Applies game-wide, not per surface. Changes: - Move the Surface Cheats menu from the Cheats tab into the Surface tab, grouping it with the surface-creation tools. Bugfixes: diff --git a/defines.lua b/defines.lua index f02f8e4..316f54d 100644 --- a/defines.lua +++ b/defines.lua @@ -686,6 +686,11 @@ creative_mode_defines.names.gui = { enemy_expansion_label = creative_mode_defines.name_prefix .. "enemy-expansion-label", enemy_expansion_on_button = creative_mode_defines.name_prefix .. "enemy-expansion-on-button", enemy_expansion_off_button = creative_mode_defines.name_prefix .. "enemy-expansion-off-button", + asteroid_spawning_rate_container = creative_mode_defines.name_prefix .. "asteroid-spawning-rate-container", + asteroid_spawning_rate_label = creative_mode_defines.name_prefix .. "asteroid-spawning-rate-label", + asteroid_spawning_rate_textfield = creative_mode_defines.name_prefix .. "asteroid-spawning-rate-textfield", + asteroid_spawning_rate_separator = creative_mode_defines.name_prefix .. "asteroid-spawning-rate-separator", + asteroid_spawning_rate_apply_button = creative_mode_defines.name_prefix .. "asteroid-spawning-rate-apply-button", enemy_expansion_min_cooldown_container = creative_mode_defines.name_prefix .. "enemy-expansion-min-cooldown-container", enemy_expansion_min_cooldown_label = creative_mode_defines.name_prefix .. "enemy-expansion-min-cooldown-label", diff --git a/locale/en/base.cfg b/locale/en/base.cfg index 5219b53..30f6255 100644 --- a/locale/en/base.cfg +++ b/locale/en/base.cfg @@ -314,6 +314,7 @@ creative-mode_enemy-expansion-enabled=__1__ has enabled enemy expansion. creative-mode_enemy-expansion-disabled=__1__ has disabled enemy expansion. creative-mode_enemy-expansion-min-cooldown-updated=__1__ has updated the enemies' minimum expansion cooldown to __2__. creative-mode_enemy-expansion-max-cooldown-updated=__1__ has updated the enemies' maximum expansion cooldown to __2__. +creative-mode_asteroid-spawning-rate-updated=__1__ has set the asteroid spawning rate to __2__. creative-mode_game-speed-updated=__1__ has updated game speed to __2__. creative-mode_build-active-enabled-by-admin=__1__ has enabled build options - active for you. @@ -551,6 +552,8 @@ creative-mode_enemy-expansion-min-cooldown=Expansion min CD [?] creative-mode_enemy-expansion-min-cooldown-tooltip=Minimum number of ticks before the enemies expand a base. Default is __1__.\nEffective only when enemy expansion is on. creative-mode_enemy-expansion-max-cooldown=Expansion max CD [?] creative-mode_enemy-expansion-max-cooldown-tooltip=Maximum number of ticks before the enemies expand a base. Default is __1__.\nEffective only when enemy expansion is on. +creative-mode_asteroid-spawning-rate=Asteroid spawning rate [?] +creative-mode_asteroid-spawning-rate-tooltip=Multiplier for how often asteroids spawn around space platforms. Default is __1__. Set to 0 to stop asteroids spawning entirely. Applies game-wide, not per surface. creative-mode_game-speed=Game speed [?] creative-mode_game-speed-tooltip=Speed to update the map. Default is __2__ (60 UPS). Minimum is __1__. diff --git a/scripts/cheats.lua b/scripts/cheats.lua index 1b03e41..3e74156 100644 --- a/scripts/cheats.lua +++ b/scripts/cheats.lua @@ -1919,6 +1919,28 @@ cheats.global_cheats_data = { end, get_player_can_access_function = nil, }, + asteroid_spawning_rate = { + is_default = false, + default_enable_value = 1, + default_disable_value = 1, + get_value_function = function(target) + return game.map_settings.asteroids.spawning_rate + end, + limit_value_before_apply_function = function(value) + -- Rate is a multiplier; clamp to a non-negative, safe range. 0 = no asteroids, 1 = vanilla. + return util.clamp(value, 0, 4294967296) + end, + apply_to_target_function = function(target, value, source_player) + game.map_settings.asteroids.spawning_rate = value + return nil + end, + print_applied_by_admin_message_function = function(source_player, target, value) + game.print({ "message.creative-mode_asteroid-spawning-rate-updated", source_player.name, value }) + end, + get_player_can_access_function = function(player) + return script.feature_flags["space_travel"] == true + end, + }, game_speed = { is_default = false, default_enable_value = 1, diff --git a/scripts/gui-menu-cheats.lua b/scripts/gui-menu-cheats.lua index c116031..1da5161 100644 --- a/scripts/gui-menu-cheats.lua +++ b/scripts/gui-menu-cheats.lua @@ -1265,6 +1265,18 @@ local global_cheats_menu_gui_data = { off_button_name = creative_mode_defines.names.gui.enemy_expansion_off_button, off_button_caption = creative_mode_defines.names.gui_captions.off, }, + asteroid_spawning_rate = { + type = gui_menu_cheats.cheat_gui_type.numeric_apply, + cheat_data = cheats.global_cheats_data.cheats.asteroid_spawning_rate, + container_name = creative_mode_defines.names.gui.asteroid_spawning_rate_container, + label_name = creative_mode_defines.names.gui.asteroid_spawning_rate_label, + label_caption = { "gui.creative-mode_asteroid-spawning-rate" }, + label_tooltip = { "gui.creative-mode_asteroid-spawning-rate-tooltip", 1 }, + textfield_name = creative_mode_defines.names.gui.asteroid_spawning_rate_textfield, + separator_name = creative_mode_defines.names.gui.asteroid_spawning_rate_separator, + apply_button_name = creative_mode_defines.names.gui.asteroid_spawning_rate_apply_button, + apply_button_caption = creative_mode_defines.names.gui_captions.ok, + }, enemy_expansion_min_cooldown = { type = gui_menu_cheats.cheat_gui_type.numeric_apply, cheat_data = cheats.global_cheats_data.cheats.enemy_expansion_min_cooldown, diff --git a/verify.py b/verify.py index 0cb55f5..1186efc 100644 --- a/verify.py +++ b/verify.py @@ -427,6 +427,23 @@ def cmd_behavior(args: argparse.Namespace) -> int: "true", "creative_thruster_placed", ), + # asteroid_spawning_rate_set_zero: setting the rate to 0 stops new asteroids spawning + # anywhere. Mirrors the numeric cheat's apply_to_target_function(value=0). + _assert_rcon( + sb, + "/c game.map_settings.asteroids.spawning_rate = 0 " + "rcon.print(tostring(game.map_settings.asteroids.spawning_rate == 0))", + "true", + "asteroid_spawning_rate_set_zero", + ), + # asteroid_spawning_rate_restore: setting it back to the vanilla rate of 1 sticks. + _assert_rcon( + sb, + "/c game.map_settings.asteroids.spawning_rate = 1 " + "rcon.print(tostring(game.map_settings.asteroids.spawning_rate == 1))", + "true", + "asteroid_spawning_rate_restore", + ), ] # Let the server tick so the per-tick refill runs on the just-placed thruster. time.sleep(1.0) @@ -465,6 +482,8 @@ def cmd_behavior(args: argparse.Namespace) -> int: "create_planet_surface_no_duplicate", "creative_wall_indestructible", "creative_thruster_placed", + "asteroid_spawning_rate_set_zero", + "asteroid_spawning_rate_restore", "creative_thruster_refuels", ), results,