diff --git a/game_mode.cpp b/game_mode.cpp index 44d3404..01f2b1d 100644 --- a/game_mode.cpp +++ b/game_mode.cpp @@ -10,9 +10,6 @@ void GameMode::_bind_methods() { GDPROPERTY_HINTED(player_scene, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); } -void GameMode::_begin() {} -void GameMode::_end() {} - void GameMode::set_player_scene(gd::Ref scene) { this->player_scene = scene; } diff --git a/game_mode.hpp b/game_mode.hpp index 55180e9..21f03e3 100644 --- a/game_mode.hpp +++ b/game_mode.hpp @@ -1,24 +1,21 @@ #ifndef UTILS_GAME_MODE_HPP #define UTILS_GAME_MODE_HPP +#include #include -#include namespace gd = godot; namespace utils { /*! Stores session-relevant data. * - * Contains any data that is only needed for the duration of the current session/match. Use GameState instead if you want data to be saved between sessions. + * Inheriting classes are intended to keep only data that is relevant for the duration of the current session/match. Use GameState instead if you want data to be saved between sessions. * Will be destroyed when a level is loaded that does not match the same game mode class. */ -class GameMode : public gd::Resource { - GDCLASS(GameMode, gd::Resource); +class GameMode : public gd::Node { + GDCLASS(GameMode, gd::Node); static void _bind_methods(); public: - virtual void _begin(); //!< Called when the match begins. - virtual void _end(); //!< Called when the match is ending. - void set_player_scene(gd::Ref scene); gd::Ref get_player_scene() const; private: diff --git a/game_root.cpp b/game_root.cpp index df4e7de..6a1b9b4 100644 --- a/game_root.cpp +++ b/game_root.cpp @@ -96,7 +96,7 @@ bool GameRoot3D::initialize_player(IPlayer *player, uint32_t id) { } void GameRoot3D::reset_game_mode() { - this->set_game_mode(gd::Ref()); + this->set_game_mode(nullptr); } Level3D *GameRoot3D::load_level(gd::Ref level) { @@ -116,13 +116,15 @@ Level3D *GameRoot3D::load_level_at(gd::Ref level, gd::Transform instance->connect("tree_exited", callable_mp(this, &GameRoot3D::level_unloaded).bind(level->get_path())); // store and add to tree at desired transform // if this is the first level containing a game mode currently active use it's gamemode as a prototype - bool const switch_game_mode{this->game_mode.is_null()}; + gd::Ref game_mode_prototype{instance->get_game_mode_prototype()}; + bool const switch_game_mode{this->game_mode->get_scene_file_path() != game_mode_prototype->get_path()}; if(switch_game_mode) { this->set_game_mode(instance->get_game_mode_prototype()); } this->add_child(instance); instance->set_global_transform(at); - if(switch_game_mode && this->game_mode.is_valid()) { + // set initial player positions if new player were spawned due to game mode switch + if(switch_game_mode && this->game_mode != nullptr) { for(gd::KeyValue> const &kvp : this->players) { this->place_player_at_spawnpoint(kvp.value.second); } @@ -172,19 +174,21 @@ void GameRoot3D::player_despawned(uint32_t id) { pair.first->clear_listeners(); } -void GameRoot3D::set_game_mode(gd::Ref prototype) { +void GameRoot3D::set_game_mode(gd::Ref prototype) { this->remove_all_players(); - // allow "unsetting" the gamemode by passing an invalid gamemode - // shorthand for this behaviour is reset_game_mode - if(prototype.is_null() || !prototype.is_valid()) { - if(!this->game_mode.is_null() && this->game_mode.is_valid()) - this->game_mode->_end(); - this->game_mode.unref(); + if(this->game_mode != nullptr) + this->game_mode->queue_free(); + if(prototype.is_null() || !prototype.is_valid()) + return; // allow "unsetting" the gamemode by passing an invalid gamemode + // Detect passing of valid scene that is an invalid game mode + if(!gd::ClassDB::is_parent_class(prototype->get_state()->get_node_type(0), "GameMode")) { + gd::UtilityFunctions::push_error("Attempted to load non-gamemode scene as gamemode"); return; } - // shallow clone the game mode prototype .. - this->game_mode = prototype->duplicate(false); - this->game_mode->_begin(); + // instantiate the game mode as a child + this->game_mode = Object::cast_to(prototype->instantiate()); + this->add_child(game_mode); + // instantiate players if(this->game_mode->get_player_scene().is_valid()) { uint32_t new_player_id = this->find_empty_player_slot(); do { @@ -197,7 +201,7 @@ void GameRoot3D::set_game_mode(gd::Ref prototype) { } GameMode *GameRoot3D::get_game_mode() const { - return this->game_mode.ptr(); + return this->game_mode; } GameState *GameRoot3D::get_game_state() const { diff --git a/game_root.hpp b/game_root.hpp index 63013e9..34b2b80 100644 --- a/game_root.hpp +++ b/game_root.hpp @@ -58,7 +58,7 @@ public: bool initialize_player(IPlayer *player, uint32_t id); /*! Un-set game mode. - * Shorthand for set_game_mode(Ref()) + * Shorthand for `set_game_mode(Ref())` */ void reset_game_mode(); @@ -91,7 +91,7 @@ public: * * Replaces game mode requires destroying and respawning all players */ - void set_game_mode(gd::Ref prototype); + void set_game_mode(gd::Ref prototype); //! get the current active game mode. GameMode *get_game_mode() const; //! Get the current active game state. @@ -134,7 +134,7 @@ private: gd::RandomNumberGenerator rng{}; //!< Global random number generator. gd::HashMap levels{}; //!< all currently active levels identified by their resource paths. gd::Vector spawn_points{}; //!< all currently available spawn points. - gd::Ref game_mode{}; //!< current active gamemode. + GameMode *game_mode{}; //!< current active gamemode. /*! Active game state. * * Will be assigned loaded save data, or game_state_prototype if no save data is found. diff --git a/level.cpp b/level.cpp index 5b7cd24..2d2d629 100644 --- a/level.cpp +++ b/level.cpp @@ -1,17 +1,24 @@ #include "level.hpp" +#include "godot_cpp/core/class_db.hpp" #include "utils/godot_macros.hpp" +#include namespace utils { void Level3D::_bind_methods() { #define CLASSNAME Level3D - GDPROPERTY_HINTED(game_mode_prototype, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "GameMode"); + GDPROPERTY_HINTED(game_mode_prototype, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); } -void Level3D::set_game_mode_prototype(gd::Ref prototype) { - this->game_mode_prototype = prototype; +void Level3D::set_game_mode_prototype(gd::Ref prototype) { + if(prototype.is_null() || !prototype.is_valid()) + this->game_mode_prototype = gd::Ref(nullptr); + else if(!gd::ClassDB::is_parent_class(prototype->get_state()->get_node_type(0), "GameMode")) + return; + else + this->game_mode_prototype = prototype; } -gd::Ref Level3D::get_game_mode_prototype() const { +gd::Ref Level3D::get_game_mode_prototype() const { return this->game_mode_prototype; } } diff --git a/level.hpp b/level.hpp index a057f5b..1e939dd 100644 --- a/level.hpp +++ b/level.hpp @@ -15,10 +15,10 @@ class Level3D : public gd::Node3D { GDCLASS(Level3D, gd::Node3D); static void _bind_methods(); public: - void set_game_mode_prototype(gd::Ref prototype); - gd::Ref get_game_mode_prototype() const; + void set_game_mode_prototype(gd::Ref prototype); + gd::Ref get_game_mode_prototype() const; private: - gd::Ref game_mode_prototype{}; //!< The starting state of the game mode to instantiate if this is the "leading" level. + gd::Ref game_mode_prototype{}; //!< The starting state of the game mode to instantiate if this is the "leading" level. }; }