diff options
author | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2022-07-12 23:12:42 +0200 |
---|---|---|
committer | Fabio Alessandrelli <fabio.alessandrelli@gmail.com> | 2022-07-26 09:31:12 +0200 |
commit | ca7d572908c58c587214b8f65bdd4078d0185ab2 (patch) | |
tree | efd1507df5b3cd45e20b36bfc22818fcfadc5c85 /modules/multiplayer/multiplayer_spawner.cpp | |
parent | c3dc887c410c5fa01eba197acba3afd8ce357ada (diff) |
[Net] Modularize multiplayer, expose MultiplayerAPI to extensions.
- RPC configurations are now dictionaries.
- Script.get_rpc_methods renamed to Script.get_rpc_config.
- Node.rpc[_id] and Callable.rpc now return an Error.
- Refactor MultiplayerAPI to allow extension.
- New MultiplayerAPI.rpc method with Array argument (for scripts).
- Move the default MultiplayerAPI implementation to a module.
Diffstat (limited to 'modules/multiplayer/multiplayer_spawner.cpp')
-rw-r--r-- | modules/multiplayer/multiplayer_spawner.cpp | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp new file mode 100644 index 0000000000..ca2fd1c023 --- /dev/null +++ b/modules/multiplayer/multiplayer_spawner.cpp @@ -0,0 +1,302 @@ +/*************************************************************************/ +/* multiplayer_spawner.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "multiplayer_spawner.h" + +#include "core/io/marshalls.h" +#include "scene/main/multiplayer_api.h" +#include "scene/main/window.h" +#include "scene/scene_string_names.h" + +#ifdef TOOLS_ENABLED +/* This is editor only */ +bool MultiplayerSpawner::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "_spawnable_scene_count") { + spawnable_scenes.resize(p_value); + notify_property_list_changed(); + return true; + } else { + String ns = p_name; + if (ns.begins_with("scenes/")) { + uint32_t index = ns.get_slicec('/', 1).to_int(); + ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); + spawnable_scenes[index].path = p_value; + return true; + } + } + return false; +} + +bool MultiplayerSpawner::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "_spawnable_scene_count") { + r_ret = spawnable_scenes.size(); + return true; + } else { + String ns = p_name; + if (ns.begins_with("scenes/")) { + uint32_t index = ns.get_slicec('/', 1).to_int(); + ERR_FAIL_UNSIGNED_INDEX_V(index, spawnable_scenes.size(), false); + r_ret = spawnable_scenes[index].path; + return true; + } + } + return false; +} + +void MultiplayerSpawner::_get_property_list(List<PropertyInfo> *p_list) const { + p_list->push_back(PropertyInfo(Variant::INT, "_spawnable_scene_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY, "Auto Spawn List,scenes/")); + List<String> exts; + ResourceLoader::get_recognized_extensions_for_type("PackedScene", &exts); + String ext_hint; + for (const String &E : exts) { + if (!ext_hint.is_empty()) { + ext_hint += ","; + } + ext_hint += "*." + E; + } + for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { + p_list->push_back(PropertyInfo(Variant::STRING, "scenes/" + itos(i), PROPERTY_HINT_FILE, ext_hint, PROPERTY_USAGE_EDITOR)); + } +} +#endif +void MultiplayerSpawner::add_spawnable_scene(const String &p_path) { + SpawnableScene sc; + sc.path = p_path; + if (Engine::get_singleton()->is_editor_hint()) { + ERR_FAIL_COND(!FileAccess::exists(p_path)); + } else { + sc.cache = ResourceLoader::load(p_path); + ERR_FAIL_COND_MSG(sc.cache.is_null(), "Invalid spawnable scene: " + p_path); + } + spawnable_scenes.push_back(sc); +} +int MultiplayerSpawner::get_spawnable_scene_count() const { + return spawnable_scenes.size(); +} +String MultiplayerSpawner::get_spawnable_scene(int p_idx) const { + return spawnable_scenes[p_idx].path; +} +void MultiplayerSpawner::clear_spawnable_scenes() { + spawnable_scenes.clear(); +} + +Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const { + Vector<String> ss; + ss.resize(spawnable_scenes.size()); + for (int i = 0; i < ss.size(); i++) { + ss.write[i] = spawnable_scenes[i].path; + } + return ss; +} + +void MultiplayerSpawner::_set_spawnable_scenes(const Vector<String> &p_scenes) { + clear_spawnable_scenes(); + for (int i = 0; i < p_scenes.size(); i++) { + add_spawnable_scene(p_scenes[i]); + } +} + +void MultiplayerSpawner::_bind_methods() { + ClassDB::bind_method(D_METHOD("add_spawnable_scene", "path"), &MultiplayerSpawner::add_spawnable_scene); + ClassDB::bind_method(D_METHOD("get_spawnable_scene_count"), &MultiplayerSpawner::get_spawnable_scene_count); + ClassDB::bind_method(D_METHOD("get_spawnable_scene", "path"), &MultiplayerSpawner::get_spawnable_scene); + ClassDB::bind_method(D_METHOD("clear_spawnable_scenes"), &MultiplayerSpawner::clear_spawnable_scenes); + + ClassDB::bind_method(D_METHOD("_get_spawnable_scenes"), &MultiplayerSpawner::_get_spawnable_scenes); + ClassDB::bind_method(D_METHOD("_set_spawnable_scenes", "scenes"), &MultiplayerSpawner::_set_spawnable_scenes); + + ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "_spawnable_scenes", PROPERTY_HINT_NONE, "", (PROPERTY_USAGE_STORAGE | PROPERTY_USAGE_INTERNAL)), "_set_spawnable_scenes", "_get_spawnable_scenes"); + + ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant())); + + ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path); + ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path); + ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path"); + + ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit); + ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit); + ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit"); + + GDVIRTUAL_BIND(_spawn_custom, "data"); + + ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); + ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node"))); +} + +void MultiplayerSpawner::_update_spawn_node() { +#ifdef TOOLS_ENABLED + if (Engine::get_singleton()->is_editor_hint()) { + return; + } +#endif + if (spawn_node.is_valid()) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)); + if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) { + node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); + } + } + Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path); + if (node) { + spawn_node = node->get_instance_id(); + if (get_spawnable_scene_count() && !GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom)) { + node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added)); + } + } else { + spawn_node = ObjectID(); + } +} + +void MultiplayerSpawner::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_POST_ENTER_TREE: { + _update_spawn_node(); + } break; + + case NOTIFICATION_EXIT_TREE: { + _update_spawn_node(); + + for (const KeyValue<ObjectID, SpawnInfo> &E : tracked_nodes) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(E.key)); + ERR_CONTINUE(!node); + node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit)); + // This is unlikely, but might still crash the engine. + if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) { + node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready)); + } + get_multiplayer()->object_configuration_remove(node, this); + } + tracked_nodes.clear(); + } break; + } +} + +void MultiplayerSpawner::_node_added(Node *p_node) { + if (!get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority()) { + return; + } + if (tracked_nodes.has(p_node->get_instance_id())) { + return; + } + const Node *parent = get_spawn_node(); + if (!parent || p_node->get_parent() != parent) { + return; + } + int id = find_spawnable_scene_index_from_path(p_node->get_scene_file_path()); + if (id == INVALID_ID) { + return; + } + const String name = p_node->get_name(); + ERR_FAIL_COND_MSG(name.validate_node_name() != name, vformat("Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names.", name)); + _track(p_node, Variant(), id); +} + +NodePath MultiplayerSpawner::get_spawn_path() const { + return spawn_path; +} + +void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) { + spawn_path = p_path; + _update_spawn_node(); +} + +void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_scene_id) { + ObjectID oid = p_node->get_instance_id(); + if (!tracked_nodes.has(oid)) { + tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id); + p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit), varray(p_node->get_instance_id()), CONNECT_ONESHOT); + p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready), varray(p_node->get_instance_id()), CONNECT_ONESHOT); + } +} + +void MultiplayerSpawner::_node_ready(ObjectID p_id) { + get_multiplayer()->object_configuration_add(ObjectDB::get_instance(p_id), this); +} + +void MultiplayerSpawner::_node_exit(ObjectID p_id) { + Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id)); + ERR_FAIL_COND(!node); + if (tracked_nodes.has(p_id)) { + tracked_nodes.erase(p_id); + get_multiplayer()->object_configuration_remove(node, this); + } +} + +int MultiplayerSpawner::find_spawnable_scene_index_from_path(const String &p_scene) const { + for (uint32_t i = 0; i < spawnable_scenes.size(); i++) { + if (spawnable_scenes[i].path == p_scene) { + return i; + } + } + return INVALID_ID; +} + +int MultiplayerSpawner::find_spawnable_scene_index_from_object(const ObjectID &p_id) const { + const SpawnInfo *info = tracked_nodes.getptr(p_id); + return info ? info->id : INVALID_ID; +} + +const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const { + const SpawnInfo *info = tracked_nodes.getptr(p_id); + return info ? info->args : Variant(); +} + +Node *MultiplayerSpawner::instantiate_scene(int p_id) { + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_UNSIGNED_INDEX_V((uint32_t)p_id, spawnable_scenes.size(), nullptr); + Ref<PackedScene> scene = spawnable_scenes[p_id].cache; + ERR_FAIL_COND_V(scene.is_null(), nullptr); + return scene->instantiate(); +} + +Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) { + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + Object *obj = nullptr; + Node *node = nullptr; + if (GDVIRTUAL_CALL(_spawn_custom, p_data, obj)) { + node = Object::cast_to<Node>(obj); + } + return node; +} + +Node *MultiplayerSpawner::spawn(const Variant &p_data) { + ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr); + ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!"); + ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script."); + + Node *parent = get_spawn_node(); + ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node."); + + Node *node = instantiate_custom(p_data); + ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node."); + + _track(node, p_data); + parent->add_child(node, true); + return node; +} |