Skip to content

Commit

Permalink
Preserving players and grabbed objects between sectors
Browse files Browse the repository at this point in the history
Previously, each sector would contain its own instances of the `Player` object for each available player.
This, however, makes it tedious to preserve player data across sectors, for example, the currently grabbed object.

`GameObjectManager` now supports moving objects to another `GameObjectManager`.

Now, the `Level` instantiates all `Player`s in the "main" sector, and each sector, on activation, moves those players to itself, so it can manage them, essentially making players global for the level.
This allows retaining the full state of all players, between sectors.

Any grabbed/carried object by a player is also moved between sectors, together with the player.

Additionally, a fix for the slow moving animation of grabbed objects, when teleporting while carrying them, is included.
Carried objects now teleport together with the player.

Fixes #2191.
  • Loading branch information
Vankata453 committed Aug 21, 2023
1 parent 0836c36 commit 9ece13d
Show file tree
Hide file tree
Showing 11 changed files with 148 additions and 82 deletions.
36 changes: 32 additions & 4 deletions src/object/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,6 @@ Player::Player(PlayerStatus& player_status, const std::string& name_, int player
m_idle_timer(),
m_idle_stage(0),
m_climbing(nullptr),
m_climbing_remove_listener(nullptr),
m_ending_direction(0),
m_collected_keys(0)
{
Expand Down Expand Up @@ -311,6 +310,21 @@ Player::do_scripting_controller(const std::string& control_text, bool pressed)
}
}

void
Player::move_to_sector(Sector& other)
{
stop_climbing(*m_climbing);
if (m_grabbed_object)
{
auto grabbed_game_object = dynamic_cast<GameObject*>(m_grabbed_object);
if (grabbed_game_object)
get_parent()->move_object(grabbed_game_object->get_uid(), other);
}

// Move the player.
get_parent()->move_object(get_uid(), other);
}

bool
Player::adjust_height(float new_height, float bottom_offset)
{
Expand Down Expand Up @@ -1589,8 +1603,11 @@ Player::handle_input()
}

void
Player::position_grabbed_object()
Player::position_grabbed_object(bool teleport)
{
if (!m_grabbed_object)
return;

auto moving_object = dynamic_cast<MovingObject*>(m_grabbed_object);
assert(moving_object);
const auto& object_bbox = moving_object->get_bbox();
Expand All @@ -1603,13 +1620,21 @@ Player::position_grabbed_object()
if (m_dir == Direction::LEFT)
pos.x -= object_bbox.get_width();
pos.y -= object_bbox.get_height();
m_grabbed_object->grab(*this, pos, m_dir);

if (teleport)
moving_object->set_pos(pos);
else
m_grabbed_object->grab(*this, pos, m_dir);
}
else
{
Vector pos(m_col.m_bbox.get_left() + (std::cos(m_swimming_angle) * 32.f),
m_col.m_bbox.get_top() + (std::sin(m_swimming_angle) * 32.f));
m_grabbed_object->grab(*this, pos, m_dir);

if (teleport)
moving_object->set_pos(pos);
else
m_grabbed_object->grab(*this, pos, m_dir);
}
}

Expand Down Expand Up @@ -2358,6 +2383,9 @@ Player::move(const Vector& vector)
m_last_ground_y = vector.y;
if (m_climbing) stop_climbing(*m_climbing);

// Make sure grabbed objects move directly with the player.
position_grabbed_object(true);

m_physic.reset();
}

Expand Down
8 changes: 6 additions & 2 deletions src/object/player.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,9 @@ class Player final : public MovingObject,
void use_scripting_controller(bool use_or_release);
void do_scripting_controller(const std::string& control, bool pressed);

/** Move the player to a different sector, including any objects that it points to, or references. */
void move_to_sector(Sector& other);

void make_invincible();

bool is_invincible() const { return m_invincible_timer.started(); }
Expand Down Expand Up @@ -229,7 +232,7 @@ class Player final : public MovingObject,
void set_dir(bool right);
void stop_backflipping();

void position_grabbed_object();
void position_grabbed_object(bool teleport = false);
bool try_grab();

/** Boosts Tux in a certain direction, sideways. Useful for bumpers/walljumping. */
Expand All @@ -243,6 +246,8 @@ class Player final : public MovingObject,
int get_collected_keys() { return m_collected_keys; }
void add_collected_keys(int keynum) { m_collected_keys += keynum; }

