#include "game_root.hpp" #include #include #include #include #include #include #include #include #include "godot_cpp/templates/pair.hpp" #include "utils/godot_macros.h" #include "utils/player_input.hpp" #include "utils/spawn_point.hpp" #include "utils/player.hpp" #include "game_mode.hpp" #include "level.hpp" namespace godot { void GameRoot::_bind_methods() { #define CLASSNAME GameRoot GDFUNCTION(reset_game_mode); ClassDB::add_signal("GameRoot", MethodInfo("player_connected", PropertyInfo(Variant::OBJECT, "player_input", PROPERTY_HINT_NODE_TYPE, "PlayerInput"))); ClassDB::add_signal("GameRoot", MethodInfo("player_disconnected", PropertyInfo(Variant::OBJECT, "player_input", PROPERTY_HINT_NODE_TYPE, "PlayerInput"))); } GameRoot *GameRoot::get_singleton() { return GameRoot::singleton_instance; } bool GameRoot::has_singleton() { return GameRoot::singleton_instance != nullptr; } void GameRoot::_enter_tree() { GDGAMEONLY(); // TODO: Replace this with detecting input devices if(this->players.is_empty()) this->player_connected(); this->grab_singleton(); } void GameRoot::_exit_tree() { GDGAMEONLY(); this->release_singleton(); } void GameRoot::set_game_mode(Ref mode) { if(mode.is_null() || !mode.is_valid()) { this->game_mode = Ref(); return; } this->game_mode = mode; } Ref GameRoot::get_game_mode() const { return this->game_mode; } void GameRoot::player_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); } bool GameRoot::initialize_player(IPlayer *player) { KeyValue> *found{nullptr}; // find an unassigned player input instance for(KeyValue> &pair : this->players) { if(pair.value.second == nullptr) { found = &pair; break; } } // no player slots available, notify caller if(!found) return false; player->player_id = found->key; found->value.second = player; player->setup_player_input(found->value.first); return true; } void GameRoot::reset_game_mode() { this->game_mode.unref(); } void GameRoot::grab_singleton() { if(GameRoot::has_singleton()) { this->set_process_mode(PROCESS_MODE_DISABLED); UtilityFunctions::push_error("More than one GameRoot instance active"); } else { GameRoot::singleton_instance = this; } } void GameRoot::release_singleton() { if(GameRoot::singleton_instance == this) { GameRoot::singleton_instance = nullptr; } else { UtilityFunctions::push_error("GameRoot instance attempted to release singleton while it is not the singleton instance"); } } GameRoot *GameRoot::singleton_instance{nullptr}; #undef CLASSNAME void GameRoot3D::_bind_methods() { #define CLASSNAME GameRoot3D GDPROPERTY_HINTED(first_boot_level, Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"); } void GameRoot3D::_ready() { GDGAMEONLY(); this->load_level(this->first_boot_level); } 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; } // store and add to tree at desired transform this->add_child(instance); instance->set_global_transform(at); this->levels.insert(level->get_path(), instance); // if this is the first level containing a game mode currently active use it's gamemode as a prototype if(this->game_mode.is_null()) { this->change_game_mode(instance->get_game_mode_prototype()); instance->connect("tree_exited", Callable(this, "reset_game_mode")); } return instance; } 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; } this->spawn_points.insert(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::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("Level3D", root_type)) { 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; } 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("Level3D", root_type)) { UtilityFunctions::push_error("Can't load level with root type '", root_type, "'. Root node has to be of type Level3D"); return false; } return true; } void GameRoot3D::change_game_mode(Ref prototype) { // free all player instances in use for(KeyValue> &pair : this->players) { if(pair.value.second == nullptr) continue; Node *node = dynamic_cast(pair.value.second); if(node == nullptr) { UtilityFunctions::push_error("Attempt to cast player '", pair.key, "' to node failed"); } else { node->queue_free(); } } // create new gamemode instance this->game_mode = prototype->duplicate(true); Node *player_node = this->game_mode->get_player_scene()->instantiate(); IPlayer *player = dynamic_cast(player_node); if(player != nullptr) { this->initialize_player(player); } else { } } }