summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/string/string_name.cpp59
-rw-r--r--doc/classes/EditorImportPlugin.xml10
-rw-r--r--doc/classes/Node.xml2
-rw-r--r--doc/classes/Viewport.xml4
-rw-r--r--editor/editor_file_system.cpp80
-rw-r--r--editor/editor_file_system.h3
-rw-r--r--editor/editor_node.cpp28
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/import/editor_import_plugin.cpp16
-rw-r--r--editor/import/editor_import_plugin.h3
-rw-r--r--editor/project_converter_3_to_4.cpp7
-rw-r--r--editor/project_converter_3_to_4.h3
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml4
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp2
-rw-r--r--modules/gdscript/gdscript_editor.cpp9
-rw-r--r--modules/gdscript/gdscript_parser.cpp46
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd12
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out2
-rw-r--r--modules/gltf/gltf_document.cpp63
-rw-r--r--modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp50
-rw-r--r--modules/text_server_adv/text_server_adv.h3
-rw-r--r--platform/android/export/export_plugin.cpp23
-rw-r--r--platform/android/export/gradle_export_util.cpp6
-rw-r--r--platform/android/java/lib/res/values/strings.xml1
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/Godot.java20
-rw-r--r--scene/gui/control.cpp6
-rw-r--r--scene/main/canvas_item.cpp11
-rw-r--r--scene/main/canvas_item.h1
-rw-r--r--scene/main/viewport.cpp31
-rw-r--r--scene/main/viewport.h3
37 files changed, 376 insertions, 158 deletions
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index 95812fc311..df9b6b3f1a 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -226,19 +226,16 @@ StringName::StringName(const char *p_name, bool p_static) {
_data = _data->next;
}
- if (_data) {
- if (_data->refcount.ref()) {
- // exists
- if (p_static) {
- _data->static_count.increment();
- }
+ if (_data && _data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
#ifdef DEBUG_ENABLED
- if (unlikely(debug_stringname)) {
- _data->debug_references++;
- }
-#endif
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
}
-
+#endif
return;
}
@@ -288,19 +285,17 @@ StringName::StringName(const StaticCString &p_static_string, bool p_static) {
_data = _data->next;
}
- if (_data) {
- if (_data->refcount.ref()) {
- // exists
- if (p_static) {
- _data->static_count.increment();
- }
+ if (_data && _data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
#ifdef DEBUG_ENABLED
- if (unlikely(debug_stringname)) {
- _data->debug_references++;
- }
-#endif
- return;
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
}
+#endif
+ return;
}
_data = memnew(_Data);
@@ -348,19 +343,17 @@ StringName::StringName(const String &p_name, bool p_static) {
_data = _data->next;
}
- if (_data) {
- if (_data->refcount.ref()) {
- // exists
- if (p_static) {
- _data->static_count.increment();
- }
+ if (_data && _data->refcount.ref()) {
+ // exists
+ if (p_static) {
+ _data->static_count.increment();
+ }
#ifdef DEBUG_ENABLED
- if (unlikely(debug_stringname)) {
- _data->debug_references++;
- }
-#endif
- return;
+ if (unlikely(debug_stringname)) {
+ _data->debug_references++;
}
+#endif
+ return;
}
_data = memnew(_Data);
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index 6a976d218f..66b61f187e 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -227,5 +227,15 @@
This method must be overridden to do the actual importing work. See this class' description for an example of overriding this method.
</description>
</method>
+ <method name="append_import_external_resource">
+ <return type="int" enum="Error" />
+ <param index="0" name="path" type="String" />
+ <param index="1" name="custom_options" type="Dictionary" default="{}" />
+ <param index="2" name="custom_importer" type="String" default="&quot;&quot;" />
+ <param index="3" name="generator_parameters" type="Variant" default="null" />
+ <description>
+ This function can only be called during the [method _import] callback and it allows manually importing resources from it. This is useful when the imported file generates external resources that require importing (as example, images). Custom parameters for the ".import" file can be passed via the [param custom_options]. Additionally, in cases where multiple importers can handle a file, the [param custom_importer] ca be specified to force a specific one. This function performs a resource import and returns immediately with a success or error code. [param generator_parameters] defines optional extra metadata which will be stored as [code]generator_parameters[/code] in the [code]remap[/code] section of the [code].import[/code] file, for example to store a md5 hash of the source data.
+ </description>
+ </method>
</methods>
</class>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index 22665c8ffb..bc43f228a7 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -666,7 +666,7 @@
channel = 0,
}
[/codeblock]
- See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding [annotation @GDScript.@rpc] annotation ([code]@rpc(any_peer)[/code], [code]@rpc(authority)[/code]). By default, methods are not exposed to networking (and RPCs).
+ See [enum MultiplayerAPI.RPCMode] and [enum MultiplayerPeer.TransferMode]. An alternative is annotating methods and properties with the corresponding [annotation @GDScript.@rpc] annotation ([code]@rpc("any_peer")[/code], [code]@rpc("authority")[/code]). By default, methods are not exposed to networking (and RPCs).
</description>
</method>
<method name="rpc_id" qualifiers="vararg">
diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml
index ab2de14638..e76f805e3c 100644
--- a/doc/classes/Viewport.xml
+++ b/doc/classes/Viewport.xml
@@ -277,6 +277,10 @@
<member name="physics_object_picking" type="bool" setter="set_physics_object_picking" getter="get_physics_object_picking" default="false">
If [code]true[/code], the objects rendered by viewport become subjects of mouse picking process.
</member>
+ <member name="physics_object_picking_sort" type="bool" setter="set_physics_object_picking_sort" getter="get_physics_object_picking_sort" default="false">
+ If [code]true[/code], objects receive mouse picking events sorted primarily by their [member CanvasItem.z_index] and secondarily by their position in the scene tree. If [code]false[/code], the order is undetermined.
+ [b]Note:[/b] This setting is disabled by default because of its potential expensive computational cost.
+ </member>
<member name="positional_shadow_atlas_16_bits" type="bool" setter="set_positional_shadow_atlas_16_bits" getter="get_positional_shadow_atlas_16_bits" default="true">
</member>
<member name="positional_shadow_atlas_quad_0" type="int" setter="set_positional_shadow_atlas_quadrant_subdiv" getter="get_positional_shadow_atlas_quadrant_subdiv" enum="Viewport.PositionalShadowAtlasQuadrantSubdiv" default="2">
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 644c32e8a4..ec1ef8a6bc 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -1900,49 +1900,56 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
return err;
}
-void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> *p_custom_options, const String &p_custom_importer) {
+Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant *p_generator_parameters) {
EditorFileSystemDirectory *fs = nullptr;
int cpos = -1;
bool found = _find_file(p_file, &fs, cpos);
- ERR_FAIL_COND_MSG(!found, "Can't find file '" + p_file + "'.");
+ ERR_FAIL_COND_V_MSG(!found, ERR_FILE_NOT_FOUND, "Can't find file '" + p_file + "'.");
//try to obtain existing params
- HashMap<StringName, Variant> params;
+ HashMap<StringName, Variant> params = p_custom_options;
String importer_name; //empty by default though
if (!p_custom_importer.is_empty()) {
importer_name = p_custom_importer;
}
- if (p_custom_options != nullptr) {
- params = *p_custom_options;
- }
ResourceUID::ID uid = ResourceUID::INVALID_ID;
+ Variant generator_parameters;
+ if (p_generator_parameters) {
+ generator_parameters = *p_generator_parameters;
+ }
if (FileAccess::exists(p_file + ".import")) {
//use existing
- if (p_custom_options == nullptr) {
- Ref<ConfigFile> cf;
- cf.instantiate();
- Error err = cf->load(p_file + ".import");
- if (err == OK) {
- if (cf->has_section("params")) {
- List<String> sk;
- cf->get_section_keys("params", &sk);
- for (const String &E : sk) {
+ Ref<ConfigFile> cf;
+ cf.instantiate();
+ Error err = cf->load(p_file + ".import");
+ if (err == OK) {
+ if (cf->has_section("params")) {
+ List<String> sk;
+ cf->get_section_keys("params", &sk);
+ for (const String &E : sk) {
+ if (!params.has(E)) {
params[E] = cf->get_value("params", E);
}
}
+ }
+
+ if (cf->has_section("remap")) {
+ if (p_custom_importer.is_empty()) {
+ importer_name = cf->get_value("remap", "importer");
+ }
- if (cf->has_section("remap")) {
- if (p_custom_importer.is_empty()) {
- importer_name = cf->get_value("remap", "importer");
- }
+ if (cf->has_section_key("remap", "uid")) {
+ String uidt = cf->get_value("remap", "uid");
+ uid = ResourceUID::get_singleton()->text_to_id(uidt);
+ }
- if (cf->has_section_key("remap", "uid")) {
- String uidt = cf->get_value("remap", "uid");
- uid = ResourceUID::get_singleton()->text_to_id(uidt);
+ if (!p_generator_parameters) {
+ if (cf->has_section_key("remap", "generator_parameters")) {
+ generator_parameters = cf->get_value("remap", "generator_parameters");
}
}
}
@@ -1957,7 +1964,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
fs->files[cpos]->type = "";
fs->files[cpos]->import_valid = false;
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
- return;
+ return OK;
}
Ref<ResourceImporter> importer;
bool load_default = false;
@@ -1971,8 +1978,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
importer = ResourceFormatImporter::get_singleton()->get_importer_by_extension(p_file.get_extension());
load_default = true;
if (importer.is_null()) {
- ERR_PRINT("BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found.");
- ERR_FAIL();
+ ERR_FAIL_V_MSG(ERR_FILE_CANT_OPEN, "BUG: File queued for import, but can't be imported, importer for type '" + importer_name + "' not found.");
}
}
@@ -2005,16 +2011,14 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
Variant meta;
Error err = importer->import(p_file, base_path, params, &import_variants, &gen_files, &meta);
- if (err != OK) {
- ERR_PRINT("Error importing '" + p_file + "'.");
- }
+ ERR_FAIL_COND_V_MSG(err != OK, ERR_FILE_UNRECOGNIZED, "Error importing '" + p_file + "'.");
//as import is complete, save the .import file
Vector<String> dest_paths;
{
Ref<FileAccess> f = FileAccess::open(p_file + ".import", FileAccess::WRITE);
- ERR_FAIL_COND_MSG(f.is_null(), "Cannot open file from path '" + p_file + ".import'.");
+ ERR_FAIL_COND_V_MSG(f.is_null(), ERR_FILE_CANT_OPEN, "Cannot open file from path '" + p_file + ".import'.");
//write manually, as order matters ([remap] has to go first for performance).
f->store_line("[remap]");
@@ -2059,6 +2063,10 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
f->store_line("metadata=" + meta.get_construct_string());
}
+ if (generator_parameters != Variant()) {
+ f->store_line("generator_parameters=" + generator_parameters.get_construct_string());
+ }
+
f->store_line("");
f->store_line("[deps]\n");
@@ -2102,7 +2110,7 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
// Store the md5's of the various files. These are stored separately so that the .import files can be version controlled.
{
Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::WRITE);
- ERR_FAIL_COND_MSG(md5s.is_null(), "Cannot open MD5 file '" + base_path + ".md5'.");
+ ERR_FAIL_COND_V_MSG(md5s.is_null(), ERR_FILE_CANT_OPEN, "Cannot open MD5 file '" + base_path + ".md5'.");
md5s->store_line("source_md5=\"" + FileAccess::get_md5(p_file) + "\"");
if (dest_paths.size()) {
@@ -2136,6 +2144,8 @@ void EditorFileSystem::_reimport_file(const String &p_file, const HashMap<String
}
EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
+
+ return OK;
}
void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap<String, Vector<String>> &group_files, HashSet<String> &groups_to_reimport) {
@@ -2156,7 +2166,7 @@ void EditorFileSystem::_find_group_files(EditorFileSystemDirectory *efd, HashMap
}
void EditorFileSystem::reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params) {
- _reimport_file(p_file, &p_custom_params, p_importer);
+ _reimport_file(p_file, p_custom_params, p_importer);
}
void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_import_data) {
@@ -2166,10 +2176,11 @@ void EditorFileSystem::_reimport_thread(uint32_t p_index, ImportThreadData *p_im
void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
importing = true;
- EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size());
Vector<String> reloads;
+ EditorProgress pr("reimport", TTR("(Re)Importing Assets"), p_files.size());
+
Vector<ImportFile> reimport_files;
HashSet<String> groups_to_reimport;
@@ -2292,6 +2303,11 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
emit_signal(SNAME("resources_reimported"), reloads);
}
+Error EditorFileSystem::reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
+ ERR_FAIL_COND_V_MSG(!importing, ERR_INVALID_PARAMETER, "Can only append files to import during a current reimport process.");
+ return _reimport_file(p_file, p_custom_options, p_custom_importer, &p_generator_parameters);
+}
+
Error EditorFileSystem::_resource_import(const String &p_path) {
Vector<String> files;
files.push_back(p_path);
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index 2490bd31b3..0d558c84c5 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -242,7 +242,7 @@ class EditorFileSystem : public Node {
void _update_extensions();
- void _reimport_file(const String &p_file, const HashMap<StringName, Variant> *p_custom_options = nullptr, const String &p_custom_importer = String());
+ Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr);
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
bool _test_for_reimport(const String &p_path, bool p_only_imported_files);
@@ -315,6 +315,7 @@ public:
EditorFileSystemDirectory *find_file(const String &p_file, int *r_index) const;
void reimport_files(const Vector<String> &p_files);
+ Error reimport_append(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters);
void reimport_file_with_custom_parameters(const String &p_file, const String &p_importer, const HashMap<StringName, Variant> &p_custom_params);
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f317c23b83..f6fe6c9f76 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -852,6 +852,18 @@ void EditorNode::_remove_plugin_from_enabled(const String &p_name) {
ps->set("editor_plugins/enabled", enabled_plugins);
}
+void EditorNode::_plugin_over_edit(EditorPlugin *p_plugin, Object *p_object) {
+ if (p_object) {
+ editor_plugins_over->add_plugin(p_plugin);
+ p_plugin->make_visible(true);
+ p_plugin->edit(p_object);
+ } else {
+ editor_plugins_over->remove_plugin(p_plugin);
+ p_plugin->make_visible(false);
+ p_plugin->edit(nullptr);
+ }
+}
+
void EditorNode::_resources_changed(const Vector<String> &p_resources) {
List<Ref<Resource>> changed;
@@ -2102,8 +2114,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
if (!item_plugins.has(plugin)) {
// Remove plugins no longer used by this editing owner.
to_remove.push_back(plugin);
- plugin->make_visible(false);
- plugin->edit(nullptr);
+ _plugin_over_edit(plugin, nullptr);
}
}
@@ -2113,6 +2124,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
for (EditorPlugin *plugin : item_plugins) {
if (active_plugins[owner_id].has(plugin)) {
+ plugin->edit(p_object);
continue;
}
@@ -2127,9 +2139,7 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
}
}
active_plugins[owner_id].insert(plugin);
- editor_plugins_over->add_plugin(plugin);
- plugin->edit(p_object);
- plugin->make_visible(true);
+ _plugin_over_edit(plugin, p_object);
}
} else {
hide_unused_editors(p_editing_owner);
@@ -2181,9 +2191,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
if (p_editing_owner) {
const ObjectID id = p_editing_owner->get_instance_id();
for (EditorPlugin *plugin : active_plugins[id]) {
- plugin->make_visible(false);
- plugin->edit(nullptr);
- editor_plugins_over->remove_plugin(plugin);
+ _plugin_over_edit(plugin, nullptr);
}
active_plugins.erase(id);
} else {
@@ -2194,9 +2202,7 @@ void EditorNode::hide_unused_editors(const Object *p_editing_owner) {
if (!ObjectDB::get_instance(kv.key)) {
to_remove.push_back(kv.key);
for (EditorPlugin *plugin : kv.value) {
- plugin->make_visible(false);
- plugin->edit(nullptr);
- editor_plugins_over->remove_plugin(plugin);
+ _plugin_over_edit(plugin, nullptr);
}
}
}
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 914dab0254..19a0e49a12 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -565,6 +565,7 @@ private:
void _update_file_menu_closed();
void _remove_plugin_from_enabled(const String &p_name);
+ void _plugin_over_edit(EditorPlugin *p_plugin, Object *p_object);
void _fs_changed();
void _resources_reimported(const Vector<String> &p_resources);
diff --git a/editor/import/editor_import_plugin.cpp b/editor/import/editor_import_plugin.cpp
index ef3d3d1276..7afce116b8 100644
--- a/editor/import/editor_import_plugin.cpp
+++ b/editor/import/editor_import_plugin.cpp
@@ -31,6 +31,7 @@
#include "editor_import_plugin.h"
#include "core/object/script_language.h"
+#include "editor/editor_file_system.h"
EditorImportPlugin::EditorImportPlugin() {
}
@@ -185,6 +186,20 @@ Error EditorImportPlugin::import(const String &p_source_file, const String &p_sa
ERR_FAIL_V_MSG(ERR_METHOD_NOT_FOUND, "Unimplemented _import in add-on.");
}
+Error EditorImportPlugin::_append_import_external_resource(const String &p_file, const Dictionary &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
+ HashMap<StringName, Variant> options;
+ List<Variant> keys;
+ p_custom_options.get_key_list(&keys);
+ for (const Variant &K : keys) {
+ options.insert(K, p_custom_options[K]);
+ }
+ return append_import_external_resource(p_file, options, p_custom_importer, p_generator_parameters);
+}
+
+Error EditorImportPlugin::append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options, const String &p_custom_importer, Variant p_generator_parameters) {
+ return EditorFileSystem::get_singleton()->reimport_append(p_file, p_custom_options, p_custom_importer, p_generator_parameters);
+}
+
void EditorImportPlugin::_bind_methods() {
GDVIRTUAL_BIND(_get_importer_name)
GDVIRTUAL_BIND(_get_visible_name)
@@ -198,4 +213,5 @@ void EditorImportPlugin::_bind_methods() {
GDVIRTUAL_BIND(_get_import_order)
GDVIRTUAL_BIND(_get_option_visibility, "path", "option_name", "options")
GDVIRTUAL_BIND(_import, "source_file", "save_path", "options", "platform_variants", "gen_files");
+ ClassDB::bind_method(D_METHOD("append_import_external_resource", "path", "custom_options", "custom_importer", "generator_parameters"), &EditorImportPlugin::_append_import_external_resource, DEFVAL(Dictionary()), DEFVAL(String()), DEFVAL(Variant()));
}
diff --git a/editor/import/editor_import_plugin.h b/editor/import/editor_import_plugin.h
index bf912058a2..fb164c7f15 100644
--- a/editor/import/editor_import_plugin.h
+++ b/editor/import/editor_import_plugin.h
@@ -53,6 +53,8 @@ protected:
GDVIRTUAL3RC(bool, _get_option_visibility, String, StringName, Dictionary)
GDVIRTUAL5RC(Error, _import, String, String, Dictionary, TypedArray<String>, TypedArray<String>)
+ Error _append_import_external_resource(const String &p_file, const Dictionary &p_custom_options = Dictionary(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant());
+
public:
EditorImportPlugin();
virtual String get_importer_name() const override;
@@ -67,6 +69,7 @@ public:
virtual void get_import_options(const String &p_path, List<ImportOption> *r_options, int p_preset) const override;
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files, Variant *r_metadata = nullptr) override;
+ Error append_import_external_resource(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant p_generator_parameters = Variant());
};
#endif // EDITOR_IMPORT_PLUGIN_H
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index 26f872421e..706466a974 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -30,13 +30,14 @@
#include "project_converter_3_to_4.h"
-#include "modules/modules_enabled.gen.h"
-
#ifndef DISABLE_DEPRECATED
-#ifdef MODULE_REGEX_ENABLED
const int ERROR_CODE = 77;
+#include "modules/modules_enabled.gen.h" // For regex.
+
+#ifdef MODULE_REGEX_ENABLED
+
#include "modules/regex/regex.h"
#include "core/io/dir_access.h"
diff --git a/editor/project_converter_3_to_4.h b/editor/project_converter_3_to_4.h
index 6ec2dd188d..641bc467ac 100644
--- a/editor/project_converter_3_to_4.h
+++ b/editor/project_converter_3_to_4.h
@@ -29,9 +29,10 @@
/**************************************************************************/
#ifndef PROJECT_CONVERTER_3_TO_4_H
-#ifndef DISABLE_DEPRECATED
#define PROJECT_CONVERTER_3_TO_4_H
+#ifndef DISABLE_DEPRECATED
+
#include "core/io/file_access.h"
#include "core/object/ref_counted.h"
#include "core/string/ustring.h"
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 026b603683..e05b17168d 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -576,10 +576,10 @@
@rpc
func fn(): pass
- @rpc(any_peer, unreliable_ordered)
+ @rpc("any_peer", "unreliable_ordered")
func fn_update_pos(): pass
- @rpc(authority, call_remote, unreliable, 0) # Equivalent to @rpc
+ @rpc("authority", "call_remote", "unreliable", 0) # Equivalent to @rpc
func fn_default(): pass
[/codeblock]
</description>
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index e84c79d681..cafc7328e0 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -4113,7 +4113,6 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
if (!is_type_compatible(true_type, false_type)) {
result = false_type;
if (!is_type_compatible(false_type, true_type)) {
- result.type_source = GDScriptParser::DataType::UNDETECTED;
result.kind = GDScriptParser::DataType::VARIANT;
#ifdef DEBUG_ENABLED
parser->push_warning(p_ternary_op, GDScriptWarning::INCOMPATIBLE_TERNARY);
@@ -4121,6 +4120,7 @@ void GDScriptAnalyzer::reduce_ternary_op(GDScriptParser::TernaryOpNode *p_ternar
}
}
}
+ result.type_source = true_type.is_hard_type() && false_type.is_hard_type() ? GDScriptParser::DataType::ANNOTATED_INFERRED : GDScriptParser::DataType::INFERRED;
p_ternary_op->set_datatype(result);
}
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 12c10642ec..4e7d278aab 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -800,6 +800,15 @@ static void _find_annotation_arguments(const GDScriptParser::AnnotationNode *p_a
warning.insert_text = warning.display.quote(p_quote_style);
r_result.insert(warning.display, warning);
}
+ } else if (p_annotation->name == SNAME("@rpc")) {
+ if (p_argument == 0 || p_argument == 1 || p_argument == 2) {
+ static const char *options[7] = { "call_local", "call_remote", "any_peer", "authority", "reliable", "unreliable", "unreliable_ordered" };
+ for (int i = 0; i < 7; i++) {
+ ScriptLanguage::CodeCompletionOption option(options[i], ScriptLanguage::CODE_COMPLETION_KIND_PLAIN_TEXT);
+ option.insert_text = option.display.quote(p_quote_style);
+ r_result.insert(option.display, option);
+ }
+ }
}
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index d99a2b86a2..acc3c5d079 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3611,6 +3611,10 @@ bool GDScriptParser::icon_annotation(const AnnotationNode *p_annotation, Node *p
bool GDScriptParser::onready_annotation(const AnnotationNode *p_annotation, Node *p_node) {
ERR_FAIL_COND_V_MSG(p_node->type != Node::VARIABLE, false, R"("@onready" annotation can only be applied to class variables.)");
+ if (head && !ClassDB::is_parent_class(head->get_datatype().native_type, SNAME("Node"))) {
+ push_error(R"("@onready" can only be used in classes that inherit "Node".)", p_annotation);
+ }
+
VariableNode *variable = static_cast<VariableNode *>(p_node);
if (variable->onready) {
push_error(R"("@onready" annotation can only be used once per variable.)");
@@ -3902,26 +3906,46 @@ bool GDScriptParser::rpc_annotation(const AnnotationNode *p_annotation, Node *p_
push_error(R"(Invalid RPC arguments. At most 4 arguments are allowed, where only the last argument can be an integer to specify the channel.')", p_annotation);
return false;
}
+
+ unsigned char locality_args = 0;
+ unsigned char permission_args = 0;
+ unsigned char transfer_mode_args = 0;
+
for (int i = last; i >= 0; i--) {
- String mode = p_annotation->resolved_arguments[i].operator String();
- if (mode == "any_peer") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
- } else if (mode == "authority") {
- rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
- } else if (mode == "call_local") {
+ String arg = p_annotation->resolved_arguments[i].operator String();
+ if (arg == "call_local") {
+ locality_args++;
rpc_config["call_local"] = true;
- } else if (mode == "call_remote") {
+ } else if (arg == "call_remote") {
+ locality_args++;
rpc_config["call_local"] = false;
- } else if (mode == "reliable") {
+ } else if (arg == "any_peer") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_ANY_PEER;
+ } else if (arg == "authority") {
+ permission_args++;
+ rpc_config["rpc_mode"] = MultiplayerAPI::RPC_MODE_AUTHORITY;
+ } else if (arg == "reliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_RELIABLE;
- } else if (mode == "unreliable") {
+ } else if (arg == "unreliable") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE;
- } else if (mode == "unreliable_ordered") {
+ } else if (arg == "unreliable_ordered") {
+ transfer_mode_args++;
rpc_config["transfer_mode"] = MultiplayerPeer::TRANSFER_MODE_UNRELIABLE_ORDERED;
} else {
- push_error(R"(Invalid RPC argument. Must be one of: 'call_local'/'call_remote' (local calls), 'any_peer'/'authority' (permission), 'reliable'/'unreliable'/'unreliable_ordered' (transfer mode).)", p_annotation);
+ push_error(R"(Invalid RPC argument. Must be one of: "call_local"/"call_remote" (local calls), "any_peer"/"authority" (permission), "reliable"/"unreliable"/"unreliable_ordered" (transfer mode).)", p_annotation);
}
}
+
+ if (locality_args > 1) {
+ push_error(R"(Invalid RPC config. The locality ("call_local"/"call_remote") must be specified no more than once.)", p_annotation);
+ } else if (permission_args > 1) {
+ push_error(R"(Invalid RPC config. The permission ("any_peer"/"authority") must be specified no more than once.)", p_annotation);
+ } else if (transfer_mode_args > 1) {
+ push_error(R"(Invalid RPC config. The transfer mode ("reliable"/"unreliable"/"unreliable_ordered") must be specified no more than once.)", p_annotation);
+ }
}
function->rpc_config = rpc_config;
return true;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd
new file mode 100644
index 0000000000..91f5071fa9
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.gd
@@ -0,0 +1,5 @@
+extends RefCounted
+
+func test():
+ var nope := $Node
+ print("Cannot use $ without a Node base")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out
new file mode 100644
index 0000000000..33365908bf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/get_node_shorthand_within_non_node.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot use shorthand "get_node()" notation ("$") on a class that isn't a node.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd
new file mode 100644
index 0000000000..e781315266
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.gd
@@ -0,0 +1,6 @@
+extends RefCounted
+
+@onready var nope := 0
+
+func test():
+ print("Cannot use @onready without a Node base")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out
new file mode 100644
index 0000000000..8088d28329
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/onready_within_non_node.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+"@onready" can only be used in classes that inherit "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd
new file mode 100644
index 0000000000..fac0e8756c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.gd
@@ -0,0 +1,6 @@
+func test():
+ var left_hard_int := 1
+ var right_weak_int = 2
+ var result_hm_int := left_hard_int if true else right_weak_int
+
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out
new file mode 100644
index 0000000000..71d1e2f8ae
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/ternary_weak_infer.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot infer the type of "result_hm_int" variable because the value doesn't have a set type.
diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
new file mode 100644
index 0000000000..fbb7530615
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.gd
@@ -0,0 +1,12 @@
+func test():
+ var left_hard_int := 1
+ var right_hard_int := 2
+ var result_hard_int := left_hard_int if true else right_hard_int
+ assert(result_hard_int == 1)
+
+ var left_hard_variant := 1 as Variant
+ var right_hard_variant := 2.0 as Variant
+ var result_hard_variant := left_hard_variant if true else right_hard_variant
+ assert(result_hard_variant == 1)
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/ternary_hard_infer.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 1a09b5bdcc..bd3ee1881f 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -54,6 +54,9 @@
#include "modules/modules_enabled.gen.h" // For csg, gridmap.
+#ifdef TOOLS_ENABLED
+#include "editor/editor_file_system.h"
+#endif
#ifdef MODULE_CSG_ENABLED
#include "modules/csg/csg_shape.h"
#endif // MODULE_CSG_ENABLED
@@ -3232,54 +3235,38 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> p_state, const String &p_base_p
p_state->source_images.push_back(Ref<Image>());
} else {
Error err = OK;
- bool must_import = false;
+ bool must_import = true;
+ Vector<uint8_t> img_data = img->get_data();
+ Dictionary generator_parameters;
String file_path = p_state->get_base_path() + "/" + p_state->filename.get_basename() + "_" + img->get_name() + ".png";
- if (!FileAccess::exists(file_path + ".import")) {
+ if (FileAccess::exists(file_path + ".import")) {
Ref<ConfigFile> config;
config.instantiate();
- config->set_value("remap", "importer", "texture");
- config->set_value("remap", "type", "Texture2D");
- // Currently, it will likely use project defaults of Detect 3D, so textures will be reimported again.
- if (!config->has_section_key("params", "mipmaps/generate")) {
- config->set_value("params", "mipmaps/generate", true);
+ config->load(file_path + ".import");
+ if (config->has_section_key("remap", "generator_parameters")) {
+ generator_parameters = (Dictionary)config->get_value("remap", "generator_parameters");
}
-
- if (ProjectSettings::get_singleton()->has_setting("importer_defaults/texture")) {
- //use defaults if exist
- Dictionary importer_defaults = GLOBAL_GET("importer_defaults/texture");
- List<Variant> importer_def_keys;
- importer_defaults.get_key_list(&importer_def_keys);
- for (const Variant &key : importer_def_keys) {
- if (!config->has_section_key("params", (String)key)) {
- config->set_value("params", (String)key, importer_defaults[key]);
- }
- }
+ if (!generator_parameters.has("md5")) {
+ must_import = false; // Didn't come form a gltf document; don't overwrite.
}
- err = config->save(file_path + ".import");
- ERR_FAIL_COND_V(err != OK, err);
- must_import = true;
- }
- Vector<uint8_t> png_buffer = img->save_png_to_buffer();
- if (ResourceLoader::exists(file_path)) {
- Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::READ, &err);
- if (err == OK && file.is_valid()) {
- Vector<uint8_t> orig_png_buffer = file->get_buffer(file->get_length());
- if (png_buffer != orig_png_buffer) {
- must_import = true;
- }
+ String existing_md5 = generator_parameters["md5"];
+ unsigned char md5_hash[16];
+ CryptoCore::md5(img_data.ptr(), img_data.size(), md5_hash);
+ String new_md5 = String::hex_encode_buffer(md5_hash, 16);
+ generator_parameters["md5"] = new_md5;
+ if (new_md5 == existing_md5) {
+ must_import = false;
}
- } else {
- must_import = true;
}
if (must_import) {
- Ref<FileAccess> file = FileAccess::open(file_path, FileAccess::WRITE, &err);
+ err = img->save_png(file_path);
ERR_FAIL_COND_V(err != OK, err);
- ERR_FAIL_COND_V(file.is_null(), FAILED);
- file->store_buffer(png_buffer);
- file->flush();
- file.unref();
// ResourceLoader::import will crash if not is_editor_hint(), so this case is protected above and will fall through to uncompressed.
- ResourceLoader::import(file_path);
+ HashMap<StringName, Variant> custom_options;
+ custom_options[SNAME("mipmaps/generate")] = true;
+ // Will only use project settings defaults if custom_importer is empty.
+ EditorFileSystem::get_singleton()->update_file(file_path);
+ EditorFileSystem::get_singleton()->reimport_append(file_path, custom_options, String(), generator_parameters);
}
Ref<Texture2D> saved_image = ResourceLoader::load(file_path, "Texture2D");
if (saved_image.is_valid()) {
diff --git a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
index 02d0226e90..93baf4e51c 100644
--- a/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotPlugins/PluginLoadContext.cs
@@ -30,7 +30,7 @@ namespace GodotPlugins
if (baseDirectory != null)
{
if (!Path.EndsInDirectorySeparator(baseDirectory))
- baseDirectory += Path.PathSeparator;
+ baseDirectory += Path.DirectorySeparatorChar;
// This SetData call effectively sets AppContext.BaseDirectory
// See https://github.com/dotnet/runtime/blob/v6.0.0/src/libraries/System.Private.CoreLib/src/System/AppContext.cs#L21-L25
AppDomain.CurrentDomain.SetData("APP_CONTEXT_BASE_DIRECTORY", baseDirectory);
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 9b474bf2ce..b55188ce0c 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -6118,20 +6118,22 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
Vector<UChar *> skeletons;
skeletons.resize(p_dict.size());
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setChecks(sc, USPOOF_CONFUSABLE, &status);
+ if (sc_conf == nullptr) {
+ sc_conf = uspoof_open(&status);
+ uspoof_setChecks(sc_conf, USPOOF_CONFUSABLE, &status);
+ }
for (int i = 0; i < p_dict.size(); i++) {
Char16String word = p_dict[i].utf16();
- int32_t len = uspoof_getSkeleton(sc, 0, word.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, NULL, 0, &status);
skeletons.write[i] = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, word.get_data(), -1, skeletons.write[i], len, &status);
+ uspoof_getSkeleton(sc_conf, 0, word.get_data(), -1, skeletons.write[i], len, &status);
}
- int32_t len = uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, NULL, 0, &status);
+ int32_t len = uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, NULL, 0, &status);
UChar *skel = (UChar *)memalloc(++len * sizeof(UChar));
status = U_ZERO_ERROR;
- uspoof_getSkeleton(sc, 0, utf16.get_data(), -1, skel, len, &status);
+ uspoof_getSkeleton(sc_conf, 0, utf16.get_data(), -1, skel, len, &status);
for (int i = 0; i < skeletons.size(); i++) {
if (u_strcmp(skel, skeletons[i]) == 0) {
match_index = i;
@@ -6143,7 +6145,6 @@ int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedS
for (int i = 0; i < skeletons.size(); i++) {
memfree(skeletons.write[i]);
}
- uspoof_close(sc);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), -1, u_errorName(status));
@@ -6159,19 +6160,18 @@ bool TextServerAdvanced::_spoof_check(const String &p_string) const {
UErrorCode status = U_ZERO_ERROR;
Char16String utf16 = p_string.utf16();
- USet *allowed = uset_openEmpty();
- uset_addAll(allowed, uspoof_getRecommendedSet(&status));
- uset_addAll(allowed, uspoof_getInclusionSet(&status));
-
- USpoofChecker *sc = uspoof_open(&status);
- uspoof_setAllowedChars(sc, allowed, &status);
- uspoof_setRestrictionLevel(sc, USPOOF_MODERATELY_RESTRICTIVE);
-
- int32_t bitmask = uspoof_check(sc, utf16.get_data(), -1, NULL, &status);
-
- uspoof_close(sc);
- uset_close(allowed);
+ if (allowed == nullptr) {
+ allowed = uset_openEmpty();
+ uset_addAll(allowed, uspoof_getRecommendedSet(&status));
+ uset_addAll(allowed, uspoof_getInclusionSet(&status));
+ }
+ if (sc_spoof == nullptr) {
+ sc_spoof = uspoof_open(&status);
+ uspoof_setAllowedChars(sc_spoof, allowed, &status);
+ uspoof_setRestrictionLevel(sc_spoof, USPOOF_MODERATELY_RESTRICTIVE);
+ }
+ int32_t bitmask = uspoof_check(sc_spoof, utf16.get_data(), -1, NULL, &status);
ERR_FAIL_COND_V_MSG(U_FAILURE(status), false, u_errorName(status));
return (bitmask != 0);
@@ -6587,5 +6587,17 @@ TextServerAdvanced::~TextServerAdvanced() {
FT_Done_FreeType(ft_library);
}
#endif
+ if (sc_spoof != nullptr) {
+ uspoof_close(sc_spoof);
+ sc_spoof = nullptr;
+ }
+ if (sc_conf != nullptr) {
+ uspoof_close(sc_conf);
+ sc_conf = nullptr;
+ }
+ if (allowed != nullptr) {
+ uset_close(allowed);
+ allowed = nullptr;
+ }
u_cleanup();
}
diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h
index c7fe46d554..1acf5b21f0 100644
--- a/modules/text_server_adv/text_server_adv.h
+++ b/modules/text_server_adv/text_server_adv.h
@@ -159,6 +159,9 @@ class TextServerAdvanced : public TextServerExtension {
// ICU support data.
bool icu_data_loaded = false;
+ mutable USet *allowed = nullptr;
+ mutable USpoofChecker *sc_spoof = nullptr;
+ mutable USpoofChecker *sc_conf = nullptr;
// Font cache data.
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index c02acbee83..8cc2b1eb97 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -252,6 +252,7 @@ static const char *APK_ASSETS_DIRECTORY = "res://android/build/assets";
static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/installTime/src/main/assets";
static const int DEFAULT_MIN_SDK_VERSION = 21; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
+static const int VULKAN_MIN_SDK_VERSION = 24;
static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
#ifndef ANDROID_ENABLED
@@ -1056,6 +1057,15 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<bool> feature_required_list;
Vector<int> feature_versions;
+ String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
+ bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile";
+ if (has_vulkan) {
+ // Require vulkan hardware level 1 support
+ feature_names.push_back("android.hardware.vulkan.level");
+ feature_required_list.push_back(true);
+ feature_versions.push_back(1);
+ }
+
if (feature_names.size() > 0) {
ofs += 24; // skip over end tag
@@ -2373,6 +2383,19 @@ bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<Edit
err += "\n";
}
+ String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
+ bool uses_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile";
+ if (current_renderer == "forward_plus") {
+ // Warning only, so don't override `valid`.
+ err += vformat(TTR("The \"%s\" renderer is designed for Desktop devices, and is not suitable for Android devices."), current_renderer);
+ err += "\n";
+ }
+ if (uses_vulkan && min_sdk_int < VULKAN_MIN_SDK_VERSION) {
+ // Warning only, so don't override `valid`.
+ err += vformat(TTR("\"Min SDK\" should be greater or equal to %d for the \"%s\" renderer."), VULKAN_MIN_SDK_VERSION, current_renderer);
+ err += "\n";
+ }
+
r_error = err;
return valid;
}
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index 5e71116c10..7eb595f48d 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -273,6 +273,12 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"com.oculus.feature.PASSTHROUGH\" android:required=\"true\" />\n";
}
}
+
+ String current_renderer = GLOBAL_GET("rendering/renderer/rendering_method.mobile");
+ bool has_vulkan = current_renderer == "forward_plus" || current_renderer == "mobile";
+ if (has_vulkan) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vulkan.level\" android:required=\"true\" android:version=\"1\" />\n";
+ }
return manifest_xr_features;
}
diff --git a/platform/android/java/lib/res/values/strings.xml b/platform/android/java/lib/res/values/strings.xml
index 7efac4ce71..f76f597140 100644
--- a/platform/android/java/lib/res/values/strings.xml
+++ b/platform/android/java/lib/res/values/strings.xml
@@ -14,6 +14,7 @@
<string name="text_button_cancel_verify">Cancel Verification</string>
<string name="text_error_title">Error!</string>
<string name="error_engine_setup_message">Unable to setup the Godot Engine! Aborting…</string>
+ <string name="error_missing_vulkan_requirements_message">This device does not meet the requirements for Vulkan support! Aborting…</string>
<!-- APK Expansion Strings -->
diff --git a/platform/android/java/lib/src/org/godotengine/godot/Godot.java b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
index 50263bc392..6296ee2c22 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/Godot.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/Godot.java
@@ -258,13 +258,13 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
*/
@Keep
private boolean onVideoInit() {
- final Activity activity = getActivity();
+ final Activity activity = requireActivity();
containerLayout = new FrameLayout(activity);
containerLayout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
// GodotEditText layout
GodotEditText editText = new GodotEditText(activity);
- editText.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT,
+ editText.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
(int)getResources().getDimension(R.dimen.text_edit_height)));
// ...add to FrameLayout
containerLayout.addView(editText);
@@ -279,6 +279,11 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
if (renderer.equals("gl_compatibility")) {
mRenderView = new GodotGLRenderView(activity, this, xrMode, use_debug_opengl);
} else {
+ if (!meetsVulkanRequirements(activity.getPackageManager())) {
+ Log.e(TAG, "Missing requirements for vulkan support! Aborting...");
+ alert(R.string.error_missing_vulkan_requirements_message, R.string.text_error_title, this::forceQuit);
+ return false;
+ }
mRenderView = new GodotVulkanRenderView(activity, this);
}
@@ -317,6 +322,17 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
return true;
}
+ /**
+ * Returns true if the device meets the base requirements for Vulkan support, false otherwise.
+ */
+ private boolean meetsVulkanRequirements(@Nullable PackageManager packageManager) {
+ if (packageManager == null) {
+ return false;
+ }
+
+ return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && packageManager.hasSystemFeature(PackageManager.FEATURE_VULKAN_HARDWARE_LEVEL, 1);
+ }
+
public void setKeepScreenOn(final boolean p_enabled) {
runOnUiThread(() -> {
if (p_enabled) {
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index f09e4962a9..a930b8d972 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -644,8 +644,10 @@ Rect2 Control::get_parent_anchorable_rect() const {
parent_rect = data.parent_canvas_item->get_anchorable_rect();
} else {
#ifdef TOOLS_ENABLED
- Node *edited_root = get_tree()->get_edited_scene_root();
- if (edited_root && (this == edited_root || edited_root->is_ancestor_of(this))) {
+ Node *edited_scene_root = get_tree()->get_edited_scene_root();
+ Node *scene_root_parent = edited_scene_root ? edited_scene_root->get_parent() : nullptr;
+
+ if (scene_root_parent && get_viewport() == scene_root_parent->get_viewport()) {
parent_rect.size = Size2(GLOBAL_GET("display/window/size/viewport_width"), GLOBAL_GET("display/window/size/viewport_height"));
} else {
parent_rect = get_viewport()->get_visible_rect();
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 0ea8f6c5f1..906b478eb3 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -491,6 +491,17 @@ int CanvasItem::get_z_index() const {
return z_index;
}
+int CanvasItem::get_effective_z_index() const {
+ int effective_z_index = z_index;
+ if (is_z_relative()) {
+ CanvasItem *p = get_parent_item();
+ if (p) {
+ effective_z_index += p->get_effective_z_index();
+ }
+ }
+ return effective_z_index;
+}
+
void CanvasItem::set_y_sort_enabled(bool p_enabled) {
y_sort_enabled = p_enabled;
RS::get_singleton()->canvas_item_set_sort_children_by_y(canvas_item, y_sort_enabled);
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 2fa1d56667..5fbf043159 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -246,6 +246,7 @@ public:
void set_z_index(int p_z);
int get_z_index() const;
+ int get_effective_z_index() const;
void set_z_as_relative(bool p_enabled);
bool is_z_relative() const;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 28521c5bbe..7091dd0388 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -36,6 +36,7 @@
#include "core/object/message_queue.h"
#include "core/string/translation.h"
#include "core/templates/pair.h"
+#include "core/templates/sort_array.h"
#include "scene/2d/audio_listener_2d.h"
#include "scene/2d/camera_2d.h"
#include "scene/2d/collision_object_2d.h"
@@ -669,6 +670,25 @@ void Viewport::_process_picking() {
point_params.pick_point = true;
int rc = ss2d->intersect_point(point_params, res, 64);
+ if (physics_object_picking_sort) {
+ struct ComparatorCollisionObjects {
+ bool operator()(const PhysicsDirectSpaceState2D::ShapeResult &p_a, const PhysicsDirectSpaceState2D::ShapeResult &p_b) const {
+ CollisionObject2D *a = Object::cast_to<CollisionObject2D>(p_a.collider);
+ CollisionObject2D *b = Object::cast_to<CollisionObject2D>(p_b.collider);
+ if (!a || !b) {
+ return false;
+ }
+ int za = a->get_effective_z_index();
+ int zb = b->get_effective_z_index();
+ if (za != zb) {
+ return zb < za;
+ }
+ return a->is_greater_than(b);
+ }
+ };
+ SortArray<PhysicsDirectSpaceState2D::ShapeResult, ComparatorCollisionObjects> sorter;
+ sorter.sort(res, rc);
+ }
for (int i = 0; i < rc; i++) {
if (res[i].collider_id.is_valid() && res[i].collider) {
CollisionObject2D *co = Object::cast_to<CollisionObject2D>(res[i].collider);
@@ -2864,6 +2884,14 @@ bool Viewport::get_physics_object_picking() {
return physics_object_picking;
}
+void Viewport::set_physics_object_picking_sort(bool p_enable) {
+ physics_object_picking_sort = p_enable;
+}
+
+bool Viewport::get_physics_object_picking_sort() {
+ return physics_object_picking_sort;
+}
+
Vector2 Viewport::get_camera_coords(const Vector2 &p_viewport_coords) const {
Transform2D xf = stretch_transform * global_canvas_transform;
return xf.xform(p_viewport_coords);
@@ -3798,6 +3826,8 @@ void Viewport::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_physics_object_picking", "enable"), &Viewport::set_physics_object_picking);
ClassDB::bind_method(D_METHOD("get_physics_object_picking"), &Viewport::get_physics_object_picking);
+ ClassDB::bind_method(D_METHOD("set_physics_object_picking_sort", "enable"), &Viewport::set_physics_object_picking_sort);
+ ClassDB::bind_method(D_METHOD("get_physics_object_picking_sort"), &Viewport::get_physics_object_picking_sort);
ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid);
ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input);
@@ -3949,6 +3979,7 @@ void Viewport::_bind_methods() {
#endif
ADD_GROUP("Physics", "physics_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking"), "set_physics_object_picking", "get_physics_object_picking");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "physics_object_picking_sort"), "set_physics_object_picking_sort", "get_physics_object_picking_sort");
ADD_GROUP("GUI", "gui_");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_disable_input"), "set_disable_input", "is_input_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "gui_snap_controls_to_pixels"), "set_snap_controls_to_pixels", "is_snap_controls_to_pixels_enabled");
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 2142aaaaef..4144eaabb9 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -246,6 +246,7 @@ private:
bool snap_2d_vertices_to_pixel = false;
bool physics_object_picking = false;
+ bool physics_object_picking_sort = false;
List<Ref<InputEvent>> physics_picking_events;
ObjectID physics_object_capture;
ObjectID physics_object_over;
@@ -574,6 +575,8 @@ public:
void set_physics_object_picking(bool p_enable);
bool get_physics_object_picking();
+ void set_physics_object_picking_sort(bool p_enable);
+ bool get_physics_object_picking_sort();
Variant gui_get_drag_data() const;