bool track_state() const override { return false; }

private:
void handle_input();
void handle_input_ghost(); /**< input handling while in ghost mode */
Expand Down Expand Up @@ -387,7 +392,6 @@ class Player final : public MovingObject,
unsigned int m_idle_stage;

Climbable* m_climbing; /**< Climbable object we are currently climbing, null if none */
std::unique_ptr<ObjectRemoveListener> m_climbing_remove_listener;

int m_ending_direction;
int m_collected_keys;
Expand Down
26 changes: 26 additions & 0 deletions src/supertux/game_object_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,32 @@ GameObjectManager::update_tilemaps()
}
}

void
GameObjectManager::move_object(const UID& uid, GameObjectManager& other)
{
if (&other == this)
return;

auto it = std::find_if(m_gameobjects.begin(), m_gameobjects.end(),
[uid](const auto& obj) {
return obj->get_uid() == uid;
});
if (it == m_gameobjects.end())
{
std::ostringstream err;
err << "Object with UID " << uid << " not found.";
throw std::runtime_error(err.str());
}

this_before_object_remove(**it);
before_object_remove(**it);

other.add_object(std::move(*it));
m_gameobjects.erase(it);

other.flush_game_objects();
}

void
GameObjectManager::toggle_undo_tracking(bool enabled)
{
Expand Down
3 changes: 3 additions & 0 deletions src/supertux/game_object_manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ class GameObjectManager
}
}

/** Move an object to another GameObjectManager. */
void move_object(const UID& uid, GameObjectManager& other);

/** Register a callback to be called once the given name can be
resolved to a UID. Note that this function is only valid in the
construction phase, not during draw() or update() calls, use
Expand Down
26 changes: 5 additions & 21 deletions src/supertux/game_session.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ GameSession::GameSession(const std::string& levelfile_, Savegame& savegame, Stat
m_activated_checkpoint(),
m_newsector(),
m_newspawnpoint(),
m_invincibilitytimeleft(),
m_best_level_statistics(statistics),
m_savegame(savegame),
m_play_time(0),
Expand Down Expand Up @@ -508,28 +507,21 @@ GameSession::update(float dt_sec, const Controller& controller)
}
assert(m_currentsector != nullptr);
m_currentsector->stop_looping_sounds();

sector->activate(m_newspawnpoint);

// Start the new sector's music only if it's different from the current one.
if (current_music != sector->get_singleton_by_type<MusicObject>().get_music())
sector->get_singleton_by_type<MusicObject>().play_music(LEVEL_MUSIC);

m_currentsector = sector;
m_currentsector->play_looping_sounds();

if (is_playing_demo())
{
reset_demo_controller();
}
// Keep persistent across sectors.
if (m_edit_mode)
for (auto* p : m_currentsector->get_players())
p->set_edit_mode(m_edit_mode);

m_newsector = "";
m_newspawnpoint = "";

// Retain invincibility if the player has it.
auto players = m_currentsector->get_players();
for (const auto& player : players)
player->m_invincible_timer.start(m_invincibilitytimeleft[player->get_id()]);
}

// Update the world state and all objects in the world.
Expand Down Expand Up @@ -647,18 +639,10 @@ GameSession::finish(bool win)
}

void
GameSession::respawn(const std::string& sector, const std::string& spawnpoint,
bool retain_invincibility)
GameSession::respawn(const std::string& sector, const std::string& spawnpoint)
{
m_newsector = sector;
m_newspawnpoint = spawnpoint;

m_invincibilitytimeleft.clear();

if (retain_invincibility)
for (const auto* player : Sector::get().get_players())
if (player->is_invincible())
m_invincibilitytimeleft[player->get_id()] = player->m_invincible_timer.get_timeleft();
}

void
Expand Down
6 changes: 1 addition & 5 deletions src/supertux/game_session.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ class GameSession final : public Screen,

/** ends the current level */
void finish(bool win = true);
void respawn(const std::string& sectorname, const std::string& spawnpointname,
bool retain_invincibility = false);
void respawn(const std::string& sectorname, const std::string& spawnpointname);
void reset_level();

