From 11fa43f0a49abcab762bc15613e1d74ce4d5869b Mon Sep 17 00:00:00 2001 From: Fabian Dill Date: Thu, 20 Feb 2025 00:17:19 +0100 Subject: [PATCH] Factorio: prevent players from getting stuck from Teleport Traps (#4537) --- worlds/factorio/Options.py | 3 +- worlds/factorio/data/mod/lib.lua | 67 +++++++++++++++++++ worlds/factorio/data/mod_template/control.lua | 13 ++-- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/worlds/factorio/Options.py b/worlds/factorio/Options.py index fe72d386..481ed009 100644 --- a/worlds/factorio/Options.py +++ b/worlds/factorio/Options.py @@ -263,7 +263,8 @@ class AttackTrapCount(TrapCount): class TeleportTrapCount(TrapCount): - """Trap items that when received trigger a random teleport.""" + """Trap items that when received trigger a random teleport. + It is ensured the player can walk back to where they got teleported from.""" display_name = "Teleport Traps" diff --git a/worlds/factorio/data/mod/lib.lua b/worlds/factorio/data/mod/lib.lua index edec5b7a..aa50b926 100644 --- a/worlds/factorio/data/mod/lib.lua +++ b/worlds/factorio/data/mod/lib.lua @@ -49,6 +49,73 @@ function fire_entity_at_entities(entity_name, entities, speed) end end +local teleport_requests = {} +local teleport_attempts = {} +local max_attempts = 100 + +function attempt_teleport_player(player, attempt) + -- global attempt storage as metadata can't be stored + if attempt == nil then + attempt = teleport_attempts[player.index] + else + teleport_attempts[player.index] = attempt + end + + if attempt > max_attempts then + player.print("Teleport failed: No valid position found after " .. max_attempts .. " attempts!") + teleport_attempts[player.index] = 0 + return + end + + local surface = player.character.surface + local prototype_name = player.character.prototype.name + local original_position = player.character.position + local candidate_position = random_offset_position(original_position, 1024) + + local non_colliding_position = surface.find_non_colliding_position( + prototype_name, candidate_position, 0, 1 + ) + + if non_colliding_position then + -- Request pathfinding asynchronously + local path_id = surface.request_path{ + bounding_box = player.character.prototype.collision_box, + collision_mask = { layers = { ["player"] = true } }, + start = original_position, + goal = non_colliding_position, + force = player.force.name, + radius = 1, + pathfind_flags = {cache = true, low_priority = true, allow_paths_through_own_entities = true}, + } + + -- Store the request with the player index as the key + teleport_requests[player.index] = path_id + else + attempt_teleport_player(player, attempt + 1) + end +end + +function handle_teleport_attempt(event) + for player_index, path_id in pairs(teleport_requests) do + -- Check if the event matches the stored path_id + if path_id == event.id then + local player = game.players[player_index] + + if event.path then + if player.character then + player.character.teleport(event.path[#event.path].position) -- Teleport to the last point in the path + -- Clear the attempts for this player + teleport_attempts[player_index] = 0 + return + end + return + end + + attempt_teleport_player(player, nil) + break + end + end +end function spill_character_inventory(character) if not (character and character.valid) then return false diff --git a/worlds/factorio/data/mod_template/control.lua b/worlds/factorio/data/mod_template/control.lua index 07fd4c04..cd0c00e9 100644 --- a/worlds/factorio/data/mod_template/control.lua +++ b/worlds/factorio/data/mod_template/control.lua @@ -134,6 +134,9 @@ end script.on_event(defines.events.on_player_changed_position, on_player_changed_position) {% endif %} +-- Handle the pathfinding result of teleport traps +script.on_event(defines.events.on_script_path_request_finished, handle_teleport_attempt) + function count_energy_bridges() local count = 0 for i, bridge in pairs(storage.energy_link_bridges) do @@ -143,9 +146,11 @@ function count_energy_bridges() end return count end + function get_energy_increment(bridge) return ENERGY_INCREMENT + (ENERGY_INCREMENT * 0.3 * bridge.quality.level) end + function on_check_energy_link(event) --- assuming 1 MJ increment and 5MJ battery: --- first 2 MJ request fill, last 2 MJ push energy, middle 1 MJ does nothing @@ -722,12 +727,10 @@ end, game.forces["enemy"].set_evolution_factor(new_factor, "nauvis") game.print({"", "New evolution factor:", new_factor}) end, -["Teleport Trap"] = function () +["Teleport Trap"] = function() for _, player in ipairs(game.forces["player"].players) do - current_character = player.character - if current_character ~= nil then - current_character.teleport(current_character.surface.find_non_colliding_position( - current_character.prototype.name, random_offset_position(current_character.position, 1024), 0, 1)) + if player.character then + attempt_teleport_player(player, 1) end end end,