فهرست منبع

Merge pull request #74884 from YuriSizov/4.0-cherrypicks

Cherry-picks for the 4.0 branch (future 4.0.1) - 2nd batch
Yuri Sizov 2 سال پیش
والد
کامیت
258ea41ffa
39فایلهای تغییر یافته به همراه562 افزوده شده و 226 حذف شده
  1. 1 1
      SConstruct
  2. 18 2
      drivers/gles3/storage/material_storage.cpp
  3. 4 0
      editor/plugins/theme_editor_plugin.cpp
  4. 303 146
      editor/project_converter_3_to_4.cpp
  5. 22 11
      editor/project_converter_3_to_4.h
  6. 5 1
      editor/property_selector.cpp
  7. 9 8
      editor/renames_map_3_to_4.cpp
  8. 7 5
      modules/gdscript/gdscript_compiler.cpp
  9. 1 1
      modules/gdscript/gdscript_compiler.h
  10. 3 2
      modules/gltf/editor/editor_scene_importer_blend.cpp
  11. 14 0
      modules/gltf/gltf_document.cpp
  12. 5 0
      modules/mono/csharp_script.cpp
  13. 26 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs
  14. 1 1
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs
  15. 6 0
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs
  16. 1 1
      modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs
  17. 3 3
      modules/mono/editor/bindings_generator.cpp
  18. 1 1
      modules/navigation/godot_navigation_server.cpp
  19. 18 5
      modules/text_server_adv/text_server_adv.cpp
  20. 3 3
      modules/text_server_fb/text_server_fb.cpp
  21. 1 2
      platform/linuxbsd/detect.py
  22. 7 0
      platform/web/dom_keys.inc
  23. 11 8
      platform/windows/display_server_windows.cpp
  24. 8 0
      platform/windows/export/export_plugin.cpp
  25. 6 2
      platform/windows/os_windows.cpp
  26. 1 0
      scene/2d/back_buffer_copy.cpp
  27. 7 7
      scene/2d/camera_2d.cpp
  28. 1 1
      scene/2d/camera_2d.h
  29. 5 0
      scene/animation/tween.cpp
  30. 2 0
      scene/animation/tween.h
  31. 6 0
      scene/gui/code_edit.cpp
  32. 1 1
      scene/gui/text_edit.cpp
  33. 6 0
      scene/resources/font.cpp
  34. 6 0
      scene/resources/texture.cpp
  35. 18 11
      servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
  36. 9 3
      servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp
  37. 7 0
      servers/rendering/renderer_rd/storage_rd/texture_storage.cpp
  38. 1 0
      servers/rendering/renderer_rd/storage_rd/texture_storage.h
  39. 8 0
      servers/rendering/shader_language.cpp

+ 1 - 1
SConstruct

@@ -645,7 +645,7 @@ if selected_platform in platform_list:
             print(
                 "Detected mingw version is not using posix threads. Only posix "
                 "version of mingw is supported. "
-                'Use "update-alternatives --config <platform>-w64-mingw32-[gcc|g++]" '
+                'Use "update-alternatives --config x86_64-w64-mingw32-g++" '
                 "to switch to posix threads."
             )
             Exit(255)

+ 18 - 2
drivers/gles3/storage/material_storage.cpp

@@ -1066,6 +1066,12 @@ static const RS::CanvasItemTextureRepeat repeat_from_uniform[ShaderLanguage::REP
 	RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED, // ShaderLanguage::TextureRepeat::REPEAT_DEFAULT,
 };
 
+static const RS::CanvasItemTextureRepeat repeat_from_uniform_canvas[ShaderLanguage::REPEAT_DEFAULT + 1] = {
+	RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED, // ShaderLanguage::TextureRepeat::REPEAT_DISABLE,
+	RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED, // ShaderLanguage::TextureRepeat::REPEAT_ENABLE,
+	RS::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_DISABLED, // ShaderLanguage::TextureRepeat::REPEAT_DEFAULT,
+};
+
 static const RS::CanvasItemTextureFilter filter_from_uniform[ShaderLanguage::FILTER_DEFAULT + 1] = {
 	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, // ShaderLanguage::TextureFilter::FILTER_NEAREST,
 	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, // ShaderLanguage::TextureFilter::FILTER_LINEAR,
@@ -1076,6 +1082,16 @@ static const RS::CanvasItemTextureFilter filter_from_uniform[ShaderLanguage::FIL
 	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, // ShaderLanguage::TextureFilter::FILTER_DEFAULT,
 };
 
+static const RS::CanvasItemTextureFilter filter_from_uniform_canvas[ShaderLanguage::FILTER_DEFAULT + 1] = {
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_NEAREST, // ShaderLanguage::TextureFilter::FILTER_NEAREST,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, // ShaderLanguage::TextureFilter::FILTER_LINEAR,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS, // ShaderLanguage::TextureFilter::FILTER_NEAREST_MIPMAP,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, // ShaderLanguage::TextureFilter::FILTER_LINEAR_MIPMAP,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_NEAREST_WITH_MIPMAPS_ANISOTROPIC, // ShaderLanguage::TextureFilter::FILTER_NEAREST_MIPMAP_ANISOTROPIC,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC, // ShaderLanguage::TextureFilter::FILTER_LINEAR_MIPMAP_ANISOTROPIC,
+	RS::CanvasItemTextureFilter::CANVAS_ITEM_TEXTURE_FILTER_LINEAR, // ShaderLanguage::TextureFilter::FILTER_DEFAULT,
+};
+
 void MaterialData::update_uniform_buffer(const HashMap<StringName, ShaderLanguage::ShaderNode::Uniform> &p_uniforms, const uint32_t *p_uniform_offsets, const HashMap<StringName, Variant> &p_parameters, uint8_t *p_buffer, uint32_t p_buffer_size, bool p_use_linear_color) {
 	MaterialStorage *material_storage = MaterialStorage::get_singleton();
 	bool uses_global_buffer = false;
@@ -3061,8 +3077,8 @@ void CanvasMaterialData::bind_uniforms() {
 			texture->render_target->used_in_frame = true;
 		}
 
-		texture->gl_set_filter(filter_from_uniform[int(texture_uniforms[ti].filter)]);
-		texture->gl_set_repeat(repeat_from_uniform[int(texture_uniforms[ti].repeat)]);
+		texture->gl_set_filter(filter_from_uniform_canvas[int(texture_uniforms[ti].filter)]);
+		texture->gl_set_repeat(repeat_from_uniform_canvas[int(texture_uniforms[ti].repeat)]);
 	}
 }
 

+ 4 - 0
editor/plugins/theme_editor_plugin.cpp

@@ -2952,6 +2952,10 @@ void ThemeTypeEditor::_item_remove_cbk(int p_data_type, String p_item_name) {
 				ur->add_undo_method(*edited_theme, "set_font", p_item_name, edited_type, Ref<Font>());
 			}
 		} break;
+		case Theme::DATA_TYPE_FONT_SIZE: {
+			ur->add_do_method(*edited_theme, "clear_font_size", p_item_name, edited_type);
+			ur->add_undo_method(*edited_theme, "set_font_size", p_item_name, edited_type, edited_theme->get_font_size(p_item_name, edited_type));
+		} break;
 		case Theme::DATA_TYPE_ICON: {
 			ur->add_do_method(*edited_theme, "clear_icon", p_item_name, edited_type);
 			if (edited_theme->has_icon(p_item_name, edited_type)) {

+ 303 - 146
editor/project_converter_3_to_4.cpp

@@ -122,6 +122,9 @@ public:
 	RegEx keyword_gdscript_master = RegEx("^master func");
 	RegEx keyword_gdscript_mastersync = RegEx("^mastersync func");
 
+	RegEx gdscript_comment = RegEx("^\\s*#");
+	RegEx csharp_comment = RegEx("^\\s*\\/\\/");
+
 	// CSharp keywords.
 	RegEx keyword_csharp_remote = RegEx("\\[Remote(Attribute)?(\\(\\))?\\]");
 	RegEx keyword_csharp_remotesync = RegEx("\\[(Remote)?Sync(Attribute)?(\\(\\))?\\]");
@@ -139,6 +142,9 @@ public:
 	LocalVector<RegEx *> class_gd_regexes;
 	LocalVector<RegEx *> class_shader_regexes;
 
+	// Keycode.
+	RegEx input_map_keycode = RegEx("\\b,\"((physical_)?)scancode\":(\\d+)\\b");
+
 	LocalVector<RegEx *> class_regexes;
 
 	RegEx class_temp_tscn = RegEx("\\bTEMP_RENAMED_CLASS.tscn\\b");
@@ -304,7 +310,7 @@ bool ProjectConverter3To4::convert() {
 	maximum_line_length = 10000; // Use only for tests bigger value, to not break them.
 
 	ERR_FAIL_COND_V_MSG(!test_array_names(), false, "Cannot start converting due to problems with data in arrays.");
-	ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Cannot start converting due to problems with converting arrays.");
+	ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Aborting conversion due to validation tests failing");
 
 	maximum_line_length = cached_maximum_line_length;
 
@@ -334,17 +340,21 @@ bool ProjectConverter3To4::convert() {
 	// Check file by file.
 	for (int i = 0; i < collected_files.size(); i++) {
 		String file_name = collected_files[i];
-		Vector<String> lines;
+		Vector<SourceLine> source_lines;
 		uint32_t ignored_lines = 0;
 		{
 			Ref<FileAccess> file = FileAccess::open(file_name, FileAccess::READ);
 			ERR_CONTINUE_MSG(file.is_null(), vformat("Unable to read content of \"%s\".", file_name));
 			while (!file->eof_reached()) {
 				String line = file->get_line();
-				lines.append(line);
+
+				SourceLine source_line;
+				source_line.line = line;
+				source_line.is_comment = reg_container.gdscript_comment.search_all(line).size() > 0 || reg_container.csharp_comment.search_all(line).size() > 0;
+				source_lines.append(source_line);
 			}
 		}
-		String file_content_before = collect_string_from_vector(lines);
+		String file_content_before = collect_string_from_vector(source_lines);
 		uint64_t hash_before = file_content_before.hash();
 		uint64_t file_size = file_content_before.size();
 		print_line(vformat("Trying to convert\t%d/%d file - \"%s\" with size - %d KB", i + 1, collected_files.size(), file_name.trim_prefix("res://"), file_size / 1024));
@@ -361,67 +371,71 @@ bool ProjectConverter3To4::convert() {
 		if (file_size < uint64_t(maximum_file_size)) {
 			// ".tscn" must work exactly the same as ".gd" files because they may contain built-in Scripts.
 			if (file_name.ends_with(".gd")) {
-				rename_classes(lines, reg_container); // Using only specialized function.
+				fix_tool_declaration(source_lines, reg_container);
+
+				rename_classes(source_lines, reg_container); // Using only specialized function.
 
-				rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, lines);
-				rename_colors(lines, reg_container); // Require to additional rename.
+				rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, source_lines);
+				rename_colors(source_lines, reg_container); // Require to additional rename.
 
-				rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, lines);
-				rename_gdscript_functions(lines, reg_container, false); // Require to additional rename.
+				rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, source_lines);
+				rename_gdscript_functions(source_lines, reg_container, false); // Require to additional rename.
 
-				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines);
-				rename_gdscript_keywords(lines, reg_container);
-				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines);
-				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines);
-				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines);
-				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines);
+				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
+				rename_gdscript_keywords(source_lines, reg_container);
+				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
+				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
+				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
+				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
 
-				custom_rename(lines, "\\.shader", ".gdshader");
+				custom_rename(source_lines, "\\.shader", ".gdshader");
 			} else if (file_name.ends_with(".tscn")) {
-				rename_classes(lines, reg_container); // Using only specialized function.
+				rename_classes(source_lines, reg_container); // Using only specialized function.
 
-				rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, lines);
-				rename_colors(lines, reg_container); // Require to do additional renames.
+				rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, source_lines);
+				rename_colors(source_lines, reg_container); // Require to do additional renames.
 
-				rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, lines);
-				rename_gdscript_functions(lines, reg_container, true); // Require to do additional renames.
+				rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, source_lines);
+				rename_gdscript_functions(source_lines, reg_container, true); // Require to do additional renames.
 