void set_start_point(const std::string& sectorname,
Expand Down Expand Up @@ -167,9 +166,6 @@ class GameSession final : public Screen,
std::string m_newsector;
std::string m_newspawnpoint;

// Whether the player had invincibility before spawning in a new sector
std::unordered_map<int, float> m_invincibilitytimeleft;

Statistics* m_best_level_statistics;
Savegame& m_savegame;

Expand Down
64 changes: 61 additions & 3 deletions src/supertux/level.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@

#include "supertux/level.hpp"

#include <numeric>

#include <physfs.h>

#include "badguy/goldbomb.hpp"
#include "editor/editor.hpp"
#include "object/bonus_block.hpp"
#include "object/coin.hpp"
#include "object/player.hpp"
#include "physfs/util.hpp"
#include "supertux/game_session.hpp"
#include "supertux/player_status_hud.hpp"
#include "supertux/savegame.hpp"
#include "supertux/sector.hpp"
#include "trigger/secretarea_trigger.hpp"
#include "util/file_system.hpp"
#include "util/log.hpp"
#include "util/string_util.hpp"
#include "util/writer.hpp"

#include <physfs.h>
#include <numeric>

Level* Level::s_current = nullptr;

Level::Level(bool worldmap) :
Expand Down Expand Up @@ -59,6 +65,47 @@ Level::~Level()
m_sectors.clear();
}

void
Level::initialize()
{
// Get the "main" sector.
Sector* main_sector = get_sector("main");
if (!main_sector)
throw std::runtime_error("No \"main\" sector found.");

m_stats.init(*this);

Savegame* savegame = (Editor::current() && Editor::is_active()) ?
Editor::current()->m_savegame.get() :
GameSession::current() ? &GameSession::current()->get_savegame() : nullptr;

PlayerStatus dummy_player_status(1);
PlayerStatus& player_status = savegame ? savegame->get_player_status() : dummy_player_status;

if (savegame && !m_suppress_pause_menu && !savegame->is_title_screen())
{
for (auto& sector : m_sectors)
sector->add<PlayerStatusHUD>(player_status);
}

for (int id = 0; id < InputManager::current()->get_num_users() || id == 0; id++)
{
if (!InputManager::current()->has_corresponsing_controller(id)
&& !InputManager::current()->m_uses_keyboard[id]
&& savegame
&& !savegame->is_title_screen()
&& id != 0)
continue;

if (id > 0 && !savegame)
dummy_player_status.add_player();

// Add players only in the main sector. Players will be moved between sectors.
main_sector->add<Player>(player_status, "Tux" + (id == 0 ? "" : std::to_string(id + 1)), id);
}
main_sector->flush_game_objects();
}

void
Level::save(std::ostream& stream)
{
Expand Down Expand Up @@ -250,6 +297,17 @@ Level::get_total_secrets() const
return std::accumulate(m_sectors.begin(), m_sectors.end(), 0, get_secret_count);
}

std::vector<Player*>
Level::get_players() const
{
std::vector<Player*> players;
for (const auto& sector : m_sectors)
for (auto& player : sector->get_objects_by_type_index(typeid(Player)))
players.push_back(static_cast<Player*>(player));

return players;
}

void
Level::reactivate()
{
Expand Down
5 changes: 5 additions & 0 deletions src/supertux/level.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "supertux/statistics.hpp"

class Player;
class ReaderMapping;
class Sector;
class Writer;
Expand Down Expand Up @@ -54,6 +55,8 @@ class Level final
Sector* get_sector(size_t num) const;
const std::vector<std::unique_ptr<Sector> >& get_sectors() const { return m_sectors; }

std::vector<Player*> get_players() const;

std::string get_tileset() const { return m_tileset; }

int get_total_coins() const;
Expand All @@ -67,6 +70,8 @@ class Level final
std::string get_license() const { return m_license; }

private:
void initialize();

void save(Writer& writer);
void load_old_format(const ReaderMapping& reader);

Expand Down
4 changes: 3 additions & 1 deletion src/supertux/level_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ LevelParser::load(const ReaderDocument& doc)
log_warning << "[" << doc.get_filename() << "] level format version " << version << " is not supported" << std::endl;
}

m_level.m_stats.init(m_level);
m_level.initialize();
}

void
Expand All @@ -203,6 +203,8 @@ LevelParser::load_old_format(const ReaderMapping& reader)

auto sector = SectorParser::from_reader_old_format(m_level, reader, m_editable);
m_level.add_sector(std::move(sector));

m_level.initialize();
}

void
Expand Down
Loading

0 comments on commit 9ece13d

Please sign in to comment.