/*************************************************************************/ /* visual_script.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2020 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 "visual_script.h" #include #include "core/core_string_names.h" #include "core/os/os.h" #include "core/project_settings.h" #include "scene/main/node.h" #include "visual_script_nodes.h" //used by editor, this is not really saved void VisualScriptNode::set_breakpoint(bool p_breakpoint) { breakpoint = p_breakpoint; } bool VisualScriptNode::is_breakpoint() const { return breakpoint; } void VisualScriptNode::ports_changed_notify() { emit_signal("ports_changed"); } void VisualScriptNode::set_default_input_value(int p_port, const Variant &p_value) { ERR_FAIL_INDEX(p_port, default_input_values.size()); default_input_values[p_port] = p_value; #ifdef TOOLS_ENABLED for (Set::Element *E = scripts_used.front(); E; E = E->next()) { E->get()->set_edited(true); } #endif } Variant VisualScriptNode::get_default_input_value(int p_port) const { ERR_FAIL_INDEX_V(p_port, default_input_values.size(), Variant()); return default_input_values[p_port]; } void VisualScriptNode::_set_default_input_values(Array p_values) { default_input_values = p_values; } void VisualScriptNode::validate_input_default_values() { default_input_values.resize(MAX(default_input_values.size(), get_input_value_port_count())); //let it grow as big as possible, we don't want to lose values on resize //actually validate on save for (int i = 0; i < get_input_value_port_count(); i++) { Variant::Type expected = get_input_value_port_info(i).type; if (expected == Variant::NIL || expected == default_input_values[i].get_type()) { continue; } else { //not the same, reconvert Callable::CallError ce; Variant existing = default_input_values[i]; const Variant *existingp = &existing; default_input_values[i] = Variant::construct(expected, &existingp, 1, ce, false); if (ce.error != Callable::CallError::CALL_OK) { //could not convert? force.. default_input_values[i] = Variant::construct(expected, NULL, 0, ce, false); } } } } Array VisualScriptNode::_get_default_input_values() const { //validate on save, since on load there is little info about this Array values = default_input_values; values.resize(get_input_value_port_count()); return values; } String VisualScriptNode::get_text() const { return ""; } void VisualScriptNode::_bind_methods() { ClassDB::bind_method(D_METHOD("get_visual_script"), &VisualScriptNode::get_visual_script); ClassDB::bind_method(D_METHOD("set_default_input_value", "port_idx", "value"), &VisualScriptNode::set_default_input_value); ClassDB::bind_method(D_METHOD("get_default_input_value", "port_idx"), &VisualScriptNode::get_default_input_value); ClassDB::bind_method(D_METHOD("ports_changed_notify"), &VisualScriptNode::ports_changed_notify); ClassDB::bind_method(D_METHOD("_set_default_input_values", "values"), &VisualScriptNode::_set_default_input_values); ClassDB::bind_method(D_METHOD("_get_default_input_values"), &VisualScriptNode::_get_default_input_values); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "_default_input_values", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_default_input_values", "_get_default_input_values"); ADD_SIGNAL(MethodInfo("ports_changed")); } VisualScriptNode::TypeGuess VisualScriptNode::guess_output_type(TypeGuess *p_inputs, int p_output) const { PropertyInfo pinfo = get_output_value_port_info(p_output); TypeGuess tg; tg.type = pinfo.type; if (pinfo.hint == PROPERTY_HINT_RESOURCE_TYPE) { tg.gdclass = pinfo.hint_string; } return tg; } Ref VisualScriptNode::get_visual_script() const { if (scripts_used.size()) return Ref(scripts_used.front()->get()); return Ref(); } VisualScriptNode::VisualScriptNode() { breakpoint = false; } //////////////// ///////////////////// VisualScriptNodeInstance::VisualScriptNodeInstance() { sequence_outputs = NULL; input_ports = NULL; } VisualScriptNodeInstance::~VisualScriptNodeInstance() { if (sequence_outputs) { memdelete_arr(sequence_outputs); } if (input_ports) { memdelete_arr(input_ports); } if (output_ports) { memdelete_arr(output_ports); } } void VisualScript::add_function(const StringName &p_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!String(p_name).is_valid_identifier()); ERR_FAIL_COND(functions.has(p_name)); functions[p_name] = Function(); functions[p_name].scroll = Vector2(-50, -100); } bool VisualScript::has_function(const StringName &p_name) const { return functions.has(p_name); } void VisualScript::remove_function(const StringName &p_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_name)); for (Map::Element *E = functions[p_name].nodes.front(); E; E = E->next()) { E->get().node->disconnect_compat("ports_changed", this, "_node_ports_changed"); E->get().node->scripts_used.erase(this); } functions.erase(p_name); } void VisualScript::rename_function(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_name)); if (p_new_name == p_name) return; ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); ERR_FAIL_COND(functions.has(p_new_name)); ERR_FAIL_COND(variables.has(p_new_name)); ERR_FAIL_COND(custom_signals.has(p_new_name)); functions[p_new_name] = functions[p_name]; functions.erase(p_name); } void VisualScript::set_function_scroll(const StringName &p_name, const Vector2 &p_scroll) { ERR_FAIL_COND(!functions.has(p_name)); functions[p_name].scroll = p_scroll; } Vector2 VisualScript::get_function_scroll(const StringName &p_name) const { ERR_FAIL_COND_V(!functions.has(p_name), Vector2()); return functions[p_name].scroll; } void VisualScript::get_function_list(List *r_functions) const { for (const Map::Element *E = functions.front(); E; E = E->next()) { r_functions->push_back(E->key()); } r_functions->sort_custom(); } int VisualScript::get_function_node_id(const StringName &p_name) const { ERR_FAIL_COND_V(!functions.has(p_name), -1); return functions[p_name].function_id; } void VisualScript::_node_ports_changed(int p_id) { StringName function; for (Map::Element *E = functions.front(); E; E = E->next()) { if (E->get().nodes.has(p_id)) { function = E->key(); break; } } ERR_FAIL_COND(function == StringName()); Function &func = functions[function]; Ref vsn = func.nodes[p_id].node; vsn->validate_input_default_values(); //must revalidate all the functions { List to_remove; for (Set::Element *E = func.sequence_connections.front(); E; E = E->next()) { if (E->get().from_node == p_id && E->get().from_output >= vsn->get_output_sequence_port_count()) { to_remove.push_back(E->get()); } if (E->get().to_node == p_id && !vsn->has_input_sequence_port()) { to_remove.push_back(E->get()); } } while (to_remove.size()) { func.sequence_connections.erase(to_remove.front()->get()); to_remove.pop_front(); } } { List to_remove; for (Set::Element *E = func.data_connections.front(); E; E = E->next()) { if (E->get().from_node == p_id && E->get().from_port >= vsn->get_output_value_port_count()) { to_remove.push_back(E->get()); } if (E->get().to_node == p_id && E->get().to_port >= vsn->get_input_value_port_count()) { to_remove.push_back(E->get()); } } while (to_remove.size()) { func.data_connections.erase(to_remove.front()->get()); to_remove.pop_front(); } } #ifdef TOOLS_ENABLED set_edited(true); //something changed, let's set as edited emit_signal("node_ports_changed", function, p_id); #endif } void VisualScript::add_node(const StringName &p_func, int p_id, const Ref &p_node, const Point2 &p_pos) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_func)); for (Map::Element *E = functions.front(); E; E = E->next()) { ERR_FAIL_COND(E->get().nodes.has(p_id)); //id can exist only one in script, even for different functions } Function &func = functions[p_func]; if (Object::cast_to(*p_node)) { //the function indeed ERR_FAIL_COND_MSG(func.function_id >= 0, "A function node has already been set here."); func.function_id = p_id; } Function::NodeData nd; nd.node = p_node; nd.pos = p_pos; Ref vsn = p_node; vsn->connect_compat("ports_changed", this, "_node_ports_changed", varray(p_id)); vsn->scripts_used.insert(this); vsn->validate_input_default_values(); // Validate when fully loaded func.nodes[p_id] = nd; } void VisualScript::remove_node(const StringName &p_func, int p_id) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; ERR_FAIL_COND(!func.nodes.has(p_id)); { List to_remove; for (Set::Element *E = func.sequence_connections.front(); E; E = E->next()) { if (E->get().from_node == p_id || E->get().to_node == p_id) { to_remove.push_back(E->get()); } } while (to_remove.size()) { func.sequence_connections.erase(to_remove.front()->get()); to_remove.pop_front(); } } { List to_remove; for (Set::Element *E = func.data_connections.front(); E; E = E->next()) { if (E->get().from_node == p_id || E->get().to_node == p_id) { to_remove.push_back(E->get()); } } while (to_remove.size()) { func.data_connections.erase(to_remove.front()->get()); to_remove.pop_front(); } } if (Object::cast_to(func.nodes[p_id].node.ptr())) { func.function_id = -1; //revert to invalid } func.nodes[p_id].node->disconnect_compat("ports_changed", this, "_node_ports_changed"); func.nodes[p_id].node->scripts_used.erase(this); func.nodes.erase(p_id); } bool VisualScript::has_node(const StringName &p_func, int p_id) const { ERR_FAIL_COND_V(!functions.has(p_func), false); const Function &func = functions[p_func]; return func.nodes.has(p_id); } Ref VisualScript::get_node(const StringName &p_func, int p_id) const { ERR_FAIL_COND_V(!functions.has(p_func), Ref()); const Function &func = functions[p_func]; ERR_FAIL_COND_V(!func.nodes.has(p_id), Ref()); return func.nodes[p_id].node; } void VisualScript::set_node_position(const StringName &p_func, int p_id, const Point2 &p_pos) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; ERR_FAIL_COND(!func.nodes.has(p_id)); func.nodes[p_id].pos = p_pos; } Point2 VisualScript::get_node_position(const StringName &p_func, int p_id) const { ERR_FAIL_COND_V(!functions.has(p_func), Point2()); const Function &func = functions[p_func]; ERR_FAIL_COND_V(!func.nodes.has(p_id), Point2()); return func.nodes[p_id].pos; } void VisualScript::get_node_list(const StringName &p_func, List *r_nodes) const { ERR_FAIL_COND(!functions.has(p_func)); const Function &func = functions[p_func]; for (const Map::Element *E = func.nodes.front(); E; E = E->next()) { r_nodes->push_back(E->key()); } } void VisualScript::sequence_connect(const StringName &p_func, int p_from_node, int p_from_output, int p_to_node) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; SequenceConnection sc; sc.from_node = p_from_node; sc.from_output = p_from_output; sc.to_node = p_to_node; ERR_FAIL_COND(func.sequence_connections.has(sc)); func.sequence_connections.insert(sc); } void VisualScript::sequence_disconnect(const StringName &p_func, int p_from_node, int p_from_output, int p_to_node) { ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; SequenceConnection sc; sc.from_node = p_from_node; sc.from_output = p_from_output; sc.to_node = p_to_node; ERR_FAIL_COND(!func.sequence_connections.has(sc)); func.sequence_connections.erase(sc); } bool VisualScript::has_sequence_connection(const StringName &p_func, int p_from_node, int p_from_output, int p_to_node) const { ERR_FAIL_COND_V(!functions.has(p_func), false); const Function &func = functions[p_func]; SequenceConnection sc; sc.from_node = p_from_node; sc.from_output = p_from_output; sc.to_node = p_to_node; return func.sequence_connections.has(sc); } void VisualScript::get_sequence_connection_list(const StringName &p_func, List *r_connection) const { ERR_FAIL_COND(!functions.has(p_func)); const Function &func = functions[p_func]; for (const Set::Element *E = func.sequence_connections.front(); E; E = E->next()) { r_connection->push_back(E->get()); } } void VisualScript::data_connect(const StringName &p_func, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; DataConnection dc; dc.from_node = p_from_node; dc.from_port = p_from_port; dc.to_node = p_to_node; dc.to_port = p_to_port; ERR_FAIL_COND(func.data_connections.has(dc)); func.data_connections.insert(dc); } void VisualScript::data_disconnect(const StringName &p_func, int p_from_node, int p_from_port, int p_to_node, int p_to_port) { ERR_FAIL_COND(!functions.has(p_func)); Function &func = functions[p_func]; DataConnection dc; dc.from_node = p_from_node; dc.from_port = p_from_port; dc.to_node = p_to_node; dc.to_port = p_to_port; ERR_FAIL_COND(!func.data_connections.has(dc)); func.data_connections.erase(dc); } bool VisualScript::has_data_connection(const StringName &p_func, int p_from_node, int p_from_port, int p_to_node, int p_to_port) const { ERR_FAIL_COND_V(!functions.has(p_func), false); const Function &func = functions[p_func]; DataConnection dc; dc.from_node = p_from_node; dc.from_port = p_from_port; dc.to_node = p_to_node; dc.to_port = p_to_port; return func.data_connections.has(dc); } bool VisualScript::is_input_value_port_connected(const StringName &p_func, int p_node, int p_port) const { ERR_FAIL_COND_V(!functions.has(p_func), false); const Function &func = functions[p_func]; for (const Set::Element *E = func.data_connections.front(); E; E = E->next()) { if (E->get().to_node == p_node && E->get().to_port == p_port) return true; } return false; } bool VisualScript::get_input_value_port_connection_source(const StringName &p_func, int p_node, int p_port, int *r_node, int *r_port) const { ERR_FAIL_COND_V(!functions.has(p_func), false); const Function &func = functions[p_func]; for (const Set::Element *E = func.data_connections.front(); E; E = E->next()) { if (E->get().to_node == p_node && E->get().to_port == p_port) { *r_node = E->get().from_node; *r_port = E->get().from_port; return true; } } return false; } void VisualScript::get_data_connection_list(const StringName &p_func, List *r_connection) const { ERR_FAIL_COND(!functions.has(p_func)); const Function &func = functions[p_func]; for (const Set::Element *E = func.data_connections.front(); E; E = E->next()) { r_connection->push_back(E->get()); } } void VisualScript::set_tool_enabled(bool p_enabled) { is_tool_script = p_enabled; } void VisualScript::add_variable(const StringName &p_name, const Variant &p_default_value, bool p_export) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!String(p_name).is_valid_identifier()); ERR_FAIL_COND(variables.has(p_name)); Variable v; v.default_value = p_default_value; v.info.type = p_default_value.get_type(); v.info.name = p_name; v.info.hint = PROPERTY_HINT_NONE; v._export = p_export; variables[p_name] = v; #ifdef TOOLS_ENABLED _update_placeholders(); #endif } bool VisualScript::has_variable(const StringName &p_name) const { return variables.has(p_name); } void VisualScript::remove_variable(const StringName &p_name) { ERR_FAIL_COND(!variables.has(p_name)); variables.erase(p_name); #ifdef TOOLS_ENABLED _update_placeholders(); #endif } void VisualScript::set_variable_default_value(const StringName &p_name, const Variant &p_value) { ERR_FAIL_COND(!variables.has(p_name)); variables[p_name].default_value = p_value; #ifdef TOOLS_ENABLED _update_placeholders(); #endif } Variant VisualScript::get_variable_default_value(const StringName &p_name) const { ERR_FAIL_COND_V(!variables.has(p_name), Variant()); return variables[p_name].default_value; } void VisualScript::set_variable_info(const StringName &p_name, const PropertyInfo &p_info) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!variables.has(p_name)); variables[p_name].info = p_info; variables[p_name].info.name = p_name; #ifdef TOOLS_ENABLED _update_placeholders(); #endif } PropertyInfo VisualScript::get_variable_info(const StringName &p_name) const { ERR_FAIL_COND_V(!variables.has(p_name), PropertyInfo()); return variables[p_name].info; } void VisualScript::set_variable_export(const StringName &p_name, bool p_export) { ERR_FAIL_COND(!variables.has(p_name)); variables[p_name]._export = p_export; #ifdef TOOLS_ENABLED _update_placeholders(); #endif } bool VisualScript::get_variable_export(const StringName &p_name) const { ERR_FAIL_COND_V(!variables.has(p_name), false); return variables[p_name]._export; } void VisualScript::_set_variable_info(const StringName &p_name, const Dictionary &p_info) { PropertyInfo pinfo; if (p_info.has("type")) pinfo.type = Variant::Type(int(p_info["type"])); if (p_info.has("name")) pinfo.name = p_info["name"]; if (p_info.has("hint")) pinfo.hint = PropertyHint(int(p_info["hint"])); if (p_info.has("hint_string")) pinfo.hint_string = p_info["hint_string"]; if (p_info.has("usage")) pinfo.usage = p_info["usage"]; set_variable_info(p_name, pinfo); } Dictionary VisualScript::_get_variable_info(const StringName &p_name) const { PropertyInfo pinfo = get_variable_info(p_name); Dictionary d; d["type"] = pinfo.type; d["name"] = pinfo.name; d["hint"] = pinfo.hint; d["hint_string"] = pinfo.hint_string; d["usage"] = pinfo.usage; return d; } void VisualScript::get_variable_list(List *r_variables) const { for (Map::Element *E = variables.front(); E; E = E->next()) { r_variables->push_back(E->key()); } r_variables->sort_custom(); } void VisualScript::set_instance_base_type(const StringName &p_type) { ERR_FAIL_COND(instances.size()); base_type = p_type; } void VisualScript::rename_variable(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!variables.has(p_name)); if (p_new_name == p_name) return; ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); ERR_FAIL_COND(functions.has(p_new_name)); ERR_FAIL_COND(variables.has(p_new_name)); ERR_FAIL_COND(custom_signals.has(p_new_name)); variables[p_new_name] = variables[p_name]; variables.erase(p_name); } void VisualScript::add_custom_signal(const StringName &p_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!String(p_name).is_valid_identifier()); ERR_FAIL_COND(custom_signals.has(p_name)); custom_signals[p_name] = Vector(); } bool VisualScript::has_custom_signal(const StringName &p_name) const { return custom_signals.has(p_name); } void VisualScript::custom_signal_add_argument(const StringName &p_func, Variant::Type p_type, const String &p_name, int p_index) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); Argument arg; arg.type = p_type; arg.name = p_name; if (p_index < 0) custom_signals[p_func].push_back(arg); else custom_signals[p_func].insert(0, arg); } void VisualScript::custom_signal_set_argument_type(const StringName &p_func, int p_argidx, Variant::Type p_type) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); custom_signals[p_func].write[p_argidx].type = p_type; } Variant::Type VisualScript::custom_signal_get_argument_type(const StringName &p_func, int p_argidx) const { ERR_FAIL_COND_V(!custom_signals.has(p_func), Variant::NIL); ERR_FAIL_INDEX_V(p_argidx, custom_signals[p_func].size(), Variant::NIL); return custom_signals[p_func][p_argidx].type; } void VisualScript::custom_signal_set_argument_name(const StringName &p_func, int p_argidx, const String &p_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); custom_signals[p_func].write[p_argidx].name = p_name; } String VisualScript::custom_signal_get_argument_name(const StringName &p_func, int p_argidx) const { ERR_FAIL_COND_V(!custom_signals.has(p_func), String()); ERR_FAIL_INDEX_V(p_argidx, custom_signals[p_func].size(), String()); return custom_signals[p_func][p_argidx].name; } void VisualScript::custom_signal_remove_argument(const StringName &p_func, int p_argidx) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); custom_signals[p_func].remove(p_argidx); } int VisualScript::custom_signal_get_argument_count(const StringName &p_func) const { ERR_FAIL_COND_V(!custom_signals.has(p_func), 0); return custom_signals[p_func].size(); } void VisualScript::custom_signal_swap_argument(const StringName &p_func, int p_argidx, int p_with_argidx) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_func)); ERR_FAIL_INDEX(p_argidx, custom_signals[p_func].size()); ERR_FAIL_INDEX(p_with_argidx, custom_signals[p_func].size()); SWAP(custom_signals[p_func].write[p_argidx], custom_signals[p_func].write[p_with_argidx]); } void VisualScript::remove_custom_signal(const StringName &p_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_name)); custom_signals.erase(p_name); } void VisualScript::rename_custom_signal(const StringName &p_name, const StringName &p_new_name) { ERR_FAIL_COND(instances.size()); ERR_FAIL_COND(!custom_signals.has(p_name)); if (p_new_name == p_name) return; ERR_FAIL_COND(!String(p_new_name).is_valid_identifier()); ERR_FAIL_COND(functions.has(p_new_name)); ERR_FAIL_COND(variables.has(p_new_name)); ERR_FAIL_COND(custom_signals.has(p_new_name)); custom_signals[p_new_name] = custom_signals[p_name]; custom_signals.erase(p_name); } void VisualScript::get_custom_signal_list(List *r_custom_signals) const { for (const Map >::Element *E = custom_signals.front(); E; E = E->next()) { r_custom_signals->push_back(E->key()); } r_custom_signals->sort_custom(); } int VisualScript::get_available_id() const { int max_id = 0; for (Map::Element *E = functions.front(); E; E = E->next()) { if (E->get().nodes.empty()) continue; int last_id = E->get().nodes.back()->key(); max_id = MAX(max_id, last_id + 1); } return max_id; } ///////////////////////////////// bool VisualScript::can_instance() const { return true; //ScriptServer::is_scripting_enabled(); } StringName VisualScript::get_instance_base_type() const { return base_type; } Ref