-				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines);
-				rename_gdscript_keywords(lines, reg_container);
-				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines);
-				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines);
-				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines);
-				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines);
+				rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
+				rename_gdscript_keywords(source_lines, reg_container);
+				rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
+				rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
+				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
+				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
 
-				custom_rename(lines, "\\.shader", ".gdshader");
+				custom_rename(source_lines, "\\.shader", ".gdshader");
 			} else if (file_name.ends_with(".cs")) { // TODO, C# should use different methods.
-				rename_classes(lines, reg_container); // Using only specialized function.
-				rename_common(RenamesMap3To4::csharp_function_renames, reg_container.csharp_function_regexes, lines);
-				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines);
-				rename_common(RenamesMap3To4::csharp_properties_renames, reg_container.csharp_properties_regexes, lines);
-				rename_common(RenamesMap3To4::csharp_signals_renames, reg_container.csharp_signal_regexes, lines);
-				rename_csharp_functions(lines, reg_container);
-				rename_csharp_attributes(lines, reg_container);
-				custom_rename(lines, "public class ", "public partial class ");
+				rename_classes(source_lines, reg_container); // Using only specialized function.
+				rename_common(RenamesMap3To4::csharp_function_renames, reg_container.csharp_function_regexes, source_lines);
+				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
+				rename_common(RenamesMap3To4::csharp_properties_renames, reg_container.csharp_properties_regexes, source_lines);
+				rename_common(RenamesMap3To4::csharp_signals_renames, reg_container.csharp_signal_regexes, source_lines);
+				rename_csharp_functions(source_lines, reg_container);
+				rename_csharp_attributes(source_lines, reg_container);
+				custom_rename(source_lines, "public class ", "public partial class ");
 			} else if (file_name.ends_with(".gdshader") || file_name.ends_with(".shader")) {
-				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines);
+				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
 			} else if (file_name.ends_with("tres")) {
-				rename_classes(lines, reg_container); // Using only specialized function.
+				rename_classes(source_lines, reg_container); // Using only specialized function.
 
-				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines);
-				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines);
+				rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
+				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
 
