diff --git a/.pi/skills/factorio-mod-dev/SKILL.md b/.pi/skills/factorio-mod-dev/SKILL.md index ba6cbf2..fb4e3c0 100644 --- a/.pi/skills/factorio-mod-dev/SKILL.md +++ b/.pi/skills/factorio-mod-dev/SKILL.md @@ -70,6 +70,20 @@ Output channels for the values you inspect: → See `DEBUGGING.md` (this skill folder) for debugging methodology and porting guide. → See `RELEASE.md` (this skill folder) for release checklist and GitHub Actions workflow reference. +## Changelog & versioning + +Every PR that changes user-facing behavior (a feature, bugfix, or change) **must +include a `changelog.txt` entry in the same PR** — it is part of the feature, not +a separate step. + +- Prepend the entry at the **top** of `changelog.txt` using the documented format, + under the right section (`Features:` / `Bugfixes:` / `Changes:`). Set the + `Version:` header to the next release version (pick the bump from the version + table in `RELEASE.md`: new user-facing feature → minor; bugfix → patch). +- **Do not bump `info.json` in the feature PR.** The `info.json` version bump and + the git tag happen together as a dedicated release step on `master` — see + `RELEASE.md`. This keeps feature branches free of version churn/conflicts. + ## Base game as a reference The full Factorio base mod ships with the install and is readable on disk: diff --git a/changelog.txt b/changelog.txt index d7b970a..6aafbaf 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,4 +1,9 @@ --------------------------------------------------------------------------------------------------- +Version: 2.2.0 +Date: 2026-06-27 + Features: + - Add cross-surface teleport. A new Teleport tab lets you teleport yourself to a safe position on any existing surface (Nauvis, other planets, or space platforms), both in god mode and with a character. Access is gated by a new "teleport" right (admin only / current surface only / free). +--------------------------------------------------------------------------------------------------- Version: 2.1.6 Date: 2026-06-25 Bugfixes: diff --git a/defines.lua b/defines.lua index 3dfe959..c62a59d 100644 --- a/defines.lua +++ b/defines.lua @@ -348,6 +348,7 @@ creative_mode_defines.names.gui = { personal_cheats_menu_button = creative_mode_defines.name_prefix .. "personal-cheats-menu-button", team_cheats_menu_button = creative_mode_defines.name_prefix .. "team-cheats-menu-button", surface_cheats_menu_button = creative_mode_defines.name_prefix .. "surface-cheats-menu-button", + teleport_menu_button = creative_mode_defines.name_prefix .. "teleport-menu-button", global_cheats_menu_button = creative_mode_defines.name_prefix .. "global-cheats-menu-button", personal_cheats_menu_frame = creative_mode_defines.name_prefix .. "personal-cheats-menu-frame", personal_cheats_outer_container = creative_mode_defines.name_prefix .. "personal-cheats-outer-container", @@ -593,6 +594,18 @@ creative_mode_defines.names.gui = { .. "surface-cheats-targets-select-all-button", surface_cheats_cheats_scroll_pane = creative_mode_defines.name_prefix .. "surface-cheats-cheats-scroll-pane", surface_cheats_cheats_container = creative_mode_defines.name_prefix .. "surface-cheats-cheats-container", + teleport_menu_frame = creative_mode_defines.name_prefix .. "teleport-menu-frame", + teleport_outer_container = creative_mode_defines.name_prefix .. "teleport-outer-container", + teleport_targets_scroll_pane = creative_mode_defines.name_prefix .. "teleport-targets-scroll-pane", + teleport_targets_container = creative_mode_defines.name_prefix .. "teleport-targets-container", + teleport_targets_inner_container = creative_mode_defines.name_prefix .. "teleport-targets-inner-container", + teleport_target_name_button_prefix = creative_mode_defines.name_prefix .. "teleport-target-name-button-", + teleport_targets_select_all_button = creative_mode_defines.name_prefix .. "teleport-targets-select-all-button", + teleport_cheats_scroll_pane = creative_mode_defines.name_prefix .. "teleport-cheats-scroll-pane", + teleport_cheats_container = creative_mode_defines.name_prefix .. "teleport-cheats-container", + teleport_to_surface_container = creative_mode_defines.name_prefix .. "teleport-to-surface-container", + teleport_to_surface_label = creative_mode_defines.name_prefix .. "teleport-to-surface-label", + teleport_to_surface_apply_button = creative_mode_defines.name_prefix .. "teleport-to-surface-apply-button", freeze_daytime_container = creative_mode_defines.name_prefix .. "freeze-daytime-container", freeze_daytime_label = creative_mode_defines.name_prefix .. "freeze-daytime-label", freeze_daytime_on_button = creative_mode_defines.name_prefix .. "freeze-daytime-on-button", @@ -1144,6 +1157,13 @@ creative_mode_defines.names.gui = { access_surface_cheat_current_surface_button = creative_mode_defines.name_prefix .. "access-surface-cheat-current-surface-button", access_surface_cheat_free_button = creative_mode_defines.name_prefix .. "access-surface-cheat-free-button", + access_teleport_container = creative_mode_defines.name_prefix .. "access-teleport-container", + access_teleport_label = creative_mode_defines.name_prefix .. "access-teleport-label", + access_teleport_inner_container = creative_mode_defines.name_prefix .. "access-teleport-inner-container", + access_teleport_admin_only_button = creative_mode_defines.name_prefix .. "access-teleport-admin-only-button", + access_teleport_current_surface_button = creative_mode_defines.name_prefix + .. "access-teleport-current-surface-button", + access_teleport_free_button = creative_mode_defines.name_prefix .. "access-teleport-free-button", access_global_cheat_container = creative_mode_defines.name_prefix .. "access-global-cheat-container", access_global_cheat_label = creative_mode_defines.name_prefix .. "access-global-cheat-label", access_global_cheat_inner_container = creative_mode_defines.name_prefix .. "access-global-cheat-inner-container", @@ -1403,6 +1423,7 @@ creative_mode_defines.match_patterns.gui = { .. "team%-cheats%-target%-name%-button%-(.+)", surface_cheats_target_name_button = creative_mode_defines.name_prefix_pattern .. "surface%-cheats%-target%-name%-button%-(.+)", + teleport_target_name_button = creative_mode_defines.name_prefix_pattern .. "teleport%-target%-name%-button%-(.+)", build_options_target_index_button = creative_mode_defines.name_prefix_pattern .. "build%-options%-target%-index%-button%-(%d+)", build_team_target_name_button = creative_mode_defines.name_prefix_pattern diff --git a/locale/en/base.cfg b/locale/en/base.cfg index a2fb388..f56c36e 100644 --- a/locale/en/base.cfg +++ b/locale/en/base.cfg @@ -326,6 +326,9 @@ creative-mode_access-right-team-cheat-free-all-teams=__1__ has changed the team creative-mode_access-right-surface-cheat-admin-only=__1__ has changed the surface cheats accessibility - only admins can access them. creative-mode_access-right-surface-cheat-free-own-surface=__1__ has changed the surface cheats accessibility - non-admin players can apply cheats on his/her current surface. creative-mode_access-right-surface-cheat-free-all-surfaces=__1__ has changed the surface cheats accessibility - everyone can apply cheats on any surface. +creative-mode_access-right-teleport-admin-only=__1__ has changed the teleport accessibility - only admins can teleport. +creative-mode_access-right-teleport-current-surface=__1__ has changed the teleport accessibility - non-admin players can teleport only on his/her current surface. +creative-mode_access-right-teleport-free=__1__ has changed the teleport accessibility - everyone can teleport to any surface. creative-mode_access-right-global-cheat-admin-only=__1__ has changed the game settings accessibility - only admins can access them. creative-mode_access-right-global-cheat-free=__1__ has changed the game settings accessibility - everyone can access them. creative-mode_access-right-build-options-admin-only=__1__ has changed the build options accessibility - only admins can access them. @@ -376,6 +379,9 @@ creative-mode_cheats=Cheats creative-mode_personal-cheats=Personal Cheats creative-mode_team-cheats=Team Cheats creative-mode_surface-cheats=Surface Cheats +creative-mode_teleport=Teleport +creative-mode_teleport-here=Teleport here [?] +creative-mode_teleport-here-tooltip=Teleports you to a safe position on the selected surface. Works in god mode and with a character. creative-mode_global-cheats=Game Settings creative-mode_build-options=Build Options creative-mode_magic-wand=Magic Wand @@ -678,6 +684,11 @@ creative-mode_access-right-surface-cheat-free-own-surface=Current surface only [ creative-mode_access-right-surface-cheat-free-own-surface-tooltip=Non-admin players can apply cheats on his/her current surface. creative-mode_access-right-surface-cheat-free-all-surfaces=Free [?] creative-mode_access-right-surface-cheat-free-all-surfaces-tooltip=Everyone can apply cheats on any surface. +creative-mode_access-right-teleport-admin-only-tooltip=Only admins can teleport. +creative-mode_access-right-teleport-current-surface=Current surface only [?] +creative-mode_access-right-teleport-current-surface-tooltip=Non-admin players can teleport only on his/her current surface. +creative-mode_access-right-teleport-free=Free [?] +creative-mode_access-right-teleport-free-tooltip=Everyone can teleport to any surface. creative-mode_access-right-global-cheat-admin-only-tooltip=Only admins can change settings for the current game. creative-mode_access-right-global-cheat-free=Free [?] creative-mode_access-right-global-cheat-free-tooltip=Everyone can change settings for the current game. diff --git a/locale/zh-CN/base.cfg b/locale/zh-CN/base.cfg index ceef2f1..d107611 100644 --- a/locale/zh-CN/base.cfg +++ b/locale/zh-CN/base.cfg @@ -326,6 +326,9 @@ creative-mode_access-right-team-cheat-free-all-teams=__1__ 已更改团队作弊 creative-mode_access-right-surface-cheat-admin-only=__1__ 已更改表面作弊功能的访问权限 - 仅管理员可访问。 creative-mode_access-right-surface-cheat-free-own-surface=__1__ 已更改表面作弊功能的访问权限 - 非管理员玩家可对自己当前表面应用作弊功能。 creative-mode_access-right-surface-cheat-free-all-surfaces=__1__ 已更改表面作弊功能的访问权限 - 所有人可对任何表面应用作弊功能。 +creative-mode_access-right-teleport-admin-only=__1__ 已更改传送的访问权限 - 仅管理员可传送。 +creative-mode_access-right-teleport-current-surface=__1__ 已更改传送的访问权限 - 非管理员玩家只能在自己当前表面传送。 +creative-mode_access-right-teleport-free=__1__ 已更改传送的访问权限 - 所有人可传送到任何表面。 creative-mode_access-right-global-cheat-admin-only=__1__ 已更改游戏设置的访问权限 - 仅管理员可访问。 creative-mode_access-right-global-cheat-free=__1__ 已更改游戏设置的访问权限 - 所有人可访问。 creative-mode_access-right-build-options-admin-only=__1__ 已更改建造选项的访问权限 - 仅管理员可访问。 @@ -378,6 +381,9 @@ creative-mode_cheats=作弊功能 creative-mode_personal-cheats=个人作弊功能 creative-mode_team-cheats=团队作弊功能 creative-mode_surface-cheats=表面作弊功能 +creative-mode_teleport=传送 +creative-mode_teleport-here=传送到此处 [?] +creative-mode_teleport-here-tooltip=将你传送到所选表面上的安全位置。在上帝模式和拥有角色时均可使用。 creative-mode_global-cheats=游戏设置 creative-mode_build-options=建造选项 creative-mode_magic-wand=魔法棒 @@ -681,6 +687,11 @@ creative-mode_access-right-surface-cheat-free-own-surface=仅当前表面 [?] creative-mode_access-right-surface-cheat-free-own-surface-tooltip=非管理员玩家可以对自己所在的表面应用作弊。 creative-mode_access-right-surface-cheat-free-all-surfaces=自由 [?] creative-mode_access-right-surface-cheat-free-all-surfaces-tooltip=每个人都可以对任何表面应用作弊。 +creative-mode_access-right-teleport-admin-only-tooltip=只有管理员可以传送。 +creative-mode_access-right-teleport-current-surface=仅当前表面 [?] +creative-mode_access-right-teleport-current-surface-tooltip=非管理员玩家只能在自己当前的表面传送。 +creative-mode_access-right-teleport-free=自由 [?] +creative-mode_access-right-teleport-free-tooltip=每个人都可以传送到任何表面。 creative-mode_access-right-global-cheat-admin-only-tooltip=只有管理员可以更改当前游戏的设置。 creative-mode_access-right-global-cheat-free=自由 [?] creative-mode_access-right-global-cheat-free-tooltip=每个人都可以更改当前游戏的设置。 diff --git a/locale/zh-TW/base.cfg b/locale/zh-TW/base.cfg index 6537122..1868963 100644 --- a/locale/zh-TW/base.cfg +++ b/locale/zh-TW/base.cfg @@ -297,6 +297,9 @@ creative-mode_access-right-team-cheat-free-all-teams=__1__已更改團隊作弊 creative-mode_access-right-surface-cheat-admin-only=__1__已更改空間作弊選項的存取限制──只有管理員可用。 creative-mode_access-right-surface-cheat-free-own-surface=__1__已更改空間作弊選項的存取限制──非管理員玩家可以在所在空間上應用任何空間作弊。 creative-mode_access-right-surface-cheat-free-all-surfaces=__1__已更改空間作弊選項的存取限制──所有玩家都可以在任何空間應用任何空間作弊。 +creative-mode_access-right-teleport-admin-only=__1__已更改傳送的存取限制──只有管理員可以傳送。 +creative-mode_access-right-teleport-current-surface=__1__已更改傳送的存取限制──非管理員玩家只能在自己目前的空間傳送。 +creative-mode_access-right-teleport-free=__1__已更改傳送的存取限制──所有玩家都可以傳送到任何空間。 creative-mode_access-right-global-cheat-admin-only=__1__已更改遊戲設定的存取限制──只有管理員可用。 creative-mode_access-right-global-cheat-free=__1__已更改遊戲設定的存取限制──所有玩家都可用。 creative-mode_access-right-build-options-admin-only=__1__已更改建造設定的存取限制──只有管理員可用。 @@ -347,6 +350,9 @@ creative-mode_cheats=作弊列表 creative-mode_personal-cheats=個人作弊選項 creative-mode_team-cheats=團隊作弊選項 creative-mode_surface-cheats=空間作弊選項 +creative-mode_teleport=傳送 +creative-mode_teleport-here=傳送到此處 [?] +creative-mode_teleport-here-tooltip=將你傳送到所選空間上的安全位置。在上帝模式和擁有角色時皆可使用。 creative-mode_global-cheats=遊戲設定 creative-mode_build-options=建造設定 creative-mode_magic-wand=魔法棒設定 @@ -643,6 +649,11 @@ creative-mode_access-right-surface-cheat-free-own-surface=限所在空間[?] creative-mode_access-right-surface-cheat-free-own-surface-tooltip=非管理員玩家可以在所在空間上應用任何空間作弊 creative-mode_access-right-surface-cheat-free-all-surfaces=自由[?] creative-mode_access-right-surface-cheat-free-all-surfaces-tooltip=所有玩家都可以在任何空間應用任何空間作弊 +creative-mode_access-right-teleport-admin-only-tooltip=只有管理員可以傳送 +creative-mode_access-right-teleport-current-surface=僅目前空間[?] +creative-mode_access-right-teleport-current-surface-tooltip=非管理員玩家只能在自己目前的空間傳送 +creative-mode_access-right-teleport-free=自由[?] +creative-mode_access-right-teleport-free-tooltip=所有玩家都可以傳送到任何空間 creative-mode_access-right-global-cheat-admin-only-tooltip=只有管理員可以更改此場遊戲的設定 creative-mode_access-right-global-cheat-free=自由[?] creative-mode_access-right-global-cheat-free-tooltip=全部玩家都可以更改此場遊戲的設定 diff --git a/scripts/cheats.lua b/scripts/cheats.lua index a49b19b..1ee8e2a 100644 --- a/scripts/cheats.lua +++ b/scripts/cheats.lua @@ -37,6 +37,26 @@ local function large_range_limit_value_before_apply_function(value) return util.clamp(value, -1, 4294967296) end +-- Computes a safe, non-colliding landing position for the given player on the given surface. +-- Follows the base-game pattern (pvp.lua): start from the force's spawn position, generate the +-- surrounding chunks so we don't land in ungenerated void, then search outward for a free spot. +local function compute_safe_position(player, surface) + -- Search radius for the non-colliding spot, in TILES. + local search_radius = 64 + local origin = player.force.get_spawn_position(surface) + -- Make sure the destination terrain exists before searching/teleporting. + -- NOTE: request_to_generate_chunks takes a radius in CHUNKS (1 chunk = 32 tiles), so we + -- derive a small chunk radius that covers the tile search radius (+1 for boundary slack). + -- Reusing the tile radius here would force-generate ~16k chunks synchronously and freeze the game. + local chunk_radius = math.ceil(search_radius / 32) + 1 + surface.request_to_generate_chunks(origin, chunk_radius) + surface.force_generate_chunk_requests() + -- Find a non-colliding tile within a finite radius (radius = 0 would be unlimited and could hang). + local position = surface.find_non_colliding_position("character", origin, search_radius, 2) + -- Fall back to the spawn origin if no free spot was found within the radius. + return position or origin +end + -- Applies all character-related personal cheats to the given player. Also updates GUI status about the cheat for all players, in case the given player is selected. local function apply_character_cheats_to_player(player) -- Cheat mode. @@ -147,6 +167,30 @@ local function apply_character_cheats_to_player(player) gui_menu_cheats.update_character_cheats_status_for_all_players_as_player_updated_his_status(player) end +-- Moves a player who is controlling a character to another surface. +-- The engine cannot teleport a character across surfaces, so we clone the body onto the +-- destination (a single clone call carries the full main inventory and equipment grid - +-- spike-confirmed), teleport the player, bind it to the clone, destroy the original body, +-- then restore cheat_mode (the engine resets it on controller change) and reapply the +-- character-dependent cheats. +local function move_player_with_character(player, target_surface, dest) + local old_char = player.character + -- Capture cheat_mode now; binding to a new character resets it. + local was_cheat = player.cheat_mode + -- Clone the body onto the destination surface (carries inventory + equipment grid). + local clone = old_char.clone({ position = dest, surface = target_surface }) + -- Teleport the player to the destination so the new character is on the same surface. + player.teleport(dest, target_surface) + if clone and clone.valid then + -- Bind the player to the clone and remove the original body. + player.character = clone + old_char.destroy() + end + -- Restore cheat_mode and character-dependent cheats after the controller change. + player.cheat_mode = was_cheat + apply_character_cheats_to_player(player) +end + -- Data about all personal cheats. cheats.personal_cheats_data = { -- Function for checking whether the target is self of the source player. @@ -1660,6 +1704,51 @@ cheats.surface_cheats_data = { }, } +-- Data about all teleport cheats. The target of each cheat is the destination surface. +cheats.teleport_cheats_data = { + check_is_self_function = function(source_player, surface) + -- Selecting the player's own surface still performs a (reposition) teleport, so it is never "self". + return false + end, + print_admin_failed_to_apply_to_single_target_message_function = function(source_player, target, reason) + -- No reason to fail so far. + end, + print_admin_failed_to_apply_to_multi_targets_message_function = function(source_player, fail_count, reason) + -- No reason to fail so far. + end, + print_enabled_all_by_admin_message_function = function(source_player, surface, enable) + -- No enable all so far. + end, + cheats = { + teleport_to_surface = { + is_default = false, + get_value_function = nil, + limit_value_before_apply_function = nil, + apply_to_target_function = function(target_surface, value, source_player) + if not (target_surface and target_surface.valid and source_player) then + return nil + end + local dest = compute_safe_position(source_player, target_surface) + if source_player.surface == target_surface then + -- Same-surface selection: reposition to a fresh safe spot on the current surface. + source_player.teleport(dest) + elseif source_player.character then + -- Player is controlling a character: bring the body along (clone protocol). + move_player_with_character(source_player, target_surface, dest) + else + -- God/spectator: clean single cross-surface teleport. + source_player.teleport(dest, target_surface) + end + return nil + end, + print_applied_by_admin_message_function = function(source_player, surface, value) + -- Teleport is a personal action; no surface broadcast. + end, + get_player_can_access_function = nil, + }, + }, +} + -- Data about all global cheats. cheats.global_cheats_data = { check_is_self_function = function(source_player, target) diff --git a/scripts/global-util.lua b/scripts/global-util.lua index 8b78f49..c05d448 100644 --- a/scripts/global-util.lua +++ b/scripts/global-util.lua @@ -164,6 +164,10 @@ function global_util.initialize_or_update_global() if storage.creative_mode.player_rights.access_surface_cheats == nil then storage.creative_mode.player_rights.access_surface_cheats = rights.default_access_surface_cheats_level end + -- Right for accessing the teleport menu. + if storage.creative_mode.player_rights.access_teleport == nil then + storage.creative_mode.player_rights.access_teleport = rights.default_access_teleport_level + end -- Right for enabling/disabling global cheats. if storage.creative_mode.player_rights.access_global_cheats == nil then storage.creative_mode.player_rights.access_global_cheats = rights.default_access_global_cheats_level diff --git a/scripts/gui-menu-admin.lua b/scripts/gui-menu-admin.lua index 2dde839..672be45 100644 --- a/scripts/gui-menu-admin.lua +++ b/scripts/gui-menu-admin.lua @@ -120,6 +120,43 @@ local access_rights_gui_data = { storage.creative_mode.player_rights.access_surface_cheats = value end, }, + teleport = { + is_overall = false, + container_name = creative_mode_defines.names.gui.access_teleport_container, + label_name = creative_mode_defines.names.gui.access_teleport_label, + label_caption = { "gui.creative-mode_teleport" }, + inner_container_name = creative_mode_defines.names.gui.access_teleport_inner_container, + access_right_code = rights.access_teleport_code, + options = { + admin_only = { + button_name = creative_mode_defines.names.gui.access_teleport_admin_only_button, + button_caption = { "gui.creative-mode_access-right-admin-only" }, + button_tooltip = { "gui.creative-mode_access-right-teleport-admin-only-tooltip" }, + value = rights.access_teleport_level.admin_only, + message_key = "message.creative-mode_access-right-teleport-admin-only", + }, + current_surface = { + button_name = creative_mode_defines.names.gui.access_teleport_current_surface_button, + button_caption = { "gui.creative-mode_access-right-teleport-current-surface" }, + button_tooltip = { "gui.creative-mode_access-right-teleport-current-surface-tooltip" }, + value = rights.access_teleport_level.current_surface_only, + message_key = "message.creative-mode_access-right-teleport-current-surface", + }, + free = { + button_name = creative_mode_defines.names.gui.access_teleport_free_button, + button_caption = { "gui.creative-mode_access-right-teleport-free" }, + button_tooltip = { "gui.creative-mode_access-right-teleport-free-tooltip" }, + value = rights.access_teleport_level.free, + message_key = "message.creative-mode_access-right-teleport-free", + }, + }, + get_value_function = function() + return storage.creative_mode.player_rights.access_teleport + end, + set_value_function = function(value) + storage.creative_mode.player_rights.access_teleport = value + end, + }, global_cheat = { is_overall = false, container_name = creative_mode_defines.names.gui.access_global_cheat_container, diff --git a/scripts/gui-menu-cheats.lua b/scripts/gui-menu-cheats.lua index 45e9d4e..ecce0c4 100644 --- a/scripts/gui-menu-cheats.lua +++ b/scripts/gui-menu-cheats.lua @@ -1063,6 +1063,116 @@ local surface_cheats_menu_gui_data = { end, } +-- GUI data about the whole teleport menu. Cloned from the surface cheats menu: it reuses the +-- surface target picker, but lists surfaces with friendly (planet/platform) labels and offers a +-- single "Teleport here" apply action whose target is the selected destination surface. +local teleport_menu_gui_data = { + parent = nil, + -- Teleport is a single-destination picker: ctrl/shift multi-select are no-ops here (see the shared target-button click handler). + single_select_only = true, + frame = { + name = creative_mode_defines.names.gui.teleport_menu_frame, + caption = { "gui.creative-mode_teleport" }, + outer_container = { + name = creative_mode_defines.names.gui.teleport_outer_container, + targets_scroll_pane = { + name = creative_mode_defines.names.gui.teleport_targets_scroll_pane, + outer_container = { + name = creative_mode_defines.names.gui.teleport_targets_container, + inner_container = { + name = creative_mode_defines.names.gui.teleport_targets_inner_container, + target_button = { + name_prefix = creative_mode_defines.names.gui.teleport_target_name_button_prefix, + }, + select_all_button = { + name = creative_mode_defines.names.gui.teleport_targets_select_all_button, + }, + }, + }, + }, + cheats_scroll_pane = { + name = creative_mode_defines.names.gui.teleport_cheats_scroll_pane, + cheats_container = { + name = creative_mode_defines.names.gui.teleport_cheats_container, + enable_disable_all_container = nil, + notes = nil, + }, + }, + }, + }, + cheats_gui_data = { + teleport_to_surface = { + type = gui_menu_cheats.cheat_gui_type.apply, + cheat_data = cheats.teleport_cheats_data.cheats.teleport_to_surface, + container_name = creative_mode_defines.names.gui.teleport_to_surface_container, + label_name = creative_mode_defines.names.gui.teleport_to_surface_label, + label_caption = { "gui.creative-mode_teleport-here" }, + label_tooltip = { "gui.creative-mode_teleport-here-tooltip" }, + apply_button_name = creative_mode_defines.names.gui.teleport_to_surface_apply_button, + apply_button_caption = creative_mode_defines.names.gui_captions.ok, + }, + }, + cheats_data = cheats.teleport_cheats_data, + get_unverified_targets_function = function(player) + if rights.can_player_teleport_to_other_surfaces(player) then + return game.surfaces, nil + else + return nil, player.surface + end + end, + verify_target_function = function(player, target) + return target.valid + end, + verify_target_for_insert_function = function(player, target) + if not target.valid then + return false + end + if target == player.surface then + return true + end + if rights.can_player_teleport_to_other_surfaces(player) then + return true + end + return false + end, + get_target_button_name_postfix_function = function(player, target) + return target.name + end, + get_target_button_caption_function = function(player, target) + -- Friendly label: planet name, then platform name, falling back to the raw surface name on vanilla. + if target.planet then + return target.planet.name + elseif target.platform then + return target.platform.name + end + return target.name + end, + get_target_button_tooltip_function = function(player, target) + return { "gui.creative-mode_surface-name-tooltip", target.name } + end, + check_is_target_self_function = function(player, target) + return player.surface == target + end, + post_create_target_button_function = function(player, target, button) end, + check_is_target_button_valid_function = function(player, target, button) + return button.visible ~= false + end, + check_is_target_button_valid_unknown_target_function = function(player, button) + return button.visible ~= false + end, + get_button_actual_target_function = function(player, button) + local surface_name = string.match(button.name, creative_mode_defines.match_patterns.gui.teleport_target_name_button) + if surface_name then + surface_name = tostring(surface_name) + return game.surfaces[surface_name] + end + return nil + end, + remove_target_button_function = function(player, target, button) + return true + end, +} + -- GUI data about the whole global cheats menu. local global_cheats_menu_gui_data = { parent = nil, @@ -1233,6 +1343,13 @@ local cheats_menus_gui_data = { get_player_can_access_function = rights.can_player_access_surface_cheats_menu, cheats_menu_gui_data = surface_cheats_menu_gui_data, }, + teleport = { + button_name = creative_mode_defines.names.gui.teleport_menu_button, + button_caption = { "gui.creative-mode_teleport" }, + access_right_code = rights.access_teleport_code, + get_player_can_access_function = rights.can_player_access_teleport_menu, + cheats_menu_gui_data = teleport_menu_gui_data, + }, global_cheats = { button_name = creative_mode_defines.names.gui.global_cheats_menu_button, button_caption = { "gui.creative-mode_global-cheats" }, @@ -2556,15 +2673,18 @@ local function create_or_destroy_cheats_menu_for_player(player, cheats_menu_gui_ end end - -- Select all button. - current_structure_data = inner_container_data.select_all_button - targets_scroll_pane.add({ - type = "button", - name = current_structure_data.name, - style = creative_mode_defines.names.gui_styles.cheat_select_all_targets_button, - caption = { "gui.creative-mode_list-select-all" }, - tooltip = { "gui.creative-mode_list-select-all-tooltip" }, - }) + -- Select all button. Skipped for single-select pickers (e.g. teleport), where selecting + -- multiple targets makes no sense and would bypass the single_select_only restriction. + if not cheats_menu_gui_data.single_select_only then + current_structure_data = inner_container_data.select_all_button + targets_scroll_pane.add({ + type = "button", + name = current_structure_data.name, + style = creative_mode_defines.names.gui_styles.cheat_select_all_targets_button, + caption = { "gui.creative-mode_list-select-all" }, + tooltip = { "gui.creative-mode_list-select-all-tooltip" }, + }) + end -- Show or hide the target selection scroll pane according to the number of valid options. show_or_hide_targets_selection(outer_container, targets_scroll_pane, valid_target_count > 1) @@ -3436,6 +3556,13 @@ local function on_gui_click_in_cheats_menu_target_buttons( shift, cheats_menu_gui_data ) + -- Single-select pickers (e.g. teleport) behave as a radio: ignore ctrl/shift so a plain click always selects exactly one target. + -- This is additive and only affects pickers that opt in via single_select_only; every other picker keeps its multi-select behavior. + if cheats_menu_gui_data.single_select_only then + control = false + shift = false + end + local inner_container = get_targets_inner_container_in_cheats_menu_for_player(player, cheats_menu_gui_data) if element.parent and element.parent == inner_container then if cheats_menu_gui_data.get_button_actual_target_function then diff --git a/scripts/rights.lua b/scripts/rights.lua index b7fd058..67a5d57 100644 --- a/scripts/rights.lua +++ b/scripts/rights.lua @@ -43,6 +43,20 @@ rights.default_access_surface_cheats_level = rights.access_surface_cheats_level. -- Code, used for determining which right is changed by the admin. rights.access_surface_cheats_code = "surface_cheats" +-- All possible player right levels for accessing the teleport menu. +rights.access_teleport_level = { + -- Only admins have the right to teleport. + admin_only = 0, + -- Each non-admin player can teleport only on the surface he is on. + current_surface_only = 10, + -- All players can teleport to any surface. + free = 20, +} +-- Default teleport access right level. +rights.default_access_teleport_level = rights.access_teleport_level.current_surface_only +-- Code, used for determining which right is changed by the admin. +rights.access_teleport_code = "teleport" + -- All possible player right levels for enabling/disabling global cheats. rights.access_global_cheats_level = { -- Only admin have the right to enable/disable the global cheats. @@ -130,6 +144,34 @@ function rights.can_player_access_surface_cheats_menu(player) return true end +-- Returns whether the given player can teleport to surfaces that he isn't on. +function rights.can_player_teleport_to_other_surfaces(player) + -- Admins can do anything they want. + if player.admin then + return true + end + + if storage.creative_mode.player_rights.access_teleport == rights.access_teleport_level.free then + return true + end + + return false +end + +-- Returns whether the given player can access the teleport menu. +function rights.can_player_access_teleport_menu(player) + -- Admins can do anything they want. + if player.admin then + return true + end + + if storage.creative_mode.player_rights.access_teleport == rights.access_teleport_level.admin_only then + return false + end + + return true +end + -- Returns whether the given player can access the global cheats menu. function rights.can_player_access_global_cheats_menu(player) -- Admins can do anything they want. @@ -149,6 +191,7 @@ function rights.can_player_access_cheats_menu(player) return rights.can_player_access_personal_cheats_menu(player) or rights.can_player_access_team_cheats_menu(player) or rights.can_player_access_surface_cheats_menu(player) + or rights.can_player_access_teleport_menu(player) or rights.can_player_access_global_cheats_menu(player) end @@ -294,6 +337,8 @@ function rights.set_overall_admin_only() storage.creative_mode.player_rights.access_team_cheats = rights.access_team_cheats_level.admin_only -- Surface cheats. storage.creative_mode.player_rights.access_surface_cheats = rights.access_surface_cheats_level.admin_only + -- Teleport. + storage.creative_mode.player_rights.access_teleport = rights.access_teleport_level.admin_only -- Global cheats. storage.creative_mode.player_rights.access_global_cheats = rights.access_global_cheats_level.admin_only -- Build options. @@ -316,6 +361,8 @@ function rights.set_overall_default() storage.creative_mode.player_rights.access_team_cheats = rights.default_access_team_cheats_level -- Surface cheats. storage.creative_mode.player_rights.access_surface_cheats = rights.default_access_surface_cheats_level + -- Teleport. + storage.creative_mode.player_rights.access_teleport = rights.default_access_teleport_level -- Global cheats. storage.creative_mode.player_rights.access_global_cheats = rights.default_access_global_cheats_level -- Build options.