diff --git a/changelog.txt b/changelog.txt index 8710a54..8e84289 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ --------------------------------------------------------------------------------------------------- Version: 2.3.0 -Date: ???? +Date: 2026-06-29 Features: - 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. @@ -8,6 +8,8 @@ Date: ???? - 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. + - "Create Surface → Blank surface" now creates a genuinely flat, empty test surface (no resources, water, cliffs, trees, decoratives, or enemies; always day) with a floor dropdown: Lab tiles, Concrete, or Refined concrete. + - Add an "Ignore surface conditions" on/off Surface Cheat that lets planet-gated buildings be placed and planet-gated recipes crafted on the selected surface(s). 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 316f54d..ac46eb8 100644 --- a/defines.lua +++ b/defines.lua @@ -629,6 +629,10 @@ creative_mode_defines.names.gui = { peaceful_mode_label = creative_mode_defines.name_prefix .. "peaceful-mode-label", peaceful_mode_on_button = creative_mode_defines.name_prefix .. "peaceful-mode-on-button", peaceful_mode_off_button = creative_mode_defines.name_prefix .. "peaceful-mode-off-button", + ignore_surface_conditions_container = creative_mode_defines.name_prefix .. "ignore-surface-conditions-container", + ignore_surface_conditions_label = creative_mode_defines.name_prefix .. "ignore-surface-conditions-label", + ignore_surface_conditions_on_button = creative_mode_defines.name_prefix .. "ignore-surface-conditions-on-button", + ignore_surface_conditions_off_button = creative_mode_defines.name_prefix .. "ignore-surface-conditions-off-button", destroy_all_enemies_container = creative_mode_defines.name_prefix .. "destroy-all-enemies-container", destroy_all_enemies_label = creative_mode_defines.name_prefix .. "destroy-all-enemies-label", destroy_all_enemies_apply_button = creative_mode_defines.name_prefix .. "destroy-all-enemies-apply-button", @@ -1307,6 +1311,8 @@ creative_mode_defines.names.gui = { surface_blank_container = creative_mode_defines.name_prefix .. "surface-blank-container", surface_blank_name_label = creative_mode_defines.name_prefix .. "surface-blank-name-label", surface_blank_name_textfield = creative_mode_defines.name_prefix .. "surface-blank-name-textfield", + surface_blank_floor_label = creative_mode_defines.name_prefix .. "surface-blank-floor-label", + surface_blank_floor_drop_down = creative_mode_defines.name_prefix .. "surface-blank-floor-drop-down", surface_blank_create_button = creative_mode_defines.name_prefix .. "surface-blank-create-button", surface_platform_frame = creative_mode_defines.name_prefix .. "surface-platform-frame", surface_platform_container = creative_mode_defines.name_prefix .. "surface-platform-container", diff --git a/locale/en/base.cfg b/locale/en/base.cfg index 30f6255..5469ccb 100644 --- a/locale/en/base.cfg +++ b/locale/en/base.cfg @@ -295,6 +295,8 @@ creative-mode_daytime-unfrozen=__1__ has unfrozen the daytime on this surface. creative-mode_daytime-updated=__1__ has set the current daytime on this surface to __2__. creative-mode_peaceful-mode-enabled=__1__ has enabled peaceful mode on this surface. creative-mode_peaceful-mode-disabled=__1__ has disabled peaceful mode on this surface. +creative-mode_ignore-surface-conditions-enabled=__1__ has enabled ignore surface conditions on this surface. +creative-mode_ignore-surface-conditions-disabled=__1__ has disabled ignore surface conditions on this surface. creative-mode_destroy-all-enemies-applied=__1__ has killed all enemies on this surface. creative-mode_remove-all-enemies-applied=__1__ has removed all enemies on this surface. creative-mode_dont-generate-enemy-enabled=__1__ has enabled don't generate enemies on this surface. @@ -411,6 +413,10 @@ creative-mode_surface-creation-nav-planet=Planet surface creative-mode_surface-creation-blank-title=Create Blank Surface creative-mode_surface-creation-blank-name-label=Name creative-mode_surface-creation-blank-create-button=Create +creative-mode_surface-creation-blank-floor-label=Floor +creative-mode_surface-creation-blank-floor-lab=Lab tiles +creative-mode_surface-creation-blank-floor-concrete=Concrete +creative-mode_surface-creation-blank-floor-refined-concrete=Refined concrete creative-mode_surface-creation-platform-title=Create Space Platform creative-mode_surface-creation-platform-name-label=Name creative-mode_surface-creation-platform-planet-label=Orbit @@ -521,6 +527,8 @@ creative-mode_daytime=Daytime [?] creative-mode_daytime-tooltip=Sets the current time of day.\n0 = __1__\n0.5 = __2__\n1 = __1__ creative-mode_peaceful-mode=Peaceful mode [?] creative-mode_peaceful-mode-tooltip=Enemy will not attack first. You will have to kill all enemy units in order to refresh their status. +creative-mode_ignore-surface-conditions=Ignore surface conditions [?] +creative-mode_ignore-surface-conditions-tooltip=Disables planet/surface condition checks for building placement and recipe crafting on this surface (e.g. Foundry, Metallurgic science pack, Big mining drill, Turbo transport belt). Note: this only lifts condition gating - it does not add planet effects such as lightning, heating, or spoilage, and lightning rods still cannot be placed. creative-mode_destroy-all-enemies=Kill all enemies [?] creative-mode_destroy-all-enemies-tooltip=Kills all enemies, including both units and spawners, in all generated chunks, leaving loots on the ground.\nWarning: executing in a huge surface may result in temporary hang. creative-mode_remove-all-enemies=Remove all enemies [?] diff --git a/scripts/cheats.lua b/scripts/cheats.lua index 3e74156..17feaae 100644 --- a/scripts/cheats.lua +++ b/scripts/cheats.lua @@ -1576,6 +1576,30 @@ cheats.surface_cheats_data = { end, get_player_can_access_function = nil, }, + ignore_surface_conditions = { + is_default = false, + default_enable_value = true, + default_disable_value = false, + get_value_function = function(surface) + if surface then + return surface.ignore_surface_conditions + end + return nil + end, + limit_value_before_apply_function = nil, + apply_to_target_function = function(surface, enable, source_player) + surface.ignore_surface_conditions = enable + return nil + end, + print_applied_by_admin_message_function = function(source_player, surface, enable) + if enable then + surface.print({ "message.creative-mode_ignore-surface-conditions-enabled", source_player.name }) + else + surface.print({ "message.creative-mode_ignore-surface-conditions-disabled", source_player.name }) + end + end, + get_player_can_access_function = nil, + }, destroy_all_enemies = { is_default = false, default_enable_value = true, @@ -3078,6 +3102,20 @@ end function cheats.on_chunk_generated(event) local area = event.area local surface = event.surface + -- Flat test surface with a concrete / refined-concrete floor: lay the tile over the whole + -- chunk and clear any stray decoratives. (Lab-tile surfaces use the engine flag instead.) + local floor_tile = storage.creative_mode.surface_cheats.flat_test_floor[surface.index] + if floor_tile then + local tiles = {} + local x1, y1 = area.left_top.x, area.left_top.y + for x = x1, area.right_bottom.x - 1 do + for y = y1, area.right_bottom.y - 1 do + tiles[#tiles + 1] = { name = floor_tile, position = { x, y } } + end + end + surface.set_tiles(tiles) + surface.destroy_decoratives({ area = area }) + end -- Surface cheat - don't generate enemy. if storage.creative_mode.surface_cheats.dont_generate_enemy[surface.index] then -- Remove the enemies in the on_tick event. diff --git a/scripts/global-util.lua b/scripts/global-util.lua index ca70802..9f0a2ff 100644 --- a/scripts/global-util.lua +++ b/scripts/global-util.lua @@ -316,6 +316,12 @@ function global_util.initialize_or_update_global() if not storage.creative_mode.surface_cheats.dont_generate_enemy then storage.creative_mode.surface_cheats.dont_generate_enemy = {} end + -- Per-surface floor tile for flat test surfaces that need a chunk-generated floor + -- (concrete / refined-concrete). Keyed by surface.index → tile prototype name. + -- Lab-tile surfaces are handled by the engine flag and are not tracked here. + if not storage.creative_mode.surface_cheats.flat_test_floor then + storage.creative_mode.surface_cheats.flat_test_floor = {} + end -- Table of build options for each player. if not storage.creative_mode.build_options then diff --git a/scripts/gui-menu-cheats.lua b/scripts/gui-menu-cheats.lua index 1da5161..d2ec8ae 100644 --- a/scripts/gui-menu-cheats.lua +++ b/scripts/gui-menu-cheats.lua @@ -938,6 +938,18 @@ local surface_cheats_menu_gui_data = { off_button_name = creative_mode_defines.names.gui.peaceful_mode_off_button, off_button_caption = creative_mode_defines.names.gui_captions.off, }, + ignore_surface_conditions = { + type = gui_menu_cheats.cheat_gui_type.on_off, + cheat_data = cheats.surface_cheats_data.cheats.ignore_surface_conditions, + container_name = creative_mode_defines.names.gui.ignore_surface_conditions_container, + label_name = creative_mode_defines.names.gui.ignore_surface_conditions_label, + label_caption = { "gui.creative-mode_ignore-surface-conditions" }, + label_tooltip = { "gui.creative-mode_ignore-surface-conditions-tooltip" }, + on_button_name = creative_mode_defines.names.gui.ignore_surface_conditions_on_button, + on_button_caption = creative_mode_defines.names.gui_captions.on, + off_button_name = creative_mode_defines.names.gui.ignore_surface_conditions_off_button, + off_button_caption = creative_mode_defines.names.gui_captions.off, + }, destroy_all_enemies = { type = gui_menu_cheats.cheat_gui_type.apply, cheat_data = cheats.surface_cheats_data.cheats.destroy_all_enemies, diff --git a/scripts/gui-menu-surface.lua b/scripts/gui-menu-surface.lua index d5d99c0..009764b 100644 --- a/scripts/gui-menu-surface.lua +++ b/scripts/gui-menu-surface.lua @@ -60,6 +60,23 @@ local function build_blank_section_content(frame) name = creative_mode_defines.names.gui.surface_blank_name_textfield, style = creative_mode_defines.names.gui_styles.cheat_numeric_textfield, }) + blank_container.add({ + type = "label", + name = creative_mode_defines.names.gui.surface_blank_floor_label, + style = creative_mode_defines.names.gui_styles.cheat_name_label, + caption = { "gui.creative-mode_surface-creation-blank-floor-label" }, + }) + -- Floor options, captions ordered to match surface_creation.floor_options. + local floor_items = {} + for i, option in ipairs(surface_creation.floor_options) do + floor_items[i] = { "gui.creative-mode_surface-creation-blank-floor-" .. option.key } + end + blank_container.add({ + type = "drop-down", + name = creative_mode_defines.names.gui.surface_blank_floor_drop_down, + items = floor_items, + selected_index = 1, + }) frame.add({ type = "button", name = creative_mode_defines.names.gui.surface_blank_create_button, @@ -336,8 +353,11 @@ function gui_menu_surface.on_gui_click(element, element_name, player, button, al local blank_container = blank_frame[creative_mode_defines.names.gui.surface_blank_container] local textfield = blank_container[creative_mode_defines.names.gui.surface_blank_name_textfield] local name = textfield.text + local floor_drop_down = blank_container[creative_mode_defines.names.gui.surface_blank_floor_drop_down] + local floor_option = surface_creation.floor_options[floor_drop_down.selected_index] + local floor = floor_option and floor_option.key - local success, result = surface_creation.create_blank_surface(name) + local success, result = surface_creation.create_blank_surface(name, floor) if success then player.print({ "message.creative-mode_surface-creation-blank-success", result.name }) -- Clear the field for convenience. @@ -410,6 +430,7 @@ function gui_menu_surface.on_gui_selection_state_changed(element, element_name, if element_name == creative_mode_defines.names.gui.surface_platform_planet_drop_down or element_name == creative_mode_defines.names.gui.surface_planet_planet_drop_down + or element_name == creative_mode_defines.names.gui.surface_blank_floor_drop_down then -- The selected planet is read directly from the drop-down at create time, so nothing to -- record here. Consume the event so the chain stops. diff --git a/scripts/surface-creation.lua b/scripts/surface-creation.lua index bc73e51..149c0f3 100644 --- a/scripts/surface-creation.lua +++ b/scripts/surface-creation.lua @@ -5,10 +5,32 @@ end -- Creates a new blank surface with the given name. -- The name is trimmed; empty and duplicate names are rejected (game.create_surface errors on a duplicate name). +-- floor selects the floor tile: one of surface_creation.floor_options keys ("lab", "concrete", +-- "refined-concrete"); defaults to "lab" when nil/unknown. -- Returns: -- on success: true, surface -- on failure: false, localised-string error message -function surface_creation.create_blank_surface(name) + +-- Floor options offered at flat-surface creation. Order is the dropdown order. +-- tile == nil means "lab tiles" (engine flag, no chunk handler); otherwise tile is the +-- tile prototype name laid by the on_chunk_generated handler. +surface_creation.floor_options = { + { key = "lab", tile = nil }, + { key = "concrete", tile = "concrete" }, + { key = "refined-concrete", tile = "refined-concrete" }, +} + +-- Resolves a floor key to its floor_options entry, defaulting to the first (lab tiles). +local function resolve_floor(floor) + for _, option in ipairs(surface_creation.floor_options) do + if option.key == floor then + return option + end + end + return surface_creation.floor_options[1] +end + +function surface_creation.create_blank_surface(name, floor) -- Trim leading/trailing whitespace. local trimmed_name if type(name) == "string" then @@ -25,11 +47,34 @@ function surface_creation.create_blank_surface(name) return false, { "message.creative-mode_surface-creation-blank-duplicate-name", trimmed_name } end - local surface = game.create_surface(trimmed_name) + local floor_option = resolve_floor(floor) + + -- Flat MapGenSettings: disable every autoplace (resources/trees/decoratives/enemies), + -- force no enemies / peaceful, no starting area, and zero out cliffs + enemy bases. + local surface = game.create_surface(trimmed_name, { + default_enable_all_autoplace_controls = false, + no_enemies_mode = true, + peaceful_mode = true, + starting_area = "none", + property_expression_names = { + cliffiness = 0, + ["enemy-base-intensity"] = 0, + }, + }) if not surface then return false, { "message.creative-mode_surface-creation-blank-failed", trimmed_name } end + surface.always_day = true + + if floor_option.tile == nil then + -- Lab tiles: engine fills each new chunk; no chunk handler required. + surface.generate_with_lab_tiles = true + else + -- Concrete / refined-concrete: record so on_chunk_generated lays the tile + clears decoratives. + storage.creative_mode.surface_cheats.flat_test_floor[surface.index] = floor_option.tile + end + return true, surface end