#include "game_root.hpp" #include "game_mode.hpp" #include "godot_macros.hpp" #include "level.hpp" #include "player.hpp" #include "player_input.hpp" #include "player.hpp" #include "spawn_point.hpp" #include "utils/game_state.hpp" #include #include #include #include #include #include #include #include #include namespace utils { void GameRoot3D::_bind_methods() { #define CLASSNAME GameRoot3D GDFUNCTION(reset_game_mode); GDPROPERTY_HINTED(first_boot_level, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); GDPROPERTY_HINTED(game_state_prototype, gd::Variant::OBJECT, gd::PROPERTY_HINT_RESOURCE_TYPE, "GameState"); GDSIGNAL("player_connected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_disconnected", gd::PropertyInfo(gd::Variant::OBJECT, "player_input", gd::PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_spawned", gd::PropertyInfo(gd::Variant::OBJECT, "player_info", gd::PROPERTY_HINT_NODE_TYPE, "Node")); } GameRoot3D *GameRoot3D::get_singleton() { return GameRoot3D::singleton_instance; } bool GameRoot3D::has_singleton() { return GameRoot3D::singleton_instance != nullptr; } void GameRoot3D::_enter_tree() { GDGAMEONLY(); // TODO: Replace this with detecting input devices this->player_input_connected(); this->grab_singleton(); this->rng = godot::Ref(memnew(godot::RandomNumberGenerator)); } void GameRoot3D::_ready() { GDGAMEONLY(); this->load_level(this->first_boot_level); // TODO: try load save data from file. this->game_state = this->game_state_prototype->duplicate(true); } void GameRoot3D::_exit_tree() { GDGAMEONLY(); this->release_singleton(); } void GameRoot3D::player_input_connected() { PlayerInput *input = memnew(PlayerInput); this->add_child(input); this->players.insert(this->next_player_id++, {input, nullptr}); this->emit_signal(gd::StringName("player_connected"), input); } void GameRoot3D::remove_player(uint32_t player_id) { if(!this->players.has(player_id)) return; // convert player object to node gd::Node *node = this->players.get(player_id).second->to_node(); if(node == nullptr) { gd::UtilityFunctions::push_error("IPlayer::to_node failed for player with id '", player_id, "'"); return; } node->queue_free(); this->players.get(player_id).second = nullptr; } void GameRoot3D::remove_all_players() { // free all player instances in use for(gd::KeyValue> &pair : this->players) { // skip unused player slots if(pair.value.second == nullptr) continue; else this->remove_player(pair.key); } } bool GameRoot3D::initialize_player(IPlayer *player, uint32_t id) { if(!this->players.has(id)) return false; // register the player gd::Pair &found{this->players.get(id)}; found.second = player; // set player id player->player_id = id; this->emit_signal("player_spawned", player->to_node()); this->add_child(player->to_node()); player->setup_player_input(found.first); return true; } void GameRoot3D::reset_game_mode() { this->set_game_mode(nullptr); } Level3D *GameRoot3D::load_level(gd::Ref level) { return this->load_level_at(level, gd::Transform3D()); } Level3D *GameRoot3D::load_level_at(gd::Ref level, gd::Transform3D at) { if(!GameRoot3D::is_valid_level(level)) { return nullptr; } Level3D *instance = Object::cast_to(level->instantiate()); if(instance == nullptr) { gd::UtilityFunctions::push_error("Unexpected failure to instantiate level scene '", level->get_path(), "'."); return nullptr; } this->levels.insert(level->get_path(), instance); 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 gd::Ref game_mode_prototype{instance->get_game_mode_prototype()}; bool const switch_game_mode{!this->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); // 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); } } return instance; } void GameRoot3D::unload_all_levels() { gd::HashMap levels = this->get_levels(); for(gd::KeyValue &kvp : levels) kvp.value->call_deferred("queue_free"); this->get_levels().clear(); this->reset_game_mode(); } void GameRoot3D::replace_levels(gd::Ref scene) { this->unload_all_levels(); this->load_level(scene); } void GameRoot3D::register_spawn_point(SpawnPoint3D *spawn_point) { if(this->spawn_points.has(spawn_point)) { gd::UtilityFunctions::push_error("Duplicate attempt to register spawnpoint '", spawn_point->get_path(), "'"); return; } if(!this->spawn_points.has(spawn_point)) this->spawn_points.push_back(spawn_point); } void GameRoot3D::unregister_spawn_point(SpawnPoint3D *spawn_point) { if(!this->spawn_points.has(spawn_point)) { gd::UtilityFunctions::push_error("Attempt to unregister spawnpoint '", spawn_point->get_path(), "', which is not registered."); return; } this->spawn_points.erase(spawn_point); } void GameRoot3D::place_player_at_spawnpoint(IPlayer *player) { if(this->spawn_points.is_empty()) return; SpawnPoint3D *spawn_point = this->spawn_points[rng->randi() % this->spawn_points.size()]; player->spawn_at_position(spawn_point->get_global_transform()); } void GameRoot3D::player_despawned(uint32_t id) { gd::Pair &pair = this->players.get(id); pair.second = nullptr; pair.first->clear_listeners(); } void GameRoot3D::set_game_mode(gd::Ref prototype) { this->remove_all_players(); 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; } // 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 { IPlayer *player = this->spawn_player(new_player_id); if(player != nullptr) this->initialize_player(player, new_player_id); new_player_id = this->find_empty_player_slot(); } while(new_player_id != 0); } } GameMode *GameRoot3D::get_game_mode() const { return this->game_mode; } GameState *GameRoot3D::get_game_state() const { return this->game_state.ptr(); } gd::HashMap &GameRoot3D::get_levels() { return this->levels; } IPlayer *GameRoot3D::get_player(uint32_t id) { return this->players[id].second; } gd::Vector GameRoot3D::get_players() { gd::Vector players{}; for(gd::KeyValue> pair : this->players) { players.push_back(pair.value.second); } return players; } void GameRoot3D::set_first_boot_level(gd::Ref level) { if(level.is_null() || !level.is_valid()) { this->first_boot_level.unref(); return; } gd::StringName const root_type = level->get_state()->get_node_type(0); if(!gd::ClassDB::is_parent_class(root_type, "Level3D")) { gd::UtilityFunctions::push_error("First boot level cannot be of type '", root_type, "'. First boot level has to inherit from Level3D"); this->first_boot_level.unref(); return; } this->first_boot_level = level; } gd::Ref GameRoot3D::get_first_boot_level() const { return this->first_boot_level; } void GameRoot3D::set_game_state_prototype(gd::Ref game_state) { this->game_state_prototype = game_state; } gd::Ref GameRoot3D::get_game_state_prototype() const { return this->game_state_prototype; } gd::RandomNumberGenerator &GameRoot3D::get_rng() { return *this->rng.ptr(); } void GameRoot3D::grab_singleton() { if(GameRoot3D::has_singleton()) { this->set_process_mode(PROCESS_MODE_DISABLED); gd::UtilityFunctions::push_error("More than one GameRoot instance active"); } else { GameRoot3D::singleton_instance = this; } } void GameRoot3D::release_singleton() { if(GameRoot3D::singleton_instance == this) { GameRoot3D::singleton_instance = nullptr; } else { gd::UtilityFunctions::push_error("GameRoot instance attempted to release singleton while it is not the singleton instance"); } } uint32_t GameRoot3D::find_empty_player_slot() const { for(gd::KeyValue> const &kvp : this->players) { if(kvp.value.second == nullptr) { return kvp.key; } } return 0; } IPlayer *GameRoot3D::spawn_player(uint32_t id) { if(id == 0) { gd::UtilityFunctions::push_error("Failed to find any valid player slot when spawning player"); return nullptr; } Node *player_node = this->game_mode->get_player_scene()->instantiate(); if(player_node == nullptr) { gd::UtilityFunctions::push_error("Failed to instantiate player scene '", this->game_mode->get_player_scene()->get_path(), "'"); return nullptr; } IPlayer *player = dynamic_cast(player_node); if(player == nullptr) { gd::UtilityFunctions::push_error("Player scene does not implement required IPlayer interface"); player_node->queue_free(); return nullptr; } player_node->connect("tree_exited", callable_mp(this, &GameRoot3D::player_despawned).bind(id)); return player; } void GameRoot3D::level_unloaded(gd::StringName scene_path) { this->levels.erase(scene_path); } bool GameRoot3D::is_valid_level(gd::Ref &level) { if(level.is_null() || !level.is_valid() || !level->can_instantiate()) { gd::UtilityFunctions::push_error("Can't load level from invalid packed scene"); return false; } gd::StringName const root_type = level->get_state()->get_node_type(0); if(!gd::ClassDB::is_parent_class(root_type, "Level3D")) { gd::UtilityFunctions::push_error("Can't load level with root type '", root_type, "'. Root node has to be of type Level3D"); return false; } return true; } GameRoot3D *GameRoot3D::singleton_instance{nullptr}; }