summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/gdscript/gdscript.cpp1
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp61
-rw-r--r--modules/gdscript/gdscript_analyzer.h11
-rw-r--r--modules/gdscript/gdscript_editor.cpp8
-rw-r--r--modules/gdscript/gdscript_parser.cpp84
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp17
-rw-r--r--modules/gdscript/gdscript_vm.cpp32
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd21
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out6
-rw-r--r--modules/gltf/gltf_document.cpp368
-rw-r--r--modules/gltf/gltf_document.h3
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs10
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs4
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs8
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs16
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs35
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp6
-rw-r--r--modules/multiplayer/multiplayer_spawner.cpp18
-rw-r--r--modules/webp/SCsub1
-rw-r--r--modules/webxr/native/library_godot_webxr.js7
29 files changed, 539 insertions, 222 deletions
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index a876229276..fe79f37454 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -2578,7 +2578,6 @@ GDScriptLanguage::GDScriptLanguage() {
#ifdef DEBUG_ENABLED
GLOBAL_DEF("debug/gdscript/warnings/enable", true);
- GLOBAL_DEF("debug/gdscript/warnings/treat_warnings_as_errors", false);
GLOBAL_DEF("debug/gdscript/warnings/exclude_addons", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
GDScriptWarning::Code code = (GDScriptWarning::Code)i;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 602d07d9a7..de0dacece3 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -42,6 +42,11 @@
#include "gdscript_utility_functions.h"
#include "scene/resources/packed_scene.h"
+#if defined(TOOLS_ENABLED) && !defined(DISABLE_DEPRECATED)
+#define SUGGEST_GODOT4_RENAMES
+#include "editor/renames_map_3_to_4.h"
+#endif
+
#define UNNAMED_ENUM "<anonymous enum>"
#define ENUM_SEPARATOR "::"
@@ -2601,9 +2606,8 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
p_binary_op->set_datatype(result);
}
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
-const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String key) {
+#ifdef SUGGEST_GODOT4_RENAMES
+const char *get_rename_from_map(const char *map[][2], String key) {
for (int index = 0; map[index][0]; index++) {
if (map[index][0] == key) {
return map[index][1];
@@ -2614,39 +2618,39 @@ const char *GDScriptAnalyzer::get_rename_from_map(const char *map[][2], String k
// Checks if an identifier/function name has been renamed in Godot 4, uses ProjectConverter3To4 for rename map.
// Returns the new name if found, nullptr otherwise.
-const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
+const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type) {
switch (type) {
case GDScriptParser::Node::IDENTIFIER: {
// Check properties
- const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_properties_renames, identifier);
+ const char *result = get_rename_from_map(RenamesMap3To4::gdscript_properties_renames, identifier);
if (result) {
return result;
}
// Check enum values
- result = get_rename_from_map(ProjectConverter3To4::enum_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::enum_renames, identifier);
if (result) {
return result;
}
// Check color constants
- result = get_rename_from_map(ProjectConverter3To4::color_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::color_renames, identifier);
if (result) {
return result;
}
// Check type names
- result = get_rename_from_map(ProjectConverter3To4::class_renames, identifier);
+ result = get_rename_from_map(RenamesMap3To4::class_renames, identifier);
if (result) {
return result;
}
- return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ return get_rename_from_map(RenamesMap3To4::builtin_types_renames, identifier);
}
case GDScriptParser::Node::CALL: {
- const char *result = get_rename_from_map(ProjectConverter3To4::gdscript_function_renames, identifier);
+ const char *result = get_rename_from_map(RenamesMap3To4::gdscript_function_renames, identifier);
if (result) {
return result;
}
// Built-in Types are mistaken for function calls when the built-in type is not found.
// Check built-in types if function rename not found
- return get_rename_from_map(ProjectConverter3To4::builtin_types_renames, identifier);
+ return get_rename_from_map(RenamesMap3To4::builtin_types_renames, identifier);
}
// Signal references don't get parsed through the GDScriptAnalyzer. No support for signal rename hints.
default:
@@ -2654,8 +2658,7 @@ const char *GDScriptAnalyzer::check_for_renamed_identifier(String identifier, GD
return nullptr;
}
}
-#endif // DISABLE_DEPRECATED
-#endif // TOOLS_ENABLED
+#endif // SUGGEST_GODOT4_RENAMES
void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_await, bool p_is_root) {
bool all_is_constant = true;
@@ -3078,8 +3081,7 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (!found && (is_self || (base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::BUILTIN))) {
String base_name = is_self && !p_call->is_super ? "self" : base_type.to_string();
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_function_name = check_for_renamed_identifier(p_call->function_name, p_call->type);
@@ -3088,12 +3090,9 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
}
push_error(vformat(R"*(Function "%s()" not found in base %s.%s)*", p_call->function_name, base_name, rename_hint), p_call->is_super ? p_call : p_call->callee);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"*(Function "%s()" not found in base %s.)*", p_call->function_name, base_name), p_call->is_super ? p_call : p_call->callee);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
} else if (!found && (!p_call->is_super && base_type.is_hard_type() && base_type.kind == GDScriptParser::DataType::NATIVE && base_type.is_meta_type)) {
push_error(vformat(R"*(Static function "%s()" not found in base "%s".)*", p_call->function_name, base_type.native_type), p_call);
}
@@ -3283,8 +3282,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
p_identifier->reduced_value = result;
p_identifier->set_datatype(type_from_variant(result, p_identifier));
} else if (base.is_hard_type()) {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3293,12 +3291,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
push_error(vformat(R"(Cannot find constant "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find constant "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
} else {
switch (base.builtin_type) {
@@ -3327,8 +3322,7 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
if (base.is_hard_type()) {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3337,12 +3331,9 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
}
}
push_error(vformat(R"(Cannot find property "%s" on base "%s".%s)", name, base.to_string(), rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Cannot find property "%s" on base "%s".)", name, base.to_string()), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
}
}
@@ -3682,8 +3673,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (GDScriptUtilityFunctions::function_exists(name)) {
push_error(vformat(R"(Built-in function "%s" cannot be used as an identifier.)", name), p_identifier);
} else {
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
+#ifdef SUGGEST_GODOT4_RENAMES
String rename_hint = String();
if (GLOBAL_GET(GDScriptWarning::get_settings_path_from_code(GDScriptWarning::Code::RENAMED_IN_GD4_HINT)).booleanize()) {
const char *renamed_identifier_name = check_for_renamed_identifier(name, p_identifier->type);
@@ -3692,12 +3682,9 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
}
}
push_error(vformat(R"(Identifier "%s" not declared in the current scope.%s)", name, rename_hint), p_identifier);
-#else // !DISABLE_DEPRECATED
- push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
-#endif // DISABLE_DEPRECATED
#else
push_error(vformat(R"(Identifier "%s" not declared in the current scope.)", name), p_identifier);
-#endif
+#endif // SUGGEST_GODOT4_RENAMES
}
GDScriptParser::DataType dummy;
dummy.kind = GDScriptParser::DataType::VARIANT;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index a4c84db6b9..cdeba374c7 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -37,10 +37,6 @@
#include "gdscript_cache.h"
#include "gdscript_parser.h"
-#ifdef TOOLS_ENABLED
-#include "editor/project_converter_3_to_4.h"
-#endif
-
class GDScriptAnalyzer {
GDScriptParser *parser = nullptr;
HashMap<String, Ref<GDScriptParserRef>> depended_parsers;
@@ -137,13 +133,6 @@ class GDScriptAnalyzer {
bool is_shadowing(GDScriptParser::IdentifierNode *p_local, const String &p_context);
#endif
-#ifdef TOOLS_ENABLED
-#ifndef DISABLE_DEPRECATED
- const char *get_rename_from_map(const char *map[][2], String key);
- const char *check_for_renamed_identifier(String identifier, GDScriptParser::Node::Type type);
-#endif // DISABLE_DEPRECATED
-#endif // TOOLS_ENABLED
-
public:
Error resolve_inheritance();
Error resolve_interface();
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index 3543c0a79f..8cfd48b52b 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -3491,14 +3491,6 @@ static Error _lookup_symbol_from_base(const GDScriptParser::DataType &p_base, co
break;
}
- if (context.current_class) {
- if (context.type != GDScriptParser::COMPLETION_SUPER_METHOD) {
- base.type = context.current_class->get_datatype();
- } else {
- base.type = context.current_class->base_type;
- }
- }
-
if (_lookup_symbol_from_base(base.type, p_symbol, is_function, r_result) == OK) {
return OK;
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index c6e4222213..b5cb5a4680 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -175,7 +175,7 @@ void GDScriptParser::push_warning(const Node *p_source, GDScriptWarning::Code p_
warning.leftmost_column = p_source->leftmost_column;
warning.rightmost_column = p_source->rightmost_column;
- if (warn_level == GDScriptWarning::WarnLevel::ERROR || bool(GLOBAL_GET("debug/gdscript/warnings/treat_warnings_as_errors"))) {
+ if (warn_level == GDScriptWarning::WarnLevel::ERROR) {
push_error(warning.get_message() + String(" (Warning treated as error.)"), p_source);
return;
}
@@ -483,24 +483,34 @@ void GDScriptParser::parse_program() {
current_class = head;
bool can_have_class_or_extends = true;
- while (match(GDScriptTokenizer::Token::ANNOTATION)) {
- AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
- if (annotation != nullptr) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- // `@icon` needs to be applied in the parser. See GH-72444.
- if (annotation->name == SNAME("@icon")) {
- annotation->apply(this, head);
+ while (!check(GDScriptTokenizer::Token::TK_EOF)) {
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ // `@icon` needs to be applied in the parser. See GH-72444.
+ if (annotation->name == SNAME("@icon")) {
+ annotation->apply(this, head);
+ } else {
+ head->annotations.push_back(annotation);
+ }
} else {
- head->annotations.push_back(annotation);
+ annotation_stack.push_back(annotation);
+ // This annotation must appear after script-level annotations
+ // and class_name/extends (ex: could be @onready or @export),
+ // so we stop looking for script-level stuff.
+ can_have_class_or_extends = false;
+ break;
}
- } else {
- annotation_stack.push_back(annotation);
- // This annotation must appear after script-level annotations
- // and class_name/extends (ex: could be @onready or @export),
- // so we stop looking for script-level stuff.
- can_have_class_or_extends = false;
- break;
}
+ } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ } else {
+ break;
}
}
@@ -524,6 +534,16 @@ void GDScriptParser::parse_program() {
end_statement("superclass");
}
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false;
@@ -829,6 +849,16 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::DEDENT:
class_end = true;
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// Display a completion with identifiers.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
@@ -1675,6 +1705,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
// Standalone lambdas can't be used, so make this an error.
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
break;
+ case Node::LITERAL:
+ if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) {
+ // Allow strings as multiline comments.
+ break;
+ }
+ [[fallthrough]];
default:
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
@@ -2145,7 +2181,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
GDScriptTokenizer::Token token = current;
- ParseFunction prefix_rule = get_rule(token.type)->prefix;
+ GDScriptTokenizer::Token::Type token_type = token.type;
+ if (token.is_identifier()) {
+ // Allow keywords that can be treated as identifiers.
+ token_type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
+ ParseFunction prefix_rule = get_rule(token_type)->prefix;
if (prefix_rule == nullptr) {
// Expected expression. Let the caller give the proper error message.
@@ -3010,7 +3051,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_NODE_NAME;
} else if (current.is_node_name()) {
advance();
- get_node->full_path += previous.get_identifier();
+ String identifier = previous.get_identifier();
+#ifdef DEBUG_ENABLED
+ // Check spoofing.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) {
+ push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier);
+ }
+#endif
+ get_node->full_path += identifier;
path_state = PATH_STATE_NODE_NAME;
} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index d7f1114fd3..d586380c41 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -168,7 +168,11 @@ bool GDScriptTokenizer::Token::is_identifier() const {
switch (type) {
case IDENTIFIER:
case MATCH: // Used in String.match().
- case CONST_INF: // Used in Vector{2,3,4}.INF
+ // Allow constants to be treated as regular identifiers.
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
return true;
default:
return false;
@@ -188,6 +192,10 @@ bool GDScriptTokenizer::Token::is_node_name() const {
case CLASS_NAME:
case CLASS:
case CONST:
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
case CONTINUE:
case ELIF:
case ELSE:
@@ -530,9 +538,12 @@ void GDScriptTokenizer::make_keyword_list() {
#endif // DEBUG_ENABLED
GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+ bool only_ascii = _peek(-1) < 128;
+
// Consume all identifier characters.
while (is_unicode_identifier_continue(_peek())) {
- _advance();
+ char32_t c = _advance();
+ only_ascii = only_ascii && c < 128;
}
int len = _current - _start;
@@ -587,7 +598,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#ifdef DEBUG_ENABLED
// Additional checks for identifiers but only in debug and if it's available in TextServer.
- if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
+ if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
int64_t confusable = TS->is_confusable(name, keyword_list);
if (confusable >= 0) {
push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp
index b99f5d2685..7a11ea52f0 100644
--- a/modules/gdscript/gdscript_vm.cpp
+++ b/modules/gdscript/gdscript_vm.cpp
@@ -447,6 +447,9 @@ void (*type_init_function_table[])(Variant *) = {
#define OP_GET_BASIS get_basis
#define OP_GET_RID get_rid
+#define METHOD_CALL_ON_NULL_VALUE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a null value."
+#define METHOD_CALL_ON_FREED_INSTANCE_ERROR(method_pointer) "Cannot call method '" + (method_pointer)->get_name() + "' on a previously freed instance."
+
Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_args, int p_argcount, Callable::CallError &r_err, CallState *p_state) {
OPCODES_TABLE;
@@ -1675,10 +1678,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -1839,10 +1842,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false; \
Object *base_obj = base->get_validated_object_with_check(freed); \
if (freed) { \
- err_text = "Trying to call a function on a previously freed instance."; \
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method); \
OPCODE_BREAK; \
} else if (!base_obj) { \
- err_text = "Trying to call a function on a null value."; \
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method); \
OPCODE_BREAK; \
} \
const void **argptrs = call_args_ptr; \
@@ -1941,10 +1944,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -1969,7 +1972,13 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
VariantInternal::initialize(ret, Variant::OBJECT);
Object **ret_opaque = VariantInternal::get_object(ret);
method->ptrcall(base_obj, argptrs, ret_opaque);
- VariantInternal::update_object_id(ret);
+ if (method->is_return_type_raw_object_ptr()) {
+ // The Variant has to participate in the ref count since the method returns a raw Object *.
+ VariantInternal::object_assign(ret, *ret_opaque);
+ } else {
+ // The method, in case it returns something, returns an already encapsulated object.
+ VariantInternal::update_object_id(ret);
+ }
#ifdef DEBUG_ENABLED
if (GDScriptLanguage::get_singleton()->profiling) {
@@ -1996,10 +2005,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
bool freed = false;
Object *base_obj = base->get_validated_object_with_check(freed);
if (freed) {
- err_text = "Trying to call a function on a previously freed instance.";
+ err_text = METHOD_CALL_ON_FREED_INSTANCE_ERROR(method);
OPCODE_BREAK;
} else if (!base_obj) {
- err_text = "Trying to call a function on a null value.";
+ err_text = METHOD_CALL_ON_NULL_VALUE_ERROR(method);
OPCODE_BREAK;
}
#else
@@ -3427,7 +3436,10 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a
String message_str;
if (_code_ptr[ip + 2] != 0) {
GET_VARIANT_PTR(message, 1);
- message_str = *message;
+ Variant message_var = *message;
+ if (message->get_type() != Variant::NIL) {
+ message_str = message_var;
+ }
}
if (message_str.is_empty()) {
err_text = "Assertion failed.";
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index b9e6921034..35fbdca949 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -108,6 +108,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
scr->reload(true);
}
scr->update_exports();
+ ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
}
}
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
new file mode 100644
index 0000000000..390d314b94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
@@ -0,0 +1,3 @@
+func test():
+ var P1 = "ok" # Technically it is visually similar to keyword "PI" but allowed since it's in ASCII range.
+ print(P1)
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
new file mode 100644
index 0000000000..3ecd65ad9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
@@ -0,0 +1,21 @@
+"""
+This is a comment.
+"""
+
+@tool
+
+"""
+This is also a comment.
+"""
+
+extends RefCounted
+
+'''
+This is a comment too.
+'''
+
+func test():
+ """
+ This too is a comment.
+ """
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
new file mode 100644
index 0000000000..7e1982597c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
@@ -0,0 +1,16 @@
+func test():
+ # The following keywords are allowed as identifiers:
+ var match = "match"
+ print(match)
+
+ var PI = "PI"
+ print(PI)
+
+ var INF = "INF"
+ print(INF)
+
+ var NAN = "NAN"
+ print(NAN)
+
+ var TAU = "TAU"
+ print(TAU)
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
new file mode 100644
index 0000000000..aae2ae13d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+match
+PI
+INF
+NAN
+TAU
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
index e2caac8ffd..41b38c4bba 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
@@ -1,5 +1,12 @@
+extends Node
+
func test():
var port = 0 # Only latin characters.
var pοrt = 1 # The "ο" is Greek omicron.
prints(port, pοrt)
+
+# Do not call this since nodes aren't in the tree. It is just a parser check.
+func nodes():
+ var _node1 = $port # Only latin characters.
+ var _node2 = $pοrt # The "ο" is Greek omicron.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
index c483396443..c189204285 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
@@ -1,6 +1,10 @@
GDTEST_OK
>> WARNING
->> Line: 3
+>> Line: 5
+>> CONFUSABLE_IDENTIFIER
+>> The identifier "pοrt" has misleading characters and might be confused with something else.
+>> WARNING
+>> Line: 12
>> CONFUSABLE_IDENTIFIER
>> The identifier "pοrt" has misleading characters and might be confused with something else.
0 1
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
index 18ea260fa2..dc4223ec2d 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
@@ -1,6 +1,5 @@
func test():
# The following statements should all be reported as standalone expressions:
- "This is a standalone expression"
1234
0.0 + 0.0
Color(1, 1, 1)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
index 99ec87438e..a2c67a6e51 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
@@ -8,14 +8,10 @@ GDTEST_OK
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
->> Line: 5
+>> Line: 6
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
>> Line: 7
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
->> WARNING
->> Line: 8
->> STANDALONE_EXPRESSION
->> Standalone expression (the line has no effect).
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index bd3ee1881f..028028a103 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -4894,13 +4894,11 @@ Error GLTFDocument::_serialize_animations(Ref<GLTFState> p_state) {
return OK;
}
for (int32_t player_i = 0; player_i < p_state->animation_players.size(); player_i++) {
- List<StringName> animation_names;
AnimationPlayer *animation_player = p_state->animation_players[player_i];
- animation_player->get_animation_list(&animation_names);
- if (animation_names.size()) {
- for (int animation_name_i = 0; animation_name_i < animation_names.size(); animation_name_i++) {
- _convert_animation(p_state, animation_player, animation_names[animation_name_i]);
- }
+ List<StringName> animations;
+ animation_player->get_animation_list(&animations);
+ for (StringName animation_name : animations) {
+ _convert_animation(p_state, animation_player, animation_name);
}
}
Array animations;
@@ -6028,7 +6026,7 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
Node *root = p_animation_player->get_parent();
ERR_FAIL_COND(root == nullptr);
HashMap<GLTFNodeIndex, Node *>::Iterator node_element = p_state->scene_nodes.find(node_index);
- ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation", node_index));
+ ERR_CONTINUE_MSG(!node_element, vformat("Unable to find node %d for animation.", node_index));
node_path = root->get_path_to(node_element->value);
HashMap<GLTFNodeIndex, ImporterMeshInstance3D *>::Iterator mesh_instance_element = p_state->scene_mesh_instances.find(node_index);
if (mesh_instance_element) {
@@ -6450,58 +6448,179 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
for (int32_t key_i = 0; key_i < key_count; key_i++) {
times.write[key_i] = p_animation->track_get_key_time(p_track_i, key_i);
}
+ double anim_end = p_animation->get_length();
if (track_type == Animation::TYPE_SCALE_3D) {
- p_track.scale_track.times = times;
- p_track.scale_track.interpolation = gltf_interpolation;
- p_track.scale_track.values.resize(key_count);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale;
- Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
- ERR_CONTINUE(err != OK);
- p_track.scale_track.values.write[key_i] = scale;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.scale_track.times.clear();
+ p_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.push_back(scale);
+ p_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.scale_track.times = times;
+ p_track.scale_track.interpolation = gltf_interpolation;
+ p_track.scale_track.values.resize(key_count);
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_get_key(p_track_i, key_i, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.write[key_i] = scale;
+ }
}
} else if (track_type == Animation::TYPE_POSITION_3D) {
- p_track.position_track.times = times;
- p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position;
- Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
- ERR_CONTINUE(err != OK);
- p_track.position_track.values.write[key_i] = position;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.position_track.times.clear();
+ p_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->position_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.push_back(scale);
+ p_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.position_track.times = times;
+ p_track.position_track.values.resize(key_count);
+ p_track.position_track.interpolation = gltf_interpolation;
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position;
+ Error err = p_animation->position_track_get_key(p_track_i, key_i, &position);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.write[key_i] = position;
+ }
}
} else if (track_type == Animation::TYPE_ROTATION_3D) {
- p_track.rotation_track.times = times;
- p_track.rotation_track.interpolation = gltf_interpolation;
- p_track.rotation_track.values.resize(key_count);
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Quaternion rotation;
- Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
- ERR_CONTINUE(err != OK);
- p_track.rotation_track.values.write[key_i] = rotation;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.rotation_track.times.clear();
+ p_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_interpolate(p_track_i, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.push_back(rotation);
+ p_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ p_track.rotation_track.times = times;
+ p_track.rotation_track.values.resize(key_count);
+ p_track.rotation_track.interpolation = gltf_interpolation;
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_get_key(p_track_i, key_i, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.write[key_i] = rotation;
+ }
}
} else if (track_type == Animation::TYPE_VALUE) {
if (path.contains(":position")) {
- p_track.position_track.times = times;
p_track.position_track.interpolation = gltf_interpolation;
-
+ p_track.position_track.times = times;
p_track.position_track.values.resize(key_count);
- p_track.position_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.position_track.values.write[key_i] = position;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.position_track.times.clear();
+ p_track.position_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 position;
+ Error err = p_animation->position_track_interpolate(p_track_i, time, &position);
+ ERR_CONTINUE(err != OK);
+ p_track.position_track.values.push_back(position);
+ p_track.position_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.position_track.values.write[key_i] = position;
+ }
}
} else if (path.contains(":rotation")) {
- p_track.rotation_track.times = times;
p_track.rotation_track.interpolation = gltf_interpolation;
-
+ p_track.rotation_track.times = times;
p_track.rotation_track.values.resize(key_count);
- p_track.rotation_track.interpolation = gltf_interpolation;
-
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.rotation_track.times.clear();
+ p_track.rotation_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Quaternion rotation;
+ Error err = p_animation->rotation_track_interpolate(p_track_i, time, &rotation);
+ ERR_CONTINUE(err != OK);
+ p_track.rotation_track.values.push_back(rotation);
+ p_track.rotation_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
+ }
}
} else if (path.contains(":scale")) {
p_track.scale_track.times = times;
@@ -6510,68 +6629,115 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> p_sta
p_track.scale_track.values.resize(key_count);
p_track.scale_track.interpolation = gltf_interpolation;
- for (int32_t key_i = 0; key_i < key_count; key_i++) {
- Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.scale_track.values.write[key_i] = scale_track;
+ if (gltf_interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE) {
+ gltf_interpolation = GLTFAnimation::INTERP_LINEAR;
+ p_track.scale_track.times.clear();
+ p_track.scale_track.values.clear();
+ // CATMULLROMSPLINE or CUBIC_SPLINE have to be baked, apologies.
+ const double increment = 1.0 / BAKE_FPS;
+ double time = 0.0;
+ bool last = false;
+ while (true) {
+ Vector3 scale;
+ Error err = p_animation->scale_track_interpolate(p_track_i, time, &scale);
+ ERR_CONTINUE(err != OK);
+ p_track.scale_track.values.push_back(scale);
+ p_track.scale_track.times.push_back(time);
+ if (last) {
+ break;
+ }
+ time += increment;
+ if (time >= anim_end) {
+ last = true;
+ time = anim_end;
+ }
+ }
+ } else {
+ for (int32_t key_i = 0; key_i < key_count; key_i++) {
+ Vector3 scale_track = p_animation->track_get_key_value(p_track_i, key_i);
+ p_track.scale_track.values.write[key_i] = scale_track;
+ }
}
}
} else if (track_type == Animation::TYPE_BEZIER) {
- if (path.contains("/scale")) {
- const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
+ const int32_t keys = anim_end * BAKE_FPS;
+ if (path.contains(":scale")) {
if (!p_track.scale_track.times.size()) {
+ p_track.scale_track.interpolation = gltf_interpolation;
Vector<real_t> new_times;
new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
new_times.write[key_i] = key_i / BAKE_FPS;
}
p_track.scale_track.times = new_times;
- p_track.scale_track.interpolation = gltf_interpolation;
p_track.scale_track.values.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
p_track.scale_track.values.write[key_i] = Vector3(1.0f, 1.0f, 1.0f);
}
- p_track.scale_track.interpolation = gltf_interpolation;
- }
- for (int32_t key_i = 0; key_i < keys; key_i++) {
- Vector3 bezier_track = p_track.scale_track.values[key_i];
- if (path.contains("/scale:x")) {
- bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/scale:y")) {
- bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/scale:z")) {
- bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Vector3 bezier_track = p_track.scale_track.values[key_i];
+ if (path.contains(":scale:x")) {
+ bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":scale:y")) {
+ bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":scale:z")) {
+ bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ }
+ p_track.scale_track.values.write[key_i] = bezier_track;
}
- p_track.scale_track.values.write[key_i] = bezier_track;
}
- } else if (path.contains("/position")) {
- const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
+ } else if (path.contains(":position")) {
if (!p_track.position_track.times.size()) {
+ p_track.position_track.interpolation = gltf_interpolation;
Vector<real_t> new_times;
new_times.resize(keys);
for (int32_t key_i = 0; key_i < keys; key_i++) {
new_times.write[key_i] = key_i / BAKE_FPS;
}
p_track.position_track.times = new_times;
- p_track.position_track.interpolation = gltf_interpolation;
p_track.position_track.values.resize(keys);
- p_track.position_track.interpolation = gltf_interpolation;
}
for (int32_t key_i = 0; key_i < keys; key_i++) {
Vector3 bezier_track = p_track.position_track.values[key_i];
- if (path.contains("/position:x")) {
+ if (path.contains(":position:x")) {
bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/position:y")) {
+ } else if (path.contains(":position:y")) {
bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.contains("/position:z")) {
+ } else if (path.contains(":position:z")) {
bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
}
p_track.position_track.values.write[key_i] = bezier_track;
}
+ } else if (path.contains(":rotation")) {
+ if (!p_track.rotation_track.times.size()) {
+ p_track.rotation_track.interpolation = gltf_interpolation;
+ Vector<real_t> new_times;
+ new_times.resize(keys);
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ new_times.write[key_i] = key_i / BAKE_FPS;
+ }
+ p_track.rotation_track.times = new_times;
+
+ p_track.rotation_track.values.resize(keys);
+ }
+ for (int32_t key_i = 0; key_i < keys; key_i++) {
+ Quaternion bezier_track = p_track.rotation_track.values[key_i];
+ if (path.contains(":rotation:x")) {
+ bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:y")) {
+ bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:z")) {
+ bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ } else if (path.contains(":rotation:w")) {
+ bezier_track.w = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
+ }
+ p_track.rotation_track.values.write[key_i] = bezier_track;
+ }
}
}
return p_track;
@@ -6582,16 +6748,18 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
Ref<GLTFAnimation> gltf_animation;
gltf_animation.instantiate();
gltf_animation->set_name(_gen_unique_name(p_state, p_animation_track_name));
-
for (int32_t track_i = 0; track_i < animation->get_track_count(); track_i++) {
if (!animation->track_is_enabled(track_i)) {
continue;
}
- String orig_track_path = animation->track_get_path(track_i);
- if (String(orig_track_path).contains(":position")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":position");
+ String final_track_path = animation->track_get_path(track_i);
+ Node *animation_base_node = p_animation_player->get_parent();
+ ERR_CONTINUE_MSG(!animation_base_node, "Cannot get the parent of the animation player.");
+ if (String(final_track_path).contains(":position")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":position");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a position path.");
for (const KeyValue<GLTFNodeIndex, Node *> &position_scene_node_i : p_state->scene_nodes) {
if (position_scene_node_i.value == node) {
GLTFNodeIndex node_index = position_scene_node_i.key;
@@ -6604,10 +6772,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":rotation_degrees")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees");
+ } else if (String(final_track_path).contains(":rotation_degrees")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":rotation_degrees");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a rotation degrees path.");
for (const KeyValue<GLTFNodeIndex, Node *> &rotation_degree_scene_node_i : p_state->scene_nodes) {
if (rotation_degree_scene_node_i.value == node) {
GLTFNodeIndex node_index = rotation_degree_scene_node_i.key;
@@ -6620,10 +6789,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":scale")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":scale");
+ } else if (String(final_track_path).contains(":scale")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":scale");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a scale path.");
for (const KeyValue<GLTFNodeIndex, Node *> &scale_scene_node_i : p_state->scene_nodes) {
if (scale_scene_node_i.value == node) {
GLTFNodeIndex node_index = scale_scene_node_i.key;
@@ -6636,10 +6806,11 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).contains(":transform")) {
- const Vector<String> node_suffix = String(orig_track_path).split(":transform");
+ } else if (String(final_track_path).contains(":transform")) {
+ const Vector<String> node_suffix = String(final_track_path).split(":transform");
const NodePath path = node_suffix[0];
- const Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ const Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a transform path.");
for (const KeyValue<GLTFNodeIndex, Node *> &transform_track_i : p_state->scene_nodes) {
if (transform_track_i.value == node) {
GLTFAnimation::Track track;
@@ -6647,12 +6818,16 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks().insert(transform_track_i.key, track);
}
}
- } else if (String(orig_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
- const Vector<String> node_suffix = String(orig_track_path).split(":");
+ } else if (String(final_track_path).contains(":") && animation->track_get_type(track_i) == Animation::TYPE_BLEND_SHAPE) {
+ const Vector<String> node_suffix = String(final_track_path).split(":");
const NodePath path = node_suffix[0];
const String suffix = node_suffix[1];
- Node *node = p_animation_player->get_parent()->get_node_or_null(path);
+ Node *node = animation_base_node->get_node_or_null(path);
+ ERR_CONTINUE_MSG(!node, "Cannot get the node from a blend shape path.");
MeshInstance3D *mi = cast_to<MeshInstance3D>(node);
+ if (!mi) {
+ continue;
+ }
Ref<Mesh> mesh = mi->get_mesh();
ERR_CONTINUE(mesh.is_null());
int32_t mesh_index = -1;
@@ -6703,14 +6878,20 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
}
tracks[mesh_index] = track;
}
- } else if (String(orig_track_path).contains(":")) {
+ } else if (String(final_track_path).contains(":")) {
//Process skeleton
- const Vector<String> node_suffix = String(orig_track_path).split(":");
+ const Vector<String> node_suffix = String(final_track_path).split(":");
const String node = node_suffix[0];
const NodePath node_path = node;
const String suffix = node_suffix[1];
- Node *godot_node = p_animation_player->get_parent()->get_node_or_null(node_path);
- Skeleton3D *skeleton = nullptr;
+ Node *godot_node = animation_base_node->get_node_or_null(node_path);
+ if (!godot_node) {
+ continue;
+ }
+ Skeleton3D *skeleton = cast_to<Skeleton3D>(animation_base_node->get_node_or_null(node));
+ if (!skeleton) {
+ continue;
+ }
GLTFSkeletonIndex skeleton_gltf_i = -1;
for (GLTFSkeletonIndex skeleton_i = 0; skeleton_i < p_state->skeletons.size(); skeleton_i++) {
if (p_state->skeletons[skeleton_i]->godot_skeleton == cast_to<Skeleton3D>(godot_node)) {
@@ -6719,7 +6900,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
ERR_CONTINUE(!skeleton);
Ref<GLTFSkeleton> skeleton_gltf = p_state->skeletons[skeleton_gltf_i];
int32_t bone = skeleton->find_bone(suffix);
- ERR_CONTINUE(bone == -1);
+ ERR_CONTINUE_MSG(bone == -1, vformat("Cannot find the bone %s.", suffix));
if (!skeleton_gltf->godot_bone_node.has(bone)) {
continue;
}
@@ -6733,9 +6914,10 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p
gltf_animation->get_tracks()[node_i] = track;
}
}
- } else if (!String(orig_track_path).contains(":")) {
- ERR_CONTINUE(!p_animation_player->get_parent());
- Node *godot_node = p_animation_player->get_parent()->get_node_or_null(orig_track_path);
+ } else if (!String(final_track_path).contains(":")) {
+ ERR_CONTINUE(!animation_base_node);
+ Node *godot_node = animation_base_node->get_node_or_null(final_track_path);
+ ERR_CONTINUE_MSG(!godot_node, vformat("Cannot get the node from a skeleton path %s.", final_track_path));
for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : p_state->scene_nodes) {
if (scene_node_i.value == godot_node) {
GLTFNodeIndex node_i = scene_node_i.key;
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index b8b989bf89..ae19f67390 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -364,8 +364,7 @@ public:
Ref<GLTFNode> p_gltf_node);
GLTFMeshIndex _convert_mesh_to_gltf(Ref<GLTFState> p_state,
MeshInstance3D *p_mesh_instance);
- void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player,
- String p_animation_track_name);
+ void _convert_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, String p_animation_track_name);
Error _serialize(Ref<GLTFState> p_state, const String &p_path);
Error _parse(Ref<GLTFState> p_state, String p_path, Ref<FileAccess> p_file);
};
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
index 1e4fd2f09a..7c02f29606 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildInfo.cs
@@ -12,6 +12,7 @@ namespace GodotTools.Build
public sealed partial class BuildInfo : RefCounted // TODO Remove RefCounted once we have proper serialization
{
public string Solution { get; private set; }
+ public string Project { get; private set; }
public string Configuration { get; private set; }
public string? RuntimeIdentifier { get; private set; }
public string? PublishOutputDir { get; private set; }
@@ -28,6 +29,7 @@ namespace GodotTools.Build
{
return obj is BuildInfo other &&
other.Solution == Solution &&
+ other.Project == Project &&
other.Configuration == Configuration && other.RuntimeIdentifier == RuntimeIdentifier &&
other.PublishOutputDir == PublishOutputDir && other.Restore == Restore &&
other.Rebuild == Rebuild && other.OnlyClean == OnlyClean &&
@@ -41,6 +43,7 @@ namespace GodotTools.Build
{
int hash = 17;
hash = (hash * 29) + Solution.GetHashCode();
+ hash = (hash * 29) + Project.GetHashCode();
hash = (hash * 29) + Configuration.GetHashCode();
hash = (hash * 29) + (RuntimeIdentifier?.GetHashCode() ?? 0);
hash = (hash * 29) + (PublishOutputDir?.GetHashCode() ?? 0);
@@ -57,22 +60,25 @@ namespace GodotTools.Build
private BuildInfo()
{
Solution = string.Empty;
+ Project = string.Empty;
Configuration = string.Empty;
}
- public BuildInfo(string solution, string configuration, bool restore, bool rebuild, bool onlyClean)
+ public BuildInfo(string solution, string project, string configuration, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
+ Project = project;
Configuration = configuration;
Restore = restore;
Rebuild = rebuild;
OnlyClean = onlyClean;
}
- public BuildInfo(string solution, string configuration, string runtimeIdentifier,
+ public BuildInfo(string solution, string project, string configuration, string runtimeIdentifier,
string publishOutputDir, bool restore, bool rebuild, bool onlyClean)
{
Solution = solution;
+ Project = project;
Configuration = configuration;
RuntimeIdentifier = runtimeIdentifier;
PublishOutputDir = publishOutputDir;
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
index 349f9d0cb8..ed3a4c6e26 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildManager.cs
@@ -262,7 +262,7 @@ namespace GodotTools.Build
bool onlyClean = false
)
{
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, GodotSharpDirs.ProjectCsProjPath, configuration,
restore: true, rebuild, onlyClean);
// If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
@@ -282,7 +282,7 @@ namespace GodotTools.Build
[DisallowNull] string publishOutputDir
)
{
- var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, configuration,
+ var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, GodotSharpDirs.ProjectCsProjPath, configuration,
runtimeIdentifier, publishOutputDir, restore: true, rebuild: false, onlyClean: false);
buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
diff --git a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
index d6549c1b70..d550c36b82 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Build/BuildSystem.cs
@@ -139,8 +139,8 @@ namespace GodotTools.Build
// `dotnet clean` / `dotnet build` commands
arguments.Add(buildInfo.OnlyClean ? "clean" : "build");
- // Solution
- arguments.Add(buildInfo.Solution);
+ // C# Project
+ arguments.Add(buildInfo.Project);
// `dotnet clean` doesn't recognize these options
if (!buildInfo.OnlyClean)
@@ -180,8 +180,8 @@ namespace GodotTools.Build
{
arguments.Add("publish"); // `dotnet publish` command
- // Solution
- arguments.Add(buildInfo.Solution);
+ // C# Project
+ arguments.Add(buildInfo.Project);
// Restore
// `dotnet publish` restores by default, unless requested not to
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 70b48b0e3a..a284451a35 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -24,19 +24,7 @@ namespace GodotTools.Export
public void RegisterExportSettings()
{
// TODO: These would be better as export preset options, but that doesn't seem to be supported yet
-
- GlobalDef("mono/export/include_scripts_content", false);
-
- GlobalDef("mono/export/aot/enabled", false);
- GlobalDef("mono/export/aot/full_aot", false);
- GlobalDef("mono/export/aot/use_interpreter", true);
-
- // --aot or --aot=opt1,opt2 (use 'mono --aot=help AuxAssembly.dll' to list AOT options)
- GlobalDef("mono/export/aot/extra_aot_options", Array.Empty<string>());
- // --optimize/-O=opt1,opt2 (use 'mono --list-opt'' to list optimize options)
- GlobalDef("mono/export/aot/extra_optimizer_options", Array.Empty<string>());
-
- GlobalDef("mono/export/aot/android_toolchain_path", "");
+ GlobalDef("dotnet/export/include_scripts_content", false);
}
private string _maybeLastExportError;
@@ -56,7 +44,7 @@ namespace GodotTools.Export
// TODO What if the source file is not part of the game's C# project
- bool includeScriptsContent = (bool)ProjectSettings.GetSetting("mono/export/include_scripts_content");
+ bool includeScriptsContent = (bool)ProjectSettings.GetSetting("dotnet/export/include_scripts_content");
if (!includeScriptsContent)
{
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
index 027eab30fc..79030c79cc 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GodotSynchronizationContext.cs
@@ -1,17 +1,44 @@
using System;
using System.Collections.Concurrent;
-using System.Collections.Generic;
using System.Threading;
+using System.Threading.Tasks;
namespace Godot
{
public sealed class GodotSynchronizationContext : SynchronizationContext, IDisposable
{
- private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue = new();
+ private readonly BlockingCollection<(SendOrPostCallback Callback, object State)> _queue = new();
+
+ public override void Send(SendOrPostCallback d, object state)
+ {
+ // Shortcut if we're already on this context
+ // Also necessary to avoid a deadlock, since Send is blocking
+ if (Current == this)
+ {
+ d(state);
+ return;
+ }
+
+ var source = new TaskCompletionSource();
+
+ _queue.Add((st =>
+ {
+ try
+ {
+ d(st);
+ }
+ finally
+ {
+ source.SetResult();
+ }
+ }, state));
+
+ source.Task.Wait();
+ }
public override void Post(SendOrPostCallback d, object state)
{
- _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
+ _queue.Add((d, state));
}
/// <summary>
@@ -21,7 +48,7 @@ namespace Godot
{
while (_queue.TryTake(out var workItem))
{
- workItem.Key(workItem.Value);
+ workItem.Callback(workItem.State);
}
}
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index e320657ab5..f8e75d5ef5 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -253,7 +253,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_spacer();
Label *lb = memnew(Label);
- lb->set_text(TTR("Down"));
+ // TRANSLATORS: This is the label for the network profiler's incoming bandwidth.
+ lb->set_text(TTR("Down", "Network"));
hb->add_child(lb);
incoming_bandwidth_text = memnew(LineEdit);
@@ -267,7 +268,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_child(down_up_spacer);
lb = memnew(Label);
- lb->set_text(TTR("Up"));
+ // TRANSLATORS: This is the label for the network profiler's outgoing bandwidth.
+ lb->set_text(TTR("Up", "Network"));
hb->add_child(lb);
outgoing_bandwidth_text = memnew(LineEdit);
diff --git a/modules/multiplayer/multiplayer_spawner.cpp b/modules/multiplayer/multiplayer_spawner.cpp
index 0aa54b69f9..4b1b6b541d 100644
--- a/modules/multiplayer/multiplayer_spawner.cpp
+++ b/modules/multiplayer/multiplayer_spawner.cpp
@@ -103,6 +103,15 @@ void MultiplayerSpawner::add_spawnable_scene(const String &p_path) {
ERR_FAIL_COND(!FileAccess::exists(p_path));
}
spawnable_scenes.push_back(sc);
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = get_spawn_node();
+ if (spawnable_scenes.size() == 1 && node && !node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
+ node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
}
int MultiplayerSpawner::get_spawnable_scene_count() const {
@@ -116,6 +125,15 @@ String MultiplayerSpawner::get_spawnable_scene(int p_idx) const {
void MultiplayerSpawner::clear_spawnable_scenes() {
spawnable_scenes.clear();
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ Node *node = get_spawn_node();
+ if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
+ node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
}
Vector<String> MultiplayerSpawner::_get_spawnable_scenes() const {
diff --git a/modules/webp/SCsub b/modules/webp/SCsub
index 72ad1ea5e4..e78236a60b 100644
--- a/modules/webp/SCsub
+++ b/modules/webp/SCsub
@@ -13,6 +13,7 @@ if env["builtin_libwebp"]:
thirdparty_dir = "#thirdparty/libwebp/"
thirdparty_sources = [
"sharpyuv/sharpyuv.c",
+ "sharpyuv/sharpyuv_cpu.c",
"sharpyuv/sharpyuv_csp.c",
"sharpyuv/sharpyuv_dsp.c",
"sharpyuv/sharpyuv_gamma.c",
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 1c00ebebb4..5c01d88a30 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -584,12 +584,11 @@ const GodotWebXR = {
}
const buf = GodotRuntime.malloc(point_count * 3 * 4);
- GodotRuntime.setHeapValue(buf, point_count, 'i32');
for (let i = 0; i < point_count; i++) {
const point = GodotWebXR.space.boundsGeometry[i];
- GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.x, 'float');
- GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.y, 'float');
- GodotRuntime.setHeapValue(buf + ((i * 3) + 3) * 4, point.z, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 0) * 4, point.x, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 1) * 4, point.y, 'float');
+ GodotRuntime.setHeapValue(buf + ((i * 3) + 2) * 4, point.z, 'float');
}
GodotRuntime.setHeapValue(r_points, buf, 'i32');