diff options
Diffstat (limited to 'modules')
16 files changed, 144 insertions, 72 deletions
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. |