#include "game_root.hpp" #include "game_mode.hpp" #include "godot_macros.h" #include "level.hpp" #include "player.hpp" #include "player_input.hpp" #include "spawn_point.hpp" #include #include #include #include #include #include #include #include #include namespace godot { void GameRoot3D::_bind_methods() { #define CLASSNAME GameRoot3D GDFUNCTION(reset_game_mode); GDPROPERTY_HINTED(first_boot_level, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); GDSIGNAL("player_connected", PropertyInfo(Variant::OBJECT, "player_input", PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_disconnected", PropertyInfo(Variant::OBJECT, "player_input", PROPERTY_HINT_NODE_TYPE, "PlayerInput")); GDSIGNAL("player_spawned", PropertyInfo(Variant::OBJECT, "player_info", 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 if(this->players.is_empty()) { this->player_input_connected(); } this->grab_singleton(); } void GameRoot3D::_ready() { GDGAMEONLY(); this->load_level(this->first_boot_level); } 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(StringName("player_connected"), input); } void GameRoot3D::remove_player(uint32_t player_id) { if(!this->players.has(player_id)) return; // convert player object to node Node *node = this->players.get(player_id).second->to_node(); if(node == nullptr) { 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(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 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(Ref()); } Level3D *GameRoot3D::load_level(Ref level) { return this->load_level_at(level, Transform3D()); } Level3D *GameRoot3D::load_level_at(Ref level, Transform3D at) { if(!GameRoot3D::is_valid_level(level)) { return nullptr; } Level3D *instance = Object::cast_to(level->instantiate()); if(instance == nullptr) { 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 bool const switch_game_mode{this->game_mode.is_null()}; 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()) { for(KeyValue> const &kvp : this->players) { this->place_player_at_spawnpoint(kvp.value.second); } } return instance; } void GameRoot3D::unload_all_levels() { HashMap levels = this->get_levels(); for(KeyValue &kvp : levels) kvp.value->call_deferred("queue_free"); this->get_levels().clear(); this->reset_game_mode(); } void GameRoot3D::replace_levels(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)) { 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)) { 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) { Pair &pair = this->players.get(id); pair.second = nullptr; pair.first->clear_listeners(); } void GameRoot3D::set_game_mode(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(); return; } // shallow clone the game mode prototype .. this->game_mode = prototype->duplicate(false); // .. except for the game state, which should be cloned as well this->game_mode->set_game_state(prototype->get_game_state()->duplicate(false)); this->game_mode->_begin(); 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); } } Ref GameRoot3D::get_game_mode() const { return this->game_mode; } Ref GameRoot3D::get_game_state() const { return this->game_mode->get_game_state(); } void GameRoot3D::set_first_boot_level(Ref level) { if(level.is_null() || !level.is_valid()) { this->first_boot_level.unref(); return; } StringName const root_type = level->get_state()->get_node_type(0); if(!ClassDB::is_parent_class(root_type, "Level3D")) { 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; } Ref GameRoot3D::get_first_boot_level() const { return this->first_boot_level; } HashMap &GameRoot3D::get_levels() { return this->levels; } IPlayer *GameRoot3D::get_player(uint32_t id) { return this->players[id].second; } Vector GameRoot3D::get_players() { Vector players{}; for(KeyValue> pair : this->players) { players.push_back(pair.value.second); } return players; } void GameRoot3D::grab_singleton() { if(GameRoot3D::has_singleton()) { this->set_process_mode(PROCESS_MODE_DISABLED); 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 { 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(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) { 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) { 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) { 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(StringName scene_path) { this->levels.erase(scene_path); } bool GameRoot3D::is_valid_level(Ref &level) { if(level.is_null() || !level.is_valid() || !level->can_instantiate()) { UtilityFunctions::push_error("Can't load level from invalid packed scene"); return false; } StringName const root_type = level->get_state()->get_node_type(0); if(!ClassDB::is_parent_class(root_type, "Level3D")) { 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}; }