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:
Relintai 2023-09-06 01:45:29 +02:00
parent 2a7a431c94
commit 0a9c9ca15e
26 changed files with 878 additions and 230 deletions

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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; }

View File

@ -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>

View File

@ -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>

View 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>

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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);

View File

@ -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) {

View File

@ -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");
}

View File

@ -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();
};

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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;
}
}
}