diff options
Diffstat (limited to 'scene/main/node.cpp')
-rw-r--r-- | scene/main/node.cpp | 283 |
1 files changed, 196 insertions, 87 deletions
diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 34bb1cde05..b4701637a4 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -257,6 +257,9 @@ void Node::_propagate_after_exit_tree() { } if (!found) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.owner = nullptr; } @@ -297,7 +300,7 @@ void Node::_propagate_exit_tree() { if (data.parent) { Variant c = this; const Variant *cptr = &c; - data.parent->emit_signalp(SNAME("child_exited_tree"), &cptr, 1); + data.parent->emit_signalp(SNAME("child_exiting_tree"), &cptr, 1); } // exit groups @@ -381,11 +384,7 @@ void Node::_move_child(Node *p_child, int p_pos, bool p_ignore_end) { for (int i = motion_from; i <= motion_to; i++) { data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT); } - for (const KeyValue<StringName, GroupData> &E : p_child->data.grouped) { - if (E.value.group) { - E.value.group->changed = true; - } - } + p_child->_propagate_groups_dirty(); data.blocked--; } @@ -405,6 +404,18 @@ void Node::raise() { } } +void Node::_propagate_groups_dirty() { + for (const KeyValue<StringName, GroupData> &E : data.grouped) { + if (E.value.group) { + E.value.group->changed = true; + } + } + + for (int i = 0; i < data.children.size(); i++) { + data.children[i]->_propagate_groups_dirty(); + } +} + void Node::add_child_notify(Node *p_child) { // to be used when not wanted } @@ -417,6 +428,9 @@ void Node::move_child_notify(Node *p_child) { // to be used when not wanted } +void Node::owner_changed_notify() { +} + void Node::set_physics_process(bool p_process) { if (data.physics_process == p_process) { return; @@ -425,9 +439,9 @@ void Node::set_physics_process(bool p_process) { data.physics_process = p_process; if (data.physics_process) { - add_to_group("physics_process", false); + add_to_group(SNAME("_physics_process"), false); } else { - remove_from_group("physics_process"); + remove_from_group(SNAME("_physics_process")); } } @@ -443,9 +457,9 @@ void Node::set_physics_process_internal(bool p_process_internal) { data.physics_process_internal = p_process_internal; if (data.physics_process_internal) { - add_to_group("physics_process_internal", false); + add_to_group(SNAME("_physics_process_internal"), false); } else { - remove_from_group("physics_process_internal"); + remove_from_group(SNAME("_physics_process_internal")); } } @@ -650,21 +664,10 @@ void Node::rpcp(int p_peer_id, const StringName &p_method, const Variant **p_arg } Ref<MultiplayerAPI> Node::get_multiplayer() const { - if (multiplayer.is_valid()) { - return multiplayer; - } if (!is_inside_tree()) { return Ref<MultiplayerAPI>(); } - return get_tree()->get_multiplayer(); -} - -Ref<MultiplayerAPI> Node::get_custom_multiplayer() const { - return multiplayer; -} - -void Node::set_custom_multiplayer(Ref<MultiplayerAPI> p_multiplayer) { - multiplayer = p_multiplayer; + return get_tree()->get_multiplayer(get_path()); } Vector<Multiplayer::RPCConfig> Node::get_node_rpc_methods() const { @@ -767,9 +770,9 @@ void Node::set_process(bool p_process) { data.process = p_process; if (data.process) { - add_to_group("process", false); + add_to_group(SNAME("_process"), false); } else { - remove_from_group("process"); + remove_from_group(SNAME("_process")); } } @@ -785,9 +788,9 @@ void Node::set_process_internal(bool p_process_internal) { data.process_internal = p_process_internal; if (data.process_internal) { - add_to_group("process_internal", false); + add_to_group(SNAME("_process_internal"), false); } else { - remove_from_group("process_internal"); + remove_from_group(SNAME("_process_internal")); } } @@ -804,19 +807,19 @@ void Node::set_process_priority(int p_priority) { } if (is_processing()) { - data.tree->make_group_changed("process"); + data.tree->make_group_changed(SNAME("_process")); } if (is_processing_internal()) { - data.tree->make_group_changed("process_internal"); + data.tree->make_group_changed(SNAME("_process_internal")); } if (is_physics_processing()) { - data.tree->make_group_changed("physics_process"); + data.tree->make_group_changed(SNAME("_physics_process")); } if (is_physics_processing_internal()) { - data.tree->make_group_changed("physics_process_internal"); + data.tree->make_group_changed(SNAME("_physics_process_internal")); } } @@ -917,12 +920,20 @@ void Node::set_name(const String &p_name) { String name = p_name.validate_node_name(); ERR_FAIL_COND(name.is_empty()); + + if (data.unique_name_in_owner && data.owner) { + _release_unique_name_in_owner(); + } data.name = name; if (data.parent) { data.parent->_validate_child_name(this, true); } + if (data.unique_name_in_owner && data.owner) { + _acquire_unique_name_in_owner(); + } + propagate_notification(NOTIFICATION_PATH_RENAMED); if (is_inside_tree()) { @@ -1303,6 +1314,24 @@ Node *Node::get_node_or_null(const NodePath &p_path) const { next = root; } + } else if (name.is_node_unique_name()) { + if (current->data.owned_unique_nodes.size()) { + // Has unique nodes in ownership + Node **unique = current->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else if (current->data.owner) { + Node **unique = current->data.owner->data.owned_unique_nodes.getptr(name); + if (!unique) { + return nullptr; + } + next = *unique; + } else { + return nullptr; + } + } else { next = nullptr; @@ -1344,9 +1373,39 @@ bool Node::has_node(const NodePath &p_path) const { return get_node_or_null(p_path) != nullptr; } -TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bool p_recursive, bool p_owned) const { +// Finds the first child node (in tree order) whose name matches the given pattern. +// Can be recursive or not, and limited to owned nodes. +Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const { + ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr); + + Node *const *cptr = data.children.ptr(); + int ccount = data.children.size(); + for (int i = 0; i < ccount; i++) { + if (p_owned && !cptr[i]->data.owner) { + continue; + } + if (cptr[i]->data.name.operator String().match(p_pattern)) { + return cptr[i]; + } + + if (!p_recursive) { + continue; + } + + Node *ret = cptr[i]->find_child(p_pattern, true, p_owned); + if (ret) { + return ret; + } + } + return nullptr; +} + +// Finds child nodes based on their name using pattern matching, or class name, +// or both (either pattern or type can be left empty). +// Can be recursive or not, and limited to owned nodes. +TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const { TypedArray<Node> ret; - ERR_FAIL_COND_V(p_mask.is_empty() && p_type.is_empty(), ret); + ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret); Node *const *cptr = data.children.ptr(); int ccount = data.children.size(); @@ -1355,8 +1414,8 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo continue; } - if (!p_mask.is_empty()) { - if (!cptr[i]->data.name.operator String().match(p_mask)) { + if (!p_pattern.is_empty()) { + if (!cptr[i]->data.name.operator String().match(p_pattern)) { continue; } else if (p_type.is_empty()) { ret.append(cptr[i]); @@ -1378,7 +1437,7 @@ TypedArray<Node> Node::find_nodes(const String &p_mask, const String &p_type, bo } if (p_recursive) { - ret.append_array(cptr[i]->find_nodes(p_mask, p_type, true, p_owned)); + ret.append_array(cptr[i]->find_children(p_pattern, p_type, true, p_owned)); } } @@ -1389,10 +1448,10 @@ Node *Node::get_parent() const { return data.parent; } -Node *Node::find_parent(const String &p_mask) const { +Node *Node::find_parent(const String &p_pattern) const { Node *p = data.parent; while (p) { - if (p->data.name.operator String().match(p_mask)) { + if (p->data.name.operator String().match(p_pattern)) { return p; } p = p->data.parent; @@ -1496,10 +1555,60 @@ void Node::_set_owner_nocheck(Node *p_owner) { data.owner = p_owner; data.owner->data.owned.push_back(this); data.OW = data.owner->data.owned.back(); + + owner_changed_notify(); +} + +void Node::_release_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which == nullptr || *which != this) { + return; // Ignore. + } + data.owner->data.owned_unique_nodes.erase(key); +} + +void Node::_acquire_unique_name_in_owner() { + ERR_FAIL_NULL(data.owner); // Sanity check. + StringName key = StringName(UNIQUE_NODE_PREFIX + data.name.operator String()); + Node **which = data.owner->data.owned_unique_nodes.getptr(key); + if (which != nullptr && *which != this) { + String which_path = is_inside_tree() ? (*which)->get_path() : data.owner->get_path_to(*which); + WARN_PRINT(vformat(RTR("Setting node name '%s' to be unique within scene for '%s', but it's already claimed by '%s'.\n'%s' is no longer set as having a unique name."), + get_name(), is_inside_tree() ? get_path() : data.owner->get_path_to(this), which_path, which_path)); + data.unique_name_in_owner = false; + return; + } + data.owner->data.owned_unique_nodes[key] = this; +} + +void Node::set_unique_name_in_owner(bool p_enabled) { + if (data.unique_name_in_owner == p_enabled) { + return; + } + + if (data.unique_name_in_owner && data.owner != nullptr) { + _release_unique_name_in_owner(); + } + data.unique_name_in_owner = p_enabled; + + if (data.unique_name_in_owner && data.owner != nullptr) { + _acquire_unique_name_in_owner(); + } + + update_configuration_warnings(); +} + +bool Node::is_unique_name_in_owner() const { + return data.unique_name_in_owner; } void Node::set_owner(Node *p_owner) { if (data.owner) { + if (data.unique_name_in_owner) { + _release_unique_name_in_owner(); + } data.owner->data.owned.erase(data.OW); data.OW = nullptr; data.owner = nullptr; @@ -1526,6 +1635,10 @@ void Node::set_owner(Node *p_owner) { ERR_FAIL_COND(!owner_valid); _set_owner_nocheck(p_owner); + + if (data.unique_name_in_owner) { + _acquire_unique_name_in_owner(); + } } Node *Node::get_owner() const { @@ -1537,7 +1650,7 @@ Node *Node::find_common_parent_with(const Node *p_node) const { return const_cast<Node *>(p_node); } - Set<const Node *> visited; + HashSet<const Node *> visited; const Node *n = this; @@ -1569,7 +1682,7 @@ NodePath Node::get_path_to(const Node *p_node) const { return NodePath("."); } - Set<const Node *> visited; + HashSet<const Node *> visited; const Node *n = this; @@ -1663,15 +1776,15 @@ void Node::add_to_group(const StringName &p_identifier, bool p_persistent) { void Node::remove_from_group(const StringName &p_identifier) { ERR_FAIL_COND(!data.grouped.has(p_identifier)); - Map<StringName, GroupData>::Element *E = data.grouped.find(p_identifier); + HashMap<StringName, GroupData>::Iterator E = data.grouped.find(p_identifier); ERR_FAIL_COND(!E); if (data.tree) { - data.tree->remove_from_group(E->key(), this); + data.tree->remove_from_group(E->key, this); } - data.grouped.erase(E); + data.grouped.remove(E); } Array Node::_get_groups() const { @@ -1912,35 +2025,28 @@ Node *Node::get_deepest_editable_node(Node *p_start_node) const { #ifdef TOOLS_ENABLED void Node::set_property_pinned(const String &p_property, bool p_pinned) { bool current_pinned = false; - bool has_pinned = has_meta("_edit_pinned_properties_"); - Array pinned; - String psa = get_property_store_alias(p_property); - if (has_pinned) { - pinned = get_meta("_edit_pinned_properties_"); - current_pinned = pinned.has(psa); - } + Array pinned = get_meta("_edit_pinned_properties_", Array()); + StringName psa = get_property_store_alias(p_property); + current_pinned = pinned.has(psa); if (current_pinned != p_pinned) { if (p_pinned) { pinned.append(psa); - if (!has_pinned) { - set_meta("_edit_pinned_properties_", pinned); - } } else { pinned.erase(psa); - if (pinned.is_empty()) { - remove_meta("_edit_pinned_properties_"); - } } } + + if (pinned.is_empty()) { + remove_meta("_edit_pinned_properties_"); + } else { + set_meta("_edit_pinned_properties_", pinned); + } } bool Node::is_property_pinned(const StringName &p_property) const { - if (!has_meta("_edit_pinned_properties_")) { - return false; - } - Array pinned = get_meta("_edit_pinned_properties_"); - String psa = get_property_store_alias(p_property); + Array pinned = get_meta("_edit_pinned_properties_", Array()); + StringName psa = get_property_store_alias(p_property); return pinned.has(psa); } @@ -1949,7 +2055,7 @@ StringName Node::get_property_store_alias(const StringName &p_property) const { } #endif -void Node::get_storable_properties(Set<StringName> &r_storable_properties) const { +void Node::get_storable_properties(HashSet<StringName> &r_storable_properties) const { List<PropertyInfo> pi; get_property_list(&pi); for (List<PropertyInfo>::Element *E = pi.front(); E; E = E->next()) { @@ -1995,7 +2101,7 @@ bool Node::get_scene_instance_load_placeholder() const { return data.use_placeholder; } -Node *Node::_duplicate(int p_flags, Map<const Node *, Node *> *r_duplimap) const { +Node *Node::_duplicate(int p_flags, HashMap<const Node *, Node *> *r_duplimap) const { Node *node = nullptr; bool instantiated = false; @@ -2187,11 +2293,11 @@ Node *Node::duplicate(int p_flags) const { } #ifdef TOOLS_ENABLED -Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const { - return duplicate_from_editor(r_duplimap, Map<RES, RES>()); +Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap) const { + return duplicate_from_editor(r_duplimap, HashMap<Ref<Resource>, Ref<Resource>>()); } -Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const { +Node *Node::duplicate_from_editor(HashMap<const Node *, Node *> &r_duplimap, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANCING | DUPLICATE_FROM_EDITOR, &r_duplimap); // This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated. @@ -2207,7 +2313,7 @@ Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const M return dupe; } -void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const { +void Node::remap_node_resources(Node *p_node, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { List<PropertyInfo> props; p_node->get_property_list(&props); @@ -2218,7 +2324,7 @@ void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_re Variant v = p_node->get(E.name); if (v.is_ref_counted()) { - RES res = v; + Ref<Resource> res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { p_node->set(E.name, p_resource_remap[res]); @@ -2233,7 +2339,7 @@ void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_re } } -void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const { +void Node::remap_nested_resources(Ref<Resource> p_resource, const HashMap<Ref<Resource>, Ref<Resource>> &p_resource_remap) const { List<PropertyInfo> props; p_resource->get_property_list(&props); @@ -2244,7 +2350,7 @@ void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resourc Variant v = p_resource->get(E.name); if (v.is_ref_counted()) { - RES res = v; + Ref<Resource> res = v; if (res.is_valid()) { if (p_resource_remap.has(res)) { p_resource->set(E.name, p_resource_remap[res]); @@ -2393,7 +2499,7 @@ bool Node::has_node_and_resource(const NodePath &p_path) const { if (!has_node(p_path)) { return false; } - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = get_node_and_resource(p_path, res, leftover_path, false); @@ -2401,7 +2507,7 @@ bool Node::has_node_and_resource(const NodePath &p_path) const { } Array Node::_get_node_and_resource(const NodePath &p_path) { - RES res; + Ref<Resource> res; Vector<StringName> leftover_path; Node *node = get_node_and_resource(p_path, res, leftover_path, false); Array result; @@ -2423,9 +2529,9 @@ Array Node::_get_node_and_resource(const NodePath &p_path) { return result; } -Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { +Node *Node::get_node_and_resource(const NodePath &p_path, Ref<Resource> &r_res, Vector<StringName> &r_leftover_subpath, bool p_last_is_property) const { Node *node = get_node(p_path); - r_res = RES(); + r_res = Ref<Resource>(); r_leftover_subpath = Vector<StringName>(); if (!node) { return nullptr; @@ -2435,13 +2541,14 @@ Node *Node::get_node_and_resource(const NodePath &p_path, RES &r_res, Vector<Str int j = 0; // If not p_last_is_property, we shouldn't consider the last one as part of the resource for (; j < p_path.get_subname_count() - (int)p_last_is_property; j++) { - Variant new_res_v = j == 0 ? node->get(p_path.get_subname(j)) : r_res->get(p_path.get_subname(j)); + bool is_valid = false; + Variant new_res_v = j == 0 ? node->get(p_path.get_subname(j), &is_valid) : r_res->get(p_path.get_subname(j), &is_valid); - if (new_res_v.get_type() == Variant::NIL) { // Found nothing on that path + if (!is_valid) { // Found nothing on that path return nullptr; } - RES new_res = new_res_v; + Ref<Resource> new_res = new_res_v; if (new_res.is_null()) { // No longer a resource, assume property break; @@ -2585,16 +2692,16 @@ void Node::clear_internal_tree_resource_paths() { } TypedArray<String> Node::get_configuration_warnings() const { + TypedArray<String> ret; + Vector<String> warnings; if (GDVIRTUAL_CALL(_get_configuration_warnings, warnings)) { - TypedArray<String> ret; - ret.resize(warnings.size()); for (int i = 0; i < warnings.size(); i++) { - ret[i] = warnings[i]; + ret.push_back(warnings[i]); } - return ret; } - return Array(); + + return ret; } String Node::get_configuration_warnings_as_string() const { @@ -2701,8 +2808,9 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("get_node", "path"), &Node::get_node); ClassDB::bind_method(D_METHOD("get_node_or_null", "path"), &Node::get_node_or_null); ClassDB::bind_method(D_METHOD("get_parent"), &Node::get_parent); - ClassDB::bind_method(D_METHOD("find_nodes", "mask", "type", "recursive", "owned"), &Node::find_nodes, DEFVAL(""), DEFVAL(true), DEFVAL(true)); - ClassDB::bind_method(D_METHOD("find_parent", "mask"), &Node::find_parent); + ClassDB::bind_method(D_METHOD("find_child", "pattern", "recursive", "owned"), &Node::find_child, DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("find_children", "pattern", "type", "recursive", "owned"), &Node::find_children, DEFVAL(""), DEFVAL(true), DEFVAL(true)); + ClassDB::bind_method(D_METHOD("find_parent", "pattern"), &Node::find_parent); ClassDB::bind_method(D_METHOD("has_node_and_resource", "path"), &Node::has_node_and_resource); ClassDB::bind_method(D_METHOD("get_node_and_resource", "path"), &Node::_get_node_and_resource); @@ -2780,8 +2888,6 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("is_multiplayer_authority"), &Node::is_multiplayer_authority); ClassDB::bind_method(D_METHOD("get_multiplayer"), &Node::get_multiplayer); - ClassDB::bind_method(D_METHOD("get_custom_multiplayer"), &Node::get_custom_multiplayer); - ClassDB::bind_method(D_METHOD("set_custom_multiplayer", "api"), &Node::set_custom_multiplayer); ClassDB::bind_method(D_METHOD("rpc_config", "method", "rpc_mode", "call_local", "transfer_mode", "channel"), &Node::rpc_config, DEFVAL(false), DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0)); ClassDB::bind_method(D_METHOD("set_editor_description", "editor_description"), &Node::set_editor_description); @@ -2790,6 +2896,9 @@ void Node::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_import_path", "import_path"), &Node::set_import_path); ClassDB::bind_method(D_METHOD("_get_import_path"), &Node::get_import_path); + ClassDB::bind_method(D_METHOD("set_unique_name_in_owner", "enable"), &Node::set_unique_name_in_owner); + ClassDB::bind_method(D_METHOD("is_unique_name_in_owner"), &Node::is_unique_name_in_owner); + #ifdef TOOLS_ENABLED ClassDB::bind_method(D_METHOD("_set_property_pinned", "property", "pinned"), &Node::set_property_pinned); #endif @@ -2877,13 +2986,13 @@ void Node::_bind_methods() { ADD_SIGNAL(MethodInfo("tree_exiting")); ADD_SIGNAL(MethodInfo("tree_exited")); ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); - ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); + ADD_SIGNAL(MethodInfo("child_exiting_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node"))); ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner"); ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_owner", "get_owner"); ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "", "get_multiplayer"); - ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "custom_multiplayer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerAPI", PROPERTY_USAGE_NONE), "set_custom_multiplayer", "get_custom_multiplayer"); ADD_GROUP("Process", "process_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "process_mode", PROPERTY_HINT_ENUM, "Inherit,Pausable,When Paused,Always,Disabled"), "set_process_mode", "get_process_mode"); |