From 2a5a9988de1c462165b4cdc3387c50f4b1e8d2aa Mon Sep 17 00:00:00 2001 From: Joana Saraiva Date: Tue, 24 Mar 2026 14:19:30 +0000 Subject: [PATCH 1/4] Fix #3728: Saving worldmap position on unstable tiles When leaving to the main menu, the game saves Tux's current worldmap position even if he is mid-path or on a non-stable tile. Upon reloading, Tux is restored to that invalid position. Save Tux on the last valid "stable" tile (such as a stop or level tile) instead of the current one. Also store the corresponding back direction to keep movement consistent after loading. In ghost mode, avoid persisting invalid tiles by falling back to the last valid level tile. This ensures Tux always respawns on a valid, navigable position. --- src/worldmap/tux.cpp | 36 +++++++++++++++++++++++++++++++++ src/worldmap/tux.hpp | 16 +++++++++++++++ src/worldmap/worldmap_state.cpp | 20 ++++++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index 7d3091483a7..853282b4784 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -48,6 +48,10 @@ Tux::Tux(WorldMap* worldmap) : m_direction(Direction::NONE), m_initial_tile_pos(), m_tile_pos(), + m_last_stable_tile_pos(), + m_last_level_tile_pos(), + m_last_stable_back_direction(Direction::NONE), + m_last_level_back_direction(Direction::NONE), m_offset(0), m_moving(false), m_ghost_mode(false) @@ -304,6 +308,24 @@ Tux::try_continue_walking(float dt_sec) m_worldmap->set_passive_message({}, 0.0f); } + if (const auto level = worldmap_sector->at_object(); level) + { + m_last_level_tile_pos = m_tile_pos; + m_last_level_back_direction = m_back_direction; + m_last_stable_tile_pos = m_tile_pos; + m_last_stable_back_direction = m_back_direction; + } + else if (!m_ghost_mode) + { + m_last_stable_tile_pos = m_tile_pos; + m_last_stable_back_direction = m_back_direction; + } + else + { + m_last_stable_tile_pos = m_last_level_tile_pos; + m_last_stable_back_direction = m_last_level_back_direction; + } + stop(); return; } @@ -392,6 +414,20 @@ Tux::setup() if (m_initial_tile_pos != Vector()) m_tile_pos = m_initial_tile_pos; + m_last_stable_tile_pos = m_tile_pos; + m_last_stable_back_direction = m_back_direction; + + if (m_worldmap->get_sector().at_object(m_tile_pos)) + { + m_last_level_tile_pos = m_tile_pos; + m_last_level_back_direction = m_back_direction; + } + else + { + m_last_level_tile_pos = m_last_stable_tile_pos; + m_last_level_back_direction = m_last_stable_back_direction; + } + // check if we already touch a SpriteChange object auto sprite_change = m_worldmap->get_sector().at_object(m_tile_pos); change_sprite(sprite_change); diff --git a/src/worldmap/tux.hpp b/src/worldmap/tux.hpp index 28ef439a5e9..f9e4a06438f 100644 --- a/src/worldmap/tux.hpp +++ b/src/worldmap/tux.hpp @@ -54,6 +54,18 @@ class Tux final : public GameObject inline void set_initial_pos(const Vector& pos) { m_initial_tile_pos = pos / 32.f; } inline void set_tile_pos(const Vector& pos) { m_tile_pos = pos; } + inline Vector get_last_stable_tile_pos() const { return m_last_stable_tile_pos; } + inline void set_last_stable_tile_pos(const Vector& pos) { m_last_stable_tile_pos = pos; } + + inline Vector get_last_level_tile_pos() const { return m_last_level_tile_pos; } + inline void set_last_level_tile_pos(const Vector& pos) { m_last_level_tile_pos = pos; } + + inline Direction get_last_stable_back_direction() const { return m_last_stable_back_direction; } + inline void set_last_stable_back_direction(Direction dir) { m_last_stable_back_direction = dir; } + + inline Direction get_last_level_back_direction() const { return m_last_level_back_direction; } + inline void set_last_level_back_direction(Direction dir) { m_last_level_back_direction = dir; } + void process_special_tile(SpecialTile* special_tile); private: @@ -78,6 +90,10 @@ class Tux final : public GameObject Direction m_direction; Vector m_initial_tile_pos; Vector m_tile_pos; + Vector m_last_stable_tile_pos; + Vector m_last_level_tile_pos; + Direction m_last_stable_back_direction; + Direction m_last_level_back_direction; /** Length by which tux is away from its current tile, length is in input_direction direction */ float m_offset; diff --git a/src/worldmap/worldmap_state.cpp b/src/worldmap/worldmap_state.cpp index 25289337c1a..5d91f330754 100644 --- a/src/worldmap/worldmap_state.cpp +++ b/src/worldmap/worldmap_state.cpp @@ -155,12 +155,14 @@ WorldMapState::load_tux(const ssq::Table& table) log_warning << "Player position not set, respawning." << std::endl; sector.move_to_spawnpoint(DEFAULT_SPAWNPOINT_NAME); m_position_was_reset = true; + return; } std::string back_str; tux.get("back", back_str); sector.m_tux->m_back_direction = string_to_direction(back_str); sector.m_tux->set_tile_pos(p); + Direction back_dir = string_to_direction(back_str); int tile_data = sector.tile_data_at(p); if (!(tile_data & (Tile::WORLDMAP_NORTH | Tile::WORLDMAP_SOUTH | Tile::WORLDMAP_WEST | Tile::WORLDMAP_EAST))) @@ -168,6 +170,18 @@ WorldMapState::load_tux(const ssq::Table& table) log_warning << "Player at illegal position " << p.x << ", " << p.y << " respawning." << std::endl; sector.move_to_spawnpoint(DEFAULT_SPAWNPOINT_NAME); m_position_was_reset = true; + return; + } + + sector.m_tux->m_back_direction = back_dir; + sector.m_tux->set_tile_pos(p); + sector.m_tux->set_last_stable_tile_pos(p); + sector.m_tux->set_last_stable_back_direction(back_dir); + + if (sector.at_object(p)) + { + sector.m_tux->set_last_level_tile_pos(p); + sector.m_tux->set_last_level_back_direction(back_dir); } } @@ -296,10 +310,16 @@ WorldMapState::save_state(bool initial) const sector_table.set("music", music_object.get_music()); /** Save Tux **/ + const Vector save_pos = sector.m_tux->get_last_stable_tile_pos(); + const Direction save_back = sector.m_tux->get_last_stable_back_direction(); + ssq::Table tux = sector_table.addTable("tux"); tux.set("x", sector.m_tux->get_tile_pos().x); tux.set("y", sector.m_tux->get_tile_pos().y); tux.set("back", direction_to_string(sector.m_tux->m_back_direction)); + tux.set("x", save_pos.x); + tux.set("y", save_pos.y); + tux.set("back", direction_to_string(save_back)); /** Save levels **/ ssq::Table levels = sector_table.addTable("levels"); From c5557b9292ce0002b715089d040aeea9dec5283e Mon Sep 17 00:00:00 2001 From: Joana Saraiva Date: Wed, 6 May 2026 11:54:33 +0100 Subject: [PATCH 2/4] Apply requested changes --- src/worldmap/tux.cpp | 31 +++++++++++-------------------- src/worldmap/worldmap_state.cpp | 4 ---- 2 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index 853282b4784..3f809ba03e8 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -304,27 +304,18 @@ Tux::try_continue_walking(float dt_sec) (teleporter) || m_ghost_mode) { - if (special_tile && !special_tile->get_map_message().empty() && !special_tile->is_passive_message()) { - m_worldmap->set_passive_message({}, 0.0f); - } + if (special_tile && !special_tile->get_map_message().empty() && !special_tile->is_passive_message()) { + m_worldmap->set_passive_message({}, 0.0f); + } - if (const auto level = worldmap_sector->at_object(); level) - { - m_last_level_tile_pos = m_tile_pos; - m_last_level_back_direction = m_back_direction; - m_last_stable_tile_pos = m_tile_pos; - m_last_stable_back_direction = m_back_direction; - } - else if (!m_ghost_mode) - { - m_last_stable_tile_pos = m_tile_pos; - m_last_stable_back_direction = m_back_direction; - } - else - { - m_last_stable_tile_pos = m_last_level_tile_pos; - m_last_stable_back_direction = m_last_level_back_direction; - } + if (worldmap_sector->at_object() != nullptr){ + m_last_level_tile_pos = m_tile_pos; + m_last_level_back_direction = m_back_direction; + } + + m_last_stable_tile_pos = m_ghost_mode ? m_last_level_tile_pos : m_tile_pos; + m_last_stable_back_direction = m_ghost_mode ? m_last_level_back_direction : m_back_direction; + } stop(); return; diff --git a/src/worldmap/worldmap_state.cpp b/src/worldmap/worldmap_state.cpp index 5d91f330754..cc60f3ecc8c 100644 --- a/src/worldmap/worldmap_state.cpp +++ b/src/worldmap/worldmap_state.cpp @@ -152,7 +152,6 @@ WorldMapState::load_tux(const ssq::Table& table) Vector p(0.0f, 0.0f); if (!tux.get("x", p.x) || !tux.get("y", p.y)) { - log_warning << "Player position not set, respawning." << std::endl; sector.move_to_spawnpoint(DEFAULT_SPAWNPOINT_NAME); m_position_was_reset = true; return; @@ -314,9 +313,6 @@ WorldMapState::save_state(bool initial) const const Direction save_back = sector.m_tux->get_last_stable_back_direction(); ssq::Table tux = sector_table.addTable("tux"); - tux.set("x", sector.m_tux->get_tile_pos().x); - tux.set("y", sector.m_tux->get_tile_pos().y); - tux.set("back", direction_to_string(sector.m_tux->m_back_direction)); tux.set("x", save_pos.x); tux.set("y", save_pos.y); tux.set("back", direction_to_string(save_back)); From 98009050d97be6f145b2be573f6dc88f72cc1c5d Mon Sep 17 00:00:00 2001 From: Joana Saraiva <143503427+joanasaraiva10@users.noreply.github.com> Date: Mon, 8 Jun 2026 19:37:43 +0100 Subject: [PATCH 3/4] Update src/worldmap/tux.cpp Co-authored-by: MatusGuy <85036874+MatusGuy@users.noreply.github.com> --- src/worldmap/tux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index 3f809ba03e8..46bafc2276b 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -308,7 +308,8 @@ Tux::try_continue_walking(float dt_sec) m_worldmap->set_passive_message({}, 0.0f); } - if (worldmap_sector->at_object() != nullptr){ + if (worldmap_sector->at_object() != nullptr) + { m_last_level_tile_pos = m_tile_pos; m_last_level_back_direction = m_back_direction; } From fe1c3e45e3b8c21a9ece1dc3d4ccf04d7ba97fad Mon Sep 17 00:00:00 2001 From: Joana Saraiva <143503427+joanasaraiva10@users.noreply.github.com> Date: Mon, 8 Jun 2026 19:40:53 +0100 Subject: [PATCH 4/4] Update src/worldmap/tux.cpp Co-authored-by: MatusGuy <85036874+MatusGuy@users.noreply.github.com> --- src/worldmap/tux.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/worldmap/tux.cpp b/src/worldmap/tux.cpp index 46bafc2276b..ee94825dce2 100644 --- a/src/worldmap/tux.cpp +++ b/src/worldmap/tux.cpp @@ -304,8 +304,9 @@ Tux::try_continue_walking(float dt_sec) (teleporter) || m_ghost_mode) { - if (special_tile && !special_tile->get_map_message().empty() && !special_tile->is_passive_message()) { - m_worldmap->set_passive_message({}, 0.0f); + if (special_tile && !special_tile->get_map_message().empty() && !special_tile->is_passive_message()) + { + m_worldmap->set_passive_message("", 0.0f); } if (worldmap_sector->at_object() != nullptr)