1/**************************************************************************/
2/* project_converter_3_to_4.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "project_converter_3_to_4.h"
32
33#ifndef DISABLE_DEPRECATED
34
35#include "modules/modules_enabled.gen.h" // For regex.
36
37#ifdef MODULE_REGEX_ENABLED
38
39#include "core/error/error_macros.h"
40#include "core/io/dir_access.h"
41#include "core/io/file_access.h"
42#include "core/object/ref_counted.h"
43#include "core/os/time.h"
44#include "core/templates/hash_map.h"
45#include "core/templates/list.h"
46#include "editor/renames_map_3_to_4.h"
47#include "modules/regex/regex.h"
48
49// Find "OS.set_property(x)", capturing x into $1.
50static String make_regex_gds_os_property_set(String name_set) {
51 return String("\\bOS\\.") + name_set + "\\s*\\((.*)\\)";
52}
53// Find "OS.property = x", capturing x into $1 or $2.
54static String make_regex_gds_os_property_assign(String name) {
55 return String("\\bOS\\.") + name + "\\s*=\\s*([^#]+)";
56}
57// Find "OS.property" OR "OS.get_property()" / "OS.is_property()".
58static String make_regex_gds_os_property_get(String name, String get) {
59 return String("\\bOS\\.(") + get + "_)?" + name + "(\\s*\\(\\s*\\))?";
60}
61
62class ProjectConverter3To4::RegExContainer {
63public:
64 // Custom GDScript.
65 RegEx reg_is_empty = RegEx("\\bempty\\(");
66 RegEx reg_super = RegEx("([\t ])\\.([a-zA-Z_])");
67 RegEx reg_json_to = RegEx("\\bto_json\\b");
68 RegEx reg_json_parse = RegEx("([\t ]{0,})([^\n]+)parse_json\\(([^\n]+)");
69 RegEx reg_json_non_new = RegEx("([\t ]{0,})([^\n]+)JSON\\.parse\\(([^\n]+)");
70 RegEx reg_json_print = RegEx("\\bJSON\\b\\.print\\(");
71 RegEx reg_export_simple = RegEx("export\\(([a-zA-Z0-9_]+)\\)[ ]+var[ ]+([a-zA-Z0-9_]+)");
72 RegEx reg_export_typed = RegEx("export\\(([a-zA-Z0-9_]+)\\)[ ]+var[ ]+([a-zA-Z0-9_]+)[ ]*:[ ]*[a-zA-Z0-9_]+");
73 RegEx reg_export_inferred_type = RegEx("export\\([a-zA-Z0-9_]+\\)[ ]+var[ ]+([a-zA-Z0-9_]+)[ ]*:[ ]*=");
74 RegEx reg_export_advanced = RegEx("export\\(([^)^\n]+)\\)[ ]+var[ ]+([a-zA-Z0-9_]+)([^\n]+)");
75 RegEx reg_setget_setget = RegEx("var[ ]+([a-zA-Z0-9_]+)([^\n]+?)[ \t]*setget[ \t]+([a-zA-Z0-9_]+)[ \t]*,[ \t]*([a-zA-Z0-9_]+)");
76 RegEx reg_setget_set = RegEx("var[ ]+([a-zA-Z0-9_]+)([^\n]+?)[ \t]*setget[ \t]+([a-zA-Z0-9_]+)[ \t]*[,]*[^\n]*$");
77 RegEx reg_setget_get = RegEx("var[ ]+([a-zA-Z0-9_]+)([^\n]+?)[ \t]*setget[ \t]+,[ \t]*([a-zA-Z0-9_]+)[ \t]*$");
78 RegEx reg_join = RegEx("([\\(\\)a-zA-Z0-9_]+)\\.join\\(([^\n^\\)]+)\\)");
79 RegEx reg_image_lock = RegEx("([a-zA-Z0-9_\\.]+)\\.lock\\(\\)");
80 RegEx reg_image_unlock = RegEx("([a-zA-Z0-9_\\.]+)\\.unlock\\(\\)");
81 RegEx reg_instantiate = RegEx("\\.instance\\(([^\\)]*)\\)");
82 // Simple OS properties with getters/setters.
83 RegEx reg_os_current_screen = RegEx("\\bOS\\.((set_|get_)?)current_screen\\b");
84 RegEx reg_os_min_window_size = RegEx("\\bOS\\.((set_|get_)?)min_window_size\\b");
85 RegEx reg_os_max_window_size = RegEx("\\bOS\\.((set_|get_)?)max_window_size\\b");
86 RegEx reg_os_window_position = RegEx("\\bOS\\.((set_|get_)?)window_position\\b");
87 RegEx reg_os_window_size = RegEx("\\bOS\\.((set_|get_)?)window_size\\b");
88 RegEx reg_os_getset_screen_orient = RegEx("\\bOS\\.(s|g)et_screen_orientation\\b");
89 // OS property getters/setters for non trivial replacements.
90 RegEx reg_os_set_window_resizable = RegEx(make_regex_gds_os_property_set("set_window_resizable"));
91 RegEx reg_os_assign_window_resizable = RegEx(make_regex_gds_os_property_assign("window_resizable"));
92 RegEx reg_os_is_window_resizable = RegEx(make_regex_gds_os_property_get("window_resizable", "is"));
93 RegEx reg_os_set_fullscreen = RegEx(make_regex_gds_os_property_set("set_window_fullscreen"));
94 RegEx reg_os_assign_fullscreen = RegEx(make_regex_gds_os_property_assign("window_fullscreen"));
95 RegEx reg_os_is_fullscreen = RegEx(make_regex_gds_os_property_get("window_fullscreen", "is"));
96 RegEx reg_os_set_maximized = RegEx(make_regex_gds_os_property_set("set_window_maximized"));
97 RegEx reg_os_assign_maximized = RegEx(make_regex_gds_os_property_assign("window_maximized"));
98 RegEx reg_os_is_maximized = RegEx(make_regex_gds_os_property_get("window_maximized", "is"));
99 RegEx reg_os_set_minimized = RegEx(make_regex_gds_os_property_set("set_window_minimized"));
100 RegEx reg_os_assign_minimized = RegEx(make_regex_gds_os_property_assign("window_minimized"));
101 RegEx reg_os_is_minimized = RegEx(make_regex_gds_os_property_get("window_minimized", "is"));
102 RegEx reg_os_set_vsync = RegEx(make_regex_gds_os_property_set("set_use_vsync"));
103 RegEx reg_os_assign_vsync = RegEx(make_regex_gds_os_property_assign("vsync_enabled"));
104 RegEx reg_os_is_vsync = RegEx(make_regex_gds_os_property_get("vsync_enabled", "is"));
105 // OS properties specific cases & specific replacements.
106 RegEx reg_os_assign_screen_orient = RegEx("^(\\s*)OS\\.screen_orientation\\s*=\\s*([^#]+)"); // $1 - indent, $2 - value
107 RegEx reg_os_set_always_on_top = RegEx(make_regex_gds_os_property_set("set_window_always_on_top"));
108 RegEx reg_os_is_always_on_top = RegEx("\\bOS\\.is_window_always_on_top\\s*\\(.*\\)");
109 RegEx reg_os_set_borderless = RegEx(make_regex_gds_os_property_set("set_borderless_window"));
110 RegEx reg_os_get_borderless = RegEx("\\bOS\\.get_borderless_window\\s*\\(\\s*\\)");
111 RegEx reg_os_screen_orient_enum = RegEx("\\bOS\\.SCREEN_ORIENTATION_(\\w+)\\b"); // $1 - constant suffix
112
113 // GDScript keywords.
114 RegEx keyword_gdscript_tool = RegEx("^tool");
115 RegEx keyword_gdscript_export_single = RegEx("^export");
116 RegEx keyword_gdscript_export_mutli = RegEx("([\t]+)export\\b");
117 RegEx keyword_gdscript_onready = RegEx("^onready");
118 RegEx keyword_gdscript_remote = RegEx("^remote func");
119 RegEx keyword_gdscript_remotesync = RegEx("^remotesync func");
120 RegEx keyword_gdscript_sync = RegEx("^sync func");
121 RegEx keyword_gdscript_slave = RegEx("^slave func");
122 RegEx keyword_gdscript_puppet = RegEx("^puppet func");
123 RegEx keyword_gdscript_puppetsync = RegEx("^puppetsync func");
124 RegEx keyword_gdscript_master = RegEx("^master func");
125 RegEx keyword_gdscript_mastersync = RegEx("^mastersync func");
126
127 RegEx gdscript_comment = RegEx("^\\s*#");
128 RegEx csharp_comment = RegEx("^\\s*\\/\\/");
129
130 // CSharp keywords.
131 RegEx keyword_csharp_remote = RegEx("\\[Remote(Attribute)?(\\(\\))?\\]");
132 RegEx keyword_csharp_remotesync = RegEx("\\[(Remote)?Sync(Attribute)?(\\(\\))?\\]");
133 RegEx keyword_csharp_puppet = RegEx("\\[(Puppet|Slave)(Attribute)?(\\(\\))?\\]");
134 RegEx keyword_csharp_puppetsync = RegEx("\\[PuppetSync(Attribute)?(\\(\\))?\\]");
135 RegEx keyword_csharp_master = RegEx("\\[Master(Attribute)?(\\(\\))?\\]");
136 RegEx keyword_csharp_mastersync = RegEx("\\[MasterSync(Attribute)?(\\(\\))?\\]");
137
138 // Colors.
139 LocalVector<RegEx *> color_regexes;
140 LocalVector<String> color_renamed;
141
142 // Classes.
143 LocalVector<RegEx *> class_tscn_regexes;
144 LocalVector<RegEx *> class_gd_regexes;
145 LocalVector<RegEx *> class_shader_regexes;
146
147 // Keycode.
148 RegEx input_map_keycode = RegEx("\\b,\"((physical_)?)scancode\":(\\d+)\\b");
149
150 // Button index and joypad axis.
151 RegEx joypad_button_index = RegEx("\\b,\"button_index\":(\\d+),(\"pressure\":\\d+\\.\\d+,\"pressed\":(false|true))\\b");
152 RegEx joypad_axis = RegEx("\\b,\"axis\":(\\d+)\\b");
153
154 // Index represents Godot 3's value, entry represents Godot 4 value equivalency.
155 // i.e: Button4(L1 - Godot3) -> joypad_button_mappings[4]=9 -> Button9(L1 - Godot4).
156 int joypad_button_mappings[23] = { 0, 1, 2, 3, 9, 10, -1 /*L2*/, -1 /*R2*/, 7, 8, 4, 6, 11, 12, 13, 14, 5, 15, 16, 17, 18, 19, 20 };
157 // Entries for L2 and R2 are -1 since they match to joypad axes and no longer to joypad buttons in Godot 4.
158
159 LocalVector<RegEx *> class_regexes;
160
161 RegEx class_temp_tscn = RegEx("\\bTEMP_RENAMED_CLASS.tscn\\b");
162 RegEx class_temp_gd = RegEx("\\bTEMP_RENAMED_CLASS.gd\\b");
163 RegEx class_temp_shader = RegEx("\\bTEMP_RENAMED_CLASS.shader\\b");
164
165 LocalVector<String> class_temp_tscn_renames;
166 LocalVector<String> class_temp_gd_renames;
167 LocalVector<String> class_temp_shader_renames;
168
169 // Common.
170 LocalVector<RegEx *> enum_regexes;
171 LocalVector<RegEx *> gdscript_function_regexes;
172 LocalVector<RegEx *> project_settings_regexes;
173 LocalVector<RegEx *> project_godot_regexes;
174 LocalVector<RegEx *> input_map_regexes;
175 LocalVector<RegEx *> gdscript_properties_regexes;
176 LocalVector<RegEx *> gdscript_signals_regexes;
177 LocalVector<RegEx *> shaders_regexes;
178 LocalVector<RegEx *> builtin_types_regexes;
179 LocalVector<RegEx *> theme_override_regexes;
180 LocalVector<RegEx *> csharp_function_regexes;
181 LocalVector<RegEx *> csharp_properties_regexes;
182 LocalVector<RegEx *> csharp_signal_regexes;
183
184 RegExContainer() {
185 // Common.
186 {
187 // Enum.
188 for (unsigned int current_index = 0; RenamesMap3To4::enum_renames[current_index][0]; current_index++) {
189 enum_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::enum_renames[current_index][0] + "\\b")));
190 }
191 // GDScript functions.
192 for (unsigned int current_index = 0; RenamesMap3To4::gdscript_function_renames[current_index][0]; current_index++) {
193 gdscript_function_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::gdscript_function_renames[current_index][0] + "\\b")));
194 }
195 // Project Settings in scripts.
196 for (unsigned int current_index = 0; RenamesMap3To4::project_settings_renames[current_index][0]; current_index++) {
197 project_settings_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::project_settings_renames[current_index][0] + "\\b")));
198 }
199 // Project Settings in project.godot.
200 for (unsigned int current_index = 0; RenamesMap3To4::project_godot_renames[current_index][0]; current_index++) {
201 project_godot_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::project_godot_renames[current_index][0] + "\\b")));
202 }
203 // Input Map.
204 for (unsigned int current_index = 0; RenamesMap3To4::input_map_renames[current_index][0]; current_index++) {
205 input_map_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::input_map_renames[current_index][0] + "\\b")));
206 }
207 // GDScript properties.
208 for (unsigned int current_index = 0; RenamesMap3To4::gdscript_properties_renames[current_index][0]; current_index++) {
209 gdscript_properties_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::gdscript_properties_renames[current_index][0] + "\\b")));
210 }
211 // GDScript Signals.
212 for (unsigned int current_index = 0; RenamesMap3To4::gdscript_signals_renames[current_index][0]; current_index++) {
213 gdscript_signals_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::gdscript_signals_renames[current_index][0] + "\\b")));
214 }
215 // Shaders.
216 for (unsigned int current_index = 0; RenamesMap3To4::shaders_renames[current_index][0]; current_index++) {
217 shaders_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::shaders_renames[current_index][0] + "\\b")));
218 }
219 // Builtin types.
220 for (unsigned int current_index = 0; RenamesMap3To4::builtin_types_renames[current_index][0]; current_index++) {
221 builtin_types_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::builtin_types_renames[current_index][0] + "\\b")));
222 }
223 // Theme overrides.
224 for (unsigned int current_index = 0; RenamesMap3To4::theme_override_renames[current_index][0]; current_index++) {
225 theme_override_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::theme_override_renames[current_index][0] + "\\b")));
226 }
227 // CSharp function renames.
228 for (unsigned int current_index = 0; RenamesMap3To4::csharp_function_renames[current_index][0]; current_index++) {
229 csharp_function_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::csharp_function_renames[current_index][0] + "\\b")));
230 }
231 // CSharp properties renames.
232 for (unsigned int current_index = 0; RenamesMap3To4::csharp_properties_renames[current_index][0]; current_index++) {
233 csharp_properties_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::csharp_properties_renames[current_index][0] + "\\b")));
234 }
235 // CSharp signals renames.
236 for (unsigned int current_index = 0; RenamesMap3To4::csharp_signals_renames[current_index][0]; current_index++) {
237 csharp_signal_regexes.push_back(memnew(RegEx(String("\\b") + RenamesMap3To4::csharp_signals_renames[current_index][0] + "\\b")));
238 }
239 }
240
241 // Colors.
242 {
243 for (unsigned int current_index = 0; RenamesMap3To4::color_renames[current_index][0]; current_index++) {
244 color_regexes.push_back(memnew(RegEx(String("\\bColor.") + RenamesMap3To4::color_renames[current_index][0] + "\\b")));
245 color_renamed.push_back(String("Color.") + RenamesMap3To4::color_renames[current_index][1]);
246 }
247 }
248 // Classes.
249 {
250 for (unsigned int current_index = 0; RenamesMap3To4::class_renames[current_index][0]; current_index++) {
251 const String class_name = RenamesMap3To4::class_renames[current_index][0];
252 class_tscn_regexes.push_back(memnew(RegEx(String("\\b") + class_name + ".tscn\\b")));
253 class_gd_regexes.push_back(memnew(RegEx(String("\\b") + class_name + ".gd\\b")));
254 class_shader_regexes.push_back(memnew(RegEx(String("\\b") + class_name + ".shader\\b")));
255
256 class_regexes.push_back(memnew(RegEx(String("\\b") + class_name + "\\b")));
257
258 class_temp_tscn_renames.push_back(class_name + ".tscn");
259 class_temp_gd_renames.push_back(class_name + ".gd");
260 class_temp_shader_renames.push_back(class_name + ".shader");
261 }
262 }
263 }
264 ~RegExContainer() {
265 for (RegEx *regex : color_regexes) {
266 memdelete(regex);
267 }
268 for (unsigned int i = 0; i < class_tscn_regexes.size(); i++) {
269 memdelete(class_tscn_regexes[i]);
270 memdelete(class_gd_regexes[i]);
271 memdelete(class_shader_regexes[i]);
272 memdelete(class_regexes[i]);
273 }
274 for (RegEx *regex : enum_regexes) {
275 memdelete(regex);
276 }
277 for (RegEx *regex : gdscript_function_regexes) {
278 memdelete(regex);
279 }
280 for (RegEx *regex : project_settings_regexes) {
281 memdelete(regex);
282 }
283 for (RegEx *regex : project_godot_regexes) {
284 memdelete(regex);
285 }
286 for (RegEx *regex : input_map_regexes) {
287 memdelete(regex);
288 }
289 for (RegEx *regex : gdscript_properties_regexes) {
290 memdelete(regex);
291 }
292 for (RegEx *regex : gdscript_signals_regexes) {
293 memdelete(regex);
294 }
295 for (RegEx *regex : shaders_regexes) {
296 memdelete(regex);
297 }
298 for (RegEx *regex : builtin_types_regexes) {
299 memdelete(regex);
300 }
301 for (RegEx *regex : theme_override_regexes) {
302 memdelete(regex);
303 }
304 for (RegEx *regex : csharp_function_regexes) {
305 memdelete(regex);
306 }
307 for (RegEx *regex : csharp_properties_regexes) {
308 memdelete(regex);
309 }
310 for (RegEx *regex : csharp_signal_regexes) {
311 memdelete(regex);
312 }
313 }
314};
315
316ProjectConverter3To4::ProjectConverter3To4(int p_maximum_file_size_kb, int p_maximum_line_length) {
317 maximum_file_size = p_maximum_file_size_kb * 1024;
318 maximum_line_length = p_maximum_line_length;
319}
320
321// Function responsible for converting project.
322bool ProjectConverter3To4::convert() {
323 print_line("Starting conversion.");
324 uint64_t conversion_start_time = Time::get_singleton()->get_ticks_msec();
325
326 RegExContainer reg_container = RegExContainer();
327
328 int cached_maximum_line_length = maximum_line_length;
329 maximum_line_length = 10000; // Use only for tests bigger value, to not break them.
330
331 ERR_FAIL_COND_V_MSG(!test_array_names(), false, "Cannot start converting due to problems with data in arrays.");
332 ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Aborting conversion due to validation tests failing");
333
334 maximum_line_length = cached_maximum_line_length;
335
336 // Checking if folder contains valid Godot 3 project.
337 // Project should not be converted more than once.
338 {
339 String converter_text = "; Project was converted by built-in tool to Godot 4";
340
341 ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current working directory doesn't contain a \"project.godot\" file for a Godot 3 project.");
342
343 Error err = OK;
344 String project_godot_content = FileAccess::get_file_as_string("project.godot", &err);
345
346 ERR_FAIL_COND_V_MSG(err != OK, false, "Unable to read \"project.godot\".");
347 ERR_FAIL_COND_V_MSG(project_godot_content.contains(converter_text), false, "Project was already converted with this tool.");
348
349 Ref<FileAccess> file = FileAccess::open("project.godot", FileAccess::WRITE);
350 ERR_FAIL_COND_V_MSG(file.is_null(), false, "Unable to open \"project.godot\".");
351
352 file->store_string(converter_text + "\n" + project_godot_content);
353 }
354
355 Vector<String> collected_files = check_for_files();
356
357 uint32_t converted_files = 0;
358
359 // Check file by file.
360 for (int i = 0; i < collected_files.size(); i++) {
361 String file_name = collected_files[i];
362 Vector<SourceLine> source_lines;
363 uint32_t ignored_lines = 0;
364 {
365 Ref<FileAccess> file = FileAccess::open(file_name, FileAccess::READ);
366 ERR_CONTINUE_MSG(file.is_null(), vformat("Unable to read content of \"%s\".", file_name));
367 while (!file->eof_reached()) {
368 String line = file->get_line();
369
370 SourceLine source_line;
371 source_line.line = line;
372 source_line.is_comment = reg_container.gdscript_comment.search_all(line).size() > 0 || reg_container.csharp_comment.search_all(line).size() > 0;
373 source_lines.append(source_line);
374 }
375 }
376 String file_content_before = collect_string_from_vector(source_lines);
377 uint64_t hash_before = file_content_before.hash();
378 uint64_t file_size = file_content_before.size();
379 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));
380
381 Vector<String> reason;
382 bool is_ignored = false;
383 uint64_t start_time = Time::get_singleton()->get_ticks_msec();
384
385 if (file_name.ends_with(".shader")) {
386 DirAccess::remove_file_or_error(file_name.trim_prefix("res://"));
387 file_name = file_name.replace(".shader", ".gdshader");
388 }
389
390 if (file_size < uint64_t(maximum_file_size)) {
391 // ".tscn" must work exactly the same as ".gd" files because they may contain built-in Scripts.
392 if (file_name.ends_with(".gd")) {
393 fix_tool_declaration(source_lines, reg_container);
394
395 rename_classes(source_lines, reg_container); // Using only specialized function.
396
397 rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, source_lines);
398 rename_colors(source_lines, reg_container); // Require to additional rename.
399
400 rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, source_lines);
401 rename_gdscript_functions(source_lines, reg_container, false); // Require to additional rename.
402
403 rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
404 rename_gdscript_keywords(source_lines, reg_container, false);
405 rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
406 rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
407 rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
408 rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
409 rename_common(RenamesMap3To4::theme_override_renames, reg_container.theme_override_regexes, source_lines);
410
411 custom_rename(source_lines, "\\.shader", ".gdshader");
412 } else if (file_name.ends_with(".tscn")) {
413 fix_pause_mode(source_lines, reg_container);
414
415 rename_classes(source_lines, reg_container); // Using only specialized function.
416
417 rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, source_lines);
418 rename_colors(source_lines, reg_container); // Require to do additional renames.
419
420 rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, source_lines);
421 rename_gdscript_functions(source_lines, reg_container, true); // Require to do additional renames.
422
423 rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, source_lines);
424 rename_gdscript_keywords(source_lines, reg_container, true);
425 rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, source_lines);
426 rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, source_lines);
427 rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
428 rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
429 rename_common(RenamesMap3To4::theme_override_renames, reg_container.theme_override_regexes, source_lines);
430
431 custom_rename(source_lines, "\\.shader", ".gdshader");
432 } else if (file_name.ends_with(".cs")) { // TODO, C# should use different methods.
433 rename_classes(source_lines, reg_container); // Using only specialized function.
434 rename_common(RenamesMap3To4::csharp_function_renames, reg_container.csharp_function_regexes, source_lines);
435 rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
436 rename_common(RenamesMap3To4::csharp_properties_renames, reg_container.csharp_properties_regexes, source_lines);
437 rename_common(RenamesMap3To4::csharp_signals_renames, reg_container.csharp_signal_regexes, source_lines);
438 rename_csharp_functions(source_lines, reg_container);
439 rename_csharp_attributes(source_lines, reg_container);
440 custom_rename(source_lines, "public class ", "public partial class ");
441 } else if (file_name.ends_with(".gdshader") || file_name.ends_with(".shader")) {
442 rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
443 } else if (file_name.ends_with("tres")) {
444 rename_classes(source_lines, reg_container); // Using only specialized function.
445
446 rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, source_lines);
447 rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
448
449 custom_rename(source_lines, "\\.shader", ".gdshader");
450 } else if (file_name.ends_with("project.godot")) {
451 rename_common(RenamesMap3To4::project_godot_renames, reg_container.project_godot_regexes, source_lines);
452 rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, source_lines);
453 rename_input_map_scancode(source_lines, reg_container);
454 rename_joypad_buttons_and_axes(source_lines, reg_container);
455 rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, source_lines);
456 custom_rename(source_lines, "config_version=4", "config_version=5");
457 } else if (file_name.ends_with(".csproj")) {
458 // TODO
459 } else if (file_name.ends_with(".import")) {
460 for (SourceLine &source_line : source_lines) {
461 String &line = source_line.line;
462 if (line.contains("nodes/root_type=\"Spatial\"")) {
463 line = "nodes/root_type=\"Node3D\"";
464 } else if (line == "importer=\"ogg_vorbis\"") {
465 line = "importer=\"oggvorbisstr\"";
466 }
467 }
468 } else {
469 ERR_PRINT(file_name + " is not supported!");
470 continue;
471 }
472
473 for (SourceLine &source_line : source_lines) {
474 if (source_line.is_comment) {
475 continue;
476 }
477
478 String &line = source_line.line;
479 if (uint64_t(line.length()) > maximum_line_length) {
480 ignored_lines += 1;
481 }
482 }
483 } else {
484 reason.append(vformat(" ERROR: File has exceeded the maximum size allowed - %d KB", maximum_file_size / 1024));
485 is_ignored = true;
486 }
487
488 uint64_t end_time = Time::get_singleton()->get_ticks_msec();
489 if (is_ignored) {
490 String end_message = vformat(" Checking file took %d ms.", end_time - start_time);
491 print_line(end_message);
492 } else {
493 String file_content_after = collect_string_from_vector(source_lines);
494 uint64_t hash_after = file_content_after.hash64();
495 // Don't need to save file without any changes.
496 // Save if this is a shader, because it was renamed.
497 if (hash_before != hash_after || file_name.ends_with(".gdshader")) {
498 converted_files++;
499
500 Ref<FileAccess> file = FileAccess::open(file_name, FileAccess::WRITE);
501 ERR_CONTINUE_MSG(file.is_null(), vformat("Unable to apply changes to \"%s\", no writing access.", file_name));
502 file->store_string(file_content_after);
503 reason.append(vformat(" File was changed, conversion took %d ms.", end_time - start_time));
504 } else {
505 reason.append(vformat(" File was left unchanged, checking took %d ms.", end_time - start_time));
506 }
507 if (ignored_lines != 0) {
508 reason.append(vformat(" Ignored %d lines, because their length exceeds maximum allowed characters - %d.", ignored_lines, maximum_line_length));
509 }
510 }
511 for (int k = 0; k < reason.size(); k++) {
512 print_line(reason[k]);
513 }
514 }
515 print_line(vformat("Conversion ended - all files(%d), converted files: (%d), not converted files: (%d).", collected_files.size(), converted_files, collected_files.size() - converted_files));
516 uint64_t conversion_end_time = Time::get_singleton()->get_ticks_msec();
517 print_line(vformat("Conversion of all files took %10.3f seconds.", (conversion_end_time - conversion_start_time) / 1000.0));
518 return true;
519}
520
521// Function responsible for validating project conversion.
522bool ProjectConverter3To4::validate_conversion() {
523 print_line("Starting checking if project conversion can be done.");
524 uint64_t conversion_start_time = Time::get_singleton()->get_ticks_msec();
525
526 RegExContainer reg_container = RegExContainer();
527
528 int cached_maximum_line_length = maximum_line_length;
529 maximum_line_length = 10000; // To avoid breaking the tests, only use this for the their larger value.
530
531 ERR_FAIL_COND_V_MSG(!test_array_names(), false, "Cannot start converting due to problems with data in arrays.");
532 ERR_FAIL_COND_V_MSG(!test_conversion(reg_container), false, "Aborting conversion due to validation tests failing");
533
534 maximum_line_length = cached_maximum_line_length;
535
536 // Checking if folder contains valid Godot 3 project.
537 // Project should not be converted more than once.
538 {
539 String conventer_text = "; Project was converted by built-in tool to Godot 4";
540
541 ERR_FAIL_COND_V_MSG(!FileAccess::exists("project.godot"), false, "Current directory doesn't contain any Godot 3 project");
542
543 Error err = OK;
544 String project_godot_content = FileAccess::get_file_as_string("project.godot", &err);
545
546 ERR_FAIL_COND_V_MSG(err != OK, false, "Failed to read content of \"project.godot\" file.");
547 ERR_FAIL_COND_V_MSG(project_godot_content.contains(conventer_text), false, "Project already was converted with this tool.");
548 }
549
550 Vector<String> collected_files = check_for_files();
551
552 uint32_t converted_files = 0;
553
554 // Check file by file.
555 for (int i = 0; i < collected_files.size(); i++) {
556 String file_name = collected_files[i];
557 Vector<String> lines;
558 uint32_t ignored_lines = 0;
559 uint64_t file_size = 0;
560 {
561 Ref<FileAccess> file = FileAccess::open(file_name, FileAccess::READ);
562 ERR_CONTINUE_MSG(file.is_null(), vformat("Unable to read content of \"%s\".", file_name));
563 while (!file->eof_reached()) {
564 String line = file->get_line();
565 file_size += line.size();
566 lines.append(line);
567 }
568 }
569 print_line(vformat("Checking for conversion - %d/%d file - \"%s\" with size - %d KB", i + 1, collected_files.size(), file_name.trim_prefix("res://"), file_size / 1024));
570
571 Vector<String> changed_elements;
572 Vector<String> reason;
573 bool is_ignored = false;
574 uint64_t start_time = Time::get_singleton()->get_ticks_msec();
575
576 if (file_name.ends_with(".shader")) {
577 reason.append("\tFile extension will be renamed from \"shader\" to \"gdshader\".");
578 }
579
580 if (file_size < uint64_t(maximum_file_size)) {
581 if (file_name.ends_with(".gd")) {
582 changed_elements.append_array(check_for_rename_classes(lines, reg_container));
583
584 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, lines));
585 changed_elements.append_array(check_for_rename_colors(lines, reg_container));
586
587 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, lines));
588 changed_elements.append_array(check_for_rename_gdscript_functions(lines, reg_container, false));
589
590 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines));
591 changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container, false));
592 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines));
593 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines));
594 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
595 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
596 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::theme_override_renames, reg_container.theme_override_regexes, lines));
597
598 changed_elements.append_array(check_for_custom_rename(lines, "\\.shader", ".gdshader"));
599 } else if (file_name.ends_with(".tscn")) {
600 changed_elements.append_array(check_for_rename_classes(lines, reg_container));
601
602 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::enum_renames, reg_container.enum_regexes, lines));
603 changed_elements.append_array(check_for_rename_colors(lines, reg_container));
604
605 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, lines));
606 changed_elements.append_array(check_for_rename_gdscript_functions(lines, reg_container, true));
607
608 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, lines));
609 changed_elements.append_array(check_for_rename_gdscript_keywords(lines, reg_container, true));
610 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, lines));
611 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, lines));
612 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
613 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
614 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::theme_override_renames, reg_container.theme_override_regexes, lines));
615
616 changed_elements.append_array(check_for_custom_rename(lines, "\\.shader", ".gdshader"));
617 } else if (file_name.ends_with(".cs")) {
618 changed_elements.append_array(check_for_rename_classes(lines, reg_container));
619 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::csharp_function_renames, reg_container.csharp_function_regexes, lines));
620 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
621 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::csharp_properties_renames, reg_container.csharp_properties_regexes, lines));
622 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::csharp_signals_renames, reg_container.csharp_signal_regexes, lines));
623 changed_elements.append_array(check_for_rename_csharp_functions(lines, reg_container));
624 changed_elements.append_array(check_for_rename_csharp_attributes(lines, reg_container));
625 changed_elements.append_array(check_for_custom_rename(lines, "public class ", "public partial class "));
626 } else if (file_name.ends_with(".gdshader") || file_name.ends_with(".shader")) {
627 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
628 } else if (file_name.ends_with("tres")) {
629 changed_elements.append_array(check_for_rename_classes(lines, reg_container));
630
631 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, lines));
632 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
633
634 changed_elements.append_array(check_for_custom_rename(lines, "\\.shader", ".gdshader"));
635 } else if (file_name.ends_with("project.godot")) {
636 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::project_godot_renames, reg_container.project_godot_regexes, lines));
637 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, lines));
638 changed_elements.append_array(check_for_rename_input_map_scancode(lines, reg_container));
639 changed_elements.append_array(check_for_rename_joypad_buttons_and_axes(lines, reg_container));
640 changed_elements.append_array(check_for_rename_common(RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, lines));
641 } else if (file_name.ends_with(".csproj")) {
642 // TODO
643 } else {
644 ERR_PRINT(vformat("\"%s\", is not supported!", file_name));
645 continue;
646 }
647
648 for (String &line : lines) {
649 if (uint64_t(line.length()) > maximum_line_length) {
650 ignored_lines += 1;
651 }
652 }
653 } else {
654 reason.append(vformat("\tERROR: File has exceeded the maximum size allowed - %d KB.", maximum_file_size / 1024));
655 is_ignored = true;
656 }
657
658 uint64_t end_time = Time::get_singleton()->get_ticks_msec();
659 String end_message = vformat(" Checking file took %10.3f ms.", (end_time - start_time) / 1000.0);
660 if (ignored_lines != 0) {
661 end_message += vformat(" Ignored %d lines, because their length exceeds maximum allowed characters - %d.", ignored_lines, maximum_line_length);
662 }
663 print_line(end_message);
664
665 for (int k = 0; k < reason.size(); k++) {
666 print_line(reason[k]);
667 }
668
669 if (changed_elements.size() > 0 && !is_ignored) {
670 converted_files++;
671
672 for (int k = 0; k < changed_elements.size(); k++) {
673 print_line(String("\t\t") + changed_elements[k]);
674 }
675 }
676 }
677
678 print_line(vformat("Checking for valid conversion ended - all files(%d), files which would be converted(%d), files which would not be converted(%d).", collected_files.size(), converted_files, collected_files.size() - converted_files));
679 uint64_t conversion_end_time = Time::get_singleton()->get_ticks_msec();
680 print_line(vformat("Conversion of all files took %10.3f seconds.", (conversion_end_time - conversion_start_time) / 1000.0));
681 return true;
682}
683
684// Collect files which will be checked, excluding ".txt", ".mp4", ".wav" etc. files.
685Vector<String> ProjectConverter3To4::check_for_files() {
686 Vector<String> collected_files = Vector<String>();
687
688 Vector<String> directories_to_check = Vector<String>();
689 directories_to_check.push_back("res://");
690
691 while (!directories_to_check.is_empty()) {
692 String path = directories_to_check.get(directories_to_check.size() - 1); // Is there any pop_back function?
693 directories_to_check.resize(directories_to_check.size() - 1); // Remove last element
694
695 Ref<DirAccess> dir = DirAccess::open(path);
696 if (dir.is_valid()) {
697 dir->set_include_hidden(true);
698 dir->list_dir_begin();
699 String current_dir = dir->get_current_dir();
700 String file_name = dir->_get_next();
701
702 while (file_name != "") {
703 if (file_name == ".git" || file_name == ".godot") {
704 file_name = dir->_get_next();
705 continue;
706 }
707 if (dir->current_is_dir()) {
708 directories_to_check.append(current_dir.path_join(file_name) + "/");
709 } else {
710 bool proper_extension = false;
711 if (file_name.ends_with(".gd") || file_name.ends_with(".shader") || file_name.ends_with(".gdshader") || file_name.ends_with(".tscn") || file_name.ends_with(".tres") || file_name.ends_with(".godot") || file_name.ends_with(".cs") || file_name.ends_with(".csproj") || file_name.ends_with(".import"))
712 proper_extension = true;
713
714 if (proper_extension) {
715 collected_files.append(current_dir.path_join(file_name));
716 }
717 }
718 file_name = dir->_get_next();
719 }
720 } else {
721 print_verbose("Failed to open " + path);
722 }
723 }
724 return collected_files;
725}
726
727Vector<SourceLine> ProjectConverter3To4::split_lines(const String &text) {
728 Vector<String> lines = text.split("\n");
729 Vector<SourceLine> source_lines;
730 for (String &line : lines) {
731 SourceLine source_line;
732 source_line.line = line;
733 source_line.is_comment = false;
734
735 source_lines.append(source_line);
736 }
737 return source_lines;
738}
739
740// Test expected results of gdscript
741bool 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) {
742 Vector<SourceLine> got = split_lines(name);
743
744 (this->*func)(got, reg_container, builtin_script);
745 String got_str = collect_string_from_vector(got);
746 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));
747
748 return true;
749}
750
751bool ProjectConverter3To4::test_conversion_with_regex(String name, String expected, void (ProjectConverter3To4::*func)(Vector<SourceLine> &, const RegExContainer &), String what, const RegExContainer &reg_container) {
752 Vector<SourceLine> got = split_lines(name);
753
754 (this->*func)(got, reg_container);
755 String got_str = collect_string_from_vector(got);
756 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));
757
758 return true;
759}
760
761bool ProjectConverter3To4::test_conversion_basic(String name, String expected, const char *array[][2], LocalVector<RegEx *> &regex_cache, String what) {
762 Vector<SourceLine> got = split_lines(name);
763
764 rename_common(array, regex_cache, got);
765 String got_str = collect_string_from_vector(got);
766 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));
767
768 return true;
769}
770
771// Validate if conversions are proper.
772bool ProjectConverter3To4::test_conversion(RegExContainer &reg_container) {
773 bool valid = true;
774
775 valid = valid && test_conversion_with_regex("tool", "@tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
776 valid = valid && test_conversion_with_regex("\n tool", "\n tool", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
777 valid = valid && test_conversion_with_regex("\n\ntool", "@tool\n\n", &ProjectConverter3To4::fix_tool_declaration, "gdscript keyword", reg_container);
778
779 valid = valid && test_conversion_with_regex("pause_mode = 2", "pause_mode = 3", &ProjectConverter3To4::fix_pause_mode, "pause_mode", reg_container);
780 valid = valid && test_conversion_with_regex("pause_mode = 1", "pause_mode = 1", &ProjectConverter3To4::fix_pause_mode, "pause_mode", reg_container);
781 valid = valid && test_conversion_with_regex("pause_mode = 3", "pause_mode = 3", &ProjectConverter3To4::fix_pause_mode, "pause_mode", reg_container);
782 valid = valid && test_conversion_with_regex("somepause_mode = 2", "somepause_mode = 2", &ProjectConverter3To4::fix_pause_mode, "pause_mode", reg_container);
783 valid = valid && test_conversion_with_regex("pause_mode_ext = 2", "pause_mode_ext = 2", &ProjectConverter3To4::fix_pause_mode, "pause_mode", reg_container);
784
785 valid = valid && test_conversion_basic("TYPE_REAL", "TYPE_FLOAT", RenamesMap3To4::enum_renames, reg_container.enum_regexes, "enum");
786
787 valid = valid && test_conversion_basic("can_instance", "can_instantiate", RenamesMap3To4::gdscript_function_renames, reg_container.gdscript_function_regexes, "gdscript function");
788
789 valid = valid && test_conversion_basic("CanInstance", "CanInstantiate", RenamesMap3To4::csharp_function_renames, reg_container.csharp_function_regexes, "csharp function");
790
791 valid = valid && test_conversion_basic("translation", "position", RenamesMap3To4::gdscript_properties_renames, reg_container.gdscript_properties_regexes, "gdscript property");
792
793 valid = valid && test_conversion_basic("Translation", "Position", RenamesMap3To4::csharp_properties_renames, reg_container.csharp_properties_regexes, "csharp property");
794
795 valid = valid && test_conversion_basic("NORMALMAP", "NORMAL_MAP", RenamesMap3To4::shaders_renames, reg_container.shaders_regexes, "shader");
796
797 valid = valid && test_conversion_basic("text_entered", "text_submitted", RenamesMap3To4::gdscript_signals_renames, reg_container.gdscript_signals_regexes, "gdscript signal");
798
799 valid = valid && test_conversion_basic("TextEntered", "TextSubmitted", RenamesMap3To4::csharp_signals_renames, reg_container.csharp_signal_regexes, "csharp signal");
800
801 valid = valid && test_conversion_basic("audio/channel_disable_threshold_db", "audio/buses/channel_disable_threshold_db", RenamesMap3To4::project_settings_renames, reg_container.project_settings_regexes, "project setting");
802
803 valid = valid && test_conversion_basic("\"device\":-1,\"alt\":false,\"shift\":false,\"control\":false,\"meta\":false,\"doubleclick\":false,\"scancode\":0,\"physical_scancode\":16777254,\"script\":null", "\"device\":-1,\"alt_pressed\":false,\"shift_pressed\":false,\"ctrl_pressed\":false,\"meta_pressed\":false,\"double_click\":false,\"keycode\":0,\"physical_keycode\":16777254,\"script\":null", RenamesMap3To4::input_map_renames, reg_container.input_map_regexes, "input map");
804
805 valid = valid && test_conversion_basic("Transform", "Transform3D", RenamesMap3To4::builtin_types_renames, reg_container.builtin_types_regexes, "builtin type");
806
807 valid = valid && test_conversion_basic("custom_constants/margin_right", "theme_override_constants/margin_right", RenamesMap3To4::theme_override_renames, reg_container.theme_override_regexes, "theme overrides");
808
809 // Custom Renames.
810
811 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);
812 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);
813 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);
814
815 valid = valid && test_conversion_with_regex("[Remote]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
816 valid = valid && test_conversion_with_regex("[RemoteSync]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
817 valid = valid && test_conversion_with_regex("[Sync]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
818 valid = valid && test_conversion_with_regex("[Slave]", "[RPC]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
819 valid = valid && test_conversion_with_regex("[Puppet]", "[RPC]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
820 valid = valid && test_conversion_with_regex("[PuppetSync]", "[RPC(CallLocal = true)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
821 valid = valid && test_conversion_with_regex("[Master]", "The master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using Multiplayer.GetRemoteSenderId()\n[RPC]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
822 valid = valid && test_conversion_with_regex("[MasterSync]", "The master and mastersync rpc behavior is not officially supported anymore. Try using another keyword or making custom logic using Multiplayer.GetRemoteSenderId()\n[RPC(CallLocal = true)]", &ProjectConverter3To4::rename_csharp_attributes, "custom rename csharp", reg_container);
823
824 valid = valid && test_conversion_gdscript_builtin("\tif OS.window_resizable: pass", "\tif (not get_window().unresizable): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
825 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_window_resizable(): pass", "\tif (not get_window().unresizable): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
826 valid = valid && test_conversion_gdscript_builtin("\tOS.set_window_resizable(Settings.resizable)", "\tget_window().unresizable = not (Settings.resizable)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
827 valid = valid && test_conversion_gdscript_builtin("\tOS.window_resizable = Settings.resizable", "\tget_window().unresizable = not (Settings.resizable)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
828
829 valid = valid && test_conversion_gdscript_builtin("\tif OS.window_fullscreen: pass", "\tif ((get_window().mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (get_window().mode == Window.MODE_FULLSCREEN)): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
830 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_window_fullscreen(): pass", "\tif ((get_window().mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (get_window().mode == Window.MODE_FULLSCREEN)): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
831 valid = valid && test_conversion_gdscript_builtin("\tOS.set_window_fullscreen(Settings.fullscreen)", "\tget_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (Settings.fullscreen) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
832 valid = valid && test_conversion_gdscript_builtin("\tOS.window_fullscreen = Settings.fullscreen", "\tget_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (Settings.fullscreen) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
833
834 valid = valid && test_conversion_gdscript_builtin("\tif OS.window_maximized: pass", "\tif (get_window().mode == Window.MODE_MAXIMIZED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
835 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_window_maximized(): pass", "\tif (get_window().mode == Window.MODE_MAXIMIZED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
836 valid = valid && test_conversion_gdscript_builtin("\tOS.set_window_maximized(Settings.maximized)", "\tget_window().mode = Window.MODE_MAXIMIZED if (Settings.maximized) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
837 valid = valid && test_conversion_gdscript_builtin("\tOS.window_maximized = Settings.maximized", "\tget_window().mode = Window.MODE_MAXIMIZED if (Settings.maximized) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
838
839 valid = valid && test_conversion_gdscript_builtin("\tif OS.window_minimized: pass", "\tif (get_window().mode == Window.MODE_MINIMIZED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
840 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_window_minimized(): pass", "\tif (get_window().mode == Window.MODE_MINIMIZED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
841 valid = valid && test_conversion_gdscript_builtin("\tOS.set_window_minimized(Settings.minimized)", "\tget_window().mode = Window.MODE_MINIMIZED if (Settings.minimized) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
842 valid = valid && test_conversion_gdscript_builtin("\tOS.window_minimized = Settings.minimized", "\tget_window().mode = Window.MODE_MINIMIZED if (Settings.minimized) else Window.MODE_WINDOWED", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
843
844 valid = valid && test_conversion_gdscript_builtin("\tif OS.vsync_enabled: pass", "\tif (DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
845 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_vsync_enabled(): pass", "\tif (DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED): pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
846 valid = valid && test_conversion_gdscript_builtin("\tOS.set_use_vsync(Settings.vsync)", "\tDisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if (Settings.vsync) else DisplayServer.VSYNC_DISABLED)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
847 valid = valid && test_conversion_gdscript_builtin("\tOS.vsync_enabled = Settings.vsync", "\tDisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if (Settings.vsync) else DisplayServer.VSYNC_DISABLED)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
848
849 valid = valid && test_conversion_gdscript_builtin("\tif OS.screen_orientation = OS.SCREEN_ORIENTATION_VERTICAL: pass", "\tif DisplayServer.screen_get_orientation() = DisplayServer.SCREEN_VERTICAL: pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
850 valid = valid && test_conversion_gdscript_builtin("\tif OS.get_screen_orientation() = OS.SCREEN_ORIENTATION_LANDSCAPE: pass", "\tif DisplayServer.screen_get_orientation() = DisplayServer.SCREEN_LANDSCAPE: pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
851 valid = valid && test_conversion_gdscript_builtin("\tOS.set_screen_orientation(Settings.orient)", "\tDisplayServer.screen_set_orientation(Settings.orient)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
852 valid = valid && test_conversion_gdscript_builtin("\tOS.screen_orientation = Settings.orient", "\tDisplayServer.screen_set_orientation(Settings.orient)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
853
854 valid = valid && test_conversion_gdscript_builtin("\tif OS.is_window_always_on_top(): pass", "\tif get_window().always_on_top: pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
855 valid = valid && test_conversion_gdscript_builtin("\tOS.set_window_always_on_top(Settings.alwaystop)", "\tget_window().always_on_top = (Settings.alwaystop)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
856
857 valid = valid && test_conversion_gdscript_builtin("\tif OS.get_borderless_window(): pass", "\tif get_window().borderless: pass", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
858 valid = valid && test_conversion_gdscript_builtin("\tOS.set_borderless_window(Settings.borderless)", "\tget_window().borderless = (Settings.borderless)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
859
860 valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide( a, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
861 valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide( a, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
862 valid = valid && test_conversion_gdscript_builtin("\tvar aa = roman(r.move_and_slide_with_snap( a, g, b, c, d, e, f )) # Roman", "\tr.set_velocity(a)\n\t# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `g`\n\tr.set_up_direction(b)\n\tr.set_floor_stop_on_slope_enabled(c)\n\tr.set_max_slides(d)\n\tr.set_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tr.move_and_slide()\n\tvar aa = roman(r.velocity) # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
863 valid = valid && test_conversion_gdscript_builtin("\tmove_and_slide_with_snap( a, g, b, c, d, e, f ) # Roman", "\tset_velocity(a)\n\t# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `g`\n\tset_up_direction(b)\n\tset_floor_stop_on_slope_enabled(c)\n\tset_max_slides(d)\n\tset_floor_max_angle(e)\n\t# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `f`\n\tmove_and_slide() # Roman", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
864
865 valid = valid && test_conversion_gdscript_builtin("remove_and_slide(a,b,c,d,e,f)", "remove_and_slide(a,b,c,d,e,f)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
866
867 valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a , b )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
868 valid = valid && test_conversion_gdscript_builtin("list_dir_begin( a )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
869 valid = valid && test_conversion_gdscript_builtin("list_dir_begin( )", "list_dir_begin() # TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
870
871 valid = valid && test_conversion_gdscript_builtin("sort_custom( a , b )", "sort_custom(Callable(a, b))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
872
873 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);
874
875 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);
876
877 valid = valid && test_conversion_gdscript_builtin("\timage.lock()", "\tfalse # image.lock() # TODOConverter3To4, 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);
878 valid = valid && test_conversion_gdscript_builtin("\timage.unlock()", "\tfalse # image.unlock() # TODOConverter3To4, 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);
879 valid = valid && test_conversion_gdscript_builtin("\troman.image.unlock()", "\tfalse # roman.image.unlock() # TODOConverter3To4, 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);
880 valid = valid && test_conversion_gdscript_builtin("\tmtx.lock()", "\tmtx.lock()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
881 valid = valid && test_conversion_gdscript_builtin("\tmutex.unlock()", "\tmutex.unlock()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
882
883 valid = valid && test_conversion_with_regex("extends CSGBox", "extends CSGBox3D", &ProjectConverter3To4::rename_classes, "classes", reg_container);
884 valid = valid && test_conversion_with_regex("CSGBox", "CSGBox3D", &ProjectConverter3To4::rename_classes, "classes", reg_container);
885 valid = valid && test_conversion_with_regex("Spatial", "Node3D", &ProjectConverter3To4::rename_classes, "classes", reg_container);
886 valid = valid && test_conversion_with_regex("Spatial.tscn", "Spatial.tscn", &ProjectConverter3To4::rename_classes, "classes", reg_container);
887 valid = valid && test_conversion_with_regex("Spatial.gd", "Spatial.gd", &ProjectConverter3To4::rename_classes, "classes", reg_container);
888 valid = valid && test_conversion_with_regex("Spatial.shader", "Spatial.shader", &ProjectConverter3To4::rename_classes, "classes", reg_container);
889 valid = valid && test_conversion_with_regex("Spatial.other", "Node3D.other", &ProjectConverter3To4::rename_classes, "classes", reg_container);
890
891 valid = valid && test_conversion_gdscript_builtin("\nonready", "\n@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
892 valid = valid && test_conversion_gdscript_builtin("onready", "@onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
893 valid = valid && test_conversion_gdscript_builtin(" onready", " onready", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
894 valid = valid && test_conversion_gdscript_builtin("\nexport", "\n@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
895 valid = valid && test_conversion_gdscript_builtin("\texport", "\t@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
896 valid = valid && test_conversion_gdscript_builtin("\texport_dialog", "\texport_dialog", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
897 valid = valid && test_conversion_gdscript_builtin("export", "@export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
898 valid = valid && test_conversion_gdscript_builtin(" export", " export", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
899 valid = valid && test_conversion_gdscript_builtin("\n\nremote func", "\n\n@rpc(\"any_peer\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
900 valid = valid && test_conversion_gdscript_builtin("\n\nremote func", "\n\n@rpc(\\\"any_peer\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
901 valid = valid && test_conversion_gdscript_builtin("\n\nremotesync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
902 valid = valid && test_conversion_gdscript_builtin("\n\nremotesync func", "\n\n@rpc(\\\"any_peer\\\", \\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
903 valid = valid && test_conversion_gdscript_builtin("\n\nsync func", "\n\n@rpc(\"any_peer\", \"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
904 valid = valid && test_conversion_gdscript_builtin("\n\nsync func", "\n\n@rpc(\\\"any_peer\\\", \\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
905 valid = valid && test_conversion_gdscript_builtin("\n\nslave func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
906 valid = valid && test_conversion_gdscript_builtin("\n\npuppet func", "\n\n@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
907 valid = valid && test_conversion_gdscript_builtin("\n\npuppetsync func", "\n\n@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
908 valid = valid && test_conversion_gdscript_builtin("\n\npuppetsync func", "\n\n@rpc(\\\"call_local\\\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, true);
909 valid = valid && test_conversion_gdscript_builtin("\n\nmaster func", "\n\nThe 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@rpc func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
910 valid = valid && test_conversion_gdscript_builtin("\n\nmastersync func", "\n\nThe 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@rpc(\"call_local\") func", &ProjectConverter3To4::rename_gdscript_keywords, "gdscript keyword", reg_container, false);
911
912 valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget set_function, get_function", "var size: Vector2 = Vector2(): get = get_function, set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
913 valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget set_function, ", "var size: Vector2 = Vector2(): set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
914 valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget set_function", "var size: Vector2 = Vector2(): set = set_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
915 valid = valid && test_conversion_gdscript_builtin("var size: Vector2 = Vector2() setget , get_function", "var size: Vector2 = Vector2(): get = get_function", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
916
917 valid = valid && test_conversion_gdscript_builtin("get_node(@", "get_node(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
918
919 valid = valid && test_conversion_gdscript_builtin("yield(this, \"timeout\")", "await this.timeout", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
920 valid = valid && test_conversion_gdscript_builtin("yield(this, \\\"timeout\\\")", "await this.timeout", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, true);
921
922 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);
923 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);
924
925 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);
926 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
927 valid = valid && test_conversion_gdscript_builtin("export(PackedScene) var mob_scene", "export var mob_scene: PackedScene", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
928 valid = valid && test_conversion_gdscript_builtin("export(float) var lifetime: float = 3.0", "export var lifetime: float = 3.0", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
929 valid = valid && test_conversion_gdscript_builtin("export var lifetime: float = 3.0", "export var lifetime: float = 3.0", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
930 valid = valid && test_conversion_gdscript_builtin("export var lifetime := 3.0", "export var lifetime := 3.0", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
931 valid = valid && test_conversion_gdscript_builtin("export(float) var lifetime := 3.0", "export var lifetime := 3.0", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
932
933 valid = valid && test_conversion_gdscript_builtin("var d = parse_json(roman(sfs))", "var test_json_conv = JSON.new()\ntest_json_conv.parse(roman(sfs))\nvar d = test_json_conv.get_data()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
934
935 valid = valid && test_conversion_gdscript_builtin("to_json( AA ) szon", "JSON.new().stringify( AA ) szon", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
936 valid = valid && test_conversion_gdscript_builtin("s to_json", "s JSON.new().stringify", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
937 valid = valid && test_conversion_gdscript_builtin("AF to_json2", "AF to_json2", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
938 valid = valid && test_conversion_gdscript_builtin("var rr = JSON.parse(a)", "var test_json_conv = JSON.new()\ntest_json_conv.parse(a)\nvar rr = test_json_conv.get_data()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
939
940 valid = valid && test_conversion_gdscript_builtin("empty()", "is_empty()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
941 valid = valid && test_conversion_gdscript_builtin(".empty", ".empty", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
942
943 valid = valid && test_conversion_gdscript_builtin(").roman(", ").roman(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
944 valid = valid && test_conversion_gdscript_builtin("\t.roman(", "\tsuper.roman(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
945 valid = valid && test_conversion_gdscript_builtin(" .roman(", " super.roman(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
946 valid = valid && test_conversion_gdscript_builtin(".1", ".1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
947 valid = valid && test_conversion_gdscript_builtin(" .1", " .1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
948 valid = valid && test_conversion_gdscript_builtin("'.'", "'.'", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
949 valid = valid && test_conversion_gdscript_builtin("'.a'", "'.a'", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
950 valid = valid && test_conversion_gdscript_builtin("\t._input(_event)", "\tsuper._input(_event)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
951
952 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);
953 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);
954 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);
955 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);
956 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);
957 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);
958
959 valid = valid && test_conversion_gdscript_builtin(".connect(A,B,C)", ".connect(A, Callable(B, C))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
960 valid = valid && test_conversion_gdscript_builtin("abc.connect(A,B,C)", "abc.connect(A, Callable(B, C))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
961 valid = valid && test_conversion_gdscript_builtin("\tconnect(A,B,C)", "\tconnect(A, Callable(B, C))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
962 valid = valid && test_conversion_gdscript_builtin(" connect(A,B,C)", " connect(A, Callable(B, C))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
963 valid = valid && test_conversion_gdscript_builtin("_connect(A,B,C)", "_connect(A,B,C)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
964 valid = valid && test_conversion_gdscript_builtin("do_connect(A,B,C)", "do_connect(A,B,C)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
965 valid = valid && test_conversion_gdscript_builtin("$connect(A,B,C)", "$connect(A,B,C)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
966 valid = valid && test_conversion_gdscript_builtin("@connect(A,B,C)", "@connect(A,B,C)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
967
968 valid = valid && test_conversion_gdscript_builtin("(start(A,B) != OK):", "(start(Callable(A, B)) != OK):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
969 valid = valid && test_conversion_gdscript_builtin("func start(A,B):", "func start(A,B):", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
970 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);
971 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);
972 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);
973 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);
974
975 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);
976 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);
977 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);
978 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);
979
980 valid = valid && test_conversion_gdscript_builtin("func _init(", "func _init(", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
981 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);
982 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);
983 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);
984 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);
985 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);
986
987 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);
988 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);
989
990 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);
991 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);
992 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);
993 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);
994 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);
995
996 valid = valid && test_conversion_gdscript_builtin("PackedStringArray(req_godot).join('.')", "'.'.join(PackedStringArray(req_godot))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
997 valid = valid && test_conversion_gdscript_builtin("=PackedStringArray(req_godot).join('.')", "='.'.join(PackedStringArray(req_godot))", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
998
999 valid = valid && test_conversion_gdscript_builtin("apply_force(position, impulse)", "apply_force(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1000 valid = valid && test_conversion_gdscript_builtin("apply_impulse(position, impulse)", "apply_impulse(impulse, position)", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1001 valid = valid && test_conversion_gdscript_builtin("draw_rect(a,b,c,d,e).abc", "draw_rect(a, b, c, d).abc# e) TODOConverter3To4 Antialiasing argument is missing", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1002 valid = valid && test_conversion_gdscript_builtin("get_focus_owner()", "get_viewport().gui_get_focus_owner()", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1003 valid = valid && test_conversion_gdscript_builtin("button.pressed = 1", "button.button_pressed = 1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1004 valid = valid && test_conversion_gdscript_builtin("button.pressed=1", "button.button_pressed=1", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1005 valid = valid && test_conversion_gdscript_builtin("button.pressed SF", "button.pressed SF", &ProjectConverter3To4::rename_gdscript_functions, "custom rename", reg_container, false);
1006
1007 valid = valid && test_conversion_with_regex("AAA Color.white AF", "AAA Color.WHITE AF", &ProjectConverter3To4::rename_colors, "custom rename", reg_container);
1008
1009 // Note: Do not change to *scancode*, it is applied before that conversion.
1010 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);
1011 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);
1012
1013 valid = valid && test_conversion_with_regex("\"device\":0,\"button_index\":5,\"pressure\":0.0,\"pressed\":false,", "\"device\":0,\"button_index\":10,\"pressure\":0.0,\"pressed\":false,", &ProjectConverter3To4::rename_joypad_buttons_and_axes, "custom rename", reg_container);
1014 valid = valid && test_conversion_with_regex("\"device\":0,\"axis\":6,", "\"device\":0,\"axis\":4,", &ProjectConverter3To4::rename_joypad_buttons_and_axes, "custom rename", reg_container);
1015 valid = valid && test_conversion_with_regex("InputEventJoypadButton,\"button_index\":7,\"pressure\":0.0,\"pressed\":false,\"script\":null", "InputEventJoypadMotion,\"axis\":5,\"axis_value\":1.0,\"script\":null", &ProjectConverter3To4::rename_joypad_buttons_and_axes, "custom rename", reg_container);
1016
1017 // Custom rule conversion
1018 {
1019 String from = "instance";
1020 String to = "instantiate";
1021 String name = "AA.instance()";
1022
1023 Vector<SourceLine> got = split_lines(name);
1024
1025 String expected = "AA.instantiate()";
1026 custom_rename(got, from, to);
1027 String got_str = collect_string_from_vector(got);
1028 if (got_str != expected) {
1029 ERR_PRINT(vformat("Failed to convert custom rename \"%s\" to \"%s\", got \"%s\", instead.", name, expected, got_str));
1030 }
1031 valid = valid && (got_str == expected);
1032 }
1033
1034 // get_object_of_execution
1035 {
1036 { String base = "var roman = kieliszek.";
1037 String expected = "kieliszek.";
1038 String got = get_object_of_execution(base);
1039 if (got != expected) {
1040 ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size()));
1041 }
1042 valid = valid && (got == expected);
1043}
1044{
1045 String base = "r.";
1046 String expected = "r.";
1047 String got = get_object_of_execution(base);
1048 if (got != expected) {
1049 ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size()));
1050 }
1051 valid = valid && (got == expected);
1052}
1053{
1054 String base = "mortadela(";
1055 String expected = "";
1056 String got = get_object_of_execution(base);
1057 if (got != expected) {
1058 ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size()));
1059 }
1060 valid = valid && (got == expected);
1061}
1062{
1063 String base = "var node = $world/ukraine/lviv.";
1064 String expected = "$world/ukraine/lviv.";
1065 String got = get_object_of_execution(base);
1066 if (got != expected) {
1067 ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size()));
1068 }
1069 valid = valid && (got == expected);
1070}
1071}
1072// get_starting_space
1073{
1074 String base = "\t\t\t var roman = kieliszek.";
1075 String expected = "\t\t\t";
1076 String got = get_starting_space(base);
1077 if (got != expected) {
1078 ERR_PRINT(vformat("Failed to get proper data from get_object_of_execution. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", base, expected, expected.size(), got, got.size()));
1079 }
1080 valid = valid && (got == expected);
1081}
1082// Parse Arguments
1083{
1084 String line = "( )";
1085 Vector<String> got_vector = parse_arguments(line);
1086 String got = "";
1087 String expected = "";
1088 for (String &part : got_vector) {
1089 got += part + "|||";
1090 }
1091 if (got != expected) {
1092 ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size()));
1093 }
1094 valid = valid && (got == expected);
1095}
1096{
1097 String line = "(a , b , c)";
1098 Vector<String> got_vector = parse_arguments(line);
1099 String got = "";
1100 String expected = "a|||b|||c|||";
1101 for (String &part : got_vector) {
1102 got += part + "|||";
1103 }
1104 if (got != expected) {
1105 ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size()));
1106 }
1107 valid = valid && (got == expected);
1108}
1109{
1110 String line = "(a , \"b,\" , c)";
1111 Vector<String> got_vector = parse_arguments(line);
1112 String got = "";
1113 String expected = "a|||\"b,\"|||c|||";
1114 for (String &part : got_vector) {
1115 got += part + "|||";
1116 }
1117 if (got != expected) {
1118 ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size()));
1119 }
1120 valid = valid && (got == expected);
1121}
1122{
1123 String line = "(a , \"(,),,,,\" , c)";
1124 Vector<String> got_vector = parse_arguments(line);
1125 String got = "";
1126 String expected = "a|||\"(,),,,,\"|||c|||";
1127 for (String &part : got_vector) {
1128 got += part + "|||";
1129 }
1130 if (got != expected) {
1131 ERR_PRINT(vformat("Failed to get proper data from parse_arguments. \"%s\" should return \"%s\"(%d), got \"%s\"(%d), instead.", line, expected, expected.size(), got, got.size()));
1132 }
1133 valid = valid && (got == expected);
1134}
1135
1136return valid;
1137}
1138
1139// Validate in all arrays if names don't do cyclic renames "Node" -> "Node2D" | "Node2D" -> "2DNode"
1140bool ProjectConverter3To4::test_array_names() {
1141 bool valid = true;
1142 Vector<String> names = Vector<String>();
1143
1144 // Validate if all classes are valid.
1145 {
1146 for (unsigned int current_index = 0; RenamesMap3To4::class_renames[current_index][0]; current_index++) {
1147 const String old_class = RenamesMap3To4::class_renames[current_index][0];
1148 const String new_class = RenamesMap3To4::class_renames[current_index][1];
1149
1150 // Light2D, Texture, Viewport are special classes(probably virtual ones).
1151 if (ClassDB::class_exists(StringName(old_class)) && old_class != "Light2D" && old_class != "Texture" && old_class != "Viewport") {
1152 ERR_PRINT(vformat("Class \"%s\" exists in Godot 4, so it cannot be renamed to something else.", old_class));
1153 valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
1154 }
1155
1156 // Callable is special class, to which normal classes may be renamed.
1157 if (!ClassDB::class_exists(StringName(new_class)) && new_class != "Callable") {
1158 ERR_PRINT(vformat("Class \"%s\" does not exist in Godot 4, so it cannot be used in the conversion.", old_class));
1159 valid = false; // This probably should be only a warning, but not 100% sure - this would need to be added to CI.
1160 }
1161 }
1162 }
1163
1164 {
1165 HashSet<String> all_functions;
1166
1167 // List of excluded functions from builtin types and global namespace, because currently it is not possible to get list of functions from them.
1168 // This will be available when https://github.com/godotengine/godot/pull/49053 or similar will be included into Godot.
1169 static const char *builtin_types_excluded_functions[] = { "dict_to_inst", "inst_to_dict", "bytes_to_var", "bytes_to_var_with_objects", "db_to_linear", "deg_to_rad", "linear_to_db", "rad_to_deg", "randf_range", "snapped", "str_to_var", "var_to_str", "var_to_bytes", "var_to_bytes_with_objects", "move_toward", "uri_encode", "uri_decode", "remove_at", "get_rotation_quaternion", "limit_length", "grow_side", "is_absolute_path", "is_valid_int", "lerp", "to_ascii_buffer", "to_utf8_buffer", "to_utf32_buffer", "to_wchar_buffer", "snapped", "remap", "rfind", nullptr };
1170 for (int current_index = 0; builtin_types_excluded_functions[current_index]; current_index++) {
1171 all_functions.insert(builtin_types_excluded_functions[current_index]);
1172 }
1173
1174 //for (int type = Variant::Type::NIL + 1; type < Variant::Type::VARIANT_MAX; type++) {
1175 // List<MethodInfo> method_list;
1176 // Variant::get_method_list_by_type(&method_list, Variant::Type(type));
1177 // for (MethodInfo &function_data : method_list) {
1178 // if (!all_functions.has(function_data.name)) {
1179 // all_functions.insert(function_data.name);
1180 // }
1181 // }
1182 //}
1183
1184 List<StringName> classes_list;
1185 ClassDB::get_class_list(&classes_list);
1186 for (StringName &name_of_class : classes_list) {
1187 List<MethodInfo> method_list;
1188 ClassDB::get_method_list(name_of_class, &method_list, true);
1189 for (MethodInfo &function_data : method_list) {
1190 if (!all_functions.has(function_data.name)) {
1191 all_functions.insert(function_data.name);
1192 }
1193 }
1194 }
1195
1196 int current_element = 0;
1197 while (RenamesMap3To4::gdscript_function_renames[current_element][0] != nullptr) {
1198 String name_3_x = RenamesMap3To4::gdscript_function_renames[current_element][0];
1199 String name_4_0 = RenamesMap3To4::gdscript_function_renames[current_element][1];
1200 if (!all_functions.has(name_4_0)) {
1201 ERR_PRINT(vformat("Missing GDScript function in pair (%s - ===> %s <===)", name_3_x, name_4_0));
1202 valid = false;
1203 }
1204 current_element++;
1205 }
1206 }
1207 if (!valid) {
1208 ERR_PRINT("Found function which is used in the converter, but it cannot be found in Godot 4. Rename this element or remove its entry if it's obsolete.");
1209 }
1210
1211 valid = valid && test_single_array(RenamesMap3To4::enum_renames);
1212 valid = valid && test_single_array(RenamesMap3To4::class_renames, true);
1213 valid = valid && test_single_array(RenamesMap3To4::gdscript_function_renames, true);
1214 valid = valid && test_single_array(RenamesMap3To4::csharp_function_renames, true);
1215 valid = valid && test_single_array(RenamesMap3To4::gdscript_properties_renames, true);
1216 valid = valid && test_single_array(RenamesMap3To4::csharp_properties_renames, true);
1217 valid = valid && test_single_array(RenamesMap3To4::shaders_renames, true);
1218 valid = valid && test_single_array(RenamesMap3To4::gdscript_signals_renames);
1219 valid = valid && test_single_array(RenamesMap3To4::project_settings_renames);
1220 valid = valid && test_single_array(RenamesMap3To4::project_godot_renames);
1221 valid = valid && test_single_array(RenamesMap3To4::input_map_renames);
1222 valid = valid && test_single_array(RenamesMap3To4::builtin_types_renames);
1223 valid = valid && test_single_array(RenamesMap3To4::color_renames);
1224
1225 return valid;
1226}
1227
1228// Validates the array to prevent cyclic renames, such as `Node` -> `Node2D`, then `Node2D` -> `2DNode`.
1229// Also checks if names contain leading or trailing spaces.
1230bool ProjectConverter3To4::test_single_array(const char *p_array[][2], bool p_ignore_4_0_name) {
1231 bool valid = true;
1232 Vector<String> names = Vector<String>();
1233
1234 for (unsigned int current_index = 0; p_array[current_index][0]; current_index++) {
1235 String name_3_x = p_array[current_index][0];
1236 String name_4_0 = p_array[current_index][1];
1237 if (name_3_x != name_3_x.strip_edges()) {
1238 ERR_PRINT(vformat("Invalid Entry \"%s\" contains leading or trailing spaces.", name_3_x));
1239 valid = false;
1240 }
1241 if (names.has(name_3_x)) {
1242 ERR_PRINT(vformat("Found duplicated entry, pair ( -> %s , %s)", name_3_x, name_4_0));
1243 valid = false;
1244 }
1245 names.append(name_3_x);
1246
1247 if (name_4_0 != name_4_0.strip_edges()) {
1248 ERR_PRINT(vformat("Invalid Entry \"%s\" contains leading or trailing spaces.", name_3_x));
1249 valid = false;
1250 }
1251 if (names.has(name_4_0)) {
1252 ERR_PRINT(vformat("Found duplicated entry, pair ( -> %s , %s)", name_3_x, name_4_0));
1253 valid = false;
1254 }
1255 if (!p_ignore_4_0_name) {
1256 names.append(name_4_0);
1257 }
1258 }
1259 return valid;
1260};
1261
1262// Returns arguments from given function execution, this cannot be really done as regex.
1263// `abc(d,e(f,g),h)` -> [d], [e(f,g)], [h]
1264Vector<String> ProjectConverter3To4::parse_arguments(const String &line) {
1265 Vector<String> parts;
1266 int string_size = line.length();
1267 int start_part = 0; // Index of beginning of start part.
1268 int parts_counter = 0;
1269 char32_t previous_character = '\0';
1270 bool is_inside_string = false; // If true, it ignores these 3 characters ( , ) inside string.
1271
1272 ERR_FAIL_COND_V_MSG(line.count("(") != line.count(")"), parts, vformat("Converter internal bug: substring should have equal number of open and close parentheses in line - \"%s\".", line));
1273
1274 for (int current_index = 0; current_index < string_size; current_index++) {
1275 char32_t character = line.get(current_index);
1276 switch (character) {
1277 case '(':
1278 case '[':
1279 case '{': {
1280 parts_counter++;
1281 if (parts_counter == 1 && !is_inside_string) {
1282 start_part = current_index;
1283 }
1284 break;
1285 };
1286 case ')':
1287 case '}': {
1288 parts_counter--;
1289 if (parts_counter == 0 && !is_inside_string) {
1290 parts.append(line.substr(start_part + 1, current_index - start_part - 1));
1291 start_part = current_index;
1292 }
1293 break;
1294 };
1295 case ']': {
1296 parts_counter--;
1297 if (parts_counter == 0 && !is_inside_string) {
1298 parts.append(line.substr(start_part, current_index - start_part));
1299 start_part = current_index;
1300 }
1301 break;
1302 };
1303 case ',': {
1304 if (parts_counter == 1 && !is_inside_string) {
1305 parts.append(line.substr(start_part + 1, current_index - start_part - 1));
1306 start_part = current_index;
1307 }
1308 break;
1309 };
1310 case '"': {
1311 if (previous_character != '\\')
1312 is_inside_string = !is_inside_string;
1313 }
1314 }
1315 previous_character = character;
1316 }
1317
1318 Vector<String> clean_parts;
1319 for (String &part : parts) {
1320 part = part.strip_edges();
1321 if (!part.is_empty()) {
1322 clean_parts.append(part);
1323 }
1324 }
1325
1326 return clean_parts;
1327}
1328
1329// Finds latest parenthesis owned by function.
1330// `function(abc(a,b),DD)):` finds this parenthess `function(abc(a,b),DD => ) <= ):`
1331int ProjectConverter3To4::get_end_parenthesis(const String &line) const {
1332 int current_state = 0;
1333 for (int current_index = 0; line.length() > current_index; current_index++) {
1334 char32_t character = line.get(current_index);
1335 if (character == '(') {
1336 current_state++;
1337 }
1338 if (character == ')') {
1339 current_state--;
1340 if (current_state == 0) {
1341 return current_index;
1342 }
1343 }
1344 }
1345 return -1;
1346}
1347
1348// Merges multiple arguments into a single String.
1349// Needed when after processing e.g. 2 arguments, later arguments are not changed in any way.
1350String ProjectConverter3To4::connect_arguments(const Vector<String> &arguments, int from, int to) const {
1351 if (to == -1) {
1352 to = arguments.size();
1353 }
1354
1355 String value;
1356 if (arguments.size() > 0 && from != 0 && from < to) {
1357 value = ", ";
1358 }
1359
1360 for (int i = from; i < to; i++) {
1361 value += arguments[i];
1362 if (i != to - 1) {
1363 value += ", ";
1364 }
1365 }
1366 return value;
1367}
1368
1369// Returns the indentation (spaces and tabs) at the start of the line e.g. `\t\tmove_this` returns `\t\t`.
1370String ProjectConverter3To4::get_starting_space(const String &line) const {
1371 String empty_space;
1372 int current_character = 0;
1373
1374 if (line.is_empty()) {
1375 return empty_space;
1376 }
1377
1378 if (line[0] == ' ') {
1379 while (current_character < line.size()) {
1380 if (line[current_character] == ' ') {
1381 empty_space += ' ';
1382 current_character++;
1383 } else {
1384 break;
1385 }
1386 }
1387 }
1388 if (line[0] == '\t') {
1389 while (current_character < line.size()) {
1390 if (line[current_character] == '\t') {
1391 empty_space += '\t';
1392 current_character++;
1393 } else {
1394 break;
1395 }
1396 }
1397 }
1398 return empty_space;
1399}
1400
1401// Returns the object that’s executing the function in the line.
1402// e.g. Passing the line "var roman = kieliszek.funkcja()" to this function returns "kieliszek".
1403String ProjectConverter3To4::get_object_of_execution(const String &line) const {
1404 int end = line.size() - 1; // Last one is \0
1405 int variable_start = end - 1;
1406 int start = end - 1;
1407
1408 bool is_possibly_nodepath = false;
1409 bool is_valid_nodepath = false;
1410
1411 while (start >= 0) {
1412 char32_t character = line[start];
1413 bool is_variable_char = (character >= 'A' && character <= 'Z') || (character >= 'a' && character <= 'z') || character == '.' || character == '_';
1414 bool is_nodepath_start = character == '$';
1415 bool is_nodepath_sep = character == '/';
1416 if (is_variable_char || is_nodepath_start || is_nodepath_sep) {
1417 if (start == 0) {
1418 break;
1419 } else if (is_nodepath_sep) {
1420 // Freeze variable_start, try to fetch more chars since this might be a Node path literal.
1421 is_possibly_nodepath = true;
1422 } else if (is_nodepath_start) {
1423 // Found $, this is a Node path literal.
1424 is_valid_nodepath = true;
1425 break;
1426 }
1427 if (!is_possibly_nodepath) {
1428 variable_start--;
1429 }
1430 start--;
1431 continue;
1432 } else {
1433 // Abandon all hope, this is neither a variable nor a Node path literal.
1434 variable_start++; // Found invalid character, needs to be ignored.
1435 break;
1436 }
1437 }
1438 if (is_valid_nodepath) {
1439 variable_start = start;
1440 }
1441 return line.substr(variable_start, (end - variable_start));
1442}
1443
1444void ProjectConverter3To4::rename_colors(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
1445 for (SourceLine &source_line : source_lines) {
1446 if (source_line.is_comment) {
1447 continue;
1448 }
1449
1450 String &line = source_line.line;
1451 if (uint64_t(line.length()) <= maximum_line_length) {
1452 if (line.contains("Color.")) {
1453 for (unsigned int current_index = 0; RenamesMap3To4::color_renames[current_index][0]; current_index++) {
1454 line = reg_container.color_regexes[current_index]->sub(line, reg_container.color_renamed[current_index], true);
1455 }
1456 }
1457 }
1458 }
1459};
1460
1461Vector<String> ProjectConverter3To4::check_for_rename_colors(Vector<String> &lines, const RegExContainer &reg_container) {
1462 Vector<String> found_renames;
1463
1464 int current_line = 1;
1465 for (String &line : lines) {
1466 if (uint64_t(line.length()) <= maximum_line_length) {
1467 if (line.contains("Color.")) {
1468 for (unsigned int current_index = 0; RenamesMap3To4::color_renames[current_index][0]; current_index++) {
1469 TypedArray<RegExMatch> reg_match = reg_container.color_regexes[current_index]->search_all(line);
1470 if (reg_match.size() > 0) {
1471 found_renames.append(line_formatter(current_line, RenamesMap3To4::color_renames[current_index][0], RenamesMap3To4::color_renames[current_index][1], line));
1472 }
1473 }
1474 }
1475 }
1476 current_line++;
1477 }
1478
1479 return found_renames;
1480}
1481
1482void ProjectConverter3To4::fix_tool_declaration(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
1483 // In godot4, "tool" became "@tool" and must be located at the top of the file.
1484 for (int i = 0; i < source_lines.size(); ++i) {
1485 if (source_lines[i].line == "tool") {
1486 source_lines.remove_at(i);
1487 source_lines.insert(0, { "@tool", false });
1488 return; // assuming there's at most 1 tool declaration.
1489 }
1490 }
1491}
1492
1493void ProjectConverter3To4::fix_pause_mode(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
1494 // In Godot 3, the pause_mode 2 equals the PAUSE_MODE_PROCESS value.
1495 // In Godot 4, the pause_mode PAUSE_MODE_PROCESS was renamed to PROCESS_MODE_ALWAYS and equals the number 3.
1496 // We therefore convert pause_mode = 2 to pause_mode = 3.
1497 for (SourceLine &source_line : source_lines) {
1498 String &line = source_line.line;
1499
1500 if (line == "pause_mode = 2") {
1501 // Note: pause_mode is renamed to process_mode later on, so no need to do it here.
1502 line = "pause_mode = 3";
1503 }
1504 }
1505}
1506
1507void ProjectConverter3To4::rename_classes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
1508 for (SourceLine &source_line : source_lines) {
1509 if (source_line.is_comment) {
1510 continue;
1511 }
1512
1513 String &line = source_line.line;
1514 if (uint64_t(line.length()) <= maximum_line_length) {
1515 for (unsigned int current_index = 0; RenamesMap3To4::class_renames[current_index][0]; current_index++) {
1516 if (line.contains(RenamesMap3To4::class_renames[current_index][0])) {
1517 bool found_ignored_items = false;
1518 // Renaming Spatial.tscn to TEMP_RENAMED_CLASS.tscn.
1519 if (line.contains(String(RenamesMap3To4::class_renames[current_index][0]) + ".")) {
1520 found_ignored_items = true;
1521 line = reg_container.class_tscn_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.tscn", true);
1522 line = reg_container.class_gd_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.gd", true);
1523 line = reg_container.class_shader_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.shader", true);
1524 }
1525
1526 // Causal renaming Spatial -> Node3D.
1527 line = reg_container.class_regexes[current_index]->sub(line, RenamesMap3To4::class_renames[current_index][1], true);
1528
1529 // Restore Spatial.tscn from TEMP_RENAMED_CLASS.tscn.
1530 if (found_ignored_items) {
1531 line = reg_container.class_temp_tscn.sub(line, reg_container.class_temp_tscn_renames[current_index], true);
1532 line = reg_container.class_temp_gd.sub(line, reg_container.class_temp_gd_renames[current_index], true);
1533 line = reg_container.class_temp_shader.sub(line, reg_container.class_temp_shader_renames[current_index], true);
1534 }
1535 }
1536 }
1537 }
1538 }
1539};
1540
1541Vector<String> ProjectConverter3To4::check_for_rename_classes(Vector<String> &lines, const RegExContainer &reg_container) {
1542 Vector<String> found_renames;
1543
1544 int current_line = 1;
1545
1546 for (String &line : lines) {
1547 if (uint64_t(line.length()) <= maximum_line_length) {
1548 for (unsigned int current_index = 0; RenamesMap3To4::class_renames[current_index][0]; current_index++) {
1549 if (line.contains(RenamesMap3To4::class_renames[current_index][0])) {
1550 String old_line = line;
1551 bool found_ignored_items = false;
1552 // Renaming Spatial.tscn to TEMP_RENAMED_CLASS.tscn.
1553 if (line.contains(String(RenamesMap3To4::class_renames[current_index][0]) + ".")) {
1554 found_ignored_items = true;
1555 line = reg_container.class_tscn_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.tscn", true);
1556 line = reg_container.class_gd_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.gd", true);
1557 line = reg_container.class_shader_regexes[current_index]->sub(line, "TEMP_RENAMED_CLASS.shader", true);
1558 }
1559
1560 // Causal renaming Spatial -> Node3D.
1561 TypedArray<RegExMatch> reg_match = reg_container.class_regexes[current_index]->search_all(line);
1562 if (reg_match.size() > 0) {
1563 found_renames.append(line_formatter(current_line, RenamesMap3To4::class_renames[current_index][0], RenamesMap3To4::class_renames[current_index][1], old_line));
1564 }
1565
1566 // Restore Spatial.tscn from TEMP_RENAMED_CLASS.tscn.
1567 if (found_ignored_items) {
1568 line = reg_container.class_temp_tscn.sub(line, reg_container.class_temp_tscn_renames[current_index], true);
1569 line = reg_container.class_temp_gd.sub(line, reg_container.class_temp_gd_renames[current_index], true);
1570 line = reg_container.class_temp_shader.sub(line, reg_container.class_temp_shader_renames[current_index], true);
1571 }
1572 }
1573 }
1574 }
1575 current_line++;
1576 }
1577 return found_renames;
1578}
1579
1580void ProjectConverter3To4::rename_gdscript_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container, bool builtin) {
1581 for (SourceLine &source_line : source_lines) {
1582 if (source_line.is_comment) {
1583 continue;
1584 }
1585
1586 String &line = source_line.line;
1587 if (uint64_t(line.length()) <= maximum_line_length) {
1588 process_gdscript_line(line, reg_container, builtin);
1589 }
1590 }
1591};
1592
1593Vector<String> ProjectConverter3To4::check_for_rename_gdscript_functions(Vector<String> &lines, const RegExContainer &reg_container, bool builtin) {
1594 int current_line = 1;
1595
1596 Vector<String> found_renames;
1597
1598 for (String &line : lines) {
1599 if (uint64_t(line.length()) <= maximum_line_length) {
1600 String old_line = line;
1601 process_gdscript_line(line, reg_container, builtin);
1602 if (old_line != line) {
1603 found_renames.append(simple_line_formatter(current_line, old_line, line));
1604 }
1605 }
1606 }
1607
1608 return found_renames;
1609}
1610
1611bool ProjectConverter3To4::contains_function_call(String &line, String function) const {
1612 // We want to convert the function only if it is completely standalone.
1613 // For example, when we search for "connect(", we don't want to accidentally convert "reconnect(".
1614 if (!line.contains(function)) {
1615 return false;
1616 }
1617
1618 int index = line.find(function);
1619 if (index == 0) {
1620 return true;
1621 }
1622
1623 char32_t previous_char = line.get(index - 1);
1624 return (previous_char < '0' || previous_char > '9') && (previous_char < 'a' || previous_char > 'z') && (previous_char < 'A' || previous_char > 'Z') && previous_char != '_' && previous_char != '$' && previous_char != '@';
1625}
1626
1627// TODO, this function should run only on all ".gd" files and also on lines in ".tscn" files which are parts of built-in Scripts.
1628void ProjectConverter3To4::process_gdscript_line(String &line, const RegExContainer &reg_container, bool builtin) {
1629 // In this and other functions, reg.sub() is used only after checking lines with str.contains().
1630 // With longer lines, doing so can sometimes be significantly faster.
1631
1632 if ((line.contains(".lock") || line.contains(".unlock")) && !line.contains("mtx") && !line.contains("mutex") && !line.contains("Mutex")) {
1633 line = reg_container.reg_image_lock.sub(line, "false # $1.lock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
1634 line = reg_container.reg_image_unlock.sub(line, "false # $1.unlock() # TODOConverter3To4, Image no longer requires locking, `false` helps to not break one line if/else, so it can freely be removed", true);
1635 }
1636
1637 // PackedStringArray(req_godot).join('.') -> '.'.join(PackedStringArray(req_godot)) PoolStringArray
1638 if (line.contains(".join")) {
1639 line = reg_container.reg_join.sub(line, "$2.join($1)", true);
1640 }
1641
1642 // -- empty() -> is_empty() Pool*Array
1643 if (line.contains("empty")) {
1644 line = reg_container.reg_is_empty.sub(line, "is_empty(", true);
1645 }
1646
1647 // -- \t.func() -> \tsuper.func() Object
1648 if (line.contains("(") && line.contains(".")) {
1649 line = reg_container.reg_super.sub(line, "$1super.$2", true); // TODO, not sure if possible, but for now this broke String text e.g. "Chosen .gitignore" -> "Chosen super.gitignore"
1650 }
1651
1652 // -- JSON.parse(a) -> JSON.new().parse(a) etc. JSON
1653 if (line.contains("parse")) {
1654 line = reg_container.reg_json_non_new.sub(line, "$1var test_json_conv = JSON.new()\n$1test_json_conv.parse($3\n$1$2test_json_conv.get_data()", true);
1655 }
1656
1657 // -- to_json(a) -> JSON.new().stringify(a) Object
1658 if (line.contains("to_json")) {
1659 line = reg_container.reg_json_to.sub(line, "JSON.new().stringify", true);
1660 }
1661 // -- parse_json(a) -> JSON.get_data() etc. Object
1662 if (line.contains("parse_json")) {
1663 line = reg_container.reg_json_parse.sub(line, "$1var test_json_conv = JSON.new()\n$1test_json_conv.parse($3\n$1$2test_json_conv.get_data()", true);
1664 }
1665 // -- JSON.print( -> JSON.stringify(
1666 if (line.contains("JSON.print(")) {
1667 line = reg_container.reg_json_print.sub(line, "JSON.stringify(", true);
1668 }
1669
1670 // -- get_node(@ -> get_node( Node
1671 if (line.contains("get_node")) {
1672 line = line.replace("get_node(@", "get_node(");
1673 }
1674
1675 if (line.contains("export")) {
1676 // 1. export(float) var lifetime: float = 3.0 -> export var lifetime: float = 3.0
1677 line = reg_container.reg_export_typed.sub(line, "export var $2: $1");
1678 // 2. export(float) var lifetime := 3.0 -> export var lifetime := 3.0
1679 line = reg_container.reg_export_inferred_type.sub(line, "export var $1 :=");
1680 // 3. export(float) var lifetime = 3.0 -> export var lifetime: float = 3.0 GDScript
1681 line = reg_container.reg_export_simple.sub(line, "export var $2: $1");
1682 // 4. export(String, 'AnonymousPro', 'CourierPrime') var _font_name = 'AnonymousPro' -> export var _font_name = 'AnonymousPro' #(String, 'AnonymousPro', 'CourierPrime') GDScript
1683 line = reg_container.reg_export_advanced.sub(line, "export var $2$3 # ($1)");
1684 }
1685
1686 // Setget Setget
1687 if (line.contains("setget")) {
1688 line = reg_container.reg_setget_setget.sub(line, "var $1$2: get = $4, set = $3", true);
1689 }
1690
1691 // Setget set
1692 if (line.contains("setget")) {
1693 line = reg_container.reg_setget_set.sub(line, "var $1$2: set = $3", true);
1694 }
1695
1696 // Setget get
1697 if (line.contains("setget")) {
1698 line = reg_container.reg_setget_get.sub(line, "var $1$2: get = $3", true);
1699 }
1700
1701 if (line.contains("window_resizable")) {
1702 // OS.set_window_resizable(a) -> get_window().unresizable = not (a)
1703 line = reg_container.reg_os_set_window_resizable.sub(line, "get_window().unresizable = not ($1)", true);
1704 // OS.window_resizable = a -> same
1705 line = reg_container.reg_os_assign_window_resizable.sub(line, "get_window().unresizable = not ($1)", true);
1706 // OS.[is_]window_resizable() -> (not get_window().unresizable)
1707 line = reg_container.reg_os_is_window_resizable.sub(line, "(not get_window().unresizable)", true);
1708 }
1709
1710 if (line.contains("window_fullscreen")) {
1711 // OS.window_fullscreen(a) -> get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if (a) else Window.MODE_WINDOWED
1712 line = reg_container.reg_os_set_fullscreen.sub(line, "get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if ($1) else Window.MODE_WINDOWED", true);
1713 // window_fullscreen = a -> same
1714 line = reg_container.reg_os_assign_fullscreen.sub(line, "get_window().mode = Window.MODE_EXCLUSIVE_FULLSCREEN if ($1) else Window.MODE_WINDOWED", true);
1715 // OS.[is_]window_fullscreen() -> ((get_window().mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (get_window().mode == Window.MODE_FULLSCREEN))
1716 line = reg_container.reg_os_is_fullscreen.sub(line, "((get_window().mode == Window.MODE_EXCLUSIVE_FULLSCREEN) or (get_window().mode == Window.MODE_FULLSCREEN))", true);
1717 }
1718
1719 if (line.contains("window_maximized")) {
1720 // OS.window_maximized(a) -> get_window().mode = Window.MODE_MAXIMIZED if (a) else Window.MODE_WINDOWED
1721 line = reg_container.reg_os_set_maximized.sub(line, "get_window().mode = Window.MODE_MAXIMIZED if ($1) else Window.MODE_WINDOWED", true);
1722 // window_maximized = a -> same
1723 line = reg_container.reg_os_assign_maximized.sub(line, "get_window().mode = Window.MODE_MAXIMIZED if ($1) else Window.MODE_WINDOWED", true);
1724 // OS.[is_]window_maximized() -> (get_window().mode == Window.MODE_MAXIMIZED)
1725 line = reg_container.reg_os_is_maximized.sub(line, "(get_window().mode == Window.MODE_MAXIMIZED)", true);
1726 }
1727
1728 if (line.contains("window_minimized")) {
1729 // OS.window_minimized(a) -> get_window().mode = Window.MODE_MINIMIZED if (a) else Window.MODE_WINDOWED
1730 line = reg_container.reg_os_set_minimized.sub(line, "get_window().mode = Window.MODE_MINIMIZED if ($1) else Window.MODE_WINDOWED", true);
1731 // window_minimized = a -> same
1732 line = reg_container.reg_os_assign_minimized.sub(line, "get_window().mode = Window.MODE_MINIMIZED if ($1) else Window.MODE_WINDOWED", true);
1733 // OS.[is_]window_minimized() -> (get_window().mode == Window.MODE_MINIMIZED)
1734 line = reg_container.reg_os_is_minimized.sub(line, "(get_window().mode == Window.MODE_MINIMIZED)", true);
1735 }
1736
1737 if (line.contains("set_use_vsync")) {
1738 // OS.set_use_vsync(a) -> get_window().window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if (a) else DisplayServer.VSYNC_DISABLED)
1739 line = reg_container.reg_os_set_vsync.sub(line, "DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if ($1) else DisplayServer.VSYNC_DISABLED)", true);
1740 }
1741 if (line.contains("vsync_enabled")) {
1742 // vsync_enabled = a -> get_window().window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if (a) else DisplayServer.VSYNC_DISABLED)
1743 line = reg_container.reg_os_assign_vsync.sub(line, "DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED if ($1) else DisplayServer.VSYNC_DISABLED)", true);
1744 // OS.[is_]vsync_enabled() -> (DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED)
1745 line = reg_container.reg_os_is_vsync.sub(line, "(DisplayServer.window_get_vsync_mode() != DisplayServer.VSYNC_DISABLED)", true);
1746 }
1747
1748 if (line.contains("OS.screen_orientation")) { // keep "OS." at start
1749 // OS.screen_orientation = a -> DisplayServer.screen_set_orientation(a)
1750 line = reg_container.reg_os_assign_screen_orient.sub(line, "$1DisplayServer.screen_set_orientation($2)", true); // assignment
1751 line = line.replace("OS.screen_orientation", "DisplayServer.screen_get_orientation()"); // value access
1752 }
1753
1754 if (line.contains("_window_always_on_top")) {
1755 // OS.set_window_always_on_top(a) -> get_window().always_on_top = (a)
1756 line = reg_container.reg_os_set_always_on_top.sub(line, "get_window().always_on_top = ($1)", true);
1757 // OS.is_window_always_on_top() -> get_window().always_on_top
1758 line = reg_container.reg_os_is_always_on_top.sub(line, "get_window().always_on_top", true);
1759 }
1760
1761 if (line.contains("et_borderless_window")) {
1762 // OS.set_borderless_window(a) -> get_window().borderless = (a)
1763 line = reg_container.reg_os_set_borderless.sub(line, "get_window().borderless = ($1)", true);
1764 // OS.get_borderless_window() -> get_window().borderless
1765 line = reg_container.reg_os_get_borderless.sub(line, "get_window().borderless", true);
1766 }
1767
1768 // OS.SCREEN_ORIENTATION_* -> DisplayServer.SCREEN_*
1769 if (line.contains("OS.SCREEN_ORIENTATION_")) {
1770 line = reg_container.reg_os_screen_orient_enum.sub(line, "DisplayServer.SCREEN_$1", true);
1771 }
1772
1773 // OS -> Window simple replacements with optional set/get.
1774 if (line.contains("current_screen")) {
1775 line = reg_container.reg_os_current_screen.sub(line, "get_window().$1current_screen", true);
1776 }
1777 if (line.contains("min_window_size")) {
1778 line = reg_container.reg_os_min_window_size.sub(line, "get_window().$1min_size", true);
1779 }
1780 if (line.contains("max_window_size")) {
1781 line = reg_container.reg_os_max_window_size.sub(line, "get_window().$1max_size", true);
1782 }
1783 if (line.contains("window_position")) {
1784 line = reg_container.reg_os_window_position.sub(line, "get_window().$1position", true);
1785 }
1786 if (line.contains("window_size")) {
1787 line = reg_container.reg_os_window_size.sub(line, "get_window().$1size", true);
1788 }
1789 if (line.contains("et_screen_orientation")) {
1790 line = reg_container.reg_os_getset_screen_orient.sub(line, "DisplayServer.screen_$1et_orientation", true);
1791 }
1792
1793 // Instantiate
1794 if (contains_function_call(line, "instance")) {
1795 line = reg_container.reg_instantiate.sub(line, ".instantiate($1)", true);
1796 }
1797
1798 // -- r.move_and_slide( a, b, c, d, e ) -> r.set_velocity(a) ... r.move_and_slide() KinematicBody
1799 if (contains_function_call(line, "move_and_slide(")) {
1800 int start = line.find("move_and_slide(");
1801 int end = get_end_parenthesis(line.substr(start)) + 1;
1802 if (end > -1) {
1803 String base_obj = get_object_of_execution(line.substr(0, start));
1804 String starting_space = get_starting_space(line);
1805
1806 Vector<String> parts = parse_arguments(line.substr(start, end));
1807 if (parts.size() >= 1) {
1808 String line_new;
1809
1810 // motion_velocity
1811 line_new += starting_space + base_obj + "set_velocity(" + parts[0] + ")\n";
1812
1813 // up_direction
1814 if (parts.size() >= 2) {
1815 line_new += starting_space + base_obj + "set_up_direction(" + parts[1] + ")\n";
1816 }
1817
1818 // stop_on_slope
1819 if (parts.size() >= 3) {
1820 line_new += starting_space + base_obj + "set_floor_stop_on_slope_enabled(" + parts[2] + ")\n";
1821 }
1822
1823 // max_slides
1824 if (parts.size() >= 4) {
1825 line_new += starting_space + base_obj + "set_max_slides(" + parts[3] + ")\n";
1826 }
1827
1828 // floor_max_angle
1829 if (parts.size() >= 5) {
1830 line_new += starting_space + base_obj + "set_floor_max_angle(" + parts[4] + ")\n";
1831 }
1832
1833 // infiinite_interia
1834 if (parts.size() >= 6) {
1835 line_new += starting_space + "# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `" + parts[5] + "`\n";
1836 }
1837
1838 line_new += starting_space + base_obj + "move_and_slide()";
1839
1840 if (!line.begins_with(starting_space + "move_and_slide")) {
1841 line = line_new + "\n" + line.substr(0, start) + "velocity" + line.substr(end + start);
1842 } else {
1843 line = line_new + line.substr(end + start);
1844 }
1845 }
1846 }
1847 }
1848
1849 // -- r.move_and_slide_with_snap( a, b, c, d, e ) -> r.set_velocity(a) ... r.move_and_slide() KinematicBody
1850 if (contains_function_call(line, "move_and_slide_with_snap(")) {
1851 int start = line.find("move_and_slide_with_snap(");
1852 int end = get_end_parenthesis(line.substr(start)) + 1;
1853 if (end > -1) {
1854 String base_obj = get_object_of_execution(line.substr(0, start));
1855 String starting_space = get_starting_space(line);
1856
1857 Vector<String> parts = parse_arguments(line.substr(start, end));
1858 if (parts.size() >= 1) {
1859 String line_new;
1860
1861 // motion_velocity
1862 line_new += starting_space + base_obj + "set_velocity(" + parts[0] + ")\n";
1863
1864 // snap
1865 if (parts.size() >= 2) {
1866 line_new += starting_space + "# TODOConverter3To4 looks that snap in Godot 4 is float, not vector like in Godot 3 - previous value `" + parts[1] + "`\n";
1867 }
1868
1869 // up_direction
1870 if (parts.size() >= 3) {
1871 line_new += starting_space + base_obj + "set_up_direction(" + parts[2] + ")\n";
1872 }
1873
1874 // stop_on_slope
1875 if (parts.size() >= 4) {
1876 line_new += starting_space + base_obj + "set_floor_stop_on_slope_enabled(" + parts[3] + ")\n";
1877 }
1878
1879 // max_slides
1880 if (parts.size() >= 5) {
1881 line_new += starting_space + base_obj + "set_max_slides(" + parts[4] + ")\n";
1882 }
1883
1884 // floor_max_angle
1885 if (parts.size() >= 6) {
1886 line_new += starting_space + base_obj + "set_floor_max_angle(" + parts[5] + ")\n";
1887 }
1888
1889 // infiinite_interia
1890 if (parts.size() >= 7) {
1891 line_new += starting_space + "# TODOConverter3To4 infinite_inertia were removed in Godot 4 - previous value `" + parts[6] + "`\n";
1892 }
1893
1894 line_new += starting_space + base_obj + "move_and_slide()";
1895
1896 if (!line.begins_with(starting_space + "move_and_slide_with_snap")) {
1897 line = line_new + "\n" + line.substr(0, start) + "velocity" + line.substr(end + start);
1898 } else {
1899 line = line_new + line.substr(end + start);
1900 }
1901 }
1902 }
1903 }
1904
1905 // -- sort_custom( a , b ) -> sort_custom(Callable( a , b )) Object
1906 if (contains_function_call(line, "sort_custom(")) {
1907 int start = line.find("sort_custom(");
1908 int end = get_end_parenthesis(line.substr(start)) + 1;
1909 if (end > -1) {
1910 Vector<String> parts = parse_arguments(line.substr(start, end));
1911 if (parts.size() == 2) {
1912 line = line.substr(0, start) + "sort_custom(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
1913 }
1914 }
1915 }
1916
1917 // -- list_dir_begin( ) -> list_dir_begin() Object
1918 if (contains_function_call(line, "list_dir_begin(")) {
1919 int start = line.find("list_dir_begin(");
1920 int end = get_end_parenthesis(line.substr(start)) + 1;
1921 if (end > -1) {
1922 line = line.substr(0, start) + "list_dir_begin() " + line.substr(end + start) + "# TODOConverter3To4 fill missing arguments https://github.com/godotengine/godot/pull/40547";
1923 }
1924 }
1925
1926 // -- draw_line(1,2,3,4,5) -> draw_line(1, 2, 3, 4) CanvasItem
1927 if (contains_function_call(line, "draw_line(")) {
1928 int start = line.find("draw_line(");
1929 int end = get_end_parenthesis(line.substr(start)) + 1;
1930 if (end > -1) {
1931 Vector<String> parts = parse_arguments(line.substr(start, end));
1932 if (parts.size() == 5) {
1933 line = line.substr(0, start) + "draw_line(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start);
1934 }
1935 }
1936 }
1937
1938 // -- func c(var a, var b) -> func c(a, b)
1939 if (line.contains("func ") && line.contains("var ")) {
1940 int start = line.find("func ");
1941 start = line.substr(start).find("(") + start;
1942 int end = get_end_parenthesis(line.substr(start)) + 1;
1943 if (end > -1) {
1944 Vector<String> parts = parse_arguments(line.substr(start, end));
1945
1946 String start_string = line.substr(0, start) + "(";
1947 for (int i = 0; i < parts.size(); i++) {
1948 start_string += parts[i].strip_edges().trim_prefix("var ");
1949 if (i != parts.size() - 1) {
1950 start_string += ", ";
1951 }
1952 }
1953 line = start_string + ")" + line.substr(end + start);
1954 }
1955 }
1956
1957 // -- yield(this, \"timeout\") -> await this.timeout GDScript
1958 if (contains_function_call(line, "yield(")) {
1959 int start = line.find("yield(");
1960 int end = get_end_parenthesis(line.substr(start)) + 1;
1961 if (end > -1) {
1962 Vector<String> parts = parse_arguments(line.substr(start, end));
1963 if (parts.size() == 2) {
1964 if (builtin) {
1965 line = line.substr(0, start) + "await " + parts[0] + "." + parts[1].replace("\\\"", "").replace("\\'", "").replace(" ", "") + line.substr(end + start);
1966 } else {
1967 line = line.substr(0, start) + "await " + parts[0] + "." + parts[1].replace("\"", "").replace("\'", "").replace(" ", "") + line.substr(end + start);
1968 }
1969 }
1970 }
1971 }
1972
1973 // -- parse_json( AA ) -> TODO Object
1974 if (contains_function_call(line, "parse_json(")) {
1975 int start = line.find("parse_json(");
1976 int end = get_end_parenthesis(line.substr(start)) + 1;
1977 if (end > -1) {
1978 Vector<String> parts = parse_arguments(line.substr(start, end));
1979 line = line.substr(0, start) + "JSON.new().stringify(" + connect_arguments(parts, 0) + ")" + line.substr(end + start);
1980 }
1981 }
1982
1983 // -- .xform(Vector3(a,b,c)) -> * Vector3(a,b,c) Transform
1984 if (line.contains(".xform(")) {
1985 int start = line.find(".xform(");
1986 int end = get_end_parenthesis(line.substr(start)) + 1;
1987 if (end > -1) {
1988 Vector<String> parts = parse_arguments(line.substr(start, end));
1989 if (parts.size() == 1) {
1990 line = line.substr(0, start) + " * (" + parts[0] + ")" + line.substr(end + start);
1991 }
1992 }
1993 }
1994
1995 // -- .xform_inv(Vector3(a,b,c)) -> * Vector3(a,b,c) Transform
1996 if (line.contains(".xform_inv(")) {
1997 int start = line.find(".xform_inv(");
1998 int end = get_end_parenthesis(line.substr(start)) + 1;
1999 if (end > -1) {
2000 String object_exec = get_object_of_execution(line.substr(0, start));
2001 if (line.contains(object_exec + ".xform")) {
2002 int start2 = line.find(object_exec + ".xform");
2003 Vector<String> parts = parse_arguments(line.substr(start, end));
2004 if (parts.size() == 1) {
2005 line = line.substr(0, start2) + "(" + parts[0] + ") * " + object_exec + line.substr(end + start);
2006 }
2007 }
2008 }
2009 }
2010
2011 // -- "(connect(A,B,C,D,E) != OK):", "(connect(A, Callable(B, C).bind(D), E) Object
2012 if (contains_function_call(line, "connect(")) {
2013 int start = line.find("connect(");
2014 int end = get_end_parenthesis(line.substr(start)) + 1;
2015 if (end > -1) {
2016 Vector<String> parts = parse_arguments(line.substr(start, end));
2017 if (parts.size() == 3) {
2018 line = line.substr(0, start) + "connect(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2019 } else if (parts.size() >= 4) {
2020 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);
2021 }
2022 }
2023 }
2024 // -- disconnect(a,b,c) -> disconnect(a,Callable(b,c)) Object
2025 if (contains_function_call(line, "disconnect(")) {
2026 int start = line.find("disconnect(");
2027 int end = get_end_parenthesis(line.substr(start)) + 1;
2028 if (end > -1) {
2029 Vector<String> parts = parse_arguments(line.substr(start, end));
2030 if (parts.size() == 3) {
2031 line = line.substr(0, start) + "disconnect(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2032 }
2033 }
2034 }
2035 // -- is_connected(a,b,c) -> is_connected(a,Callable(b,c)) Object
2036 if (contains_function_call(line, "is_connected(")) {
2037 int start = line.find("is_connected(");
2038 int end = get_end_parenthesis(line.substr(start)) + 1;
2039 if (end > -1) {
2040 Vector<String> parts = parse_arguments(line.substr(start, end));
2041 if (parts.size() == 3) {
2042 line = line.substr(0, start) + "is_connected(" + parts[0] + ", Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2043 }
2044 }
2045 }
2046 // -- "(tween_method(A,B,C,D,E) != OK):", "(tween_method(Callable(A,B),C,D,E) Object
2047 // -- "(tween_method(A,B,C,D,E,[F,G]) != OK):", "(tween_method(Callable(A,B).bind(F,G),C,D,E) Object
2048 if (contains_function_call(line, "tween_method(")) {
2049 int start = line.find("tween_method(");
2050 int end = get_end_parenthesis(line.substr(start)) + 1;
2051 if (end > -1) {
2052 Vector<String> parts = parse_arguments(line.substr(start, end));
2053 if (parts.size() == 5) {
2054 line = line.substr(0, start) + "tween_method(Callable(" + parts[0] + ", " + parts[1] + "), " + parts[2] + ", " + parts[3] + ", " + parts[4] + ")" + line.substr(end + start);
2055 } else if (parts.size() >= 6) {
2056 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);
2057 }
2058 }
2059 }
2060 // -- "(tween_callback(A,B,[C,D]) != OK):", "(connect(Callable(A,B).bind(C,D)) Object
2061 if (contains_function_call(line, "tween_callback(")) {
2062 int start = line.find("tween_callback(");
2063 int end = get_end_parenthesis(line.substr(start)) + 1;
2064 if (end > -1) {
2065 Vector<String> parts = parse_arguments(line.substr(start, end));
2066 if (parts.size() == 2) {
2067 line = line.substr(0, start) + "tween_callback(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
2068 } else if (parts.size() >= 3) {
2069 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);
2070 }
2071 }
2072 }
2073 // -- start(a,b) -> start(Callable(a, b)) Thread
2074 // -- start(a,b,c,d) -> start(Callable(a, b).bind(c), d) Thread
2075 if (contains_function_call(line, "start(")) {
2076 int start = line.find("start(");
2077 int end = get_end_parenthesis(line.substr(start)) + 1;
2078 // Protection from 'func start'
2079 if (!line.begins_with("func ")) {
2080 if (end > -1) {
2081 Vector<String> parts = parse_arguments(line.substr(start, end));
2082 if (parts.size() == 2) {
2083 line = line.substr(0, start) + "start(Callable(" + parts[0] + ", " + parts[1] + "))" + line.substr(end + start);
2084 } else if (parts.size() >= 3) {
2085 line = line.substr(0, start) + "start(Callable(" + parts[0] + ", " + parts[1] + ").bind(" + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
2086 }
2087 }
2088 }
2089 }
2090 // -- func _init(p_x:int).(p_x): -> func _init(p_x:int):\n\tsuper(p_x) Object # https://github.com/godotengine/godot/issues/70542
2091 if (line.contains(" _init(") && line.rfind(":") > 0) {
2092 // func _init(p_arg1).(super4, super5, super6)->void:
2093 // ^--^indent ^super_start super_end^
2094 int indent = line.count("\t", 0, line.find("func"));
2095 int super_start = line.find(".(");
2096 int super_end = line.rfind(")");
2097 if (super_start > 0 && super_end > super_start) {
2098 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);
2099 }
2100 }
2101
2102 // create_from_image(aa, bb) -> create_from_image(aa) #, bb ImageTexture
2103 if (contains_function_call(line, "create_from_image(")) {
2104 int start = line.find("create_from_image(");
2105 int end = get_end_parenthesis(line.substr(start)) + 1;
2106 if (end > -1) {
2107 Vector<String> parts = parse_arguments(line.substr(start, end));
2108 if (parts.size() == 2) {
2109 line = line.substr(0, start) + "create_from_image(" + parts[0] + ") " + "#," + parts[1] + line.substr(end + start);
2110 }
2111 }
2112 }
2113 // set_cell_item(a, b, c, d ,e) -> set_cell_item(Vector3(a, b, c), d ,e)
2114 if (contains_function_call(line, "set_cell_item(")) {
2115 int start = line.find("set_cell_item(");
2116 int end = get_end_parenthesis(line.substr(start)) + 1;
2117 if (end > -1) {
2118 Vector<String> parts = parse_arguments(line.substr(start, end));
2119 if (parts.size() > 2) {
2120 line = line.substr(0, start) + "set_cell_item(Vector3(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ")" + connect_arguments(parts, 3).lstrip(" ") + ")" + line.substr(end + start);
2121 }
2122 }
2123 }
2124 // get_cell_item(a, b, c) -> get_cell_item(Vector3i(a, b, c))
2125 if (contains_function_call(line, "get_cell_item(")) {
2126 int start = line.find("get_cell_item(");
2127 int end = get_end_parenthesis(line.substr(start)) + 1;
2128 if (end > -1) {
2129 Vector<String> parts = parse_arguments(line.substr(start, end));
2130 if (parts.size() == 3) {
2131 line = line.substr(0, start) + "get_cell_item(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2132 }
2133 }
2134 }
2135 // get_cell_item_orientation(a, b, c) -> get_cell_item_orientation(Vector3i(a, b, c))
2136 if (contains_function_call(line, "get_cell_item_orientation(")) {
2137 int start = line.find("get_cell_item_orientation(");
2138 int end = get_end_parenthesis(line.substr(start)) + 1;
2139 if (end > -1) {
2140 Vector<String> parts = parse_arguments(line.substr(start, end));
2141 if (parts.size() == 3) {
2142 line = line.substr(0, start) + "get_cell_item_orientation(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2143 }
2144 }
2145 }
2146 // apply_impulse(A, B) -> apply_impulse(B, A)
2147 if (contains_function_call(line, "apply_impulse(")) {
2148 int start = line.find("apply_impulse(");
2149 int end = get_end_parenthesis(line.substr(start)) + 1;
2150 if (end > -1) {
2151 Vector<String> parts = parse_arguments(line.substr(start, end));
2152 if (parts.size() == 2) {
2153 line = line.substr(0, start) + "apply_impulse(" + parts[1] + ", " + parts[0] + ")" + line.substr(end + start);
2154 }
2155 }
2156 }
2157 // apply_force(A, B) -> apply_force(B, A)
2158 if (contains_function_call(line, "apply_force(")) {
2159 int start = line.find("apply_force(");
2160 int end = get_end_parenthesis(line.substr(start)) + 1;
2161 if (end > -1) {
2162 Vector<String> parts = parse_arguments(line.substr(start, end));
2163 if (parts.size() == 2) {
2164 line = line.substr(0, start) + "apply_force(" + parts[1] + ", " + parts[0] + ")" + line.substr(end + start);
2165 }
2166 }
2167 }
2168 // map_to_world(a, b, c) -> map_to_local(Vector3i(a, b, c))
2169 if (contains_function_call(line, "map_to_world(")) {
2170 int start = line.find("map_to_world(");
2171 int end = get_end_parenthesis(line.substr(start)) + 1;
2172 if (end > -1) {
2173 Vector<String> parts = parse_arguments(line.substr(start, end));
2174 if (parts.size() == 3) {
2175 line = line.substr(0, start) + "map_to_local(Vector3i(" + parts[0] + ", " + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2176 } else if (parts.size() == 1) {
2177 line = line.substr(0, start) + "map_to_local(" + parts[0] + ")" + line.substr(end + start);
2178 }
2179 }
2180 }
2181
2182 // set_rotating(true) -> set_ignore_rotation(false)
2183 if (contains_function_call(line, "set_rotating(")) {
2184 int start = line.find("set_rotating(");
2185 int end = get_end_parenthesis(line.substr(start)) + 1;
2186 if (end > -1) {
2187 Vector<String> parts = parse_arguments(line.substr(start, end));
2188 if (parts.size() == 1) {
2189 String opposite = parts[0] == "true" ? "false" : "true";
2190 line = line.substr(0, start) + "set_ignore_rotation(" + opposite + ")";
2191 }
2192 }
2193 }
2194
2195 // OS.get_window_safe_area() -> DisplayServer.get_display_safe_area()
2196 if (line.contains("OS.get_window_safe_area(")) {
2197 int start = line.find("OS.get_window_safe_area(");
2198 int end = get_end_parenthesis(line.substr(start)) + 1;
2199 if (end > -1) {
2200 Vector<String> parts = parse_arguments(line.substr(start, end));
2201 if (parts.size() == 0) {
2202 line = line.substr(0, start) + "DisplayServer.get_display_safe_area()" + line.substr(end + start);
2203 }
2204 }
2205 }
2206 // draw_rect(a,b,c,d,e) -> draw_rect(a,b,c,d)#e) TODOConverter3To4 Antialiasing argument is missing
2207 if (contains_function_call(line, "draw_rect(")) {
2208 int start = line.find("draw_rect(");
2209 int end = get_end_parenthesis(line.substr(start)) + 1;
2210 if (end > -1) {
2211 Vector<String> parts = parse_arguments(line.substr(start, end));
2212 if (parts.size() == 5) {
2213 line = line.substr(0, start) + "draw_rect(" + parts[0] + ", " + parts[1] + ", " + parts[2] + ", " + parts[3] + ")" + line.substr(end + start) + "# " + parts[4] + ") TODOConverter3To4 Antialiasing argument is missing";
2214 }
2215 }
2216 }
2217 // get_focus_owner() -> get_viewport().gui_get_focus_owner()
2218 if (contains_function_call(line, "get_focus_owner()")) {
2219 line = line.replace("get_focus_owner()", "get_viewport().gui_get_focus_owner()");
2220 }
2221
2222 // button.pressed = 1 -> button.button_pressed = 1
2223 if (line.contains(".pressed")) {
2224 int start = line.find(".pressed");
2225 bool foundNextEqual = false;
2226 String line_to_check = line.substr(start + String(".pressed").length());
2227 for (int current_index = 0; line_to_check.length() > current_index; current_index++) {
2228 char32_t chr = line_to_check.get(current_index);
2229 if (chr == '\t' || chr == ' ') {
2230 continue;
2231 } else if (chr == '=') {
2232 foundNextEqual = true;
2233 } else {
2234 break;
2235 }
2236 }
2237 if (foundNextEqual) {
2238 line = line.substr(0, start) + ".button_pressed" + line.substr(start + String(".pressed").length());
2239 }
2240 }
2241
2242 // rotating = true -> ignore_rotation = false # reversed "rotating" for Camera2D
2243 if (contains_function_call(line, "rotating")) {
2244 String function_name = "rotating";
2245 int start = line.find(function_name);
2246 bool foundNextEqual = false;
2247 String line_to_check = line.substr(start + function_name.length());
2248 String assigned_value;
2249 for (int current_index = 0; line_to_check.length() > current_index; current_index++) {
2250 char32_t chr = line_to_check.get(current_index);
2251 if (chr == '\t' || chr == ' ') {
2252 continue;
2253 } else if (chr == '=') {
2254 foundNextEqual = true;
2255 assigned_value = line.substr(start + function_name.length() + current_index + 1).strip_edges();
2256 assigned_value = assigned_value == "true" ? "false" : "true";
2257 } else {
2258 break;
2259 }
2260 }
2261 if (foundNextEqual) {
2262 line = line.substr(0, start) + "ignore_rotation = " + assigned_value + " # reversed \"rotating\" for Camera2D";
2263 }
2264 }
2265
2266 // OS -> Time functions
2267 if (line.contains("OS.get_ticks_msec")) {
2268 line = line.replace("OS.get_ticks_msec", "Time.get_ticks_msec");
2269 }
2270 if (line.contains("OS.get_ticks_usec")) {
2271 line = line.replace("OS.get_ticks_usec", "Time.get_ticks_usec");
2272 }
2273 if (line.contains("OS.get_unix_time")) {
2274 line = line.replace("OS.get_unix_time", "Time.get_unix_time_from_system");
2275 }
2276 if (line.contains("OS.get_datetime")) {
2277 line = line.replace("OS.get_datetime", "Time.get_datetime_dict_from_system");
2278 }
2279
2280 // OS -> DisplayServer
2281 if (line.contains("OS.get_display_cutouts")) {
2282 line = line.replace("OS.get_display_cutouts", "DisplayServer.get_display_cutouts");
2283 }
2284 if (line.contains("OS.get_screen_count")) {
2285 line = line.replace("OS.get_screen_count", "DisplayServer.get_screen_count");
2286 }
2287 if (line.contains("OS.get_screen_dpi")) {
2288 line = line.replace("OS.get_screen_dpi", "DisplayServer.screen_get_dpi");
2289 }
2290 if (line.contains("OS.get_screen_max_scale")) {
2291 line = line.replace("OS.get_screen_max_scale", "DisplayServer.screen_get_max_scale");
2292 }
2293 if (line.contains("OS.get_screen_position")) {
2294 line = line.replace("OS.get_screen_position", "DisplayServer.screen_get_position");
2295 }
2296 if (line.contains("OS.get_screen_refresh_rate")) {
2297 line = line.replace("OS.get_screen_refresh_rate", "DisplayServer.screen_get_refresh_rate");
2298 }
2299 if (line.contains("OS.get_screen_scale")) {
2300 line = line.replace("OS.get_screen_scale", "DisplayServer.screen_get_scale");
2301 }
2302 if (line.contains("OS.get_screen_size")) {
2303 line = line.replace("OS.get_screen_size", "DisplayServer.screen_get_size");
2304 }
2305 if (line.contains("OS.set_icon")) {
2306 line = line.replace("OS.set_icon", "DisplayServer.set_icon");
2307 }
2308 if (line.contains("OS.set_native_icon")) {
2309 line = line.replace("OS.set_native_icon", "DisplayServer.set_native_icon");
2310 }
2311
2312 // OS -> Window
2313 if (line.contains("OS.window_borderless")) {
2314 line = line.replace("OS.window_borderless", "get_window().borderless");
2315 }
2316 if (line.contains("OS.get_real_window_size")) {
2317 line = line.replace("OS.get_real_window_size", "get_window().get_size_with_decorations");
2318 }
2319 if (line.contains("OS.is_window_focused")) {
2320 line = line.replace("OS.is_window_focused", "get_window().has_focus");
2321 }
2322 if (line.contains("OS.move_window_to_foreground")) {
2323 line = line.replace("OS.move_window_to_foreground", "get_window().move_to_foreground");
2324 }
2325 if (line.contains("OS.request_attention")) {
2326 line = line.replace("OS.request_attention", "get_window().request_attention");
2327 }
2328 if (line.contains("OS.set_window_title")) {
2329 line = line.replace("OS.set_window_title", "get_window().set_title");
2330 }
2331
2332 // get_tree().set_input_as_handled() -> get_viewport().set_input_as_handled()
2333 if (line.contains("get_tree().set_input_as_handled()")) {
2334 line = line.replace("get_tree().set_input_as_handled()", "get_viewport().set_input_as_handled()");
2335 }
2336
2337 // Fix the simple case of using _unhandled_key_input
2338 // func _unhandled_key_input(event: InputEventKey) -> _unhandled_key_input(event: InputEvent)
2339 if (line.contains("_unhandled_key_input(event: InputEventKey)")) {
2340 line = line.replace("_unhandled_key_input(event: InputEventKey)", "_unhandled_key_input(event: InputEvent)");
2341 }
2342
2343 if (line.contains("Engine.editor_hint")) {
2344 line = line.replace("Engine.editor_hint", "Engine.is_editor_hint()");
2345 }
2346}
2347
2348void ProjectConverter3To4::process_csharp_line(String &line, const RegExContainer &reg_container) {
2349 line = line.replace("OS.GetWindowSafeArea()", "DisplayServer.ScreenGetUsableRect()");
2350
2351 // GetTree().SetInputAsHandled() -> GetViewport().SetInputAsHandled()
2352 if (line.contains("GetTree().SetInputAsHandled()")) {
2353 line = line.replace("GetTree().SetInputAsHandled()", "GetViewport().SetInputAsHandled()");
2354 }
2355
2356 // Fix the simple case of using _UnhandledKeyInput
2357 // func _UnhandledKeyInput(InputEventKey @event) -> _UnhandledKeyInput(InputEvent @event)
2358 if (line.contains("_UnhandledKeyInput(InputEventKey @event)")) {
2359 line = line.replace("_UnhandledKeyInput(InputEventKey @event)", "_UnhandledKeyInput(InputEvent @event)");
2360 }
2361
2362 // -- Connect(,,,things) -> Connect(,Callable(,),things) Object
2363 if (line.contains("Connect(")) {
2364 int start = line.find("Connect(");
2365 // Protection from disconnect
2366 if (start == 0 || line.get(start - 1) != 's') {
2367 int end = get_end_parenthesis(line.substr(start)) + 1;
2368 if (end > -1) {
2369 Vector<String> parts = parse_arguments(line.substr(start, end));
2370 if (parts.size() >= 3) {
2371 line = line.substr(0, start) + "Connect(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + ")" + connect_arguments(parts, 3) + ")" + line.substr(end + start);
2372 }
2373 }
2374 }
2375 }
2376 // -- Disconnect(a,b,c) -> Disconnect(a,Callable(b,c)) Object
2377 if (line.contains("Disconnect(")) {
2378 int start = line.find("Disconnect(");
2379 int end = get_end_parenthesis(line.substr(start)) + 1;
2380 if (end > -1) {
2381 Vector<String> parts = parse_arguments(line.substr(start, end));
2382 if (parts.size() == 3) {
2383 line = line.substr(0, start) + "Disconnect(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2384 }
2385 }
2386 }
2387 // -- IsConnected(a,b,c) -> IsConnected(a,Callable(b,c)) Object
2388 if (line.contains("IsConnected(")) {
2389 int start = line.find("IsConnected(");
2390 int end = get_end_parenthesis(line.substr(start)) + 1;
2391 if (end > -1) {
2392 Vector<String> parts = parse_arguments(line.substr(start, end));
2393 if (parts.size() == 3) {
2394 line = line.substr(0, start) + "IsConnected(" + parts[0] + ", new Callable(" + parts[1] + ", " + parts[2] + "))" + line.substr(end + start);
2395 }
2396 }
2397 }
2398}
2399
2400void ProjectConverter3To4::rename_csharp_functions(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
2401 for (SourceLine &source_line : source_lines) {
2402 if (source_line.is_comment) {
2403 continue;
2404 }
2405
2406 String &line = source_line.line;
2407 if (uint64_t(line.length()) <= maximum_line_length) {
2408 process_csharp_line(line, reg_container);
2409 }
2410 }
2411};
2412
2413Vector<String> ProjectConverter3To4::check_for_rename_csharp_functions(Vector<String> &lines, const RegExContainer &reg_container) {
2414 int current_line = 1;
2415
2416 Vector<String> found_renames;
2417
2418 for (String &line : lines) {
2419 if (uint64_t(line.length()) <= maximum_line_length) {
2420 String old_line = line;
2421 process_csharp_line(line, reg_container);
2422 if (old_line != line) {
2423 found_renames.append(simple_line_formatter(current_line, old_line, line));
2424 }
2425 }
2426 }
2427
2428 return found_renames;
2429}
2430
2431void ProjectConverter3To4::rename_csharp_attributes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
2432 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";
2433
2434 for (SourceLine &source_line : source_lines) {
2435 if (source_line.is_comment) {
2436 continue;
2437 }
2438
2439 String &line = source_line.line;
2440 if (uint64_t(line.length()) <= maximum_line_length) {
2441 line = reg_container.keyword_csharp_remote.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", true);
2442 line = reg_container.keyword_csharp_remotesync.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", true);
2443 line = reg_container.keyword_csharp_puppet.sub(line, "[RPC]", true);
2444 line = reg_container.keyword_csharp_puppetsync.sub(line, "[RPC(CallLocal = true)]", true);
2445 line = reg_container.keyword_csharp_master.sub(line, error_message + "[RPC]", true);
2446 line = reg_container.keyword_csharp_mastersync.sub(line, error_message + "[RPC(CallLocal = true)]", true);
2447 }
2448 }
2449}
2450
2451Vector<String> ProjectConverter3To4::check_for_rename_csharp_attributes(Vector<String> &lines, const RegExContainer &reg_container) {
2452 int current_line = 1;
2453
2454 Vector<String> found_renames;
2455
2456 for (String &line : lines) {
2457 if (uint64_t(line.length()) <= maximum_line_length) {
2458 String old;
2459 old = line;
2460 line = reg_container.keyword_csharp_remote.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", true);
2461 if (old != line) {
2462 found_renames.append(line_formatter(current_line, "[Remote]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer)]", line));
2463 }
2464
2465 old = line;
2466 line = reg_container.keyword_csharp_remotesync.sub(line, "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", true);
2467 if (old != line) {
2468 found_renames.append(line_formatter(current_line, "[RemoteSync]", "[RPC(MultiplayerAPI.RPCMode.AnyPeer, CallLocal = true)]", line));
2469 }
2470
2471 old = line;
2472 line = reg_container.keyword_csharp_puppet.sub(line, "[RPC]", true);
2473 if (old != line) {
2474 found_renames.append(line_formatter(current_line, "[Puppet]", "[RPC]", line));
2475 }
2476
2477 old = line;
2478 line = reg_container.keyword_csharp_puppetsync.sub(line, "[RPC(CallLocal = true)]", true);
2479 if (old != line) {
2480 found_renames.append(line_formatter(current_line, "[PuppetSync]", "[RPC(CallLocal = true)]", line));
2481 }
2482
2483 old = line;
2484 line = reg_container.keyword_csharp_master.sub(line, "[RPC]", true);
2485 if (old != line) {
2486 found_renames.append(line_formatter(current_line, "[Master]", "[RPC]", line));
2487 }
2488
2489 old = line;
2490 line = reg_container.keyword_csharp_mastersync.sub(line, "[RPC(CallLocal = true)]", true);
2491 if (old != line) {
2492 found_renames.append(line_formatter(current_line, "[MasterSync]", "[RPC(CallLocal = true)]", line));
2493 }
2494 }
2495 current_line++;
2496 }
2497
2498 return found_renames;
2499}
2500
2501_FORCE_INLINE_ static String builtin_escape(const String &p_str, bool p_builtin) {
2502 if (p_builtin) {
2503 return p_str.replace("\"", "\\\"");
2504 } else {
2505 return p_str;
2506 }
2507}
2508
2509void ProjectConverter3To4::rename_gdscript_keywords(Vector<SourceLine> &source_lines, const RegExContainer &reg_container, bool builtin) {
2510 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";
2511
2512 for (SourceLine &source_line : source_lines) {
2513 if (source_line.is_comment) {
2514 continue;
2515 }
2516
2517 String &line = source_line.line;
2518 if (uint64_t(line.length()) <= maximum_line_length) {
2519 if (line.contains("export")) {
2520 line = reg_container.keyword_gdscript_export_single.sub(line, "@export", true);
2521 }
2522 if (line.contains("export")) {
2523 line = reg_container.keyword_gdscript_export_mutli.sub(line, "$1@export", true);
2524 }
2525 if (line.contains("onready")) {
2526 line = reg_container.keyword_gdscript_onready.sub(line, "@onready", true);
2527 }
2528 if (line.contains("remote")) {
2529 line = reg_container.keyword_gdscript_remote.sub(line, builtin_escape("@rpc(\"any_peer\") func", builtin), true);
2530 }
2531 if (line.contains("remote")) {
2532 line = reg_container.keyword_gdscript_remotesync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\") func", builtin), true);
2533 }
2534 if (line.contains("sync")) {
2535 line = reg_container.keyword_gdscript_sync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\") func", builtin), true);
2536 }
2537 if (line.contains("slave")) {
2538 line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true);
2539 }
2540 if (line.contains("puppet")) {
2541 line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true);
2542 }
2543 if (line.contains("puppet")) {
2544 line = reg_container.keyword_gdscript_puppetsync.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
2545 }
2546 if (line.contains("master")) {
2547 line = reg_container.keyword_gdscript_master.sub(line, error_message + "@rpc func", true);
2548 }
2549 if (line.contains("master")) {
2550 line = reg_container.keyword_gdscript_mastersync.sub(line, error_message + builtin_escape("@rpc(\"call_local\") func", builtin), true);
2551 }
2552 }
2553 }
2554}
2555
2556Vector<String> ProjectConverter3To4::check_for_rename_gdscript_keywords(Vector<String> &lines, const RegExContainer &reg_container, bool builtin) {
2557 Vector<String> found_renames;
2558
2559 int current_line = 1;
2560 for (String &line : lines) {
2561 if (uint64_t(line.length()) <= maximum_line_length) {
2562 String old;
2563
2564 if (line.contains("tool")) {
2565 old = line;
2566 line = reg_container.keyword_gdscript_tool.sub(line, "@tool", true);
2567 if (old != line) {
2568 found_renames.append(line_formatter(current_line, "tool", "@tool", line));
2569 }
2570 }
2571
2572 if (line.contains("export")) {
2573 old = line;
2574 line = reg_container.keyword_gdscript_export_single.sub(line, "$1@export", true);
2575 if (old != line) {
2576 found_renames.append(line_formatter(current_line, "export", "@export", line));
2577 }
2578 }
2579
2580 if (line.contains("export")) {
2581 old = line;
2582 line = reg_container.keyword_gdscript_export_mutli.sub(line, "@export", true);
2583 if (old != line) {
2584 found_renames.append(line_formatter(current_line, "export", "@export", line));
2585 }
2586 }
2587
2588 if (line.contains("onready")) {
2589 old = line;
2590 line = reg_container.keyword_gdscript_tool.sub(line, "@onready", true);
2591 if (old != line) {
2592 found_renames.append(line_formatter(current_line, "onready", "@onready", line));
2593 }
2594 }
2595
2596 if (line.contains("remote")) {
2597 old = line;
2598 line = reg_container.keyword_gdscript_remote.sub(line, builtin_escape("@rpc(\"any_peer\") func", builtin), true);
2599 if (old != line) {
2600 found_renames.append(line_formatter(current_line, "remote func", builtin_escape("@rpc(\"any_peer\") func", builtin), line));
2601 }
2602 }
2603
2604 if (line.contains("remote")) {
2605 old = line;
2606 line = reg_container.keyword_gdscript_remotesync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), true);
2607 if (old != line) {
2608 found_renames.append(line_formatter(current_line, "remotesync func", builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), line));
2609 }
2610 }
2611
2612 if (line.contains("sync")) {
2613 old = line;
2614 line = reg_container.keyword_gdscript_sync.sub(line, builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), true);
2615 if (old != line) {
2616 found_renames.append(line_formatter(current_line, "sync func", builtin_escape("@rpc(\"any_peer\", \"call_local\")) func", builtin), line));
2617 }
2618 }
2619
2620 if (line.contains("slave")) {
2621 old = line;
2622 line = reg_container.keyword_gdscript_slave.sub(line, "@rpc func", true);
2623 if (old != line) {
2624 found_renames.append(line_formatter(current_line, "slave func", "@rpc func", line));
2625 }
2626 }
2627
2628 if (line.contains("puppet")) {
2629 old = line;
2630 line = reg_container.keyword_gdscript_puppet.sub(line, "@rpc func", true);
2631 if (old != line) {
2632 found_renames.append(line_formatter(current_line, "puppet func", "@rpc func", line));
2633 }
2634 }
2635
2636 if (line.contains("puppet")) {
2637 old = line;
2638 line = reg_container.keyword_gdscript_puppetsync.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
2639 if (old != line) {
2640 found_renames.append(line_formatter(current_line, "puppetsync func", builtin_escape("@rpc(\"call_local\") func", builtin), line));
2641 }
2642 }
2643
2644 if (line.contains("master")) {
2645 old = line;
2646 line = reg_container.keyword_gdscript_master.sub(line, "@rpc func", true);
2647 if (old != line) {
2648 found_renames.append(line_formatter(current_line, "master func", "@rpc func", line));
2649 }
2650 }
2651
2652 if (line.contains("master")) {
2653 old = line;
2654 line = reg_container.keyword_gdscript_master.sub(line, builtin_escape("@rpc(\"call_local\") func", builtin), true);
2655 if (old != line) {
2656 found_renames.append(line_formatter(current_line, "mastersync func", builtin_escape("@rpc(\"call_local\") func", builtin), line));
2657 }
2658 }
2659 }
2660 current_line++;
2661 }
2662
2663 return found_renames;
2664}
2665
2666void ProjectConverter3To4::rename_input_map_scancode(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
2667 // The old Special Key, now colliding with CMD_OR_CTRL.
2668 const int old_spkey = (1 << 24);
2669
2670 for (SourceLine &source_line : source_lines) {
2671 if (source_line.is_comment) {
2672 continue;
2673 }
2674
2675 String &line = source_line.line;
2676 if (uint64_t(line.length()) <= maximum_line_length) {
2677 TypedArray<RegExMatch> reg_match = reg_container.input_map_keycode.search_all(line);
2678
2679 for (int i = 0; i < reg_match.size(); ++i) {
2680 Ref<RegExMatch> match = reg_match[i];
2681 PackedStringArray strings = match->get_strings();
2682 int key = strings[3].to_int();
2683
2684 if (key & old_spkey) {
2685 // Create new key, clearing old Special Key and setting new one.
2686 key = (key & ~old_spkey) | (int)Key::SPECIAL;
2687
2688 line = line.replace(strings[0], String(",\"") + strings[1] + "scancode\":" + String::num_int64(key));
2689 }
2690 }
2691 }
2692 }
2693}
2694
2695void ProjectConverter3To4::rename_joypad_buttons_and_axes(Vector<SourceLine> &source_lines, const RegExContainer &reg_container) {
2696 for (SourceLine &source_line : source_lines) {
2697 if (source_line.is_comment) {
2698 continue;
2699 }
2700 String &line = source_line.line;
2701 if (uint64_t(line.length()) <= maximum_line_length) {
2702 // Remap button indexes.
2703 TypedArray<RegExMatch> reg_match = reg_container.joypad_button_index.search_all(line);
2704 for (int i = 0; i < reg_match.size(); ++i) {
2705 Ref<RegExMatch> match = reg_match[i];
2706 PackedStringArray strings = match->get_strings();
2707 String button_index_entry = strings[0];
2708 int button_index_value = strings[1].to_int();
2709 if (button_index_value == 6) { // L2 and R2 are mapped to joypad axes in Godot 4.
2710 line = line.replace("InputEventJoypadButton", "InputEventJoypadMotion");
2711 line = line.replace(button_index_entry, ",\"axis\":4,\"axis_value\":1.0");
2712 } else if (button_index_value == 7) {
2713 line = line.replace("InputEventJoypadButton", "InputEventJoypadMotion");
2714 line = line.replace(button_index_entry, ",\"axis\":5,\"axis_value\":1.0");
2715 } else if (button_index_value < 22) { // There are no mappings for indexes greater than 22 in both Godot 3 & 4.
2716 String pressure_and_pressed_properties = strings[2];
2717 line = line.replace(button_index_entry, ",\"button_index\":" + String::num_int64(reg_container.joypad_button_mappings[button_index_value]) + "," + pressure_and_pressed_properties);
2718 }
2719 }
2720 // Remap axes. Only L2 and R2 need remapping.
2721 reg_match = reg_container.joypad_axis.search_all(line);
2722 for (int i = 0; i < reg_match.size(); ++i) {
2723 Ref<RegExMatch> match = reg_match[i];
2724 PackedStringArray strings = match->get_strings();
2725 String axis_entry = strings[0];
2726 int axis_value = strings[1].to_int();
2727 if (axis_value == 6) {
2728 line = line.replace(axis_entry, ",\"axis\":4");
2729 } else if (axis_value == 7) {
2730 line = line.replace(axis_entry, ",\"axis\":5");
2731 }
2732 }
2733 }
2734 }
2735}
2736
2737Vector<String> ProjectConverter3To4::check_for_rename_joypad_buttons_and_axes(Vector<String> &lines, const RegExContainer &reg_container) {
2738 Vector<String> found_renames;
2739 int current_line = 1;
2740 for (String &line : lines) {
2741 if (uint64_t(line.length()) <= maximum_line_length) {
2742 // Remap button indexes.
2743 TypedArray<RegExMatch> reg_match = reg_container.joypad_button_index.search_all(line);
2744 for (int i = 0; i < reg_match.size(); ++i) {
2745 Ref<RegExMatch> match = reg_match[i];
2746 PackedStringArray strings = match->get_strings();
2747 String button_index_entry = strings[0];
2748 int button_index_value = strings[1].to_int();
2749 if (button_index_value == 6) { // L2 and R2 are mapped to joypad axes in Godot 4.
2750 found_renames.append(line_formatter(current_line, "InputEventJoypadButton", "InputEventJoypadMotion", line));
2751 found_renames.append(line_formatter(current_line, button_index_entry, ",\"axis\":4", line));
2752 } else if (button_index_value == 7) {
2753 found_renames.append(line_formatter(current_line, "InputEventJoypadButton", "InputEventJoypadMotion", line));
2754 found_renames.append(line_formatter(current_line, button_index_entry, ",\"axis\":5", line));
2755 } else if (button_index_value < 22) { // There are no mappings for indexes greater than 22 in both Godot 3 & 4.
2756 found_renames.append(line_formatter(current_line, "\"button_index\":" + strings[1], "\"button_index\":" + String::num_int64(reg_container.joypad_button_mappings[button_index_value]), line));
2757 }
2758 }
2759 // Remap axes. Only L2 and R2 need remapping.
2760 reg_match = reg_container.joypad_axis.search_all(line);
2761 for (int i = 0; i < reg_match.size(); ++i) {
2762 Ref<RegExMatch> match = reg_match[i];
2763 PackedStringArray strings = match->get_strings();
2764 String axis_entry = strings[0];
2765 int axis_value = strings[1].to_int();
2766 if (axis_value == 6) {
2767 found_renames.append(line_formatter(current_line, axis_entry, ",\"axis\":4", line));
2768 } else if (axis_value == 7) {
2769 found_renames.append(line_formatter(current_line, axis_entry, ",\"axis\":5", line));
2770 }
2771 }
2772 current_line++;
2773 }
2774 }
2775 return found_renames;
2776}
2777
2778Vector<String> ProjectConverter3To4::check_for_rename_input_map_scancode(Vector<String> &lines, const RegExContainer &reg_container) {
2779 Vector<String> found_renames;
2780
2781 // The old Special Key, now colliding with CMD_OR_CTRL.
2782 const int old_spkey = (1 << 24);
2783
2784 int current_line = 1;
2785 for (String &line : lines) {
2786 if (uint64_t(line.length()) <= maximum_line_length) {
2787 TypedArray<RegExMatch> reg_match = reg_container.input_map_keycode.search_all(line);
2788
2789 for (int i = 0; i < reg_match.size(); ++i) {
2790 Ref<RegExMatch> match = reg_match[i];
2791 PackedStringArray strings = match->get_strings();
2792 int key = strings[3].to_int();
2793
2794 if (key & old_spkey) {
2795 // Create new key, clearing old Special Key and setting new one.
2796 key = (key & ~old_spkey) | (int)Key::SPECIAL;
2797
2798 found_renames.append(line_formatter(current_line, strings[3], String::num_int64(key), line));
2799 }
2800 }
2801 }
2802 current_line++;
2803 }
2804 return found_renames;
2805}
2806
2807void ProjectConverter3To4::custom_rename(Vector<SourceLine> &source_lines, String from, String to) {
2808 RegEx reg = RegEx(String("\\b") + from + "\\b");
2809 CRASH_COND(!reg.is_valid());
2810 for (SourceLine &source_line : source_lines) {
2811 if (source_line.is_comment) {
2812 continue;
2813 }
2814
2815 String &line = source_line.line;
2816 if (uint64_t(line.length()) <= maximum_line_length) {
2817 line = reg.sub(line, to, true);
2818 }
2819 }
2820};
2821
2822Vector<String> ProjectConverter3To4::check_for_custom_rename(Vector<String> &lines, String from, String to) {
2823 Vector<String> found_renames;
2824
2825 RegEx reg = RegEx(String("\\b") + from + "\\b");
2826 CRASH_COND(!reg.is_valid());
2827
2828 int current_line = 1;
2829 for (String &line : lines) {
2830 if (uint64_t(line.length()) <= maximum_line_length) {
2831 TypedArray<RegExMatch> reg_match = reg.search_all(line);
2832 if (reg_match.size() > 0) {
2833 found_renames.append(line_formatter(current_line, from.replace("\\.", "."), to, line)); // Without replacing it will print "\.shader" instead ".shader".
2834 }
2835 }
2836 current_line++;
2837 }
2838 return found_renames;
2839}
2840
2841void ProjectConverter3To4::rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<SourceLine> &source_lines) {
2842 for (SourceLine &source_line : source_lines) {
2843 if (source_line.is_comment) {
2844 continue;
2845 }
2846
2847 String &line = source_line.line;
2848 if (uint64_t(line.length()) <= maximum_line_length) {
2849 for (unsigned int current_index = 0; current_index < cached_regexes.size(); current_index++) {
2850 if (line.contains(array[current_index][0])) {
2851 line = cached_regexes[current_index]->sub(line, array[current_index][1], true);
2852 }
2853 }
2854 }
2855 }
2856}
2857
2858Vector<String> ProjectConverter3To4::check_for_rename_common(const char *array[][2], LocalVector<RegEx *> &cached_regexes, Vector<String> &lines) {
2859 Vector<String> found_renames;
2860
2861 int current_line = 1;
2862
2863 for (String &line : lines) {
2864 if (uint64_t(line.length()) <= maximum_line_length) {
2865 for (unsigned int current_index = 0; current_index < cached_regexes.size(); current_index++) {
2866 if (line.contains(array[current_index][0])) {
2867 TypedArray<RegExMatch> reg_match = cached_regexes[current_index]->search_all(line);
2868 if (reg_match.size() > 0) {
2869 found_renames.append(line_formatter(current_line, array[current_index][0], array[current_index][1], line));
2870 }
2871 }
2872 }
2873 }
2874 current_line++;
2875 }
2876
2877 return found_renames;
2878}
2879
2880// Prints full info about renamed things e.g.:
2881// Line (67) remove -> remove_at - LINE """ doubler._blacklist.remove(0) """
2882String ProjectConverter3To4::line_formatter(int current_line, String from, String to, String line) {
2883 if (from.size() > 200) {
2884 from = from.substr(0, 197) + "...";
2885 }
2886 if (to.size() > 200) {
2887 to = to.substr(0, 197) + "...";
2888 }
2889 if (line.size() > 400) {
2890 line = line.substr(0, 397) + "...";
2891 }
2892
2893 from = from.strip_escapes();
2894 to = to.strip_escapes();
2895 line = line.replace("\r", "").replace("\n", "").strip_edges();
2896
2897 return vformat("Line(%d), %s -> %s - LINE \"\"\" %s \"\"\"", current_line, from, to, line);
2898}
2899
2900// Prints only full lines e.g.:
2901// Line (1) - FULL LINES - """yield(get_tree().create_timer(3), 'timeout')""" =====> """ await get_tree().create_timer(3).timeout """
2902String ProjectConverter3To4::simple_line_formatter(int current_line, String old_line, String new_line) {
2903 if (old_line.size() > 1000) {
2904 old_line = old_line.substr(0, 997) + "...";
2905 }
2906 if (new_line.size() > 1000) {
2907 new_line = new_line.substr(0, 997) + "...";
2908 }
2909
2910 old_line = old_line.replace("\r", "").replace("\n", "").strip_edges();
2911 new_line = new_line.replace("\r", "").replace("\n", "").strip_edges();
2912
2913 return vformat("Line (%d) - FULL LINES - \"\"\" %s \"\"\" =====> \"\"\" %s \"\"\"", current_line, old_line, new_line);
2914}
2915
2916// Collects string from vector strings
2917String ProjectConverter3To4::collect_string_from_vector(Vector<SourceLine> &vector) {
2918 String string = "";
2919 for (int i = 0; i < vector.size(); i++) {
2920 string += vector[i].line;
2921
2922 if (i != vector.size() - 1) {
2923 string += "\n";
2924 }
2925 }
2926 return string;
2927}
2928
2929#endif // MODULE_REGEX_ENABLED
2930
2931#endif // DISABLE_DEPRECATED
2932