mirror of
https://github.com/Relintai/pandemonium_engine.git
synced 2024-11-22 00:48:09 +01:00
Ported godot pr: [3.x] Allow exporting custom resources from/to any scripting language (GDScript, VisualScript, C#, NativeScript, PluginScript)
- willnationsdev
https://github.com/godotengine/godot/pull/44879
Using the rebased version from 02d1f70ee5
by Atlinx
This commit is contained in:
parent
2a7a431c94
commit
0a9c9ca15e
@ -38,6 +38,7 @@
|
||||
#include "core/io/marshalls.h"
|
||||
#include "core/math/geometry.h"
|
||||
#include "core/object/method_bind_ext.gen.inc"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/os.h"
|
||||
|
||||
@ -3127,6 +3128,90 @@ _ClassDB::_ClassDB() {
|
||||
}
|
||||
_ClassDB::~_ClassDB() {
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
bool _ScriptServer::_set(const StringName &p_name, const Variant &p_value) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _ScriptServer::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (ScriptServer::is_global_class(p_name)) {
|
||||
r_ret = ResourceLoader::load(ScriptServer::get_global_class_path(p_name), "Script");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void _ScriptServer::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
ERR_FAIL_COND(!p_list);
|
||||
List<StringName> names;
|
||||
ScriptServer::get_global_class_list(&names);
|
||||
for (List<StringName>::Element *E = names.front(); E; E = E->next()) {
|
||||
StringName n = E->get();
|
||||
String class_name = String(n).get_file().get_extension();
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, class_name, PROPERTY_HINT_RESOURCE_TYPE, "Script", PROPERTY_USAGE_NETWORK, ResourceLoader::get_resource_type(ScriptServer::get_global_class_path(n))));
|
||||
}
|
||||
}
|
||||
|
||||
bool _ScriptServer::is_global_class(const StringName &p_class) const {
|
||||
return ScriptServer::is_global_class(p_class);
|
||||
}
|
||||
|
||||
String _ScriptServer::get_global_class_path(const String &p_class) const {
|
||||
return ScriptServer::get_global_class_path(p_class);
|
||||
}
|
||||
|
||||
StringName _ScriptServer::get_global_class_base(const String &p_class) const {
|
||||
return ScriptServer::get_global_class_base(p_class);
|
||||
}
|
||||
|
||||
StringName _ScriptServer::get_global_class_native_base(const String &p_class) const {
|
||||
return ScriptServer::get_global_class_native_base(p_class);
|
||||
}
|
||||
|
||||
StringName _ScriptServer::get_global_class_name(const String &p_path) const {
|
||||
return ScriptServer::get_global_class_name(p_path);
|
||||
}
|
||||
|
||||
Ref<Script> _ScriptServer::get_global_class_script(const StringName &p_class) const {
|
||||
return ScriptServer::get_global_class_script(p_class);
|
||||
}
|
||||
|
||||
Variant _ScriptServer::instantiate_global_class(const StringName &p_class) const {
|
||||
return ScriptServer::instantiate_global_class(p_class);
|
||||
}
|
||||
|
||||
Array _ScriptServer::get_global_class_list() const {
|
||||
Array ret;
|
||||
List<StringName> lst;
|
||||
ScriptServer::get_global_class_list(&lst);
|
||||
for (List<StringName>::Element *E = lst.front(); E; E = E->next()) {
|
||||
ret.push_back(E->get());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void _ScriptServer::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("is_global_class", "class"), &_ScriptServer::is_global_class);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_path", "class"), &_ScriptServer::get_global_class_path);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_base", "class"), &_ScriptServer::get_global_class_base);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_native_base", "class"), &_ScriptServer::get_global_class_native_base);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_name", "path"), &_ScriptServer::get_global_class_name);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_script", "class"), &_ScriptServer::get_global_class_script);
|
||||
ClassDB::bind_method(D_METHOD("instantiate_global_class", "class"), &_ScriptServer::instantiate_global_class);
|
||||
ClassDB::bind_method(D_METHOD("get_global_class_list"), &_ScriptServer::get_global_class_list);
|
||||
}
|
||||
|
||||
_ScriptServer::_ScriptServer() {
|
||||
singleton = this;
|
||||
}
|
||||
|
||||
_ScriptServer::~_ScriptServer() {
|
||||
}
|
||||
|
||||
_ScriptServer *_ScriptServer::singleton = nullptr;
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void _Engine::set_physics_ticks_per_second(int p_ips) {
|
||||
|
@ -806,6 +806,33 @@ public:
|
||||
~_ClassDB();
|
||||
};
|
||||
|
||||
class _ScriptServer : public Object {
|
||||
GDCLASS(_ScriptServer, Object);
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
static _ScriptServer *singleton;
|
||||
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
|
||||
public:
|
||||
static _ScriptServer *get_singleton() { return singleton; }
|
||||
|
||||
bool is_global_class(const StringName &p_class) const;
|
||||
String get_global_class_path(const String &p_class) const;
|
||||
StringName get_global_class_base(const String &p_class) const;
|
||||
StringName get_global_class_native_base(const String &p_class) const;
|
||||
StringName get_global_class_name(const String &p_path) const;
|
||||
Ref<Script> get_global_class_script(const StringName &p_class) const;
|
||||
Variant instantiate_global_class(const StringName &p_class) const;
|
||||
Array get_global_class_list() const;
|
||||
|
||||
_ScriptServer();
|
||||
~_ScriptServer();
|
||||
};
|
||||
|
||||
class _Engine : public Object {
|
||||
GDCLASS(_Engine, Object);
|
||||
|
||||
|
@ -32,6 +32,7 @@
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_string_names.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
|
||||
ScriptLanguage *ScriptServer::_languages[MAX_LANGUAGES];
|
||||
int ScriptServer::_language_count = 0;
|
||||
@ -200,20 +201,28 @@ void ScriptServer::thread_exit() {
|
||||
}
|
||||
|
||||
HashMap<StringName, ScriptServer::GlobalScriptClass> ScriptServer::global_classes;
|
||||
HashMap<String, StringName> ScriptServer::global_class_paths;
|
||||
|
||||
void ScriptServer::global_classes_clear() {
|
||||
global_classes.clear();
|
||||
global_class_paths.clear();
|
||||
}
|
||||
|
||||
void ScriptServer::add_global_class(const StringName &p_class, const StringName &p_base, const StringName &p_language, const String &p_path) {
|
||||
ERR_FAIL_COND_MSG(p_class == p_base || (global_classes.has(p_base) && get_global_class_native_base(p_base) == p_class), "Cyclic inheritance in script class.");
|
||||
ERR_FAIL_COND_MSG(p_class == StringName(), vformat("Attempted to register global script class at path '%s' without a class name.", p_path));
|
||||
ERR_FAIL_COND_MSG(p_base == StringName(), vformat("Attempted to register global script class at path '%s' without a base name.", p_path));
|
||||
ERR_FAIL_COND_MSG(p_language == StringName(), vformat("Attempted to register global script class at path '%s' without a language name.", p_path));
|
||||
ERR_FAIL_COND_MSG(p_path.empty(), vformat("Attempted to register global script class named '%s' with an empty path.", p_class));
|
||||
GlobalScriptClass g;
|
||||
g.language = p_language;
|
||||
g.path = p_path;
|
||||
g.base = p_base;
|
||||
global_classes[p_class] = g;
|
||||
global_class_paths[p_path] = p_class;
|
||||
}
|
||||
void ScriptServer::remove_global_class(const StringName &p_class) {
|
||||
global_class_paths.erase(global_classes[p_class].path);
|
||||
global_classes.erase(p_class);
|
||||
}
|
||||
bool ScriptServer::is_global_class(const StringName &p_class) {
|
||||
@ -223,23 +232,71 @@ StringName ScriptServer::get_global_class_language(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
|
||||
return global_classes[p_class].language;
|
||||
}
|
||||
String ScriptServer::get_global_class_path(const String &p_class) {
|
||||
String ScriptServer::get_global_class_path(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
return global_classes[p_class].path;
|
||||
}
|
||||
|
||||
StringName ScriptServer::get_global_class_base(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
StringName ScriptServer::get_global_class_name(const String &p_path) {
|
||||
if (global_class_paths.has(p_path)) {
|
||||
return global_class_paths[p_path];
|
||||
}
|
||||
return StringName();
|
||||
}
|
||||
|
||||
StringName ScriptServer::get_global_class_base(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
|
||||
return global_classes[p_class].base;
|
||||
}
|
||||
StringName ScriptServer::get_global_class_native_base(const String &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), String());
|
||||
StringName ScriptServer::get_global_class_native_base(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V(!global_classes.has(p_class), StringName());
|
||||
String base = global_classes[p_class].base;
|
||||
while (global_classes.has(base)) {
|
||||
base = global_classes[base].base;
|
||||
}
|
||||
return base;
|
||||
}
|
||||
|
||||
Ref<Script> ScriptServer::get_global_class_script(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V_MSG(!ScriptServer::is_global_class(p_class), Ref<Script>(), vformat("Class to load '%s' is not a script class.", p_class));
|
||||
if (!ScriptServer::is_global_class(p_class)) {
|
||||
return Ref<Script>();
|
||||
}
|
||||
|
||||
String path = ScriptServer::get_global_class_path(p_class);
|
||||
return ResourceLoader::load(path, "Script");
|
||||
}
|
||||
|
||||
Variant ScriptServer::instantiate_global_class(const StringName &p_class) {
|
||||
ERR_FAIL_COND_V_MSG(!global_classes.has(p_class), Variant(), vformat("Class to instantiate '%s' is not a script class.", p_class));
|
||||
String native = get_global_class_native_base(p_class);
|
||||
Object *o = ClassDB::instance(native);
|
||||
ERR_FAIL_COND_V_MSG(!o, Variant(), vformat("Could not instantiate global script class '%s'. It extends native class '%s' which is not instantiable.", p_class, native));
|
||||
|
||||
REF ref;
|
||||
Reference *r = Object::cast_to<Reference>(o);
|
||||
if (r) {
|
||||
ref = REF(r);
|
||||
}
|
||||
|
||||
Variant ret;
|
||||
if (ref.is_valid()) {
|
||||
ret = ref;
|
||||
} else {
|
||||
ret = o;
|
||||
}
|
||||
|
||||
Ref<Script> s = get_global_class_script(p_class);
|
||||
ERR_FAIL_COND_V_MSG(s.is_null(), Variant(), vformat("Failed to load global script class '%s'.", p_class));
|
||||
|
||||
o->set_script(s.get_ref_ptr());
|
||||
|
||||
ScriptInstance *si = o->get_script_instance();
|
||||
ERR_FAIL_COND_V_MSG(!si, Variant(), vformat("Failed to create script instance for global script class '%s'.", p_class));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ScriptServer::get_global_class_list(List<StringName> *r_global_classes) {
|
||||
const StringName *K = nullptr;
|
||||
List<StringName> classes;
|
||||
|
@ -36,6 +36,7 @@
|
||||
#include "core/object/resource.h"
|
||||
|
||||
class ScriptLanguage;
|
||||
class Script;
|
||||
|
||||
typedef void (*ScriptEditRequestFunction)(const String &p_path);
|
||||
|
||||
@ -58,6 +59,7 @@ class ScriptServer {
|
||||
};
|
||||
|
||||
static HashMap<StringName, GlobalScriptClass> global_classes;
|
||||
static HashMap<String, StringName> global_class_paths;
|
||||
|
||||
public:
|
||||
static ScriptEditRequestFunction edit_request_func;
|
||||
@ -80,9 +82,12 @@ public:
|
||||
static void remove_global_class(const StringName &p_class);
|
||||
static bool is_global_class(const StringName &p_class);
|
||||
static StringName get_global_class_language(const StringName &p_class);
|
||||
static String get_global_class_path(const String &p_class);
|
||||
static StringName get_global_class_base(const String &p_class);
|
||||
static StringName get_global_class_native_base(const String &p_class);
|
||||
static String get_global_class_path(const StringName &p_class);
|
||||
static StringName get_global_class_name(const String &p_path);
|
||||
static StringName get_global_class_base(const StringName &p_class);
|
||||
static StringName get_global_class_native_base(const StringName &p_class);
|
||||
static Ref<Script> get_global_class_script(const StringName &p_class);
|
||||
static Variant instantiate_global_class(const StringName &p_class);
|
||||
static void get_global_class_list(List<StringName> *r_global_classes);
|
||||
static void save_global_classes();
|
||||
|
||||
@ -282,6 +287,7 @@ public:
|
||||
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const = 0;
|
||||
virtual Error open_in_external_editor(const Ref<Script> &p_script, int p_line, int p_col) { return ERR_UNAVAILABLE; }
|
||||
virtual bool overrides_external_editor() { return false; }
|
||||
virtual bool has_delayed_script_class_metadata() const { return false; }
|
||||
|
||||
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint) { return ERR_UNAVAILABLE; }
|
||||
|
||||
|
@ -107,6 +107,9 @@
|
||||
<member name="Time" type="Time" setter="" getter="">
|
||||
The [Time] singleton.
|
||||
</member>
|
||||
<member name="ScriptServer" type="ScriptServer" setter="" getter="">
|
||||
The [ScriptServer] singleton.
|
||||
</member>
|
||||
<member name="TranslationServer" type="TranslationServer" setter="" getter="">
|
||||
The [TranslationServer] singleton.
|
||||
</member>
|
||||
|
@ -41,6 +41,15 @@
|
||||
[b]Warning:[/b] Removing and freeing this node will render the editor useless and may cause a crash.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_class_icon">
|
||||
<return type="Texture" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<argument index="1" name="fallback" type="String" />
|
||||
<description>
|
||||
Returns the editor icon bound to a class name or the [code]fallback[/code] class's icon if not found. The [code]fallback[/code] defaults to "Object". If still not found, returns [code]null[/code].
|
||||
[b]Node:[/b] This includes icons from custom types (see [method EditorPlugin.add_custom_type]) and global script classes from [ScriptServer].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_current_path" qualifiers="const">
|
||||
<return type="String" />
|
||||
<description>
|
||||
@ -88,6 +97,15 @@
|
||||
[b]Warning:[/b] Removing and freeing this node will render a part of the editor useless and may cause a crash.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_object_icon">
|
||||
<return type="Texture" />
|
||||
<argument index="0" name="object" type="Object" />
|
||||
<argument index="1" name="fallback" type="String" />
|
||||
<description>
|
||||
Returns the editor icon bound to [Object] [code]object[/code] or the class [code]fallback[/code] if a type cannot be determined. If [code]object[/code] extends [Script], then return the editor icon bound to the scripted class, not the actual script. If still not found, return [code]null[/code].
|
||||
[b]Note:[/b] if you need the editor icon for a script such as [GDScript], use [method get_class_icon].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_open_scenes" qualifiers="const">
|
||||
<return type="Array" />
|
||||
<description>
|
||||
|
72
doc/classes/ScriptServer.xml
Normal file
72
doc/classes/ScriptServer.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<class name="ScriptServer" inherits="Object" version="3.5">
|
||||
<brief_description>
|
||||
Global script class management singleton.
|
||||
</brief_description>
|
||||
<description>
|
||||
ScriptServer manages all information related to global script classes in Godot projects, similar to [ClassDB] for engine classes. Scripts independently opt-in to become global classes. With it, you can check if a script has a global name or icon, what its base classes are, or even instantiate them directly.
|
||||
[b]Note:[/b] This class shouldn't be instantiated directly. Instead, access the singleton through a global variable.
|
||||
</description>
|
||||
<tutorials>
|
||||
<link>https://docs.godotengine.org/en/stable/getting_started/scripting/gdscript/gdscript_basics.html#classes</link>
|
||||
</tutorials>
|
||||
<methods>
|
||||
<method name="get_global_class_base" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns the class name that the script named [code]class[/code] directly extends. This may be an engine class or another global script class.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_global_class_list" qualifiers="const">
|
||||
<return type="Array" />
|
||||
<description>
|
||||
Returns the names of all global script class names known by the ScriptServer.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_global_class_name" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="path" type="String" />
|
||||
<description>
|
||||
Returns the global class name bound to the [Script] at [code]path[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_global_class_native_base" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns the native engine class that the script named [code]class[/code] eventually extends.
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_global_class_path" qualifiers="const">
|
||||
<return type="String" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns the file path to the script resource named [code]class[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="get_global_class_script" qualifiers="const">
|
||||
<return type="Script" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns the loaded [Script] named [code]class[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="instantiate_global_class" qualifiers="const">
|
||||
<return type="Variant" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns a new instance of the scripted type defined by the script named [code]class[/code].
|
||||
</description>
|
||||
</method>
|
||||
<method name="is_global_class" qualifiers="const">
|
||||
<return type="bool" />
|
||||
<argument index="0" name="class" type="String" />
|
||||
<description>
|
||||
Returns [code]true[/code] if the name [code]class[/code] is a global script class.
|
||||
</description>
|
||||
</method>
|
||||
</methods>
|
||||
<constants>
|
||||
</constants>
|
||||
</class>
|
@ -30,25 +30,25 @@
|
||||
|
||||
#include "create_dialog.h"
|
||||
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/math/color.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/os/keyboard.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "editor/doc/doc_data.h"
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor_help.h"
|
||||
#include "editor_node.h"
|
||||
#include "editor_scale.h"
|
||||
#include "editor_settings.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "core/math/color.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/math/rect2.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/input/input_event.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "editor/doc/doc_data.h"
|
||||
#include "editor/editor_data.h"
|
||||
#include "scene/2d/canvas_item.h"
|
||||
#include "scene/gui/box_container.h"
|
||||
#include "scene/gui/button.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/line_edit.h"
|
||||
@ -167,17 +167,18 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!search_loaded_scripts.has(p_type)) {
|
||||
search_loaded_scripts[p_type] = ed.script_class_load_script(p_type);
|
||||
}
|
||||
|
||||
if (!ScriptServer::is_global_class(p_type) || !ed.script_class_is_parent(p_type, base_type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!search_loaded_scripts.has(p_type)) {
|
||||
search_loaded_scripts[p_type] = ScriptServer::get_global_class_script(p_type);
|
||||
}
|
||||
|
||||
String script_path = ScriptServer::get_global_class_path(p_type);
|
||||
if (script_path.find("res://addons/", 0) != -1) {
|
||||
if (!EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
|
||||
String cfg_path = script_path.plus_file("plugin.cfg");
|
||||
if (FileAccess::exists(cfg_path) && !EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -206,7 +207,11 @@ void CreateDialog::add_type(const String &p_type, HashMap<String, TreeItem *> &p
|
||||
item->set_text(0, p_type);
|
||||
} else {
|
||||
item->set_metadata(0, p_type);
|
||||
item->set_text(0, p_type + " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")");
|
||||
String text = p_type;
|
||||
if (!EDITOR_GET("interface/editors/create_dialog_hide_script_class_filepath")) {
|
||||
text += " (" + ScriptServer::get_global_class_path(p_type).get_file() + ")";
|
||||
}
|
||||
item->set_text(0, text);
|
||||
}
|
||||
if (!can_instance) {
|
||||
item->set_custom_color(0, get_theme_color("disabled_font_color", "Editor"));
|
||||
@ -356,7 +361,7 @@ void CreateDialog::_update_search() {
|
||||
bool cpp_type2 = cpp_type;
|
||||
|
||||
if (!cpp_type && !search_loaded_scripts.has(type)) {
|
||||
search_loaded_scripts[type] = ed.script_class_load_script(type);
|
||||
search_loaded_scripts[type] = ScriptServer::get_global_class_script(type);
|
||||
}
|
||||
|
||||
while (type2 != "" && (cpp_type2 ? ClassDB::is_parent_class(type2, base_type) : ed.script_class_is_parent(type2, base_type)) && type2 != base_type) {
|
||||
@ -369,7 +374,7 @@ void CreateDialog::_update_search() {
|
||||
cpp_type2 = cpp_type2 || ClassDB::class_exists(type2); // Built-in class can't inherit from custom type, so we can skip the check if it's already true.
|
||||
|
||||
if (!cpp_type2 && !search_loaded_scripts.has(type2)) {
|
||||
search_loaded_scripts[type2] = ed.script_class_load_script(type2);
|
||||
search_loaded_scripts[type2] = ScriptServer::get_global_class_script(type2);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -881,13 +881,38 @@ void EditorData::get_plugin_window_layout(Ref<ConfigFile> p_layout) {
|
||||
}
|
||||
}
|
||||
|
||||
bool EditorData::script_class_is_parent(const String &p_class, const String &p_inherits) {
|
||||
bool EditorData::class_equals_or_inherits(const StringName &p_class, const StringName &p_inherits) const {
|
||||
if (p_class == p_inherits) {
|
||||
return true;
|
||||
}
|
||||
if (ScriptServer::is_global_class(p_class)) {
|
||||
return script_class_is_parent(p_class, p_inherits);
|
||||
}
|
||||
if (custom_types.has(p_inherits)) {
|
||||
const Vector<EditorData::CustomType> &v = custom_types[p_inherits];
|
||||
for (int i = 0; i < v.size(); i++) {
|
||||
if (v[i].name == p_class) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ClassDB::class_exists(p_class)) {
|
||||
return ClassDB::is_parent_class(p_class, p_inherits);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EditorData::script_class_is_parent(const StringName &p_class, const StringName &p_inherits) const {
|
||||
if (!ScriptServer::is_global_class(p_class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String base = p_class;
|
||||
while (base != p_inherits) {
|
||||
String base = script_class_get_base(p_class);
|
||||
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
|
||||
ERR_FAIL_COND_V_MSG(script.is_null(), false, vformat("Global script class '%s' failed to load."));
|
||||
Ref<Script> base_script = script->get_base_script();
|
||||
|
||||
while (p_inherits != base) {
|
||||
if (ClassDB::class_exists(base)) {
|
||||
return ClassDB::is_parent_class(base, p_inherits);
|
||||
} else if (ScriptServer::is_global_class(base)) {
|
||||
@ -899,25 +924,29 @@ bool EditorData::script_class_is_parent(const String &p_class, const String &p_i
|
||||
return true;
|
||||
}
|
||||
|
||||
StringName EditorData::script_class_get_base(const String &p_class) const {
|
||||
Ref<Script> script = script_class_load_script(p_class);
|
||||
StringName EditorData::script_class_get_base(const StringName &p_class) const {
|
||||
if (!ScriptServer::is_global_class(p_class)) {
|
||||
return StringName();
|
||||
}
|
||||
|
||||
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
|
||||
if (script.is_null()) {
|
||||
return StringName();
|
||||
}
|
||||
|
||||
Ref<Script> base_script = script->get_base_script();
|
||||
if (base_script.is_null()) {
|
||||
return ScriptServer::get_global_class_base(p_class);
|
||||
return ScriptServer::get_global_class_native_base(p_class);
|
||||
}
|
||||
|
||||
return script->get_language()->get_global_class_name(base_script->get_path());
|
||||
return ScriptServer::get_global_class_name(base_script->get_path());
|
||||
}
|
||||
|
||||
Variant EditorData::script_class_instance(const String &p_class) {
|
||||
Variant EditorData::script_class_instance(const StringName &p_class) const {
|
||||
if (ScriptServer::is_global_class(p_class)) {
|
||||
Variant obj = ClassDB::instance(ScriptServer::get_global_class_native_base(p_class));
|
||||
if (obj) {
|
||||
Ref<Script> script = script_class_load_script(p_class);
|
||||
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
|
||||
if (script.is_valid()) {
|
||||
((Object *)obj)->set_script(script.get_ref_ptr());
|
||||
}
|
||||
@ -927,20 +956,11 @@ Variant EditorData::script_class_instance(const String &p_class) {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
Ref<Script> EditorData::script_class_load_script(const String &p_class) const {
|
||||
if (!ScriptServer::is_global_class(p_class)) {
|
||||
return Ref<Script>();
|
||||
}
|
||||
|
||||
String path = ScriptServer::get_global_class_path(p_class);
|
||||
return ResourceLoader::load(path, "Script");
|
||||
}
|
||||
|
||||
void EditorData::script_class_set_icon_path(const String &p_class, const String &p_icon_path) {
|
||||
void EditorData::script_class_set_icon_path(const StringName &p_class, const String &p_icon_path) {
|
||||
_script_class_icon_paths[p_class] = p_icon_path;
|
||||
}
|
||||
|
||||
String EditorData::script_class_get_icon_path(const String &p_class) const {
|
||||
String EditorData::script_class_get_icon_path(const StringName &p_class) const {
|
||||
if (!ScriptServer::is_global_class(p_class)) {
|
||||
return String();
|
||||
}
|
||||
@ -965,12 +985,25 @@ String EditorData::script_class_get_icon_path(const String &p_class) const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
StringName EditorData::script_class_get_name(const String &p_path) const {
|
||||
return _script_class_file_to_path.has(p_path) ? _script_class_file_to_path[p_path] : StringName();
|
||||
}
|
||||
|
||||
void EditorData::script_class_set_name(const String &p_path, const StringName &p_class) {
|
||||
_script_class_file_to_path[p_path] = p_class;
|
||||
Ref<Script> EditorData::script_class_get_base_from_anonymous_path(const String &p_path) const {
|
||||
StringName name = ScriptServer::get_global_class_name(p_path);
|
||||
if (name != StringName()) {
|
||||
return nullptr;
|
||||
}
|
||||
Ref<Script> script = ResourceLoader::load(p_path, "Script");
|
||||
if (script.is_null()) {
|
||||
return nullptr;
|
||||
}
|
||||
do {
|
||||
if (ScriptServer::get_global_class_name(script->get_path()) != StringName()) {
|
||||
return script;
|
||||
}
|
||||
if (script->get_path().find("::") != -1) {
|
||||
WARN_PRINT_ONCE("If you remove a built-in script that derives a script class, inheritance cannot be determined. The entire script is removed.");
|
||||
}
|
||||
script = script->get_base_script();
|
||||
} while (script.is_valid());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void EditorData::script_class_save_icon_paths() {
|
||||
@ -979,14 +1012,18 @@ void EditorData::script_class_save_icon_paths() {
|
||||
|
||||
Dictionary d;
|
||||
for (List<StringName>::Element *E = keys.front(); E; E = E->next()) {
|
||||
Variant v = _script_class_icon_paths[E->get()];
|
||||
StringName name = E->get();
|
||||
|
||||
Variant v = _script_class_icon_paths[name];
|
||||
|
||||
//TODO is this still needed?
|
||||
if (v.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ScriptServer::is_global_class(E->get())) {
|
||||
d[E->get()] = String(v);
|
||||
String icon_path = v;
|
||||
if (ScriptServer::is_global_class(name)) {
|
||||
d[name] = icon_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1017,17 +1054,15 @@ void EditorData::script_class_load_icon_paths() {
|
||||
d.get_key_list(&keys);
|
||||
|
||||
for (List<Variant>::Element *E = keys.front(); E; E = E->next()) {
|
||||
Variant name = E->get();
|
||||
Variant v = E->get();
|
||||
|
||||
if (name.is_null()) {
|
||||
// TODO is this still needed?
|
||||
if (v.is_null()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//String name = E->get().operator String();
|
||||
StringName name = v.operator StringName();
|
||||
_script_class_icon_paths[name] = d[name];
|
||||
|
||||
String path = ScriptServer::get_global_class_path(name);
|
||||
script_class_set_name(path, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,23 +34,23 @@
|
||||
|
||||
#include "scene/resources/texture.h"
|
||||
|
||||
#include "core/containers/hash_map.h"
|
||||
#include "core/containers/list.h"
|
||||
#include "core/containers/pair.h"
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/containers/hash_map.h"
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/string/node_path.h"
|
||||
#include "core/containers/rb_set.h"
|
||||
#include "core/containers/vector.h"
|
||||
#include "core/object/object_id.h"
|
||||
#include "core/object/reference.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/containers/rb_set.h"
|
||||
#include "core/object/undo_redo.h"
|
||||
#include "core/string/node_path.h"
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "core/containers/vector.h"
|
||||
|
||||
#include "modules/modules_enabled.gen.h"
|
||||
|
||||
@ -165,7 +165,6 @@ private:
|
||||
bool _find_updated_instances(Node *p_root, Node *p_node, RBSet<String> &checked_paths);
|
||||
|
||||
HashMap<StringName, String> _script_class_icon_paths;
|
||||
HashMap<String, StringName> _script_class_file_to_path;
|
||||
|
||||
public:
|
||||
EditorPlugin *get_editor(Object *p_object);
|
||||
@ -234,17 +233,15 @@ public:
|
||||
void notify_edited_scene_changed();
|
||||
void notify_resource_saved(const Ref<Resource> &p_resource);
|
||||
|
||||
bool script_class_is_parent(const String &p_class, const String &p_inherits);
|
||||
StringName script_class_get_base(const String &p_class) const;
|
||||
Variant script_class_instance(const String &p_class);
|
||||
bool class_equals_or_inherits(const StringName &p_class, const StringName &p_inherits) const;
|
||||
bool script_class_is_parent(const StringName &p_class, const StringName &p_inherits) const;
|
||||
StringName script_class_get_base(const StringName &p_class) const;
|
||||
Variant script_class_instance(const StringName &p_class) const;
|
||||
|
||||
Ref<Script> script_class_load_script(const String &p_class) const;
|
||||
Ref<Script> script_class_get_base_from_anonymous_path(const String &p_path) const;
|
||||
|
||||
StringName script_class_get_name(const String &p_path) const;
|
||||
void script_class_set_name(const String &p_path, const StringName &p_class);
|
||||
|
||||
String script_class_get_icon_path(const String &p_class) const;
|
||||
void script_class_set_icon_path(const String &p_class, const String &p_icon_path);
|
||||
String script_class_get_icon_path(const StringName &p_class) const;
|
||||
void script_class_set_icon_path(const StringName &p_class, const String &p_icon_path);
|
||||
void script_class_clear_icon_paths() { _script_class_icon_paths.clear(); }
|
||||
void script_class_save_icon_paths();
|
||||
void script_class_load_icon_paths();
|
||||
|
@ -30,30 +30,30 @@
|
||||
|
||||
#include "editor_file_system.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/io/resource_importer.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
#include "editor_node.h"
|
||||
#include "editor_resource_preview.h"
|
||||
#include "editor_settings.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "core/object/class_db.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "core/io/config_file.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/object/reference.h"
|
||||
#include "core/object/resource.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/os/memory.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/typedefs.h"
|
||||
#include "core/variant/array.h"
|
||||
#include "core/variant/dictionary.h"
|
||||
#include "core/variant/variant.h"
|
||||
#include "core/variant/variant_parser.h"
|
||||
#include "editor/editor_data.h"
|
||||
#include "editor_node.h"
|
||||
#include "editor_resource_preview.h"
|
||||
#include "editor_settings.h"
|
||||
|
||||
EditorFileSystem *EditorFileSystem::singleton = nullptr;
|
||||
//the name is the version, to keep compatibility with different versions of Pandemonium
|
||||
@ -339,6 +339,7 @@ void EditorFileSystem::_save_filesystem_cache() {
|
||||
|
||||
void EditorFileSystem::_thread_func(void *_userdata) {
|
||||
EditorFileSystem *sd = (EditorFileSystem *)_userdata;
|
||||
sd->init_compiled_lang_script_class_file_cache();
|
||||
sd->_scan_filesystem();
|
||||
}
|
||||
|
||||
@ -1375,19 +1376,29 @@ Vector<String> EditorFileSystem::_get_dependencies(const String &p_path) {
|
||||
|
||||
String EditorFileSystem::_get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
if (ScriptServer::get_language(i)->handles_global_class_type(p_type)) {
|
||||
String global_name;
|
||||
String extends;
|
||||
String icon_path;
|
||||
|
||||
global_name = ScriptServer::get_language(i)->get_global_class_name(p_path, &extends, &icon_path);
|
||||
*r_extends = extends;
|
||||
*r_icon_path = icon_path;
|
||||
return global_name;
|
||||
ScriptLanguage *lang = ScriptServer::get_language(i);
|
||||
if (lang->handles_global_class_type(p_type)) {
|
||||
if (lang->has_delayed_script_class_metadata() && compiled_lang_script_class_file_cache.has(p_path)) {
|
||||
Dictionary d = compiled_lang_script_class_file_cache[p_path];
|
||||
if (r_extends) {
|
||||
*r_extends = d["base"].operator String();
|
||||
}
|
||||
if (r_icon_path) {
|
||||
*r_icon_path = d.has("icon_path") ? d["icon_path"] : "";
|
||||
}
|
||||
return d["class"].operator String();
|
||||
} else {
|
||||
return lang->get_global_class_name(p_path, r_extends, r_icon_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
*r_extends = String();
|
||||
*r_icon_path = String();
|
||||
|
||||
if (r_extends) {
|
||||
*r_extends = String();
|
||||
}
|
||||
if (r_icon_path) {
|
||||
*r_icon_path = String();
|
||||
}
|
||||
return String();
|
||||
}
|
||||
|
||||
@ -1407,7 +1418,6 @@ void EditorFileSystem::_scan_script_classes(EditorFileSystemDirectory *p_dir) {
|
||||
}
|
||||
ScriptServer::add_global_class(files[i]->script_class_name, files[i]->script_class_extends, lang, p_dir->get_file_path(i));
|
||||
EditorNode::get_editor_data().script_class_set_icon_path(files[i]->script_class_name, files[i]->script_class_icon_path);
|
||||
EditorNode::get_editor_data().script_class_set_name(files[i]->file, files[i]->script_class_name);
|
||||
}
|
||||
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
|
||||
_scan_script_classes(p_dir->get_subdir(i));
|
||||
@ -1437,6 +1447,56 @@ void EditorFileSystem::update_script_classes() {
|
||||
ResourceSaver::add_custom_savers();
|
||||
}
|
||||
|
||||
void EditorFileSystem::update_file_script_class_metadata(const String &p_path, const StringName &p_name, const StringName &p_base, const StringName &p_language, const String &p_icon_path) {
|
||||
EditorFileSystemDirectory *fs = NULL;
|
||||
int cpos = -1;
|
||||
|
||||
if (!_find_file(p_path, &fs, cpos)) {
|
||||
if (!fs)
|
||||
return;
|
||||
}
|
||||
EditorFileSystemDirectory::FileInfo *fi = fs->files[cpos];
|
||||
fi->script_class_name = p_name;
|
||||
fi->script_class_extends = p_base;
|
||||
fi->script_class_icon_path = p_icon_path;
|
||||
if (p_name != StringName() && !ScriptServer::is_global_class(p_name)) {
|
||||
ScriptServer::add_global_class(p_name, p_base, p_language, p_path);
|
||||
EditorNode::get_editor_data().script_class_set_icon_path(p_name, p_icon_path);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFileSystem::init_compiled_lang_script_class_file_cache() {
|
||||
if (compiled_lang_script_class_file_cache.empty() && ProjectSettings::get_singleton()->has_setting("_global_script_classes")) {
|
||||
Array script_classes = ProjectSettings::get_singleton()->get_setting("_global_script_classes");
|
||||
Dictionary script_class_icons = ProjectSettings::get_singleton()->get_setting("_global_script_class_icons");
|
||||
RBSet<StringName> compiled_language_names;
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptLanguage *lang = ScriptServer::get_language(i);
|
||||
if (lang->has_delayed_script_class_metadata()) {
|
||||
String n = lang->get_name();
|
||||
compiled_language_names.insert(n);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < script_classes.size(); i++) {
|
||||
Dictionary d = script_classes[i];
|
||||
StringName c = d["class"];
|
||||
String p = d["path"];
|
||||
StringName lg = d["language"];
|
||||
if (compiled_language_names.has(lg)) {
|
||||
String ip = script_class_icons[c];
|
||||
d["icon_path"] = ip;
|
||||
compiled_lang_script_class_file_cache[p] = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFileSystem::remove_compiled_lang_script_class_file_cache(const String &p_file) {
|
||||
if (compiled_lang_script_class_file_cache.has(p_file)) {
|
||||
compiled_lang_script_class_file_cache.erase(p_file);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorFileSystem::_queue_update_script_classes() {
|
||||
if (update_script_classes_queued.is_set()) {
|
||||
return;
|
||||
|
@ -30,21 +30,21 @@
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#include "scene/main/node.h"
|
||||
#include "core/object/object.h"
|
||||
#include "scene/main/node.h"
|
||||
|
||||
#include "core/os/dir_access.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/os/safe_refcount.h"
|
||||
#include "core/containers/rb_set.h"
|
||||
#include "core/error/error_list.h"
|
||||
#include "core/containers/hash_map.h"
|
||||
#include "core/containers/list.h"
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/containers/rb_set.h"
|
||||
#include "core/containers/vector.h"
|
||||
#include "core/error/error_list.h"
|
||||
#include "core/os/dir_access.h"
|
||||
#include "core/os/safe_refcount.h"
|
||||
#include "core/os/thread.h"
|
||||
#include "core/os/thread_safe.h"
|
||||
#include "core/string/string_name.h"
|
||||
#include "core/string/ustring.h"
|
||||
#include "core/containers/vector.h"
|
||||
|
||||
class FileAccess;
|
||||
struct EditorProgressBG;
|
||||
@ -168,6 +168,8 @@ class EditorFileSystem : public Node {
|
||||
|
||||
void _save_late_updated_files();
|
||||
|
||||
HashMap<String, Dictionary> compiled_lang_script_class_file_cache; // keep track of script classes from compiled languages
|
||||
|
||||
EditorFileSystemDirectory *filesystem;
|
||||
|
||||
static EditorFileSystem *singleton;
|
||||
@ -244,7 +246,8 @@ class EditorFileSystem : public Node {
|
||||
SafeFlag update_script_classes_queued;
|
||||
void _queue_update_script_classes();
|
||||
|
||||
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
|
||||
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends = NULL, String *r_icon_path = NULL) const;
|
||||
//String _get_global_class_name(String p_path, String *p_base = nullptr, String *p_icon_path = nullptr);
|
||||
|
||||
static Error _resource_import(const String &p_path);
|
||||
|
||||
@ -280,6 +283,9 @@ public:
|
||||
void reimport_files(const Vector<String> &p_files);
|
||||
|
||||
void update_script_classes();
|
||||
void update_file_script_class_metadata(const String &p_path, const StringName &p_name, const StringName &p_base, const StringName &p_language, const String &p_icon_path);
|
||||
void remove_compiled_lang_script_class_file_cache(const String &p_file);
|
||||
void init_compiled_lang_script_class_file_cache();
|
||||
|
||||
bool is_group_file(const String &p_path) const;
|
||||
void move_group_file(const String &p_path, const String &p_new_path);
|
||||
|
@ -3962,15 +3962,10 @@ Ref<Script> EditorNode::get_object_custom_type_base(const Object *p_object) cons
|
||||
Ref<Script> script = p_object->get_script();
|
||||
|
||||
if (script.is_valid()) {
|
||||
// Uncommenting would break things! Consider adding a parameter if you need it.
|
||||
// StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
||||
// if (name != StringName())
|
||||
// return name;
|
||||
|
||||
// should probably be deprecated in 4.x
|
||||
StringName base = script->get_instance_base_type();
|
||||
if (base != StringName() && EditorNode::get_editor_data().get_custom_types().has(base)) {
|
||||
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[base];
|
||||
StringName native = script->get_instance_base_type();
|
||||
if (native != StringName() && EditorNode::get_editor_data().get_custom_types().has(native)) {
|
||||
const Vector<EditorData::CustomType> &types = EditorNode::get_editor_data().get_custom_types()[native];
|
||||
|
||||
Ref<Script> base_script = script;
|
||||
while (base_script.is_valid()) {
|
||||
@ -3998,7 +3993,7 @@ StringName EditorNode::get_object_custom_type_name(const Object *p_object) const
|
||||
if (script.is_valid()) {
|
||||
Ref<Script> base_script = script;
|
||||
while (base_script.is_valid()) {
|
||||
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
||||
StringName name = ScriptServer::get_global_class_name(base_script->get_path());
|
||||
if (name != StringName()) {
|
||||
return name;
|
||||
}
|
||||
@ -4064,7 +4059,7 @@ Ref<Texture> EditorNode::get_object_icon(const Object *p_object, const String &p
|
||||
if (script.is_valid() && !script_icon_cache.has(script)) {
|
||||
Ref<Script> base_script = script;
|
||||
while (base_script.is_valid()) {
|
||||
StringName name = EditorNode::get_editor_data().script_class_get_name(base_script->get_path());
|
||||
StringName name = ScriptServer::get_global_class_name(base_script->get_path());
|
||||
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(name);
|
||||
Ref<ImageTexture> icon = _load_custom_class_icon(icon_path);
|
||||
if (icon.is_valid()) {
|
||||
@ -4111,13 +4106,16 @@ Ref<Texture> EditorNode::get_object_icon(const Object *p_object, const String &p
|
||||
Ref<Texture> EditorNode::get_class_icon(const String &p_class, const String &p_fallback) const {
|
||||
ERR_FAIL_COND_V_MSG(p_class.empty(), nullptr, "Class name cannot be empty.");
|
||||
|
||||
EditorData &ed = get_editor_data();
|
||||
if (ScriptServer::is_global_class(p_class)) {
|
||||
Ref<ImageTexture> icon;
|
||||
Ref<Script> script = ScriptServer::get_global_class_script(p_class);
|
||||
String class_name = p_class;
|
||||
Ref<Script> script = EditorNode::get_editor_data().script_class_load_script(class_name);
|
||||
|
||||
while (true) {
|
||||
String icon_path = EditorNode::get_editor_data().script_class_get_icon_path(class_name);
|
||||
Ref<Texture> icon = _load_custom_class_icon(icon_path);
|
||||
while (script.is_valid()) {
|
||||
class_name = ScriptServer::get_global_class_name(script->get_path());
|
||||
String current_icon_path = ed.script_class_get_icon_path(class_name);
|
||||
icon = _load_custom_class_icon(current_icon_path);
|
||||
if (icon.is_valid()) {
|
||||
return icon; // Current global class has icon.
|
||||
}
|
||||
@ -4135,12 +4133,12 @@ Ref<Texture> EditorNode::get_class_icon(const String &p_class, const String &p_f
|
||||
return gui_base->get_theme_icon(p_fallback, "EditorIcons");
|
||||
}
|
||||
script = base_script;
|
||||
class_name = EditorNode::get_editor_data().script_class_get_name(script->get_path());
|
||||
class_name = ScriptServer::get_global_class_name(script->get_path());
|
||||
} while (class_name.empty());
|
||||
}
|
||||
}
|
||||
|
||||
const RBMap<String, Vector<EditorData::CustomType>> &p_map = EditorNode::get_editor_data().get_custom_types();
|
||||
const RBMap<String, Vector<EditorData::CustomType>> &p_map = ed.get_custom_types();
|
||||
for (const RBMap<String, Vector<EditorData::CustomType>>::Element *E = p_map.front(); E; E = E->next()) {
|
||||
const Vector<EditorData::CustomType> &ct = E->value();
|
||||
for (int i = 0; i < ct.size(); ++i) {
|
||||
|
@ -362,6 +362,14 @@ bool EditorInterface::is_distraction_free_mode_enabled() const {
|
||||
return EditorNode::get_singleton()->is_distraction_free_mode_enabled();
|
||||
}
|
||||
|
||||
Ref<Texture> EditorInterface::get_class_icon(const String &p_class, const String &p_fallback) {
|
||||
return EditorNode::get_singleton()->get_class_icon(p_class, p_fallback);
|
||||
}
|
||||
|
||||
Ref<Texture> EditorInterface::get_object_icon(const Object *p_object, const String &p_fallback) {
|
||||
return EditorNode::get_singleton()->get_object_icon(p_object, p_fallback);
|
||||
}
|
||||
|
||||
void EditorInterface::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("inspect_object", "object", "for_property", "inspector_only"), &EditorInterface::inspect_object, DEFVAL(String()), DEFVAL(false));
|
||||
ClassDB::bind_method(D_METHOD("get_selection"), &EditorInterface::get_selection);
|
||||
@ -406,6 +414,9 @@ void EditorInterface::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_distraction_free_mode", "enter"), &EditorInterface::set_distraction_free_mode);
|
||||
ClassDB::bind_method(D_METHOD("is_distraction_free_mode_enabled"), &EditorInterface::is_distraction_free_mode_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_class_icon", "class", "fallback"), &EditorInterface::get_class_icon);
|
||||
ClassDB::bind_method(D_METHOD("get_object_icon", "object", "fallback"), &EditorInterface::get_object_icon);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "distraction_free_mode"), "set_distraction_free_mode", "is_distraction_free_mode_enabled");
|
||||
}
|
||||
|
||||
|
@ -146,6 +146,9 @@ public:
|
||||
void set_distraction_free_mode(bool p_enter);
|
||||
bool is_distraction_free_mode_enabled() const;
|
||||
|
||||
Ref<Texture> get_class_icon(const String &p_name, const String &p_fallback);
|
||||
Ref<Texture> get_object_icon(const Object *p_object, const String &p_fallback);
|
||||
|
||||
EditorInterface();
|
||||
};
|
||||
|
||||
|
@ -4182,13 +4182,14 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ
|
||||
EditorPropertyResource *editor = memnew(EditorPropertyResource);
|
||||
editor->setup(p_object, p_path, p_hint == PROPERTY_HINT_RESOURCE_TYPE ? p_hint_text : "Resource");
|
||||
|
||||
EditorData &ed = EditorNode::get_editor_data();
|
||||
if (p_hint == PROPERTY_HINT_RESOURCE_TYPE) {
|
||||
String open_in_new = EDITOR_GET("interface/inspector/resources_to_open_in_new_inspector");
|
||||
for (int i = 0; i < open_in_new.get_slice_count(","); i++) {
|
||||
String type = open_in_new.get_slicec(',', i).strip_edges();
|
||||
for (int j = 0; j < p_hint_text.get_slice_count(","); j++) {
|
||||
String inherits = p_hint_text.get_slicec(',', j);
|
||||
if (ClassDB::is_parent_class(inherits, type)) {
|
||||
if (ed.class_equals_or_inherits(inherits, type)) {
|
||||
editor->set_use_sub_inspector(false);
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,15 @@ void EditorResourcePicker::_update_resource() {
|
||||
} else if (edited_resource->get_path().is_resource_file()) {
|
||||
assign_button->set_text(edited_resource->get_path().get_file());
|
||||
} else {
|
||||
assign_button->set_text(edited_resource->get_class());
|
||||
String class_name = edited_resource->get_class();
|
||||
Ref<Script> res_script = edited_resource->get_script();
|
||||
if (res_script.is_valid()) {
|
||||
String script_name = ScriptServer::get_global_class_name(res_script->get_path());
|
||||
if (!script_name.empty()) {
|
||||
class_name = script_name;
|
||||
}
|
||||
}
|
||||
assign_button->set_text(class_name);
|
||||
}
|
||||
|
||||
String resource_path;
|
||||
@ -158,9 +166,19 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
|
||||
if (base_type != "") {
|
||||
bool any_type_matches = false;
|
||||
|
||||
StringName res_type = loaded_resource->get_class();
|
||||
Ref<Script> res_script = loaded_resource->get_script();
|
||||
if (res_script.is_valid()) {
|
||||
StringName script_type = ScriptServer::get_global_class_name(res_script->get_path());
|
||||
if (script_type != StringName()) {
|
||||
res_type = script_type;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < base_type.get_slice_count(","); i++) {
|
||||
String base = base_type.get_slice(",", i);
|
||||
if (loaded_resource->is_class(base)) {
|
||||
|
||||
if (EditorNode::get_editor_data().class_equals_or_inherits(res_type, base)) {
|
||||
any_type_matches = true;
|
||||
break;
|
||||
}
|
||||
@ -225,7 +243,9 @@ void EditorResourcePicker::_update_menu_items() {
|
||||
paste_valid = true;
|
||||
} else {
|
||||
for (int i = 0; i < base_type.get_slice_count(","); i++) {
|
||||
if (ClassDB::class_exists(cb->get_class()) && ClassDB::is_parent_class(cb->get_class(), base_type.get_slice(",", i))) {
|
||||
StringName script_name = ScriptServer::get_global_class_name(cb->get_path());
|
||||
StringName class_name = script_name != StringName() ? script_name : StringName(cb->get_class());
|
||||
if (EditorNode::get_editor_data().class_equals_or_inherits(class_name, base_type.get_slice(",", i))) {
|
||||
paste_valid = true;
|
||||
break;
|
||||
}
|
||||
@ -253,12 +273,7 @@ void EditorResourcePicker::_update_menu_items() {
|
||||
}
|
||||
for (int i = 0; i < conversions.size(); i++) {
|
||||
String what = conversions[i]->converts_to();
|
||||
Ref<Texture> icon;
|
||||
if (has_theme_icon(what, "EditorIcons")) {
|
||||
icon = get_theme_icon(what, "EditorIcons");
|
||||
} else {
|
||||
icon = get_theme_icon(what, "Resource");
|
||||
}
|
||||
Ref<Texture> icon = EditorNode::get_singleton()->get_class_icon(what, Resource::get_class_static());
|
||||
|
||||
edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + i);
|
||||
}
|
||||
@ -336,10 +351,20 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
|
||||
propvalues.push_back(p);
|
||||
}
|
||||
|
||||
String orig_type = edited_resource->get_class();
|
||||
Object *inst = ClassDB::instance(orig_type);
|
||||
Ref<Resource> unique_resource = Ref<Resource>(Object::cast_to<Resource>(inst));
|
||||
ERR_FAIL_COND(unique_resource.is_null());
|
||||
Ref<Resource> inst;
|
||||
Ref<Script> res_script = edited_resource->get_script();
|
||||
if (res_script.is_valid()) {
|
||||
StringName script_name = ScriptServer::get_global_class_name(res_script->get_path());
|
||||
if (ScriptServer::is_global_class(script_name)) {
|
||||
inst = ScriptServer::instantiate_global_class(script_name);
|
||||
}
|
||||
}
|
||||
if (inst.is_null()) {
|
||||
inst = ClassDB::instance(edited_resource->get_class());
|
||||
}
|
||||
ERR_FAIL_COND_MSG(inst.is_null(), "Failed to instantiate resource during Make Unique.");
|
||||
Ref<Resource> unique_resource = Ref<Resource>(inst);
|
||||
ERR_FAIL_COND_MSG(unique_resource.is_null(), "Failed to copy resource reference during Make Unique.");
|
||||
|
||||
for (List<Pair<String, Variant>>::Element *E = propvalues.front(); E; E = E->next()) {
|
||||
Pair<String, Variant> &p = E->get();
|
||||
@ -400,13 +425,7 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
|
||||
Variant obj;
|
||||
|
||||
if (ScriptServer::is_global_class(intype)) {
|
||||
obj = ClassDB::instance(ScriptServer::get_global_class_native_base(intype));
|
||||
if (obj) {
|
||||
Ref<Script> script = ResourceLoader::load(ScriptServer::get_global_class_path(intype));
|
||||
if (script.is_valid()) {
|
||||
((Object *)obj)->set_script(script.get_ref_ptr());
|
||||
}
|
||||
}
|
||||
obj = ScriptServer::instantiate_global_class(intype);
|
||||
} else {
|
||||
obj = ClassDB::instance(intype);
|
||||
}
|
||||
@ -683,22 +702,19 @@ void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_
|
||||
for (RBSet<String>::Element *E = allowed_types.front(); E; E = E->next()) {
|
||||
String at = E->get().strip_edges();
|
||||
|
||||
if (at == "SpatialMaterial" && ClassDB::is_parent_class(dropped_resource->get_class(), "Texture")) {
|
||||
// Use existing resource if possible and only replace its data.
|
||||
Ref<SpatialMaterial> mat = edited_resource;
|
||||
if (mat.is_null()) {
|
||||
mat.instance();
|
||||
}
|
||||
EditorData &ed = EditorNode::get_editor_data();
|
||||
StringName script_name = ScriptServer::get_global_class_name(dropped_resource->get_path());
|
||||
String class_name = script_name != StringName() ? script_name : StringName(dropped_resource->get_class());
|
||||
|
||||
if (at == "SpatialMaterial" && ed.class_equals_or_inherits(class_name, "Texture")) {
|
||||
Ref<SpatialMaterial> mat = memnew(SpatialMaterial);
|
||||
mat->set_texture(SpatialMaterial::TextureParam::TEXTURE_ALBEDO, dropped_resource);
|
||||
dropped_resource = mat;
|
||||
break;
|
||||
}
|
||||
|
||||
if (at == "ShaderMaterial" && ClassDB::is_parent_class(dropped_resource->get_class(), "Shader")) {
|
||||
Ref<ShaderMaterial> mat = edited_resource;
|
||||
if (mat.is_null()) {
|
||||
mat.instance();
|
||||
}
|
||||
if (at == "ShaderMaterial" && ed.class_equals_or_inherits(class_name, "Shader")) {
|
||||
Ref<ShaderMaterial> mat = memnew(ShaderMaterial);
|
||||
mat->set_shader(dropped_resource);
|
||||
dropped_resource = mat;
|
||||
break;
|
||||
|
@ -1319,7 +1319,7 @@ Error ResourceImporterScene::import(const String &p_source_file, const String &p
|
||||
|
||||
Ref<Script> root_script = nullptr;
|
||||
if (ScriptServer::is_global_class(root_type)) {
|
||||
root_script = ResourceLoader::load(ScriptServer::get_global_class_path(root_type));
|
||||
root_script = ScriptServer::get_global_class_script(root_type);
|
||||
root_type = ScriptServer::get_global_class_base(root_type);
|
||||
}
|
||||
|
||||
|
@ -620,23 +620,64 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
|
||||
return;
|
||||
}
|
||||
|
||||
editor_data->get_undo_redo().create_action(TTR("Detach Script"));
|
||||
editor_data->get_undo_redo().add_do_method(editor, "push_item", (Script *)nullptr);
|
||||
Array update_array;
|
||||
EditorData &ed = EditorNode::get_editor_data();
|
||||
|
||||
for (int i = 0; i < selection.size(); i++) {
|
||||
Node *n = Object::cast_to<Node>(selection[i]);
|
||||
Ref<Script> existing = n->get_script();
|
||||
Ref<Script> empty = EditorNode::get_singleton()->get_object_custom_type_base(n);
|
||||
if (existing != empty) {
|
||||
editor_data->get_undo_redo().add_do_method(n, "set_script", empty);
|
||||
editor_data->get_undo_redo().add_undo_method(n, "set_script", existing);
|
||||
Ref<Script> base = nullptr;
|
||||
if (existing.is_valid()) {
|
||||
StringName script_class_name = ScriptServer::get_global_class_name(existing->get_path());
|
||||
if (script_class_name != StringName()) {
|
||||
update_array.clear();
|
||||
print_error("Unable to remove script class '" + script_class_name + "'. Can only remove anonymous scripts.");
|
||||
break;
|
||||
}
|
||||
base = ed.script_class_get_base_from_anonymous_path(existing->get_path());
|
||||
if (base.is_null()) {
|
||||
const RBMap<String, Vector<EditorData::CustomType>> &ct = EditorNode::get_editor_data().get_custom_types();
|
||||
if (ct.has(n->get_class())) {
|
||||
const Vector<EditorData::CustomType> &v = ct[n->get_class()];
|
||||
String ct_name;
|
||||
for (int j = 0; j < v.size() && !ct_name.empty(); j++) {
|
||||
if (v[j].script == existing) {
|
||||
ct_name = v[j].name;
|
||||
}
|
||||
}
|
||||
if (!ct_name.empty()) {
|
||||
update_array.clear();
|
||||
print_error("Unable to remove custom type '" + ct_name + "'. Can only remove anonymous scripts.");
|
||||
break;
|
||||
}
|
||||
}
|
||||
base = EditorNode::get_singleton()->get_object_custom_type_base(n);
|
||||
}
|
||||
}
|
||||
if (existing != base) {
|
||||
Array an_update;
|
||||
an_update.push_back(n);
|
||||
an_update.push_back(base);
|
||||
an_update.push_back(existing);
|
||||
update_array.push_back(an_update);
|
||||
}
|
||||
}
|
||||
if (update_array.size()) {
|
||||
editor_data->get_undo_redo().create_action(TTR("Detach Script"));
|
||||
editor_data->get_undo_redo().add_do_method(editor, "push_item", (Script *)NULL);
|
||||
for (int i = 0; i < update_array.size(); i++) {
|
||||
Array an_update = update_array[i];
|
||||
Node *n = an_update[0];
|
||||
Ref<Script> do_script = an_update[1];
|
||||
Ref<Script> undo_script = an_update[2];
|
||||
editor_data->get_undo_redo().add_do_method(n, "set_script", do_script);
|
||||
editor_data->get_undo_redo().add_undo_method(n, "set_script", undo_script);
|
||||
}
|
||||
editor_data->get_undo_redo().add_do_method(this, "_update_script_button");
|
||||
editor_data->get_undo_redo().add_undo_method(this, "_update_script_button");
|
||||
|
||||
editor_data->get_undo_redo().add_do_method(this, "_update_script_button");
|
||||
editor_data->get_undo_redo().add_undo_method(this, "_update_script_button");
|
||||
|
||||
editor_data->get_undo_redo().commit_action();
|
||||
editor_data->get_undo_redo().commit_action();
|
||||
}
|
||||
} break;
|
||||
case TOOL_MOVE_UP:
|
||||
case TOOL_MOVE_DOWN: {
|
||||
@ -1308,6 +1349,7 @@ void SceneTreeDock::_notification(int p_what) {
|
||||
button_add->set_icon(get_theme_icon("Add", "EditorIcons"));
|
||||
button_instance->set_icon(get_theme_icon("Instance", "EditorIcons"));
|
||||
button_create_script->set_icon(get_theme_icon("ScriptCreate", "EditorIcons"));
|
||||
button_extend_script->set_icon(get_theme_icon("ScriptExtend", "EditorIcons"));
|
||||
button_detach_script->set_icon(get_theme_icon("ScriptRemove", "EditorIcons"));
|
||||
|
||||
filter->set_right_icon(get_theme_icon("Search", "EditorIcons"));
|
||||
@ -1390,6 +1432,7 @@ void SceneTreeDock::_notification(int p_what) {
|
||||
button_add->set_icon(get_theme_icon("Add", "EditorIcons"));
|
||||
button_instance->set_icon(get_theme_icon("Instance", "EditorIcons"));
|
||||
button_create_script->set_icon(get_theme_icon("ScriptCreate", "EditorIcons"));
|
||||
button_extend_script->set_icon(get_theme_icon("ScriptExtend", "EditorIcons"));
|
||||
button_detach_script->set_icon(get_theme_icon("ScriptRemove", "EditorIcons"));
|
||||
button_2d->set_icon(get_theme_icon("Node2D", "EditorIcons"));
|
||||
button_3d->set_icon(get_theme_icon("Spatial", "EditorIcons"));
|
||||
@ -2204,26 +2247,45 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
|
||||
void SceneTreeDock::_update_script_button() {
|
||||
if (editor_selection->get_selection().size() == 0) {
|
||||
button_create_script->hide();
|
||||
button_extend_script->hide();
|
||||
button_detach_script->hide();
|
||||
} else if (editor_selection->get_selection().size() == 1) {
|
||||
Node *n = editor_selection->get_selected_node_list()[0];
|
||||
if (n->get_script().is_null()) {
|
||||
button_create_script->show();
|
||||
button_detach_script->hide();
|
||||
Ref<Script> s = n->get_script();
|
||||
if (s.is_valid()) {
|
||||
if (ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
|
||||
button_create_script->hide();
|
||||
button_extend_script->show();
|
||||
button_detach_script->hide();
|
||||
} else {
|
||||
button_create_script->hide();
|
||||
button_extend_script->hide();
|
||||
button_detach_script->show();
|
||||
}
|
||||
} else {
|
||||
button_create_script->hide();
|
||||
button_detach_script->show();
|
||||
button_create_script->show();
|
||||
button_extend_script->hide();
|
||||
button_detach_script->hide();
|
||||
}
|
||||
} else {
|
||||
button_create_script->hide();
|
||||
Array selection = editor_selection->get_selected_nodes();
|
||||
for (int i = 0; i < selection.size(); i++) {
|
||||
Node *n = Object::cast_to<Node>(selection[i]);
|
||||
if (!n->get_script().is_null()) {
|
||||
button_detach_script->show();
|
||||
return;
|
||||
Ref<Script> s = n->get_script();
|
||||
if (s.is_valid()) {
|
||||
if (ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
|
||||
button_extend_script->show();
|
||||
button_detach_script->hide();
|
||||
return;
|
||||
} else {
|
||||
button_extend_script->hide();
|
||||
button_detach_script->show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
button_extend_script->hide();
|
||||
button_detach_script->hide();
|
||||
}
|
||||
}
|
||||
@ -2415,6 +2477,11 @@ void SceneTreeDock::replace_node(Node *p_node, Node *p_by_node, bool p_keep_prop
|
||||
List<PropertyInfo> pinfo;
|
||||
n->get_property_list(&pinfo);
|
||||
|
||||
Ref<Script> s = n->get_script();
|
||||
if (s.is_valid() && ScriptServer::get_global_class_name(s->get_path()) != StringName()) {
|
||||
n->set_script(RefPtr());
|
||||
}
|
||||
|
||||
for (List<PropertyInfo>::Element *E = pinfo.front(); E; E = E->next()) {
|
||||
if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) {
|
||||
continue;
|
||||
@ -2805,7 +2872,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
|
||||
menu->clear();
|
||||
|
||||
Ref<Script> existing_script;
|
||||
bool exisiting_script_removable = true;
|
||||
bool existing_script_removable = true;
|
||||
if (selection.size() == 1) {
|
||||
Node *selected = selection[0];
|
||||
|
||||
@ -2824,8 +2891,9 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
|
||||
|
||||
existing_script = selected->get_script();
|
||||
|
||||
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script) {
|
||||
exisiting_script_removable = false;
|
||||
if (EditorNode::get_singleton()->get_object_custom_type_base(selected) == existing_script ||
|
||||
(existing_script.is_valid() && ScriptServer::get_global_class_name(existing_script->get_path()) != StringName())) {
|
||||
existing_script_removable = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2846,7 +2914,7 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) {
|
||||
}
|
||||
}
|
||||
|
||||
if (existing_script.is_valid() && exisiting_script_removable) {
|
||||
if (existing_script.is_valid() && existing_script_removable) {
|
||||
add_separator = true;
|
||||
menu->add_icon_shortcut(get_theme_icon("ScriptRemove", "EditorIcons"), ED_GET_SHORTCUT("scene_tree/detach_script"), TOOL_DETACH_SCRIPT);
|
||||
} else if (full_selection.size() > 1) {
|
||||
@ -3033,7 +3101,7 @@ void SceneTreeDock::attach_script_to_selected(bool p_extend) {
|
||||
for (int i = 0; i < ScriptServer::get_language_count(); i++) {
|
||||
ScriptLanguage *l = ScriptServer::get_language(i);
|
||||
if (l->get_type() == existing->get_class()) {
|
||||
String name = l->get_global_class_name(existing->get_path());
|
||||
String name = ScriptServer::get_global_class_name(existing->get_path());
|
||||
if (ScriptServer::is_global_class(name) && EDITOR_GET("interface/editors/derive_script_globals_by_name").operator bool()) {
|
||||
inherits = name;
|
||||
} else if (l->can_inherit_from_file()) {
|
||||
@ -3425,6 +3493,13 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
|
||||
filter_hbc->add_child(button_create_script);
|
||||
button_create_script->hide();
|
||||
|
||||
button_extend_script = memnew(ToolButton);
|
||||
button_extend_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_EXTEND_SCRIPT, false));
|
||||
button_extend_script->set_tooltip(TTR("Attach a new script extending the selected node's script."));
|
||||
button_extend_script->set_shortcut(ED_GET_SHORTCUT("scene_tree/extend_script"));
|
||||
filter_hbc->add_child(button_extend_script);
|
||||
button_extend_script->hide();
|
||||
|
||||
button_detach_script = memnew(ToolButton);
|
||||
button_detach_script->connect("pressed", this, "_tool_selected", make_binds(TOOL_DETACH_SCRIPT, false));
|
||||
button_detach_script->set_tooltip(TTR("Detach the script from the selected node."));
|
||||
@ -3551,6 +3626,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel
|
||||
|
||||
EDITOR_DEF("interface/editors/show_scene_tree_root_selection", true);
|
||||
EDITOR_DEF("interface/editors/derive_script_globals_by_name", true);
|
||||
EDITOR_DEF("interface/editors/create_dialog_hide_script_class_filepath", false);
|
||||
EDITOR_DEF("_use_favorites_root_selection", false);
|
||||
}
|
||||
|
||||
|
@ -138,6 +138,7 @@ class SceneTreeDock : public VBoxContainer {
|
||||
ToolButton *button_add;
|
||||
ToolButton *button_instance;
|
||||
ToolButton *button_create_script;
|
||||
ToolButton *button_extend_script;
|
||||
ToolButton *button_detach_script;
|
||||
|
||||
Button *button_2d;
|
||||
|
@ -246,15 +246,17 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
|
||||
Color accent = get_theme_color("accent_color", "Editor");
|
||||
|
||||
Ref<Script> script = p_node->get_script();
|
||||
if (!script.is_null() && EditorNode::get_singleton()->get_object_custom_type_base(p_node) != script) {
|
||||
if (script.is_valid() &&
|
||||
ScriptServer::get_global_class_name(script->get_path()) == StringName() &&
|
||||
EditorNode::get_singleton()->get_object_custom_type_base(p_node) != script) {
|
||||
//has script
|
||||
item->add_button(0, get_theme_icon("Script", "EditorIcons"), BUTTON_SCRIPT);
|
||||
} else {
|
||||
//has no script (or script is a custom type)
|
||||
//has no script (or script is a custom type / script class)
|
||||
item->set_custom_color(0, get_theme_color("disabled_font_color", "Editor"));
|
||||
item->set_selectable(0, false);
|
||||
|
||||
if (!script.is_null()) { // make sure to mark the script if a custom type
|
||||
if (script.is_valid()) { // make sure to mark the script if a custom type or script class
|
||||
item->add_button(0, get_theme_icon("Script", "EditorIcons"), BUTTON_SCRIPT);
|
||||
item->set_button_disabled(0, item->get_button_count(0) - 1, true);
|
||||
}
|
||||
@ -381,6 +383,11 @@ bool SceneTreeEditor::_add_nodes(Node *p_node, TreeItem *p_parent, bool p_scroll
|
||||
additional_notes += "\n" + TTR("This script is currently running in the editor.");
|
||||
button_color = get_theme_color("accent_color", "Editor");
|
||||
}
|
||||
item->add_button(0, get_theme_icon("Script", "EditorIcons"), BUTTON_SCRIPT, false, TTR("Open Script:") + " " + script->get_path());
|
||||
if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == script ||
|
||||
ScriptServer::get_global_class_name(script->get_path()) != StringName()) {
|
||||
item->set_button_color(0, item->get_button_count(0) - 1, Color(1, 1, 1, 0.5));
|
||||
}
|
||||
if (EditorNode::get_singleton()->get_object_custom_type_base(p_node) == script) {
|
||||
additional_notes += "\n" + TTR("This script is a custom type.");
|
||||
button_color.a = 0.5;
|
||||
|
@ -30,14 +30,14 @@
|
||||
|
||||
#include "cscript_parser.h"
|
||||
|
||||
#include "core/core_string_names.h"
|
||||
#include "core/config/engine.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/core_string_names.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/object/reference.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/string/print_string.h"
|
||||
#include "cscript.h"
|
||||
|
||||
template <class T>
|
||||
@ -4580,19 +4580,41 @@ void CScriptParser::_parse_class(ClassNode *p_class) {
|
||||
Variant constant = static_cast<ConstantNode *>(subexpr)->value;
|
||||
|
||||
if (constant.get_type() == Variant::OBJECT) {
|
||||
StringName class_name;
|
||||
CScriptNativeClass *native_class = Object::cast_to<CScriptNativeClass>(constant);
|
||||
|
||||
if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
|
||||
if (native_class) {
|
||||
if (ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
|
||||
class_name = native_class->get_name();
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The export hint isn't a resource type.");
|
||||
}
|
||||
} else {
|
||||
Ref<Script> res_script = constant;
|
||||
StringName script_class;
|
||||
if (res_script.is_valid()) {
|
||||
script_class = res_script->get_language()->get_global_class_name(res_script->get_path());
|
||||
|
||||
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(script_class), "Resource")) {
|
||||
class_name = script_class;
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The exported script does not extend Resource.");
|
||||
}
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The exported script isn't a script class.");
|
||||
}
|
||||
}
|
||||
|
||||
if (class_name != StringName()) {
|
||||
current_export.type = Variant::OBJECT;
|
||||
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
|
||||
current_export.hint_string = native_class->get_name();
|
||||
current_export.class_name = native_class->get_name();
|
||||
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The export hint isn't a resource type.");
|
||||
current_export.hint_string = class_name;
|
||||
current_export.class_name = class_name;
|
||||
}
|
||||
} else if (constant.get_type() == Variant::DICTIONARY) {
|
||||
// Enumeration
|
||||
@ -4764,12 +4786,43 @@ void CScriptParser::_parse_class(ClassNode *p_class) {
|
||||
member._export.hint_string = member.data_type.native_type;
|
||||
member._export.class_name = member.data_type.native_type;
|
||||
} else {
|
||||
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
||||
_set_error(vformat("Invalid native export type. \"%s\" is not a Resource type.", member.data_type.native_type), member.line);
|
||||
return;
|
||||
}
|
||||
} else if (member.data_type.kind == DataType::SCRIPT || member.data_type.kind == DataType::CSCRIPT) {
|
||||
if (member.data_type.script_type.is_null()) {
|
||||
_set_error("Invalid script export type. Could not load member script value.", member.line);
|
||||
return;
|
||||
}
|
||||
Ref<Script> s = member.data_type.script_type;
|
||||
StringName class_name = s->get_language()->get_global_class_name(s->get_path());
|
||||
if (class_name == StringName()) {
|
||||
_set_error("Invalid script export type. The member is a script that has no global script class name.", member.line);
|
||||
return;
|
||||
}
|
||||
if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
|
||||
_set_error("Invalid script export type. The member is a script class that does not extend a Resource type.", member.line);
|
||||
return;
|
||||
}
|
||||
|
||||
member._export.type = Variant::OBJECT;
|
||||
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
member._export.hint_string = class_name;
|
||||
member._export.class_name = class_name;
|
||||
|
||||
} else if (member.data_type.kind == DataType::UNRESOLVED && ScriptServer::is_global_class(member.data_type.native_type)) {
|
||||
StringName class_name = member.data_type.native_type;
|
||||
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
|
||||
member._export.type = Variant::OBJECT;
|
||||
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
member._export.hint_string = class_name;
|
||||
member._export.class_name = class_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
||||
_set_error("Invalid export type. Only built-in types and native or script class Resource types can be exported.", member.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -5850,7 +5903,22 @@ CScriptParser::DataType CScriptParser::_type_from_property(const PropertyInfo &p
|
||||
ret.builtin_type = p_property.type;
|
||||
if (p_property.type == Variant::OBJECT) {
|
||||
ret.kind = DataType::NATIVE;
|
||||
ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
|
||||
ret.native_type = "Object";
|
||||
if (p_property.class_name != StringName()) {
|
||||
if (ScriptServer::is_global_class(p_property.class_name)) {
|
||||
String p = ScriptServer::get_global_class_path(p_property.class_name);
|
||||
ret.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
|
||||
if (CScriptLanguage::get_singleton()->get_extension() == p.get_extension()) {
|
||||
ret.kind = DataType::CSCRIPT;
|
||||
ret.script_type = ResourceLoader::load(p, "CScript");
|
||||
} else {
|
||||
ret.kind = DataType::SCRIPT;
|
||||
ret.script_type = ResourceLoader::load(p, "Script");
|
||||
}
|
||||
} else {
|
||||
ret.native_type = p_property.class_name;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret.kind = DataType::BUILTIN;
|
||||
}
|
||||
@ -7758,13 +7826,24 @@ void CScriptParser::_check_class_level_types(ClassNode *p_class) {
|
||||
}
|
||||
|
||||
// Check export hint
|
||||
if (v.data_type.has_type && v._export.type != Variant::NIL) {
|
||||
if (v._export.type != Variant::NIL) {
|
||||
DataType export_type = _type_from_property(v._export);
|
||||
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
||||
_set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
|
||||
v.data_type.to_string() + ").",
|
||||
v.line);
|
||||
return;
|
||||
if (export_type.kind == DataType::CSCRIPT || export_type.kind == DataType::SCRIPT) {
|
||||
String class_name = v._export.class_name;
|
||||
if (ScriptServer::is_global_class(class_name)) {
|
||||
class_name = ScriptServer::get_global_class_native_base(class_name);
|
||||
}
|
||||
if (!ClassDB::is_parent_class(class_name, "Resource")) {
|
||||
_set_error(vformat("Exported script-defined type (%s) must inherit from Resource.", export_type.to_string()), v.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (v.data_type.has_type) {
|
||||
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
||||
_set_error(vformat("The export hint's type (%s) doesn't match the variable's type (%s).", export_type.to_string(), v.data_type.to_string()), v.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,6 +128,7 @@ typedef struct {
|
||||
const char **string_delimiters; // NULL terminated array
|
||||
pandemonium_bool has_named_classes;
|
||||
pandemonium_bool supports_builtin_mode;
|
||||
pandemonium_bool has_delayed_script_class_metadata;
|
||||
|
||||
pandemonium_string (*get_template_source_code)(pandemonium_pluginscript_language_data *p_data, const pandemonium_string *p_class_name, const pandemonium_string *p_base_class_name);
|
||||
pandemonium_bool (*validate)(pandemonium_pluginscript_language_data *p_data, const pandemonium_string *p_script, int *r_line_error, int *r_col_error, pandemonium_string *r_test_error, const pandemonium_string *p_path, pandemonium_pool_string_array *r_functions);
|
||||
|
@ -29,9 +29,9 @@
|
||||
/**************************************************************************/
|
||||
|
||||
// Pandemonium imports
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/os/file_access.h"
|
||||
#include "core/os/os.h"
|
||||
#include "core/config/project_settings.h"
|
||||
// PluginScript imports
|
||||
#include "pluginscript_language.h"
|
||||
#include "pluginscript_script.h"
|
||||
@ -146,6 +146,10 @@ bool PluginScriptLanguage::supports_builtin_mode() const {
|
||||
return _desc.supports_builtin_mode;
|
||||
}
|
||||
|
||||
bool PluginScriptLanguage::has_delayed_script_class_metadata() const {
|
||||
return _desc.has_delayed_script_class_metadata;
|
||||
}
|
||||
|
||||
int PluginScriptLanguage::find_function(const String &p_function, const String &p_code) const {
|
||||
if (_desc.find_function) {
|
||||
return _desc.find_function(_data, (pandemonium_string *)&p_function, (pandemonium_string *)&p_code);
|
||||
|
@ -32,11 +32,11 @@
|
||||
#define PLUGINSCRIPT_LANGUAGE_H
|
||||
|
||||
// Pandemonium imports
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/containers/self_list.h"
|
||||
#include "core/io/resource_loader.h"
|
||||
#include "core/io/resource_saver.h"
|
||||
#include "core/containers/rb_map.h"
|
||||
#include "core/object/script_language.h"
|
||||
#include "core/containers/self_list.h"
|
||||
// PluginScript imports
|
||||
#include "pluginscript_loader.h"
|
||||
#include <pluginscript/pandemonium_pluginscript.h>
|
||||
@ -80,6 +80,7 @@ public:
|
||||
virtual bool has_named_classes() const;
|
||||
virtual bool supports_builtin_mode() const;
|
||||
virtual bool can_inherit_from_file() { return true; }
|
||||
virtual bool has_delayed_script_class_metadata() const;
|
||||
virtual int find_function(const String &p_function, const String &p_code) const;
|
||||
virtual String make_function(const String &p_class, const String &p_name, const PoolStringArray &p_args) const;
|
||||
virtual Error complete_code(const String &p_code, const String &p_path, Object *p_owner, List<ScriptCodeCompletionOption> *r_options, bool &r_force, String &r_call_hint);
|
||||
|
@ -4656,19 +4656,41 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
|
||||
Variant constant = static_cast<ConstantNode *>(subexpr)->value;
|
||||
|
||||
if (constant.get_type() == Variant::OBJECT) {
|
||||
StringName class_name;
|
||||
GDScriptNativeClass *native_class = Object::cast_to<GDScriptNativeClass>(constant);
|
||||
|
||||
if (native_class && ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
|
||||
if (native_class) {
|
||||
if (ClassDB::is_parent_class(native_class->get_name(), "Resource")) {
|
||||
class_name = native_class->get_name();
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The export hint isn't a resource type.");
|
||||
}
|
||||
} else {
|
||||
Ref<Script> res_script = constant;
|
||||
StringName script_class;
|
||||
if (res_script.is_valid()) {
|
||||
script_class = res_script->get_language()->get_global_class_name(res_script->get_path());
|
||||
|
||||
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(script_class), "Resource")) {
|
||||
class_name = script_class;
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The exported script does not extend Resource.");
|
||||
}
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The exported script isn't a script class.");
|
||||
}
|
||||
}
|
||||
|
||||
if (class_name != StringName()) {
|
||||
current_export.type = Variant::OBJECT;
|
||||
current_export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
current_export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
|
||||
current_export.hint_string = native_class->get_name();
|
||||
current_export.class_name = native_class->get_name();
|
||||
|
||||
} else {
|
||||
current_export = PropertyInfo();
|
||||
_set_error("The export hint isn't a resource type.");
|
||||
current_export.hint_string = class_name;
|
||||
current_export.class_name = class_name;
|
||||
}
|
||||
} else if (constant.get_type() == Variant::DICTIONARY) {
|
||||
// Enumeration
|
||||
@ -4840,12 +4862,43 @@ void GDScriptParser::_parse_class(ClassNode *p_class) {
|
||||
member._export.hint_string = member.data_type.native_type;
|
||||
member._export.class_name = member.data_type.native_type;
|
||||
} else {
|
||||
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
||||
_set_error(vformat("Invalid native export type. \"%s\" is not a Resource type.", member.data_type.native_type), member.line);
|
||||
return;
|
||||
}
|
||||
} else if (member.data_type.kind == DataType::SCRIPT || member.data_type.kind == DataType::GDSCRIPT) {
|
||||
if (member.data_type.script_type.is_null()) {
|
||||
_set_error("Invalid script export type. Could not load member script value.", member.line);
|
||||
return;
|
||||
}
|
||||
Ref<Script> s = member.data_type.script_type;
|
||||
StringName class_name = s->get_language()->get_global_class_name(s->get_path());
|
||||
if (class_name == StringName()) {
|
||||
_set_error("Invalid script export type. The member is a script that has no global script class name.", member.line);
|
||||
return;
|
||||
}
|
||||
if (!ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
|
||||
_set_error("Invalid script export type. The member is a script class that does not extend a Resource type.", member.line);
|
||||
return;
|
||||
}
|
||||
|
||||
member._export.type = Variant::OBJECT;
|
||||
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
member._export.hint_string = class_name;
|
||||
member._export.class_name = class_name;
|
||||
|
||||
} else if (member.data_type.kind == DataType::UNRESOLVED && ScriptServer::is_global_class(member.data_type.native_type)) {
|
||||
StringName class_name = member.data_type.native_type;
|
||||
if (ClassDB::is_parent_class(ScriptServer::get_global_class_native_base(class_name), "Resource")) {
|
||||
member._export.type = Variant::OBJECT;
|
||||
member._export.hint = PROPERTY_HINT_RESOURCE_TYPE;
|
||||
member._export.usage |= PROPERTY_USAGE_SCRIPT_VARIABLE;
|
||||
member._export.hint_string = class_name;
|
||||
member._export.class_name = class_name;
|
||||
}
|
||||
|
||||
} else {
|
||||
_set_error("Invalid export type. Only built-in and native resource types can be exported.", member.line);
|
||||
_set_error("Invalid export type. Only built-in types and native or script class Resource types can be exported.", member.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -5926,7 +5979,22 @@ GDScriptParser::DataType GDScriptParser::_type_from_property(const PropertyInfo
|
||||
ret.builtin_type = p_property.type;
|
||||
if (p_property.type == Variant::OBJECT) {
|
||||
ret.kind = DataType::NATIVE;
|
||||
ret.native_type = p_property.class_name == StringName() ? "Object" : p_property.class_name;
|
||||
ret.native_type = "Object";
|
||||
if (p_property.class_name != StringName()) {
|
||||
if (ScriptServer::is_global_class(p_property.class_name)) {
|
||||
String p = ScriptServer::get_global_class_path(p_property.class_name);
|
||||
ret.native_type = ScriptServer::get_global_class_native_base(p_property.class_name);
|
||||
if (GDScriptLanguage::get_singleton()->get_extension() == p.get_extension()) {
|
||||
ret.kind = DataType::GDSCRIPT;
|
||||
ret.script_type = ResourceLoader::load(p, "GDScript");
|
||||
} else {
|
||||
ret.kind = DataType::SCRIPT;
|
||||
ret.script_type = ResourceLoader::load(p, "Script");
|
||||
}
|
||||
} else {
|
||||
ret.native_type = p_property.class_name;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ret.kind = DataType::BUILTIN;
|
||||
}
|
||||
@ -7857,13 +7925,24 @@ void GDScriptParser::_check_class_level_types(ClassNode *p_class) {
|
||||
}
|
||||
|
||||
// Check export hint
|
||||
if (v.data_type.has_type && v._export.type != Variant::NIL) {
|
||||
if (v._export.type != Variant::NIL) {
|
||||
DataType export_type = _type_from_property(v._export);
|
||||
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
||||
_set_error("The export hint's type (" + export_type.to_string() + ") doesn't match the variable's type (" +
|
||||
v.data_type.to_string() + ").",
|
||||
v.line);
|
||||
return;
|
||||
if (export_type.kind == DataType::GDSCRIPT || export_type.kind == DataType::SCRIPT) {
|
||||
String class_name = v._export.class_name;
|
||||
if (ScriptServer::is_global_class(class_name)) {
|
||||
class_name = ScriptServer::get_global_class_native_base(class_name);
|
||||
}
|
||||
if (!ClassDB::is_parent_class(class_name, "Resource")) {
|
||||
_set_error(vformat("Exported script-defined type (%s) must inherit from Resource.", export_type.to_string()), v.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (v.data_type.has_type) {
|
||||
if (!_is_type_compatible(v.data_type, export_type, true)) {
|
||||
_set_error(vformat("The export hint's type (%s) doesn't match the variable's type (%s).", export_type.to_string(), v.data_type.to_string()), v.line);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user