-				custom_rename(lines, "\\.shader", ".gdshader");
+				custom_rename(source_lines, "\\.shader", ".gdshader");
 			} else if (file_name.ends_with("project.godot")) {
-				rename_common(RenamesMap3To4::project_godot_renames, reg_container.project_godot_regexes, lines);
-				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines);
-				rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, lines);
+				rename_common(RenamesMap3To4::project_godot_renames, reg_container.project_godot_regexes, source_lines);
+				rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
+				rename_input_map_scancode(source_lines, reg_container);
+				rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, source_lines);
 			} else if (file_name.ends_with(".csproj")) {
 				// TODO
 			} else if (file_name.ends_with(".import")) {
-				for (int x = 0; x < lines.size(); x++) {
-					if (lines[x].contains("nodes/root_type=\"Spatial\"")) {
-						lines.set(x, "nodes/root_type=\"Node3D\"");
+				for (SourceLine &source_line : source_lines) {
+					String &line = source_line.line;
+					if (line.contains("nodes/root_type=\"Spatial\"")) {
+						line = "nodes/root_type=\"Node3D\"";
 					}
 				}
 			} else {
@@ -429,7 +443,12 @@ bool ProjectConverter3To4::convert() {
 				continue;
 			}
 
-			for (String &line : lines) {
+			for (SourceLine &source_line : source_lines) {
+				if (source_line.is_comment) {
+					continue;
+				}
+
+				String &line = source_line.line;
 				if (uint64_t(line.length()) > maximum_line_length) {
 					ignored_lines += 1;
 				}
@@ -444,7 +463,7 @@ bool ProjectConverter3To4::convert() {
 			String end_message = vformat("    Checking file took %d ms.", end_time - start_time);
 			print_line(end_message);
 		} else {
-			String file_content_after = collect_string_from_vector(lines);
+			String file_content_after = collect_string_from_vector(source_lines);
 			uint64_t hash_after = file_content_after.hash64();
 			// Don't need to save file without any changes.
 			// Save if this is a shader, because it was renamed.
@@ -483,7 +502,7 @@ bool ProjectConverter3To4::validate_conversion() {
 	maximum_line_length = 10000; // To avoid breaking the tests, only use this for the their larger value.
 
 	ERR_FAIL_COND_V_MSG(!test_array_names(), false, "Cannot start converting due to problems with data in arrays.");
-	ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Cannot start converting due to problems with converting arrays.");
+	ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Aborting conversion due to validation tests failing");
 
 	maximum_line_length = cached_maximum_line_length;
 
@@ -492,7 +511,7 @@ bool ProjectConverter3To4::validate_conversion() {
 	{
 		String conventer_text = "; Project was converted by built-in tool to Godot 4.0";
 
-		ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current directory doesn't contains any Godot 3 project");
+		ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current directory doesn't contain any Godot 3 project");
 
 		Error err = OK;
 		String project_godot_content = FileAccess::get_file_as_string("project.godot", &err);
@@ -587,6 +606,7 @@ bool ProjectConverter3To4::validate_conversion() {
 			} else if (file_name.ends_with("project.godot")) {
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_godot_renames, reg_container.project_godot_regexes, lines));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
+				changed_elements.append_array(check_for_rename_input_map_scancode(lines, reg_container));
 				changed_elements.append_array(check_for_rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, lines));
 			} else if (file_name.ends_with(".csproj")) {
 				// TODO
@@ -674,9 +694,23 @@ Vector<String> ProjectConverter3To4::check_for_files() {
 	return collected_files;
 }
 
+Vector<SourceLine> ProjectConverter3To4::split_lines(const String &text) {
+	Vector<String> lines = text.split("\n");
+	Vector<SourceLine> source_lines;
+	for (String &line : lines) {
+		SourceLine source_line;
+		source_line.line = line;
+		source_line.is_comment = false;
+
+		source_lines.append(source_line);
+	}
+	return source_lines;
+}
+
 // Test expected results of gdscript
-bool ProjectConverter3To4::test_conversion_gdscript_builtin(String name, String expected, void (ProjectConverter3To4::*func)(Vector<String> &, const RegExContainer &, bool), String what, const RegExContainer &reg_container, bool builtin_script) {
-	Vector<String> got = name.split("\n");
+bool ProjectConverter3To4::test_conversion_gdscript_builtin(String name, String expected, void (ProjectConverter3To4::*func)(Vector<SourceLine> &, const RegExContainer &, bool), String what, const RegExContainer &reg_container, bool builtin_script) {
+	Vector<SourceLine> got = split_lines(name);
+
 	(this->*func)(got, reg_container, builtin_script);
 	String got_str = collect_string_from_vector(got);
 	ERR_FAIL_COND_V_MSG(expected != got_str, false, vformat("Failed to convert %s \"%s\" to \"%s\", got instead \"%s\"", what, name, expected, got_str));
@@ -684,8 +718,9 @@ bool ProjectConverter3To4::test_conversion_gdscript_builtin(String name, String
 	return true;
 }
 
-bool ProjectConverter3To4::test_conversion_with_regex(String name, String expected, void (ProjectConverter3To4::*func)(Vector<String> &, const RegExContainer &), String what, const RegExContainer &reg_container) {
-	Vector<String> got = name.split("\n");
+bool ProjectConverter3To4::test_conversion_with_regex(String name, String expected, void (ProjectConverter3To4::*func)(Vector<SourceLine> &, const RegExContainer &), String what, const RegExContainer &reg_container) {
+	Vector<SourceLine> got = split_lines(name);
+
 	(this->*func)(got, reg_container);
 	String got_str = collect_string_from_vector(got);
 	ERR_FAIL_COND_V_MSG(expected != got_str, false, vformat("Failed to convert %s \"%s\" to \"%s\", got instead \"%s\"", what, name, expected, got_str));
@@ -694,7 +729,8 @@ bool ProjectConverter3To4::test_conversion_with_regex(String name, String expect
 }
 
 bool ProjectConverter3To4::test_conversion_basic(String name, String expected, const char *array[][2], LocalVector<RegEx *> &regex_cache, String what) {
-	Vector<String> got = name.split("\n");
+	Vector<SourceLine> got = split_lines(name);
+
 	rename_common(array, regex_cache, got);
 	String got_str = collect_string_from_vector(got);
 	ERR_FAIL_COND_V_MSG(expected != got_str, false, vformat("Failed to convert %s \"%s\" to \"%s\", got instead \"%s\"", what, name, expected, got_str));
@@ -706,6 +742,10 @@ bool ProjectConverter3To4::test_conversion_basic(String name, String expected, c
 bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	bool valid = true;
 
+	valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n    tool", "\n    tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+	valid = valid && test_conversion_with_regex("\n\ntool", "@tool\n\n", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
+
 	valid = valid && test_conversion_basic("TYPE_REAL", "TYPE_FLOAT", RenamesMap3To4::enum_renames, reg_container.enum_regexes, "enum");
 
 	valid = valid && test_conversion_basic("can_instance", "can_instantiate", RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, "gdscript function");
@@ -730,9 +770,9 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 
 	// Custom Renames.
 
-	valid = valid && test_conversion_with_regex("(Connect(A,B,C,D,E,F,G) != OK):", "(Connect(A,new Callable(B,C),D,E,F,G) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename csharp", reg_container);
-	valid = valid && test_conversion_with_regex("(Disconnect(A,B,C) != OK):", "(Disconnect(A,new Callable(B,C)) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename csharp", reg_container);
-	valid = valid && test_conversion_with_regex("(IsConnected(A,B,C) != OK):", "(IsConnected(A,new Callable(B,C)) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename", reg_container);
+	valid = valid && test_conversion_with_regex("(Connect(A,B,C,D,E,F,G) != OK):", "(Connect(A, new Callable(B, C), D, E, F, G) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename csharp", reg_container);
+	valid = valid && test_conversion_with_regex("(Disconnect(A,B,C) != OK):", "(Disconnect(A, new Callable(B, C)) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename csharp", reg_container);
+	valid = valid && test_conversion_with_regex("(IsConnected(A,B,C) != OK):", "(IsConnected(A, new Callable(B, C)) != OK):", &ProjectConverter3To4::rename_csharp_functions, "custom rename", reg_container);
 
 	valid = valid && test_conversion_with_regex("[Remote]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
 	valid = valid && test_conversion_with_regex("[RemoteSync]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
@@ -788,11 +828,11 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a )", "list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("list_dir_begin( )", "list_dir_begin() # TODOGODOT4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("sort_custom( a , b )", "sort_custom(Callable(a,b))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("sort_custom( a , b )", "sort_custom(Callable(a, b))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("func c(var a, var b)", "func c(a, b)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("draw_line(1, 2, 3, 4, 5)", "draw_line(1,2,3,4)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("draw_line(1, 2, 3, 4, 5)", "draw_line(1, 2, 3, 4)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("\timage.lock()", "\tfalse # image.lock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("\timage.unlock()", "\tfalse # image.unlock() # TODOConverter40, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -816,9 +856,6 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_with_regex("\texport_dialog", "\texport_dialog", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("export", "@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex(" export", " export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n    tool", "\n    tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
-	valid = valid && test_conversion_with_regex("\n\ntool", "\n\n@tool", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
 	valid = valid && test_conversion_with_regex("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container);
@@ -838,8 +875,8 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_gdscript_builtin("yield(this, \"timeout\")", "await this.timeout", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("yield(this, \\\"timeout\\\")", "await this.timeout", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, true);
 
-	valid = valid && test_conversion_gdscript_builtin(" Transform.xform(Vector3(a,b,c)) ", " Transform * Vector3(a,b,c) ", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin(" Transform.xform_inv(Vector3(a,b,c)) ", " Vector3(a,b,c) * Transform ", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin(" Transform.xform(Vector3(a,b,c) + Vector3.UP) ", " Transform * (Vector3(a,b,c) + Vector3.UP) ", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin(" Transform.xform_inv(Vector3(a,b,c) + Vector3.UP) ", " (Vector3(a,b,c) + Vector3.UP) * Transform ", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("export(float) var lifetime = 3.0", "export var lifetime: float = 3.0", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("export(String, 'AnonymousPro', 'CourierPrime') var _font_name = 'AnonymousPro'", "export var _font_name = 'AnonymousPro' # (String, 'AnonymousPro', 'CourierPrime')", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false); // TODO, this is only a workaround
@@ -864,27 +901,30 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_gdscript_builtin("'.a'", "'.a'", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("\t._input(_event)", "\tsuper._input(_event)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C) != OK):", "(connect(A,Callable(B,C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,D) != OK):", "(connect(A,Callable(B,C).bind(D)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D]) != OK):", "(connect(A,Callable(B,C).bind(D)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D,E]) != OK):", "(connect(A,Callable(B,C).bind(D,E)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D,E],F) != OK):", "(connect(A,Callable(B,C).bind(D,E),F) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,D,E) != OK):", "(connect(A,Callable(B,C).bind(D),E) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C) != OK):", "(connect(A, Callable(B, C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,D) != OK):", "(connect(A, Callable(B, C).bind(D)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D]) != OK):", "(connect(A, Callable(B, C).bind(D)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D,E]) != OK):", "(connect(A, Callable(B, C).bind(D,E)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,[D,E],F) != OK):", "(connect(A, Callable(B, C).bind(D,E), F) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(connect(A,B,C,D,E) != OK):", "(connect(A, Callable(B, C).bind(D), E) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("(start(A,B) != OK):", "(start(Callable(A,B)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(start(A,B) != OK):", "(start(Callable(A, B)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("func start(A,B):", "func start(A,B):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(start(A,B,C,D,E,F,G) != OK):", "(start(Callable(A,B).bind(C),D,E,F,G) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("disconnect(A,B,C) != OK):", "disconnect(A,Callable(B,C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("is_connected(A,B,C) != OK):", "is_connected(A,Callable(B,C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("is_connected(A,B,C))", "is_connected(A,Callable(B,C)))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(start(A,B,C,D,E,F,G) != OK):", "(start(Callable(A, B).bind(C), D, E, F, G) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("disconnect(A,B,C) != OK):", "disconnect(A, Callable(B, C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("is_connected(A,B,C) != OK):", "is_connected(A, Callable(B, C)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("is_connected(A,B,C))", "is_connected(A, Callable(B, C)))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("(tween_method(A,B,C,D,E).foo())", "(tween_method(Callable(A,B),C,D,E).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(tween_method(A,B,C,D,E,[F,G]).foo())", "(tween_method(Callable(A,B).bind(F,G),C,D,E).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(tween_callback(A,B).foo())", "(tween_callback(Callable(A,B)).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("(tween_callback(A,B,[C,D]).foo())", "(tween_callback(Callable(A,B).bind(C,D)).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(tween_method(A,B,C,D,E).foo())", "(tween_method(Callable(A, B), C, D, E).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(tween_method(A,B,C,D,E,[F,G]).foo())", "(tween_method(Callable(A, B).bind(F,G), C, D, E).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(tween_callback(A,B).foo())", "(tween_callback(Callable(A, B)).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("(tween_callback(A,B,[C,D]).foo())", "(tween_callback(Callable(A, B).bind(C,D)).foo())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("func _init(", "func _init(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("func _init(p_x:int)->void:", "func _init(p_x:int):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("func _init(a,b,c).(d,e,f):", "func _init(a,b,c):\n\tsuper(d,e,f)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("func _init(a,b,c).(a.b(),c.d()):", "func _init(a,b,c):\n\tsuper(a.b(),c.d())", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("func _init(p_x:int)->void:", "func _init(p_x:int)->void:", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("func _init(a: int).(d,e,f) -> void:", "func _init(a: int) -> void:\n\tsuper(d,e,f)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("q_PackedDataContainer._iter_init(variable1)", "q_PackedDataContainer._iter_init(variable1)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("assert(speed < 20, str(randi()%10))", "assert(speed < 20) #,str(randi()%10))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -894,18 +934,18 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 	valid = valid && test_conversion_gdscript_builtin("create_from_image(aa, bb)", "create_from_image(aa) #,bb", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("q_ImageTexture.create_from_image(variable1, variable2)", "q_ImageTexture.create_from_image(variable1) #,variable2", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
-	valid = valid && test_conversion_gdscript_builtin("set_cell_item(a, b, c, d ,e) # AA", "set_cell_item( Vector3(a,b,c) ,d,e) # AA", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("set_cell_item(a, b, c, d ,e) # AA", "set_cell_item(Vector3(a, b, c), d, e) # AA", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("set_cell_item(a, b)", "set_cell_item(a, b)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("get_cell_item_orientation(a, b,c)", "get_cell_item_orientation(Vector3i(a,b,c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("get_cell_item(a, b,c)", "get_cell_item(Vector3i(a,b,c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("map_to_world(a, b,c)", "map_to_local(Vector3i(a,b,c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("get_cell_item_orientation(a, b,c)", "get_cell_item_orientation(Vector3i(a, b, c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("get_cell_item(a, b,c)", "get_cell_item(Vector3i(a, b, c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("map_to_world(a, b,c)", "map_to_local(Vector3i(a, b, c))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("PackedStringArray(req_godot).join('.')", "'.'.join(PackedStringArray(req_godot))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("=PackedStringArray(req_godot).join('.')", "='.'.join(PackedStringArray(req_godot))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 
 	valid = valid && test_conversion_gdscript_builtin("apply_force(position, impulse)", "apply_force(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("apply_impulse(position, impulse)", "apply_impulse(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
-	valid = valid && test_conversion_gdscript_builtin("draw_rect(a,b,c,d,e).abc", "draw_rect(a,b,c,d).abc# e) TODOGODOT4 Antialiasing argument is missing", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
+	valid = valid && test_conversion_gdscript_builtin("draw_rect(a,b,c,d,e).abc", "draw_rect(a, b, c, d).abc# e) TODOGODOT4 Antialiasing argument is missing", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("get_focus_owner()", "get_viewport().gui_get_focus_owner()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("button.pressed = 1", "button.button_pressed = 1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
 	valid = valid && test_conversion_gdscript_builtin("button.pressed=1", "button.button_pressed=1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
@@ -913,12 +953,18 @@ bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
 
 	valid = valid && test_conversion_with_regex("AAA Color.white AF", "AAA Color.WHITE AF", &ProjectConverter3To4::rename_colors, "custom rename", reg_container);
 
+	// Note: Do not change to *scancode*, it is applied before that conversion.
+	valid = valid && test_conversion_with_regex("\"device\":-1,\"scancode\":16777231,\"physical_scancode\":16777232", "\"device\":-1,\"scancode\":4194319,\"physical_scancode\":4194320", &ProjectConverter3To4::rename_input_map_scancode, "custom rename", reg_container);
+	valid = valid && test_conversion_with_regex("\"device\":-1,\"scancode\":65,\"physical_scancode\":66", "\"device\":-1,\"scancode\":65,\"physical_scancode\":66", &ProjectConverter3To4::rename_input_map_scancode, "custom rename", reg_container);
+
 	// Custom rule conversion
 	{
 		String from = "instance";
 		String to = "instantiate";
 		String name = "AA.instance()";
-		Vector<String> got = String("AA.instance()").split("\n");
+
+		Vector<SourceLine> got = split_lines(name);
+
 		String expected = "AA.instantiate()";
 		custom_rename(got, from, to);
 		String got_str = collect_string_from_vector(got);
@@ -1255,13 +1301,13 @@ String ProjectConverter3To4::connect_arguments(const Vector<String> &arguments,
 
 	String value;
 	if (arguments.size() > 0 && from != 0 && from < to) {
-		value = ",";
+		value = ", ";
 	}
 
 	for (int i = from; i < to; i++) {
 		value += arguments[i];
 		if (i != to - 1) {
-			value += ',';
+			value += ", ";
 		}
 	}
 	return value;
@@ -1342,8 +1388,13 @@ String ProjectConverter3To4::get_object_of_execution(const String &line) const {
 	return line.substr(variable_start, (end - variable_start));
 }
 
-void ProjectConverter3To4::rename_colors(Vector<String> &lines, const RegExContainer &reg_container) {
-	for (String &line : lines) {
+void ProjectConverter3To4::rename_colors(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			if (line.contains("Color.")) {
 				for (unsigned int current_index = 0; RenamesMap3To4::color_renames[current_index][0]; current_index++) {
@@ -1375,8 +1426,24 @@ Vector<String> ProjectConverter3To4::check_for_rename_colors(Vector<String> &lin
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_classes(Vector<String> &lines, const RegExContainer &reg_container) {
-	for (String &line : lines) {
+void ProjectConverter3To4::fix_tool_declaration(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+	// In godot4, "tool" became "@tool" and must be located at the top of the file
+	for (int i = 0; i < source_lines.size(); ++i) {
+		if (source_lines[i].line == "tool") {
+			source_lines.remove_at(i);
+			source_lines.insert(0, { "@tool", false });
+			return; // assuming there's at most 1 tool declaration
+		}
+	}
+}
+
+void ProjectConverter3To4::rename_classes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			for (unsigned int current_index = 0; RenamesMap3To4::class_renames[current_index][0]; current_index++) {
 				if (line.contains(RenamesMap3To4::class_renames[current_index][0])) {
@@ -1443,8 +1510,13 @@ Vector<String> ProjectConverter3To4::check_for_rename_classes(Vector<String> &li
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_gdscript_functions(Vector<String> &lines, const RegExContainer &reg_container, bool builtin) {
-	for (String &line : lines) {
+void ProjectConverter3To4::rename_gdscript_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container, bool builtin) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			process_gdscript_line(line, reg_container, builtin);
 		}
@@ -1753,7 +1825,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 2) {
-				line = line.substr(0, start) + "sort_custom(Callable(" + parts[0] + "," + parts[1] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "sort_custom(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -1767,14 +1839,14 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		}
 	}
 
-	// -- draw_line(1,2,3,4,5) -> draw_line(1,2,3,4)            CanvasItem
+	// -- draw_line(1,2,3,4,5) -> draw_line(1, 2, 3, 4)            CanvasItem
 	if (line.contains("draw_line(")) {
 		int start = line.find("draw_line(");
 		int end = get_end_parenthesis(line.substr(start)) + 1;
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 5) {
-				line = line.substr(0, start) + "draw_line(" + parts[0] + "," + parts[1] + "," + parts[2] + "," + parts[3] + ")" + line.substr(end + start);
+				line = line.substr(0, start) + "draw_line(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start);
 			}
 		}
 	}
@@ -1831,7 +1903,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 1) {
-				line = line.substr(0, start) + " * " + parts[0] + line.substr(end + start);
+				line = line.substr(0, start) + " * (" + parts[0] + ")" + line.substr(end + start);
 			}
 		}
 	}
@@ -1846,13 +1918,13 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 				int start2 = line.find(object_exec + ".xform");
 				Vector<String> parts = parse_arguments(line.substr(start, end));
 				if (parts.size() == 1) {
-					line = line.substr(0, start2) + parts[0] + " * " + object_exec + line.substr(end + start);
+					line = line.substr(0, start2) + "(" + parts[0] + ") * " + object_exec + line.substr(end + start);
 				}
 			}
 		}
 	}
 
-	// -- "(connect(A,B,C,D,E) != OK):", "(connect(A,Callable(B,C).bind(D),E)      Object
+	// -- "(connect(A,B,C,D,E) != OK):", "(connect(A, Callable(B, C).bind(D), E)      Object
 	if (line.contains("connect(")) {
 		int start = line.find("connect(");
 		// Protection from disconnect
@@ -1861,9 +1933,9 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 			if (end > -1) {
 				Vector<String> parts = parse_arguments(line.substr(start, end));
 				if (parts.size() == 3) {
-					line = line.substr(0, start) + "connect(" + parts[0] + ",Callable(" + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+					line = line.substr(0, start) + "connect(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 				} else if (parts.size() >= 4) {
-					line = line.substr(0, start) + "connect(" + parts[0] + ",Callable(" + parts[1] + "," + parts[2] + ").bind(" + parts[3].lstrip("[").rstrip("]") + ")" + connect_arguments(parts, 4) + ")" + line.substr(end + start);
+					line = line.substr(0, start) + "connect(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + ").bind(" + parts[3].lstrip(" [").rstrip("] ") + ")" + connect_arguments(parts, 4) + ")" + line.substr(end + start);
 				}
 			}
 		}
@@ -1875,7 +1947,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "disconnect(" + parts[0] + ",Callable(" + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "disconnect(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -1886,7 +1958,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "is_connected(" + parts[0] + ",Callable(" + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "is_connected(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -1898,9 +1970,9 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 5) {
-				line = line.substr(0, start) + "tween_method(Callable(" + parts[0] + "," + parts[1] + ")," + parts[2] + "," + parts[3] + "," + parts[4] + ")" + line.substr(end + start);
+				line = line.substr(0, start) + "tween_method(Callable(" + parts[0] + ", " + parts[1] + "), " + parts[2] + ", " + parts[3] + ", " + parts[4] + ")" + line.substr(end + start);
 			} else if (parts.size() >= 6) {
-				line = line.substr(0, start) + "tween_method(Callable(" + parts[0] + "," + parts[1] + ").bind(" + connect_arguments(parts, 5).substr(1).lstrip("[").rstrip("]") + ")," + parts[2] + "," + parts[3] + "," + parts[4] + ")" + line.substr(end + start);
+				line = line.substr(0, start) + "tween_method(Callable(" + parts[0] + ", " + parts[1] + ").bind(" + connect_arguments(parts, 5).substr(1).lstrip(" [").rstrip("] ") + "), " + parts[2] + ", " + parts[3] + ", " + parts[4] + ")" + line.substr(end + start);
 			}
 		}
 	}
@@ -1911,14 +1983,14 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 2) {
-				line = line.substr(0, start) + "tween_callback(Callable(" + parts[0] + "," + parts[1] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "tween_callback(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
 			} else if (parts.size() >= 3) {
-				line = line.substr(0, start) + "tween_callback(Callable(" + parts[0] + "," + parts[1] + ").bind(" + connect_arguments(parts, 2).substr(1).lstrip("[").rstrip("]") + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "tween_callback(Callable(" + parts[0] + ", " + parts[1] + ").bind(" + connect_arguments(parts, 2).substr(1).lstrip(" [").rstrip("] ") + "))" + line.substr(end + start);
 			}
 		}
 	}
-	// -- start(a,b) -> start(Callable(a,b))      Thread
-	// -- start(a,b,c,d) -> start(Callable(a,b).bind(c),d)      Thread
+	// -- start(a,b) -> start(Callable(a, b))      Thread
+	// -- start(a,b,c,d) -> start(Callable(a, b).bind(c), d)      Thread
 	if (line.contains("start(")) {
 		int start = line.find("start(");
 		int end = get_end_parenthesis(line.substr(start)) + 1;
@@ -1927,24 +1999,25 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 			if (end > -1) {
 				Vector<String> parts = parse_arguments(line.substr(start, end));
 				if (parts.size() == 2) {
-					line = line.substr(0, start) + "start(Callable(" + parts[0] + "," + parts[1] + "))" + line.substr(end + start);
+					line = line.substr(0, start) + "start(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
 				} else if (parts.size() >= 3) {
-					line = line.substr(0, start) + "start(Callable(" + parts[0] + "," + parts[1] + ").bind(" + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
+					line = line.substr(0, start) + "start(Callable(" + parts[0] + ", " + parts[1] + ").bind(" + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
 				}
 			}
 		}
 	}
-	// -- func _init(p_x:int)->void:  -> func _init(p_x:int):    Object # https://github.com/godotengine/godot/issues/50589
-	if (line.contains(" _init(")) {
-		int start = line.find(" _init(");
-		if (line.contains(":")) {
-			int end = line.rfind(":") + 1;
-			if (end > -1) {
-				Vector<String> parts = parse_arguments(line.substr(start, end));
-				line = line.substr(0, start) + " _init(" + connect_arguments(parts, 0) + "):" + line.substr(end + start);
-			}
+	// -- func _init(p_x:int).(p_x):  -> func _init(p_x:int):\n\tsuper(p_x)    Object # https://github.com/godotengine/godot/issues/70542
+	if (line.contains(" _init(") && line.rfind(":") > 0) {
+		//     func _init(p_arg1).(super4, super5, super6)->void:
+		// ^--^indent            ^super_start   super_end^
+		int indent = line.count("\t", 0, line.find("func"));
+		int super_start = line.find(".(");
+		int super_end = line.rfind(")");
+		if (super_start > 0 && super_end > super_start) {
+			line = line.substr(0, super_start) + line.substr(super_end + 1) + "\n" + String("\t").repeat(indent + 1) + "super" + line.substr(super_start + 1, super_end - super_start);
 		}
 	}
+
 	//  assert(speed < 20, str(randi()%10))  ->  assert(speed < 20) #,str(randi()%10))    GDScript - GDScript bug constant message
 	if (line.contains("assert(")) {
 		int start = line.find("assert(");
@@ -1974,7 +2047,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() > 2) {
-				line = line.substr(0, start) + "set_cell_item( Vector3(" + parts[0] + "," + parts[1] + "," + parts[2] + ") " + connect_arguments(parts, 3) + ")" + line.substr(end + start);
+				line = line.substr(0, start) + "set_cell_item(Vector3(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ")" + connect_arguments(parts, 3).lstrip(" ") + ")" + line.substr(end + start);
 			}
 		}
 	}
@@ -1985,7 +2058,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "get_cell_item(Vector3i(" + parts[0] + "," + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "get_cell_item(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -1996,7 +2069,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "get_cell_item_orientation(Vector3i(" + parts[0] + "," + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "get_cell_item_orientation(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -2029,7 +2102,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "map_to_local(Vector3i(" + parts[0] + "," + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "map_to_local(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			} else if (parts.size() == 1) {
 				line = line.substr(0, start) + "map_to_local(" + parts[0] + ")" + line.substr(end + start);
 			}
@@ -2067,7 +2140,7 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 5) {
-				line = line.substr(0, start) + "draw_rect(" + parts[0] + "," + parts[1] + "," + parts[2] + "," + parts[3] + ")" + line.substr(end + start) + "# " + parts[4] + ") TODOGODOT4 Antialiasing argument is missing";
+				line = line.substr(0, start) + "draw_rect(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start) + "# " + parts[4] + ") TODOGODOT4 Antialiasing argument is missing";
 			}
 		}
 	}
@@ -2195,6 +2268,10 @@ void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContai
 	if (line.contains("_unhandled_key_input(event: InputEventKey)")) {
 		line = line.replace("_unhandled_key_input(event: InputEventKey)", "_unhandled_key_input(event: InputEvent)");
 	}
+
+	if (line.contains("Engine.editor_hint")) {
+		line = line.replace("Engine.editor_hint", "Engine.is_editor_hint()");
+	}
 }
 
 void ProjectConverter3To4::process_csharp_line(String &line, const RegExContainer &reg_container) {
@@ -2220,7 +2297,7 @@ void ProjectConverter3To4::process_csharp_line(String &line, const RegExContaine
 			if (end > -1) {
 				Vector<String> parts = parse_arguments(line.substr(start, end));
 				if (parts.size() >= 3) {
-					line = line.substr(0, start) + "Connect(" + parts[0] + ",new Callable(" + parts[1] + "," + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
+					line = line.substr(0, start) + "Connect(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
 				}
 			}
 		}
@@ -2232,7 +2309,7 @@ void ProjectConverter3To4::process_csharp_line(String &line, const RegExContaine
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "Disconnect(" + parts[0] + ",new Callable(" + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "Disconnect(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
@@ -2243,14 +2320,19 @@ void ProjectConverter3To4::process_csharp_line(String &line, const RegExContaine
 		if (end > -1) {
 			Vector<String> parts = parse_arguments(line.substr(start, end));
 			if (parts.size() == 3) {
-				line = line.substr(0, start) + "IsConnected(" + parts[0] + ",new Callable(" + parts[1] + "," + parts[2] + "))" + line.substr(end + start);
+				line = line.substr(0, start) + "IsConnected(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
 			}
 		}
 	}
 }
 
-void ProjectConverter3To4::rename_csharp_functions(Vector<String> &lines, const RegExContainer &reg_container) {
-	for (String &line : lines) {
+void ProjectConverter3To4::rename_csharp_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			process_csharp_line(line, reg_container);
 		}
@@ -2275,10 +2357,15 @@ Vector<String> ProjectConverter3To4::check_for_rename_csharp_functions(Vector<St
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_csharp_attributes(Vector<String> &lines, const RegExContainer &reg_container) {
+void ProjectConverter3To4::rename_csharp_attributes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
 	static String error_message = "The master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using Multiplayer.GetRemoteSenderId()\n";
 
-	for (String &line : lines) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			line = reg_container.keyword_csharp_remote.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", true);
 			line = reg_container.keyword_csharp_remotesync.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", true);
@@ -2340,14 +2427,16 @@ Vector<String> ProjectConverter3To4::check_for_rename_csharp_attributes(Vector<S
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container) {
+void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
 	static String error_message = "The master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using get_multiplayer().get_remote_sender_id()\n";
 
-	for (String &line : lines) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
-			if (line.contains("tool")) {
-				line = reg_container.keyword_gdscript_tool.sub(line, "@tool", true);
-			}
 			if (line.contains("export")) {
 				line = reg_container.keyword_gdscript_export_single.sub(line, "@export", true);
 			}
@@ -2495,10 +2584,73 @@ Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<S
 	return found_renames;
 }
 
-void ProjectConverter3To4::custom_rename(Vector<String> &lines, String from, String to) {
+void ProjectConverter3To4::rename_input_map_scancode(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
+	// The old Special Key, now colliding with CMD_OR_CTRL.
+	const int old_spkey = (1 << 24);
+
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
+		if (uint64_t(line.length()) <= maximum_line_length) {
+			TypedArray<RegExMatch> reg_match = reg_container.input_map_keycode.search_all(line);
+
+			for (int i = 0; i < reg_match.size(); ++i) {
+				Ref<RegExMatch> match = reg_match[i];
+				PackedStringArray strings = match->get_strings();
+				int key = strings[3].to_int();
+
+				if (key & old_spkey) {
+					// Create new key, clearing old Special Key and setting new one.
+					key = (key & ~old_spkey) | (int)Key::SPECIAL;
+
+					line = line.replace(strings[0], String(",\"") + strings[1] + "scancode\":" + String::num_int64(key));
+				}
+			}
+		}
+	}
+}
+
+Vector<String> ProjectConverter3To4::check_for_rename_input_map_scancode(Vector<String> &lines, const RegExContainer &reg_container) {
+	Vector<String> found_renames;
+
+	// The old Special Key, now colliding with CMD_OR_CTRL.
+	const int old_spkey = (1 << 24);
+
+	int current_line = 1;
+	for (String &line : lines) {
+		if (uint64_t(line.length()) <= maximum_line_length) {
+			TypedArray<RegExMatch> reg_match = reg_container.input_map_keycode.search_all(line);
+
+			for (int i = 0; i < reg_match.size(); ++i) {
+				Ref<RegExMatch> match = reg_match[i];
+				PackedStringArray strings = match->get_strings();
+				int key = strings[3].to_int();
+
+				if (key & old_spkey) {
+					// Create new key, clearing old Special Key and setting new one.
+					key = (key & ~old_spkey) | (int)Key::SPECIAL;
+
+					found_renames.append(line_formatter(current_line, strings[3], String::num_int64(key), line));
+				}
+			}
+		}
+		current_line++;
+	}
+	return found_renames;
+}
+
+void ProjectConverter3To4::custom_rename(Vector<SourceLine> &source_lines, String from, String to) {
 	RegEx reg = RegEx(String("\\b") + from + "\\b");
 	CRASH_COND(!reg.is_valid());
-	for (String &line : lines) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			line = reg.sub(line, to, true);
 		}
@@ -2524,8 +2676,13 @@ Vector<String> ProjectConverter3To4::check_for_custom_rename(Vector<String> &lin
 	return found_renames;
 }
 
-void ProjectConverter3To4::rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<String> &lines) {
-	for (String &line : lines) {
+void ProjectConverter3To4::rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<SourceLine> &source_lines) {
+	for (SourceLine &source_line : source_lines) {
+		if (source_line.is_comment) {
+			continue;
+		}
+
+		String &line = source_line.line;
 		if (uint64_t(line.length()) <= maximum_line_length) {
 			for (unsigned int current_index = 0; current_index < cached_regexes.size(); current_index++) {
 				if (line.contains(array[current_index][0])) {
@@ -2595,10 +2752,10 @@ String ProjectConverter3To4::simple_line_formatter(int current_line, String old_
 }
 
 // Collects string from vector strings
-String ProjectConverter3To4::collect_string_from_vector(Vector<String> &vector) {
+String ProjectConverter3To4::collect_string_from_vector(Vector<SourceLine> &vector) {
 	String string = "";
 	for (int i = 0; i < vector.size(); i++) {
-		string += vector[i];
+		string += vector[i].line;
 
 		if (i != vector.size() - 1) {
 			string += "\n";

+ 22 - 11
editor/project_converter_3_to_4.h

@@ -58,6 +58,11 @@ public:
 #include "core/templates/local_vector.h"
 #include "core/templates/vector.h"
 
+struct SourceLine {
+	String line;
+	bool is_comment;
+};
+
 class RegEx;
 
 class ProjectConverter3To4 {
@@ -66,30 +71,35 @@ class ProjectConverter3To4 {
 	uint64_t maximum_file_size;
 	uint64_t maximum_line_length;
 
-	void rename_colors(Vector<String> &lines, const RegExContainer &reg_container);
+	void fix_tool_declaration(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
+
+	void rename_colors(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_colors(Vector<String> &lines, const RegExContainer &reg_container);
 
-	void rename_classes(Vector<String> &lines, const RegExContainer &reg_container);
+	void rename_classes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_classes(Vector<String> &lines, const RegExContainer &reg_container);
 
-	void rename_gdscript_functions(Vector<String> &lines, const RegExContainer &reg_container, bool builtin);
+	void rename_gdscript_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container, bool builtin);
 	Vector<String> check_for_rename_gdscript_functions(Vector<String> &lines, const RegExContainer &reg_container, bool builtin);
 	void process_gdscript_line(String &line, const RegExContainer &reg_container, bool builtin);
 
-	void rename_csharp_functions(Vector<String> &lines, const RegExContainer &reg_container);
+	void rename_csharp_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_csharp_functions(Vector<String> &lines, const RegExContainer &reg_container);
 	void process_csharp_line(String &line, const RegExContainer &reg_container);
 
-	void rename_csharp_attributes(Vector<String> &lines, const RegExContainer &reg_container);
+	void rename_csharp_attributes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_csharp_attributes(Vector<String> &lines, const RegExContainer &reg_container);
 
-	void rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container);
+	void rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
 	Vector<String> check_for_rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container);
 
-	void custom_rename(Vector<String> &lines, String from, String to);
+	void rename_input_map_scancode(Vector<SourceLine> &source_lines, const RegExContainer &reg_container);
+	Vector<String> check_for_rename_input_map_scancode(Vector<String> &lines, const RegExContainer &reg_container);
+
+	void custom_rename(Vector<SourceLine> &source_lines, String from, String to);
 	Vector<String> check_for_custom_rename(Vector<String> &lines, String from, String to);
 
-	void rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<String> &lines);
+	void rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<SourceLine> &source_lines);
 	Vector<String> check_for_rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<String> &lines);
 
 	Vector<String> check_for_files();
@@ -102,11 +112,12 @@ class ProjectConverter3To4 {
 
 	String line_formatter(int current_line, String from, String to, String line);
 	String simple_line_formatter(int current_line, String old_line, String line);
-	String collect_string_from_vector(Vector<String> &vector);
+	String collect_string_from_vector(Vector<SourceLine> &vector);
+	Vector<SourceLine> split_lines(const String &text);
 
 	bool test_single_array(const char *array[][2], bool ignore_second_check = false);
-	bool test_conversion_gdscript_builtin(String name, String expected, void (ProjectConverter3To4::*func)(Vector<String> &, const RegExContainer &, bool), String what, const RegExContainer &reg_container, bool builtin);
-	bool test_conversion_with_regex(String name, String expected, void (ProjectConverter3To4::*func)(Vector<String> &, const RegExContainer &), String what, const RegExContainer &reg_container);
+	bool test_conversion_gdscript_builtin(String name, String expected, void (ProjectConverter3To4::*func)(Vector<SourceLine> &, const RegExContainer &, bool), String what, const RegExContainer &reg_container, bool builtin);
+	bool test_conversion_with_regex(String name, String expected, void (ProjectConverter3To4::*func)(Vector<SourceLine> &, const RegExContainer &), String what, const RegExContainer &reg_container);
 	bool test_conversion_basic(String name, String expected, const char *array[][2], LocalVector<RegEx *> &regex_cache, String what);
 	bool test_array_names();
 	bool test_conversion(RegExContainer &reg_container);

+ 5 - 1
editor/property_selector.cpp

@@ -124,7 +124,7 @@ void PropertySelector::_update_search() {
 
 		bool found = false;
 
-		Ref<Texture2D> type_icons[Variant::VARIANT_MAX] = {
+		Ref<Texture2D> type_icons[] = {
 			search_options->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("bool"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("int"), SNAME("EditorIcons")),
@@ -137,11 +137,14 @@ void PropertySelector::_update_search() {
 			search_options->get_theme_icon(SNAME("Vector3"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("Vector3i"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("Transform2D"), SNAME("EditorIcons")),
+			search_options->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")),
+			search_options->get_theme_icon(SNAME("Vector4"), SNAME("EditorIcons")), // Vector4i, needs icon.
 			search_options->get_theme_icon(SNAME("Plane"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("Quaternion"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("AABB"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("Basis"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("Transform3D"), SNAME("EditorIcons")),
+			search_options->get_theme_icon(SNAME("Variant"), SNAME("EditorIcons")), // Projection, needs icon.
 			search_options->get_theme_icon(SNAME("Color"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("StringName"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("NodePath"), SNAME("EditorIcons")),
@@ -161,6 +164,7 @@ void PropertySelector::_update_search() {
 			search_options->get_theme_icon(SNAME("PackedVector3Array"), SNAME("EditorIcons")),
 			search_options->get_theme_icon(SNAME("PackedColorArray"), SNAME("EditorIcons"))
 		};
+		static_assert((sizeof(type_icons) / sizeof(type_icons[0])) == Variant::VARIANT_MAX, "Number of type icons doesn't match the number of Variant types.");
 
 		for (const PropertyInfo &E : props) {
 			if (E.usage == PROPERTY_USAGE_CATEGORY) {

+ 9 - 8
editor/renames_map_3_to_4.cpp

@@ -387,7 +387,6 @@ const char *RenamesMap3To4::gdscript_function_renames[][2] = {
 	{ "get_unit_db", "get_volume_db" }, // AudioStreamPlayer3D
 	{ "get_unit_offset", "get_progress_ratio" }, // PathFollow2D, PathFollow3D
 	{ "get_use_in_baked_light", "is_baking_navigation" }, // GridMap
-	{ "get_used_cells_by_id", "get_used_cells" }, // TileMap
 	{ "get_v_scrollbar", "get_v_scroll_bar" }, // ScrollContainer
 	{ "get_visible_name", "_get_visible_name" }, // EditorImportPlugin
 	{ "get_window_layout", "_get_window_layout" }, // EditorPlugin
@@ -721,7 +720,7 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = {
 	{ "GetEndianSwap", "IsBigEndian" }, // File
 	{ "GetErrorString", "GetErrorMessage" }, // JSON
 	{ "GetFocusNeighbour", "GetFocusNeighbor" }, // Control
-	{ "GetFollowSmoothing", "GetFollowSmoothingSpeed" }, // Camera2D
+	{ "GetFollowSmoothing", "GetPositionSmoothingSpeed" }, // Camera2D
 	{ "GetFontTypes", "GetFontTypeList" }, // Theme
 	{ "GetFrameColor", "GetColor" }, // ColorRect
 	{ "GetGlobalRateScale", "GetPlaybackSpeedScale" }, // AudioServer
@@ -795,7 +794,6 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = {
 	{ "GetUnitDb", "GetVolumeDb" }, // AudioStreamPlayer3D
 	{ "GetUnitOffset", "GetProgressRatio" }, // PathFollow2D, PathFollow3D
 	{ "GetUseInBakedLight", "IsBakingNavigation" }, // GridMap
-	{ "GetUsedCellsById", "GetUsedCells" }, // TileMap
 	{ "GetVScrollbar", "GetVScrollBar" }, // ScrollContainer
 	{ "GetVisibleName", "_GetVisibleName" }, // EditorImportPlugin
 	{ "GetWindowLayout", "_GetWindowLayout" }, // EditorPlugin
@@ -907,12 +905,12 @@ const char *RenamesMap3To4::csharp_function_renames[][2] = {
 	{ "SetDepthBiasEnable", "SetDepthBiasEnabled" }, // RDPipelineRasterizationState
 	{ "SetDevice", "SetOutputDevice" }, // AudioServer
 	{ "SetDoubleclick", "SetDoubleClick" }, // InputEventMouseButton
-	{ "SetEnableFollowSmoothing", "SetFollowSmoothingEnabled" }, // Camera2D
+	{ "SetEnableFollowSmoothing", "SetPositionSmoothingEnabled" }, // Camera2D
 	{ "SetEnabledFocusMode", "SetFocusMode" }, // BaseButton
 	{ "SetEndianSwap", "SetBigEndian" }, // File
 	{ "SetExpandToTextLength", "SetExpandToTextLengthEnabled" }, // LineEdit
 	{ "SetFocusNeighbour", "SetFocusNeighbor" }, // Control
-	{ "SetFollowSmoothing", "SetFollowSmoothingSpeed" }, // Camera2D
+	{ "SetFollowSmoothing", "SetPositionSmoothingSpeed" }, // Camera2D
 	{ "SetFrameColor", "SetColor" }, // ColorRect
 	{ "SetGlobalRateScale", "SetPlaybackSpeedScale" }, // AudioServer
 	{ "SetGravityDistanceScale", "SetGravityPointDistanceScale" }, // Area2D
@@ -1084,6 +1082,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = {
 	{ "files_disabled", "file_disabled_color" }, // Theme
 	{ "folder_icon_modulate", "folder_icon_color" }, // Theme
 	{ "global_rate_scale", "playback_speed_scale" }, // AudioServer
+	{ "global_translation", "global_position" }, // Node3D
 	{ "gravity_distance_scale", "gravity_point_unit_distance" }, // Area(2D/3D)
 	{ "gravity_vec", "gravity_direction" }, // Area(2D/3D)
 	{ "hint_tooltip", "tooltip_text" }, // Control
@@ -1122,7 +1121,7 @@ const char *RenamesMap3To4::gdscript_properties_renames[][2] = {
 	{ "selectedframe", "selected_frame" }, // Theme
 	{ "size_override_stretch", "size_2d_override_stretch" }, // SubViewport
 	{ "slips_on_slope", "slide_on_slope" }, // SeparationRayShape2D
-	{ "smoothing_enabled", "follow_smoothing_enabled" }, // Camera2D
+	{ "smoothing_enabled", "position_smoothing_enabled" }, // Camera2D
 	{ "smoothing_speed", "position_smoothing_speed" }, // Camera2D
 	{ "ss_reflections_depth_tolerance", "ssr_depth_tolerance" }, // Environment
 	{ "ss_reflections_enabled", "ssr_enabled" }, // Environment
@@ -1215,8 +1214,8 @@ const char *RenamesMap3To4::csharp_properties_renames[][2] = {
 	{ "Selectedframe", "SelectedFrame" }, // Theme
 	{ "SizeOverrideStretch", "Size2dOverrideStretch" }, // SubViewport
 	{ "SlipsOnSlope", "SlideOnSlope" }, // SeparationRayShape2D
-	{ "SmoothingEnabled", "FollowSmoothingEnabled" }, // Camera2D
-	{ "SmoothingSpeed", "FollowSmoothingSpeed" }, // Camera2D
+	{ "SmoothingEnabled", "PositionSmoothingEnabled" }, // Camera2D
+	{ "SmoothingSpeed", "PositionSmoothingSpeed" }, // Camera2D
 	{ "SsReflectionsDepthTolerance", "SsrDepthTolerance" }, // Environment
 	{ "SsReflectionsEnabled", "SsrEnabled" }, // Environment
 	{ "SsReflectionsFadeIn", "SsrFadeIn" }, // Environment
@@ -1478,6 +1477,7 @@ const char *RenamesMap3To4::class_renames[][2] = {
 	{ "CubeMesh", "BoxMesh" },
 	{ "CylinderShape", "CylinderShape3D" },
 	{ "DirectionalLight", "DirectionalLight3D" },
+	{ "Directory", "DirAccess" },
 	{ "DynamicFont", "FontFile" },
 	{ "DynamicFontData", "FontFile" },
 	{ "EditorNavigationMeshGenerator", "NavigationMeshGenerator" },
@@ -1520,6 +1520,7 @@ const char *RenamesMap3To4::class_renames[][2] = {
 	{ "NavigationPolygonInstance", "NavigationRegion2D" },
 	{ "NavigationRegion", "NavigationRegion3D" },
 	{ "NavigationServer", "NavigationServer3D" },
+	{ "NetworkedMultiplayerCustom", "MultiplayerPeerExtension" },
 	{ "NetworkedMultiplayerENet", "ENetMultiplayerPeer" },
 	{ "NetworkedMultiplayerPeer", "MultiplayerPeer" },
 	{ "Occluder", "OccluderInstance3D" },

+ 7 - 5
modules/gdscript/gdscript_compiler.cpp

@@ -511,6 +511,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 		} break;
 		case GDScriptParser::Node::CALL: {
 			const GDScriptParser::CallNode *call = static_cast<const GDScriptParser::CallNode *>(p_expression);
+			bool is_awaited = p_expression == awaited_node;
 			GDScriptDataType type = _gdtype_from_datatype(call->get_datatype(), codegen.script);
 			GDScriptCodeGenerator::Address result;
 			if (p_root) {
@@ -565,13 +566,13 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 						} else if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
 							GDScriptCodeGenerator::Address self;
 							self.mode = GDScriptCodeGenerator::Address::CLASS;
-							if (within_await) {
+							if (is_awaited) {
 								gen->write_call_async(result, self, call->function_name, arguments);
 							} else {
 								gen->write_call(result, self, call->function_name, arguments);
 							}
 						} else {
-							if (within_await) {
+							if (is_awaited) {
 								gen->write_call_self_async(result, call->function_name, arguments);
 							} else {
 								gen->write_call_self(result, call->function_name, arguments);
@@ -593,7 +594,7 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 								if (r_error) {
 									return GDScriptCodeGenerator::Address();
 								}
-								if (within_await) {
+								if (is_awaited) {
 									gen->write_call_async(result, base, call->function_name, arguments);
 								} else if (base.type.has_type && base.type.kind != GDScriptDataType::BUILTIN) {
 									// Native method, use faster path.
@@ -666,9 +667,10 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
 			const GDScriptParser::AwaitNode *await = static_cast<const GDScriptParser::AwaitNode *>(p_expression);
 
 			GDScriptCodeGenerator::Address result = codegen.add_temporary(_gdtype_from_datatype(p_expression->get_datatype(), codegen.script));
-			within_await = true;
+			GDScriptParser::ExpressionNode *previous_awaited_node = awaited_node;
+			awaited_node = await->to_await;
 			GDScriptCodeGenerator::Address argument = _parse_expression(codegen, r_error, await->to_await);
-			within_await = false;
+			awaited_node = previous_awaited_node;
 			if (r_error) {
 				return GDScriptCodeGenerator::Address();
 			}

+ 1 - 1
modules/gdscript/gdscript_compiler.h

@@ -137,7 +137,7 @@ class GDScriptCompiler {
 	int err_column = 0;
 	StringName source;
 	String error;
-	bool within_await = false;
+	GDScriptParser::ExpressionNode *awaited_node = nullptr;
 
 public:
 	static void convert_to_initializer_type(Variant &p_variant, const GDScriptParser::VariableNode *p_node);

+ 3 - 2
modules/gltf/editor/editor_scene_importer_blend.cpp

@@ -297,13 +297,14 @@ static bool _test_blender_path(const String &p_path, String *r_err = nullptr) {
 		}
 		return false;
 	}
-
-	if (pipe.find("Blender ") != 0) {
+	int bl = pipe.find("Blender ");
+	if (bl == -1) {
 		if (r_err) {
 			*r_err = vformat(TTR("Unexpected --version output from Blender binary at: %s"), path);
 		}
 		return false;
 	}
+	pipe = pipe.substr(bl);
 	pipe = pipe.replace_first("Blender ", "");
 	int pp = pipe.find(".");
 	if (pp == -1) {

+ 14 - 0
modules/gltf/gltf_document.cpp

@@ -3748,6 +3748,14 @@ Error GLTFDocument::_serialize_materials(Ref<GLTFState> p_state) {
 			d["alphaMode"] = "BLEND";
 		}
 
+		Dictionary extensions;
+		if (base_material->get_shading_mode() == BaseMaterial3D::SHADING_MODE_UNSHADED) {
+			Dictionary mat_unlit;
+			extensions["KHR_materials_unlit"] = mat_unlit;
+			p_state->add_used_extension("KHR_materials_unlit");
+		}
+		d["extensions"] = extensions;
+
 		materials.push_back(d);
 	}
 	if (!materials.size()) {
@@ -3780,6 +3788,11 @@ Error GLTFDocument::_parse_materials(Ref<GLTFState> p_state) {
 		if (d.has("extensions")) {
 			pbr_spec_gloss_extensions = d["extensions"];
 		}
+
+		if (pbr_spec_gloss_extensions.has("KHR_materials_unlit")) {
+			material->set_shading_mode(BaseMaterial3D::SHADING_MODE_UNSHADED);
+		}
+
 		if (pbr_spec_gloss_extensions.has("KHR_materials_pbrSpecularGlossiness")) {
 			WARN_PRINT("Material uses a specular and glossiness workflow. Textures will be converted to roughness and metallic workflow, which may not be 100% accurate.");
 			Dictionary sgm = pbr_spec_gloss_extensions["KHR_materials_pbrSpecularGlossiness"];
@@ -7440,6 +7453,7 @@ Error GLTFDocument::_parse_gltf_extensions(Ref<GLTFState> p_state) {
 	supported_extensions.insert("KHR_lights_punctual");
 	supported_extensions.insert("KHR_materials_pbrSpecularGlossiness");
 	supported_extensions.insert("KHR_texture_transform");
+	supported_extensions.insert("KHR_materials_unlit");
 	for (Ref<GLTFDocumentExtension> ext : document_extensions) {
 		ERR_CONTINUE(ext.is_null());
 		Vector<String> ext_supported_extensions = ext->get_supported_extensions();

+ 5 - 0
modules/mono/csharp_script.cpp

@@ -57,6 +57,7 @@
 #include "godotsharp_dirs.h"
 #include "managed_callable.h"
 #include "mono_gd/gd_mono_cache.h"
+#include "servers/text_server.h"
 #include "signal_awaiter_utils.h"
 #include "utils/macros.h"
 #include "utils/naming_utils.h"
@@ -366,6 +367,10 @@ String CSharpLanguage::validate_path(const String &p_path) const {
 	if (keywords.find(class_name)) {
 		return RTR("Class name can't be a reserved keyword");
 	}
+	if (!TS->is_valid_identifier(class_name)) {
+		return RTR("Class name must be a valid identifier");
+	}
+
 	return "";
 }
 

+ 26 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/Common.cs

@@ -194,6 +194,32 @@ namespace Godot.SourceGenerators
                 location?.SourceTree?.FilePath));
         }
 
+        public static void ReportExportedMemberIsExplicitInterfaceImplementation(
+            GeneratorExecutionContext context,
+            ISymbol exportedMemberSymbol
+        )
+        {
+            var locations = exportedMemberSymbol.Locations;
+            var location = locations.FirstOrDefault(l => l.SourceTree != null) ?? locations.FirstOrDefault();
+
+            string message = $"Attempted to export explicit interface property implementation: " +
+                             $"'{exportedMemberSymbol.ToDisplayString()}'";
+
+            string description = $"{message}. Explicit interface implementations can't be exported." +
+                                 " Remove the '[Export]' attribute.";
+
+            context.ReportDiagnostic(Diagnostic.Create(
+                new DiagnosticDescriptor(id: "GD0106",
+                    title: message,
+                    messageFormat: message,
+                    category: "Usage",
+                    DiagnosticSeverity.Error,
+                    isEnabledByDefault: true,
+                    description),
+                location,
+                location?.SourceTree?.FilePath));
+        }
+
         public static void ReportSignalDelegateMissingSuffix(
             GeneratorExecutionContext context,
             INamedTypeSymbol delegateSymbol)

+ 1 - 1
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertiesGenerator.cs

@@ -113,7 +113,7 @@ namespace Godot.SourceGenerators
             var propertySymbols = members
                 .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
                 .Cast<IPropertySymbol>()
-                .Where(s => !s.IsIndexer);
+                .Where(s => !s.IsIndexer && s.ExplicitInterfaceImplementations.Length == 0);
 
             var fieldSymbols = members
                 .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)

+ 6 - 0
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptPropertyDefValGenerator.cs

@@ -151,6 +151,12 @@ namespace Godot.SourceGenerators
                     continue;
                 }
 
+                if (property.ExplicitInterfaceImplementations.Length > 0)
+                {
+                    Common.ReportExportedMemberIsExplicitInterfaceImplementation(context, property);
+                    continue;
+                }
+
                 var propertyType = property.Type;
                 var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(propertyType, typeCache);
 

+ 1 - 1
modules/mono/editor/Godot.NET.Sdk/Godot.SourceGenerators/ScriptSerializationGenerator.cs

@@ -113,7 +113,7 @@ namespace Godot.SourceGenerators
             var propertySymbols = members
                 .Where(s => !s.IsStatic && s.Kind == SymbolKind.Property)
                 .Cast<IPropertySymbol>()
-                .Where(s => !s.IsIndexer);
+                .Where(s => !s.IsIndexer && s.ExplicitInterfaceImplementations.Length == 0);
 
             var fieldSymbols = members
                 .Where(s => !s.IsStatic && s.Kind == SymbolKind.Field && !s.IsImplicitlyDeclared)

+ 3 - 3
modules/mono/editor/bindings_generator.cpp

@@ -1483,9 +1483,9 @@ Error BindingsGenerator::_generate_cs_type(const TypeInterface &itype, const Str
 
 		output << MEMBER_BEGIN "public static GodotObject " CS_PROPERTY_SINGLETON "\n" INDENT1 "{\n"
 			   << INDENT2 "get\n" INDENT2 "{\n" INDENT3 "if (singleton == null)\n"
-			   << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(typeof("
-			   << itype.proxy_name
-			   << ").Name);\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n";
+			   << INDENT4 "singleton = " C_METHOD_ENGINE_GET_SINGLETON "(\""
+			   << itype.name
+			   << "\");\n" INDENT3 "return singleton;\n" INDENT2 "}\n" INDENT1 "}\n";
 
 		output.append(MEMBER_BEGIN "private static readonly StringName " BINDINGS_NATIVE_NAME_FIELD " = \"");
 		output.append(itype.name);

+ 1 - 1
modules/navigation/godot_navigation_server.cpp

@@ -757,7 +757,7 @@ COMMAND_1(free, RID, p_object) {
 		agent_owner.free(p_object);
 
 	} else {
-		ERR_FAIL_COND("Attempted to free a NavigationServer RID that did not exist (or was already freed).");
+		ERR_PRINT("Attempted to free a NavigationServer RID that did not exist (or was already freed).");
 	}
 }
 

+ 18 - 5
modules/text_server_adv/text_server_adv.cpp

@@ -4313,7 +4313,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
 					elongation_count++;
 				}
 			}
-			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
 				space_count++;
 			}
 		}
@@ -4330,9 +4330,9 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
 						int count = delta_width_per_kashida / gl.advance;
 						int prev_count = gl.repeat;
 						if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
-							gl.repeat = MAX(count, 0);
+							gl.repeat = CLAMP(count, 0, 255);
 						} else {
-							gl.repeat = MAX(count + 1, 1);
+							gl.repeat = CLAMP(count + 1, 1, 255);
 						}
 						justification_width += (gl.repeat - prev_count) * gl.advance;
 					}
@@ -4346,7 +4346,7 @@ double TextServerAdvanced::_shaped_text_fit_to_width(const RID &p_shaped, double
 		for (int i = start_pos; i <= end_pos; i++) {
 			Glyph &gl = sd->glyphs.write[i];
 			if (gl.count > 0) {
-				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
 					double old_adv = gl.advance;
 					double new_advance;
 					if ((gl.flags & GRAPHEME_IS_VIRTUAL) == GRAPHEME_IS_VIRTUAL) {
@@ -4782,6 +4782,19 @@ bool TextServerAdvanced::_shaped_text_update_breaks(const RID &p_shaped) {
 					gl.font_rid = sd_glyphs[i].font_rid;
 					gl.font_size = sd_glyphs[i].font_size;
 					gl.flags = GRAPHEME_IS_BREAK_SOFT | GRAPHEME_IS_VIRTUAL | GRAPHEME_IS_SPACE;
+					// Mark virtual space after punctuation as punctuation to avoid justification at this point.
+					if (c_punct_size == 0) {
+						if (u_ispunct(c) && c != 0x005f) {
+							gl.flags |= GRAPHEME_IS_PUNCTUATION;
+						}
+					} else {
+						for (int j = 0; j < c_punct_size; j++) {
+							if (c_punct[j] == c) {
+								gl.flags |= GRAPHEME_IS_PUNCTUATION;
+								break;
+							}
+						}
+					}
 					if (sd_glyphs[i].flags & GRAPHEME_IS_RTL) {
 						gl.flags |= GRAPHEME_IS_RTL;
 						for (int j = sd_glyphs[i].count - 1; j >= 0; j--) {
@@ -4993,7 +5006,7 @@ bool TextServerAdvanced::_shaped_text_update_justification_ops(const RID &p_shap
 								}
 							}
 						}
-					} else if ((sd_glyphs[i].flags & GRAPHEME_IS_SPACE) != GRAPHEME_IS_SPACE) {
+					} else if ((sd_glyphs[i].flags & GRAPHEME_IS_SPACE) != GRAPHEME_IS_SPACE && (sd_glyphs[i].flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
 						int count = sd_glyphs[i].count;
 						// Do not add extra spaces at the end of the line.
 						if (sd_glyphs[i].end == sd->end) {

+ 3 - 3
modules/text_server_fb/text_server_fb.cpp

@@ -3262,7 +3262,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
 	for (int i = start_pos; i <= end_pos; i++) {
 		const Glyph &gl = sd->glyphs[i];
 		if (gl.count > 0) {
-			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+			if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
 				space_count++;
 			}
 		}
@@ -3273,7 +3273,7 @@ double TextServerFallback::_shaped_text_fit_to_width(const RID &p_shaped, double
 		for (int i = start_pos; i <= end_pos; i++) {
 			Glyph &gl = sd->glyphs.write[i];
 			if (gl.count > 0) {
-				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE) {
+				if ((gl.flags & GRAPHEME_IS_SPACE) == GRAPHEME_IS_SPACE && (gl.flags & GRAPHEME_IS_PUNCTUATION) != GRAPHEME_IS_PUNCTUATION) {
 					double old_adv = gl.advance;
 					gl.advance = MAX(gl.advance + delta_width_per_space, Math::round(0.1 * gl.font_size));
 					justification_width += (gl.advance - old_adv);
@@ -3372,7 +3372,7 @@ bool TextServerFallback::_shaped_text_update_breaks(const RID &p_shaped) {
 		if (sd_glyphs[i].count > 0) {
 			char32_t c = sd->text[sd_glyphs[i].start - sd->start];
 			if (c_punct_size == 0) {
-				if (is_punct(c) && c != 0x005F) {
+				if (is_punct(c) && c != 0x005F && c != ' ') {
 					sd_glyphs[i].flags |= GRAPHEME_IS_PUNCTUATION;
 				}
 			} else {

+ 1 - 2
platform/linuxbsd/detect.py

@@ -277,11 +277,10 @@ def configure(env: "Environment"):
         env.Prepend(CPPPATH=["/usr/include/recastnavigation"])
         env.Append(LIBS=["Recast"])
 
-    if not env["builtin_embree"]:
+    if not env["builtin_embree"] and env["arch"] in ["x86_64", "arm64"]:
         # No pkgconfig file so far, hardcode expected lib name.
         env.Append(LIBS=["embree3"])
 
-    ## Flags
     if env["fontconfig"]:
         if not env["use_sowrap"]:
             if os.system("pkg-config --exists fontconfig") == 0:  # 0 means found

+ 7 - 0
platform/web/dom_keys.inc

@@ -51,6 +51,7 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
 	DOM2GODOT("Numpad9", KP_9);
 	DOM2GODOT("NumpadAdd", KP_ADD);
 	DOM2GODOT("NumpadBackspace", BACKSPACE);
+	DOM2GODOT("Clear", CLEAR); // NumLock on macOS.
 	DOM2GODOT("NumpadClear", CLEAR);
 	DOM2GODOT("NumpadClearEntry", CLEAR);
 	//DOM2GODOT("NumpadComma", UNKNOWN);
@@ -125,16 +126,22 @@ Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], b
 	DOM2GODOT("Slash", SLASH);
 
 	// Functional keys in the Alphanumeric section.
+	DOM2GODOT("Alt", ALT);
 	DOM2GODOT("AltLeft", ALT);
 	DOM2GODOT("AltRight", ALT);
 	DOM2GODOT("Backspace", BACKSPACE);
 	DOM2GODOT("CapsLock", CAPSLOCK);
 	DOM2GODOT("ContextMenu", MENU);
+	DOM2GODOT("Control", CTRL);
 	DOM2GODOT("ControlLeft", CTRL);
 	DOM2GODOT("ControlRight", CTRL);
 	DOM2GODOT("Enter", ENTER);
+	DOM2GODOT("Meta", META);
 	DOM2GODOT("MetaLeft", META);
 	DOM2GODOT("MetaRight", META);
+	DOM2GODOT("OSLeft", META); // Command on macOS.
+	DOM2GODOT("OSRight", META); // Command on macOS.
+	DOM2GODOT("Shift", SHIFT);
 	DOM2GODOT("ShiftLeft", SHIFT);
 	DOM2GODOT("ShiftRight", SHIFT);
 	DOM2GODOT("Space", SPACE);

+ 11 - 8
platform/windows/display_server_windows.cpp

@@ -3404,16 +3404,8 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 		} break;
 		case WM_SYSKEYUP:
 		case WM_KEYUP:
-			if (windows[window_id].ime_suppress_next_keyup) {
-				windows[window_id].ime_suppress_next_keyup = false;
-				break;
-			}
-			[[fallthrough]];
 		case WM_SYSKEYDOWN:
 		case WM_KEYDOWN: {
-			if (windows[window_id].ime_in_progress) {
-				break;
-			}
 			if (wParam == VK_SHIFT) {
 				shift_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
 			}
@@ -3426,6 +3418,17 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
 					gr_mem = alt_mem;
 				}
 			}
+			if (wParam == VK_LWIN || wParam == VK_RWIN) {
+				meta_mem = (uMsg == WM_KEYDOWN || uMsg == WM_SYSKEYDOWN);
+			}
+
+			if (windows[window_id].ime_suppress_next_keyup && (uMsg == WM_KEYUP || uMsg == WM_SYSKEYUP)) {
+				windows[window_id].ime_suppress_next_keyup = false;
+				break;
+			}
+			if (windows[window_id].ime_in_progress) {
+				break;
+			}
 
 			if (mouse_mode == MOUSE_MODE_CAPTURED) {
 				// When SetCapture is used, ALT+F4 hotkey is ignored by Windows, so handle it ourselves

+ 8 - 0
platform/windows/export/export_plugin.cpp

@@ -879,7 +879,11 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
 	print_line("Creating temporary directory...");
 	ep.step(TTR("Creating temporary directory..."), 2);
 	String temp_dir;
+#ifndef WINDOWS_ENABLED
 	err = ssh_run_on_remote(host, port, extra_args_ssh, "powershell -command \\\"\\$tmp = Join-Path \\$Env:Temp \\$(New-Guid); New-Item -Type Directory -Path \\$tmp | Out-Null; Write-Output \\$tmp\\\"", &temp_dir);
+#else
+	err = ssh_run_on_remote(host, port, extra_args_ssh, "powershell -command \"$tmp = Join-Path $Env:Temp $(New-Guid); New-Item -Type Directory -Path $tmp ^| Out-Null; Write-Output $tmp\"", &temp_dir);
+#endif
 	if (err != OK || temp_dir.is_empty()) {
 		CLEANUP_AND_RETURN(err);
 	}
@@ -891,6 +895,10 @@ Error EditorExportPlatformWindows::run(const Ref<EditorExportPreset> &p_preset,
 		CLEANUP_AND_RETURN(err);
 	}
 
+	if (cmd_args.is_empty()) {
+		cmd_args = " ";
+	}
+
 	{
 		String run_script = p_preset->get("ssh_remote_deploy/run_script");
 		run_script = run_script.replace("{temp_dir}", temp_dir);

+ 6 - 2
platform/windows/os_windows.cpp

@@ -69,6 +69,11 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
 #define WM_POINTERUPDATE 0x0245
 #endif
 
+// Missing in MinGW headers before 8.0.
+#ifndef DWRITE_FONT_WEIGHT_SEMI_LIGHT
+#define DWRITE_FONT_WEIGHT_SEMI_LIGHT (DWRITE_FONT_WEIGHT)350
+#endif
+
 #if defined(__GNUC__)
 // Workaround GCC warning from -Wcast-function-type.
 #define GetProcAddress (void *)GetProcAddress
@@ -673,9 +678,8 @@ Error OS_Windows::execute(const String &p_path, const List<String> &p_arguments,
 		}
 
 		CloseHandle(pipe[0]); // Close pipe read handle.
-	} else {
-		WaitForSingleObject(pi.pi.hProcess, INFINITE);
 	}
+	WaitForSingleObject(pi.pi.hProcess, INFINITE);
 
 	if (r_exitcode) {
 		DWORD ret2;

+ 1 - 0
scene/2d/back_buffer_copy.cpp

@@ -62,6 +62,7 @@ Rect2 BackBufferCopy::get_anchorable_rect() const {
 void BackBufferCopy::set_rect(const Rect2 &p_rect) {
 	rect = p_rect;
 	_update_copy_mode();
+	item_rect_changed();
 }
 
 Rect2 BackBufferCopy::get_rect() const {

+ 7 - 7
scene/2d/camera_2d.cpp

@@ -158,7 +158,7 @@ Transform2D Camera2D::get_camera_transform() {
 			}
 		}
 
-		if (follow_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) {
+		if (position_smoothing_enabled && !Engine::get_singleton()->is_editor_hint()) {
 			real_t c = position_smoothing_speed * (process_callback == CAMERA2D_PROCESS_PHYSICS ? get_physics_process_delta_time() : get_process_delta_time());
 			smoothed_camera_pos = ((camera_pos - smoothed_camera_pos) * c) + smoothed_camera_pos;
 			ret_camera_pos = smoothed_camera_pos;
@@ -186,7 +186,7 @@ Transform2D Camera2D::get_camera_transform() {
 
 	Rect2 screen_rect(-screen_offset + ret_camera_pos, screen_size * zoom_scale);
 
-	if (!follow_smoothing_enabled || !limit_smoothing_enabled) {
+	if (!position_smoothing_enabled || !limit_smoothing_enabled) {
 		if (screen_rect.position.x < limit[SIDE_LEFT]) {
 			screen_rect.position.x = limit[SIDE_LEFT];
 		}
@@ -617,18 +617,18 @@ real_t Camera2D::get_drag_horizontal_offset() const {
 void Camera2D::_set_old_smoothing(real_t p_enable) {
 	//compatibility
 	if (p_enable > 0) {
-		follow_smoothing_enabled = true;
+		position_smoothing_enabled = true;
 		set_position_smoothing_speed(p_enable);
 	}
 }
 
 void Camera2D::set_position_smoothing_enabled(bool p_enabled) {
-	follow_smoothing_enabled = p_enabled;
+	position_smoothing_enabled = p_enabled;
 	notify_property_list_changed();
 }
 
 bool Camera2D::is_position_smoothing_enabled() const {
-	return follow_smoothing_enabled;
+	return position_smoothing_enabled;
 }
 
 void Camera2D::set_custom_viewport(Node *p_viewport) {
@@ -699,7 +699,7 @@ bool Camera2D::is_margin_drawing_enabled() const {
 }
 
 void Camera2D::_validate_property(PropertyInfo &p_property) const {
-	if (!follow_smoothing_enabled && p_property.name == "smoothing_speed") {
+	if (!position_smoothing_enabled && p_property.name == "position_smoothing_speed") {
 		p_property.usage = PROPERTY_USAGE_NO_EDITOR;
 	}
 	if (!rotation_smoothing_enabled && p_property.name == "rotation_smoothing_speed") {
@@ -801,7 +801,7 @@ void Camera2D::_bind_methods() {
 	ADD_PROPERTYI(PropertyInfo(Variant::INT, "limit_bottom", PROPERTY_HINT_NONE, "suffix:px"), "set_limit", "get_limit", SIDE_BOTTOM);
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "limit_smoothed"), "set_limit_smoothing_enabled", "is_limit_smoothing_enabled");
 
-	ADD_GROUP("Follow Smoothing", "follow_smoothing_");
+	ADD_GROUP("Position Smoothing", "position_smoothing_");
 	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "position_smoothing_enabled"), "set_position_smoothing_enabled", "is_position_smoothing_enabled");
 	ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "position_smoothing_speed", PROPERTY_HINT_NONE, "suffix:px/s"), "set_position_smoothing_speed", "get_position_smoothing_speed");
 

+ 1 - 1
scene/2d/camera_2d.h

@@ -67,7 +67,7 @@ protected:
 	bool ignore_rotation = true;
 	bool enabled = true;
 	real_t position_smoothing_speed = 5.0;
-	bool follow_smoothing_enabled = false;
+	bool position_smoothing_enabled = false;
 
 	real_t camera_angle = 0.0;
 	real_t rotation_smoothing_speed = 5.0;

+ 5 - 0
scene/animation/tween.cpp

@@ -489,6 +489,11 @@ Tween::Tween(bool p_valid) {
 }
 
 Ref<PropertyTweener> PropertyTweener::from(Variant p_value) {
+	ERR_FAIL_COND_V(tween.is_null(), nullptr);
+	if (!tween->_validate_type_match(p_value, final_val)) {
+		return nullptr;
+	}
+
 	initial_val = p_value;
 	do_continue = false;
 	return this;

+ 2 - 0
scene/animation/tween.h

@@ -61,6 +61,8 @@ class MethodTweener;
 class Tween : public RefCounted {
 	GDCLASS(Tween, RefCounted);
 
+	friend class PropertyTweener;
+
 public:
 	enum TweenProcessMode {
 		TWEEN_PROCESS_PHYSICS,

+ 6 - 0
scene/gui/code_edit.cpp

@@ -2926,6 +2926,12 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
 	code_completion_options.clear();
 	code_completion_base = string_to_complete;
 
+	/* Don't autocomplete setting numerical values. */
+	if (code_completion_base.is_numeric()) {
+		cancel_code_completion();
+		return;
+	}
+
 	Vector<ScriptLanguage::CodeCompletionOption> completion_options_casei;
 	Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr;
 	Vector<ScriptLanguage::CodeCompletionOption> completion_options_substr_casei;

+ 1 - 1
scene/gui/text_edit.cpp

@@ -1263,7 +1263,7 @@ void TextEdit::_notification(int p_what) {
 							if (draw_tabs && ((glyphs[j].flags & TextServer::GRAPHEME_IS_TAB) == TextServer::GRAPHEME_IS_TAB)) {
 								int yofs = (text_height - tab_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
 								tab_icon->draw(ci, Point2(char_pos, ofs_y + yofs), gl_color);
-							} else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE)) {
+							} else if (draw_spaces && ((glyphs[j].flags & TextServer::GRAPHEME_IS_SPACE) == TextServer::GRAPHEME_IS_SPACE) && ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL)) {
 								int yofs = (text_height - space_icon->get_height()) / 2 - ldata->get_line_ascent(line_wrap_index);
 								int xofs = (glyphs[j].advance * glyphs[j].repeat - space_icon->get_width()) / 2;
 								space_icon->draw(ci, Point2(char_pos + xofs, ofs_y + yofs), gl_color);

+ 6 - 0
scene/resources/font.cpp

@@ -1458,6 +1458,9 @@ Error FontFile::load_bitmap_font(const String &p_path) {
 						}
 						if (ch[i] == 1 && first_ol_ch == -1) {
 							first_ol_ch = i;
+							if (outline == 0) {
+								outline = 1;
+							}
 						}
 						if (ch[i] == 2 && first_cm_ch == -1) {
 							first_cm_ch = i;
@@ -1747,6 +1750,9 @@ Error FontFile::load_bitmap_font(const String &p_path) {
 					}
 					if (ch[i] == 1 && first_ol_ch == -1) {
 						first_ol_ch = i;
+						if (outline == 0) {
+							outline = 1;
+						}
 					}
 					if (ch[i] == 2 && first_cm_ch == -1) {
 						first_cm_ch = i;

+ 6 - 0
scene/resources/texture.cpp

@@ -1232,6 +1232,12 @@ Error ImageTexture3D::create(Image::Format p_format, int p_width, int p_height,
 		texture = tex;
 	}
 
+	format = p_format;
+	width = p_width;
+	height = p_height;
+	depth = p_depth;
+	mipmaps = p_mipmaps;
+
 	return OK;
 }
 

+ 18 - 11
servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp

@@ -1972,22 +1972,25 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 		RD::get_singleton()->draw_list_end();
 		RD::get_singleton()->draw_command_end_label();
 	}
+	if (rb->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
+		RENDER_TIMESTAMP("Resolve MSAA");
 
-	if (!can_continue_color && rb->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
-		// Handle views individual, might want to look at rewriting our resolve to do both layers in one pass.
-		for (uint32_t v = 0; v < rb->get_view_count(); v++) {
-			RD::get_singleton()->texture_resolve_multisample(rb_data->get_color_msaa(v), rb->get_internal_texture(v));
-		}
-		if (using_separate_specular) {
+		if (!can_continue_color) {
+			// Handle views individual, might want to look at rewriting our resolve to do both layers in one pass.
 			for (uint32_t v = 0; v < rb->get_view_count(); v++) {
-				RD::get_singleton()->texture_resolve_multisample(rb_data->get_specular_msaa(v), rb_data->get_specular(v));
+				RD::get_singleton()->texture_resolve_multisample(rb_data->get_color_msaa(v), rb->get_internal_texture(v));
+			}
+			if (using_separate_specular) {
+				for (uint32_t v = 0; v < rb->get_view_count(); v++) {
+					RD::get_singleton()->texture_resolve_multisample(rb_data->get_specular_msaa(v), rb_data->get_specular(v));
+				}
 			}
 		}
-	}
 
-	if (!can_continue_depth && rb->get_msaa_3d() != RS::VIEWPORT_MSAA_DISABLED) {
-		for (uint32_t v = 0; v < rb->get_view_count(); v++) {
-			resolve_effects->resolve_depth(rb_data->get_depth_msaa(v), rb->get_depth_texture(v), rb->get_internal_size(), texture_multisamples[rb->get_msaa_3d()]);
+		if (!can_continue_depth) {
+			for (uint32_t v = 0; v < rb->get_view_count(); v++) {
+				resolve_effects->resolve_depth(rb_data->get_depth_msaa(v), rb->get_depth_texture(v), rb->get_internal_size(), texture_multisamples[rb->get_msaa_3d()]);
+			}
 		}
 	}
 
@@ -2016,11 +2019,15 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co
 	}
 
 	if (scene_state.used_screen_texture) {
+		RENDER_TIMESTAMP("Copy Screen Texture");
+
 		// Copy screen texture to backbuffer so we can read from it
 		_render_buffers_copy_screen_texture(p_render_data);
 	}
 
 	if (scene_state.used_depth_texture) {
+		RENDER_TIMESTAMP("Copy Depth Texture");
+
 		// Copy depth texture to backbuffer so we can read from it
 		_render_buffers_copy_depth_texture(p_render_data);
 	}

+ 9 - 3
servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp

@@ -271,7 +271,12 @@ RID RenderForwardMobile::RenderBufferDataForwardMobile::get_color_fbs(Framebuffe
 			// - add blit to 2D pass
 			RID render_target = render_buffers->get_render_target();
 			ERR_FAIL_COND_V(render_target.is_null(), RID());
-			RID target_buffer = texture_storage->render_target_get_rd_texture(render_target);
+			RID target_buffer;
+			if (texture_storage->render_target_get_msaa(render_target) == RS::VIEWPORT_MSAA_DISABLED) {
+				target_buffer = texture_storage->render_target_get_rd_texture(render_target);
+			} else {
+				target_buffer = texture_storage->render_target_get_rd_texture_msaa(render_target);
+			}
 			ERR_FAIL_COND_V(target_buffer.is_null(), RID());
 
 			int target_buffer_id = textures.size();
@@ -742,14 +747,15 @@ void RenderForwardMobile::_render_scene(RenderDataRD *p_render_data, const Color
 		}
 
 		// We do this last because our get_color_fbs creates and caches the framebuffer if we need it.
-		if (using_subpass_post_process && rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_FOUR_SUBPASSES).is_null()) {
+		RID four_subpasses = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_FOUR_SUBPASSES);
+		if (using_subpass_post_process && four_subpasses.is_null()) {
 			// can't do blit subpass because we don't have all subpasses
 			using_subpass_post_process = false;
 		}
 
 		if (using_subpass_post_process) {
 			// all as subpasses
-			framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_FOUR_SUBPASSES);
+			framebuffer = four_subpasses;
 		} else if (using_subpass_transparent) {
 			// our tonemap pass is separate
 			framebuffer = rb_data->get_color_fbs(RenderBufferDataForwardMobile::FB_CONFIG_THREE_SUBPASSES);

+ 7 - 0
servers/rendering/renderer_rd/storage_rd/texture_storage.cpp

@@ -2911,6 +2911,13 @@ RID TextureStorage::render_target_get_rd_texture_slice(RID p_render_target, uint
 	}
 }
 
+RID TextureStorage::render_target_get_rd_texture_msaa(RID p_render_target) {
+	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
+	ERR_FAIL_COND_V(!rt, RID());
+
+	return rt->color_multisample;
+}
+
 RID TextureStorage::render_target_get_rd_backbuffer(RID p_render_target) {
 	RenderTarget *rt = render_target_owner.get_or_null(p_render_target);
 	ERR_FAIL_COND_V(!rt, RID());

+ 1 - 0
servers/rendering/renderer_rd/storage_rd/texture_storage.h

@@ -741,6 +741,7 @@ public:
 	RID render_target_get_rd_framebuffer(RID p_render_target);
 	RID render_target_get_rd_texture(RID p_render_target);
 	RID render_target_get_rd_texture_slice(RID p_render_target, uint32_t p_layer);
+	RID render_target_get_rd_texture_msaa(RID p_render_target);
 	RID render_target_get_rd_backbuffer(RID p_render_target);
 	RID render_target_get_rd_backbuffer_framebuffer(RID p_render_target);
 

+ 8 - 0
servers/rendering/shader_language.cpp

@@ -8798,11 +8798,19 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
 										_set_error(RTR("'hint_normal_roughness_texture' is not supported in gl_compatibility shaders."));
 										return ERR_PARSE_ERROR;
 									}
+									if (String(shader_type_identifier) != "spatial") {
+										_set_error(vformat(RTR("'hint_normal_roughness_texture' is not supported in '%s' shaders."), shader_type_identifier));
+										return ERR_PARSE_ERROR;
+									}
 								} break;
 								case TK_HINT_DEPTH_TEXTURE: {
 									new_hint = ShaderNode::Uniform::HINT_DEPTH_TEXTURE;
 									--texture_uniforms;
 									--texture_binding;
+									if (String(shader_type_identifier) != "spatial") {
+										_set_error(vformat(RTR("'hint_depth_texture' is not supported in '%s' shaders."), shader_type_identifier));
+										return ERR_PARSE_ERROR;
+									}
 								} break;
 								case TK_FILTER_NEAREST: {
 									new_filter = FILTER_NEAREST;