summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/debugger/remote_debugger_peer.cpp2
-rw-r--r--core/input/input.cpp30
-rw-r--r--core/input/input.h2
-rw-r--r--core/io/dir_access.cpp2
-rw-r--r--core/io/file_access.cpp2
-rw-r--r--core/io/file_access_pack.cpp2
-rw-r--r--core/io/http_client_tcp.cpp2
-rw-r--r--core/io/image.cpp2
-rw-r--r--core/io/ip_address.cpp4
-rw-r--r--core/io/json.cpp14
-rw-r--r--core/io/pck_packer.cpp4
-rw-r--r--core/io/resource.h2
-rw-r--r--core/io/resource_format_binary.cpp4
-rw-r--r--core/io/resource_uid.cpp4
-rw-r--r--core/io/translation_loader_po.cpp2
-rw-r--r--core/math/aabb.h8
-rw-r--r--core/math/basis.h11
-rw-r--r--core/math/camera_matrix.cpp4
-rw-r--r--core/math/camera_matrix.h10
-rw-r--r--core/math/delaunay_2d.h1
-rw-r--r--core/math/dynamic_bvh.h6
-rw-r--r--core/math/expression.cpp64
-rw-r--r--core/math/face3.h6
-rw-r--r--core/math/geometry_2d.h4
-rw-r--r--core/math/plane.h5
-rw-r--r--core/math/quaternion.h3
-rw-r--r--core/math/rect2.cpp10
-rw-r--r--core/math/rect2.h218
-rw-r--r--core/math/rect2i.cpp42
-rw-r--r--core/math/rect2i.h245
-rw-r--r--core/math/transform_2d.cpp2
-rw-r--r--core/math/transform_2d.h7
-rw-r--r--core/math/transform_3d.h9
-rw-r--r--core/math/triangulate.h1
-rw-r--r--core/math/vector2.cpp92
-rw-r--r--core/math/vector2.h112
-rw-r--r--core/math/vector2i.cpp125
-rw-r--r--core/math/vector2i.h141
-rw-r--r--core/math/vector3.h3
-rw-r--r--core/multiplayer/multiplayer_api.cpp133
-rw-r--r--core/multiplayer/multiplayer_api.h41
-rw-r--r--core/multiplayer/multiplayer_replicator.cpp791
-rw-r--r--core/multiplayer/multiplayer_replicator.h138
-rw-r--r--core/multiplayer/rpc_manager.cpp56
-rw-r--r--core/object/class_db.cpp2
-rw-r--r--core/object/make_virtuals.py4
-rw-r--r--core/register_core_types.cpp2
-rw-r--r--core/string/char_utils.h92
-rw-r--r--core/string/translation.cpp22
-rw-r--r--core/string/ustring.cpp46
-rw-r--r--core/string/ustring.h4
-rw-r--r--core/variant/method_ptrcall.h1
-rw-r--r--core/variant/native_ptr.h46
-rw-r--r--core/variant/variant.h4
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--core/variant/variant_parser.cpp18
-rw-r--r--doc/classes/BoxMesh.xml2
-rw-r--r--doc/classes/BoxShape3D.xml2
-rw-r--r--doc/classes/CapsuleMesh.xml4
-rw-r--r--doc/classes/CapsuleShape3D.xml4
-rw-r--r--doc/classes/Control.xml6
-rw-r--r--doc/classes/CylinderMesh.xml4
-rw-r--r--doc/classes/CylinderShape3D.xml2
-rw-r--r--doc/classes/DisplayServer.xml13
-rw-r--r--doc/classes/Input.xml4
-rw-r--r--doc/classes/MultiplayerAPI.xml2
-rw-r--r--doc/classes/MultiplayerReplicator.xml191
-rw-r--r--doc/classes/MultiplayerSpawner.xml47
-rw-r--r--doc/classes/MultiplayerSynchronizer.xml17
-rw-r--r--doc/classes/Node.xml12
-rw-r--r--doc/classes/PopupMenu.xml7
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--doc/classes/RichTextLabel.xml15
-rw-r--r--doc/classes/SceneReplicationConfig.xml61
-rw-r--r--doc/classes/String.xml7
-rw-r--r--doc/classes/TextServerExtension.xml17
-rw-r--r--doc/classes/Window.xml5
-rw-r--r--editor/animation_track_editor.cpp2
-rw-r--r--editor/array_property_edit.cpp4
-rw-r--r--editor/code_editor.cpp4
-rw-r--r--editor/connections_dialog.cpp4
-rw-r--r--editor/debugger/editor_debugger_inspector.cpp2
-rw-r--r--editor/dependency_editor.cpp2
-rw-r--r--editor/editor_atlas_packer.cpp3
-rw-r--r--editor/editor_atlas_packer.h5
-rw-r--r--editor/editor_export.cpp8
-rw-r--r--editor/editor_feature_profile.cpp2
-rw-r--r--editor/editor_help.cpp20
-rw-r--r--editor/editor_inspector.cpp14
-rw-r--r--editor/editor_locale_dialog.cpp14
-rw-r--r--editor/editor_node.cpp72
-rw-r--r--editor/editor_node.h1
-rw-r--r--editor/editor_properties.cpp48
-rw-r--r--editor/editor_properties_array_dict.cpp47
-rw-r--r--editor/editor_properties_array_dict.h1
-rw-r--r--editor/editor_sectioned_inspector.cpp4
-rw-r--r--editor/filesystem_dock.cpp10
-rw-r--r--editor/find_in_files.cpp9
-rw-r--r--editor/import/collada.cpp8
-rw-r--r--editor/import/resource_importer_scene.cpp4
-rw-r--r--editor/plugins/animation_blend_tree_editor_plugin.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp4
-rw-r--r--editor/plugins/animation_state_machine_editor.cpp2
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp2
-rw-r--r--editor/plugins/editor_preview_plugins.cpp14
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/replication_editor_plugin.cpp390
-rw-r--r--editor/plugins/replication_editor_plugin.h108
-rw-r--r--editor/plugins/resource_preloader_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp22
-rw-r--r--editor/plugins/script_editor_plugin.h2
-rw-r--r--editor/plugins/script_text_editor.cpp2
-rw-r--r--editor/plugins/theme_editor_plugin.cpp2
-rw-r--r--editor/plugins/tiles/tile_map_editor.cpp2
-rw-r--r--editor/plugins/tiles/tile_set_editor.cpp2
-rw-r--r--editor/project_manager.cpp2
-rw-r--r--editor/project_settings_editor.cpp24
-rw-r--r--editor/property_editor.cpp2
-rw-r--r--editor/property_selector.cpp4
-rw-r--r--editor/rename_dialog.cpp4
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--editor/script_create_dialog.cpp6
-rw-r--r--editor/shader_create_dialog.cpp2
-rw-r--r--main/main.cpp14
-rw-r--r--modules/bullet/shape_bullet.h10
-rw-r--r--modules/csg/csg_shape.cpp8
-rw-r--r--modules/csg/csg_shape.h2
-rw-r--r--modules/csg/doc_classes/CSGBox3D.xml2
-rw-r--r--modules/csg/doc_classes/CSGCylinder3D.xml4
-rw-r--r--modules/csg/doc_classes/CSGTorus3D.xml4
-rw-r--r--modules/fbx/editor_scene_importer_fbx.cpp2
-rw-r--r--modules/gdnative/gdnative/packed_arrays.cpp2
-rw-r--r--modules/gdnative/gdnative/rect2.cpp2
-rw-r--r--modules/gdnative/gdnative/transform2d.cpp1
-rw-r--r--modules/gdnative/gdnative/vector2.cpp2
-rw-r--r--modules/gdnative/nativescript/api_generator.cpp8
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp22
-rw-r--r--modules/gdscript/gdscript.cpp9
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp133
-rw-r--r--modules/gdscript/gdscript_analyzer.h2
-rw-r--r--modules/gdscript/gdscript_compiler.cpp16
-rw-r--r--modules/gdscript/gdscript_editor.cpp16
-rw-r--r--modules/gdscript/gdscript_parser.cpp2
-rw-r--r--modules/gdscript/gdscript_parser.h5
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp50
-rw-r--r--modules/gdscript/gdscript_warning.cpp4
-rw-r--r--modules/gdscript/gdscript_warning.h1
-rw-r--r--modules/gdscript/language_server/gdscript_extend_parser.cpp4
-rw-r--r--modules/gdscript/language_server/gdscript_workspace.cpp4
-rw-r--r--modules/gdscript/language_server/lsp.hpp2
-rw-r--r--modules/gdscript/tests/gdscript_test_runner.cpp42
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd10
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd8
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd14
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd21
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out7
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd13
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd15
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out21
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd1
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out2
-rw-r--r--modules/gltf/gltf_document.cpp37
-rw-r--r--modules/mono/csharp_script.h2
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs25
-rw-r--r--modules/mono/editor/bindings_generator.cpp4
-rw-r--r--modules/ogg/ogg_packet_sequence.h2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp16
-rw-r--r--modules/text_server_fb/text_server_fb.cpp24
-rw-r--r--modules/visual_script/editor/visual_script_editor.cpp2
-rw-r--r--modules/visual_script/editor/visual_script_editor.h18
-rw-r--r--modules/visual_script/editor/visual_script_property_selector.cpp2
-rw-r--r--modules/visual_script/editor/visual_script_property_selector.h8
-rw-r--r--modules/visual_script/visual_script_expression.cpp16
-rw-r--r--platform/android/display_server_android.cpp10
-rw-r--r--platform/android/display_server_android.h1
-rw-r--r--platform/android/export/export_plugin.cpp10
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java8
-rw-r--r--platform/android/java_godot_io_wrapper.cpp14
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/iphone/display_server_iphone.h1
-rw-r--r--platform/iphone/display_server_iphone.mm4
-rw-r--r--platform/iphone/export/export_plugin.h2
-rw-r--r--platform/javascript/display_server_javascript.cpp7
-rw-r--r--platform/javascript/display_server_javascript.h1
-rw-r--r--platform/javascript/js/engine/config.js43
-rw-r--r--platform/linuxbsd/display_server_x11.cpp64
-rw-r--r--platform/linuxbsd/display_server_x11.h1
-rw-r--r--platform/osx/display_server_osx.h1
-rw-r--r--platform/osx/display_server_osx.mm20
-rw-r--r--platform/osx/export/export_plugin.cpp18
-rw-r--r--platform/osx/export/export_plugin.h2
-rw-r--r--platform/windows/display_server_windows.cpp201
-rw-r--r--platform/windows/display_server_windows.h5
-rw-r--r--platform/windows/export/export_plugin.cpp48
-rw-r--r--platform/windows/export/export_plugin.h1
-rw-r--r--scene/3d/skeleton_3d.cpp2
-rw-r--r--scene/SCsub1
-rw-r--r--scene/animation/animation_blend_tree.cpp2
-rw-r--r--scene/animation/animation_node_state_machine.cpp6
-rw-r--r--scene/animation/animation_player.cpp4
-rw-r--r--scene/animation/animation_tree.cpp4
-rw-r--r--scene/gui/base_button.cpp2
-rw-r--r--scene/gui/code_edit.cpp28
-rw-r--r--scene/gui/control.cpp6
-rw-r--r--scene/gui/control.h2
-rw-r--r--scene/gui/menu_button.cpp8
-rw-r--r--scene/gui/option_button.cpp9
-rw-r--r--scene/gui/popup_menu.cpp44
-rw-r--r--scene/gui/rich_text_label.cpp40
-rw-r--r--scene/gui/rich_text_label.h13
-rw-r--r--scene/gui/text_edit.cpp30
-rw-r--r--scene/main/node.cpp26
-rw-r--r--scene/main/node.h3
-rw-r--r--scene/main/scene_tree.cpp1
-rw-r--r--scene/main/scene_tree.h2
-rw-r--r--scene/main/viewport.cpp2
-rw-r--r--scene/main/window.cpp1
-rw-r--r--scene/main/window.h1
-rw-r--r--scene/multiplayer/SCsub5
-rw-r--r--scene/multiplayer/multiplayer_spawner.cpp227
-rw-r--r--scene/multiplayer/multiplayer_spawner.h101
-rw-r--r--scene/multiplayer/multiplayer_synchronizer.cpp158
-rw-r--r--scene/multiplayer/multiplayer_synchronizer.h72
-rw-r--r--scene/multiplayer/scene_replication_interface.cpp415
-rw-r--r--scene/multiplayer/scene_replication_interface.h84
-rw-r--r--scene/multiplayer/scene_replication_state.cpp258
-rw-r--r--scene/multiplayer/scene_replication_state.h121
-rw-r--r--scene/property_utils.cpp2
-rw-r--r--scene/register_scene_types.cpp8
-rw-r--r--scene/resources/box_shape_3d.cpp2
-rw-r--r--scene/resources/capsule_shape_3d.h4
-rw-r--r--scene/resources/cylinder_shape_3d.h2
-rw-r--r--scene/resources/primitive_meshes.h10
-rw-r--r--scene/resources/resource_format_text.cpp4
-rw-r--r--scene/resources/scene_replication_config.cpp187
-rw-r--r--scene/resources/scene_replication_config.h90
-rw-r--r--scene/resources/syntax_highlighter.cpp14
-rw-r--r--scene/resources/theme.cpp4
-rw-r--r--scene/resources/visual_shader.cpp16
-rw-r--r--servers/display_server.cpp2
-rw-r--r--servers/display_server.h6
-rw-r--r--servers/display_server_headless.h1
-rw-r--r--servers/rendering/rendering_device_binds.cpp4
-rw-r--r--servers/rendering/shader_compiler.cpp11
-rw-r--r--servers/rendering/shader_language.cpp162
-rw-r--r--servers/rendering/shader_language.h5
-rw-r--r--servers/text/text_server_extension.cpp26
-rw-r--r--servers/text/text_server_extension.h14
-rw-r--r--servers/text_server.h1
-rw-r--r--tests/core/math/test_math.cpp6
-rw-r--r--tests/core/math/test_rect2.h278
-rw-r--r--tests/core/math/test_rect2i.h311
-rw-r--r--tests/core/math/test_vector2.h1
-rw-r--r--tests/core/math/test_vector2i.h1
-rw-r--r--tests/core/string/test_string.h13
-rw-r--r--tests/test_main.cpp1
-rw-r--r--thirdparty/README.md6
-rw-r--r--thirdparty/libwebp/AUTHORS1
-rw-r--r--thirdparty/libwebp/src/dec/vp8_dec.c2
-rw-r--r--thirdparty/libwebp/src/dec/vp8i_dec.h2
-rw-r--r--thirdparty/libwebp/src/dec/vp8l_dec.c2
-rw-r--r--thirdparty/libwebp/src/demux/anim_decode.c40
-rw-r--r--thirdparty/libwebp/src/demux/demux.c2
-rw-r--r--thirdparty/libwebp/src/dsp/dsp.h7
-rw-r--r--thirdparty/libwebp/src/dsp/enc_neon.c2
-rw-r--r--thirdparty/libwebp/src/dsp/lossless.c58
-rw-r--r--thirdparty/libwebp/src/dsp/lossless.h45
-rw-r--r--thirdparty/libwebp/src/dsp/lossless_common.h2
-rw-r--r--thirdparty/libwebp/src/dsp/lossless_enc.c2
-rw-r--r--thirdparty/libwebp/src/dsp/lossless_mips_dsp_r2.c37
-rw-r--r--thirdparty/libwebp/src/dsp/lossless_neon.c20
-rw-r--r--thirdparty/libwebp/src/dsp/lossless_sse2.c41
-rw-r--r--thirdparty/libwebp/src/dsp/msa_macro.h5
-rw-r--r--thirdparty/libwebp/src/dsp/neon.h7
-rw-r--r--thirdparty/libwebp/src/dsp/yuv.h2
-rw-r--r--thirdparty/libwebp/src/enc/frame_enc.c9
-rw-r--r--thirdparty/libwebp/src/enc/predictor_enc.c2
-rw-r--r--thirdparty/libwebp/src/enc/quant_enc.c58
-rw-r--r--thirdparty/libwebp/src/enc/vp8i_enc.h2
-rw-r--r--thirdparty/libwebp/src/mux/muxi.h2
-rw-r--r--thirdparty/libwebp/src/utils/huffman_encode_utils.c2
-rw-r--r--thirdparty/libwebp/src/utils/quant_levels_dec_utils.c2
-rw-r--r--thirdparty/libwebp/src/utils/utils.c2
-rw-r--r--thirdparty/libwebp/src/webp/decode.h2
-rw-r--r--thirdparty/misc/patches/polypartition-godot-types.patch85
-rw-r--r--thirdparty/misc/polypartition.cpp2
300 files changed, 5396 insertions, 3036 deletions
diff --git a/core/debugger/remote_debugger_peer.cpp b/core/debugger/remote_debugger_peer.cpp
index 8c6e95e276..7c7d38ab0a 100644
--- a/core/debugger/remote_debugger_peer.cpp
+++ b/core/debugger/remote_debugger_peer.cpp
@@ -226,7 +226,7 @@ RemoteDebuggerPeer *RemoteDebuggerPeerTCP::create(const String &p_uri) {
String debug_host = p_uri.replace("tcp://", "");
uint16_t debug_port = 6007;
- if (debug_host.find(":") != -1) {
+ if (debug_host.contains(":")) {
int sep_pos = debug_host.rfind(":");
debug_port = debug_host.substr(sep_pos + 1).to_int();
debug_host = debug_host.substr(0, sep_pos);
diff --git a/core/input/input.cpp b/core/input/input.cpp
index 7106bd0745..d36d0f4da0 100644
--- a/core/input/input.cpp
+++ b/core/input/input.cpp
@@ -190,32 +190,37 @@ void Input::VelocityTrack::update(const Vector2 &p_delta_p) {
float delta_t = tdiff / 1000000.0;
last_tick = tick;
+ if (delta_t > max_ref_frame) {
+ // First movement in a long time, reset and start again.
+ velocity = Vector2();
+ accum = p_delta_p;
+ accum_t = 0;
+ return;
+ }
+
accum += p_delta_p;
accum_t += delta_t;
- if (accum_t > max_ref_frame * 10) {
- accum_t = max_ref_frame * 10;
+ if (accum_t < min_ref_frame) {
+ // Not enough time has passed to calculate speed precisely.
+ return;
}
- while (accum_t >= min_ref_frame) {
- float slice_t = min_ref_frame / accum_t;
- Vector2 slice = accum * slice_t;
- accum = accum - slice;
- accum_t -= min_ref_frame;
-
- velocity = (slice / min_ref_frame).lerp(velocity, min_ref_frame / max_ref_frame);
- }
+ velocity = accum / accum_t;
+ accum = Vector2();
+ accum_t = 0;
}
void Input::VelocityTrack::reset() {
last_tick = OS::get_singleton()->get_ticks_usec();
velocity = Vector2();
+ accum = Vector2();
accum_t = 0;
}
Input::VelocityTrack::VelocityTrack() {
min_ref_frame = 0.1;
- max_ref_frame = 0.3;
+ max_ref_frame = 3.0;
reset();
}
@@ -719,7 +724,8 @@ Point2 Input::get_mouse_position() const {
return mouse_pos;
}
-Point2 Input::get_last_mouse_velocity() const {
+Point2 Input::get_last_mouse_velocity() {
+ mouse_velocity_track.update(Vector2());
return mouse_velocity_track.velocity;
}
diff --git a/core/input/input.h b/core/input/input.h
index 80f260f30e..ab2cd377f4 100644
--- a/core/input/input.h
+++ b/core/input/input.h
@@ -270,7 +270,7 @@ public:
Vector3 get_gyroscope() const;
Point2 get_mouse_position() const;
- Vector2 get_last_mouse_velocity() const;
+ Vector2 get_last_mouse_velocity();
MouseButton get_mouse_button_mask() const;
void warp_mouse_position(const Vector2 &p_to);
diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp
index db0758d7fc..86d8dea3d9 100644
--- a/core/io/dir_access.cpp
+++ b/core/io/dir_access.cpp
@@ -159,7 +159,7 @@ Error DirAccess::make_dir_recursive(String p_dir) {
base = full_dir.substr(0, pos + 1);
} else if (full_dir.begins_with("/")) {
base = "/";
- } else if (full_dir.find(":/") != -1) {
+ } else if (full_dir.contains(":/")) {
base = full_dir.substr(0, full_dir.find(":/") + 2);
} else {
ERR_FAIL_V(ERR_INVALID_PARAMETER);
diff --git a/core/io/file_access.cpp b/core/io/file_access.cpp
index bb92648484..86836454c5 100644
--- a/core/io/file_access.cpp
+++ b/core/io/file_access.cpp
@@ -538,7 +538,7 @@ void FileAccess::store_csv_line(const Vector<String> &p_values, const String &p_
for (int i = 0; i < size; ++i) {
String value = p_values[i];
- if (value.find("\"") != -1 || value.find(p_delim) != -1 || value.find("\n") != -1) {
+ if (value.contains("\"") || value.contains(p_delim) || value.contains("\n")) {
value = "\"" + value.replace("\"", "\"\"") + "\"";
}
if (i < size - 1) {
diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp
index 3882627103..7dbea96c3d 100644
--- a/core/io/file_access_pack.cpp
+++ b/core/io/file_access_pack.cpp
@@ -70,7 +70,7 @@ void PackedData::add_path(const String &p_pkg_path, const String &p_path, uint64
String p = p_path.replace_first("res://", "");
PackedDir *cd = root;
- if (p.find("/") != -1) { //in a subdir
+ if (p.contains("/")) { //in a subdir
Vector<String> ds = p.get_base_dir().split("/");
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index e61833ce7c..f920799677 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -614,7 +614,7 @@ PackedByteArray HTTPClientTCP::read_response_body_chunk() {
for (int i = 0; i < chunk.size() - 2; i++) {
char c = chunk[i];
int v = 0;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a' + 10;
diff --git a/core/io/image.cpp b/core/io/image.cpp
index 9df2b6835c..4f72599faf 100644
--- a/core/io/image.cpp
+++ b/core/io/image.cpp
@@ -2056,7 +2056,7 @@ void Image::create(const char **p_xpm) {
for (int i = 0; i < 6; i++) {
char v = line_ptr[i];
- if (v >= '0' && v <= '9') {
+ if (is_digit(v)) {
v -= '0';
} else if (v >= 'A' && v <= 'F') {
v = (v - 'A') + 10;
diff --git a/core/io/ip_address.cpp b/core/io/ip_address.cpp
index 38f99a08a4..d183c60798 100644
--- a/core/io/ip_address.cpp
+++ b/core/io/ip_address.cpp
@@ -71,7 +71,7 @@ static void _parse_hex(const String &p_string, int p_start, uint8_t *p_dst) {
int n = 0;
char32_t c = p_string[i];
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
n = c - '0';
} else if (c >= 'a' && c <= 'f') {
n = 10 + (c - 'a');
@@ -113,7 +113,7 @@ void IPAddress::_parse_ipv6(const String &p_string) {
} else if (c == '.') {
part_ipv4 = true;
- } else if ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F')) {
+ } else if (is_hex_digit(c)) {
if (!part_found) {
parts[parts_idx++] = i;
part_found = true;
diff --git a/core/io/json.cpp b/core/io/json.cpp
index 7b642f6a59..4b745dff44 100644
--- a/core/io/json.cpp
+++ b/core/io/json.cpp
@@ -229,12 +229,12 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
r_err_str = "Unterminated String";
return ERR_PARSE_ERROR;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ if (!is_hex_digit(c)) {
r_err_str = "Malformed hex constant in string";
return ERR_PARSE_ERROR;
}
char32_t v;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
@@ -265,12 +265,12 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
r_err_str = "Unterminated String";
return ERR_PARSE_ERROR;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ if (!is_hex_digit(c)) {
r_err_str = "Malformed hex constant in string";
return ERR_PARSE_ERROR;
}
char32_t v;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
@@ -326,7 +326,7 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
break;
}
- if (p_str[index] == '-' || (p_str[index] >= '0' && p_str[index] <= '9')) {
+ if (p_str[index] == '-' || is_digit(p_str[index])) {
//a number
const char32_t *rptr;
double number = String::to_float(&p_str[index], &rptr);
@@ -335,10 +335,10 @@ Error JSON::_get_token(const char32_t *p_str, int &index, int p_len, Token &r_to
r_token.value = number;
return OK;
- } else if ((p_str[index] >= 'A' && p_str[index] <= 'Z') || (p_str[index] >= 'a' && p_str[index] <= 'z')) {
+ } else if (is_ascii_char(p_str[index])) {
String id;
- while ((p_str[index] >= 'A' && p_str[index] <= 'Z') || (p_str[index] >= 'a' && p_str[index] <= 'z')) {
+ while (is_ascii_char(p_str[index])) {
id += p_str[index];
index++;
}
diff --git a/core/io/pck_packer.cpp b/core/io/pck_packer.cpp
index 221a680130..272ace3438 100644
--- a/core/io/pck_packer.cpp
+++ b/core/io/pck_packer.cpp
@@ -62,7 +62,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &
int v = 0;
if (i * 2 < _key.length()) {
char32_t ct = _key[i * 2];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
@@ -72,7 +72,7 @@ Error PCKPacker::pck_start(const String &p_file, int p_alignment, const String &
if (i * 2 + 1 < _key.length()) {
char32_t ct = _key[i * 2 + 1];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
diff --git a/core/io/resource.h b/core/io/resource.h
index dea2160616..b1e1c15541 100644
--- a/core/io/resource.h
+++ b/core/io/resource.h
@@ -105,7 +105,7 @@ public:
virtual void set_path(const String &p_path, bool p_take_over = false);
String get_path() const;
- _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.find("::") != -1 || path_cache.begins_with("local://"); }
+ _FORCE_INLINE_ bool is_built_in() const { return path_cache.is_empty() || path_cache.contains("::") || path_cache.begins_with("local://"); }
static String generate_scene_unique_id();
void set_scene_unique_id(const String &p_id);
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 200d5fafde..8588bab0be 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -335,7 +335,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {
String exttype = get_unicode_string();
String path = get_unicode_string();
- if (path.find("://") == -1 && path.is_relative_path()) {
+ if (!path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path));
}
@@ -626,7 +626,7 @@ Error ResourceLoaderBinary::load() {
path = remaps[path];
}
- if (path.find("://") == -1 && path.is_relative_path()) {
+ if (!path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path));
}
diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp
index 1a16d5b47a..776756e64e 100644
--- a/core/io/resource_uid.cpp
+++ b/core/io/resource_uid.cpp
@@ -71,9 +71,9 @@ ResourceUID::ID ResourceUID::text_to_id(const String &p_text) const {
for (uint32_t i = 6; i < l; i++) {
uid *= base;
uint32_t c = p_text[i];
- if (c >= 'a' && c <= 'z') {
+ if (is_ascii_lower_case(c)) {
uid += c - 'a';
- } else if (c >= '0' && c <= '9') {
+ } else if (is_digit(c)) {
uid += c - '0' + char_count;
} else {
return INVALID_ID;
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 2f6742bd7a..8d3e58cad1 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -179,7 +179,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
}
if (l.is_empty() || l.begins_with("#")) {
- if (l.find("fuzzy") != -1) {
+ if (l.contains("fuzzy")) {
skip_next = true;
}
line++;
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 3d19410ddf..cb6f05e9ea 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -36,13 +36,13 @@
#include "core/math/vector3.h"
/**
- * AABB / AABB (Axis Aligned Bounding Box)
- * This is implemented by a point (position) and the box size
+ * AABB (Axis Aligned Bounding Box)
+ * This is implemented by a point (position) and the box size.
*/
+
class Variant;
-class _NO_DISCARD_ AABB {
-public:
+struct _NO_DISCARD_ AABB {
Vector3 position;
Vector3 size;
diff --git a/core/math/basis.h b/core/math/basis.h
index 802da82089..683f05150c 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -34,11 +34,7 @@
#include "core/math/quaternion.h"
#include "core/math/vector3.h"
-class _NO_DISCARD_ Basis {
-private:
- void _set_diagonal(const Vector3 &p_diag);
-
-public:
+struct _NO_DISCARD_ Basis {
Vector3 elements[3] = {
Vector3(1, 0, 0),
Vector3(0, 1, 0),
@@ -263,6 +259,10 @@ public:
}
_FORCE_INLINE_ Basis() {}
+
+private:
+ // Helper method.
+ void _set_diagonal(const Vector3 &p_diag);
};
_FORCE_INLINE_ void Basis::operator*=(const Basis &p_matrix) {
@@ -334,4 +334,5 @@ real_t Basis::determinant() const {
elements[1][0] * (elements[0][1] * elements[2][2] - elements[2][1] * elements[0][2]) +
elements[2][0] * (elements[0][1] * elements[1][2] - elements[1][1] * elements[0][2]);
}
+
#endif // BASIS_H
diff --git a/core/math/camera_matrix.cpp b/core/math/camera_matrix.cpp
index 2902ca59b9..f5d746ef0f 100644
--- a/core/math/camera_matrix.cpp
+++ b/core/math/camera_matrix.cpp
@@ -30,7 +30,11 @@
#include "camera_matrix.h"
+#include "core/math/aabb.h"
#include "core/math/math_funcs.h"
+#include "core/math/plane.h"
+#include "core/math/rect2.h"
+#include "core/math/transform_3d.h"
#include "core/string/print_string.h"
float CameraMatrix::determinant() const {
diff --git a/core/math/camera_matrix.h b/core/math/camera_matrix.h
index da1aba7562..285d2ae384 100644
--- a/core/math/camera_matrix.h
+++ b/core/math/camera_matrix.h
@@ -31,8 +31,14 @@
#ifndef CAMERA_MATRIX_H
#define CAMERA_MATRIX_H
-#include "core/math/rect2.h"
-#include "core/math/transform_3d.h"
+#include "core/math/math_defs.h"
+#include "core/math/vector3.h"
+
+struct AABB;
+struct Plane;
+struct Rect2;
+struct Transform3D;
+struct Vector2;
struct CameraMatrix {
enum Planes {
diff --git a/core/math/delaunay_2d.h b/core/math/delaunay_2d.h
index 08f5df8472..c39997d6a9 100644
--- a/core/math/delaunay_2d.h
+++ b/core/math/delaunay_2d.h
@@ -32,6 +32,7 @@
#define DELAUNAY_2D_H
#include "core/math/rect2.h"
+#include "core/templates/vector.h"
class Delaunay2D {
public:
diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h
index 3041cdf268..50ec2c2b30 100644
--- a/core/math/dynamic_bvh.h
+++ b/core/math/dynamic_bvh.h
@@ -28,8 +28,8 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef DYNAMICBVH_H
-#define DYNAMICBVH_H
+#ifndef DYNAMIC_BVH_H
+#define DYNAMIC_BVH_H
#include "core/math/aabb.h"
#include "core/templates/list.h"
@@ -474,4 +474,4 @@ void DynamicBVH::ray_query(const Vector3 &p_from, const Vector3 &p_to, QueryResu
} while (depth > 0);
}
-#endif // DYNAMICBVH_H
+#endif // DYNAMIC_BVH_H
diff --git a/core/math/expression.cpp b/core/math/expression.cpp
index 203566579d..b447d09887 100644
--- a/core/math/expression.cpp
+++ b/core/math/expression.cpp
@@ -37,10 +37,6 @@
#include "core/os/os.h"
#include "core/variant/variant_parser.h"
-static bool _is_number(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
Error Expression::_get_token(Token &r_token) {
while (true) {
#define GET_CHAR() (str_ofs >= expression.length() ? 0 : expression[str_ofs++])
@@ -88,7 +84,7 @@ Error Expression::_get_token(Token &r_token) {
r_token.type = TK_INPUT;
int index = 0;
do {
- if (!_is_number(expression[str_ofs])) {
+ if (!is_digit(expression[str_ofs])) {
_set_error("Expected number after '$'");
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
@@ -97,7 +93,7 @@ Error Expression::_get_token(Token &r_token) {
index += expression[str_ofs] - '0';
str_ofs++;
- } while (_is_number(expression[str_ofs]));
+ } while (is_digit(expression[str_ofs]));
r_token.value = index;
return OK;
@@ -247,13 +243,13 @@ Error Expression::_get_token(Token &r_token) {
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
- if (!(_is_number(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ if (!is_hex_digit(c)) {
_set_error("Malformed hex constant in string");
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
char32_t v;
- if (_is_number(c)) {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
@@ -328,27 +324,38 @@ Error Expression::_get_token(Token &r_token) {
}
char32_t next_char = (str_ofs >= expression.length()) ? 0 : expression[str_ofs];
- if (_is_number(cchar) || (cchar == '.' && _is_number(next_char))) {
+ if (is_digit(cchar) || (cchar == '.' && is_digit(next_char))) {
//a number
String num;
#define READING_SIGN 0
#define READING_INT 1
-#define READING_DEC 2
-#define READING_EXP 3
-#define READING_DONE 4
+#define READING_HEX 2
+#define READING_BIN 3
+#define READING_DEC 4
+#define READING_EXP 5
+#define READING_DONE 6
int reading = READING_INT;
char32_t c = cchar;
bool exp_sign = false;
bool exp_beg = false;
+ bool bin_beg = false;
+ bool hex_beg = false;
bool is_float = false;
+ bool is_first_char = true;
while (true) {
switch (reading) {
case READING_INT: {
- if (_is_number(c)) {
- //pass
+ if (is_digit(c)) {
+ if (is_first_char && c == '0') {
+ if (next_char == 'b') {
+ reading = READING_BIN;
+ } else if (next_char == 'x') {
+ reading = READING_HEX;
+ }
+ }
} else if (c == '.') {
reading = READING_DEC;
is_float = true;
@@ -359,8 +366,24 @@ Error Expression::_get_token(Token &r_token) {
}
} break;
+ case READING_BIN: {
+ if (bin_beg && !is_binary_digit(c)) {
+ reading = READING_DONE;
+ } else if (c == 'b') {
+ bin_beg = true;
+ }
+
+ } break;
+ case READING_HEX: {
+ if (hex_beg && !is_hex_digit(c)) {
+ reading = READING_DONE;
+ } else if (c == 'x') {
+ hex_beg = true;
+ }
+
+ } break;
case READING_DEC: {
- if (_is_number(c)) {
+ if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
@@ -370,7 +393,7 @@ Error Expression::_get_token(Token &r_token) {
} break;
case READING_EXP: {
- if (_is_number(c)) {
+ if (is_digit(c)) {
exp_beg = true;
} else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) {
@@ -390,6 +413,7 @@ Error Expression::_get_token(Token &r_token) {
}
num += String::chr(c);
c = GET_CHAR();
+ is_first_char = false;
}
str_ofs--;
@@ -398,16 +422,20 @@ Error Expression::_get_token(Token &r_token) {
if (is_float) {
r_token.value = num.to_float();
+ } else if (bin_beg) {
+ r_token.value = num.bin_to_int();
+ } else if (hex_beg) {
+ r_token.value = num.hex_to_int();
} else {
r_token.value = num.to_int();
}
return OK;
- } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') {
+ } else if (is_ascii_char(cchar) || is_underscore(cchar)) {
String id;
bool first = true;
- while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && _is_number(cchar))) {
+ while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
id += String::chr(cchar);
cchar = GET_CHAR();
first = false;
diff --git a/core/math/face3.h b/core/math/face3.h
index 3dbbca09e0..8b123f078c 100644
--- a/core/math/face3.h
+++ b/core/math/face3.h
@@ -36,8 +36,7 @@
#include "core/math/transform_3d.h"
#include "core/math/vector3.h"
-class _NO_DISCARD_ Face3 {
-public:
+struct _NO_DISCARD_ Face3 {
enum Side {
SIDE_OVER,
SIDE_UNDER,
@@ -48,14 +47,11 @@ public:
Vector3 vertex[3];
/**
- *
* @param p_plane plane used to split the face
* @param p_res array of at least 3 faces, amount used in function return
* @param p_is_point_over array of at least 3 booleans, determining which face is over the plane, amount used in function return
- * @param _epsilon constant used for numerical error rounding, to add "thickness" to the plane (so coplanar points can happen)
* @return amount of faces generated by the split, either 0 (means no split possible), 2 or 3
*/
-
int split_by_plane(const Plane &p_plane, Face3 *p_res, bool *p_is_point_over) const;
Plane get_plane(ClockDirection p_dir = CLOCKWISE) const;
diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h
index 7385dba438..a2881d5f60 100644
--- a/core/math/geometry_2d.h
+++ b/core/math/geometry_2d.h
@@ -32,7 +32,11 @@
#define GEOMETRY_2D_H
#include "core/math/delaunay_2d.h"
+#include "core/math/math_funcs.h"
#include "core/math/triangulate.h"
+#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
+#include "core/math/vector3.h"
#include "core/math/vector3i.h"
#include "core/templates/vector.h"
diff --git a/core/math/plane.h b/core/math/plane.h
index 8cb6f62b3b..66c1741662 100644
--- a/core/math/plane.h
+++ b/core/math/plane.h
@@ -35,13 +35,12 @@
class Variant;
-class _NO_DISCARD_ Plane {
-public:
+struct _NO_DISCARD_ Plane {
Vector3 normal;
real_t d = 0;
void set_normal(const Vector3 &p_normal);
- _FORCE_INLINE_ Vector3 get_normal() const { return normal; }; ///Point is coplanar, CMP_EPSILON for precision
+ _FORCE_INLINE_ Vector3 get_normal() const { return normal; };
void normalize();
Plane normalized() const;
diff --git a/core/math/quaternion.h b/core/math/quaternion.h
index 2575d7d229..7874e4f428 100644
--- a/core/math/quaternion.h
+++ b/core/math/quaternion.h
@@ -36,8 +36,7 @@
#include "core/math/vector3.h"
#include "core/string/ustring.h"
-class _NO_DISCARD_ Quaternion {
-public:
+struct _NO_DISCARD_ Quaternion {
union {
struct {
real_t x;
diff --git a/core/math/rect2.cpp b/core/math/rect2.cpp
index 9047c19434..d6e20bdc3c 100644
--- a/core/math/rect2.cpp
+++ b/core/math/rect2.cpp
@@ -28,7 +28,11 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#include "core/math/transform_2d.h" // Includes rect2.h but Rect2 needs Transform2D
+#include "rect2.h"
+
+#include "core/math/rect2i.h"
+#include "core/math/transform_2d.h"
+#include "core/string/ustring.h"
bool Rect2::is_equal_approx(const Rect2 &p_rect) const {
return position.is_equal_approx(p_rect.position) && size.is_equal_approx(p_rect.size);
@@ -278,6 +282,6 @@ Rect2::operator String() const {
return "[P: " + position.operator String() + ", S: " + size + "]";
}
-Rect2i::operator String() const {
- return "[P: " + position.operator String() + ", S: " + size + "]";
+Rect2::operator Rect2i() const {
+ return Rect2i(position, size);
}
diff --git a/core/math/rect2.h b/core/math/rect2.h
index b14c69302c..6ecc02336c 100644
--- a/core/math/rect2.h
+++ b/core/math/rect2.h
@@ -31,8 +31,11 @@
#ifndef RECT2_H
#define RECT2_H
-#include "core/math/vector2.h" // also includes math_funcs and ustring
+#include "core/error/error_macros.h"
+#include "core/math/vector2.h"
+class String;
+struct Rect2i;
struct Transform2D;
struct _NO_DISCARD_ Rect2 {
@@ -179,6 +182,7 @@ struct _NO_DISCARD_ Rect2 {
return new_rect;
}
+
inline bool has_point(const Point2 &p_point) const {
#ifdef MATH_CHECKS
if (unlikely(size.x < 0 || size.y < 0)) {
@@ -201,6 +205,7 @@ struct _NO_DISCARD_ Rect2 {
return true;
}
+
bool is_equal_approx(const Rect2 &p_rect) const;
bool operator==(const Rect2 &p_rect) const { return position == p_rect.position && size == p_rect.size; }
@@ -351,6 +356,7 @@ struct _NO_DISCARD_ Rect2 {
}
operator String() const;
+ operator Rect2i() const;
Rect2() {}
Rect2(real_t p_x, real_t p_y, real_t p_width, real_t p_height) :
@@ -363,214 +369,4 @@ struct _NO_DISCARD_ Rect2 {
}
};
-struct _NO_DISCARD_ Rect2i {
- Point2i position;
- Size2i size;
-
- const Point2i &get_position() const { return position; }
- void set_position(const Point2i &p_position) { position = p_position; }
- const Size2i &get_size() const { return size; }
- void set_size(const Size2i &p_size) { size = p_size; }
-
- int get_area() const { return size.width * size.height; }
-
- _FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); }
-
- inline bool intersects(const Rect2i &p_rect) const {
-#ifdef MATH_CHECKS
- if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
- ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
- }
-#endif
- if (position.x >= (p_rect.position.x + p_rect.size.width)) {
- return false;
- }
- if ((position.x + size.width) <= p_rect.position.x) {
- return false;
- }
- if (position.y >= (p_rect.position.y + p_rect.size.height)) {
- return false;
- }
- if ((position.y + size.height) <= p_rect.position.y) {
- return false;
- }
-
- return true;
- }
-
- inline bool encloses(const Rect2i &p_rect) const {
-#ifdef MATH_CHECKS
- if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
- ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
- }
-#endif
- return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
- ((p_rect.position.x + p_rect.size.x) <= (position.x + size.x)) &&
- ((p_rect.position.y + p_rect.size.y) <= (position.y + size.y));
- }
-
- _FORCE_INLINE_ bool has_no_area() const {
- return (size.x <= 0 || size.y <= 0);
- }
-
- // Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
- inline Rect2i intersection(const Rect2i &p_rect) const {
- Rect2i new_rect = p_rect;
-
- if (!intersects(new_rect)) {
- return Rect2i();
- }
-
- new_rect.position.x = MAX(p_rect.position.x, position.x);
- new_rect.position.y = MAX(p_rect.position.y, position.y);
-
- Point2i p_rect_end = p_rect.position + p_rect.size;
- Point2i end = position + size;
-
- new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
- new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
-
- return new_rect;
- }
-
- inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect
-#ifdef MATH_CHECKS
- if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
- ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
- }
-#endif
- Rect2i new_rect;
-
- new_rect.position.x = MIN(p_rect.position.x, position.x);
- new_rect.position.y = MIN(p_rect.position.y, position.y);
-
- new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
- new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
-
- new_rect.size = new_rect.size - new_rect.position; //make relative again
-
- return new_rect;
- }
- bool has_point(const Point2i &p_point) const {
-#ifdef MATH_CHECKS
- if (unlikely(size.x < 0 || size.y < 0)) {
- ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
- }
-#endif
- if (p_point.x < position.x) {
- return false;
- }
- if (p_point.y < position.y) {
- return false;
- }
-
- if (p_point.x >= (position.x + size.x)) {
- return false;
- }
- if (p_point.y >= (position.y + size.y)) {
- return false;
- }
-
- return true;
- }
-
- bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; }
- bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; }
-
- Rect2i grow(int p_amount) const {
- Rect2i g = *this;
- g.position.x -= p_amount;
- g.position.y -= p_amount;
- g.size.width += p_amount * 2;
- g.size.height += p_amount * 2;
- return g;
- }
-
- inline Rect2i grow_side(Side p_side, int p_amount) const {
- Rect2i g = *this;
- g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0,
- (SIDE_TOP == p_side) ? p_amount : 0,
- (SIDE_RIGHT == p_side) ? p_amount : 0,
- (SIDE_BOTTOM == p_side) ? p_amount : 0);
- return g;
- }
-
- inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const {
- return grow_side(Side(p_side), p_amount);
- }
-
- inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const {
- Rect2i g = *this;
- g.position.x -= p_left;
- g.position.y -= p_top;
- g.size.width += p_left + p_right;
- g.size.height += p_top + p_bottom;
-
- return g;
- }
-
- _FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const {
- Rect2i r = *this;
- r.expand_to(p_vector);
- return r;
- }
-
- inline void expand_to(const Point2i &p_vector) {
-#ifdef MATH_CHECKS
- if (unlikely(size.x < 0 || size.y < 0)) {
- ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
- }
-#endif
- Point2i begin = position;
- Point2i end = position + size;
-
- if (p_vector.x < begin.x) {
- begin.x = p_vector.x;
- }
- if (p_vector.y < begin.y) {
- begin.y = p_vector.y;
- }
-
- if (p_vector.x > end.x) {
- end.x = p_vector.x;
- }
- if (p_vector.y > end.y) {
- end.y = p_vector.y;
- }
-
- position = begin;
- size = end - begin;
- }
-
- _FORCE_INLINE_ Rect2i abs() const {
- return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
- }
-
- _FORCE_INLINE_ void set_end(const Vector2i &p_end) {
- size = p_end - position;
- }
-
- _FORCE_INLINE_ Vector2i get_end() const {
- return position + size;
- }
-
- operator String() const;
-
- operator Rect2() const { return Rect2(position, size); }
-
- Rect2i() {}
- Rect2i(const Rect2 &p_r2) :
- position(p_r2.position),
- size(p_r2.size) {
- }
- Rect2i(int p_x, int p_y, int p_width, int p_height) :
- position(Point2i(p_x, p_y)),
- size(Size2i(p_width, p_height)) {
- }
- Rect2i(const Point2i &p_pos, const Size2i &p_size) :
- position(p_pos),
- size(p_size) {
- }
-};
-
#endif // RECT2_H
diff --git a/core/math/rect2i.cpp b/core/math/rect2i.cpp
new file mode 100644
index 0000000000..0782c450d0
--- /dev/null
+++ b/core/math/rect2i.cpp
@@ -0,0 +1,42 @@
+/*************************************************************************/
+/* rect2i.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "rect2i.h"
+
+#include "core/math/rect2.h"
+#include "core/string/ustring.h"
+
+Rect2i::operator String() const {
+ return "[P: " + position.operator String() + ", S: " + size + "]";
+}
+
+Rect2i::operator Rect2() const {
+ return Rect2(position, size);
+}
diff --git a/core/math/rect2i.h b/core/math/rect2i.h
new file mode 100644
index 0000000000..db1459a3e6
--- /dev/null
+++ b/core/math/rect2i.h
@@ -0,0 +1,245 @@
+/*************************************************************************/
+/* rect2i.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef RECT2I_H
+#define RECT2I_H
+
+#include "core/error/error_macros.h"
+#include "core/math/vector2i.h"
+
+class String;
+struct Rect2;
+
+struct _NO_DISCARD_ Rect2i {
+ Point2i position;
+ Size2i size;
+
+ const Point2i &get_position() const { return position; }
+ void set_position(const Point2i &p_position) { position = p_position; }
+ const Size2i &get_size() const { return size; }
+ void set_size(const Size2i &p_size) { size = p_size; }
+
+ int get_area() const { return size.width * size.height; }
+
+ _FORCE_INLINE_ Vector2i get_center() const { return position + (size / 2); }
+
+ inline bool intersects(const Rect2i &p_rect) const {
+#ifdef MATH_CHECKS
+ if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+ ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+ }
+#endif
+ if (position.x >= (p_rect.position.x + p_rect.size.width)) {
+ return false;
+ }
+ if ((position.x + size.width) <= p_rect.position.x) {
+ return false;
+ }
+ if (position.y >= (p_rect.position.y + p_rect.size.height)) {
+ return false;
+ }
+ if ((position.y + size.height) <= p_rect.position.y) {
+ return false;
+ }
+
+ return true;
+ }
+
+ inline bool encloses(const Rect2i &p_rect) const {
+#ifdef MATH_CHECKS
+ if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+ ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+ }
+#endif
+ return (p_rect.position.x >= position.x) && (p_rect.position.y >= position.y) &&
+ ((p_rect.position.x + p_rect.size.x) <= (position.x + size.x)) &&
+ ((p_rect.position.y + p_rect.size.y) <= (position.y + size.y));
+ }
+
+ _FORCE_INLINE_ bool has_no_area() const {
+ return (size.x <= 0 || size.y <= 0);
+ }
+
+ // Returns the instersection between two Rect2is or an empty Rect2i if there is no intersection
+ inline Rect2i intersection(const Rect2i &p_rect) const {
+ Rect2i new_rect = p_rect;
+
+ if (!intersects(new_rect)) {
+ return Rect2i();
+ }
+
+ new_rect.position.x = MAX(p_rect.position.x, position.x);
+ new_rect.position.y = MAX(p_rect.position.y, position.y);
+
+ Point2i p_rect_end = p_rect.position + p_rect.size;
+ Point2i end = position + size;
+
+ new_rect.size.x = MIN(p_rect_end.x, end.x) - new_rect.position.x;
+ new_rect.size.y = MIN(p_rect_end.y, end.y) - new_rect.position.y;
+
+ return new_rect;
+ }
+
+ inline Rect2i merge(const Rect2i &p_rect) const { ///< return a merged rect
+#ifdef MATH_CHECKS
+ if (unlikely(size.x < 0 || size.y < 0 || p_rect.size.x < 0 || p_rect.size.y < 0)) {
+ ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+ }
+#endif
+ Rect2i new_rect;
+
+ new_rect.position.x = MIN(p_rect.position.x, position.x);
+ new_rect.position.y = MIN(p_rect.position.y, position.y);
+
+ new_rect.size.x = MAX(p_rect.position.x + p_rect.size.x, position.x + size.x);
+ new_rect.size.y = MAX(p_rect.position.y + p_rect.size.y, position.y + size.y);
+
+ new_rect.size = new_rect.size - new_rect.position; //make relative again
+
+ return new_rect;
+ }
+ bool has_point(const Point2i &p_point) const {
+#ifdef MATH_CHECKS
+ if (unlikely(size.x < 0 || size.y < 0)) {
+ ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+ }
+#endif
+ if (p_point.x < position.x) {
+ return false;
+ }
+ if (p_point.y < position.y) {
+ return false;
+ }
+
+ if (p_point.x >= (position.x + size.x)) {
+ return false;
+ }
+ if (p_point.y >= (position.y + size.y)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ bool operator==(const Rect2i &p_rect) const { return position == p_rect.position && size == p_rect.size; }
+ bool operator!=(const Rect2i &p_rect) const { return position != p_rect.position || size != p_rect.size; }
+
+ Rect2i grow(int p_amount) const {
+ Rect2i g = *this;
+ g.position.x -= p_amount;
+ g.position.y -= p_amount;
+ g.size.width += p_amount * 2;
+ g.size.height += p_amount * 2;
+ return g;
+ }
+
+ inline Rect2i grow_side(Side p_side, int p_amount) const {
+ Rect2i g = *this;
+ g = g.grow_individual((SIDE_LEFT == p_side) ? p_amount : 0,
+ (SIDE_TOP == p_side) ? p_amount : 0,
+ (SIDE_RIGHT == p_side) ? p_amount : 0,
+ (SIDE_BOTTOM == p_side) ? p_amount : 0);
+ return g;
+ }
+
+ inline Rect2i grow_side_bind(uint32_t p_side, int p_amount) const {
+ return grow_side(Side(p_side), p_amount);
+ }
+
+ inline Rect2i grow_individual(int p_left, int p_top, int p_right, int p_bottom) const {
+ Rect2i g = *this;
+ g.position.x -= p_left;
+ g.position.y -= p_top;
+ g.size.width += p_left + p_right;
+ g.size.height += p_top + p_bottom;
+
+ return g;
+ }
+
+ _FORCE_INLINE_ Rect2i expand(const Vector2i &p_vector) const {
+ Rect2i r = *this;
+ r.expand_to(p_vector);
+ return r;
+ }
+
+ inline void expand_to(const Point2i &p_vector) {
+#ifdef MATH_CHECKS
+ if (unlikely(size.x < 0 || size.y < 0)) {
+ ERR_PRINT("Rect2i size is negative, this is not supported. Use Rect2i.abs() to get a Rect2i with a positive size.");
+ }
+#endif
+ Point2i begin = position;
+ Point2i end = position + size;
+
+ if (p_vector.x < begin.x) {
+ begin.x = p_vector.x;
+ }
+ if (p_vector.y < begin.y) {
+ begin.y = p_vector.y;
+ }
+
+ if (p_vector.x > end.x) {
+ end.x = p_vector.x;
+ }
+ if (p_vector.y > end.y) {
+ end.y = p_vector.y;
+ }
+
+ position = begin;
+ size = end - begin;
+ }
+
+ _FORCE_INLINE_ Rect2i abs() const {
+ return Rect2i(Point2i(position.x + MIN(size.x, 0), position.y + MIN(size.y, 0)), size.abs());
+ }
+
+ _FORCE_INLINE_ void set_end(const Vector2i &p_end) {
+ size = p_end - position;
+ }
+
+ _FORCE_INLINE_ Vector2i get_end() const {
+ return position + size;
+ }
+
+ operator String() const;
+ operator Rect2() const;
+
+ Rect2i() {}
+ Rect2i(int p_x, int p_y, int p_width, int p_height) :
+ position(Point2i(p_x, p_y)),
+ size(Size2i(p_width, p_height)) {
+ }
+ Rect2i(const Point2i &p_pos, const Size2i &p_size) :
+ position(p_pos),
+ size(p_size) {
+ }
+};
+
+#endif // RECT2I_H
diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp
index 0201cf575c..e6e24e9b32 100644
--- a/core/math/transform_2d.cpp
+++ b/core/math/transform_2d.cpp
@@ -30,6 +30,8 @@
#include "transform_2d.h"
+#include "core/string/ustring.h"
+
void Transform2D::invert() {
// FIXME: this function assumes the basis is a rotation matrix, with no scaling.
// Transform2D::affine_inverse can handle matrices with scaling, so GDScript should eventually use that.
diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h
index 6c2d51bd9b..f4546c13c8 100644
--- a/core/math/transform_2d.h
+++ b/core/math/transform_2d.h
@@ -31,7 +31,12 @@
#ifndef TRANSFORM_2D_H
#define TRANSFORM_2D_H
-#include "core/math/rect2.h" // also includes vector2, math_funcs, and ustring
+#include "core/math/math_funcs.h"
+#include "core/math/rect2.h"
+#include "core/math/vector2.h"
+#include "core/templates/vector.h"
+
+class String;
struct _NO_DISCARD_ Transform2D {
// Warning #1: basis of Transform2D is stored differently from Basis. In terms of elements array, the basis matrix looks like "on paper":
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index c16c278e74..3b4762e221 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -28,15 +28,14 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef TRANSFORM_H
-#define TRANSFORM_H
+#ifndef TRANSFORM_3D_H
+#define TRANSFORM_3D_H
#include "core/math/aabb.h"
#include "core/math/basis.h"
#include "core/math/plane.h"
-class _NO_DISCARD_ Transform3D {
-public:
+struct _NO_DISCARD_ Transform3D {
Basis basis;
Vector3 origin;
@@ -265,4 +264,4 @@ _FORCE_INLINE_ Plane Transform3D::xform_inv_fast(const Plane &p_plane, const Tra
return Plane(normal, d);
}
-#endif // TRANSFORM_H
+#endif // TRANSFORM_3D_H
diff --git a/core/math/triangulate.h b/core/math/triangulate.h
index d96bdb8cab..0bfcfcb978 100644
--- a/core/math/triangulate.h
+++ b/core/math/triangulate.h
@@ -32,6 +32,7 @@
#define TRIANGULATE_H
#include "core/math/vector2.h"
+#include "core/templates/vector.h"
/*
https://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
diff --git a/core/math/vector2.cpp b/core/math/vector2.cpp
index 676a0004ea..40149e8cc1 100644
--- a/core/math/vector2.cpp
+++ b/core/math/vector2.cpp
@@ -30,6 +30,9 @@
#include "vector2.h"
+#include "core/math/vector2i.h"
+#include "core/string/ustring.h"
+
real_t Vector2::angle() const {
return Math::atan2(y, x);
}
@@ -202,91 +205,6 @@ Vector2::operator String() const {
return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ")";
}
-/* Vector2i */
-
-Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const {
- return Vector2i(
- CLAMP(x, p_min.x, p_max.x),
- CLAMP(y, p_min.y, p_max.y));
-}
-
-int64_t Vector2i::length_squared() const {
- return x * (int64_t)x + y * (int64_t)y;
-}
-
-double Vector2i::length() const {
- return Math::sqrt((double)length_squared());
-}
-
-Vector2i Vector2i::operator+(const Vector2i &p_v) const {
- return Vector2i(x + p_v.x, y + p_v.y);
-}
-
-void Vector2i::operator+=(const Vector2i &p_v) {
- x += p_v.x;
- y += p_v.y;
-}
-
-Vector2i Vector2i::operator-(const Vector2i &p_v) const {
- return Vector2i(x - p_v.x, y - p_v.y);
-}
-
-void Vector2i::operator-=(const Vector2i &p_v) {
- x -= p_v.x;
- y -= p_v.y;
-}
-
-Vector2i Vector2i::operator*(const Vector2i &p_v1) const {
- return Vector2i(x * p_v1.x, y * p_v1.y);
-}
-
-Vector2i Vector2i::operator*(const int32_t &rvalue) const {
- return Vector2i(x * rvalue, y * rvalue);
-}
-
-void Vector2i::operator*=(const int32_t &rvalue) {
- x *= rvalue;
- y *= rvalue;
-}
-
-Vector2i Vector2i::operator/(const Vector2i &p_v1) const {
- return Vector2i(x / p_v1.x, y / p_v1.y);
-}
-
-Vector2i Vector2i::operator/(const int32_t &rvalue) const {
- return Vector2i(x / rvalue, y / rvalue);
-}
-
-void Vector2i::operator/=(const int32_t &rvalue) {
- x /= rvalue;
- y /= rvalue;
-}
-
-Vector2i Vector2i::operator%(const Vector2i &p_v1) const {
- return Vector2i(x % p_v1.x, y % p_v1.y);
-}
-
-Vector2i Vector2i::operator%(const int32_t &rvalue) const {
- return Vector2i(x % rvalue, y % rvalue);
-}
-
-void Vector2i::operator%=(const int32_t &rvalue) {
- x %= rvalue;
- y %= rvalue;
-}
-
-Vector2i Vector2i::operator-() const {
- return Vector2i(-x, -y);
-}
-
-bool Vector2i::operator==(const Vector2i &p_vec2) const {
- return x == p_vec2.x && y == p_vec2.y;
-}
-
-bool Vector2i::operator!=(const Vector2i &p_vec2) const {
- return x != p_vec2.x || y != p_vec2.y;
-}
-
-Vector2i::operator String() const {
- return "(" + itos(x) + ", " + itos(y) + ")";
+Vector2::operator Vector2i() const {
+ return Vector2i(x, y);
}
diff --git a/core/math/vector2.h b/core/math/vector2.h
index af40b9e68d..9edaaebf89 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -32,8 +32,8 @@
#define VECTOR2_H
#include "core/math/math_funcs.h"
-#include "core/string/ustring.h"
+class String;
struct Vector2i;
struct _NO_DISCARD_ Vector2 {
@@ -167,6 +167,7 @@ struct _NO_DISCARD_ Vector2 {
real_t aspect() const { return width / height; }
operator String() const;
+ operator Vector2i() const;
_FORCE_INLINE_ Vector2() {}
_FORCE_INLINE_ Vector2(const real_t p_x, const real_t p_y) {
@@ -282,113 +283,4 @@ Vector2 Vector2::direction_to(const Vector2 &p_to) const {
typedef Vector2 Size2;
typedef Vector2 Point2;
-/* INTEGER STUFF */
-
-struct _NO_DISCARD_ Vector2i {
- enum Axis {
- AXIS_X,
- AXIS_Y,
- };
-
- union {
- int32_t x = 0;
- int32_t width;
- };
- union {
- int32_t y = 0;
- int32_t height;
- };
-
- _FORCE_INLINE_ int32_t &operator[](int p_idx) {
- return p_idx ? y : x;
- }
- _FORCE_INLINE_ const int32_t &operator[](int p_idx) const {
- return p_idx ? y : x;
- }
-
- _FORCE_INLINE_ Vector2i::Axis min_axis_index() const {
- return x < y ? Vector2i::AXIS_X : Vector2i::AXIS_Y;
- }
-
- _FORCE_INLINE_ Vector2i::Axis max_axis_index() const {
- return x < y ? Vector2i::AXIS_Y : Vector2i::AXIS_X;
- }
-
- Vector2i min(const Vector2i &p_vector2i) const {
- return Vector2(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y));
- }
-
- Vector2i max(const Vector2i &p_vector2i) const {
- return Vector2(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y));
- }
-
- Vector2i operator+(const Vector2i &p_v) const;
- void operator+=(const Vector2i &p_v);
- Vector2i operator-(const Vector2i &p_v) const;
- void operator-=(const Vector2i &p_v);
- Vector2i operator*(const Vector2i &p_v1) const;
-
- Vector2i operator*(const int32_t &rvalue) const;
- void operator*=(const int32_t &rvalue);
-
- Vector2i operator/(const Vector2i &p_v1) const;
- Vector2i operator/(const int32_t &rvalue) const;
- void operator/=(const int32_t &rvalue);
-
- Vector2i operator%(const Vector2i &p_v1) const;
- Vector2i operator%(const int32_t &rvalue) const;
- void operator%=(const int32_t &rvalue);
-
- Vector2i operator-() const;
- bool operator<(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y < p_vec2.y) : (x < p_vec2.x); }
- bool operator>(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y > p_vec2.y) : (x > p_vec2.x); }
-
- bool operator<=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y <= p_vec2.y) : (x < p_vec2.x); }
- bool operator>=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); }
-
- bool operator==(const Vector2i &p_vec2) const;
- bool operator!=(const Vector2i &p_vec2) const;
-
- int64_t length_squared() const;
- double length() const;
-
- real_t aspect() const { return width / (real_t)height; }
- Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); }
- Vector2i abs() const { return Vector2i(ABS(x), ABS(y)); }
- Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const;
-
- operator String() const;
-
- operator Vector2() const { return Vector2(x, y); }
-
- inline Vector2i() {}
- inline Vector2i(const Vector2 &p_vec2) {
- x = (int32_t)p_vec2.x;
- y = (int32_t)p_vec2.y;
- }
- inline Vector2i(const int32_t p_x, const int32_t p_y) {
- x = p_x;
- y = p_y;
- }
-};
-
-_FORCE_INLINE_ Vector2i operator*(const int32_t &p_scalar, const Vector2i &p_vector) {
- return p_vector * p_scalar;
-}
-
-_FORCE_INLINE_ Vector2i operator*(const int64_t &p_scalar, const Vector2i &p_vector) {
- return p_vector * p_scalar;
-}
-
-_FORCE_INLINE_ Vector2i operator*(const float &p_scalar, const Vector2i &p_vector) {
- return p_vector * p_scalar;
-}
-
-_FORCE_INLINE_ Vector2i operator*(const double &p_scalar, const Vector2i &p_vector) {
- return p_vector * p_scalar;
-}
-
-typedef Vector2i Size2i;
-typedef Vector2i Point2i;
-
#endif // VECTOR2_H
diff --git a/core/math/vector2i.cpp b/core/math/vector2i.cpp
new file mode 100644
index 0000000000..dfed42e4d6
--- /dev/null
+++ b/core/math/vector2i.cpp
@@ -0,0 +1,125 @@
+/*************************************************************************/
+/* vector2i.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "vector2i.h"
+
+#include "core/math/vector2.h"
+#include "core/string/ustring.h"
+
+Vector2i Vector2i::clamp(const Vector2i &p_min, const Vector2i &p_max) const {
+ return Vector2i(
+ CLAMP(x, p_min.x, p_max.x),
+ CLAMP(y, p_min.y, p_max.y));
+}
+
+int64_t Vector2i::length_squared() const {
+ return x * (int64_t)x + y * (int64_t)y;
+}
+
+double Vector2i::length() const {
+ return Math::sqrt((double)length_squared());
+}
+
+Vector2i Vector2i::operator+(const Vector2i &p_v) const {
+ return Vector2i(x + p_v.x, y + p_v.y);
+}
+
+void Vector2i::operator+=(const Vector2i &p_v) {
+ x += p_v.x;
+ y += p_v.y;
+}
+
+Vector2i Vector2i::operator-(const Vector2i &p_v) const {
+ return Vector2i(x - p_v.x, y - p_v.y);
+}
+
+void Vector2i::operator-=(const Vector2i &p_v) {
+ x -= p_v.x;
+ y -= p_v.y;
+}
+
+Vector2i Vector2i::operator*(const Vector2i &p_v1) const {
+ return Vector2i(x * p_v1.x, y * p_v1.y);
+}
+
+Vector2i Vector2i::operator*(const int32_t &rvalue) const {
+ return Vector2i(x * rvalue, y * rvalue);
+}
+
+void Vector2i::operator*=(const int32_t &rvalue) {
+ x *= rvalue;
+ y *= rvalue;
+}
+
+Vector2i Vector2i::operator/(const Vector2i &p_v1) const {
+ return Vector2i(x / p_v1.x, y / p_v1.y);
+}
+
+Vector2i Vector2i::operator/(const int32_t &rvalue) const {
+ return Vector2i(x / rvalue, y / rvalue);
+}
+
+void Vector2i::operator/=(const int32_t &rvalue) {
+ x /= rvalue;
+ y /= rvalue;
+}
+
+Vector2i Vector2i::operator%(const Vector2i &p_v1) const {
+ return Vector2i(x % p_v1.x, y % p_v1.y);
+}
+
+Vector2i Vector2i::operator%(const int32_t &rvalue) const {
+ return Vector2i(x % rvalue, y % rvalue);
+}
+
+void Vector2i::operator%=(const int32_t &rvalue) {
+ x %= rvalue;
+ y %= rvalue;
+}
+
+Vector2i Vector2i::operator-() const {
+ return Vector2i(-x, -y);
+}
+
+bool Vector2i::operator==(const Vector2i &p_vec2) const {
+ return x == p_vec2.x && y == p_vec2.y;
+}
+
+bool Vector2i::operator!=(const Vector2i &p_vec2) const {
+ return x != p_vec2.x || y != p_vec2.y;
+}
+
+Vector2i::operator String() const {
+ return "(" + itos(x) + ", " + itos(y) + ")";
+}
+
+Vector2i::operator Vector2() const {
+ return Vector2((int32_t)x, (int32_t)y);
+}
diff --git a/core/math/vector2i.h b/core/math/vector2i.h
new file mode 100644
index 0000000000..446e05f5dd
--- /dev/null
+++ b/core/math/vector2i.h
@@ -0,0 +1,141 @@
+/*************************************************************************/
+/* vector2i.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef VECTOR2I_H
+#define VECTOR2I_H
+
+#include "core/math/math_funcs.h"
+
+class String;
+struct Vector2;
+
+struct _NO_DISCARD_ Vector2i {
+ enum Axis {
+ AXIS_X,
+ AXIS_Y,
+ };
+
+ union {
+ int32_t x = 0;
+ int32_t width;
+ };
+ union {
+ int32_t y = 0;
+ int32_t height;
+ };
+
+ _FORCE_INLINE_ int32_t &operator[](int p_idx) {
+ return p_idx ? y : x;
+ }
+ _FORCE_INLINE_ const int32_t &operator[](int p_idx) const {
+ return p_idx ? y : x;
+ }
+
+ _FORCE_INLINE_ Vector2i::Axis min_axis_index() const {
+ return x < y ? Vector2i::AXIS_X : Vector2i::AXIS_Y;
+ }
+
+ _FORCE_INLINE_ Vector2i::Axis max_axis_index() const {
+ return x < y ? Vector2i::AXIS_Y : Vector2i::AXIS_X;
+ }
+
+ Vector2i min(const Vector2i &p_vector2i) const {
+ return Vector2i(MIN(x, p_vector2i.x), MIN(y, p_vector2i.y));
+ }
+
+ Vector2i max(const Vector2i &p_vector2i) const {
+ return Vector2i(MAX(x, p_vector2i.x), MAX(y, p_vector2i.y));
+ }
+
+ Vector2i operator+(const Vector2i &p_v) const;
+ void operator+=(const Vector2i &p_v);
+ Vector2i operator-(const Vector2i &p_v) const;
+ void operator-=(const Vector2i &p_v);
+ Vector2i operator*(const Vector2i &p_v1) const;
+
+ Vector2i operator*(const int32_t &rvalue) const;
+ void operator*=(const int32_t &rvalue);
+
+ Vector2i operator/(const Vector2i &p_v1) const;
+ Vector2i operator/(const int32_t &rvalue) const;
+ void operator/=(const int32_t &rvalue);
+
+ Vector2i operator%(const Vector2i &p_v1) const;
+ Vector2i operator%(const int32_t &rvalue) const;
+ void operator%=(const int32_t &rvalue);
+
+ Vector2i operator-() const;
+ bool operator<(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y < p_vec2.y) : (x < p_vec2.x); }
+ bool operator>(const Vector2i &p_vec2) const { return (x == p_vec2.x) ? (y > p_vec2.y) : (x > p_vec2.x); }
+
+ bool operator<=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y <= p_vec2.y) : (x < p_vec2.x); }
+ bool operator>=(const Vector2i &p_vec2) const { return x == p_vec2.x ? (y >= p_vec2.y) : (x > p_vec2.x); }
+
+ bool operator==(const Vector2i &p_vec2) const;
+ bool operator!=(const Vector2i &p_vec2) const;
+
+ int64_t length_squared() const;
+ double length() const;
+
+ real_t aspect() const { return width / (real_t)height; }
+ Vector2i sign() const { return Vector2i(SIGN(x), SIGN(y)); }
+ Vector2i abs() const { return Vector2i(ABS(x), ABS(y)); }
+ Vector2i clamp(const Vector2i &p_min, const Vector2i &p_max) const;
+
+ operator String() const;
+ operator Vector2() const;
+
+ inline Vector2i() {}
+ inline Vector2i(const int32_t p_x, const int32_t p_y) {
+ x = p_x;
+ y = p_y;
+ }
+};
+
+_FORCE_INLINE_ Vector2i operator*(const int32_t &p_scalar, const Vector2i &p_vector) {
+ return p_vector * p_scalar;
+}
+
+_FORCE_INLINE_ Vector2i operator*(const int64_t &p_scalar, const Vector2i &p_vector) {
+ return p_vector * p_scalar;
+}
+
+_FORCE_INLINE_ Vector2i operator*(const float &p_scalar, const Vector2i &p_vector) {
+ return p_vector * p_scalar;
+}
+
+_FORCE_INLINE_ Vector2i operator*(const double &p_scalar, const Vector2i &p_vector) {
+ return p_vector * p_scalar;
+}
+
+typedef Vector2i Size2i;
+typedef Vector2i Point2i;
+
+#endif // VECTOR2I_H
diff --git a/core/math/vector3.h b/core/math/vector3.h
index b62edef40f..79ba5c4f15 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -35,7 +35,8 @@
#include "core/math/vector2.h"
#include "core/math/vector3i.h"
#include "core/string/ustring.h"
-class Basis;
+
+struct Basis;
struct _NO_DISCARD_ Vector3 {
static const int AXIS_COUNT = 3;
diff --git a/core/multiplayer/multiplayer_api.cpp b/core/multiplayer/multiplayer_api.cpp
index 627825246a..41d6d14696 100644
--- a/core/multiplayer/multiplayer_api.cpp
+++ b/core/multiplayer/multiplayer_api.cpp
@@ -32,7 +32,6 @@
#include "core/debugger/engine_debugger.h"
#include "core/io/marshalls.h"
-#include "core/multiplayer/multiplayer_replicator.h"
#include "core/multiplayer/rpc_manager.h"
#include "scene/main/node.h"
@@ -42,6 +41,8 @@
#include "core/os/os.h"
#endif
+MultiplayerReplicationInterface *(*MultiplayerAPI::create_default_replication_interface)(MultiplayerAPI *p_multiplayer) = nullptr;
+
#ifdef DEBUG_ENABLED
void MultiplayerAPI::profile_bandwidth(const String &p_inout, int p_size) {
if (EngineDebugger::is_profiling("multiplayer")) {
@@ -74,7 +75,7 @@ void MultiplayerAPI::poll() {
Error err = multiplayer_peer->get_packet(&packet, len);
if (err != OK) {
ERR_PRINT("Error getting packet!");
- break; // Something is wrong!
+ return; // Something is wrong!
}
remote_sender_id = sender;
@@ -82,16 +83,13 @@ void MultiplayerAPI::poll() {
remote_sender_id = 0;
if (!multiplayer_peer.is_valid()) {
- break; // It's also possible that a packet or RPC caused a disconnection, so also check here.
+ return; // It's also possible that a packet or RPC caused a disconnection, so also check here.
}
}
- if (multiplayer_peer.is_valid() && multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_CONNECTED) {
- replicator->poll();
- }
+ replicator->on_network_process();
}
void MultiplayerAPI::clear() {
- replicator->clear();
connected_peers.clear();
path_get_cache.clear();
path_send_cache.clear();
@@ -133,6 +131,7 @@ void MultiplayerAPI::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) {
multiplayer_peer->connect("connection_failed", callable_mp(this, &MultiplayerAPI::_connection_failed));
multiplayer_peer->connect("server_disconnected", callable_mp(this, &MultiplayerAPI::_server_disconnected));
}
+ replicator->on_reset();
}
Ref<MultiplayerPeer> MultiplayerAPI::get_multiplayer_peer() const {
@@ -167,13 +166,13 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
_process_raw(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SPAWN: {
- replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, true);
+ replicator->on_spawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_DESPAWN: {
- replicator->process_spawn_despawn(p_from, p_packet, p_packet_len, false);
+ replicator->on_despawn_receive(p_from, p_packet, p_packet_len);
} break;
case NETWORK_COMMAND_SYNC: {
- replicator->process_sync(p_from, p_packet, p_packet_len);
+ replicator->on_sync_receive(p_from, p_packet, p_packet_len);
} break;
}
}
@@ -324,7 +323,7 @@ bool MultiplayerAPI::_send_confirm_path(Node *p_node, NodePath p_path, PathSentC
#define ENCODE_16 1 << 5
#define ENCODE_32 2 << 5
#define ENCODE_64 3 << 5
-Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len) {
+Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint8_t *r_buffer, int &r_len, bool p_allow_object_decoding) {
// Unreachable because `VARIANT_MAX` == 27 and `ENCODE_VARIANT_MASK` == 31
CRASH_COND(p_variant.get_type() > VARIANT_META_TYPE_MASK);
@@ -385,7 +384,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint
} break;
default:
// Any other case is not yet compressed.
- Error err = encode_variant(p_variant, r_buffer, r_len, allow_object_decoding);
+ Error err = encode_variant(p_variant, r_buffer, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
@@ -399,7 +398,7 @@ Error MultiplayerAPI::encode_and_compress_variant(const Variant &p_variant, uint
return OK;
}
-Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len) {
+Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding) {
const uint8_t *buf = p_buffer;
int len = p_len;
@@ -458,7 +457,7 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui
}
} break;
default:
- Error err = decode_variant(r_variant, p_buffer, p_len, r_len, allow_object_decoding);
+ Error err = decode_variant(r_variant, p_buffer, p_len, r_len, p_allow_object_decoding);
if (err != OK) {
return err;
}
@@ -467,17 +466,84 @@ Error MultiplayerAPI::decode_and_decompress_variant(Variant &r_variant, const ui
return OK;
}
+Error MultiplayerAPI::encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw, bool p_allow_object_decoding) {
+ r_len = 0;
+ int size = 0;
+
+ if (p_count == 0) {
+ if (r_raw) {
+ *r_raw = true;
+ }
+ return OK;
+ }
+
+ // Try raw encoding optimization.
+ if (r_raw && p_count == 1) {
+ *r_raw = false;
+ const Variant &v = *(p_variants[0]);
+ if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
+ *r_raw = true;
+ const PackedByteArray pba = v;
+ if (p_buffer) {
+ memcpy(p_buffer, pba.ptr(), pba.size());
+ }
+ r_len += pba.size();
+ } else {
+ encode_and_compress_variant(v, p_buffer, size, p_allow_object_decoding);
+ r_len += size;
+ }
+ return OK;
+ }
+
+ // Regular encoding.
+ for (int i = 0; i < p_count; i++) {
+ const Variant &v = *(p_variants[i]);
+ encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size, p_allow_object_decoding);
+ r_len += size;
+ }
+ return OK;
+}
+
+Error MultiplayerAPI::decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw, bool p_allow_object_decoding) {
+ r_len = 0;
+ int argc = r_variants.size();
+ if (argc == 0 && p_raw) {
+ return OK;
+ }
+ ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
+ if (p_raw) {
+ r_len = p_len;
+ PackedByteArray pba;
+ pba.resize(p_len);
+ memcpy(pba.ptrw(), p_buffer, p_len);
+ r_variants.write[0] = pba;
+ return OK;
+ }
+
+ Vector<Variant> args;
+ Vector<const Variant *> argp;
+ args.resize(argc);
+
+ for (int i = 0; i < argc; i++) {
+ ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
+
+ int vlen;
+ Error err = MultiplayerAPI::decode_and_decompress_variant(r_variants.write[i], &p_buffer[r_len], p_len - r_len, &vlen, p_allow_object_decoding);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
+ r_len += vlen;
+ }
+ return OK;
+}
+
void MultiplayerAPI::_add_peer(int p_id) {
connected_peers.insert(p_id);
path_get_cache.insert(p_id, PathGetCache());
- if (is_server()) {
- replicator->spawn_all(p_id);
- }
+ replicator->on_peer_change(p_id, true);
emit_signal(SNAME("peer_connected"), p_id);
}
void MultiplayerAPI::_del_peer(int p_id) {
- connected_peers.erase(p_id);
+ replicator->on_peer_change(p_id, false);
// Cleanup get cache.
path_get_cache.erase(p_id);
// Cleanup sent cache.
@@ -488,6 +554,7 @@ void MultiplayerAPI::_del_peer(int p_id) {
PathSentCache *psc = path_send_cache.getptr(E);
psc->confirmed_peers.erase(p_id);
}
+ connected_peers.erase(p_id);
emit_signal(SNAME("peer_disconnected"), p_id);
}
@@ -500,6 +567,7 @@ void MultiplayerAPI::_connection_failed() {
}
void MultiplayerAPI::_server_disconnected() {
+ replicator->on_reset();
emit_signal(SNAME("server_disconnected"));
}
@@ -612,14 +680,26 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
return allow_object_decoding;
}
-void MultiplayerAPI::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
- replicator->scene_enter_exit_notify(p_scene, p_node, p_enter);
-}
-
void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount) {
rpc_manager->rpcp(p_node, p_peer_id, p_method, p_arg, p_argcount);
}
+Error MultiplayerAPI::spawn(Object *p_object, Variant p_config) {
+ return replicator->on_spawn(p_object, p_config);
+}
+
+Error MultiplayerAPI::despawn(Object *p_object, Variant p_config) {
+ return replicator->on_despawn(p_object, p_config);
+}
+
+Error MultiplayerAPI::replication_start(Object *p_object, Variant p_config) {
+ return replicator->on_replication_start(p_object, p_config);
+}
+
+Error MultiplayerAPI::replication_stop(Object *p_object, Variant p_config) {
+ return replicator->on_replication_stop(p_object, p_config);
+}
+
void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node);
ClassDB::bind_method(D_METHOD("get_root_node"), &MultiplayerAPI::get_root_node);
@@ -638,14 +718,12 @@ void MultiplayerAPI::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &MultiplayerAPI::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &MultiplayerAPI::set_allow_object_decoding);
ClassDB::bind_method(D_METHOD("is_object_decoding_allowed"), &MultiplayerAPI::is_object_decoding_allowed);
- ClassDB::bind_method(D_METHOD("get_replicator"), &MultiplayerAPI::get_replicator);
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "multiplayer_peer", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerPeer", PROPERTY_USAGE_NONE), "set_multiplayer_peer", "get_multiplayer_peer");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "root_node", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_root_node", "get_root_node");
ADD_PROPERTY_DEFAULT("refuse_new_connections", false);
- ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "replicator", PROPERTY_HINT_RESOURCE_TYPE, "MultiplayerReplicator", PROPERTY_USAGE_NONE), "", "get_replicator");
ADD_SIGNAL(MethodInfo("peer_connected", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_disconnected", PropertyInfo(Variant::INT, "id")));
@@ -656,13 +734,16 @@ void MultiplayerAPI::_bind_methods() {
}
MultiplayerAPI::MultiplayerAPI() {
- replicator = memnew(MultiplayerReplicator(this));
+ if (create_default_replication_interface) {
+ replicator = Ref<MultiplayerReplicationInterface>(create_default_replication_interface(this));
+ } else {
+ replicator.instantiate();
+ }
rpc_manager = memnew(RPCManager(this));
clear();
}
MultiplayerAPI::~MultiplayerAPI() {
clear();
- memdelete(replicator);
memdelete(rpc_manager);
}
diff --git a/core/multiplayer/multiplayer_api.h b/core/multiplayer/multiplayer_api.h
index 713035428d..f4fdafc323 100644
--- a/core/multiplayer/multiplayer_api.h
+++ b/core/multiplayer/multiplayer_api.h
@@ -35,7 +35,26 @@
#include "core/multiplayer/multiplayer_peer.h"
#include "core/object/ref_counted.h"
-class MultiplayerReplicator;
+class MultiplayerAPI;
+
+class MultiplayerReplicationInterface : public RefCounted {
+ GDCLASS(MultiplayerReplicationInterface, RefCounted);
+
+public:
+ virtual void on_peer_change(int p_id, bool p_connected) {}
+ virtual void on_reset() {}
+ virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
+ virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
+ virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) { return ERR_UNAVAILABLE; }
+ virtual Error on_spawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
+ virtual Error on_despawn(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
+ virtual Error on_replication_start(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
+ virtual Error on_replication_stop(Object *p_obj, Variant p_config) { return ERR_UNAVAILABLE; }
+ virtual void on_network_process() {}
+
+ MultiplayerReplicationInterface() {}
+};
+
class RPCManager;
class MultiplayerAPI : public RefCounted {
@@ -95,7 +114,7 @@ private:
Node *root_node = nullptr;
bool allow_object_decoding = false;
- MultiplayerReplicator *replicator = nullptr;
+ Ref<MultiplayerReplicationInterface> replicator;
RPCManager *rpc_manager = nullptr;
protected:
@@ -108,6 +127,13 @@ protected:
void _process_raw(int p_from, const uint8_t *p_packet, int p_packet_len);
public:
+ static MultiplayerReplicationInterface *(*create_default_replication_interface)(MultiplayerAPI *p_multiplayer);
+
+ static Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len, bool p_allow_object_decoding);
+ static Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len, bool p_allow_object_decoding);
+ static Error encode_and_compress_variants(const Variant **p_variants, int p_count, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr, bool p_allow_object_decoding = false);
+ static Error decode_and_decompress_variants(Vector<Variant> &r_variants, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false, bool p_allow_object_decoding = false);
+
void poll();
void clear();
void set_root_node(Node *p_node);
@@ -117,13 +143,13 @@ public:
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, Multiplayer::TransferMode p_mode = Multiplayer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
- Error encode_and_compress_variant(const Variant &p_variant, uint8_t *p_buffer, int &r_len);
- Error decode_and_decompress_variant(Variant &r_variant, const uint8_t *p_buffer, int p_len, int *r_len);
-
// Called by Node.rpc
void rpcp(Node *p_node, int p_peer_id, const StringName &p_method, const Variant **p_arg, int p_argcount);
- // Called by Node._notification
- void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
+ // Replication API
+ Error spawn(Object *p_object, Variant p_config);
+ Error despawn(Object *p_object, Variant p_config);
+ Error replication_start(Object *p_object, Variant p_config);
+ Error replication_stop(Object *p_object, Variant p_config);
// Called by replicator
bool send_confirm_path(Node *p_node, NodePath p_path, int p_target, int &p_id);
Node *get_cached_node(int p_from, uint32_t p_node_id);
@@ -148,7 +174,6 @@ public:
void set_allow_object_decoding(bool p_enable);
bool is_object_decoding_allowed() const;
- MultiplayerReplicator *get_replicator() const { return replicator; }
RPCManager *get_rpc_manager() const { return rpc_manager; }
#ifdef DEBUG_ENABLED
diff --git a/core/multiplayer/multiplayer_replicator.cpp b/core/multiplayer/multiplayer_replicator.cpp
deleted file mode 100644
index e7de8219c7..0000000000
--- a/core/multiplayer/multiplayer_replicator.cpp
+++ /dev/null
@@ -1,791 +0,0 @@
-/*************************************************************************/
-/* multiplayer_replicator.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#include "core/multiplayer/multiplayer_replicator.h"
-
-#include "core/io/marshalls.h"
-#include "scene/main/node.h"
-#include "scene/resources/packed_scene.h"
-
-#define MAKE_ROOM(m_amount) \
- if (packet_cache.size() < m_amount) \
- packet_cache.resize(m_amount);
-
-Error MultiplayerReplicator::_sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer) {
- ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
- SceneConfig &cfg = replications[p_scene_id];
- int full_size = 0;
- bool same_size = true;
- int last_size = 0;
- bool all_raw = true;
- struct EncodeInfo {
- int size = 0;
- bool raw = false;
- List<Variant> state;
- };
- Map<ObjectID, struct EncodeInfo> state;
- if (tracked_objects.has(p_scene_id)) {
- for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
- Object *obj = ObjectDB::get_instance(obj_id);
- if (obj) {
- struct EncodeInfo info;
- Error err = _get_state(cfg.sync_properties, obj, info.state);
- ERR_CONTINUE(err);
- err = _encode_state(info.state, nullptr, info.size, &info.raw);
- ERR_CONTINUE(err);
- state[obj_id] = info;
- full_size += info.size;
- if (last_size && info.size != last_size) {
- same_size = false;
- }
- all_raw = all_raw && info.raw;
- last_size = info.size;
- }
- }
- }
- // Default implementation do not send empty updates.
- if (!full_size) {
- return OK;
- }
-#ifdef DEBUG_ENABLED
- if (full_size > 4096 && cfg.sync_interval) {
- WARN_PRINT_ONCE(vformat("The timed state update for scene %d is big (%d bytes) consider optimizing it", p_scene_id));
- }
-#endif
- if (same_size) {
- // This is fast and small. Should we allow more than 256 objects per type?
- // This costs us 1 byte.
- MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + 2 + full_size);
- } else {
- MAKE_ROOM(SYNC_CMD_OFFSET + 1 + 2 + state.size() * 2 + full_size);
- }
- int ofs = 0;
- uint8_t *ptr = packet_cache.ptrw();
- ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC | (same_size ? BYTE_OR_ZERO_FLAG : 0);
- ofs = 1;
- ofs += encode_uint64(p_scene_id, &ptr[ofs]);
- ptr[ofs] = cfg.sync_recv++;
- ofs += 1;
- ofs += encode_uint16(state.size(), &ptr[ofs]);
- if (same_size) {
- ofs += encode_uint16(last_size + (all_raw ? 1 << 15 : 0), &ptr[ofs]);
- }
- for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
- if (!state.has(obj_id)) {
- continue;
- }
- struct EncodeInfo &info = state[obj_id];
- Object *obj = ObjectDB::get_instance(obj_id);
- ERR_CONTINUE(!obj);
- int size = 0;
- if (!same_size) {
- // We need to encode the size of every object.
- ofs += encode_uint16(info.size + (info.raw ? 1 << 15 : 0), &ptr[ofs]);
- }
- Error err = _encode_state(info.state, &ptr[ofs], size, &info.raw);
- ERR_CONTINUE(err);
- ofs += size;
- }
- Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
- peer->set_target_peer(p_peer);
- peer->set_transfer_channel(0);
- peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_UNRELIABLE);
- return peer->put_packet(ptr, ofs);
-}
-
-void MultiplayerReplicator::_process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len) {
- ERR_FAIL_COND_MSG(p_packet_len < SYNC_CMD_OFFSET + 5, "Invalid spawn packet received");
- ERR_FAIL_COND_MSG(!replications.has(p_id), "Invalid spawn ID received " + itos(p_id));
- SceneConfig &cfg = replications[p_id];
- ERR_FAIL_COND_MSG(cfg.mode != REPLICATION_MODE_SERVER || multiplayer->is_server(), "The default implementation only allows sync packets from the server");
- const bool same_size = p_packet[0] & BYTE_OR_ZERO_FLAG;
- int ofs = SYNC_CMD_OFFSET;
- int time = p_packet[ofs];
- // Skip old update.
- if (time < cfg.sync_recv && cfg.sync_recv - time < 127) {
- return;
- }
- cfg.sync_recv = time;
- ofs += 1;
- int count = decode_uint16(&p_packet[ofs]);
- ofs += 2;
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND(!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count);
-#else
- if (!tracked_objects.has(p_id) || tracked_objects[p_id].size() != count) {
- return;
- }
-#endif
- int data_size = 0;
- bool raw = false;
- if (same_size) {
- // This is fast and optimized.
- data_size = decode_uint16(&p_packet[ofs]);
- raw = (data_size & (1 << 15)) != 0;
- data_size = data_size & ~(1 << 15);
- ofs += 2;
- ERR_FAIL_COND(p_packet_len - ofs < data_size * count);
- }
- for (const ObjectID &obj_id : tracked_objects[p_id]) {
- Object *obj = ObjectDB::get_instance(obj_id);
- ERR_CONTINUE(!obj);
- if (!same_size) {
- // This is slow and wasteful.
- data_size = decode_uint16(&p_packet[ofs]);
- raw = (data_size & (1 << 15)) != 0;
- data_size = data_size & ~(1 << 15);
- ofs += 2;
- ERR_FAIL_COND(p_packet_len - ofs < data_size);
- }
- int size = 0;
- Error err = _decode_state(cfg.sync_properties, obj, &p_packet[ofs], data_size, size, raw);
- ofs += data_size;
- ERR_CONTINUE(err);
- ERR_CONTINUE(size != data_size);
- }
-}
-
-Error MultiplayerReplicator::_send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn) {
- ERR_FAIL_COND_V(p_spawn && !p_obj, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
- Error err;
- // Prepare state
- List<Variant> state_variants;
- int state_len = 0;
- const SceneConfig &cfg = replications[p_scene_id];
- if (p_spawn) {
- if ((err = _get_state(cfg.properties, p_obj, state_variants)) != OK) {
- return err;
- }
- }
-
- bool is_raw = false;
- if (state_variants.size() == 1 && state_variants[0].get_type() == Variant::PACKED_BYTE_ARRAY) {
- is_raw = true;
- const PackedByteArray pba = state_variants[0];
- state_len = pba.size();
- } else if (state_variants.size()) {
- err = _encode_state(state_variants, nullptr, state_len);
- ERR_FAIL_COND_V(err, err);
- } else {
- is_raw = true;
- }
-
- int ofs = 0;
-
- // Prepare simplified path
- const Node *root_node = multiplayer->get_root_node();
- ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED);
- NodePath rel_path = (root_node->get_path()).rel_path_to(p_path);
- const Vector<StringName> names = rel_path.get_names();
- ERR_FAIL_COND_V(names.size() < 2, ERR_INVALID_PARAMETER);
-
- NodePath parent = NodePath(names.slice(0, names.size() - 1), false);
- ERR_FAIL_COND_V_MSG(!root_node->has_node(parent), ERR_INVALID_PARAMETER, "Path not found: " + parent);
-
- int path_id = 0;
- multiplayer->send_confirm_path(root_node->get_node(parent), parent, p_peer_id, path_id);
-
- // Encode name and parent ID.
- CharString cname = String(names[names.size() - 1]).utf8();
- int nlen = encode_cstring(cname.get_data(), nullptr);
- MAKE_ROOM(SPAWN_CMD_OFFSET + 4 + 4 + nlen + state_len);
- uint8_t *ptr = packet_cache.ptrw();
- ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) | (is_raw ? BYTE_OR_ZERO_FLAG : 0);
- ofs = 1;
- ofs += encode_uint64(p_scene_id, &ptr[ofs]);
- ofs += encode_uint32(path_id, &ptr[ofs]);
- ofs += encode_uint32(nlen, &ptr[ofs]);
- ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
-
- // Encode state.
- if (!is_raw) {
- _encode_state(state_variants, &ptr[ofs], state_len);
- } else if (state_len) {
- PackedByteArray pba = state_variants[0];
- memcpy(&ptr[ofs], pba.ptr(), state_len);
- }
-
- Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
- peer->set_target_peer(p_peer_id);
- peer->set_transfer_channel(0);
- peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
- return peer->put_packet(ptr, ofs + state_len);
-}
-
-void MultiplayerReplicator::_process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn) {
- ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET + 9, "Invalid spawn packet received");
- int ofs = SPAWN_CMD_OFFSET;
- uint32_t node_target = decode_uint32(&p_packet[ofs]);
- Node *parent = multiplayer->get_cached_node(p_from, node_target);
- ofs += 4;
- ERR_FAIL_COND_MSG(parent == nullptr, "Invalid packet received. Requested node was not found.");
-
- uint32_t name_len = decode_uint32(&p_packet[ofs]);
- ofs += 4;
- ERR_FAIL_COND_MSG(name_len > uint32_t(p_packet_len - ofs), vformat("Invalid spawn packet size: %d, wants: %d", p_packet_len, ofs + name_len));
- ERR_FAIL_COND_MSG(name_len < 1, "Zero spawn name size.");
-
- const String name = String::utf8((const char *)&p_packet[ofs], name_len);
- // We need to make sure no trickery happens here (e.g. despawning a subpath), but we want to allow autogenerated ("@") node names.
- ERR_FAIL_COND_MSG(name.validate_node_name() != name.replace("@", ""), vformat("Invalid node name received: '%s'", name));
- ofs += name_len;
-
- const SceneConfig &cfg = replications[p_scene_id];
- if (cfg.mode == REPLICATION_MODE_SERVER && p_from == 1) {
- String scene_path = ResourceUID::get_singleton()->get_id_path(p_scene_id);
- if (p_spawn) {
- const bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1;
-
- ERR_FAIL_COND_MSG(parent->has_node(name), vformat("Unable to spawn node. Node already exists: %s/%s", parent->get_path(), name));
- RES res = ResourceLoader::load(scene_path);
- ERR_FAIL_COND_MSG(!res.is_valid(), "Unable to load scene to spawn at path: " + scene_path);
- PackedScene *scene = Object::cast_to<PackedScene>(res.ptr());
- ERR_FAIL_COND(!scene);
- Node *node = scene->instantiate();
- ERR_FAIL_COND(!node);
- replicated_nodes[node->get_instance_id()] = p_scene_id;
- _track(p_scene_id, node);
- int size;
- _decode_state(cfg.properties, node, &p_packet[ofs], p_packet_len - ofs, size, is_raw);
- parent->_add_child_nocheck(node, name);
- emit_signal(SNAME("spawned"), p_scene_id, node);
- } else {
- ERR_FAIL_COND_MSG(!parent->has_node(name), vformat("Path not found: %s/%s", parent->get_path(), name));
- Node *node = parent->get_node(name);
- ERR_FAIL_COND_MSG(!replicated_nodes.has(node->get_instance_id()), vformat("Trying to despawn a Node that was not replicated: %s/%s", parent->get_path(), name));
- emit_signal(SNAME("despawned"), p_scene_id, node);
- _untrack(p_scene_id, node);
- replicated_nodes.erase(node->get_instance_id());
- node->queue_delete();
- }
- } else {
- PackedByteArray data;
- if (p_packet_len > ofs) {
- data.resize(p_packet_len - ofs);
- memcpy(data.ptrw(), &p_packet[ofs], data.size());
- }
- if (p_spawn) {
- emit_signal(SNAME("spawn_requested"), p_from, p_scene_id, parent, name, data);
- } else {
- emit_signal(SNAME("despawn_requested"), p_from, p_scene_id, parent, name, data);
- }
- }
-}
-
-void MultiplayerReplicator::process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn) {
- ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received");
- ResourceUID::ID id = decode_uint64(&p_packet[1]);
- ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id));
-
- const SceneConfig &cfg = replications[id];
- if (cfg.on_spawn_despawn_receive.is_valid()) {
- int ofs = SPAWN_CMD_OFFSET;
- bool is_raw = ((p_packet[0] & BYTE_OR_ZERO_FLAG) >> BYTE_OR_ZERO_SHIFT) == 1;
- Variant data;
- int left = p_packet_len - ofs;
- if (is_raw && left) {
- PackedByteArray pba;
- pba.resize(left);
- memcpy(pba.ptrw(), &p_packet[ofs], pba.size());
- data = pba;
- } else if (left) {
- ERR_FAIL_COND(decode_variant(data, &p_packet[ofs], left) != OK);
- }
-
- Variant args[4];
- args[0] = p_from;
- args[1] = id;
- args[2] = data;
- args[3] = p_spawn;
- const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] };
- Callable::CallError ce;
- Variant ret;
- cfg.on_spawn_despawn_receive.call(argp, 4, ret, ce);
- ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom receive function failed");
- } else {
- _process_default_spawn_despawn(p_from, id, p_packet, p_packet_len, p_spawn);
- }
-}
-
-void MultiplayerReplicator::process_sync(int p_from, const uint8_t *p_packet, int p_packet_len) {
- ERR_FAIL_COND_MSG(p_packet_len < SPAWN_CMD_OFFSET, "Invalid spawn packet received");
- ResourceUID::ID id = decode_uint64(&p_packet[1]);
- ERR_FAIL_COND_MSG(!replications.has(id), "Invalid spawn ID received " + itos(id));
- const SceneConfig &cfg = replications[id];
- if (cfg.on_sync_receive.is_valid()) {
- Array objs;
- if (tracked_objects.has(id)) {
- objs.resize(tracked_objects[id].size());
- int idx = 0;
- for (const ObjectID &obj_id : tracked_objects[id]) {
- objs[idx++] = ObjectDB::get_instance(obj_id);
- }
- }
- PackedByteArray pba;
- pba.resize(p_packet_len - SYNC_CMD_OFFSET);
- if (pba.size()) {
- memcpy(pba.ptrw(), p_packet + SYNC_CMD_OFFSET, p_packet_len - SYNC_CMD_OFFSET);
- }
- Variant args[4] = { p_from, id, objs, pba };
- Variant *argp[4] = { args, &args[1], &args[2], &args[3] };
- Callable::CallError ce;
- Variant ret;
- cfg.on_sync_receive.call((const Variant **)argp, 4, ret, ce);
- ERR_FAIL_COND_MSG(ce.error != Callable::CallError::CALL_OK, "Custom sync function failed");
- } else {
- ERR_FAIL_COND_MSG(p_from != 1, "Default sync implementation only allow syncing from server to client");
- _process_default_sync(id, p_packet, p_packet_len);
- }
-}
-
-Error MultiplayerReplicator::_get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant) {
- ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Cannot encode null object");
- for (const StringName &prop : p_properties) {
- bool valid = false;
- const Variant v = p_obj->get(prop, &valid);
- ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop));
- r_variant.push_back(v);
- }
- return OK;
-}
-
-Error MultiplayerReplicator::_encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw) {
- r_len = 0;
- int size = 0;
-
- // Try raw encoding optimization.
- if (r_raw && p_variants.size() == 1) {
- *r_raw = false;
- const Variant v = p_variants[0];
- if (v.get_type() == Variant::PACKED_BYTE_ARRAY) {
- *r_raw = true;
- const PackedByteArray pba = v;
- if (p_buffer) {
- memcpy(p_buffer, pba.ptr(), pba.size());
- }
- r_len += pba.size();
- } else {
- multiplayer->encode_and_compress_variant(v, p_buffer, size);
- r_len += size;
- }
- return OK;
- }
-
- // Regular encoding.
- for (const Variant &v : p_variants) {
- multiplayer->encode_and_compress_variant(v, p_buffer ? p_buffer + r_len : nullptr, size);
- r_len += size;
- }
- return OK;
-}
-
-Error MultiplayerReplicator::_decode_state(const List<StringName> &p_properties, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw) {
- r_len = 0;
- int argc = p_properties.size();
- if (argc == 0 && p_raw) {
- ERR_FAIL_COND_V_MSG(p_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes.");
- return OK;
- }
- ERR_FAIL_COND_V(p_raw && argc != 1, ERR_INVALID_DATA);
- if (p_raw) {
- r_len = p_len;
- PackedByteArray pba;
- pba.resize(p_len);
- memcpy(pba.ptrw(), p_buffer, p_len);
- p_obj->set(p_properties[0], pba);
- return OK;
- }
-
- Vector<Variant> args;
- Vector<const Variant *> argp;
- args.resize(argc);
-
- for (int i = 0; i < argc; i++) {
- ERR_FAIL_COND_V_MSG(r_len >= p_len, ERR_INVALID_DATA, "Invalid packet received. Size too small.");
-
- int vlen;
- Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_buffer[r_len], p_len - r_len, &vlen);
- ERR_FAIL_COND_V_MSG(err != OK, err, "Invalid packet received. Unable to decode state variable.");
- r_len += vlen;
- }
- ERR_FAIL_COND_V_MSG(p_len - r_len != 0, ERR_INVALID_DATA, "Buffer has trailing bytes.");
-
- int i = 0;
- for (const StringName &prop : p_properties) {
- p_obj->set(prop, args[i]);
- i += 1;
- }
- return OK;
-}
-
-Error MultiplayerReplicator::spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) {
- ERR_FAIL_COND_V(p_mode < REPLICATION_MODE_NONE || p_mode > REPLICATION_MODE_CUSTOM, ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty");
-#ifdef TOOLS_ENABLED
- if (!p_on_send.is_valid()) {
- // We allow non scene spawning with custom callables.
- String path = ResourceUID::get_singleton()->get_id_path(p_id);
- RES res = ResourceLoader::load(path);
- ERR_FAIL_COND_V(!res->is_class("PackedScene"), ERR_INVALID_PARAMETER);
- }
-#endif
- if (p_mode == REPLICATION_MODE_NONE) {
- if (replications.has(p_id)) {
- replications.erase(p_id);
- }
- } else {
- SceneConfig cfg;
- cfg.mode = p_mode;
- for (int i = 0; i < p_props.size(); i++) {
- cfg.properties.push_back(p_props[i]);
- }
- cfg.on_spawn_despawn_send = p_on_send;
- cfg.on_spawn_despawn_receive = p_on_recv;
- replications[p_id] = cfg;
- }
- return OK;
-}
-
-Error MultiplayerReplicator::sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props, const Callable &p_on_send, const Callable &p_on_recv) {
- ERR_FAIL_COND_V(!ResourceUID::get_singleton()->has_id(p_id), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V_MSG(p_on_send.is_valid() != p_on_recv.is_valid(), ERR_INVALID_PARAMETER, "Send and receive custom callables must be both valid or both empty");
- ERR_FAIL_COND_V(!replications.has(p_id), ERR_UNCONFIGURED);
- SceneConfig &cfg = replications[p_id];
- ERR_FAIL_COND_V_MSG(p_interval && cfg.mode != REPLICATION_MODE_SERVER && !p_on_send.is_valid(), ERR_INVALID_PARAMETER, "Timed updates in custom mode are only allowed if custom callbacks are also specified");
- for (int i = 0; i < p_props.size(); i++) {
- cfg.sync_properties.push_back(p_props[i]);
- }
- cfg.on_sync_send = p_on_send;
- cfg.on_sync_receive = p_on_recv;
- cfg.sync_interval = p_interval * 1000;
- return OK;
-}
-
-Error MultiplayerReplicator::_send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn) {
- int data_size = 0;
- int is_raw = false;
- if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) {
- const PackedByteArray pba = p_data;
- is_raw = true;
- data_size = p_data.operator PackedByteArray().size();
- } else if (p_data.get_type() == Variant::NIL) {
- is_raw = true;
- } else {
- Error err = encode_variant(p_data, nullptr, data_size);
- ERR_FAIL_COND_V(err, err);
- }
- MAKE_ROOM(SPAWN_CMD_OFFSET + data_size);
- uint8_t *ptr = packet_cache.ptrw();
- ptr[0] = (p_spawn ? MultiplayerAPI::NETWORK_COMMAND_SPAWN : MultiplayerAPI::NETWORK_COMMAND_DESPAWN) + ((is_raw ? 1 : 0) << BYTE_OR_ZERO_SHIFT);
- encode_uint64(p_scene_id, &ptr[1]);
- if (p_data.get_type() == Variant::PACKED_BYTE_ARRAY) {
- const PackedByteArray pba = p_data;
- memcpy(&ptr[SPAWN_CMD_OFFSET], pba.ptr(), pba.size());
- } else if (data_size) {
- encode_variant(p_data, &ptr[SPAWN_CMD_OFFSET], data_size);
- }
- Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
- peer->set_target_peer(p_peer_id);
- peer->set_transfer_channel(0);
- peer->set_transfer_mode(Multiplayer::TRANSFER_MODE_RELIABLE);
- return peer->put_packet(ptr, SPAWN_CMD_OFFSET + data_size);
-}
-
-Error MultiplayerReplicator::send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
- ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
- ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- if (cfg.on_spawn_despawn_send.is_valid()) {
- return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, true);
- } else {
- ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual despawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests.");
- NodePath path = p_path;
- Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr;
- if (path.is_empty() && obj) {
- Node *node = Object::cast_to<Node>(obj);
- if (node && node->is_inside_tree()) {
- path = node->get_path();
- }
- }
- ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Despawn default implementation requires a despawn path, or the data to be a node inside the SceneTree");
- return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, false);
- }
-}
-
-Error MultiplayerReplicator::send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, const NodePath &p_path) {
- ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
- ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- if (cfg.on_spawn_despawn_send.is_valid()) {
- return _send_spawn_despawn(p_peer_id, p_scene_id, p_data, false);
- } else {
- ERR_FAIL_COND_V_MSG(cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server(), ERR_UNAVAILABLE, "Manual spawn is restricted in default server mode implementation. Use custom mode if you desire control over server spawn requests.");
- NodePath path = p_path;
- Object *obj = p_data.get_type() == Variant::OBJECT ? p_data.get_validated_object() : nullptr;
- ERR_FAIL_COND_V_MSG(!obj, ERR_INVALID_PARAMETER, "Spawn default implementation requires the data to be an object.");
- if (path.is_empty()) {
- Node *node = Object::cast_to<Node>(obj);
- if (node && node->is_inside_tree()) {
- path = node->get_path();
- }
- }
- ERR_FAIL_COND_V_MSG(path.is_empty(), ERR_INVALID_PARAMETER, "Spawn default implementation requires a spawn path, or the data to be a node inside the SceneTree");
- return _send_default_spawn_despawn(p_peer_id, p_scene_id, obj, path, true);
- }
-}
-
-Error MultiplayerReplicator::_spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn) {
- ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
-
- const SceneConfig &cfg = replications[p_scene_id];
- if (cfg.on_spawn_despawn_send.is_valid()) {
- Variant args[4];
- args[0] = p_peer;
- args[1] = p_scene_id;
- args[2] = p_obj;
- args[3] = p_spawn;
- const Variant *argp[] = { &args[0], &args[1], &args[2], &args[3] };
- Callable::CallError ce;
- Variant ret;
- cfg.on_spawn_despawn_send.call(argp, 4, ret, ce);
- ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom send function failed");
- return OK;
- } else {
- Node *node = Object::cast_to<Node>(p_obj);
- ERR_FAIL_COND_V_MSG(!p_obj, ERR_INVALID_PARAMETER, "Only nodes can be replicated by the default implementation");
- return _send_default_spawn_despawn(p_peer, p_scene_id, node, node->get_path(), p_spawn);
- }
-}
-
-Error MultiplayerReplicator::spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) {
- return _spawn_despawn(p_scene_id, p_obj, p_peer, true);
-}
-
-Error MultiplayerReplicator::despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer) {
- return _spawn_despawn(p_scene_id, p_obj, p_peer, false);
-}
-
-PackedByteArray MultiplayerReplicator::encode_state(const ResourceUID::ID &p_scene_id, const Object *p_obj, bool p_initial) {
- PackedByteArray state;
- ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), state, vformat("Spawnable not found: %d", p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- int len = 0;
- List<Variant> state_vars;
- const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties;
- Error err = _get_state(props, p_obj, state_vars);
- ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to retrieve object state.");
- err = _encode_state(state_vars, nullptr, len);
- ERR_FAIL_COND_V_MSG(err != OK, state, "Unable to encode object state.");
- state.resize(len);
- _encode_state(state_vars, state.ptrw(), len);
- return state;
-}
-
-Error MultiplayerReplicator::decode_state(const ResourceUID::ID &p_scene_id, Object *p_obj, const PackedByteArray p_data, bool p_initial) {
- ERR_FAIL_COND_V_MSG(!replications.has(p_scene_id), ERR_INVALID_PARAMETER, vformat("Spawnable not found: %d", p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- const List<StringName> props = p_initial ? cfg.properties : cfg.sync_properties;
- int size;
- return _decode_state(props, p_obj, p_data.ptr(), p_data.size(), size);
-}
-
-void MultiplayerReplicator::scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter) {
- if (!multiplayer->has_multiplayer_peer()) {
- return;
- }
- Node *root_node = multiplayer->get_root_node();
- ERR_FAIL_COND(!p_node || !p_node->get_parent() || !root_node);
- NodePath path = (root_node->get_path()).rel_path_to(p_node->get_parent()->get_path());
- if (path.is_empty()) {
- return;
- }
- ResourceUID::ID id = ResourceLoader::get_resource_uid(p_scene);
- if (!replications.has(id)) {
- return;
- }
- const SceneConfig &cfg = replications[id];
- if (p_enter) {
- if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server()) {
- replicated_nodes[p_node->get_instance_id()] = id;
- _track(id, p_node);
- spawn(id, p_node, 0);
- }
- emit_signal(SNAME("replicated_instance_added"), id, p_node);
- } else {
- if (cfg.mode == REPLICATION_MODE_SERVER && multiplayer->is_server() && replicated_nodes.has(p_node->get_instance_id())) {
- replicated_nodes.erase(p_node->get_instance_id());
- _untrack(id, p_node);
- despawn(id, p_node, 0);
- }
- emit_signal(SNAME("replicated_instance_removed"), id, p_node);
- }
-}
-
-void MultiplayerReplicator::spawn_all(int p_peer) {
- for (const KeyValue<ObjectID, ResourceUID::ID> &E : replicated_nodes) {
- // Only server mode adds to replicated_nodes, no need to check it.
- Object *obj = ObjectDB::get_instance(E.key);
- ERR_CONTINUE(!obj);
- Node *node = Object::cast_to<Node>(obj);
- ERR_CONTINUE(!node);
- spawn(E.value, node, p_peer);
- }
-}
-
-void MultiplayerReplicator::poll() {
- for (KeyValue<ResourceUID::ID, SceneConfig> &E : replications) {
- if (!E.value.sync_interval) {
- continue;
- }
- if (E.value.mode == REPLICATION_MODE_SERVER && !multiplayer->is_server()) {
- continue;
- }
- uint64_t time = OS::get_singleton()->get_ticks_usec();
- if (E.value.sync_last + E.value.sync_interval <= time) {
- sync_all(E.key, 0);
- E.value.sync_last = time;
- }
- // Handle wrapping.
- if (E.value.sync_last > time) {
- E.value.sync_last = time;
- }
- }
-}
-
-void MultiplayerReplicator::track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
- ERR_FAIL_COND(!replications.has(p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
- _track(p_scene_id, p_obj);
-}
-
-void MultiplayerReplicator::_track(const ResourceUID::ID &p_scene_id, Object *p_obj) {
- ERR_FAIL_COND(!p_obj);
- ERR_FAIL_COND(!replications.has(p_scene_id));
- if (!tracked_objects.has(p_scene_id)) {
- tracked_objects[p_scene_id] = List<ObjectID>();
- }
- tracked_objects[p_scene_id].push_back(p_obj->get_instance_id());
-}
-
-void MultiplayerReplicator::untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
- ERR_FAIL_COND(!replications.has(p_scene_id));
- const SceneConfig &cfg = replications[p_scene_id];
- ERR_FAIL_COND_MSG(cfg.mode == REPLICATION_MODE_SERVER, "Manual object tracking is not allowed in server mode.");
- _untrack(p_scene_id, p_obj);
-}
-
-void MultiplayerReplicator::_untrack(const ResourceUID::ID &p_scene_id, Object *p_obj) {
- ERR_FAIL_COND(!p_obj);
- ERR_FAIL_COND(!replications.has(p_scene_id));
- if (tracked_objects.has(p_scene_id)) {
- tracked_objects[p_scene_id].erase(p_obj->get_instance_id());
- }
-}
-
-Error MultiplayerReplicator::sync_all(const ResourceUID::ID &p_scene_id, int p_peer) {
- ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
- if (!tracked_objects.has(p_scene_id)) {
- return OK;
- }
- const SceneConfig &cfg = replications[p_scene_id];
- if (cfg.on_sync_send.is_valid()) {
- Array objs;
- if (tracked_objects.has(p_scene_id)) {
- objs.resize(tracked_objects[p_scene_id].size());
- int idx = 0;
- for (const ObjectID &obj_id : tracked_objects[p_scene_id]) {
- objs[idx++] = ObjectDB::get_instance(obj_id);
- }
- }
- Variant args[3] = { p_scene_id, objs, p_peer };
- Variant *argp[3] = { args, &args[1], &args[2] };
- Callable::CallError ce;
- Variant ret;
- cfg.on_sync_send.call((const Variant **)argp, 3, ret, ce);
- ERR_FAIL_COND_V_MSG(ce.error != Callable::CallError::CALL_OK, FAILED, "Custom sync function failed");
- return OK;
- } else if (cfg.sync_properties.size()) {
- return _sync_all_default(p_scene_id, p_peer);
- }
- return OK;
-}
-
-Error MultiplayerReplicator::send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_transfer_mode, int p_channel) {
- ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
- ERR_FAIL_COND_V(!replications.has(p_scene_id), ERR_INVALID_PARAMETER);
- const SceneConfig &cfg = replications[p_scene_id];
- ERR_FAIL_COND_V_MSG(!cfg.on_sync_send.is_valid(), ERR_UNCONFIGURED, "Sending raw sync messages is only available with custom functions");
- MAKE_ROOM(SYNC_CMD_OFFSET + p_data.size());
- uint8_t *ptr = packet_cache.ptrw();
- ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
- encode_uint64(p_scene_id, &ptr[1]);
- if (p_data.size()) {
- memcpy(&ptr[SYNC_CMD_OFFSET], p_data.ptr(), p_data.size());
- }
- Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
- peer->set_target_peer(p_peer_id);
- peer->set_transfer_channel(p_channel);
- peer->set_transfer_mode(p_transfer_mode);
- return peer->put_packet(ptr, SYNC_CMD_OFFSET + p_data.size());
-}
-
-void MultiplayerReplicator::clear() {
- tracked_objects.clear();
- replicated_nodes.clear();
-}
-
-void MultiplayerReplicator::_bind_methods() {
- ClassDB::bind_method(D_METHOD("spawn_config", "scene_id", "spawn_mode", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::spawn_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
- ClassDB::bind_method(D_METHOD("sync_config", "scene_id", "interval", "properties", "custom_send", "custom_receive"), &MultiplayerReplicator::sync_config, DEFVAL(TypedArray<StringName>()), DEFVAL(Callable()), DEFVAL(Callable()));
- ClassDB::bind_method(D_METHOD("despawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::despawn, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("spawn", "scene_id", "object", "peer_id"), &MultiplayerReplicator::spawn, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("send_despawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_despawn, DEFVAL(Variant()), DEFVAL(NodePath()));
- ClassDB::bind_method(D_METHOD("send_spawn", "peer_id", "scene_id", "data", "path"), &MultiplayerReplicator::send_spawn, DEFVAL(Variant()), DEFVAL(NodePath()));
- ClassDB::bind_method(D_METHOD("send_sync", "peer_id", "scene_id", "data", "transfer_mode", "channel"), &MultiplayerReplicator::send_sync, DEFVAL(Multiplayer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
- ClassDB::bind_method(D_METHOD("sync_all", "scene_id", "peer_id"), &MultiplayerReplicator::sync_all, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("track", "scene_id", "object"), &MultiplayerReplicator::track);
- ClassDB::bind_method(D_METHOD("untrack", "scene_id", "object"), &MultiplayerReplicator::untrack);
- ClassDB::bind_method(D_METHOD("encode_state", "scene_id", "object", "initial"), &MultiplayerReplicator::encode_state, DEFVAL(true));
- ClassDB::bind_method(D_METHOD("decode_state", "scene_id", "object", "data", "initial"), &MultiplayerReplicator::decode_state, DEFVAL(true));
-
- ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
- ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
- ADD_SIGNAL(MethodInfo("despawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("spawn_requested", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "parent", PROPERTY_HINT_RESOURCE_TYPE, "Node"), PropertyInfo(Variant::STRING, "name"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "data")));
- ADD_SIGNAL(MethodInfo("replicated_instance_added", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
- ADD_SIGNAL(MethodInfo("replicated_instance_removed", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
-
- BIND_ENUM_CONSTANT(REPLICATION_MODE_NONE);
- BIND_ENUM_CONSTANT(REPLICATION_MODE_SERVER);
- BIND_ENUM_CONSTANT(REPLICATION_MODE_CUSTOM);
-}
diff --git a/core/multiplayer/multiplayer_replicator.h b/core/multiplayer/multiplayer_replicator.h
deleted file mode 100644
index a9cd6e211e..0000000000
--- a/core/multiplayer/multiplayer_replicator.h
+++ /dev/null
@@ -1,138 +0,0 @@
-/*************************************************************************/
-/* multiplayer_replicator.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* Permission is hereby granted, free of charge, to any person obtaining */
-/* a copy of this software and associated documentation files (the */
-/* "Software"), to deal in the Software without restriction, including */
-/* without limitation the rights to use, copy, modify, merge, publish, */
-/* distribute, sublicense, and/or sell copies of the Software, and to */
-/* permit persons to whom the Software is furnished to do so, subject to */
-/* the following conditions: */
-/* */
-/* The above copyright notice and this permission notice shall be */
-/* included in all copies or substantial portions of the Software. */
-/* */
-/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
-/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
-/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
-/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
-/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
-/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
-/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
-/*************************************************************************/
-
-#ifndef MULTIPLAYER_REPLICATOR_H
-#define MULTIPLAYER_REPLICATOR_H
-
-#include "core/multiplayer/multiplayer_api.h"
-
-#include "core/io/resource_uid.h"
-#include "core/templates/hash_map.h"
-#include "core/variant/typed_array.h"
-
-class MultiplayerReplicator : public Object {
- GDCLASS(MultiplayerReplicator, Object);
-
-public:
- enum {
- SPAWN_CMD_OFFSET = 9,
- SYNC_CMD_OFFSET = 9,
- };
-
- enum ReplicationMode {
- REPLICATION_MODE_NONE,
- REPLICATION_MODE_SERVER,
- REPLICATION_MODE_CUSTOM,
- };
-
- struct SceneConfig {
- ReplicationMode mode;
- uint64_t sync_interval = 0;
- uint64_t sync_last = 0;
- uint8_t sync_recv = 0;
- List<StringName> properties;
- List<StringName> sync_properties;
- Callable on_spawn_despawn_send;
- Callable on_spawn_despawn_receive;
- Callable on_sync_send;
- Callable on_sync_receive;
- };
-
-protected:
- static void _bind_methods();
-
-private:
- enum {
- BYTE_OR_ZERO_SHIFT = MultiplayerAPI::CMD_FLAG_0_SHIFT,
- };
-
- enum {
- BYTE_OR_ZERO_FLAG = 1 << BYTE_OR_ZERO_SHIFT,
- };
-
- MultiplayerAPI *multiplayer = nullptr;
- Vector<uint8_t> packet_cache;
- Map<ResourceUID::ID, SceneConfig> replications;
- Map<ObjectID, ResourceUID::ID> replicated_nodes;
- HashMap<ResourceUID::ID, List<ObjectID>> tracked_objects;
-
- // Encoding
- Error _get_state(const List<StringName> &p_properties, const Object *p_obj, List<Variant> &r_variant);
- Error _encode_state(const List<Variant> &p_variants, uint8_t *p_buffer, int &r_len, bool *r_raw = nullptr);
- Error _decode_state(const List<StringName> &p_cfg, Object *p_obj, const uint8_t *p_buffer, int p_len, int &r_len, bool p_raw = false);
-
- // Spawn
- Error _spawn_despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer, bool p_spawn);
- Error _send_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data, bool p_spawn);
- void _process_default_spawn_despawn(int p_from, const ResourceUID::ID &p_scene_id, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
- Error _send_default_spawn_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, Object *p_obj, const NodePath &p_path, bool p_spawn);
-
- // Sync
- void _process_default_sync(const ResourceUID::ID &p_id, const uint8_t *p_packet, int p_packet_len);
- Error _sync_all_default(const ResourceUID::ID &p_scene_id, int p_peer);
- void _track(const ResourceUID::ID &p_scene_id, Object *p_object);
- void _untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
-
-public:
- void clear();
-
- // Encoding
- PackedByteArray encode_state(const ResourceUID::ID &p_scene_id, const Object *p_node, bool p_initial);
- Error decode_state(const ResourceUID::ID &p_scene_id, Object *p_node, PackedByteArray p_data, bool p_initial);
-
- // Spawn
- Error spawn_config(const ResourceUID::ID &p_id, ReplicationMode p_mode, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
- Error spawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
- Error despawn(ResourceUID::ID p_scene_id, Object *p_obj, int p_peer = 0);
- Error send_despawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
- Error send_spawn(int p_peer_id, const ResourceUID::ID &p_scene_id, const Variant &p_data = Variant(), const NodePath &p_path = NodePath());
-
- // Sync
- Error sync_config(const ResourceUID::ID &p_id, uint64_t p_interval, const TypedArray<StringName> &p_props = TypedArray<StringName>(), const Callable &p_on_send = Callable(), const Callable &p_on_recv = Callable());
- Error sync_all(const ResourceUID::ID &p_scene_id, int p_peer);
- Error send_sync(int p_peer_id, const ResourceUID::ID &p_scene_id, PackedByteArray p_data, Multiplayer::TransferMode p_mode, int p_channel);
- void track(const ResourceUID::ID &p_scene_id, Object *p_object);
- void untrack(const ResourceUID::ID &p_scene_id, Object *p_object);
-
- // Used by MultiplayerAPI
- void spawn_all(int p_peer);
- void process_spawn_despawn(int p_from, const uint8_t *p_packet, int p_packet_len, bool p_spawn);
- void process_sync(int p_from, const uint8_t *p_packet, int p_packet_len);
- void scene_enter_exit_notify(const String &p_scene, Node *p_node, bool p_enter);
- void poll();
-
- MultiplayerReplicator(MultiplayerAPI *p_multiplayer) {
- multiplayer = p_multiplayer;
- }
-};
-
-VARIANT_ENUM_CAST(MultiplayerReplicator::ReplicationMode);
-
-#endif // MULTIPLAYER_REPLICATOR_H
diff --git a/core/multiplayer/rpc_manager.cpp b/core/multiplayer/rpc_manager.cpp
index 7736637349..1e6d2108be 100644
--- a/core/multiplayer/rpc_manager.cpp
+++ b/core/multiplayer/rpc_manager.cpp
@@ -235,16 +235,12 @@ void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int
ERR_FAIL_COND_MSG(!can_call, "RPC '" + String(config.name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)config.rpc_mode) + ", authority is " + itos(p_node->get_multiplayer_authority()) + ".");
int argc = 0;
- bool byte_only = false;
const bool byte_only_or_no_args = p_packet[0] & BYTE_ONLY_OR_NO_ARGS_FLAG;
if (byte_only_or_no_args) {
if (p_offset < p_packet_len) {
// This packet contains only bytes.
argc = 1;
- byte_only = true;
- } else {
- // This rpc calls a method without parameters.
}
} else {
// Normal variant, takes the argument count from the packet.
@@ -262,25 +258,10 @@ void RPCManager::_process_rpc(Node *p_node, const uint16_t p_rpc_method_id, int
_profile_node_data("in_rpc", p_node->get_instance_id());
#endif
- if (byte_only) {
- Vector<uint8_t> pure_data;
- const int len = p_packet_len - p_offset;
- pure_data.resize(len);
- memcpy(pure_data.ptrw(), &p_packet[p_offset], len);
- args.write[0] = pure_data;
- argp.write[0] = &args[0];
- p_offset += len;
- } else {
- for (int i = 0; i < argc; i++) {
- ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
-
- int vlen;
- Error err = multiplayer->decode_and_decompress_variant(args.write[i], &p_packet[p_offset], p_packet_len - p_offset, &vlen);
- ERR_FAIL_COND_MSG(err != OK, "Invalid packet received. Unable to decode RPC argument.");
-
- argp.write[i] = &args[i];
- p_offset += vlen;
- }
+ int out;
+ MultiplayerAPI::decode_and_decompress_variants(args, &p_packet[p_offset], p_packet_len - p_offset, out, byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
+ for (int i = 0; i < argc; i++) {
+ argp.write[i] = &args[i];
}
Callable::CallError ce;
@@ -380,28 +361,19 @@ void RPCManager::_send_rpc(Node *p_from, int p_to, uint16_t p_rpc_id, const Mult
ofs += 2;
}
- if (p_argcount == 0) {
- byte_only_or_no_args = true;
- } else if (p_argcount == 1 && p_arg[0]->get_type() == Variant::PACKED_BYTE_ARRAY) {
- byte_only_or_no_args = true;
- // Special optimization when only the byte vector is sent.
- const Vector<uint8_t> data = *p_arg[0];
- MAKE_ROOM(ofs + data.size());
- memcpy(&(packet_cache.write[ofs]), data.ptr(), sizeof(uint8_t) * data.size());
- ofs += data.size();
+ int len;
+ Error err = MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, nullptr, len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
+ ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC arguments. THIS IS LIKELY A BUG IN THE ENGINE!");
+ if (byte_only_or_no_args) {
+ MAKE_ROOM(ofs + len);
} else {
- // Arguments
- MAKE_ROOM(ofs + 1);
+ MAKE_ROOM(ofs + 1 + len);
packet_cache.write[ofs] = p_argcount;
ofs += 1;
- for (int i = 0; i < p_argcount; i++) {
- int len(0);
- Error err = multiplayer->encode_and_compress_variant(*p_arg[i], nullptr, len);
- ERR_FAIL_COND_MSG(err != OK, "Unable to encode RPC argument. THIS IS LIKELY A BUG IN THE ENGINE!");
- MAKE_ROOM(ofs + len);
- multiplayer->encode_and_compress_variant(*p_arg[i], &(packet_cache.write[ofs]), len);
- ofs += len;
- }
+ }
+ if (len) {
+ MultiplayerAPI::encode_and_compress_variants(p_arg, p_argcount, &packet_cache.write[ofs], len, &byte_only_or_no_args, multiplayer->is_object_decoding_allowed());
+ ofs += len;
}
ERR_FAIL_COND(command_type > 7);
diff --git a/core/object/class_db.cpp b/core/object/class_db.cpp
index 03774bc36a..72a98ca20c 100644
--- a/core/object/class_db.cpp
+++ b/core/object/class_db.cpp
@@ -732,7 +732,7 @@ void ClassDB::bind_integer_constant(const StringName &p_class, const StringName
String enum_name = p_enum;
if (!enum_name.is_empty()) {
- if (enum_name.find(".") != -1) {
+ if (enum_name.contains(".")) {
enum_name = enum_name.get_slicec('.', 1);
}
diff --git a/core/object/make_virtuals.py b/core/object/make_virtuals.py
index e961745d96..5de1b49026 100644
--- a/core/object/make_virtuals.py
+++ b/core/object/make_virtuals.py
@@ -123,9 +123,9 @@ def generate_version(argcount, const=False, returns=False):
callargtext += ","
callargtext += " m_ret& r_ret"
s = s.replace("$CALLSIBEGIN", "Variant ret = ")
- s = s.replace("$CALLSIRET", "r_ret = ret;")
+ s = s.replace("$CALLSIRET", "r_ret = VariantCaster<m_ret>::cast(ret);")
s = s.replace("$CALLPTRRETPASS", "&ret")
- s = s.replace("$CALLPTRRET", "r_ret = ret;")
+ s = s.replace("$CALLPTRRET", "r_ret = (m_ret)ret;")
else:
s = s.replace("$CALLSIBEGIN", "")
s = s.replace("$CALLSIRET", "")
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index a18ec4d6ad..388368d181 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -69,7 +69,6 @@
#include "core/math/triangle_mesh.h"
#include "core/multiplayer/multiplayer_api.h"
#include "core/multiplayer/multiplayer_peer.h"
-#include "core/multiplayer/multiplayer_replicator.h"
#include "core/object/class_db.h"
#include "core/object/undo_redo.h"
#include "core/os/main_loop.h"
@@ -200,7 +199,6 @@ void register_core_types() {
GDREGISTER_VIRTUAL_CLASS(MultiplayerPeer);
GDREGISTER_CLASS(MultiplayerPeerExtension);
- GDREGISTER_VIRTUAL_CLASS(MultiplayerReplicator);
GDREGISTER_CLASS(MultiplayerAPI);
GDREGISTER_CLASS(MainLoop);
GDREGISTER_CLASS(Translation);
diff --git a/core/string/char_utils.h b/core/string/char_utils.h
new file mode 100644
index 0000000000..0afd058f01
--- /dev/null
+++ b/core/string/char_utils.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* char_utils.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef CHAR_UTILS_H
+#define CHAR_UTILS_H
+
+#include "core/typedefs.h"
+
+static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) {
+ return (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) {
+ return (c >= 'a' && c <= 'z');
+}
+
+static _FORCE_INLINE_ bool is_digit(char32_t c) {
+ return (c >= '0' && c <= '9');
+}
+
+static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
+ return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+}
+
+static _FORCE_INLINE_ bool is_binary_digit(char32_t c) {
+ return (c == '0' || c == '1');
+}
+
+static _FORCE_INLINE_ bool is_ascii_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+}
+
+static _FORCE_INLINE_ bool is_ascii_alphanumeric_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
+}
+
+static _FORCE_INLINE_ bool is_ascii_identifier_char(char32_t c) {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
+}
+
+static _FORCE_INLINE_ bool is_symbol(char32_t c) {
+ return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
+}
+
+static _FORCE_INLINE_ bool is_control(char32_t p_char) {
+ return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009f);
+}
+
+static _FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
+ return (p_char == ' ') || (p_char == 0x00a0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
+}
+
+static _FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
+ return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
+}
+
+static _FORCE_INLINE_ bool is_punct(char32_t p_char) {
+ return (p_char >= ' ' && p_char <= '/') || (p_char >= ':' && p_char <= '@') || (p_char >= '[' && p_char <= '^') || (p_char == '`') || (p_char >= '{' && p_char <= '~') || (p_char >= 0x2000 && p_char <= 0x206f) || (p_char >= 0x3000 && p_char <= 0x303f);
+}
+
+static _FORCE_INLINE_ bool is_underscore(char32_t p_char) {
+ return (p_char == '_');
+}
+
+#endif // CHAR_UTILS_H
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 355ee238e8..7cc41df9ef 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -213,14 +213,6 @@ static _character_accent_pair _character_to_accented[] = {
{ 'z', U"ź" },
};
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
Map<String, String> TranslationServer::language_map;
@@ -309,15 +301,15 @@ String TranslationServer::standardize_locale(const String &p_locale) const {
Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
lang = locale_elements[0];
if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script = locale_elements[1];
}
- if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country = locale_elements[2];
} else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang) {
variant = locale_elements[2].to_lower();
@@ -434,15 +426,15 @@ String TranslationServer::get_locale_name(const String &p_locale) const {
Vector<String> locale_elements = locale.split("_");
lang = locale_elements[0];
if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script = locale_elements[1];
}
- if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country = locale_elements[1];
}
}
if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country = locale_elements[2];
}
}
@@ -911,7 +903,7 @@ String TranslationServer::add_padding(String &p_message, int p_length) const {
}
const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!((p_character >= 'a' && p_character <= 'z') || (p_character >= 'A' && p_character <= 'Z'))) {
+ if (!is_ascii_char(p_character)) {
return nullptr;
}
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 6e0a7c7022..c4edc8c086 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -54,34 +54,14 @@
static const int MAX_DECIMALS = 32;
-static _FORCE_INLINE_ bool is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
-static _FORCE_INLINE_ bool is_hex_digit(char32_t c) {
- return (is_digit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
static _FORCE_INLINE_ char32_t lower_case(char32_t c) {
- return (is_upper_case(c) ? (c + ('a' - 'A')) : c);
+ return (is_ascii_upper_case(c) ? (c + ('a' - 'A')) : c);
}
const char CharString::_null = 0;
const char16_t Char16String::_null = 0;
const char32_t String::_null = 0;
-bool is_symbol(char32_t c) {
- return c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t' || c == ' ');
-}
-
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end) {
const String &s = p_s;
int beg = CLAMP(p_col, 0, s.length());
@@ -974,21 +954,21 @@ String String::camelcase_to_underscore(bool lowercase) const {
int start_index = 0;
for (int i = 1; i < this->size(); i++) {
- bool is_upper = is_upper_case(cstr[i]);
+ bool is_upper = is_ascii_upper_case(cstr[i]);
bool is_number = is_digit(cstr[i]);
bool are_next_2_lower = false;
bool is_next_lower = false;
bool is_next_number = false;
- bool was_precedent_upper = is_upper_case(cstr[i - 1]);
+ bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]);
bool was_precedent_number = is_digit(cstr[i - 1]);
if (i + 2 < this->size()) {
- are_next_2_lower = is_lower_case(cstr[i + 1]) && is_lower_case(cstr[i + 2]);
+ are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]);
}
if (i + 1 < this->size()) {
- is_next_lower = is_lower_case(cstr[i + 1]);
+ is_next_lower = is_ascii_lower_case(cstr[i + 1]);
is_next_number = is_digit(cstr[i + 1]);
}
@@ -2212,7 +2192,7 @@ bool String::is_numeric() const {
return false;
}
dot = true;
- } else if (c < '0' || c > '9') {
+ } else if (!is_digit(c)) {
return false;
}
}
@@ -3691,7 +3671,7 @@ bool String::is_valid_identifier() const {
}
}
- bool valid_char = is_digit(str[i]) || is_lower_case(str[i]) || is_upper_case(str[i]) || str[i] == '_';
+ bool valid_char = is_ascii_identifier_char(str[i]);
if (!valid_char) {
return false;
@@ -3716,7 +3696,7 @@ String String::uri_encode() const {
String res;
for (int i = 0; i < temp.length(); ++i) {
char ord = temp[i];
- if (ord == '.' || ord == '-' || ord == '_' || ord == '~' || is_lower_case(ord) || is_upper_case(ord) || is_digit(ord)) {
+ if (ord == '.' || ord == '-' || ord == '~' || is_ascii_identifier_char(ord)) {
res += ord;
} else {
char h_Val[3];
@@ -3738,9 +3718,9 @@ String String::uri_decode() const {
for (int i = 0; i < src.length(); ++i) {
if (src[i] == '%' && i + 2 < src.length()) {
char ord1 = src[i + 1];
- if (is_digit(ord1) || is_upper_case(ord1)) {
+ if (is_digit(ord1) || is_ascii_upper_case(ord1)) {
char ord2 = src[i + 2];
- if (is_digit(ord2) || is_upper_case(ord2)) {
+ if (is_digit(ord2) || is_ascii_upper_case(ord2)) {
char bytes[3] = { (char)ord1, (char)ord2, 0 };
res += (char)strtol(bytes, nullptr, 16);
i += 2;
@@ -3867,7 +3847,7 @@ static _FORCE_INLINE_ int _xml_unescape(const char32_t *p_src, int p_src_len, ch
for (int i = 2; i < p_src_len; i++) {
eat = i + 1;
char32_t ct = p_src[i];
- if (ct == ';' || ct < '0' || ct > '9') {
+ if (ct == ';' || !is_digit(ct)) {
break;
}
}
@@ -3997,7 +3977,7 @@ String String::pad_zeros(int p_digits) const {
int begin = 0;
- while (begin < end && (s[begin] < '0' || s[begin] > '9')) {
+ while (begin < end && !is_digit(s[begin])) {
begin++;
}
@@ -4042,7 +4022,7 @@ bool String::is_valid_int() const {
}
for (int i = from; i < len; i++) {
- if (operator[](i) < '0' || operator[](i) > '9') {
+ if (!is_digit(operator[](i))) {
return false; // no start with number plz
}
}
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 6df87280a4..1d302b65a7 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -32,6 +32,7 @@
#define USTRING_GODOT_H
// Note: Renamed to avoid conflict with ICU header with the same name.
+#include "core/string/char_utils.h"
#include "core/templates/cowdata.h"
#include "core/templates/vector.h"
#include "core/typedefs.h"
@@ -394,6 +395,8 @@ public:
Vector<uint8_t> sha256_buffer() const;
_FORCE_INLINE_ bool is_empty() const { return length() == 0; }
+ _FORCE_INLINE_ bool contains(const char *p_str) const { return find(p_str) != -1; }
+ _FORCE_INLINE_ bool contains(const String &p_str) const { return find(p_str) != -1; }
// path functions
bool is_absolute_path() const;
@@ -531,7 +534,6 @@ String DTRN(const String &p_text, const String &p_text_plural, int p_n, const St
String RTR(const String &p_text, const String &p_context = "");
String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
-bool is_symbol(char32_t c);
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);
_FORCE_INLINE_ void sarray_add_str(Vector<String> &arr) {
diff --git a/core/variant/method_ptrcall.h b/core/variant/method_ptrcall.h
index 75a93ac4c8..d0acf60c22 100644
--- a/core/variant/method_ptrcall.h
+++ b/core/variant/method_ptrcall.h
@@ -31,7 +31,6 @@
#ifndef METHOD_PTRCALL_H
#define METHOD_PTRCALL_H
-#include "core/math/transform_2d.h"
#include "core/object/object_id.h"
#include "core/typedefs.h"
#include "core/variant/variant.h"
diff --git a/core/variant/native_ptr.h b/core/variant/native_ptr.h
index fe541c8d4b..8e9fbbc0a4 100644
--- a/core/variant/native_ptr.h
+++ b/core/variant/native_ptr.h
@@ -53,22 +53,36 @@ struct GDNativePtr {
operator Variant() const { return uint64_t(data); }
};
-#define GDVIRTUAL_NATIVE_PTR(m_type) \
- template <> \
- struct GDNativeConstPtr<const m_type> { \
- const m_type *data = nullptr; \
- GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \
- static const char *get_name() { return "const " #m_type; } \
- operator const m_type *() const { return data; } \
- operator Variant() const { return uint64_t(data); } \
- }; \
- template <> \
- struct GDNativePtr<m_type> { \
- m_type *data = nullptr; \
- GDNativePtr(m_type *p_assign) { data = p_assign; } \
- static const char *get_name() { return #m_type; } \
- operator m_type *() const { return data; } \
- operator Variant() const { return uint64_t(data); } \
+#define GDVIRTUAL_NATIVE_PTR(m_type) \
+ template <> \
+ struct GDNativeConstPtr<const m_type> { \
+ const m_type *data = nullptr; \
+ GDNativeConstPtr() {} \
+ GDNativeConstPtr(const m_type *p_assign) { data = p_assign; } \
+ static const char *get_name() { return "const " #m_type; } \
+ operator const m_type *() const { return data; } \
+ operator Variant() const { return uint64_t(data); } \
+ }; \
+ template <> \
+ struct VariantCaster<GDNativeConstPtr<const m_type>> { \
+ static _FORCE_INLINE_ GDNativeConstPtr<const m_type> cast(const Variant &p_variant) { \
+ return GDNativeConstPtr<const m_type>((const m_type *)p_variant.operator uint64_t()); \
+ } \
+ }; \
+ template <> \
+ struct GDNativePtr<m_type> { \
+ m_type *data = nullptr; \
+ GDNativePtr() {} \
+ GDNativePtr(m_type *p_assign) { data = p_assign; } \
+ static const char *get_name() { return #m_type; } \
+ operator m_type *() const { return data; } \
+ operator Variant() const { return uint64_t(data); } \
+ }; \
+ template <> \
+ struct VariantCaster<GDNativePtr<m_type>> { \
+ static _FORCE_INLINE_ GDNativePtr<m_type> cast(const Variant &p_variant) { \
+ return GDNativePtr<m_type>((m_type *)p_variant.operator uint64_t()); \
+ } \
};
template <class T>
diff --git a/core/variant/variant.h b/core/variant/variant.h
index 36fa755647..b75882a87c 100644
--- a/core/variant/variant.h
+++ b/core/variant/variant.h
@@ -39,8 +39,12 @@
#include "core/math/face3.h"
#include "core/math/plane.h"
#include "core/math/quaternion.h"
+#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
#include "core/math/transform_2d.h"
#include "core/math/transform_3d.h"
+#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
#include "core/math/vector3.h"
#include "core/math/vector3i.h"
#include "core/object/object_id.h"
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index aecc6e9a26..750f23902d 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -1421,6 +1421,7 @@ static void _register_variant_builtin_methods() {
bind_method(String, sha1_buffer, sarray(), varray());
bind_method(String, sha256_buffer, sarray(), varray());
bind_method(String, is_empty, sarray(), varray());
+ bind_methodv(String, contains, static_cast<bool (String::*)(const String &) const>(&String::contains), sarray("what"), varray());
bind_method(String, is_absolute_path, sarray(), varray());
bind_method(String, is_relative_path, sarray(), varray());
diff --git a/core/variant/variant_parser.cpp b/core/variant/variant_parser.cpp
index 96cdc0678e..e889a1bb40 100644
--- a/core/variant/variant_parser.cpp
+++ b/core/variant/variant_parser.cpp
@@ -188,7 +188,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
if (p_stream->is_eof()) {
r_token.type = TK_EOF;
return OK;
- } else if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) {
+ } else if (is_hex_digit(ch)) {
color_str += ch;
} else {
@@ -265,13 +265,13 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ if (!is_hex_digit(c)) {
r_err_str = "Malformed hex constant in string";
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
char32_t v;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
@@ -381,7 +381,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
while (true) {
switch (reading) {
case READING_INT: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
//pass
} else if (c == '.') {
reading = READING_DEC;
@@ -395,7 +395,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
} break;
case READING_DEC: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
} else {
@@ -404,7 +404,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
} break;
case READING_EXP: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
exp_beg = true;
} else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) {
@@ -433,11 +433,11 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri
r_token.value = num.as_int();
}
return OK;
- } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') {
+ } else if (is_ascii_char(cchar) || is_underscore(cchar)) {
StringBuffer<> id;
bool first = true;
- while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) {
+ while (is_ascii_char(cchar) || is_underscore(cchar) || (!first && is_digit(cchar))) {
id += cchar;
cchar = p_stream->get_char();
first = false;
@@ -1495,7 +1495,7 @@ Error VariantWriter::write(const Variant &p_variant, StoreStringFunc p_store_str
case Variant::FLOAT: {
String s = rtos_fix(p_variant.operator double());
if (s != "inf" && s != "inf_neg" && s != "nan") {
- if (s.find(".") == -1 && s.find("e") == -1) {
+ if (!s.contains(".") && !s.contains("e")) {
s += ".0";
}
}
diff --git a/doc/classes/BoxMesh.xml b/doc/classes/BoxMesh.xml
index af3365b6ea..bf499c8971 100644
--- a/doc/classes/BoxMesh.xml
+++ b/doc/classes/BoxMesh.xml
@@ -11,7 +11,7 @@
<tutorials>
</tutorials>
<members>
- <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)">
+ <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
The box's width, height and depth.
</member>
<member name="subdivide_depth" type="int" setter="set_subdivide_depth" getter="get_subdivide_depth" default="0">
diff --git a/doc/classes/BoxShape3D.xml b/doc/classes/BoxShape3D.xml
index 3bfded6512..cf2bf2338b 100644
--- a/doc/classes/BoxShape3D.xml
+++ b/doc/classes/BoxShape3D.xml
@@ -12,7 +12,7 @@
<link title="3D Platformer Demo">https://godotengine.org/asset-library/asset/125</link>
</tutorials>
<members>
- <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)">
+ <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
The box's width, height and depth.
</member>
</members>
diff --git a/doc/classes/CapsuleMesh.xml b/doc/classes/CapsuleMesh.xml
index 3b4e60ce93..45dd64c174 100644
--- a/doc/classes/CapsuleMesh.xml
+++ b/doc/classes/CapsuleMesh.xml
@@ -9,13 +9,13 @@
<tutorials>
</tutorials>
<members>
- <member name="height" type="float" setter="set_height" getter="get_height" default="3.0">
+ <member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
Total height of the capsule mesh (including the hemispherical ends).
</member>
<member name="radial_segments" type="int" setter="set_radial_segments" getter="get_radial_segments" default="64">
Number of radial segments on the capsule mesh.
</member>
- <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
+ <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
Radius of the capsule mesh.
</member>
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="8">
diff --git a/doc/classes/CapsuleShape3D.xml b/doc/classes/CapsuleShape3D.xml
index 8856ec3779..ba4ead4930 100644
--- a/doc/classes/CapsuleShape3D.xml
+++ b/doc/classes/CapsuleShape3D.xml
@@ -10,10 +10,10 @@
<link title="3D Physics Tests Demo">https://godotengine.org/asset-library/asset/675</link>
</tutorials>
<members>
- <member name="height" type="float" setter="set_height" getter="get_height" default="3.0">
+ <member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The capsule's height.
</member>
- <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
+ <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
The capsule's radius.
</member>
</members>
diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml
index d5551e1e04..b6c2dac33c 100644
--- a/doc/classes/Control.xml
+++ b/doc/classes/Control.xml
@@ -380,12 +380,6 @@
Returns the focus neighbor for the specified [enum Side]. A getter method for [member focus_neighbor_bottom], [member focus_neighbor_left], [member focus_neighbor_right] and [member focus_neighbor_top].
</description>
</method>
- <method name="get_focus_owner" qualifiers="const">
- <return type="Control" />
- <description>
- Returns the control that has the keyboard focus or [code]null[/code] if none.
- </description>
- </method>
<method name="get_global_rect" qualifiers="const">
<return type="Rect2" />
<description>
diff --git a/doc/classes/CylinderMesh.xml b/doc/classes/CylinderMesh.xml
index 077435990b..ce34cc91ad 100644
--- a/doc/classes/CylinderMesh.xml
+++ b/doc/classes/CylinderMesh.xml
@@ -9,7 +9,7 @@
<tutorials>
</tutorials>
<members>
- <member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="1.0">
+ <member name="bottom_radius" type="float" setter="set_bottom_radius" getter="get_bottom_radius" default="0.5">
Bottom radius of the cylinder. If set to [code]0.0[/code], the bottom faces will not be generated, resulting in a conic shape.
</member>
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
@@ -21,7 +21,7 @@
<member name="rings" type="int" setter="set_rings" getter="get_rings" default="4">
Number of edge rings along the height of the cylinder. Changing [member rings] does not have any visual impact unless a shader or procedural mesh tool is used to alter the vertex data. Higher values result in more subdivisions, which can be used to create smoother-looking effects with shaders or procedural mesh tools (at the cost of performance). When not altering the vertex data using a shader or procedural mesh tool, [member rings] should be kept to its default value.
</member>
- <member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="1.0">
+ <member name="top_radius" type="float" setter="set_top_radius" getter="get_top_radius" default="0.5">
Top radius of the cylinder. If set to [code]0.0[/code], the top faces will not be generated, resulting in a conic shape.
</member>
</members>
diff --git a/doc/classes/CylinderShape3D.xml b/doc/classes/CylinderShape3D.xml
index d40b96710b..6fa6c4d75e 100644
--- a/doc/classes/CylinderShape3D.xml
+++ b/doc/classes/CylinderShape3D.xml
@@ -15,7 +15,7 @@
<member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The cylinder's height.
</member>
- <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
+ <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
The cylinder's radius.
</member>
</members>
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 8be944b105..281c218d0d 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -399,6 +399,14 @@
<description>
</description>
</method>
+ <method name="screen_get_refresh_rate" qualifiers="const">
+ <return type="float" />
+ <argument index="0" name="screen" type="int" default="-1" />
+ <description>
+ Returns the current refresh rate of the specified screen. If [code]screen[/code] is [code]SCREEN_OF_MAIN_WINDOW[/code] (the default value), a screen with the main window will be used.
+ [b]Note:[/b] Returns [code]60.0[/code] if the DisplayServer fails to find the refresh rate for the specified screen. On HTML5, [method screen_get_refresh_rate] will always return [code]60.0[/code] as there is no way to retrieve the refresh rate on that platform.
+ </description>
+ </method>
<method name="screen_get_scale" qualifiers="const">
<return type="float" />
<argument index="0" name="screen" type="int" default="-1" />
@@ -900,6 +908,11 @@
Fullscreen window mode. Note that this is not [i]exclusive[/i] fullscreen. On Windows and Linux, a borderless window is used to emulate fullscreen. On macOS, a new desktop is used to display the running project.
Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
</constant>
+ <constant name="WINDOW_MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="WindowMode">
+ Exclusive fullscreen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant WINDOW_MODE_FULLSCREEN].
+ Only one window in exclusive fullscreen mode can be visible on a given screen at a time. If multiple windows are in exclusive fullscreen mode for the same screen, the last one being set to this mode takes precedence.
+ Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
+ </constant>
<constant name="WINDOW_FLAG_RESIZE_DISABLED" value="0" enum="WindowFlags">
</constant>
<constant name="WINDOW_FLAG_BORDERLESS" value="1" enum="WindowFlags">
diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml
index 60f8925ab3..b5468755c1 100644
--- a/doc/classes/Input.xml
+++ b/doc/classes/Input.xml
@@ -141,10 +141,10 @@
Returns the strength of the joypad vibration: x is the strength of the weak motor, and y is the strength of the strong motor.
</description>
</method>
- <method name="get_last_mouse_velocity" qualifiers="const">
+ <method name="get_last_mouse_velocity">
<return type="Vector2" />
<description>
- Returns the mouse velocity for the last time the cursor was moved, and this until the next frame where the mouse moves. This means that even if the mouse is not moving, this function will still return the value of the last motion.
+ Returns the last mouse velocity. To provide a precise and jitter-free velocity, mouse velocity is only calculated every 0.1s. Therefore, mouse velocity will lag mouse movements.
</description>
</method>
<method name="get_magnetometer" qualifiers="const">
diff --git a/doc/classes/MultiplayerAPI.xml b/doc/classes/MultiplayerAPI.xml
index e0da08f5bd..426d902983 100644
--- a/doc/classes/MultiplayerAPI.xml
+++ b/doc/classes/MultiplayerAPI.xml
@@ -79,8 +79,6 @@
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member multiplayer_peer] refuses new incoming connections.
</member>
- <member name="replicator" type="MultiplayerReplicator" setter="" getter="get_replicator">
- </member>
<member name="root_node" type="Node" setter="set_root_node" getter="get_root_node">
The root node to use for RPCs. Instead of an absolute path, a relative path will be used to find the node upon which the RPC should be executed.
This effectively allows to have different branches of the scene tree to be managed by different MultiplayerAPI, allowing for example to run both client and server in the same scene.
diff --git a/doc/classes/MultiplayerReplicator.xml b/doc/classes/MultiplayerReplicator.xml
deleted file mode 100644
index c2e93ddeab..0000000000
--- a/doc/classes/MultiplayerReplicator.xml
+++ /dev/null
@@ -1,191 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-<class name="MultiplayerReplicator" inherits="Object" version="4.0">
- <brief_description>
- </brief_description>
- <description>
- </description>
- <tutorials>
- </tutorials>
- <methods>
- <method name="decode_state">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <argument index="2" name="data" type="PackedByteArray" />
- <argument index="3" name="initial" type="bool" default="true" />
- <description>
- Decode the given [code]data[/code] representing a spawnable state into [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when a client receives a server spawn for a scene with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
- Tip: You may find this function useful in servers when parsing spawn requests from clients, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
- </description>
- </method>
- <method name="despawn">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <argument index="2" name="peer_id" type="int" default="0" />
- <description>
- Request a despawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behavior, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_despawn] for the default behavior.
- </description>
- </method>
- <method name="encode_state">
- <return type="PackedByteArray" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <argument index="2" name="initial" type="bool" default="true" />
- <description>
- Encode the given [code]object[/code] using the configuration associated with the provided [code]scene_id[/code]. This function is called automatically when the server spawns scenes with [constant REPLICATION_MODE_SERVER]. See [method spawn_config].
- Tip: You may find this function useful when requesting spawns from clients to server, or when implementing your own logic with [constant REPLICATION_MODE_CUSTOM].
- </description>
- </method>
- <method name="send_despawn">
- <return type="int" enum="Error" />
- <argument index="0" name="peer_id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="data" type="Variant" default="null" />
- <argument index="3" name="path" type="NodePath" default="NodePath(&quot;&quot;)" />
- <description>
- Sends a despawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically queue for deletion the node at [code]path[/code] and emit the signal [signal despawned]. In all other cases no deletion happens, and the signal [signal despawn_requested] is emitted instead.
- </description>
- </method>
- <method name="send_spawn">
- <return type="int" enum="Error" />
- <argument index="0" name="peer_id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="data" type="Variant" default="null" />
- <argument index="3" name="path" type="NodePath" default="NodePath(&quot;&quot;)" />
- <description>
- Sends a spawn request for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). If the scene is configured as [constant REPLICATION_MODE_SERVER] (see [method spawn_config]) and the request is sent by the server (see [method MultiplayerAPI.is_server]), the receiving peer(s) will automatically instantiate that scene, add it to the [SceneTree] at the given [code]path[/code] and emit the signal [signal spawned]. In all other cases no instantiation happens, and the signal [signal spawn_requested] is emitted instead.
- </description>
- </method>
- <method name="send_sync">
- <return type="int" enum="Error" />
- <argument index="0" name="peer_id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="data" type="PackedByteArray" />
- <argument index="3" name="transfer_mode" type="int" enum="TransferMode" default="2" />
- <argument index="4" name="channel" type="int" default="0" />
- <description>
- Sends a sync request for the instances of the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code] (see [method MultiplayerPeer.set_target_peer]). This function can only be called manually when overriding the send and receive sync functions (see [method sync_config]).
- </description>
- </method>
- <method name="spawn">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <argument index="2" name="peer_id" type="int" default="0" />
- <description>
- Request a spawn for the scene identified by [code]scene_id[/code] to the given [code]peer_id[/code]. This will either trigger the default behavior, or invoke the custom spawn/despawn callables specified in [method spawn_config]. See [method send_spawn] for the default behavior.
- </description>
- </method>
- <method name="spawn_config">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="spawn_mode" type="int" enum="MultiplayerReplicator.ReplicationMode" />
- <argument index="2" name="properties" type="StringName[]" default="[]" />
- <argument index="3" name="custom_send" type="Callable" />
- <argument index="4" name="custom_receive" type="Callable" />
- <description>
- Configures the MultiplayerReplicator to track instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication. When [code]mode[/code] is [constant REPLICATION_MODE_SERVER], the specified [code]properties[/code] will also be replicated to clients during the initial spawn. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behavior and customize the spawn/despawn proecess.
- Tip: You can use a custom property in the scene main script to return a customly optimized state representation.
- </description>
- </method>
- <method name="sync_all">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="peer_id" type="int" default="0" />
- <description>
- Manually request a sync for all the instances of the scene identified by [code]scene_id[/code]. This function will trigger the default sync behavior, or call your send custom send callable if specified in [method sync_config].
- [b]Note:[/b] The default implementation only allow syncing from server to clients.
- </description>
- </method>
- <method name="sync_config">
- <return type="int" enum="Error" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="interval" type="int" />
- <argument index="2" name="properties" type="StringName[]" default="[]" />
- <argument index="3" name="custom_send" type="Callable" />
- <argument index="4" name="custom_receive" type="Callable" />
- <description>
- Configures the MultiplayerReplicator to sync instances of the [PackedScene] identified by [code]scene_id[/code] (see [method ResourceLoader.get_resource_uid]) for the purpose of network replication at the desired [code]interval[/code] (in milliseconds). The specified [code]properties[/code] will be part of the state sync. You can optionally specify a [code]custom_send[/code] and a [code]custom_receive[/code] to override the default behavior and customize the synchronization proecess.
- Tip: You can use a custom property in the scene main script to return a customly optimized state representation (having a single property that returns a PackedByteArray is highly recommended when dealing with many instances).
- </description>
- </method>
- <method name="track">
- <return type="void" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <description>
- Track the given [code]object[/code] as an instance of the scene identified by [code]scene_id[/code]. This object will be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
- </description>
- </method>
- <method name="untrack">
- <return type="void" />
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="object" type="Object" />
- <description>
- Untrack the given [code]object[/code]. This object will no longer be passed to your custom sync callables (see [method sync_config]). Tracking and untracking is automatic in [constant REPLICATION_MODE_SERVER].
- </description>
- </method>
- </methods>
- <signals>
- <signal name="despawn_requested">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="parent" type="Node" />
- <argument index="3" name="name" type="String" />
- <argument index="4" name="data" type="PackedByteArray" />
- <description>
- Emitted when a network despawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
- </description>
- </signal>
- <signal name="despawned">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted on a client before deleting a local Node upon receiving a despawn request from the server.
- </description>
- </signal>
- <signal name="replicated_instance_added">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted when an instance of a [PackedScene] that has been configured for networking enters the [SceneTree]. See [method spawn_config].
- </description>
- </signal>
- <signal name="replicated_instance_removed">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted when an instance of a [PackedScene] that has been configured for networking leaves the [SceneTree]. See [method spawn_config].
- </description>
- </signal>
- <signal name="spawn_requested">
- <argument index="0" name="id" type="int" />
- <argument index="1" name="scene_id" type="int" />
- <argument index="2" name="parent" type="Node" />
- <argument index="3" name="name" type="String" />
- <argument index="4" name="data" type="PackedByteArray" />
- <description>
- Emitted when a network spawn request has been received from a client, or for a [PackedScene] that has been configured as [constant REPLICATION_MODE_CUSTOM].
- </description>
- </signal>
- <signal name="spawned">
- <argument index="0" name="scene_id" type="int" />
- <argument index="1" name="node" type="Node" />
- <description>
- Emitted on a client after a new Node is instantiated locally and added to the SceneTree upon receiving a spawn request from the server.
- </description>
- </signal>
- </signals>
- <constants>
- <constant name="REPLICATION_MODE_NONE" value="0" enum="ReplicationMode">
- Used with [method spawn_config] to identify a [PackedScene] that should not be replicated.
- </constant>
- <constant name="REPLICATION_MODE_SERVER" value="1" enum="ReplicationMode">
- Used with [method spawn_config] to identify a [PackedScene] that should be automatically replicated from server to clients.
- </constant>
- <constant name="REPLICATION_MODE_CUSTOM" value="2" enum="ReplicationMode">
- Used with [method spawn_config] to identify a [PackedScene] that can be manually replicated among peers.
- </constant>
- </constants>
-</class>
diff --git a/doc/classes/MultiplayerSpawner.xml b/doc/classes/MultiplayerSpawner.xml
new file mode 100644
index 0000000000..8bfecfce41
--- /dev/null
+++ b/doc/classes/MultiplayerSpawner.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="MultiplayerSpawner" inherits="Node" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="_spawn_custom" qualifiers="virtual">
+ <return type="Object" />
+ <argument index="0" name="data" type="Variant" />
+ <description>
+ </description>
+ </method>
+ <method name="spawn">
+ <return type="Node" />
+ <argument index="0" name="data" type="Variant" default="null" />
+ <description>
+ </description>
+ </method>
+ </methods>
+ <members>
+ <member name="auto_spawn" type="bool" setter="set_auto_spawning" getter="is_auto_spawning" default="false">
+ </member>
+ <member name="replication" type="PackedScene[]" setter="set_spawnable_scenes" getter="get_spawnable_scenes" default="[]">
+ </member>
+ <member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
+ </member>
+ <member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath(&quot;&quot;)">
+ </member>
+ </members>
+ <signals>
+ <signal name="despawned">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ </description>
+ </signal>
+ <signal name="spawned">
+ <argument index="0" name="scene_id" type="int" />
+ <argument index="1" name="node" type="Node" />
+ <description>
+ </description>
+ </signal>
+ </signals>
+</class>
diff --git a/doc/classes/MultiplayerSynchronizer.xml b/doc/classes/MultiplayerSynchronizer.xml
new file mode 100644
index 0000000000..242d4589a4
--- /dev/null
+++ b/doc/classes/MultiplayerSynchronizer.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="MultiplayerSynchronizer" inherits="Node" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <members>
+ <member name="replication_interval" type="float" setter="set_replication_interval" getter="get_replication_interval" default="0.0">
+ </member>
+ <member name="resource" type="SceneReplicationConfig" setter="set_replication_config" getter="get_replication_config">
+ </member>
+ <member name="root_path" type="NodePath" setter="set_root_path" getter="get_root_path" default="NodePath(&quot;&quot;)">
+ </member>
+ </members>
+</class>
diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index d714fbc0d5..47be5695a0 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -726,6 +726,18 @@
</member>
</members>
<signals>
+ <signal name="child_entered_tree">
+ <argument index="0" name="node" type="Node" />
+ <description>
+ Emitted when a child node enters the scene tree, either because it entered on its own or because this node entered with it.
+ </description>
+ </signal>
+ <signal name="child_exited_tree">
+ <argument index="0" name="node" type="Node" />
+ <description>
+ Emitted when a child node exits the scene tree, either because it exited on its own or because this node exited.
+ </description>
+ </signal>
<signal name="ready">
<description>
Emitted when the node is ready. Comes after [method _ready] callback and follows the same rules.
diff --git a/doc/classes/PopupMenu.xml b/doc/classes/PopupMenu.xml
index 108c674ef7..eb1b0aada7 100644
--- a/doc/classes/PopupMenu.xml
+++ b/doc/classes/PopupMenu.xml
@@ -331,6 +331,13 @@
[b]Note:[/b] The indices of items after the removed item will be shifted by one.
</description>
</method>
+ <method name="set_current_index">
+ <return type="void" />
+ <argument index="0" name="index" type="int" />
+ <description>
+ Sets the currently focused item as the given [code]index[/code].
+ </description>
+ </method>
<method name="set_item_accelerator">
<return type="void" />
<argument index="0" name="index" type="int" />
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index dc40d2fd1b..ed124d1d15 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -356,6 +356,8 @@
<member name="debug/gdscript/warnings/incompatible_ternary" type="bool" setter="" getter="" default="true">
If [code]true[/code], enables warnings when a ternary operator may emit values with incompatible types.
</member>
+ <member name="debug/gdscript/warnings/int_assigned_to_enum" type="bool" setter="" getter="" default="true">
+ </member>
<member name="debug/gdscript/warnings/integer_division" type="bool" setter="" getter="" default="true">
If [code]true[/code], enables warnings when dividing an integer by another integer (the decimal part will be discarded).
</member>
diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml
index 6f5135459f..95dffd3e28 100644
--- a/doc/classes/RichTextLabel.xml
+++ b/doc/classes/RichTextLabel.xml
@@ -372,6 +372,9 @@
</method>
</methods>
<members>
+ <member name="autowrap_mode" type="int" setter="set_autowrap_mode" getter="get_autowrap_mode" enum="RichTextLabel.AutowrapMode" default="3">
+ If set to something other than [constant AUTOWRAP_OFF], the text gets wrapped inside the node's bounding rectangle. To see how each mode behaves, see [enum AutowrapMode].
+ </member>
<member name="bbcode_enabled" type="bool" setter="set_use_bbcode" getter="is_using_bbcode" default="false">
If [code]true[/code], the label uses BBCode formatting.
</member>
@@ -454,6 +457,18 @@
</signal>
</signals>
<constants>
+ <constant name="AUTOWRAP_OFF" value="0" enum="AutowrapMode">
+ Autowrap is disabled.
+ </constant>
+ <constant name="AUTOWRAP_ARBITRARY" value="1" enum="AutowrapMode">
+ Wraps the text inside the node's bounding rectangle by allowing to break lines at arbitrary positions, which is useful when very limited space is available.
+ </constant>
+ <constant name="AUTOWRAP_WORD" value="2" enum="AutowrapMode">
+ Wraps the text inside the node's bounding rectangle by soft-breaking between words.
+ </constant>
+ <constant name="AUTOWRAP_WORD_SMART" value="3" enum="AutowrapMode">
+ Behaves similarly to [constant AUTOWRAP_WORD], but force-breaks a word if that single word does not fit in one line.
+ </constant>
<constant name="LIST_NUMBERS" value="0" enum="ListType">
Each list item has a number marker.
</constant>
diff --git a/doc/classes/SceneReplicationConfig.xml b/doc/classes/SceneReplicationConfig.xml
new file mode 100644
index 0000000000..e846740dd3
--- /dev/null
+++ b/doc/classes/SceneReplicationConfig.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="SceneReplicationConfig" inherits="Resource" version="4.0">
+ <brief_description>
+ </brief_description>
+ <description>
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="add_property">
+ <return type="void" />
+ <argument index="0" name="path" type="NodePath" />
+ <argument index="1" name="index" type="int" default="-1" />
+ <description>
+ </description>
+ </method>
+ <method name="get_properties" qualifiers="const">
+ <return type="NodePath[]" />
+ <description>
+ </description>
+ </method>
+ <method name="property_get_index" qualifiers="const">
+ <return type="int" />
+ <argument index="0" name="path" type="NodePath" />
+ <description>
+ </description>
+ </method>
+ <method name="property_get_spawn">
+ <return type="bool" />
+ <argument index="0" name="path" type="NodePath" />
+ <description>
+ </description>
+ </method>
+ <method name="property_get_sync">
+ <return type="bool" />
+ <argument index="0" name="path" type="NodePath" />
+ <description>
+ </description>
+ </method>
+ <method name="property_set_spawn">
+ <return type="void" />
+ <argument index="0" name="path" type="NodePath" />
+ <argument index="1" name="enabled" type="bool" />
+ <description>
+ </description>
+ </method>
+ <method name="property_set_sync">
+ <return type="void" />
+ <argument index="0" name="path" type="NodePath" />
+ <argument index="1" name="enabled" type="bool" />
+ <description>
+ </description>
+ </method>
+ <method name="remove_property">
+ <return type="void" />
+ <argument index="0" name="path" type="NodePath" />
+ <description>
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/doc/classes/String.xml b/doc/classes/String.xml
index 1a9b9ccdcc..eeb17c24c0 100644
--- a/doc/classes/String.xml
+++ b/doc/classes/String.xml
@@ -103,6 +103,13 @@
<description>
</description>
</method>
+ <method name="contains" qualifiers="const">
+ <return type="bool" />
+ <argument index="0" name="what" type="String" />
+ <description>
+ Returns [code]true[/code] if the string contains the given string.
+ </description>
+ </method>
<method name="count" qualifiers="const">
<return type="int" />
<argument index="0" name="what" type="String" />
diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml
index 5967340f8c..b500bd5658 100644
--- a/doc/classes/TextServerExtension.xml
+++ b/doc/classes/TextServerExtension.xml
@@ -202,7 +202,7 @@
</description>
</method>
<method name="_font_get_hinting" qualifiers="virtual const">
- <return type="int" />
+ <return type="int" enum="TextServer.Hinting" />
<argument index="0" name="font_rid" type="RID" />
<description>
Returns the font hinting mode. Used by dynamic fonts only.
@@ -1028,7 +1028,7 @@
</description>
</method>
<method name="_shaped_text_get_direction" qualifiers="virtual const">
- <return type="int" />
+ <return type="int" enum="TextServer.Direction" />
<argument index="0" name="shaped" type="RID" />
<description>
Returns direction of the text.
@@ -1051,9 +1051,8 @@
</description>
</method>
<method name="_shaped_text_get_ellipsis_glyphs" qualifiers="virtual const">
- <return type="void" />
+ <return type="Glyph*" />
<argument index="0" name="shaped" type="RID" />
- <argument index="1" name="r_glyphs" type="void*" />
<description>
Returns array of the glyphs in the ellipsis.
</description>
@@ -1073,9 +1072,8 @@
</description>
</method>
<method name="_shaped_text_get_glyphs" qualifiers="virtual const">
- <return type="void" />
+ <return type="Glyph*" />
<argument index="0" name="shaped" type="RID" />
- <argument index="1" name="r_glyphs" type="void*" />
<description>
Copies text glyphs in the visual order, into preallocated array of the size returned by [method _shaped_text_get_glyph_count].
</description>
@@ -1089,7 +1087,7 @@
</description>
</method>
<method name="_shaped_text_get_inferred_direction" qualifiers="virtual const">
- <return type="int" />
+ <return type="int" enum="TextServer.Direction" />
<argument index="0" name="shaped" type="RID" />
<description>
Returns direction of the text, inferred by the BiDi algorithm.
@@ -1132,7 +1130,7 @@
</description>
</method>
<method name="_shaped_text_get_orientation" qualifiers="virtual const">
- <return type="int" />
+ <return type="int" enum="TextServer.Orientation" />
<argument index="0" name="shaped" type="RID" />
<description>
Returns text orientation.
@@ -1337,9 +1335,8 @@
</description>
</method>
<method name="_shaped_text_sort_logical" qualifiers="virtual">
- <return type="void" />
+ <return type="Glyph*" />
<argument index="0" name="shaped" type="RID" />
- <argument index="1" name="r_glyphs" type="void*" />
<description>
Copies text glyphs in the logical order, into preallocated array of the size returned by [method _shaped_text_get_glyph_count].
</description>
diff --git a/doc/classes/Window.xml b/doc/classes/Window.xml
index 3bb6603646..ab8f51ced5 100644
--- a/doc/classes/Window.xml
+++ b/doc/classes/Window.xml
@@ -398,6 +398,11 @@
Fullscreen window mode. Note that this is not [i]exclusive[/i] fullscreen. On Windows and Linux, a borderless window is used to emulate fullscreen. On macOS, a new desktop is used to display the running project.
Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
</constant>
+ <constant name="MODE_EXCLUSIVE_FULLSCREEN" value="4" enum="Mode">
+ Exclusive fullscreen window mode. This mode is implemented on Windows only. On other platforms, it is equivalent to [constant MODE_FULLSCREEN].
+ Only one window in exclusive fullscreen mode can be visible on a given screen at a time. If multiple windows are in exclusive fullscreen mode for the same screen, the last one being set to this mode takes precedence.
+ Regardless of the platform, enabling fullscreen will change the window size to match the monitor's size. Therefore, make sure your project supports [url=$DOCS_URL/tutorials/rendering/multiple_resolutions.html]multiple resolutions[/url] when enabling fullscreen mode.
+ </constant>
<constant name="FLAG_RESIZE_DISABLED" value="0" enum="Flags">
The window's ability to be resized.
</constant>
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 433866602b..e36d0b846b 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -3481,7 +3481,7 @@ void AnimationTrackEditor::_track_remove_request(int p_track) {
void AnimationTrackEditor::_track_grab_focus(int p_track) {
// Don't steal focus if not working with the track editor.
- if (Object::cast_to<AnimationTrackEdit>(get_focus_owner())) {
+ if (Object::cast_to<AnimationTrackEdit>(get_viewport()->gui_get_focus_owner())) {
track_edits[p_track]->grab_focus();
}
}
diff --git a/editor/array_property_edit.cpp b/editor/array_property_edit.cpp
index d9c6b72241..6be8a7e564 100644
--- a/editor/array_property_edit.cpp
+++ b/editor/array_property_edit.cpp
@@ -121,7 +121,7 @@ bool ArrayPropertyEdit::_set(const StringName &p_name, const Variant &p_value) {
}
} else if (pn.begins_with("indices")) {
- if (pn.find("_") != -1) {
+ if (pn.contains("_")) {
//type
int idx = pn.get_slicec('/', 1).get_slicec('_', 0).to_int();
@@ -178,7 +178,7 @@ bool ArrayPropertyEdit::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
} else if (pn.begins_with("indices")) {
- if (pn.find("_") != -1) {
+ if (pn.contains("_")) {
//type
int idx = pn.get_slicec('/', 1).get_slicec('_', 0).to_int();
bool valid;
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 0a269d960e..967f7e0d1f 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -122,7 +122,7 @@ void FindReplaceBar::unhandled_input(const Ref<InputEvent> &p_event) {
return;
}
- Control *focus_owner = get_focus_owner();
+ Control *focus_owner = get_viewport()->gui_get_focus_owner();
if (text_editor->has_focus() || (focus_owner && vbc_lineedit->is_ancestor_of(focus_owner))) {
bool accepted = true;
@@ -724,7 +724,7 @@ void CodeTextEditor::input(const Ref<InputEvent> &event) {
}
if (!text_editor->has_focus()) {
- if ((find_replace_bar != nullptr && find_replace_bar->is_visible()) && (find_replace_bar->has_focus() || find_replace_bar->is_ancestor_of(get_focus_owner()))) {
+ if ((find_replace_bar != nullptr && find_replace_bar->is_visible()) && (find_replace_bar->has_focus() || find_replace_bar->is_ancestor_of(get_viewport()->gui_get_focus_owner()))) {
if (ED_IS_SHORTCUT("script_text_editor/find_next", key_event)) {
find_replace_bar->search_next();
accept_event();
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index 1d5fa9cbbb..7c54558cd0 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -310,7 +310,7 @@ void ConnectDialog::set_dst_node(Node *p_node) {
StringName ConnectDialog::get_dst_method_name() const {
String txt = dst_method->get_text();
- if (txt.find("(") != -1) {
+ if (txt.contains("(")) {
txt = txt.left(txt.find("(")).strip_edges();
}
return txt;
@@ -764,7 +764,7 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
String node_name = selected_node->get_name();
for (int i = 0; i < node_name.length(); i++) { // TODO: Regex filter may be cleaner.
char32_t c = node_name[i];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
+ if (!is_ascii_identifier_char(c)) {
if (c == ' ') {
// Replace spaces with underlines.
c = '_';
diff --git a/editor/debugger/editor_debugger_inspector.cpp b/editor/debugger/editor_debugger_inspector.cpp
index 38bdbe2870..ad782c87cb 100644
--- a/editor/debugger/editor_debugger_inspector.cpp
+++ b/editor/debugger/editor_debugger_inspector.cpp
@@ -155,7 +155,7 @@ ObjectID EditorDebuggerInspector::add_object(const Array &p_arr) {
if (pinfo.type == Variant::OBJECT) {
if (var.get_type() == Variant::STRING) {
String path = var;
- if (path.find("::") != -1) {
+ if (path.contains("::")) {
// built-in resource
String base_path = path.get_slice("::", 0);
RES dependency = ResourceLoader::load(base_path);
diff --git a/editor/dependency_editor.cpp b/editor/dependency_editor.cpp
index 9879671e7b..1fa5df9396 100644
--- a/editor/dependency_editor.cpp
+++ b/editor/dependency_editor.cpp
@@ -171,7 +171,7 @@ void DependencyEditor::_update_list() {
String path;
String type;
- if (n.find("::") != -1) {
+ if (n.contains("::")) {
path = n.get_slice("::", 0);
type = n.get_slice("::", 1);
} else {
diff --git a/editor/editor_atlas_packer.cpp b/editor/editor_atlas_packer.cpp
index b6ec5d1bad..aad32968de 100644
--- a/editor/editor_atlas_packer.cpp
+++ b/editor/editor_atlas_packer.cpp
@@ -30,6 +30,9 @@
#include "editor_atlas_packer.h"
+#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
+
void EditorAtlasPacker::_plot_triangle(Ref<BitMap> p_bitmap, Vector2i *vertices) {
int width = p_bitmap->get_size().width;
int height = p_bitmap->get_size().height;
diff --git a/editor/editor_atlas_packer.h b/editor/editor_atlas_packer.h
index 133c516d80..169a6bead8 100644
--- a/editor/editor_atlas_packer.h
+++ b/editor/editor_atlas_packer.h
@@ -31,11 +31,12 @@
#ifndef EDITOR_ATLAS_PACKER_H
#define EDITOR_ATLAS_PACKER_H
-#include "core/math/vector2.h"
-
#include "core/templates/vector.h"
#include "scene/resources/bit_map.h"
+struct Vector2;
+struct Vector2i;
+
class EditorAtlasPacker {
public:
struct Chart {
diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp
index 792897e451..9bdfb66235 100644
--- a/editor/editor_export.cpp
+++ b/editor/editor_export.cpp
@@ -818,7 +818,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
int v = 0;
if (i * 2 < script_key.length()) {
char32_t ct = script_key[i * 2];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
@@ -828,7 +828,7 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &
if (i * 2 + 1 < script_key.length()) {
char32_t ct = script_key[i * 2 + 1];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
@@ -1215,7 +1215,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
int v = 0;
if (i * 2 < script_key.length()) {
char32_t ct = script_key[i * 2];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
@@ -1225,7 +1225,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, c
if (i * 2 + 1 < script_key.length()) {
char32_t ct = script_key[i * 2 + 1];
- if (ct >= '0' && ct <= '9') {
+ if (is_digit(ct)) {
ct = ct - '0';
} else if (ct >= 'a' && ct <= 'f') {
ct = 10 + ct - 'a';
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 94262c2289..1d73bdfa6e 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -462,7 +462,7 @@ void EditorFeatureProfileManager::_erase_selected_profile() {
void EditorFeatureProfileManager::_create_new_profile() {
String name = new_profile_name->get_text().strip_edges();
- if (!name.is_valid_filename() || name.find(".") != -1) {
+ if (!name.is_valid_filename() || name.contains(".")) {
EditorNode::get_singleton()->show_warning(TTR("Profile must be a valid filename and must not contain '.'"));
return;
}
diff --git a/editor/editor_help.cpp b/editor/editor_help.cpp
index 946fe6d893..dfc95fb676 100644
--- a/editor/editor_help.cpp
+++ b/editor/editor_help.cpp
@@ -81,7 +81,7 @@ void EditorHelp::_class_desc_select(const String &p_select) {
if (p_select.begins_with("$")) { //enum
String select = p_select.substr(1, p_select.length());
String class_name;
- if (select.find(".") != -1) {
+ if (select.contains(".")) {
class_name = select.get_slice(".", 0);
select = select.get_slice(".", 1);
} else {
@@ -123,7 +123,7 @@ void EditorHelp::_class_desc_select(const String &p_select) {
return;
}
- if (link.find(".") != -1) {
+ if (link.contains(".")) {
emit_signal(SNAME("go_to_help"), topic + ":" + link.get_slice(".", 0) + ":" + link.get_slice(".", 1));
} else {
if (table->has(link)) {
@@ -185,7 +185,7 @@ void EditorHelp::_add_type(const String &p_type, const String &p_enum) {
if (t.is_empty()) {
t = "void";
}
- bool can_ref = (t != "void" && t.find("*") == -1) || !p_enum.is_empty();
+ bool can_ref = (t != "void" && !t.contains("*")) || !p_enum.is_empty();
if (!p_enum.is_empty()) {
if (p_enum.get_slice_count(".") > 1) {
@@ -240,7 +240,7 @@ String EditorHelp::_fix_constant(const String &p_constant) const {
void EditorHelp::_add_method(const DocData::MethodDoc &p_method, bool p_overview) {
method_line[p_method.name] = class_desc->get_line_count() - 2; //gets overridden if description
- const bool is_vararg = p_method.qualifiers.find("vararg") != -1;
+ const bool is_vararg = p_method.qualifiers.contains("vararg");
if (p_overview) {
class_desc->push_cell();
@@ -364,7 +364,7 @@ void EditorHelp::_update_method_list(const Vector<DocData::MethodDoc> p_methods,
for (int i = 0; i < p_methods.size(); i++) {
const String &q = p_methods[i].qualifiers;
- if ((pass == 0 && q.find("virtual") != -1) || (pass == 1 && q.find("virtual") == -1)) {
+ if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
m.push_back(p_methods[i]);
}
}
@@ -429,7 +429,7 @@ void EditorHelp::_update_method_descriptions(const DocData::ClassDoc p_classdoc,
for (int i = 0; i < p_methods.size(); i++) {
const String &q = p_methods[i].qualifiers;
- if ((pass == 0 && q.find("virtual") != -1) || (pass == 1 && q.find("virtual") == -1)) {
+ if ((pass == 0 && q.contains("virtual")) || (pass == 1 && !q.contains("virtual"))) {
methods_filtered.push_back(p_methods[i]);
}
}
@@ -820,7 +820,7 @@ void EditorHelp::_update_doc() {
}
}
// Ignore undocumented non virtual private.
- if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty() && cd.methods[i].qualifiers.find("virtual") == -1) {
+ if (cd.methods[i].name.begins_with("_") && cd.methods[i].description.is_empty() && !cd.methods[i].qualifiers.contains("virtual")) {
continue;
}
methods.push_back(cd.methods[i]);
@@ -1923,7 +1923,7 @@ void EditorHelpBit::_meta_clicked(String p_select) {
String select = p_select.substr(1, p_select.length());
String class_name;
- if (select.find(".") != -1) {
+ if (select.contains(".")) {
class_name = select.get_slice(".", 0);
} else {
class_name = "@Global";
@@ -1936,7 +1936,7 @@ void EditorHelpBit::_meta_clicked(String p_select) {
} else if (p_select.begins_with("@")) {
String m = p_select.substr(1, p_select.length());
- if (m.find(".") != -1) {
+ if (m.contains(".")) {
_go_to_help("class_method:" + m.get_slice(".", 0) + ":" + m.get_slice(".", 0)); //must go somewhere else
}
}
@@ -2128,7 +2128,7 @@ void FindBar::unhandled_input(const Ref<InputEvent> &p_event) {
Ref<InputEventKey> k = p_event;
if (k.is_valid()) {
- if (k->is_pressed() && (rich_text_label->has_focus() || is_ancestor_of(get_focus_owner()))) {
+ if (k->is_pressed() && (rich_text_label->has_focus() || is_ancestor_of(get_viewport()->gui_get_focus_owner()))) {
bool accepted = true;
switch (k->get_keycode()) {
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 0d68051125..425b1fc98d 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -251,7 +251,7 @@ void EditorProperty::_notification(int p_what) {
} else {
color = get_theme_color(is_read_only() ? SNAME("readonly_color") : SNAME("property_color"));
}
- if (label.find(".") != -1) {
+ if (label.contains(".")) {
// FIXME: Move this to the project settings editor, as this is only used
// for project settings feature tag overrides.
color.a = 0.5;
@@ -1780,7 +1780,7 @@ Array EditorInspectorArray::_extract_properties_as_array(const List<PropertyInfo
int to_char_index = 0;
while (to_char_index < str.length()) {
- if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ if (!is_digit(str[to_char_index])) {
break;
}
to_char_index++;
@@ -2299,7 +2299,7 @@ void EditorInspector::update_tree() {
if (property_focusable != -1) {
//check focusable is really focusable
bool restore_focus = false;
- Control *focused = get_focus_owner();
+ Control *focused = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
if (focused) {
Node *parent = focused->get_parent();
while (parent) {
@@ -2512,7 +2512,7 @@ void EditorInspector::update_tree() {
String str = p.name.trim_prefix(array_prefix);
int to_char_index = 0;
while (to_char_index < str.length()) {
- if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ if (!is_digit(str[to_char_index])) {
break;
}
to_char_index++;
@@ -2566,7 +2566,7 @@ void EditorInspector::update_tree() {
}
// Get the property label's string.
- String property_label_string = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
+ String property_label_string = (path.contains("/")) ? path.substr(path.rfind("/") + 1) : path;
if (capitalize_paths) {
// Capitalize paths.
int dot = property_label_string.find(".");
@@ -2590,7 +2590,7 @@ void EditorInspector::update_tree() {
// Ignore properties that do not fit the filter.
if (use_filter && !filter.is_empty()) {
- if (!filter.is_subsequence_ofn(path) && !filter.is_subsequence_ofn(property_label_string) && property_prefix.to_lower().find(filter.to_lower()) == -1) {
+ if (!filter.is_subsequence_ofn(path) && !filter.is_subsequence_ofn(property_label_string) && !property_prefix.to_lower().contains(filter.to_lower())) {
continue;
}
}
@@ -2664,7 +2664,7 @@ void EditorInspector::update_tree() {
array_element_prefix = p.class_name;
editor_inspector_array = memnew(EditorInspectorArray);
- String array_label = (path.find("/") != -1) ? path.substr(path.rfind("/") + 1) : path;
+ String array_label = path.contains("/") ? path.substr(path.rfind("/") + 1) : path;
array_label = property_label_string.capitalize();
int page = per_array_page.has(array_element_prefix) ? per_array_page[array_element_prefix] : 0;
editor_inspector_array->setup_with_move_element_function(object, array_label, array_element_prefix, page, c, use_folding);
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
index 5c4ece7065..48a326d6d4 100644
--- a/editor/editor_locale_dialog.cpp
+++ b/editor/editor_locale_dialog.cpp
@@ -37,14 +37,6 @@
#include "scene/gui/option_button.h"
#include "scene/gui/tree.h"
-static _FORCE_INLINE_ bool is_upper_case(char32_t c) {
- return (c >= 'A' && c <= 'Z');
-}
-
-static _FORCE_INLINE_ bool is_lower_case(char32_t c) {
- return (c >= 'a' && c <= 'z');
-}
-
void EditorLocaleDialog::_bind_methods() {
ADD_SIGNAL(MethodInfo("locale_selected", PropertyInfo(Variant::STRING, "locale")));
}
@@ -363,16 +355,16 @@ void EditorLocaleDialog::set_locale(const String &p_locale) {
Vector<String> locale_elements = p_locale.split("_");
lang_code->set_text(locale_elements[0]);
if (locale_elements.size() >= 2) {
- if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) {
+ if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) {
script_code->set_text(locale_elements[1]);
advanced->set_pressed(true);
}
- if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) {
+ if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) {
country_code->set_text(locale_elements[1]);
}
}
if (locale_elements.size() >= 3) {
- if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) {
+ if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) {
country_code->set_text(locale_elements[2]);
} else {
variant_code->set_text(locale_elements[2].to_lower());
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 076a774efa..c9417ee57c 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -162,6 +162,7 @@
#include "editor/plugins/path_3d_editor_plugin.h"
#include "editor/plugins/physical_bone_3d_editor_plugin.h"
#include "editor/plugins/polygon_2d_editor_plugin.h"
+#include "editor/plugins/replication_editor_plugin.h"
#include "editor/plugins/resource_preloader_editor_plugin.h"
#include "editor/plugins/root_motion_editor_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
@@ -312,7 +313,7 @@ void EditorNode::_update_scene_tabs() {
DisplayServer::get_singleton()->global_menu_clear("_dock");
}
- // Get all scene names, which may be ambiguous
+ // Get all scene names, which may be ambiguous.
Vector<String> disambiguated_scene_names;
Vector<String> full_path_names;
for (int i = 0; i < editor_data.get_edited_scene_count(); i++) {
@@ -349,34 +350,35 @@ void EditorNode::_update_scene_tabs() {
DisplayServer::get_singleton()->global_menu_add_item("_dock", TTR("New Window"), callable_mp(this, &EditorNode::_global_menu_new_window));
}
- scene_tabs->set_current_tab(editor_data.get_edited_scene());
+ if (scene_tabs->get_tab_count() > 0) {
+ scene_tabs->set_current_tab(editor_data.get_edited_scene());
+ }
if (scene_tabs->get_offset_buttons_visible()) {
- // move add button to fixed position on the tabbar
+ // Move the add button to a fixed position.
if (scene_tab_add->get_parent() == scene_tabs) {
- if (scene_tabs->is_layout_rtl()) {
- scene_tab_add->set_position(Point2(tabbar_container->get_size().x - scene_tab_add->get_size().x, 0));
- } else {
- scene_tab_add->set_position(Point2(0, 0));
- }
scene_tabs->remove_child(scene_tab_add);
- tabbar_container->add_child(scene_tab_add);
- tabbar_container->move_child(scene_tab_add, 1);
+ scene_tab_add_ph->add_child(scene_tab_add);
+ scene_tab_add->set_position(Point2());
}
} else {
- // move add button to after last tab
- if (scene_tab_add->get_parent() == tabbar_container) {
- tabbar_container->remove_child(scene_tab_add);
+ // Move the add button to be after the last tab.
+ if (scene_tab_add->get_parent() == scene_tab_add_ph) {
+ scene_tab_add_ph->remove_child(scene_tab_add);
scene_tabs->add_child(scene_tab_add);
}
- Rect2 last_tab = Rect2();
- if (scene_tabs->get_tab_count() != 0) {
- last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1);
+
+ if (scene_tabs->get_tab_count() == 0) {
+ scene_tab_add->set_position(Point2());
+ return;
}
+
+ Rect2 last_tab = scene_tabs->get_tab_rect(scene_tabs->get_tab_count() - 1);
+ int hsep = scene_tabs->get_theme_constant(SNAME("hseparation"));
if (scene_tabs->is_layout_rtl()) {
- scene_tab_add->set_position(Point2(last_tab.get_position().x - scene_tab_add->get_size().x - 3, last_tab.get_position().y));
+ scene_tab_add->set_position(Point2(last_tab.position.x - scene_tab_add->get_size().x - hsep, last_tab.position.y));
} else {
- scene_tab_add->set_position(Point2(last_tab.get_position().x + last_tab.get_size().x + 3, last_tab.get_position().y));
+ scene_tab_add->set_position(Point2(last_tab.position.x + last_tab.size.width + hsep, last_tab.position.y));
}
}
}
@@ -624,6 +626,10 @@ void EditorNode::_notification(int p_what) {
editor_data.clear_edited_scenes();
} break;
+ case Control::NOTIFICATION_THEME_CHANGED: {
+ scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
+ } break;
+
case NOTIFICATION_READY: {
{
_initializing_addons = true;
@@ -6227,6 +6233,9 @@ EditorNode::EditorNode() {
tab_preview->set_position(Point2(2, 2) * EDSCALE);
tab_preview_panel->add_child(tab_preview);
+ tabbar_container = memnew(HBoxContainer);
+ srt->add_child(tabbar_container);
+
scene_tabs = memnew(TabBar);
scene_tabs->add_theme_style_override("tab_selected", gui_base->get_theme_stylebox(SNAME("SceneTabFG"), SNAME("EditorStyles")));
scene_tabs->add_theme_style_override("tab_unselected", gui_base->get_theme_stylebox(SNAME("SceneTabBG"), SNAME("EditorStyles")));
@@ -6244,16 +6253,26 @@ EditorNode::EditorNode() {
scene_tabs->connect("gui_input", callable_mp(this, &EditorNode::_scene_tab_input));
scene_tabs->connect("active_tab_rearranged", callable_mp(this, &EditorNode::_reposition_active_tab));
scene_tabs->connect("resized", callable_mp(this, &EditorNode::_update_scene_tabs));
-
- tabbar_container = memnew(HBoxContainer);
scene_tabs->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ tabbar_container->add_child(scene_tabs);
scene_tabs_context_menu = memnew(PopupMenu);
tabbar_container->add_child(scene_tabs_context_menu);
scene_tabs_context_menu->connect("id_pressed", callable_mp(this, &EditorNode::_menu_option));
- srt->add_child(tabbar_container);
- tabbar_container->add_child(scene_tabs);
+ scene_tab_add = memnew(Button);
+ scene_tab_add->set_flat(true);
+ scene_tab_add->set_tooltip(TTR("Add a new scene."));
+ scene_tab_add->set_icon(gui_base->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ scene_tab_add->add_theme_color_override("icon_normal_color", Color(0.6f, 0.6f, 0.6f, 0.8f));
+ scene_tabs->add_child(scene_tab_add);
+ scene_tab_add->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(FILE_NEW_SCENE));
+
+ scene_tab_add_ph = memnew(Control);
+ scene_tab_add_ph->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ scene_tab_add_ph->set_custom_minimum_size(scene_tab_add->get_minimum_size());
+ tabbar_container->add_child(scene_tab_add_ph);
+
distraction_free = memnew(Button);
distraction_free->set_flat(true);
ED_SHORTCUT_AND_COMMAND("editor/distraction_free_mode", TTR("Distraction Free Mode"), KeyModifierMask::CMD | KeyModifierMask::SHIFT | Key::F11);
@@ -6263,15 +6282,7 @@ EditorNode::EditorNode() {
distraction_free->connect("pressed", callable_mp(this, &EditorNode::_toggle_distraction_free_mode));
distraction_free->set_icon(gui_base->get_theme_icon(SNAME("DistractionFree"), SNAME("EditorIcons")));
distraction_free->set_toggle_mode(true);
-
- scene_tab_add = memnew(Button);
- scene_tab_add->set_flat(true);
- tabbar_container->add_child(scene_tab_add);
tabbar_container->add_child(distraction_free);
- scene_tab_add->set_tooltip(TTR("Add a new scene."));
- scene_tab_add->set_icon(gui_base->get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
- scene_tab_add->add_theme_color_override("icon_normal_color", Color(0.6f, 0.6f, 0.6f, 0.8f));
- scene_tab_add->connect("pressed", callable_mp(this, &EditorNode::_menu_option), make_binds(FILE_NEW_SCENE));
scene_root_parent = memnew(PanelContainer);
scene_root_parent->set_custom_minimum_size(Size2(0, 80) * EDSCALE);
@@ -7011,6 +7022,7 @@ EditorNode::EditorNode() {
add_editor_plugin(memnew(InputEventEditorPlugin(this)));
add_editor_plugin(memnew(SubViewportPreviewEditorPlugin(this)));
add_editor_plugin(memnew(TextControlEditorPlugin(this)));
+ add_editor_plugin(memnew(ReplicationEditorPlugin(this)));
for (int i = 0; i < EditorPlugins::get_plugin_count(); i++) {
add_editor_plugin(EditorPlugins::create(i, this));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 5c013a18d9..8d322a1bfd 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -384,6 +384,7 @@ private:
HBoxContainer *tabbar_container;
Button *distraction_free;
Button *scene_tab_add;
+ Control *scene_tab_add_ph;
bool scene_distraction;
bool script_distraction;
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 36e42b8e9b..ef1ceebabf 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -592,7 +592,7 @@ void EditorPropertyMember::_property_select() {
} else if (hint == MEMBER_PROPERTY_OF_VARIANT_TYPE) {
Variant::Type type = Variant::NIL;
String tname = hint_text;
- if (tname.find(".") != -1) {
+ if (tname.contains(".")) {
tname = tname.get_slice(".", 0);
}
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
@@ -809,7 +809,7 @@ void EditorPropertyLayersGrid::_rename_operation_confirm() {
if (new_name.length() == 0) {
EditorNode::get_singleton()->show_warning(TTR("No name provided."));
return;
- } else if (new_name.find("/") != -1 || new_name.find("\\") != -1 || new_name.find(":") != -1) {
+ } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) {
EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters."));
return;
}
@@ -1263,7 +1263,7 @@ void EditorPropertyInteger::_bind_methods() {
void EditorPropertyInteger::setup(int64_t p_min, int64_t p_max, int64_t p_step, bool p_allow_greater, bool p_allow_lesser) {
spin->set_min(p_min);
spin->set_max(p_max);
- spin->set_step((p_step == 0) ? 1 : p_step);
+ spin->set_step(p_step);
spin->set_allow_greater(p_allow_greater);
spin->set_allow_lesser(p_allow_lesser);
}
@@ -1353,7 +1353,7 @@ void EditorPropertyFloat::setup(double p_min, double p_max, double p_step, bool
angle_in_radians = p_angle_in_radians;
spin->set_min(p_min);
spin->set_max(p_max);
- spin->set_step((p_step == 0) ? 0.1 : p_step);
+ spin->set_step(p_step);
spin->set_hide_slider(p_no_slider);
spin->set_exp_ratio(p_exp_range);
spin->set_allow_greater(p_greater);
@@ -2883,7 +2883,7 @@ void EditorPropertyNodePath::update_property() {
Node *target_node = base_node->get_node(p);
ERR_FAIL_COND(!target_node);
- if (String(target_node->get_name()).find("@") != -1) {
+ if (String(target_node->get_name()).contains("@")) {
assign->set_icon(Ref<Texture2D>());
assign->set_text(p);
return;
@@ -3318,9 +3318,9 @@ struct EditorPropertyRangeHint {
bool angle_in_degrees = false;
bool greater = true;
bool lesser = true;
- double min = -99999;
- double max = 99999;
- double step = 0;
+ double min = -99999.0;
+ double max = 99999.0;
+ double step = 1.0;
String suffix;
bool exp_range = false;
bool hide_slider = true;
@@ -3331,18 +3331,25 @@ static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const Stri
EditorPropertyRangeHint hint;
hint.step = p_default_step;
bool degrees = false;
- if (p_hint == PROPERTY_HINT_RANGE && p_hint_text.get_slice_count(",") >= 2) {
- hint.greater = false; //if using ranged, assume false by default
+
+ if (p_hint == PROPERTY_HINT_RANGE) {
+ Vector<String> slices = p_hint_text.split(",");
+ ERR_FAIL_COND_V_MSG(slices.size() < 2, hint,
+ vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": Missing required min and/or max values.", p_hint_text));
+
+ hint.greater = false; // If using ranged, assume false by default.
hint.lesser = false;
- hint.min = p_hint_text.get_slice(",", 0).to_float();
- hint.max = p_hint_text.get_slice(",", 1).to_float();
- if (p_hint_text.get_slice_count(",") >= 3) {
- hint.step = p_hint_text.get_slice(",", 2).to_float();
+ hint.min = slices[0].to_float();
+ hint.max = slices[1].to_float();
+
+ if (slices.size() >= 3 && slices[2].is_valid_float()) {
+ // Step is optional, could be something else if not a number.
+ hint.step = slices[2].to_float();
}
hint.hide_slider = false;
- for (int i = 2; i < p_hint_text.get_slice_count(","); i++) {
- String slice = p_hint_text.get_slice(",", i).strip_edges();
+ for (int i = 2; i < slices.size(); i++) {
+ String slice = slices[i].strip_edges();
if (slice == "radians") {
hint.radians = true;
} else if (slice == "degrees") {
@@ -3365,6 +3372,9 @@ static EditorPropertyRangeHint _parse_range_hint(PropertyHint p_hint, const Stri
hint.suffix = U"\u00B0";
}
+ ERR_FAIL_COND_V_MSG(hint.step == 0, hint,
+ vformat("Invalid PROPERTY_HINT_RANGE with hint \"%s\": Step cannot be 0.", p_hint_text));
+
return hint;
}
@@ -3435,9 +3445,6 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
EditorPropertyInteger *editor = memnew(EditorPropertyInteger);
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, 1);
- if (hint.step == 0) {
- WARN_PRINT(p_path + ": Range step size is 0.");
- }
editor->setup(hint.min, hint.max, hint.step, hint.greater, hint.lesser);
return editor;
@@ -3466,9 +3473,6 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_
EditorPropertyFloat *editor = memnew(EditorPropertyFloat);
EditorPropertyRangeHint hint = _parse_range_hint(p_hint, p_hint_text, default_float_step);
- if (hint.step == 0) {
- WARN_PRINT(p_path + ": Range step size is 0.");
- }
editor->setup(hint.min, hint.max, hint.step, hint.hide_slider, hint.exp_range, hint.greater, hint.lesser, hint.suffix, hint.radians);
return editor;
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 71a855b22c..cfed86d1ae 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -502,6 +502,16 @@ void EditorPropertyArray::drop_data_fw(const Point2 &p_point, const Variant &p_d
}
void EditorPropertyArray::_notification(int p_what) {
+ if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
+ change_type->clear();
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ String type = Variant::get_type_name(Variant::Type(i));
+ change_type->add_icon_item(get_theme_icon(type, SNAME("EditorIcons")), type, i);
+ }
+ change_type->add_separator();
+ change_type->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Remove Item"), Variant::VARIANT_MAX);
+ }
+
if (p_what == NOTIFICATION_DRAG_BEGIN) {
if (is_visible_in_tree()) {
if (_is_drop_valid(get_viewport()->gui_get_drag_data())) {
@@ -691,13 +701,6 @@ EditorPropertyArray::EditorPropertyArray() {
change_type = memnew(PopupMenu);
add_child(change_type);
change_type->connect("id_pressed", callable_mp(this, &EditorPropertyArray::_change_type_menu));
-
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- String type = Variant::get_type_name(Variant::Type(i));
- change_type->add_item(type, i);
- }
- change_type->add_separator();
- change_type->add_item(TTR("Remove Item"), Variant::VARIANT_MAX);
changing_type_index = -1;
subtype = Variant::NIL;
@@ -1119,10 +1122,11 @@ void EditorPropertyDictionary::update_property() {
prop->update_property();
if (i == amount + 1) {
- Button *butt_add_item = memnew(Button);
- butt_add_item->set_text(TTR("Add Key/Value Pair"));
- butt_add_item->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_add_key_value));
- add_vbox->add_child(butt_add_item);
+ button_add_item = memnew(Button);
+ button_add_item->set_text(TTR("Add Key/Value Pair"));
+ button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ button_add_item->connect("pressed", callable_mp(this, &EditorPropertyDictionary::_add_key_value));
+ add_vbox->add_child(button_add_item);
}
}
@@ -1142,6 +1146,19 @@ void EditorPropertyDictionary::_object_id_selected(const StringName &p_property,
}
void EditorPropertyDictionary::_notification(int p_what) {
+ if (p_what == NOTIFICATION_THEME_CHANGED || p_what == NOTIFICATION_ENTER_TREE) {
+ change_type->clear();
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ String type = Variant::get_type_name(Variant::Type(i));
+ change_type->add_icon_item(get_theme_icon(type, SNAME("EditorIcons")), type, i);
+ }
+ change_type->add_separator();
+ change_type->add_icon_item(get_theme_icon(SNAME("Remove"), SNAME("EditorIcons")), TTR("Remove Item"), Variant::VARIANT_MAX);
+
+ if (Object::cast_to<Button>(button_add_item)) {
+ button_add_item->set_icon(get_theme_icon(SNAME("Add"), SNAME("EditorIcons")));
+ }
+ }
}
void EditorPropertyDictionary::_edit_pressed() {
@@ -1179,16 +1196,10 @@ EditorPropertyDictionary::EditorPropertyDictionary() {
add_focusable(edit);
vbox = nullptr;
page_slider = nullptr;
+ button_add_item = nullptr;
updating = false;
change_type = memnew(PopupMenu);
add_child(change_type);
change_type->connect("id_pressed", callable_mp(this, &EditorPropertyDictionary::_change_type_menu));
-
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- String type = Variant::get_type_name(Variant::Type(i));
- change_type->add_item(type, i);
- }
- change_type->add_separator();
- change_type->add_item(TTR("Remove Item"), Variant::VARIANT_MAX);
changing_type_index = -1;
}
diff --git a/editor/editor_properties_array_dict.h b/editor/editor_properties_array_dict.h
index 4c31ba0b49..bb10cdf97e 100644
--- a/editor/editor_properties_array_dict.h
+++ b/editor/editor_properties_array_dict.h
@@ -147,6 +147,7 @@ class EditorPropertyDictionary : public EditorProperty {
EditorSpinSlider *size_slider;
EditorSpinSlider *page_slider;
HBoxContainer *page_hbox;
+ Button *button_add_item;
void _page_changed(double p_page);
void _edit_pressed();
diff --git a/editor/editor_sectioned_inspector.cpp b/editor/editor_sectioned_inspector.cpp
index 6853045345..1bab56ac4a 100644
--- a/editor/editor_sectioned_inspector.cpp
+++ b/editor/editor_sectioned_inspector.cpp
@@ -89,7 +89,7 @@ class SectionedInspectorFilter : public Object {
if (pi.name.begins_with(section + "/")) {
pi.name = pi.name.replace_first(section + "/", "");
- if (!allow_sub && pi.name.find("/") != -1) {
+ if (!allow_sub && pi.name.contains("/")) {
continue;
}
p_list->push_back(pi);
@@ -227,7 +227,7 @@ void SectionedInspector::update_category_list() {
continue;
}
- if (pi.name.find(":") != -1 || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene" || pi.name.begins_with("_global_script")) {
+ if (pi.name.contains(":") || pi.name == "script" || pi.name == "resource_name" || pi.name == "resource_path" || pi.name == "resource_local_to_scene" || pi.name.begins_with("_global_script")) {
continue;
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index bc71072d29..a038da4c18 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -629,7 +629,7 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
for (int i = 0; i < p_path->get_file_count(); i++) {
String file = p_path->get_file(i);
- if (file.to_lower().find(searched_string) != -1) {
+ if (file.to_lower().contains(searched_string)) {
FileInfo fi;
fi.name = file;
fi.type = p_path->get_file_type(i);
@@ -1373,8 +1373,8 @@ void FileSystemDock::_make_dir_confirm() {
if (dir_name.length() == 0) {
EditorNode::get_singleton()->show_warning(TTR("No name provided."));
return;
- } else if (dir_name.find("/") != -1 || dir_name.find("\\") != -1 || dir_name.find(":") != -1 || dir_name.find("*") != -1 ||
- dir_name.find("|") != -1 || dir_name.find(">") != -1 || dir_name.ends_with(".") || dir_name.ends_with(" ")) {
+ } else if (dir_name.contains("/") || dir_name.contains("\\") || dir_name.contains(":") || dir_name.contains("*") ||
+ dir_name.contains("|") || dir_name.contains(">") || dir_name.ends_with(".") || dir_name.ends_with(" ")) {
EditorNode::get_singleton()->show_warning(TTR("Provided name contains invalid characters."));
return;
}
@@ -1478,7 +1478,7 @@ void FileSystemDock::_rename_operation_confirm() {
if (new_name.length() == 0) {
EditorNode::get_singleton()->show_warning(TTR("No name provided."));
return;
- } else if (new_name.find("/") != -1 || new_name.find("\\") != -1 || new_name.find(":") != -1) {
+ } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) {
EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters."));
return;
} else if (to_rename.is_file && old_name.get_extension() != new_name.get_extension()) {
@@ -1540,7 +1540,7 @@ void FileSystemDock::_duplicate_operation_confirm() {
if (new_name.length() == 0) {
EditorNode::get_singleton()->show_warning(TTR("No name provided."));
return;
- } else if (new_name.find("/") != -1 || new_name.find("\\") != -1 || new_name.find(":") != -1) {
+ } else if (new_name.contains("/") || new_name.contains("\\") || new_name.contains(":")) {
EditorNode::get_singleton()->show_warning(TTR("Name contains invalid characters."));
return;
}
diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp
index 131ecc3b12..dd72def6ad 100644
--- a/editor/find_in_files.cpp
+++ b/editor/find_in_files.cpp
@@ -53,11 +53,6 @@ inline void pop_back(T &container) {
container.resize(container.size() - 1);
}
-// TODO: Copied from TextEdit private, would be nice to extract it in a single place.
-static bool is_text_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
-}
-
static bool find_next(const String &line, String pattern, int from, bool match_case, bool whole_words, int &out_begin, int &out_end) {
int end = from;
@@ -73,10 +68,10 @@ static bool find_next(const String &line, String pattern, int from, bool match_c
out_end = end;
if (whole_words) {
- if (begin > 0 && is_text_char(line[begin - 1])) {
+ if (begin > 0 && (is_ascii_identifier_char(line[begin - 1]))) {
continue;
}
- if (end < line.size() && is_text_char(line[end])) {
+ if (end < line.size() && (is_ascii_identifier_char(line[end]))) {
continue;
}
}
diff --git a/editor/import/collada.cpp b/editor/import/collada.cpp
index b40a810763..605f385de2 100644
--- a/editor/import/collada.cpp
+++ b/editor/import/collada.cpp
@@ -287,7 +287,7 @@ void Collada::_parse_image(XMLParser &parser) {
if (state.version < State::Version(1, 4, 0)) {
/* <1.4 */
String path = parser.get_attribute_value("source").strip_edges();
- if (path.find("://") == -1 && path.is_relative_path()) {
+ if (!path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
image.path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path.uri_decode()));
}
@@ -300,7 +300,7 @@ void Collada::_parse_image(XMLParser &parser) {
parser.read();
String path = parser.get_node_data().strip_edges().uri_decode();
- if (path.find("://") == -1 && path.is_relative_path()) {
+ if (!path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(state.local_path.get_base_dir().plus_file(path));
@@ -1832,10 +1832,10 @@ void Collada::_parse_animation(XMLParser &parser) {
}
}
- if (target.find("/") != -1) { //transform component
+ if (target.contains("/")) { //transform component
track.target = target.get_slicec('/', 0);
track.param = target.get_slicec('/', 1);
- if (track.param.find(".") != -1) {
+ if (track.param.contains(".")) {
track.component = track.param.get_slice(".", 1).to_upper();
}
track.param = track.param.get_slice(".", 0);
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 0fefa0f3c4..d9448dd4a9 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -316,7 +316,7 @@ static bool _teststr(const String &p_what, const String &p_str) {
String what = p_what;
//remove trailing spaces and numbers, some apps like blender add ".number" to duplicates so also compensate for this
- while (what.length() && ((what[what.length() - 1] >= '0' && what[what.length() - 1] <= '9') || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) {
+ while (what.length() && (is_digit(what[what.length() - 1]) || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) {
what = what.substr(0, what.length() - 1);
}
@@ -336,7 +336,7 @@ static String _fixstr(const String &p_what, const String &p_str) {
String what = p_what;
//remove trailing spaces and numbers, some apps like blender add ".number" to duplicates so also compensate for this
- while (what.length() && ((what[what.length() - 1] >= '0' && what[what.length() - 1] <= '9') || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) {
+ while (what.length() && (is_digit(what[what.length() - 1]) || what[what.length() - 1] <= 32 || what[what.length() - 1] == '.')) {
what = what.substr(0, what.length() - 1);
}
diff --git a/editor/plugins/animation_blend_tree_editor_plugin.cpp b/editor/plugins/animation_blend_tree_editor_plugin.cpp
index e46c81b77e..b000ff8eee 100644
--- a/editor/plugins/animation_blend_tree_editor_plugin.cpp
+++ b/editor/plugins/animation_blend_tree_editor_plugin.cpp
@@ -833,7 +833,7 @@ void AnimationNodeBlendTreeEditor::_node_renamed(const String &p_text, Ref<Anima
const String &new_name = p_text;
- ERR_FAIL_COND(new_name.is_empty() || new_name.find(".") != -1 || new_name.find("/") != -1);
+ ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/"));
if (new_name == prev_name) {
return; //nothing to do
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 83c2b53241..320c47e820 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -485,7 +485,7 @@ void AnimationPlayerEditor::_animation_name_edited() {
player->stop();
String new_name = name->get_text();
- if (new_name.is_empty() || new_name.find(":") != -1 || new_name.find("/") != -1) {
+ if (new_name.is_empty() || new_name.contains(":") || new_name.contains("/")) {
error_dialog->set_text(TTR("Invalid animation name!"));
error_dialog->popup_centered();
return;
@@ -746,7 +746,7 @@ void AnimationPlayerEditor::_load_animations(Vector<String> p_files) {
file = file.substr(file.rfind("\\") + 1, file.length());
}
- if (file.find(".") != -1) {
+ if (file.contains(".")) {
file = file.substr(0, file.find("."));
}
diff --git a/editor/plugins/animation_state_machine_editor.cpp b/editor/plugins/animation_state_machine_editor.cpp
index 649fc53b3a..f750c92fb3 100644
--- a/editor/plugins/animation_state_machine_editor.cpp
+++ b/editor/plugins/animation_state_machine_editor.cpp
@@ -1059,7 +1059,7 @@ void AnimationNodeStateMachineEditor::_removed_from_graph() {
void AnimationNodeStateMachineEditor::_name_edited(const String &p_text) {
const String &new_name = p_text;
- ERR_FAIL_COND(new_name.is_empty() || new_name.find(".") != -1 || new_name.find("/") != -1);
+ ERR_FAIL_COND(new_name.is_empty() || new_name.contains(".") || new_name.contains("/"));
if (new_name == prev_name) {
return; // Nothing to do.
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index dff01cbf05..d496804bf2 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -2507,7 +2507,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref<InputEvent> &p_event) {
_update_cursor();
// Grab focus
- if (!viewport->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
+ if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
viewport->call_deferred(SNAME("grab_focus"));
}
}
diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp
index cef505181a..7e0019faac 100644
--- a/editor/plugins/editor_preview_plugins.cpp
+++ b/editor/plugins/editor_preview_plugins.cpp
@@ -462,10 +462,6 @@ EditorMaterialPreviewPlugin::~EditorMaterialPreviewPlugin() {
///////////////////////////////////////////////////////////////////////////
-static bool _is_text_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
-}
-
bool EditorScriptPreviewPlugin::handles(const String &p_type) const {
return ClassDB::is_parent_class(p_type, "Script");
}
@@ -538,15 +534,15 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
if (in_comment) {
color = comment_color;
} else {
- if (c != '_' && ((c >= '!' && c <= '/') || (c >= ':' && c <= '@') || (c >= '[' && c <= '`') || (c >= '{' && c <= '~') || c == '\t')) {
+ if (is_symbol(c)) {
//make symbol a little visible
color = symbol_color;
in_control_flow_keyword = false;
in_keyword = false;
- } else if (!prev_is_text && _is_text_char(c)) {
+ } else if (!prev_is_text && is_ascii_identifier_char(c)) {
int pos = i;
- while (_is_text_char(code[pos])) {
+ while (is_ascii_identifier_char(code[pos])) {
pos++;
}
String word = code.substr(i, pos - i);
@@ -556,7 +552,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
in_keyword = true;
}
- } else if (!_is_text_char(c)) {
+ } else if (!is_ascii_identifier_char(c)) {
in_keyword = false;
}
@@ -571,7 +567,7 @@ Ref<Texture2D> EditorScriptPreviewPlugin::generate(const RES &p_from, const Size
img->set_pixel(col, y0 + line * 2, bg_color.blend(ul));
img->set_pixel(col, y0 + line * 2 + 1, color);
- prev_is_text = _is_text_char(c);
+ prev_is_text = is_ascii_identifier_char(c);
}
col++;
} else {
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 4610171d68..0c0188e8d1 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1203,7 +1203,7 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const
}
void Node3DEditorViewport::_surface_mouse_enter() {
- if (!surface->has_focus() && (!get_focus_owner() || !get_focus_owner()->is_text_field())) {
+ if (!surface->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) {
surface->grab_focus();
}
}
diff --git a/editor/plugins/replication_editor_plugin.cpp b/editor/plugins/replication_editor_plugin.cpp
new file mode 100644
index 0000000000..93f4a853f3
--- /dev/null
+++ b/editor/plugins/replication_editor_plugin.cpp
@@ -0,0 +1,390 @@
+/*************************************************************************/
+/* replication_editor_plugin.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "replication_editor_plugin.h"
+
+#include "editor/editor_scale.h"
+#include "editor/inspector_dock.h"
+#include "scene/gui/dialogs.h"
+#include "scene/gui/tree.h"
+#include "scene/multiplayer/multiplayer_synchronizer.h"
+
+/// ReplicationEditor
+ReplicationEditor::ReplicationEditor(EditorNode *p_editor) {
+ editor = p_editor;
+ set_v_size_flags(SIZE_EXPAND_FILL);
+ set_custom_minimum_size(Size2(0, 200) * EDSCALE);
+
+ delete_dialog = memnew(ConfirmationDialog);
+ delete_dialog->connect("cancelled", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(false));
+ delete_dialog->connect("confirmed", callable_mp(this, &ReplicationEditor::_dialog_closed), varray(true));
+ add_child(delete_dialog);
+
+ error_dialog = memnew(AcceptDialog);
+ error_dialog->get_ok_button()->set_text(TTR("Close"));
+ error_dialog->set_title(TTR("Error!"));
+ add_child(error_dialog);
+
+ VBoxContainer *vb = memnew(VBoxContainer);
+ vb->set_v_size_flags(SIZE_EXPAND_FILL);
+ add_child(vb);
+
+ HBoxContainer *hb = memnew(HBoxContainer);
+ vb->add_child(hb);
+ np_line_edit = memnew(LineEdit);
+ np_line_edit->set_placeholder(":property");
+ np_line_edit->set_h_size_flags(SIZE_EXPAND_FILL);
+ hb->add_child(np_line_edit);
+ add_button = memnew(Button);
+ add_button->connect("pressed", callable_mp(this, &ReplicationEditor::_add_pressed));
+ add_button->set_text(TTR("Add"));
+ hb->add_child(add_button);
+
+ tree = memnew(Tree);
+ tree->set_hide_root(true);
+ tree->set_columns(4);
+ tree->set_column_titles_visible(true);
+ tree->set_column_title(0, TTR("Properties"));
+ tree->set_column_expand(0, true);
+ tree->set_column_title(1, TTR("Spawn"));
+ tree->set_column_expand(1, false);
+ tree->set_column_custom_minimum_width(1, 100);
+ tree->set_column_title(2, TTR("Sync"));
+ tree->set_column_custom_minimum_width(2, 100);
+ tree->set_column_expand(2, false);
+ tree->set_column_expand(3, false);
+ tree->create_item();
+ tree->connect("button_pressed", callable_mp(this, &ReplicationEditor::_tree_button_pressed));
+ tree->connect("item_edited", callable_mp(this, &ReplicationEditor::_tree_item_edited));
+ tree->set_v_size_flags(SIZE_EXPAND_FILL);
+ vb->add_child(tree);
+}
+
+void ReplicationEditor::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("_update_config"), &ReplicationEditor::_update_config);
+ ClassDB::bind_method(D_METHOD("_update_checked", "property", "column", "checked"), &ReplicationEditor::_update_checked);
+ ADD_SIGNAL(MethodInfo("keying_changed"));
+}
+
+void ReplicationEditor::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE || p_what == EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED) {
+ add_theme_style_override("panel", editor->get_gui_base()->get_theme_stylebox(SNAME("panel"), SNAME("Panel")));
+ } else if (p_what == NOTIFICATION_VISIBILITY_CHANGED) {
+ update_keying();
+ }
+}
+
+void ReplicationEditor::_add_pressed() {
+ if (!current) {
+ error_dialog->set_text(TTR("Please select a MultiplayerSynchronizer first."));
+ error_dialog->popup_centered();
+ return;
+ }
+ if (current->get_root_path().is_empty()) {
+ error_dialog->set_text(TTR("The MultiplayerSynchronizer needs a root path."));
+ error_dialog->popup_centered();
+ return;
+ }
+ String np_text = np_line_edit->get_text();
+ if (np_text.find(":") == -1) {
+ np_text = ":" + np_text;
+ }
+ NodePath prop = NodePath(np_text);
+ if (prop.is_empty()) {
+ return;
+ }
+ UndoRedo *undo_redo = editor->get_undo_redo();
+ undo_redo->create_action(TTR("Add property"));
+ config = current->get_replication_config();
+ if (config.is_null()) {
+ config.instantiate();
+ current->set_replication_config(config);
+ undo_redo->add_do_method(current, "set_replication_config", config);
+ undo_redo->add_undo_method(current, "set_replication_config", Ref<SceneReplicationConfig>());
+ _update_config();
+ }
+ undo_redo->add_do_method(config.ptr(), "add_property", prop);
+ undo_redo->add_undo_method(config.ptr(), "remove_property", prop);
+ undo_redo->add_do_method(this, "_update_config");
+ undo_redo->add_undo_method(this, "_update_config");
+ undo_redo->commit_action();
+}
+
+void ReplicationEditor::_tree_item_edited() {
+ TreeItem *ti = tree->get_edited();
+ if (!ti || config.is_null()) {
+ return;
+ }
+ int column = tree->get_edited_column();
+ ERR_FAIL_COND(column < 1 || column > 2);
+ const NodePath prop = ti->get_metadata(0);
+ UndoRedo *undo_redo = editor->get_undo_redo();
+ bool value = ti->is_checked(column);
+ String method;
+ if (column == 1) {
+ undo_redo->create_action(TTR("Set spawn property"));
+ method = "property_set_spawn";
+ } else {
+ undo_redo->create_action(TTR("Set sync property"));
+ method = "property_set_sync";
+ }
+ undo_redo->add_do_method(config.ptr(), method, prop, value);
+ undo_redo->add_undo_method(config.ptr(), method, prop, !value);
+ undo_redo->add_do_method(this, "_update_checked", prop, column, value);
+ undo_redo->add_undo_method(this, "_update_checked", prop, column, !value);
+ undo_redo->commit_action();
+}
+
+void ReplicationEditor::_tree_button_pressed(Object *p_item, int p_column, int p_id) {
+ TreeItem *ti = Object::cast_to<TreeItem>(p_item);
+ if (!ti) {
+ return;
+ }
+ deleting = ti->get_metadata(0);
+ delete_dialog->set_text(TTR("Delete Property?") + "\n\"" + ti->get_text(0) + "\"");
+ delete_dialog->popup_centered();
+}
+
+void ReplicationEditor::_dialog_closed(bool p_confirmed) {
+ if (deleting.is_empty() || config.is_null()) {
+ return;
+ }
+ if (p_confirmed) {
+ const NodePath prop = deleting;
+ int idx = config->property_get_index(prop);
+ bool spawn = config->property_get_spawn(prop);
+ bool sync = config->property_get_sync(prop);
+ UndoRedo *undo_redo = editor->get_undo_redo();
+ undo_redo->create_action(TTR("Remove Property"));
+ undo_redo->add_do_method(config.ptr(), "remove_property", prop);
+ undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx);
+ undo_redo->add_undo_method(config.ptr(), "property_set_spawn", prop, spawn);
+ undo_redo->add_undo_method(config.ptr(), "property_set_sync", prop, sync);
+ undo_redo->add_do_method(this, "_update_config");
+ undo_redo->add_undo_method(this, "_update_config");
+ undo_redo->commit_action();
+ }
+ deleting = NodePath();
+}
+
+void ReplicationEditor::_update_checked(const NodePath &p_prop, int p_column, bool p_checked) {
+ if (!tree->get_root()) {
+ return;
+ }
+ TreeItem *ti = tree->get_root()->get_first_child();
+ while (ti) {
+ if (ti->get_metadata(0).operator NodePath() == p_prop) {
+ ti->set_checked(p_column, p_checked);
+ return;
+ }
+ ti = ti->get_next();
+ }
+}
+
+void ReplicationEditor::update_keying() {
+ /// TODO make keying usable.
+#if 0
+ bool keying_enabled = false;
+ EditorHistory *editor_history = EditorNode::get_singleton()->get_editor_history();
+ if (is_visible_in_tree() && config.is_valid() && editor_history->get_path_size() > 0) {
+ Object *obj = ObjectDB::get_instance(editor_history->get_path_object(0));
+ keying_enabled = Object::cast_to<Node>(obj) != nullptr;
+ }
+
+ if (keying_enabled == keying) {
+ return;
+ }
+
+ keying = keying_enabled;
+ emit_signal(SNAME("keying_changed"));
+#endif
+}
+
+void ReplicationEditor::_update_config() {
+ deleting = NodePath();
+ tree->clear();
+ tree->create_item();
+ if (!config.is_valid()) {
+ update_keying();
+ return;
+ }
+ TypedArray<NodePath> props = config->get_properties();
+ for (int i = 0; i < props.size(); i++) {
+ const NodePath path = props[i];
+ _add_property(path, config->property_get_spawn(path), config->property_get_sync(path));
+ }
+ update_keying();
+}
+
+void ReplicationEditor::edit(MultiplayerSynchronizer *p_sync) {
+ if (current == p_sync) {
+ return;
+ }
+ current = p_sync;
+ if (current) {
+ config = current->get_replication_config();
+ } else {
+ config.unref();
+ }
+ _update_config();
+}
+
+Ref<Texture2D> ReplicationEditor::_get_class_icon(const Node *p_node) {
+ if (!p_node || !has_theme_icon(p_node->get_class(), "EditorIcons")) {
+ return get_theme_icon("ImportFail", "EditorIcons");
+ }
+ return get_theme_icon(p_node->get_class(), "EditorIcons");
+}
+
+void ReplicationEditor::_add_property(const NodePath &p_property, bool p_spawn, bool p_sync) {
+ String prop = String(p_property);
+ TreeItem *item = tree->create_item();
+ item->set_selectable(0, false);
+ item->set_selectable(1, false);
+ item->set_selectable(2, false);
+ item->set_selectable(3, false);
+ item->set_text(0, prop);
+ item->set_metadata(0, prop);
+ Node *root_node = current && !current->get_root_path().is_empty() ? current->get_node(current->get_root_path()) : nullptr;
+ Ref<Texture2D> icon = _get_class_icon(root_node);
+ if (root_node) {
+ String path = prop.substr(0, prop.find(":"));
+ String subpath = prop.substr(path.size());
+ Node *node = root_node->get_node_or_null(path);
+ if (!node) {
+ node = root_node;
+ }
+ item->set_text(0, String(node->get_name()) + ":" + subpath);
+ icon = _get_class_icon(node);
+ }
+ item->set_icon(0, icon);
+ item->add_button(3, get_theme_icon("Remove", "EditorIcons"));
+ item->set_text_alignment(1, HORIZONTAL_ALIGNMENT_CENTER);
+ item->set_cell_mode(1, TreeItem::CELL_MODE_CHECK);
+ item->set_checked(1, p_spawn);
+ item->set_editable(1, true);
+ item->set_text_alignment(2, HORIZONTAL_ALIGNMENT_CENTER);
+ item->set_cell_mode(2, TreeItem::CELL_MODE_CHECK);
+ item->set_checked(2, p_sync);
+ item->set_editable(2, true);
+}
+
+void ReplicationEditor::property_keyed(const String &p_property) {
+ ERR_FAIL_COND(!current || config.is_null());
+ Node *root = current->get_node(current->get_root_path());
+ ERR_FAIL_COND(!root);
+ EditorHistory *history = editor->get_editor_history();
+ ERR_FAIL_COND(history->get_path_size() == 0);
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(history->get_path_object(0)));
+ ERR_FAIL_COND(!node);
+ if (node->is_class("MultiplayerSynchronizer")) {
+ error_dialog->set_text(TTR("Properties of 'MultiplayerSynchronizer' cannot be configured for replication."));
+ error_dialog->popup_centered();
+ return;
+ }
+ if (history->get_path_size() > 1 || p_property.get_slice_count(":") > 1) {
+ error_dialog->set_text(TTR("Subresources cannot yet be configured for replication."));
+ error_dialog->popup_centered();
+ return;
+ }
+
+ String path = root->get_path_to(node);
+ for (int i = 1; i < history->get_path_size(); i++) {
+ String prop = history->get_path_property(i);
+ ERR_FAIL_COND(prop == "");
+ path += ":" + prop;
+ }
+ path += ":" + p_property;
+
+ NodePath prop = path;
+ UndoRedo *undo_redo = editor->get_undo_redo();
+ undo_redo->create_action(TTR("Add property"));
+ undo_redo->add_do_method(config.ptr(), "add_property", prop);
+ undo_redo->add_undo_method(config.ptr(), "remove_property", prop);
+ undo_redo->add_do_method(this, "_update_config");
+ undo_redo->add_undo_method(this, "_update_config");
+ undo_redo->commit_action();
+}
+
+/// ReplicationEditorPlugin
+ReplicationEditorPlugin::ReplicationEditorPlugin(EditorNode *p_node) {
+ editor = p_node;
+ repl_editor = memnew(ReplicationEditor(editor));
+ editor->add_bottom_panel_item(TTR("Replication"), repl_editor);
+}
+
+ReplicationEditorPlugin::~ReplicationEditorPlugin() {
+}
+
+void ReplicationEditorPlugin::_keying_changed() {
+ // TODO make lock usable.
+ //InspectorDock::get_inspector_singleton()->set_keying(repl_editor->has_keying(), this);
+}
+
+void ReplicationEditorPlugin::_property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance) {
+ if (!repl_editor->has_keying()) {
+ return;
+ }
+ repl_editor->property_keyed(p_keyed);
+}
+
+void ReplicationEditorPlugin::_notification(int p_what) {
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ //Node3DEditor::get_singleton()->connect("transform_key_request", callable_mp(this, &AnimationPlayerEditorPlugin::_transform_key_request));
+ InspectorDock::get_inspector_singleton()->connect("property_keyed", callable_mp(this, &ReplicationEditorPlugin::_property_keyed));
+ repl_editor->connect("keying_changed", callable_mp(this, &ReplicationEditorPlugin::_keying_changed));
+ // TODO make lock usable.
+ //InspectorDock::get_inspector_singleton()->connect("object_inspected", callable_mp(repl_editor, &ReplicationEditor::update_keying));
+ get_tree()->connect("node_removed", callable_mp(this, &ReplicationEditorPlugin::_node_removed));
+ }
+}
+
+void ReplicationEditorPlugin::_node_removed(Node *p_node) {
+ if (p_node && p_node == repl_editor->get_current()) {
+ repl_editor->edit(nullptr);
+ if (repl_editor->is_visible_in_tree()) {
+ editor->hide_bottom_panel();
+ }
+ }
+}
+
+void ReplicationEditorPlugin::edit(Object *p_object) {
+ repl_editor->edit(Object::cast_to<MultiplayerSynchronizer>(p_object));
+}
+
+bool ReplicationEditorPlugin::handles(Object *p_object) const {
+ return p_object->is_class("MultiplayerSynchronizer");
+}
+
+void ReplicationEditorPlugin::make_visible(bool p_visible) {
+ if (p_visible) {
+ editor->make_bottom_panel_item_visible(repl_editor);
+ }
+}
diff --git a/editor/plugins/replication_editor_plugin.h b/editor/plugins/replication_editor_plugin.h
new file mode 100644
index 0000000000..049eda99cc
--- /dev/null
+++ b/editor/plugins/replication_editor_plugin.h
@@ -0,0 +1,108 @@
+/*************************************************************************/
+/* replication_editor_plugin.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef REPLICATION_EDITOR_PLUGIN_H
+#define REPLICATION_EDITOR_PLUGIN_H
+
+#include "editor/editor_node.h"
+#include "editor/editor_plugin.h"
+#include "scene/resources/scene_replication_config.h"
+
+class ConfirmationDialog;
+class MultiplayerSynchronizer;
+class Tree;
+
+class ReplicationEditor : public VBoxContainer {
+ GDCLASS(ReplicationEditor, VBoxContainer);
+
+private:
+ EditorNode *editor;
+ MultiplayerSynchronizer *current = nullptr;
+
+ AcceptDialog *error_dialog = nullptr;
+ ConfirmationDialog *delete_dialog = nullptr;
+ Button *add_button = nullptr;
+ LineEdit *np_line_edit = nullptr;
+
+ Ref<SceneReplicationConfig> config;
+ NodePath deleting;
+ Tree *tree;
+ bool keying = false;
+
+ Ref<Texture2D> _get_class_icon(const Node *p_node);
+
+ void _add_pressed();
+ void _tree_item_edited();
+ void _tree_button_pressed(Object *p_item, int p_column, int p_id);
+ void _update_checked(const NodePath &p_prop, int p_column, bool p_checked);
+ void _update_config();
+ void _dialog_closed(bool p_confirmed);
+ void _add_property(const NodePath &p_property, bool p_spawn = true, bool p_sync = true);
+
+protected:
+ static void _bind_methods();
+
+ void _notification(int p_what);
+
+public:
+ void update_keying();
+ void edit(MultiplayerSynchronizer *p_object);
+ bool has_keying() const { return keying; }
+ MultiplayerSynchronizer *get_current() const { return current; }
+ void property_keyed(const String &p_property);
+
+ ReplicationEditor(EditorNode *p_node);
+ ~ReplicationEditor() {}
+};
+
+class ReplicationEditorPlugin : public EditorPlugin {
+ GDCLASS(ReplicationEditorPlugin, EditorPlugin);
+
+private:
+ EditorNode *editor;
+ ReplicationEditor *repl_editor;
+
+ void _node_removed(Node *p_node);
+ void _keying_changed();
+ void _property_keyed(const String &p_keyed, const Variant &p_value, bool p_advance);
+
+protected:
+ void _notification(int p_what);
+
+public:
+ virtual void edit(Object *p_object) override;
+ virtual bool handles(Object *p_object) const override;
+ virtual void make_visible(bool p_visible) override;
+
+ ReplicationEditorPlugin(EditorNode *p_node);
+ ~ReplicationEditorPlugin();
+};
+
+#endif // REPLICATION_EDITOR_PLUGIN_H
diff --git a/editor/plugins/resource_preloader_editor_plugin.cpp b/editor/plugins/resource_preloader_editor_plugin.cpp
index d5287bc2fb..786217a5c2 100644
--- a/editor/plugins/resource_preloader_editor_plugin.cpp
+++ b/editor/plugins/resource_preloader_editor_plugin.cpp
@@ -110,7 +110,7 @@ void ResourcePreloaderEditor::_item_edited() {
return;
}
- if (new_name.is_empty() || new_name.find("\\") != -1 || new_name.find("/") != -1 || preloader->has_resource(new_name)) {
+ if (new_name.is_empty() || new_name.contains("\\") || new_name.contains("/") || preloader->has_resource(new_name)) {
s->set_text(0, old_name);
return;
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 9e46150813..26227fa5bb 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -159,7 +159,7 @@ void EditorStandardSyntaxHighlighter::_update_cache() {
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
- if (name.find("/") != -1) {
+ if (name.contains("/")) {
continue;
}
highlighter->add_member_keyword_color(name, member_variable_color);
@@ -714,7 +714,7 @@ void ScriptEditor::_open_recent_script(int p_idx) {
return;
}
// if it's a path then it's most likely a deleted file not help
- } else if (path.find("::") != -1) {
+ } else if (path.contains("::")) {
// built-in script
String res_path = path.get_slice("::", 0);
if (ResourceLoader::get_resource_type(res_path) == "PackedScene") {
@@ -1603,7 +1603,8 @@ void ScriptEditor::_notification(int p_what) {
members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected));
help_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_help_overview_selected));
- script_split->connect("dragged", callable_mp(this, &ScriptEditor::_script_split_dragged));
+ script_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));
+ list_split->connect("dragged", callable_mp(this, &ScriptEditor::_split_dragged));
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed));
EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed));
@@ -2292,7 +2293,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
} else if (flags[i] == '\0' || (!inside_quotes && flags[i] == ' ')) {
String arg = flags.substr(from, num_chars);
- if (arg.find("{file}") != -1) {
+ if (arg.contains("{file}")) {
has_file_flag = true;
}
@@ -2809,7 +2810,7 @@ void ScriptEditor::_tree_changed() {
call_deferred(SNAME("_update_script_connections"));
}
-void ScriptEditor::_script_split_dragged(float) {
+void ScriptEditor::_split_dragged(float) {
_save_layout();
}
@@ -3199,8 +3200,12 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
tab_container->get_child(i)->set_meta("__editor_pass", Variant());
}
- if (p_layout->has_section_key("ScriptEditor", "split_offset")) {
- script_split->set_split_offset(p_layout->get_value("ScriptEditor", "split_offset"));
+ if (p_layout->has_section_key("ScriptEditor", "script_split_offset")) {
+ script_split->set_split_offset(p_layout->get_value("ScriptEditor", "script_split_offset"));
+ }
+
+ if (p_layout->has_section_key("ScriptEditor", "list_split_offset")) {
+ list_split->set_split_offset(p_layout->get_value("ScriptEditor", "list_split_offset"));
}
// Remove any deleted editors that have been removed between launches.
@@ -3253,7 +3258,8 @@ void ScriptEditor::get_window_layout(Ref<ConfigFile> p_layout) {
p_layout->set_value("ScriptEditor", "open_scripts", scripts);
p_layout->set_value("ScriptEditor", "open_help", helps);
- p_layout->set_value("ScriptEditor", "split_offset", script_split->get_split_offset());
+ p_layout->set_value("ScriptEditor", "script_split_offset", script_split->get_split_offset());
+ p_layout->set_value("ScriptEditor", "list_split_offset", list_split->get_split_offset());
// Save the cache.
script_editor_cache->save(EditorSettings::get_singleton()->get_project_settings_dir().plus_file("script_editor_cache.cfg"));
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 67a6a9da02..d754f1a378 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -418,7 +418,7 @@ class ScriptEditor : public PanelContainer {
void _tree_changed();
- void _script_split_dragged(float);
+ void _split_dragged(float);
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index 09af73520b..19ed11acb4 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -1295,7 +1295,7 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() {
script->get_language()->get_comment_delimiters(&comment_delimiters);
for (const String &script_delimiter : comment_delimiters) {
- if (script_delimiter.find(" ") == -1) {
+ if (!script_delimiter.contains(" ")) {
delimiter = script_delimiter;
break;
}
diff --git a/editor/plugins/theme_editor_plugin.cpp b/editor/plugins/theme_editor_plugin.cpp
index bc2739bdac..aaa09237cf 100644
--- a/editor/plugins/theme_editor_plugin.cpp
+++ b/editor/plugins/theme_editor_plugin.cpp
@@ -2226,7 +2226,7 @@ void ThemeTypeEditor::_update_type_list() {
}
updating = true;
- Control *focused = get_focus_owner();
+ Control *focused = get_viewport()->gui_get_focus_owner();
if (focused) {
if (focusables.has(focused)) {
// If focus is currently on one of the internal property editors, don't update.
diff --git a/editor/plugins/tiles/tile_map_editor.cpp b/editor/plugins/tiles/tile_map_editor.cpp
index 5d6b4e1fbb..6e3724ead9 100644
--- a/editor/plugins/tiles/tile_map_editor.cpp
+++ b/editor/plugins/tiles/tile_map_editor.cpp
@@ -3735,7 +3735,7 @@ void TileMapEditor::_move_tile_map_array_element(Object *p_undo_redo, Object *p_
String str = pi.name.trim_prefix(p_array_prefix);
int to_char_index = 0;
while (to_char_index < str.length()) {
- if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ if (!is_digit(str[to_char_index])) {
break;
}
to_char_index++;
diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp
index be261927ee..ab355d4658 100644
--- a/editor/plugins/tiles/tile_set_editor.cpp
+++ b/editor/plugins/tiles/tile_set_editor.cpp
@@ -458,7 +458,7 @@ void TileSetEditor::_move_tile_set_array_element(Object *p_undo_redo, Object *p_
String str = pi.name.trim_prefix(p_array_prefix);
int to_char_index = 0;
while (to_char_index < str.length()) {
- if (str[to_char_index] < '0' || str[to_char_index] > '9') {
+ if (!is_digit(str[to_char_index])) {
break;
}
to_char_index++;
diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp
index 08e0f7ae30..c582582a7d 100644
--- a/editor/project_manager.cpp
+++ b/editor/project_manager.cpp
@@ -1475,7 +1475,7 @@ void ProjectList::sort_projects() {
bool visible = true;
if (!_search_term.is_empty()) {
String search_path;
- if (_search_term.find("/") != -1) {
+ if (_search_term.contains("/")) {
// Search path will match the whole path
search_path = item.path;
} else {
diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp
index bedae4f8a9..c0ff1d72ee 100644
--- a/editor/project_settings_editor.cpp
+++ b/editor/project_settings_editor.cpp
@@ -242,7 +242,7 @@ void ProjectSettingsEditor::unhandled_input(const Ref<InputEvent> &p_event) {
String ProjectSettingsEditor::_get_setting_name() const {
String name = property_box->get_text().strip_edges();
- if (name.find("/") == -1) {
+ if (!name.contains("/")) {
name = "global/" + name;
}
return name;
@@ -512,6 +512,16 @@ void ProjectSettingsEditor::_update_theme() {
restart_container->add_theme_style_override("panel", get_theme_stylebox(SNAME("bg"), SNAME("Tree")));
restart_icon->set_texture(get_theme_icon(SNAME("StatusWarning"), SNAME("EditorIcons")));
restart_label->add_theme_color_override("font_color", get_theme_color(SNAME("warning_color"), SNAME("Editor")));
+
+ type_box->clear();
+ for (int i = 0; i < Variant::VARIANT_MAX; i++) {
+ // There's no point in adding Nil types, and Object types
+ // can't be serialized correctly in the project settings.
+ if (i != Variant::NIL && i != Variant::OBJECT) {
+ String type = Variant::get_type_name(Variant::Type(i));
+ type_box->add_icon_item(get_theme_icon(type, SNAME("EditorIcons")), type, i);
+ }
+ }
}
void ProjectSettingsEditor::_notification(int p_what) {
@@ -526,9 +536,9 @@ void ProjectSettingsEditor::_notification(int p_what) {
_update_action_map_editor();
_update_theme();
} break;
- case NOTIFICATION_THEME_CHANGED:
+ case NOTIFICATION_THEME_CHANGED: {
_update_theme();
- break;
+ } break;
}
}
@@ -589,14 +599,6 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
type_box->set_custom_minimum_size(Size2(120, 0) * EDSCALE);
header->add_child(type_box);
- for (int i = 0; i < Variant::VARIANT_MAX; i++) {
- // There's no point in adding Nil types, and Object types
- // can't be serialized correctly in the project settings.
- if (i != Variant::NIL && i != Variant::OBJECT) {
- type_box->add_item(Variant::get_type_name(Variant::Type(i)), i);
- }
- }
-
add_button = memnew(Button);
add_button->set_text(TTR("Add"));
add_button->set_disabled(true);
diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp
index 9d894afa6f..27fe696cc3 100644
--- a/editor/property_editor.cpp
+++ b/editor/property_editor.cpp
@@ -623,7 +623,7 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant::
MAKE_PROPSELECT
Variant::Type type = Variant::NIL;
String tname = hint_text;
- if (tname.find(".") != -1) {
+ if (tname.contains(".")) {
tname = tname.get_slice(".", 0);
}
for (int i = 0; i < Variant::VARIANT_MAX; i++) {
diff --git a/editor/property_selector.cpp b/editor/property_selector.cpp
index 7c0683c37b..406bcbe342 100644
--- a/editor/property_selector.cpp
+++ b/editor/property_selector.cpp
@@ -276,7 +276,7 @@ void PropertySelector::_update_search() {
TreeItem *item = search_options->create_item(category ? category : root);
String desc;
- if (mi.name.find(":") != -1) {
+ if (mi.name.contains(":")) {
desc = mi.name.get_slice(":", 1) + " ";
mi.name = mi.name.get_slice(":", 0);
} else if (mi.return_val.type != Variant::NIL) {
@@ -296,7 +296,7 @@ void PropertySelector::_update_search() {
if (mi.arguments[i].type == Variant::NIL) {
desc += ": Variant";
- } else if (mi.arguments[i].name.find(":") != -1) {
+ } else if (mi.arguments[i].name.contains(":")) {
desc += vformat(": %s", mi.arguments[i].name.get_slice(":", 1));
mi.arguments[i].name = mi.arguments[i].name.get_slice(":", 0);
} else {
diff --git a/editor/rename_dialog.cpp b/editor/rename_dialog.cpp
index 92dcd53830..c6a4c0d86a 100644
--- a/editor/rename_dialog.cpp
+++ b/editor/rename_dialog.cpp
@@ -337,7 +337,7 @@ void RenameDialog::_bind_methods() {
}
void RenameDialog::_update_substitute() {
- LineEdit *focus_owner_line_edit = Object::cast_to<LineEdit>(scene_tree_editor->get_focus_owner());
+ LineEdit *focus_owner_line_edit = Object::cast_to<LineEdit>(scene_tree_editor->get_viewport()->gui_get_focus_owner());
bool is_main_field = _is_main_field(focus_owner_line_edit);
but_insert_name->set_disabled(!is_main_field);
@@ -632,7 +632,7 @@ bool RenameDialog::_is_main_field(LineEdit *line_edit) {
}
void RenameDialog::_insert_text(String text) {
- LineEdit *focus_owner = Object::cast_to<LineEdit>(scene_tree_editor->get_focus_owner());
+ LineEdit *focus_owner = Object::cast_to<LineEdit>(scene_tree_editor->get_viewport()->gui_get_focus_owner());
if (_is_main_field(focus_owner)) {
focus_owner->selection_delete();
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 30ea1bcbb9..9b18d3a491 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -78,7 +78,7 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) {
void SceneTreeDock::unhandled_key_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
- if (get_focus_owner() && get_focus_owner()->is_text_field()) {
+ if (get_viewport()->gui_get_focus_owner() && get_viewport()->gui_get_focus_owner()->is_text_field()) {
return;
}
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index 360ba5780c..0c916bf56a 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -153,7 +153,7 @@ bool ScriptCreateDialog::_validate_class(const String &p_string) {
}
}
- bool valid_char = (p_string[i] >= '0' && p_string[i] <= '9') || (p_string[i] >= 'a' && p_string[i] <= 'z') || (p_string[i] >= 'A' && p_string[i] <= 'Z') || p_string[i] == '_' || p_string[i] == '.';
+ bool valid_char = is_ascii_identifier_char(p_string[i]) || p_string[i] == '.';
if (!valid_char) {
return false;
@@ -378,7 +378,7 @@ void ScriptCreateDialog::_language_changed(int l) {
String path = file_path->get_text();
String extension = "";
if (!path.is_empty()) {
- if (path.find(".") != -1) {
+ if (path.contains(".")) {
extension = path.get_extension();
}
@@ -811,7 +811,7 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL
List<String> comment_delimiters;
language->get_comment_delimiters(&comment_delimiters);
for (const String &script_delimiter : comment_delimiters) {
- if (script_delimiter.find(" ") == -1) {
+ if (!script_delimiter.contains(" ")) {
meta_delimiter = script_delimiter;
break;
}
diff --git a/editor/shader_create_dialog.cpp b/editor/shader_create_dialog.cpp
index 239860c9ab..95c4c5ff0d 100644
--- a/editor/shader_create_dialog.cpp
+++ b/editor/shader_create_dialog.cpp
@@ -221,7 +221,7 @@ void ShaderCreateDialog::_language_changed(int p_language) {
String extension = "";
if (!path.is_empty()) {
- if (path.find(".") != -1) {
+ if (path.contains(".")) {
extension = path.get_extension();
}
if (extension.length() == 0) {
diff --git a/main/main.cpp b/main/main.cpp
index 5cc1d3f7da..f8088cba1c 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -827,7 +827,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (I->next()) {
String vm = I->next()->get();
- if (vm.find("x") == -1) { // invalid parameter format
+ if (!vm.contains("x")) { // invalid parameter format
OS::get_singleton()->print("Invalid resolution '%s', it should be e.g. '1280x720'.\n",
vm.utf8().get_data());
@@ -858,7 +858,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (I->next()) {
String vm = I->next()->get();
- if (vm.find(",") == -1) { // invalid parameter format
+ if (!vm.contains(",")) { // invalid parameter format
OS::get_singleton()->print("Invalid position '%s', it should be e.g. '80,128'.\n",
vm.utf8().get_data());
@@ -940,7 +940,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "--debug-server") {
if (I->next()) {
debug_server_uri = I->next()->get();
- if (debug_server_uri.find("://") == -1) { // wrong address
+ if (!debug_server_uri.contains("://")) { // wrong address
OS::get_singleton()->print("Invalid debug server uri. It should be of the form <protocol>://<bind_address>:<port>.\n");
goto error;
}
@@ -1073,7 +1073,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
} else if (I->get() == "--remote-debug") {
if (I->next()) {
debug_uri = I->next()->get();
- if (debug_uri.find("://") == -1) { // wrong address
+ if (!debug_uri.contains("://")) { // wrong address
OS::get_singleton()->print(
"Invalid debug host address, it should be of the form <protocol>://<host/IP>:<port>.\n");
goto error;
@@ -1130,7 +1130,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
if (!remotefs.is_empty()) {
file_access_network_client = memnew(FileAccessNetworkClient);
int port;
- if (remotefs.find(":") != -1) {
+ if (remotefs.contains(":")) {
port = remotefs.get_slicec(':', 1).to_int();
remotefs = remotefs.get_slicec(':', 0);
} else {
@@ -1310,7 +1310,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/viewport_width",
PropertyInfo(Variant::INT, "display/window/size/viewport_width",
PROPERTY_HINT_RANGE,
- "0,7680,or_greater")); // 8K resolution
+ "0,7680,1,or_greater")); // 8K resolution
GLOBAL_DEF_BASIC("display/window/size/viewport_height", 600);
ProjectSettings::get_singleton()->set_custom_property_info("display/window/size/viewport_height",
@@ -1333,7 +1333,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
PropertyInfo(Variant::INT,
"display/window/size/window_height_override",
PROPERTY_HINT_RANGE,
- "0,4320,or_greater")); // 8K resolution
+ "0,4320,1,or_greater")); // 8K resolution
if (use_custom_res) {
if (!force_res) {
diff --git a/modules/bullet/shape_bullet.h b/modules/bullet/shape_bullet.h
index 6377f8915d..dffcadbcdc 100644
--- a/modules/bullet/shape_bullet.h
+++ b/modules/bullet/shape_bullet.h
@@ -105,7 +105,7 @@ private:
};
class SphereShapeBullet : public ShapeBullet {
- real_t radius;
+ real_t radius = 0.0;
public:
SphereShapeBullet();
@@ -137,8 +137,8 @@ private:
};
class CapsuleShapeBullet : public ShapeBullet {
- real_t height;
- real_t radius;
+ real_t height = 0.0;
+ real_t radius = 0.0;
public:
CapsuleShapeBullet();
@@ -155,8 +155,8 @@ private:
};
class CylinderShapeBullet : public ShapeBullet {
- real_t height;
- real_t radius;
+ real_t height = 0.0;
+ real_t radius = 0.0;
public:
CylinderShapeBullet();
diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp
index 4b36bca02a..fbddedbe55 100644
--- a/modules/csg/csg_shape.cpp
+++ b/modules/csg/csg_shape.cpp
@@ -1451,8 +1451,8 @@ Ref<Material> CSGCylinder3D::get_material() const {
CSGCylinder3D::CSGCylinder3D() {
// defaults
- radius = 1.0;
- height = 1.0;
+ radius = 0.5;
+ height = 2.0;
sides = 8;
cone = false;
smooth_faces = true;
@@ -1671,8 +1671,8 @@ Ref<Material> CSGTorus3D::get_material() const {
CSGTorus3D::CSGTorus3D() {
// defaults
- inner_radius = 2.0;
- outer_radius = 3.0;
+ inner_radius = 0.5;
+ outer_radius = 1.0;
sides = 8;
ring_sides = 6;
smooth_faces = true;
diff --git a/modules/csg/csg_shape.h b/modules/csg/csg_shape.h
index eed995a40e..6da9893368 100644
--- a/modules/csg/csg_shape.h
+++ b/modules/csg/csg_shape.h
@@ -239,7 +239,7 @@ class CSGBox3D : public CSGPrimitive3D {
virtual CSGBrush *_build_brush() override;
Ref<Material> material;
- Vector3 size = Vector3(2, 2, 2);
+ Vector3 size = Vector3(1, 1, 1);
protected:
static void _bind_methods();
diff --git a/modules/csg/doc_classes/CSGBox3D.xml b/modules/csg/doc_classes/CSGBox3D.xml
index d64e58ae4d..4b479ed42e 100644
--- a/modules/csg/doc_classes/CSGBox3D.xml
+++ b/modules/csg/doc_classes/CSGBox3D.xml
@@ -12,7 +12,7 @@
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the box.
</member>
- <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(2, 2, 2)">
+ <member name="size" type="Vector3" setter="set_size" getter="get_size" default="Vector3(1, 1, 1)">
The box's width, height and depth.
</member>
</members>
diff --git a/modules/csg/doc_classes/CSGCylinder3D.xml b/modules/csg/doc_classes/CSGCylinder3D.xml
index 40e989bfb3..1fe2025bab 100644
--- a/modules/csg/doc_classes/CSGCylinder3D.xml
+++ b/modules/csg/doc_classes/CSGCylinder3D.xml
@@ -12,13 +12,13 @@
<member name="cone" type="bool" setter="set_cone" getter="is_cone" default="false">
If [code]true[/code] a cone is created, the [member radius] will only apply to one side.
</member>
- <member name="height" type="float" setter="set_height" getter="get_height" default="1.0">
+ <member name="height" type="float" setter="set_height" getter="get_height" default="2.0">
The height of the cylinder.
</member>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the cylinder.
</member>
- <member name="radius" type="float" setter="set_radius" getter="get_radius" default="1.0">
+ <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.5">
The radius of the cylinder.
</member>
<member name="sides" type="int" setter="set_sides" getter="get_sides" default="8">
diff --git a/modules/csg/doc_classes/CSGTorus3D.xml b/modules/csg/doc_classes/CSGTorus3D.xml
index 91ee63a4c9..2c0eef8f09 100644
--- a/modules/csg/doc_classes/CSGTorus3D.xml
+++ b/modules/csg/doc_classes/CSGTorus3D.xml
@@ -9,13 +9,13 @@
<tutorials>
</tutorials>
<members>
- <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="2.0">
+ <member name="inner_radius" type="float" setter="set_inner_radius" getter="get_inner_radius" default="0.5">
The inner radius of the torus.
</member>
<member name="material" type="Material" setter="set_material" getter="get_material">
The material used to render the torus.
</member>
- <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="3.0">
+ <member name="outer_radius" type="float" setter="set_outer_radius" getter="get_outer_radius" default="1.0">
The outer radius of the torus.
</member>
<member name="ring_sides" type="int" setter="set_ring_sides" getter="get_ring_sides" default="6">
diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp
index 4cca907bf2..758c47eecc 100644
--- a/modules/fbx/editor_scene_importer_fbx.cpp
+++ b/modules/fbx/editor_scene_importer_fbx.cpp
@@ -123,7 +123,7 @@ Node3D *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_
bool corrupt = false;
// safer to check this way as there can be different formatted headers
- if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) {
+ if (fbx_header_string.contains("Kaydara FBX Binary")) {
is_binary = true;
print_verbose("[doc] is binary");
diff --git a/modules/gdnative/gdnative/packed_arrays.cpp b/modules/gdnative/gdnative/packed_arrays.cpp
index bb6f0324a8..0c49694e0b 100644
--- a/modules/gdnative/gdnative/packed_arrays.cpp
+++ b/modules/gdnative/gdnative/packed_arrays.cpp
@@ -32,7 +32,7 @@
#include "core/variant/variant.h"
-#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
#include "core/math/vector3i.h"
static_assert(sizeof(godot_packed_byte_array) == sizeof(PackedByteArray), "PackedByteArray size mismatch");
diff --git a/modules/gdnative/gdnative/rect2.cpp b/modules/gdnative/gdnative/rect2.cpp
index f4674850e3..7e0ce76c26 100644
--- a/modules/gdnative/gdnative/rect2.cpp
+++ b/modules/gdnative/gdnative/rect2.cpp
@@ -31,6 +31,8 @@
#include "gdnative/rect2.h"
#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
+#include "core/os/memory.h"
static_assert(sizeof(godot_rect2) == sizeof(Rect2), "Rect2 size mismatch");
static_assert(sizeof(godot_rect2i) == sizeof(Rect2i), "Rect2i size mismatch");
diff --git a/modules/gdnative/gdnative/transform2d.cpp b/modules/gdnative/gdnative/transform2d.cpp
index 45ba790dc1..7dc07024e5 100644
--- a/modules/gdnative/gdnative/transform2d.cpp
+++ b/modules/gdnative/gdnative/transform2d.cpp
@@ -31,6 +31,7 @@
#include "gdnative/transform2d.h"
#include "core/math/transform_2d.h"
+#include "core/os/memory.h"
static_assert(sizeof(godot_transform2d) == sizeof(Transform2D), "Transform2D size mismatch");
diff --git a/modules/gdnative/gdnative/vector2.cpp b/modules/gdnative/gdnative/vector2.cpp
index eb8ffd74cd..a8d4281d25 100644
--- a/modules/gdnative/gdnative/vector2.cpp
+++ b/modules/gdnative/gdnative/vector2.cpp
@@ -31,6 +31,8 @@
#include "gdnative/vector2.h"
#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
+#include "core/os/memory.h"
static_assert(sizeof(godot_vector2) == sizeof(Vector2), "Vector2 size mismatch");
static_assert(sizeof(godot_vector2i) == sizeof(Vector2i), "Vector2i size mismatch");
diff --git a/modules/gdnative/nativescript/api_generator.cpp b/modules/gdnative/nativescript/api_generator.cpp
index ddde28811c..0309d1d9c7 100644
--- a/modules/gdnative/nativescript/api_generator.cpp
+++ b/modules/gdnative/nativescript/api_generator.cpp
@@ -288,7 +288,7 @@ List<ClassAPI> generate_c_api_classes() {
String type;
String name = argument.name;
- if (argument.name.find(":") != -1) {
+ if (argument.name.contains(":")) {
type = argument.name.get_slice(":", 1);
name = argument.name.get_slice(":", 0);
} else {
@@ -324,7 +324,7 @@ List<ClassAPI> generate_c_api_classes() {
property_api.getter = ClassDB::get_property_getter(class_name, p->get().name);
property_api.setter = ClassDB::get_property_setter(class_name, p->get().name);
- if (p->get().name.find(":") != -1) {
+ if (p->get().name.contains(":")) {
property_api.type = p->get().name.get_slice(":", 1);
property_api.name = p->get().name.get_slice(":", 0);
} else {
@@ -355,7 +355,7 @@ List<ClassAPI> generate_c_api_classes() {
//method name
method_api.method_name = method_info.name;
//method return type
- if (method_api.method_name.find(":") != -1) {
+ if (method_api.method_name.contains(":")) {
method_api.return_type = method_api.method_name.get_slice(":", 1);
method_api.method_name = method_api.method_name.get_slice(":", 0);
} else {
@@ -388,7 +388,7 @@ List<ClassAPI> generate_c_api_classes() {
arg_name = arg_info.name;
- if (arg_info.name.find(":") != -1) {
+ if (arg_info.name.contains(":")) {
arg_type = arg_info.name.get_slice(":", 1);
arg_name = arg_info.name.get_slice(":", 0);
} else if (arg_info.hint == PROPERTY_HINT_RESOURCE_TYPE) {
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 5cc295bbab..ac6684a29c 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -33,18 +33,6 @@
#include "../gdscript_tokenizer.h"
#include "editor/editor_settings.h"
-static bool _is_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
-}
-
-static bool _is_hex_symbol(char32_t c) {
- return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
-static bool _is_bin_symbol(char32_t c) {
- return (c == '0' || c == '1');
-}
-
Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
Dictionary color_map;
@@ -102,7 +90,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
color = font_color;
bool is_char = !is_symbol(str[j]);
bool is_a_symbol = is_symbol(str[j]);
- bool is_number = (str[j] >= '0' && str[j] <= '9');
+ bool is_number = is_digit(str[j]);
/* color regions */
if (is_a_symbol || in_region != -1) {
@@ -241,14 +229,14 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
// allow ABCDEF in hex notation
- if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) {
+ if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) {
is_number = true;
} else {
is_hex_notation = false;
}
// disallow anything not a 0 or 1
- if (is_bin_notation && (_is_bin_symbol(str[j]))) {
+ if (is_bin_notation && (is_binary_digit(str[j]))) {
is_number = true;
} else if (is_bin_notation) {
is_bin_notation = false;
@@ -270,7 +258,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
}
- if (!in_word && _is_char(str[j]) && !is_number) {
+ if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) {
in_word = true;
}
@@ -585,7 +573,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
if (E.usage & PROPERTY_USAGE_CATEGORY || E.usage & PROPERTY_USAGE_GROUP || E.usage & PROPERTY_USAGE_SUBGROUP) {
continue;
}
- if (name.find("/") != -1) {
+ if (name.contains("/")) {
continue;
}
member_keywords[name] = member_variable_color;
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 84db97625b..a80874d785 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -827,7 +827,7 @@ Error GDScript::reload(bool p_keep_state) {
return OK;
}
#else
- if (source.find("_BASE_") != -1) {
+ if (source.contains("_BASE_")) {
return OK;
}
#endif
@@ -2022,8 +2022,6 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"preload",
"signal",
"super",
- "trait",
- "yield",
// var
"const",
"enum",
@@ -2040,6 +2038,11 @@ void GDScriptLanguage::get_reserved_words(List<String> *p_words) const {
"return",
"match",
"while",
+ // These keywords are not implemented currently, but reserved for (potential) future use.
+ // We highlight them as keywords to make errors easier to understand.
+ "trait",
+ "namespace",
+ "yield",
nullptr
};
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index d11174227a..9ff52347e9 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -108,7 +108,7 @@ static GDScriptParser::DataType make_native_enum_type(const StringName &p_native
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
type.kind = GDScriptParser::DataType::ENUM;
- type.builtin_type = Variant::OBJECT;
+ type.builtin_type = Variant::INT;
type.is_constant = true;
type.is_meta_type = true;
@@ -650,9 +650,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas
datatype = specified_type;
if (member.variable->initializer != nullptr) {
- if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true)) {
+ if (!is_type_compatible(datatype, member.variable->initializer->get_datatype(), true, member.variable->initializer)) {
// Try reverse test since it can be a masked subtype.
- if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true)) {
+ if (!is_type_compatible(member.variable->initializer->get_datatype(), datatype, true, member.variable->initializer)) {
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", member.variable->initializer->get_datatype().to_string(), datatype.to_string()), member.variable->initializer);
} else {
// TODO: Add warning.
@@ -1400,9 +1400,9 @@ void GDScriptAnalyzer::resolve_variable(GDScriptParser::VariableNode *p_variable
type.is_meta_type = false;
if (p_variable->initializer != nullptr) {
- if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true)) {
+ if (!is_type_compatible(type, p_variable->initializer->get_datatype(), true, p_variable->initializer)) {
// Try reverse test since it can be a masked subtype.
- if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true)) {
+ if (!is_type_compatible(p_variable->initializer->get_datatype(), type, true, p_variable->initializer)) {
push_error(vformat(R"(Value of type "%s" cannot be assigned to a variable of type "%s".)", p_variable->initializer->get_datatype().to_string(), type.to_string()), p_variable->initializer);
} else {
// TODO: Add warning.
@@ -1877,11 +1877,11 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
if (!assignee_type.is_variant() && assigned_value_type.is_hard_type()) {
if (compatible) {
- compatible = is_type_compatible(assignee_type, op_type, true);
+ compatible = is_type_compatible(assignee_type, op_type, true, p_assignment->assigned_value);
if (!compatible) {
if (assignee_type.is_hard_type()) {
// Try reverse test since it can be a masked subtype.
- if (!is_type_compatible(op_type, assignee_type, true)) {
+ if (!is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) {
push_error(vformat(R"(Cannot assign a value of type "%s" to a target of type "%s".)", assigned_value_type.to_string(), assignee_type.to_string()), p_assignment->assigned_value);
} else {
// TODO: Add warning.
@@ -2416,6 +2416,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
validate_call_arg(par_types, default_arg_count, is_vararg, p_call);
+ if (base_type.kind == GDScriptParser::DataType::ENUM && base_type.is_meta_type) {
+ // Enum type is treated as a dictionary value for function calls.
+ base_type.is_meta_type = false;
+ }
+
if (is_self && parser->current_function != nullptr && parser->current_function->is_static && !is_static) {
push_error(vformat(R"*(Cannot call non-static function "%s()" from static function "%s()".)*", p_call->function_name, parser->current_function->identifier->name), p_call->callee);
} else if (!is_self && base_type.is_meta_type && !is_static) {
@@ -2474,17 +2479,24 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
GDScriptParser::DataType cast_type = resolve_datatype(p_cast->cast_type);
if (!cast_type.is_set()) {
+ mark_node_unsafe(p_cast);
return;
}
- cast_type.is_meta_type = false; // The casted value won't be a type name.
+ cast_type = type_from_metatype(cast_type); // The casted value won't be a type name.
p_cast->set_datatype(cast_type);
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
if (!op_type.is_variant()) {
bool valid = false;
- if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
+ if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) {
+ // Enum types are compatible between each other, so it's a safe cast.
+ valid = true;
+ } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
+ // Convertint int to enum is always valid.
+ valid = true;
+ } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && cast_type.kind == GDScriptParser::DataType::BUILTIN) {
valid = Variant::can_convert(op_type.builtin_type, cast_type.builtin_type);
} else if (op_type.kind != GDScriptParser::DataType::BUILTIN && cast_type.kind != GDScriptParser::DataType::BUILTIN) {
valid = is_type_compatible(cast_type, op_type) || is_type_compatible(op_type, cast_type);
@@ -2586,6 +2598,34 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
const StringName &name = p_identifier->name;
+ if (base.kind == GDScriptParser::DataType::ENUM) {
+ if (base.is_meta_type) {
+ if (base.enum_values.has(name)) {
+ p_identifier->is_constant = true;
+ p_identifier->reduced_value = base.enum_values[name];
+
+ GDScriptParser::DataType result;
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::ENUM;
+ result.is_constant = true;
+ result.builtin_type = Variant::INT;
+ result.native_type = base.native_type;
+ result.enum_type = base.enum_type;
+ p_identifier->set_datatype(result);
+ return;
+ } else {
+ // Consider as a Dictionary, so it can be anything.
+ // This will be evaluated in the next if block.
+ base.kind = GDScriptParser::DataType::BUILTIN;
+ base.builtin_type = Variant::DICTIONARY;
+ base.is_meta_type = false;
+ }
+ } else {
+ push_error(R"(Cannot get property from enum value.)", p_identifier);
+ return;
+ }
+ }
+
if (base.kind == GDScriptParser::DataType::BUILTIN) {
if (base.is_meta_type) {
bool valid = true;
@@ -2632,32 +2672,6 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod
return;
}
- if (base.kind == GDScriptParser::DataType::ENUM) {
- if (base.is_meta_type) {
- if (base.enum_values.has(name)) {
- p_identifier->is_constant = true;
- p_identifier->reduced_value = base.enum_values[name];
-
- GDScriptParser::DataType result;
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::ENUM_VALUE;
- result.is_constant = true;
- result.builtin_type = Variant::INT;
- result.native_type = base.native_type;
- result.enum_type = name;
- p_identifier->set_datatype(result);
- } else {
- // Consider as a Dictionary
- GDScriptParser::DataType dummy;
- dummy.kind = GDScriptParser::DataType::VARIANT;
- p_identifier->set_datatype(dummy);
- }
- } else {
- push_error(R"(Cannot get property from enum value.)", p_identifier);
- }
- return;
- }
-
GDScriptParser::ClassNode *base_class = base.class_type;
// TODO: Switch current class/function/suite here to avoid misrepresenting identifiers (in recursive reduce calls).
@@ -2793,7 +2807,7 @@ void GDScriptAnalyzer::reduce_identifier(GDScriptParser::IdentifierNode *p_ident
if (element.identifier->name == p_identifier->name) {
GDScriptParser::DataType type;
type.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM_VALUE : GDScriptParser::DataType::BUILTIN;
+ type.kind = element.parent_enum->identifier ? GDScriptParser::DataType::ENUM : GDScriptParser::DataType::BUILTIN;
type.builtin_type = Variant::INT;
type.is_constant = true;
if (element.parent_enum->identifier) {
@@ -3493,6 +3507,9 @@ GDScriptParser::DataType GDScriptAnalyzer::type_from_metatype(const GDScriptPars
GDScriptParser::DataType result = p_meta_type;
result.is_meta_type = false;
result.is_constant = false;
+ if (p_meta_type.kind == GDScriptParser::DataType::ENUM) {
+ result.builtin_type = Variant::INT;
+ }
return result;
}
@@ -3549,6 +3566,18 @@ bool GDScriptAnalyzer::get_function_signature(GDScriptParser::CallNode *p_source
r_default_arg_count = 0;
StringName function_name = p_function;
+ if (p_base_type.kind == GDScriptParser::DataType::ENUM) {
+ if (p_base_type.is_meta_type) {
+ // Enum type can be treated as a dictionary value.
+ p_base_type.kind = GDScriptParser::DataType::BUILTIN;
+ p_base_type.builtin_type = Variant::DICTIONARY;
+ p_base_type.is_meta_type = false;
+ } else {
+ push_error("Cannot call function on enum value.", p_source);
+ return false;
+ }
+ }
+
if (p_base_type.kind == GDScriptParser::DataType::BUILTIN) {
// Construct a base type to get methods.
Callable::CallError err;
@@ -3799,6 +3828,22 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
Variant::Type a_type = p_a.builtin_type;
Variant::Type b_type = p_b.builtin_type;
+
+ if (p_a.kind == GDScriptParser::DataType::ENUM) {
+ if (p_a.is_meta_type) {
+ a_type = Variant::DICTIONARY;
+ } else {
+ a_type = Variant::INT;
+ }
+ }
+ if (p_b.kind == GDScriptParser::DataType::ENUM) {
+ if (p_b.is_meta_type) {
+ b_type = Variant::DICTIONARY;
+ } else {
+ b_type = Variant::INT;
+ }
+ }
+
Variant::ValidatedOperatorEvaluator op_eval = Variant::get_validated_operator_evaluator(p_operation, a_type, b_type);
bool hard_operation = p_a.is_hard_type() && p_b.is_hard_type();
@@ -3828,7 +3873,7 @@ GDScriptParser::DataType GDScriptAnalyzer::get_operation_type(Variant::Operator
}
// TODO: Add safe/unsafe return variable (for variant cases)
-bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion) const {
+bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion, const GDScriptParser::Node *p_source_node) {
// These return "true" so it doesn't affect users negatively.
ERR_FAIL_COND_V_MSG(!p_target.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset target type");
ERR_FAIL_COND_V_MSG(!p_source.is_set(), true, "Parser bug (please report): Trying to check compatibility of unset value type");
@@ -3848,7 +3893,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
if (!valid && p_allow_implicit_conversion) {
valid = Variant::can_convert_strict(p_source.builtin_type, p_target.builtin_type);
}
- if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
+ if (!valid && p_target.builtin_type == Variant::INT && p_source.kind == GDScriptParser::DataType::ENUM && !p_source.is_meta_type) {
// Enum value is also integer.
valid = true;
}
@@ -3869,6 +3914,11 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
if (p_target.kind == GDScriptParser::DataType::ENUM) {
if (p_source.kind == GDScriptParser::DataType::BUILTIN && p_source.builtin_type == Variant::INT) {
+#ifdef DEBUG_ENABLED
+ if (p_source_node) {
+ parser->push_warning(p_source_node, GDScriptWarning::INT_ASSIGNED_TO_ENUM);
+ }
+#endif
return true;
}
if (p_source.kind == GDScriptParser::DataType::ENUM) {
@@ -3876,11 +3926,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
return true;
}
}
- if (p_source.kind == GDScriptParser::DataType::ENUM_VALUE) {
- if (p_source.native_type == p_target.native_type && p_target.enum_values.has(p_source.enum_type)) {
- return true;
- }
- }
return false;
}
@@ -3935,7 +3980,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
case GDScriptParser::DataType::VARIANT:
case GDScriptParser::DataType::BUILTIN:
case GDScriptParser::DataType::ENUM:
- case GDScriptParser::DataType::ENUM_VALUE:
case GDScriptParser::DataType::UNRESOLVED:
break; // Already solved before.
}
@@ -3972,7 +4016,6 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
case GDScriptParser::DataType::VARIANT:
case GDScriptParser::DataType::BUILTIN:
case GDScriptParser::DataType::ENUM:
- case GDScriptParser::DataType::ENUM_VALUE:
case GDScriptParser::DataType::UNRESOLVED:
break; // Already solved before.
}
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 4cee5cb44a..2697a6ec2b 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -112,7 +112,7 @@ class GDScriptAnalyzer {
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, const GDScriptParser::DataType &p_b, bool &r_valid, const GDScriptParser::Node *p_source);
GDScriptParser::DataType get_operation_type(Variant::Operator p_operation, const GDScriptParser::DataType &p_a, bool &r_valid, const GDScriptParser::Node *p_source);
void update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal);
- bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false) const;
+ bool is_type_compatible(const GDScriptParser::DataType &p_target, const GDScriptParser::DataType &p_source, bool p_allow_implicit_conversion = false, const GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin);
void mark_node_unsafe(const GDScriptParser::Node *p_node);
bool class_exists(const StringName &p_class) const;
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index ca125d3a07..108c988add 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -141,10 +141,13 @@ GDScriptDataType GDScriptCompiler::_gdtype_from_datatype(const GDScriptParser::D
}
} break;
case GDScriptParser::DataType::ENUM:
- case GDScriptParser::DataType::ENUM_VALUE:
result.has_type = true;
result.kind = GDScriptDataType::BUILTIN;
- result.builtin_type = Variant::INT;
+ if (p_datatype.is_meta_type) {
+ result.builtin_type = Variant::DICTIONARY;
+ } else {
+ result.builtin_type = Variant::INT;
+ }
break;
case GDScriptParser::DataType::UNRESOLVED: {
ERR_PRINT("Parser bug: converting unresolved type.");
@@ -469,7 +472,14 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code
} break;
case GDScriptParser::Node::CAST: {
const GDScriptParser::CastNode *cn = static_cast<const GDScriptParser::CastNode *>(p_expression);
- GDScriptDataType cast_type = _gdtype_from_datatype(cn->cast_type->get_datatype());
+ GDScriptParser::DataType og_cast_type = cn->cast_type->get_datatype();
+ GDScriptDataType cast_type = _gdtype_from_datatype(og_cast_type);
+
+ if (og_cast_type.kind == GDScriptParser::DataType::ENUM) {
+ // Enum types are usually treated as dictionaries, but in this case we want to cast to an integer.
+ cast_type.kind = GDScriptDataType::BUILTIN;
+ cast_type.builtin_type = Variant::INT;
+ }
// Create temporary for result first since it will be deleted last.
GDScriptCodeGenerator::Address result = codegen.add_temporary(cast_type);
diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp
index d10e120410..33a88dd2dd 100644
--- a/modules/gdscript/gdscript_editor.cpp
+++ b/modules/gdscript/gdscript_editor.cpp
@@ -486,7 +486,7 @@ struct GDScriptCompletionIdentifier {
static String _get_visual_datatype(const PropertyInfo &p_info, bool p_is_arg = true) {
if (p_info.usage & PROPERTY_USAGE_CLASS_IS_ENUM) {
String enum_name = p_info.class_name;
- if (enum_name.find(".") == -1) {
+ if (!enum_name.contains(".")) {
return enum_name;
}
return enum_name.get_slice(".", 1);
@@ -610,7 +610,7 @@ static String _make_arguments_hint(const GDScriptParser::FunctionNode *p_functio
case GDScriptParser::Node::SUBSCRIPT: {
const GDScriptParser::SubscriptNode *sub = static_cast<const GDScriptParser::SubscriptNode *>(par->default_value);
if (sub->is_constant) {
- if (sub->datatype.kind == GDScriptParser::DataType::ENUM_VALUE) {
+ if (sub->datatype.kind == GDScriptParser::DataType::ENUM) {
def_val = sub->get_datatype().to_string();
} else if (sub->reduced) {
const Variant::Type vt = sub->reduced_value.get_type();
@@ -955,7 +955,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
if (E.usage & (PROPERTY_USAGE_GROUP | PROPERTY_USAGE_CATEGORY)) {
continue;
}
- if (E.name.find("/") != -1) {
+ if (E.name.contains("/")) {
continue;
}
ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER);
@@ -1000,7 +1000,7 @@ static void _find_identifiers_in_base(const GDScriptCompletionIdentifier &p_base
}
for (const PropertyInfo &E : members) {
- if (String(E.name).find("/") == -1) {
+ if (!String(E.name).contains("/")) {
ScriptCodeCompletionOption option(E.name, ScriptCodeCompletionOption::KIND_MEMBER);
r_result.insert(option.display, option);
}
@@ -2182,7 +2182,7 @@ static bool _guess_method_return_type_from_base(GDScriptParser::CompletionContex
}
static void _find_enumeration_candidates(GDScriptParser::CompletionContext &p_context, const String &p_enum_hint, Map<String, ScriptCodeCompletionOption> &r_result) {
- if (p_enum_hint.find(".") == -1) {
+ if (!p_enum_hint.contains(".")) {
// Global constant or in the current class.
StringName current_enum = p_enum_hint;
if (p_context.current_class && p_context.current_class->has_member(current_enum) && p_context.current_class->get_member(current_enum).type == GDScriptParser::ClassNode::Member::ENUM) {
@@ -2291,7 +2291,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
}
}
- if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().find("action") != -1) {
+ if (p_argidx == 0 && method_args > 0 && ClassDB::is_parent_class(class_name, "InputEvent") && p_method.operator String().contains("action")) {
// Get input actions
List<PropertyInfo> props;
ProjectSettings::get_singleton()->get_property_list(&props);
@@ -2639,7 +2639,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
ClassDB::get_virtual_methods(class_name, &virtual_methods);
for (const MethodInfo &mi : virtual_methods) {
String method_hint = mi.name;
- if (method_hint.find(":") != -1) {
+ if (method_hint.contains(":")) {
method_hint = method_hint.get_slice(":", 0);
}
method_hint += "(";
@@ -2650,7 +2650,7 @@ static void _find_call_arguments(GDScriptParser::CompletionContext &p_context, c
method_hint += ", ";
}
String arg = mi.arguments[i].name;
- if (arg.find(":") != -1) {
+ if (arg.contains(":")) {
arg = arg.substr(0, arg.find(":"));
}
method_hint += arg;
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 460bd85a86..cfad832a6c 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -3740,8 +3740,6 @@ String GDScriptParser::DataType::to_string() const {
}
case ENUM:
return enum_type.operator String() + " (enum)";
- case ENUM_VALUE:
- return enum_type.operator String() + " (enum value)";
case UNRESOLVED:
return "<unresolved type>";
}
diff --git a/modules/gdscript/gdscript_parser.h b/modules/gdscript/gdscript_parser.h
index e4311d2d5e..c09b07282f 100644
--- a/modules/gdscript/gdscript_parser.h
+++ b/modules/gdscript/gdscript_parser.h
@@ -106,8 +106,7 @@ public:
NATIVE,
SCRIPT,
CLASS, // GDScript.
- ENUM, // Full enumeration.
- ENUM_VALUE, // Value from enumeration.
+ ENUM, // Enumeration.
VARIANT, // Can be any type.
UNRESOLVED,
};
@@ -185,8 +184,6 @@ public:
return builtin_type == p_other.builtin_type;
case NATIVE:
case ENUM:
- return native_type == p_other.native_type;
- case ENUM_VALUE:
return native_type == p_other.native_type && enum_type == p_other.enum_type;
case SCRIPT:
return script_type == p_other.script_type;
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index 9977b88aa1..d3287ab345 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -312,22 +312,6 @@ GDScriptTokenizer::Token GDScriptTokenizer::pop_error() {
return error;
}
-static bool _is_alphanumeric(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
-}
-
-static bool _is_digit(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
-static bool _is_hex_digit(char32_t c) {
- return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
-}
-
-static bool _is_binary_digit(char32_t c) {
- return (c == '0' || c == '1');
-}
-
GDScriptTokenizer::Token GDScriptTokenizer::make_token(Token::Type p_type) {
Token token(p_type);
token.start_line = start_line;
@@ -448,10 +432,10 @@ GDScriptTokenizer::Token GDScriptTokenizer::check_vcs_marker(char32_t p_test, To
}
GDScriptTokenizer::Token GDScriptTokenizer::annotation() {
- if (!_is_alphanumeric(_peek())) {
+ if (!is_ascii_identifier_char(_peek())) {
push_error("Expected annotation identifier after \"@\".");
}
- while (_is_alphanumeric(_peek())) {
+ while (is_ascii_identifier_char(_peek())) {
// Consume all identifier characters.
_advance();
}
@@ -526,7 +510,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#define MAX_KEYWORD_LENGTH 10
// Consume all alphanumeric characters.
- while (_is_alphanumeric(_peek())) {
+ while (is_ascii_identifier_char(_peek())) {
_advance();
}
@@ -612,7 +596,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
bool has_decimal = false;
bool has_exponent = false;
bool has_error = false;
- bool (*digit_check_func)(char32_t) = _is_digit;
+ bool (*digit_check_func)(char32_t) = is_digit;
if (_peek(-1) == '.') {
has_decimal = true;
@@ -620,20 +604,20 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
if (_peek() == 'x') {
// Hexadecimal.
base = 16;
- digit_check_func = _is_hex_digit;
+ digit_check_func = is_hex_digit;
_advance();
} else if (_peek() == 'b') {
// Binary.
base = 2;
- digit_check_func = _is_binary_digit;
+ digit_check_func = is_binary_digit;
_advance();
}
}
// Allow '_' to be used in a number, for readability.
bool previous_was_underscore = false;
- while (digit_check_func(_peek()) || _peek() == '_') {
- if (_peek() == '_') {
+ while (digit_check_func(_peek()) || is_underscore(_peek())) {
+ if (is_underscore(_peek())) {
if (previous_was_underscore) {
Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
error.start_column = column;
@@ -682,7 +666,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
_advance();
// Consume decimal digits.
- while (_is_digit(_peek()) || _peek() == '_') {
+ while (is_digit(_peek()) || is_underscore(_peek())) {
_advance();
}
}
@@ -696,7 +680,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
_advance();
}
// Consume exponent digits.
- if (!_is_digit(_peek())) {
+ if (!is_digit(_peek())) {
Token error = make_error(R"(Expected exponent value after "e".)");
error.start_column = column;
error.leftmost_column = column;
@@ -705,8 +689,8 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
push_error(error);
}
previous_was_underscore = false;
- while (_is_digit(_peek()) || _peek() == '_') {
- if (_peek() == '_') {
+ while (is_digit(_peek()) || is_underscore(_peek())) {
+ if (is_underscore(_peek())) {
if (previous_was_underscore) {
Token error = make_error(R"(Only one underscore can be used as a numeric separator.)");
error.start_column = column;
@@ -733,7 +717,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::number() {
error.rightmost_column = column + 1;
push_error(error);
has_error = true;
- } else if (_is_alphanumeric(_peek())) {
+ } else if (is_ascii_identifier_char(_peek())) {
// Letter at the end of the number.
push_error("Invalid numeric notation.");
}
@@ -865,7 +849,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::string() {
char32_t digit = _peek();
char32_t value = 0;
- if (digit >= '0' && digit <= '9') {
+ if (is_digit(digit)) {
value = digit - '0';
} else if (digit >= 'a' && digit <= 'f') {
value = digit - 'a';
@@ -1322,9 +1306,9 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
line_continuation = false;
- if (_is_digit(c)) {
+ if (is_digit(c)) {
return number();
- } else if (_is_alphanumeric(c)) {
+ } else if (is_ascii_identifier_char(c)) {
return potential_identifier();
}
@@ -1392,7 +1376,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::scan() {
if (_peek() == '.') {
_advance();
return make_token(Token::PERIOD_PERIOD);
- } else if (_is_digit(_peek())) {
+ } else if (is_digit(_peek())) {
// Number starting with '.'.
return number();
} else {
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index 73536f5f8e..ad96e36640 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -152,6 +152,9 @@ String GDScriptWarning::get_message() const {
CHECK_SYMBOLS(3);
return vformat(R"(The %s '%s' has the same name as a %s.)", symbols[0], symbols[1], symbols[2]);
}
+ case INT_ASSIGNED_TO_ENUM: {
+ return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
+ }
case WARNING_MAX:
break; // Can't happen, but silences warning
}
@@ -199,6 +202,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"REDUNDANT_AWAIT",
"EMPTY_FILE",
"SHADOWED_GLOBAL_IDENTIFIER",
+ "INT_ASSIGNED_TO_ENUM",
};
static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names.");
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 112b40781a..82efe3568f 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -70,6 +70,7 @@ public:
REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine).
EMPTY_FILE, // A script file is empty.
SHADOWED_GLOBAL_IDENTIFIER, // A global class or function has the same name as variable.
+ INT_ASSIGNED_TO_ENUM, // An integer value was assigned to an enum-typed variable without casting.
WARNING_MAX,
};
diff --git a/modules/gdscript/language_server/gdscript_extend_parser.cpp b/modules/gdscript/language_server/gdscript_extend_parser.cpp
index 49f5303ae6..17886181d5 100644
--- a/modules/gdscript/language_server/gdscript_extend_parser.cpp
+++ b/modules/gdscript/language_server/gdscript_extend_parser.cpp
@@ -541,7 +541,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
for (int c = p_position.character; c >= 0; c--) {
start_pos = c;
char32_t ch = line[c];
- bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ bool valid_char = is_ascii_identifier_char(ch);
if (!valid_char) {
break;
}
@@ -550,7 +550,7 @@ String ExtendGDScriptParser::get_identifier_under_position(const lsp::Position &
int end_pos = p_position.character;
for (int c = p_position.character; c < line.length(); c++) {
char32_t ch = line[c];
- bool valid_char = (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_';
+ bool valid_char = is_ascii_identifier_char(ch);
if (!valid_char) {
break;
}
diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp
index ed879b088a..d20b243616 100644
--- a/modules/gdscript/language_server/gdscript_workspace.cpp
+++ b/modules/gdscript/language_server/gdscript_workspace.cpp
@@ -63,7 +63,7 @@ void GDScriptWorkspace::apply_new_signal(Object *obj, String function, PackedStr
String function_signature = "func " + function;
String source = script->get_source_code();
- if (source.find(function_signature) != -1) {
+ if (source.contains(function_signature)) {
return;
}
@@ -380,7 +380,7 @@ Error GDScriptWorkspace::initialize() {
symbol.children.push_back(symbol_arg);
}
- if (data.qualifiers.find("vararg") != -1) {
+ if (data.qualifiers.contains("vararg")) {
params += params.is_empty() ? "..." : ", ...";
}
diff --git a/modules/gdscript/language_server/lsp.hpp b/modules/gdscript/language_server/lsp.hpp
index 8e503a9df9..a63f9df918 100644
--- a/modules/gdscript/language_server/lsp.hpp
+++ b/modules/gdscript/language_server/lsp.hpp
@@ -1940,7 +1940,7 @@ static String marked_documentation(const String &p_bbcode) {
line = "\t" + line.substr(code_block_indent, line.length());
}
- if (in_code_block && line.find("[/codeblock]") != -1) {
+ if (in_code_block && line.contains("[/codeblock]")) {
line = "\n";
in_code_block = false;
}
diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp
index 47772b8039..73a424dae4 100644
--- a/modules/gdscript/tests/gdscript_test_runner.cpp
+++ b/modules/gdscript/tests/gdscript_test_runner.cpp
@@ -134,12 +134,14 @@ GDScriptTestRunner::GDScriptTestRunner(const String &p_source_dir, bool p_init_l
if (do_init_languages) {
init_language(p_source_dir);
}
+#ifdef DEBUG_ENABLED
// Enable all warnings for GDScript, so we can test them.
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/enable", true);
for (int i = 0; i < (int)GDScriptWarning::WARNING_MAX; i++) {
String warning = GDScriptWarning::get_name_from_code((GDScriptWarning::Code)i).to_lower();
ProjectSettings::get_singleton()->set_setting("debug/gdscript/warnings/" + warning, true);
}
+#endif
// Enable printing to show results
_print_line_enabled = true;
@@ -153,6 +155,21 @@ GDScriptTestRunner::~GDScriptTestRunner() {
}
}
+#ifndef DEBUG_ENABLED
+static String strip_warnings(const String &p_expected) {
+ // On release builds we don't have warnings. Here we remove them from the output before comparison
+ // so it doesn't fail just because of difference in warnings.
+ String expected_no_warnings;
+ for (String line : p_expected.split("\n")) {
+ if (line.begins_with(">> ")) {
+ continue;
+ }
+ expected_no_warnings += line + "\n";
+ }
+ return expected_no_warnings.strip_edges() + "\n";
+}
+#endif
+
int GDScriptTestRunner::run_tests() {
if (!make_tests()) {
FAIL("An error occurred while making the tests.");
@@ -170,6 +187,9 @@ int GDScriptTestRunner::run_tests() {
GDScriptTest::TestResult result = test.run_test();
String expected = FileAccess::get_file_as_string(test.get_output_file());
+#ifndef DEBUG_ENABLED
+ expected = strip_warnings(expected);
+#endif
INFO(test.get_source_file());
if (!result.passed) {
INFO(expected);
@@ -233,6 +253,22 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) {
}
} else {
if (next.get_extension().to_lower() == "gd") {
+#ifndef DEBUG_ENABLED
+ // On release builds, skip tests marked as debug only.
+ Error open_err = OK;
+ FileAccessRef script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err));
+ if (open_err != OK) {
+ ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next));
+ next = dir->get_next();
+ continue;
+ } else {
+ if (script_file->get_line() == "#debug-only") {
+ next = dir->get_next();
+ continue;
+ }
+ }
+#endif
+
String out_file = next.get_basename() + ".out";
if (!is_generating && !dir->file_exists(out_file)) {
ERR_FAIL_V_MSG(false, "Could not find output file for " + next);
@@ -387,6 +423,10 @@ bool GDScriptTest::check_output(const String &p_output) const {
String got = p_output.strip_edges(); // TODO: may be hacky.
got += "\n"; // Make sure to insert newline for CI static checks.
+#ifndef DEBUG_ENABLED
+ expected = strip_warnings(expected);
+#endif
+
return got == expected;
}
@@ -469,6 +509,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
return result;
}
+#ifdef DEBUG_ENABLED
StringBuilder warning_string;
for (const GDScriptWarning &E : parser.get_warnings()) {
const GDScriptWarning warning = E;
@@ -482,6 +523,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) {
warning_string.append("\n");
}
result.output += warning_string.as_string();
+#endif
// Test compiling.
GDScriptCompiler compiler;
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd
new file mode 100644
index 0000000000..928c886650
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.gd
@@ -0,0 +1,10 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+# Different enum types can't be assigned without casting.
+var class_var: MyEnum = MyEnum.ENUM_VALUE_1
+
+func test():
+ print(class_var)
+ class_var = MyOtherEnum.OTHER_ENUM_VALUE_2
+ print(class_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
new file mode 100644
index 0000000000..fde7e92f8c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_assign_with_wrong_enum_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd
new file mode 100644
index 0000000000..03a1711d7b
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.gd
@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+# Different enum types can't be assigned without casting.
+var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1
+
+func test():
+ print(class_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
new file mode 100644
index 0000000000..b1710c798d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_class_var_init_with_wrong_enum_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd
new file mode 100644
index 0000000000..d08d3dd7b2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.gd
@@ -0,0 +1,8 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func test():
+ var local_var: MyEnum = MyEnum.ENUM_VALUE_1
+ print(local_var)
+ local_var = MyOtherEnum.OTHER_ENUM_VALUE_2
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
new file mode 100644
index 0000000000..fde7e92f8c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_with_wrong_enum_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "MyOtherEnum (enum)" to a target of type "MyEnum (enum)".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd
new file mode 100644
index 0000000000..ca6d892218
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.gd
@@ -0,0 +1,6 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+func test():
+ var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
new file mode 100644
index 0000000000..b1710c798d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_init_with_wrong_enum_type.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Value of type "MyOtherEnum (enum)" cannot be assigned to a variable of type "MyEnum (enum)".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd
new file mode 100644
index 0000000000..edb785c8b6
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.gd
@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: int = MyEnum.ENUM_VALUE_1
+
+func test():
+ print(class_var)
+ class_var = MyEnum.ENUM_VALUE_2
+ print(class_var)
+
+ var local_var: int = MyEnum.ENUM_VALUE_1
+ print(local_var)
+ local_var = MyEnum.ENUM_VALUE_2
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out
new file mode 100644
index 0000000000..5f53802c33
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_enum_to_int_typed_var.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd
new file mode 100644
index 0000000000..726e4fd413
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.gd
@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: MyEnum = 0 as MyEnum
+
+func test():
+ print(class_var)
+ class_var = 1 as MyEnum
+ print(class_var)
+
+ var local_var: MyEnum = 0 as MyEnum
+ print(local_var)
+ local_var = 1 as MyEnum
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out
new file mode 100644
index 0000000000..5f53802c33
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_int_cast_to_same_enum.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd
new file mode 100644
index 0000000000..798912c987
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.gd
@@ -0,0 +1,14 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+enum MyOtherEnum { OTHER_ENUM_VALUE_1, OTHER_ENUM_VALUE_2 }
+
+var class_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum
+
+func test():
+ print(class_var)
+ class_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum
+ print(class_var)
+
+ var local_var: MyEnum = MyOtherEnum.OTHER_ENUM_VALUE_1 as MyEnum
+ print(local_var)
+ local_var = MyOtherEnum.OTHER_ENUM_VALUE_2 as MyEnum
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out
new file mode 100644
index 0000000000..5f53802c33
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_other_enum_cast_to_same_enum.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd
new file mode 100644
index 0000000000..2bfb318c3c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.gd
@@ -0,0 +1,13 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+var class_var: MyEnum = MyEnum.ENUM_VALUE_1
+
+func test():
+ print(class_var)
+ class_var = MyEnum.ENUM_VALUE_2
+ print(class_var)
+
+ var local_var: MyEnum = MyEnum.ENUM_VALUE_1
+ print(local_var)
+ local_var = MyEnum.ENUM_VALUE_2
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out
new file mode 100644
index 0000000000..5f53802c33
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_assign_same_enum.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+0
+1
+0
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd
new file mode 100644
index 0000000000..7022d14566
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.gd
@@ -0,0 +1,21 @@
+# Enum is equivalent to int for comparisons and operations.
+enum MyEnum {
+ ZERO,
+ ONE,
+ TWO,
+}
+
+enum OtherEnum {
+ ZERO,
+ ONE,
+ TWO,
+}
+
+func test():
+ print(MyEnum.ZERO == OtherEnum.ZERO)
+ print(MyEnum.ZERO == 1)
+ print(MyEnum.ZERO != OtherEnum.ONE)
+ print(MyEnum.ZERO != 0)
+
+ print(MyEnum.ONE + OtherEnum.TWO)
+ print(2 - MyEnum.ONE)
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out
new file mode 100644
index 0000000000..c8f34c11db
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_is_treated_as_int.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+true
+false
+true
+false
+3
+1
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd
new file mode 100644
index 0000000000..885d70408a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.gd
@@ -0,0 +1,13 @@
+enum MyEnum {
+ ZERO,
+ ONE,
+ TWO,
+}
+
+func test():
+ for key in MyEnum.keys():
+ prints(key, MyEnum[key])
+
+ # https://github.com/godotengine/godot/issues/55491
+ for key in MyEnum:
+ prints(key, MyEnum[key])
diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out
new file mode 100644
index 0000000000..d29f53109c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/enum_type_is_treated_as_dictionary.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+ZERO 0
+ONE 1
+TWO 2
+ZERO 0
+ONE 1
+TWO 2
diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd
new file mode 100644
index 0000000000..2be1024214
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.gd
@@ -0,0 +1,15 @@
+enum MyEnum { ENUM_VALUE_1, ENUM_VALUE_2 }
+
+# Assigning int value to enum-typed variable without explicit cast causes a warning.
+# While it is valid it may be a mistake in the assignment.
+var class_var: MyEnum = 0
+
+func test():
+ print(class_var)
+ class_var = 1
+ print(class_var)
+
+ var local_var: MyEnum = 0
+ print(local_var)
+ local_var = 1
+ print(local_var)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out
new file mode 100644
index 0000000000..eef13bbff8
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out
@@ -0,0 +1,21 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 9
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 12
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+>> WARNING
+>> Line: 14
+>> INT_ASSIGNED_TO_ENUM
+>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
+0
+1
+0
+1
diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
index 10780b5379..7b3c112fe9 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
+++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.gd
@@ -1,3 +1,4 @@
+#debug-only
func test():
var node := Node.new()
var inside_tree = node.is_inside_tree
diff --git a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out
index e585c374e2..fe48ade26b 100644
--- a/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out
+++ b/modules/gdscript/tests/scripts/runtime/errors/callable_call_after_free_object.out
@@ -2,5 +2,5 @@ GDTEST_RUNTIME_ERROR
>> SCRIPT ERROR
>> on function: test()
>> runtime/errors/callable_call_after_free_object.gd
->> 5
+>> 6
>> Attempt to call function 'null::is_inside_tree (Callable)' on a null instance.
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index 51608273a1..baa39a3b80 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -50,6 +50,7 @@
#include "core/io/file_access.h"
#include "core/io/file_access_memory.h"
#include "core/io/json.h"
+#include "core/io/stream_peer.h"
#include "core/math/disjoint_set.h"
#include "core/math/vector2.h"
#include "core/variant/dictionary.h"
@@ -6277,7 +6278,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
p_track.rotation_track.values.write[key_i] = rotation;
}
} else if (track_type == Animation::TYPE_VALUE) {
- if (path.find(":position") != -1) {
+ if (path.contains(":position")) {
p_track.position_track.times = times;
p_track.position_track.interpolation = gltf_interpolation;
@@ -6288,7 +6289,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
Vector3 position = p_animation->track_get_key_value(p_track_i, key_i);
p_track.position_track.values.write[key_i] = position;
}
- } else if (path.find(":rotation") != -1) {
+ } else if (path.contains(":rotation")) {
p_track.rotation_track.times = times;
p_track.rotation_track.interpolation = gltf_interpolation;
@@ -6299,7 +6300,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian);
}
- } else if (path.find(":scale") != -1) {
+ } else if (path.contains(":scale")) {
p_track.scale_track.times = times;
p_track.scale_track.interpolation = gltf_interpolation;
@@ -6312,7 +6313,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
}
}
} else if (track_type == Animation::TYPE_BEZIER) {
- if (path.find("/scale") != -1) {
+ if (path.contains("/scale")) {
const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
if (!p_track.scale_track.times.size()) {
Vector<real_t> new_times;
@@ -6333,16 +6334,16 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
for (int32_t key_i = 0; key_i < keys; key_i++) {
Vector3 bezier_track = p_track.scale_track.values[key_i];
- if (path.find("/scale:x") != -1) {
+ if (path.contains("/scale:x")) {
bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.find("/scale:y") != -1) {
+ } else if (path.contains("/scale:y")) {
bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.find("/scale:z") != -1) {
+ } else if (path.contains("/scale:z")) {
bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
}
p_track.scale_track.values.write[key_i] = bezier_track;
}
- } else if (path.find("/position") != -1) {
+ } else if (path.contains("/position")) {
const int32_t keys = p_animation->track_get_key_time(p_track_i, key_count - 1) * BAKE_FPS;
if (!p_track.position_track.times.size()) {
Vector<real_t> new_times;
@@ -6359,11 +6360,11 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
for (int32_t key_i = 0; key_i < keys; key_i++) {
Vector3 bezier_track = p_track.position_track.values[key_i];
- if (path.find("/position:x") != -1) {
+ if (path.contains("/position:x")) {
bezier_track.x = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.find("/position:y") != -1) {
+ } else if (path.contains("/position:y")) {
bezier_track.y = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
- } else if (path.find("/position:z") != -1) {
+ } else if (path.contains("/position:z")) {
bezier_track.z = p_animation->bezier_track_interpolate(p_track_i, key_i / BAKE_FPS);
}
p_track.position_track.values.write[key_i] = bezier_track;
@@ -6384,7 +6385,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
continue;
}
String orig_track_path = animation->track_get_path(track_i);
- if (String(orig_track_path).find(":position") != -1) {
+ if (String(orig_track_path).contains(":position")) {
const Vector<String> node_suffix = String(orig_track_path).split(":position");
const NodePath path = node_suffix[0];
const Node *node = ap->get_parent()->get_node_or_null(path);
@@ -6400,7 +6401,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).find(":rotation_degrees") != -1) {
+ } else if (String(orig_track_path).contains(":rotation_degrees")) {
const Vector<String> node_suffix = String(orig_track_path).split(":rotation_degrees");
const NodePath path = node_suffix[0];
const Node *node = ap->get_parent()->get_node_or_null(path);
@@ -6416,7 +6417,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).find(":scale") != -1) {
+ } else if (String(orig_track_path).contains(":scale")) {
const Vector<String> node_suffix = String(orig_track_path).split(":scale");
const NodePath path = node_suffix[0];
const Node *node = ap->get_parent()->get_node_or_null(path);
@@ -6432,7 +6433,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
gltf_animation->get_tracks().insert(node_index, track);
}
}
- } else if (String(orig_track_path).find(":transform") != -1) {
+ } else if (String(orig_track_path).contains(":transform")) {
const Vector<String> node_suffix = String(orig_track_path).split(":transform");
const NodePath path = node_suffix[0];
const Node *node = ap->get_parent()->get_node_or_null(path);
@@ -6443,7 +6444,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
gltf_animation->get_tracks().insert(transform_track_i.key, track);
}
}
- } else if (String(orig_track_path).find(":blend_shapes/") != -1) {
+ } else if (String(orig_track_path).contains(":blend_shapes/")) {
const Vector<String> node_suffix = String(orig_track_path).split(":blend_shapes/");
const NodePath path = node_suffix[0];
const String suffix = node_suffix[1];
@@ -6499,7 +6500,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
}
tracks[mesh_index] = track;
}
- } else if (String(orig_track_path).find(":") != -1) {
+ } else if (String(orig_track_path).contains(":")) {
//Process skeleton
const Vector<String> node_suffix = String(orig_track_path).split(":");
const String node = node_suffix[0];
@@ -6529,7 +6530,7 @@ void GLTFDocument::_convert_animation(Ref<GLTFState> state, AnimationPlayer *ap,
gltf_animation->get_tracks()[node_i] = track;
}
}
- } else if (String(orig_track_path).find(":") == -1) {
+ } else if (!String(orig_track_path).contains(":")) {
ERR_CONTINUE(!ap->get_parent());
Node *godot_node = ap->get_parent()->get_node_or_null(orig_track_path);
for (const KeyValue<GLTFNodeIndex, Node *> &scene_node_i : state->scene_nodes) {
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index 2be588cac4..2de923c125 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -103,8 +103,6 @@ private:
bool valid = false;
bool reload_invalidated = false;
- bool builtin;
-
GDMonoClass *base = nullptr;
GDMonoClass *native = nullptr;
GDMonoClass *script_class = nullptr;
diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
index ce2b378623..7a4641dbbc 100644
--- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs
@@ -47,9 +47,13 @@ namespace GodotTools.OpenVisualStudio
if (dte == null)
{
// Open a new instance
+ dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0");
- var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true);
- dte = (DTE)Activator.CreateInstance(visualStudioDteType);
+ if (dte == null)
+ {
+ // Launch of VS 2022 failed, fallback to 2019
+ dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0");
+ }
dte.UserControl = true;
@@ -133,6 +137,21 @@ namespace GodotTools.OpenVisualStudio
return 0;
}
+ private static DTE TryVisualStudioLaunch(string version)
+ {
+ try
+ {
+ var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true);
+ var dte = (DTE)Activator.CreateInstance(visualStudioDteType);
+
+ return dte;
+ }
+ catch (COMException)
+ {
+ return null;
+ }
+ }
+
private static DTE FindInstanceEditingSolution(string solutionPath)
{
if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0)
@@ -164,7 +183,7 @@ namespace GodotTools.OpenVisualStudio
continue;
// The digits after the colon are the process ID
- if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]"))
+ if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]"))
continue;
if (pprot.GetObject(moniker[0], out object ppunkObject) == 0)
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 1b4ab0ef4b..1de41821f9 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -690,11 +690,11 @@ void BindingsGenerator::_apply_prefix_to_enum_constants(BindingsGenerator::EnumI
continue;
}
- if (parts[curr_prefix_length][0] >= '0' && parts[curr_prefix_length][0] <= '9') {
+ if (is_digit(parts[curr_prefix_length][0])) {
// The name of enum constants may begin with a numeric digit when strip from the enum prefix,
// so we make the prefix for this constant one word shorter in those cases.
for (curr_prefix_length = curr_prefix_length - 1; curr_prefix_length > 0; curr_prefix_length--) {
- if (parts[curr_prefix_length][0] < '0' || parts[curr_prefix_length][0] > '9') {
+ if (!is_digit(parts[curr_prefix_length][0])) {
break;
}
}
diff --git a/modules/ogg/ogg_packet_sequence.h b/modules/ogg/ogg_packet_sequence.h
index 7e56b14a24..73e3cb4fff 100644
--- a/modules/ogg/ogg_packet_sequence.h
+++ b/modules/ogg/ogg_packet_sequence.h
@@ -104,7 +104,7 @@ class OGGPacketSequencePlayback : public RefCounted {
mutable ogg_packet *packet;
- uint64_t data_version;
+ uint64_t data_version = 0;
mutable int64_t packetno = 0;
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index e50a5337cb..c7511f587e 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -303,22 +303,6 @@ _FORCE_INLINE_ bool is_connected_to_prev(char32_t p_chr, char32_t p_pchr) {
return (prop != U_JT_RIGHT_JOINING) && (prop != U_JT_NON_JOINING) ? !is_ligature(p_pchr, p_chr) : false;
}
-_FORCE_INLINE_ bool is_control(char32_t p_char) {
- return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F);
-}
-
-_FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
- return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
-}
-
-_FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
- return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
-}
-
-_FORCE_INLINE_ bool is_underscore(char32_t p_char) {
- return (p_char == 0x005F);
-}
-
/*************************************************************************/
String TextServerAdvanced::interface_name = "ICU / HarfBuzz / Graphite";
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index dd520a2e40..182d2a02ad 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -44,30 +44,6 @@
#endif
/*************************************************************************/
-/* Character properties. */
-/*************************************************************************/
-
-_FORCE_INLINE_ bool is_control(char32_t p_char) {
- return (p_char <= 0x001f) || (p_char >= 0x007f && p_char <= 0x009F);
-}
-
-_FORCE_INLINE_ bool is_whitespace(char32_t p_char) {
- return (p_char == 0x0020) || (p_char == 0x00A0) || (p_char == 0x1680) || (p_char >= 0x2000 && p_char <= 0x200a) || (p_char == 0x202f) || (p_char == 0x205f) || (p_char == 0x3000) || (p_char == 0x2028) || (p_char == 0x2029) || (p_char >= 0x0009 && p_char <= 0x000d) || (p_char == 0x0085);
-}
-
-_FORCE_INLINE_ bool is_linebreak(char32_t p_char) {
- return (p_char >= 0x000a && p_char <= 0x000d) || (p_char == 0x0085) || (p_char == 0x2028) || (p_char == 0x2029);
-}
-
-_FORCE_INLINE_ bool is_punct(char32_t p_char) {
- return (p_char >= 0x0020 && p_char <= 0x002F) || (p_char >= 0x003A && p_char <= 0x0040) || (p_char >= 0x005B && p_char <= 0x005E) || (p_char == 0x0060) || (p_char >= 0x007B && p_char <= 0x007E) || (p_char >= 0x2000 && p_char <= 0x206F) || (p_char >= 0x3000 && p_char <= 0x303F);
-}
-
-_FORCE_INLINE_ bool is_underscore(char32_t p_char) {
- return (p_char == 0x005F);
-}
-
-/*************************************************************************/
String TextServerFallback::interface_name = "Fallback";
uint32_t TextServerFallback::interface_features = 0; // Nothing is supported.
diff --git a/modules/visual_script/editor/visual_script_editor.cpp b/modules/visual_script/editor/visual_script_editor.cpp
index b00b5a2ddd..1a7d473bd4 100644
--- a/modules/visual_script/editor/visual_script_editor.cpp
+++ b/modules/visual_script/editor/visual_script_editor.cpp
@@ -2803,7 +2803,7 @@ void VisualScriptEditor::add_callback(const String &p_function, PackedStringArra
String name = p_args[i];
Variant::Type type = Variant::NIL;
- if (name.find(":") != -1) {
+ if (name.contains(":")) {
String tt = name.get_slice(":", 1);
name = name.get_slice(":", 0);
for (int j = 0; j < Variant::VARIANT_MAX; j++) {
diff --git a/modules/visual_script/editor/visual_script_editor.h b/modules/visual_script/editor/visual_script_editor.h
index f1b01aa6dc..b01732b2fd 100644
--- a/modules/visual_script/editor/visual_script_editor.h
+++ b/modules/visual_script/editor/visual_script_editor.h
@@ -173,10 +173,10 @@ class VisualScriptEditor : public ScriptEditorBase {
String member_name;
PortAction port_action;
- int port_action_node;
- int port_action_output;
+ int port_action_node = 0;
+ int port_action_output = 0;
Vector2 port_action_pos;
- int port_action_new_node;
+ int port_action_new_node = 0;
bool saved_pos_dirty = false;
@@ -196,7 +196,7 @@ class VisualScriptEditor : public ScriptEditorBase {
int _create_new_node_from_name(const String &p_text, const Vector2 &p_point);
void _selected_new_virtual_method(const String &p_text, const String &p_category, const bool p_connecting);
- int error_line;
+ int error_line = -1;
void _node_selected(Node *p_node);
void _center_on_node(int p_id);
@@ -267,12 +267,12 @@ class VisualScriptEditor : public ScriptEditorBase {
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
- int editing_id;
- int editing_input;
+ int editing_id = 0;
+ int editing_input = 0;
- bool can_swap;
- int data_disconnect_node;
- int data_disconnect_port;
+ bool can_swap = false;
+ int data_disconnect_node = 0;
+ int data_disconnect_port = 0;
void _default_value_changed();
void _default_value_edited(Node *p_button, int p_id, int p_input_port);
diff --git a/modules/visual_script/editor/visual_script_property_selector.cpp b/modules/visual_script/editor/visual_script_property_selector.cpp
index 1059d126bc..4072dcebe5 100644
--- a/modules/visual_script/editor/visual_script_property_selector.cpp
+++ b/modules/visual_script/editor/visual_script_property_selector.cpp
@@ -379,8 +379,6 @@ void VisualScriptPropertySelector::_bind_methods() {
}
VisualScriptPropertySelector::VisualScriptPropertySelector() {
- virtuals_only = false;
-
vbox = memnew(VBoxContainer);
add_child(vbox);
diff --git a/modules/visual_script/editor/visual_script_property_selector.h b/modules/visual_script/editor/visual_script_property_selector.h
index 3970c4473e..6b5112f1af 100644
--- a/modules/visual_script/editor/visual_script_property_selector.h
+++ b/modules/visual_script/editor/visual_script_property_selector.h
@@ -96,16 +96,16 @@ class VisualScriptPropertySelector : public ConfirmationDialog {
EditorHelpBit *help_bit;
- bool properties;
- bool visual_script_generic;
- bool connecting;
+ bool properties = false;
+ bool visual_script_generic = false;
+ bool connecting = false;
String selected;
Variant::Type type;
String base_type;
String base_script;
ObjectID script;
Object *instance;
- bool virtuals_only;
+ bool virtuals_only = false;
VBoxContainer *vbox;
protected:
diff --git a/modules/visual_script/visual_script_expression.cpp b/modules/visual_script/visual_script_expression.cpp
index 17a3566ed2..e8942b9788 100644
--- a/modules/visual_script/visual_script_expression.cpp
+++ b/modules/visual_script/visual_script_expression.cpp
@@ -377,13 +377,13 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
- if (!((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))) {
+ if (!is_hex_digit(c)) {
_set_error("Malformed hex constant in string");
r_token.type = TK_ERROR;
return ERR_PARSE_ERROR;
}
char32_t v;
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
v = c - '0';
} else if (c >= 'a' && c <= 'f') {
v = c - 'a';
@@ -457,7 +457,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
break;
}
- if (cchar >= '0' && cchar <= '9') {
+ if (is_digit(cchar)) {
//a number
String num;
@@ -476,7 +476,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
while (true) {
switch (reading) {
case READING_INT: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
//pass
} else if (c == '.') {
reading = READING_DEC;
@@ -489,7 +489,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
} break;
case READING_DEC: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
} else if (c == 'e') {
reading = READING_EXP;
@@ -499,7 +499,7 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
} break;
case READING_EXP: {
- if (c >= '0' && c <= '9') {
+ if (is_digit(c)) {
exp_beg = true;
} else if ((c == '-' || c == '+') && !exp_sign && !exp_beg) {
@@ -532,11 +532,11 @@ Error VisualScriptExpression::_get_token(Token &r_token) {
}
return OK;
- } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') {
+ } else if (is_ascii_char(cchar) || cchar == '_') {
String id;
bool first = true;
- while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) {
+ while (is_ascii_char(cchar) || cchar == '_' || (!first && is_digit(cchar))) {
id += String::chr(cchar);
cchar = GET_CHAR();
first = false;
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 3d0dabc56e..b0f16337ed 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -161,6 +161,16 @@ int DisplayServerAndroid::screen_get_dpi(int p_screen) const {
return godot_io_java->get_screen_dpi();
}
+float DisplayServerAndroid::screen_get_refresh_rate(int p_screen) const {
+ GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
+ if (!godot_io_java) {
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+
+ return godot_io_java->get_screen_refresh_rate(SCREEN_REFRESH_RATE_FALLBACK);
+}
+
bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
return true;
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index e52e07bf1a..23077a6529 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -106,6 +106,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 61d2f897ef..df2d32e152 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -416,10 +416,10 @@ String EditorExportPlatformAndroid::get_package_name(const String &p_package) co
bool first = true;
for (int i = 0; i < basename.length(); i++) {
char32_t c = basename[i];
- if (c >= '0' && c <= '9' && first) {
+ if (is_digit(c) && first) {
continue;
}
- if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')) {
+ if (is_ascii_alphanumeric_char(c)) {
name += String::chr(c);
first = false;
}
@@ -462,19 +462,19 @@ bool EditorExportPlatformAndroid::is_package_name_valid(const String &p_package,
first = true;
continue;
}
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_')) {
+ if (!is_ascii_identifier_char(c)) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Android application package names."), String::chr(c));
}
return false;
}
- if (first && (c >= '0' && c <= '9')) {
+ if (first && is_digit(c)) {
if (r_error) {
*r_error = TTR("A digit cannot be the first character in a package segment.");
}
return false;
}
- if (first && c == '_') {
+ if (first && is_underscore(c)) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' cannot be the first character in a package segment."), String::chr(c));
}
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index d679fd92c0..b151e7eec1 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -226,6 +226,14 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
+ public double getScreenRefreshRate(double fallback) {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ if (display != null) {
+ return display.getRefreshRate();
+ }
+ return fallback;
+ }
+
public int[] screenGetUsableRect() {
DisplayMetrics metrics = activity.getResources().getDisplayMetrics();
Display display = activity.getWindowManager().getDefaultDisplay();
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index e0a535f16e..8a2788e848 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -53,6 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
+ _get_screen_refresh_rate = p_env->GetMethodID(cls, "getScreenRefreshRate", "(D)D");
_screen_get_usable_rect = p_env->GetMethodID(cls, "screenGetUsableRect", "()[I"),
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
_show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
@@ -136,6 +137,19 @@ int GodotIOJavaWrapper::get_screen_dpi() {
}
}
+float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
+ if (_get_screen_refresh_rate) {
+ JNIEnv *env = get_jni_env();
+ if (env == nullptr) {
+ ERR_PRINT("An error occured while trying to get screen refresh rate.");
+ return fallback;
+ }
+ return (float)env->CallDoubleMethod(godot_io_instance, _get_screen_refresh_rate, (double)fallback);
+ }
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return fallback;
+}
+
void GodotIOJavaWrapper::screen_get_usable_rect(int (&p_rect_xywh)[4]) {
if (_screen_get_usable_rect) {
JNIEnv *env = get_jni_env();
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index c96abf1101..38a2b710a9 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -51,6 +51,7 @@ private:
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
jmethodID _get_screen_DPI = 0;
+ jmethodID _get_screen_refresh_rate = 0;
jmethodID _screen_get_usable_rect = 0;
jmethodID _get_unique_id = 0;
jmethodID _show_keyboard = 0;
@@ -71,6 +72,7 @@ public:
String get_locale();
String get_model();
int get_screen_dpi();
+ float get_screen_refresh_rate(float fallback);
void screen_get_usable_rect(int (&p_rect_xywh)[4]);
String get_unique_id();
bool has_vk();
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
index 6434483641..7441550f67 100644
--- a/platform/iphone/display_server_iphone.h
+++ b/platform/iphone/display_server_iphone.h
@@ -129,6 +129,7 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<DisplayServer::WindowID> get_window_list() const override;
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
index 48bda89fc3..9491c9cf90 100644
--- a/platform/iphone/display_server_iphone.mm
+++ b/platform/iphone/display_server_iphone.mm
@@ -393,6 +393,10 @@ int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
}
}
+float DisplayServerIPhone::screen_get_refresh_rate(int p_screen) const {
+ return [UIScreen mainScreen].maximumFramesPerSecond;
+}
+
float DisplayServerIPhone::screen_get_scale(int p_screen) const {
return [UIScreen mainScreen].nativeScale;
}
diff --git a/platform/iphone/export/export_plugin.h b/platform/iphone/export/export_plugin.h
index 756bca14dd..93b23f7ee2 100644
--- a/platform/iphone/export/export_plugin.h
+++ b/platform/iphone/export/export_plugin.h
@@ -130,7 +130,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 2842fc2f5e..a0e1246c55 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -663,7 +663,7 @@ DisplayServerJavaScript::DisplayServerJavaScript(const String &p_rendering_drive
godot_js_config_canvas_id_get(canvas_id, 256);
// Handle contextmenu, webglcontextlost
- godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, p_window_mode == WINDOW_MODE_FULLSCREEN, OS::get_singleton()->is_hidpi_allowed() ? 1 : 0);
+ godot_js_display_setup_canvas(p_resolution.x, p_resolution.y, (p_window_mode == WINDOW_MODE_FULLSCREEN || p_window_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), OS::get_singleton()->is_hidpi_allowed() ? 1 : 0);
// Check if it's windows.
swap_cancel_ok = godot_js_display_is_swap_ok_cancel() == 1;
@@ -794,6 +794,10 @@ float DisplayServerJavaScript::screen_get_scale(int p_screen) const {
return godot_js_display_pixel_ratio_get();
}
+float DisplayServerJavaScript::screen_get_refresh_rate(int p_screen) const {
+ return SCREEN_REFRESH_RATE_FALLBACK; // Javascript doesn't have much of a need for the screen refresh rate, and there's no native way to do so.
+}
+
Vector<DisplayServer::WindowID> DisplayServerJavaScript::get_window_list() const {
Vector<WindowID> ret;
ret.push_back(MAIN_WINDOW_ID);
@@ -897,6 +901,7 @@ void DisplayServerJavaScript::window_set_mode(WindowMode p_mode, WindowID p_wind
}
window_mode = WINDOW_MODE_WINDOWED;
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
int result = godot_js_display_fullscreen_request();
ERR_FAIL_COND_MSG(result, "The request was denied. Remember that enabling fullscreen is only possible from an input callback for the HTML5 platform.");
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index 1ae5d68787..b50956d91c 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -139,6 +139,7 @@ public:
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1) override;
virtual void virtual_keyboard_hide() override;
diff --git a/platform/javascript/js/engine/config.js b/platform/javascript/js/engine/config.js
index 5628ddbab5..a6f9c4614c 100644
--- a/platform/javascript/js/engine/config.js
+++ b/platform/javascript/js/engine/config.js
@@ -225,34 +225,35 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
*/
Config.prototype.update = function (opts) {
const config = opts || {};
- const me = this;
- function parse(key) {
+ // NOTE: We must explicitly pass the default, accessing it via
+ // the key will fail due to closure compiler renames.
+ function parse(key, def) {
if (typeof (config[key]) === 'undefined') {
- return me[key];
+ return def;
}
return config[key];
}
// Module config
- this.unloadAfterInit = parse('unloadAfterInit');
- this.onPrintError = parse('onPrintError');
- this.onPrint = parse('onPrint');
- this.onProgress = parse('onProgress');
+ this.unloadAfterInit = parse('unloadAfterInit', this.unloadAfterInit);
+ this.onPrintError = parse('onPrintError', this.onPrintError);
+ this.onPrint = parse('onPrint', this.onPrint);
+ this.onProgress = parse('onProgress', this.onProgress);
// Godot config
- this.canvas = parse('canvas');
- this.executable = parse('executable');
- this.mainPack = parse('mainPack');
- this.locale = parse('locale');
- this.canvasResizePolicy = parse('canvasResizePolicy');
- this.persistentPaths = parse('persistentPaths');
- this.persistentDrops = parse('persistentDrops');
- this.experimentalVK = parse('experimentalVK');
- this.focusCanvas = parse('focusCanvas');
- this.gdnativeLibs = parse('gdnativeLibs');
- this.fileSizes = parse('fileSizes');
- this.args = parse('args');
- this.onExecute = parse('onExecute');
- this.onExit = parse('onExit');
+ this.canvas = parse('canvas', this.canvas);
+ this.executable = parse('executable', this.executable);
+ this.mainPack = parse('mainPack', this.mainPack);
+ this.locale = parse('locale', this.locale);
+ this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
+ this.persistentPaths = parse('persistentPaths', this.persistentPaths);
+ this.persistentDrops = parse('persistentDrops', this.persistentDrops);
+ this.experimentalVK = parse('experimentalVK', this.experimentalVK);
+ this.focusCanvas = parse('focusCanvas', this.focusCanvas);
+ this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
+ this.fileSizes = parse('fileSizes', this.fileSizes);
+ this.args = parse('args', this.args);
+ this.onExecute = parse('onExecute', this.onExecute);
+ this.onExit = parse('onExit', this.onExit);
};
/**
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 198eacd1f3..844b5616c4 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -1054,6 +1054,66 @@ int DisplayServerX11::screen_get_dpi(int p_screen) const {
return 96;
}
+float DisplayServerX11::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ //invalid screen?
+ ERR_FAIL_INDEX_V(p_screen, get_screen_count(), SCREEN_REFRESH_RATE_FALLBACK);
+
+ //Use xrandr to get screen refresh rate.
+ if (xrandr_ext_ok) {
+ XRRScreenResources *screen_info = XRRGetScreenResources(x11_display, windows[MAIN_WINDOW_ID].x11_window);
+ if (screen_info) {
+ RRMode current_mode = 0;
+ xrr_monitor_info *monitors = nullptr;
+
+ if (xrr_get_monitors) {
+ int count = 0;
+ monitors = xrr_get_monitors(x11_display, windows[MAIN_WINDOW_ID].x11_window, true, &count);
+ ERR_FAIL_INDEX_V(p_screen, count, SCREEN_REFRESH_RATE_FALLBACK);
+ } else {
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+
+ bool found_active_mode = false;
+ for (int crtc = 0; crtc < screen_info->ncrtc; crtc++) { // Loop through outputs to find which one is currently outputting.
+ XRRCrtcInfo *monitor_info = XRRGetCrtcInfo(x11_display, screen_info, screen_info->crtcs[crtc]);
+ if (monitor_info->x != monitors[p_screen].x || monitor_info->y != monitors[p_screen].y) { // If X and Y aren't the same as the monitor we're looking for, this isn't the right monitor. Continue.
+ continue;
+ }
+
+ if (monitor_info->mode != None) {
+ current_mode = monitor_info->mode;
+ found_active_mode = true;
+ break;
+ }
+ }
+
+ if (found_active_mode) {
+ for (int mode = 0; mode < screen_info->nmode; mode++) {
+ XRRModeInfo m_info = screen_info->modes[mode];
+ if (m_info.id == current_mode) {
+ return (float)m_info.dotClock / ((float)m_info.hTotal * (float)m_info.vTotal);
+ }
+ }
+ }
+
+ ERR_PRINT("An error occured while trying to get the screen refresh rate."); // We should have returned the refresh rate by now. An error must have occured.
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ } else {
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+ }
+ }
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+}
+
bool DisplayServerX11::screen_is_touchscreen(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -1825,6 +1885,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
//Remove full-screen
wd.fullscreen = false;
@@ -1877,6 +1938,7 @@ void DisplayServerX11::window_set_mode(WindowMode p_mode, WindowID p_window) {
XSendEvent(x11_display, DefaultRootWindow(x11_display), False, SubstructureRedirectMask | SubstructureNotifyMask, &xev);
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
wd.last_position_before_fs = wd.position;
@@ -2417,7 +2479,7 @@ Key DisplayServerX11::keyboard_get_keycode_from_physical(Key p_keycode) const {
Key keycode_no_mod = p_keycode & KeyModifierMask::CODE_MASK;
unsigned int xkeycode = KeyMappingX11::get_xlibcode(keycode_no_mod);
KeySym xkeysym = XkbKeycodeToKeysym(x11_display, xkeycode, 0, 0);
- if (xkeysym >= 'a' && xkeysym <= 'z') {
+ if (is_ascii_lower_case(xkeysym)) {
xkeysym -= ('a' - 'A');
}
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index c17e120db5..2d07361deb 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -303,6 +303,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
#if defined(DBUS_ENABLED)
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index 719a45c2a8..4fc733fe2c 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -230,6 +230,7 @@ public:
virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual float screen_get_max_scale() const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Vector<int> get_window_list() const override;
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index f7add5b688..000be79852 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -2203,6 +2203,24 @@ Rect2i DisplayServerOSX::screen_get_usable_rect(int p_screen) const {
return Rect2i();
}
+float DisplayServerOSX::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ if (p_screen == SCREEN_OF_MAIN_WINDOW) {
+ p_screen = window_get_current_screen();
+ }
+
+ NSArray *screenArray = [NSScreen screens];
+ if ((NSUInteger)p_screen < [screenArray count]) {
+ NSDictionary *description = [[screenArray objectAtIndex:p_screen] deviceDescription];
+ const CGDisplayModeRef displayMode = CGDisplayCopyDisplayMode([[description objectForKey:@"NSScreenNumber"] unsignedIntValue]);
+ const double displayRefreshRate = CGDisplayModeGetRefreshRate(displayMode);
+ return (float)displayRefreshRate;
+ }
+ ERR_PRINT("An error occured while trying to get the screen refresh rate.");
+ return SCREEN_REFRESH_RATE_FALLBACK;
+}
+
Vector<DisplayServer::WindowID> DisplayServerOSX::get_window_list() const {
_THREAD_SAFE_METHOD_
@@ -2652,6 +2670,7 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
case WINDOW_MODE_MINIMIZED: {
[wd.window_object deminiaturize:nil];
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
[wd.window_object setLevel:NSNormalWindowLevel];
if (wd.layered_window) {
@@ -2685,6 +2704,7 @@ void DisplayServerOSX::window_set_mode(WindowMode p_mode, WindowID p_window) {
case WINDOW_MODE_MINIMIZED: {
[wd.window_object performMiniaturize:nil];
} break;
+ case WINDOW_MODE_EXCLUSIVE_FULLSCREEN:
case WINDOW_MODE_FULLSCREEN: {
if (wd.layered_window)
_set_window_per_pixel_transparency_enabled(false, p_window);
diff --git a/platform/osx/export/export_plugin.cpp b/platform/osx/export/export_plugin.cpp
index 4d1f72f5c9..f0b58efb63 100644
--- a/platform/osx/export/export_plugin.cpp
+++ b/platform/osx/export/export_plugin.cpp
@@ -777,6 +777,24 @@ Error EditorExportPlatformOSX::export_project(const Ref<EditorExportPreset> &p_p
err = tmp_app_dir->make_dir_recursive(tmp_app_path_name + "/Contents/Resources");
}
+ Vector<String> translations = ProjectSettings::get_singleton()->get("internationalization/locale/translations");
+ if (translations.size() > 0) {
+ {
+ String fname = tmp_app_path_name + "/Contents/Resources/en.lproj";
+ tmp_app_dir->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ }
+
+ for (const String &E : translations) {
+ Ref<Translation> tr = ResourceLoader::load(E);
+ if (tr.is_valid()) {
+ String fname = tmp_app_path_name + "/Contents/Resources/" + tr->get_locale() + ".lproj";
+ tmp_app_dir->make_dir_recursive(fname);
+ FileAccessRef f = FileAccess::open(fname + "/InfoPlist.strings", FileAccess::WRITE);
+ }
+ }
+ }
+
// Now process our template.
bool found_binary = false;
Vector<String> dylibs_found;
diff --git a/platform/osx/export/export_plugin.h b/platform/osx/export/export_plugin.h
index 0c2ac90206..931ce7e41a 100644
--- a/platform/osx/export/export_plugin.h
+++ b/platform/osx/export/export_plugin.h
@@ -87,7 +87,7 @@ class EditorExportPlatformOSX : public EditorExportPlatform {
for (int i = 0; i < pname.length(); i++) {
char32_t c = pname[i];
- if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '-' || c == '.')) {
+ if (!(is_ascii_alphanumeric_char(c) || c == '-' || c == '.')) {
if (r_error) {
*r_error = vformat(TTR("The character '%s' is not allowed in Identifier."), String::chr(c));
}
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index d288c27016..b340129a16 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -326,6 +326,12 @@ typedef struct {
Rect2i rect;
} EnumRectData;
+typedef struct {
+ int count;
+ int screen;
+ float rate;
+} EnumRefreshRateData;
+
static BOOL CALLBACK _MonitorEnumProcSize(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
EnumSizeData *data = (EnumSizeData *)dwData;
if (data->count == data->screen) {
@@ -363,6 +369,26 @@ static BOOL CALLBACK _MonitorEnumProcUsableSize(HMONITOR hMonitor, HDC hdcMonito
return TRUE;
}
+static BOOL CALLBACK _MonitorEnumProcRefreshRate(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
+ EnumRefreshRateData *data = (EnumRefreshRateData *)dwData;
+ if (data->count == data->screen) {
+ MONITORINFOEXW minfo;
+ memset(&minfo, 0, sizeof(minfo));
+ minfo.cbSize = sizeof(minfo);
+ GetMonitorInfoW(hMonitor, &minfo);
+
+ DEVMODEW dm;
+ memset(&dm, 0, sizeof(dm));
+ dm.dmSize = sizeof(dm);
+ EnumDisplaySettingsW(minfo.szDevice, ENUM_CURRENT_SETTINGS, &dm);
+
+ data->rate = dm.dmDisplayFrequency;
+ }
+
+ data->count++;
+ return TRUE;
+}
+
Rect2i DisplayServerWindows::screen_get_usable_rect(int p_screen) const {
_THREAD_SAFE_METHOD_
@@ -446,6 +472,13 @@ int DisplayServerWindows::screen_get_dpi(int p_screen) const {
EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcDpi, (LPARAM)&data);
return data.dpi;
}
+float DisplayServerWindows::screen_get_refresh_rate(int p_screen) const {
+ _THREAD_SAFE_METHOD_
+
+ EnumRefreshRateData data = { 0, p_screen == SCREEN_OF_MAIN_WINDOW ? window_get_current_screen() : p_screen, SCREEN_REFRESH_RATE_FALLBACK };
+ EnumDisplayMonitors(nullptr, nullptr, _MonitorEnumProcRefreshRate, (LPARAM)&data);
+ return data.rate;
+}
bool DisplayServerWindows::screen_is_touchscreen(int p_screen) const {
#ifndef _MSC_VER
@@ -506,7 +539,7 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
if (p_flags & WINDOW_FLAG_BORDERLESS_BIT) {
wd.borderless = true;
}
- if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (p_flags & WINDOW_FLAG_ALWAYS_ON_TOP_BIT && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.always_on_top = true;
}
if (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) {
@@ -946,7 +979,7 @@ Size2i DisplayServerWindows::window_get_real_size(WindowID p_window) const {
return Size2();
}
-void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
+void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex) {
// Windows docs for window styles:
// https://docs.microsoft.com/en-us/windows/win32/winmsg/window-styles
// https://docs.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
@@ -959,6 +992,9 @@ void DisplayServerWindows::_get_window_style(bool p_main_window, bool p_fullscre
if (p_fullscreen || p_borderless) {
r_style |= WS_POPUP; // p_borderless was WS_EX_TOOLWINDOW in the past.
+ if (p_fullscreen && p_multiwindow_fs) {
+ r_style |= WS_BORDER; // Allows child windows to be displayed on top of full screen.
+ }
} else {
if (p_resizable) {
if (p_maximized) {
@@ -989,7 +1025,7 @@ void DisplayServerWindows::_update_window_style(WindowID p_window, bool p_repain
DWORD style = 0;
DWORD style_ex = 0;
- _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
+ _get_window_style(p_window == MAIN_WINDOW_ID, wd.fullscreen, wd.multiwindow_fs, wd.borderless, wd.resizable, wd.maximized, wd.no_focus, style, style_ex);
SetWindowLongPtr(wd.hWnd, GWL_STYLE, style);
SetWindowLongPtr(wd.hWnd, GWL_EXSTYLE, style_ex);
@@ -1009,10 +1045,11 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
ERR_FAIL_COND(!windows.has(p_window));
WindowData &wd = windows[p_window];
- if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (wd.fullscreen && p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
RECT rect;
wd.fullscreen = false;
+ wd.multiwindow_fs = false;
wd.maximized = wd.was_maximized;
if (wd.pre_fs_valid) {
@@ -1051,7 +1088,15 @@ void DisplayServerWindows::window_set_mode(WindowMode p_mode, WindowID p_window)
wd.minimized = true;
}
- if (p_mode == WINDOW_MODE_FULLSCREEN && !wd.fullscreen) {
+ if (p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
+ wd.multiwindow_fs = false;
+ _update_window_style(false);
+ } else {
+ wd.multiwindow_fs = true;
+ _update_window_style(false);
+ }
+
+ if ((p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) && !wd.fullscreen) {
if (wd.minimized) {
ShowWindow(wd.hWnd, SW_RESTORE);
}
@@ -1098,7 +1143,11 @@ DisplayServer::WindowMode DisplayServerWindows::window_get_mode(WindowID p_windo
const WindowData &wd = windows[p_window];
if (wd.fullscreen) {
- return WINDOW_MODE_FULLSCREEN;
+ if (wd.multiwindow_fs) {
+ return WINDOW_MODE_FULLSCREEN;
+ } else {
+ return WINDOW_MODE_EXCLUSIVE_FULLSCREEN;
+ }
} else if (wd.minimized) {
return WINDOW_MODE_MINIMIZED;
} else if (wd.maximized) {
@@ -2649,98 +2698,72 @@ LRESULT DisplayServerWindows::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARA
}
} break;
- case WM_MOVE: {
- if (!IsIconic(windows[window_id].hWnd)) {
- int x = int16_t(LOWORD(lParam));
- int y = int16_t(HIWORD(lParam));
- windows[window_id].last_pos = Point2(x, y);
-
- if (!windows[window_id].rect_changed_callback.is_null()) {
- Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
- Variant *sizep = &size;
- Variant ret;
- Callable::CallError ce;
- windows[window_id].rect_changed_callback.call((const Variant **)&sizep, 1, ret, ce);
+
+ case WM_WINDOWPOSCHANGED: {
+ Rect2i window_client_rect;
+ {
+ RECT rect;
+ GetClientRect(hWnd, &rect);
+ ClientToScreen(hWnd, (POINT *)&rect.left);
+ ClientToScreen(hWnd, (POINT *)&rect.right);
+ window_client_rect = Rect2i(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
+ }
+
+ WINDOWPOS *window_pos_params = (WINDOWPOS *)lParam;
+ WindowData &window = windows[window_id];
+
+ bool rect_changed = false;
+ if (!(window_pos_params->flags & SWP_NOSIZE) || window_pos_params->flags & SWP_FRAMECHANGED) {
+ int screen_id = window_get_current_screen(window_id);
+ Size2i screen_size = screen_get_size(screen_id);
+ Point2i screen_position = screen_get_position(screen_id);
+
+ window.maximized = false;
+ window.minimized = false;
+ window.fullscreen = false;
+
+ if (IsIconic(hWnd)) {
+ window.minimized = true;
+ } else if (IsZoomed(hWnd)) {
+ window.maximized = true;
+ } else if (window_client_rect.position == screen_position && window_client_rect.size == screen_size) {
+ window.fullscreen = true;
}
- }
- } break;
- case WM_SIZE: {
- // Ignore window size change when a SIZE_MINIMIZED event is triggered.
- if (wParam != SIZE_MINIMIZED) {
- // The new width and height of the client area.
- int window_w = LOWORD(lParam);
- int window_h = HIWORD(lParam);
-
- // Set new value to the size if it isn't preserved.
- if (window_w > 0 && window_h > 0 && !windows[window_id].preserve_window_size) {
- windows[window_id].width = window_w;
- windows[window_id].height = window_h;
+
+ if (!window.minimized) {
+ window.width = window_client_rect.size.width;
+ window.height = window_client_rect.size.height;
#if defined(VULKAN_ENABLED)
if (context_vulkan && window_created) {
- context_vulkan->window_resize(window_id, windows[window_id].width, windows[window_id].height);
+ context_vulkan->window_resize(window_id, window.width, window.height);
}
#endif
+ rect_changed = true;
+ }
+ }
- } else { // If the size is preserved.
- windows[window_id].preserve_window_size = false;
+ if (!window.minimized && (!(window_pos_params->flags & SWP_NOMOVE) || window_pos_params->flags & SWP_FRAMECHANGED)) {
+ window.last_pos = window_client_rect.position;
+ rect_changed = true;
+ }
- // Restore the old size.
- window_set_size(Size2(windows[window_id].width, windows[window_id].height), window_id);
+ if (rect_changed) {
+ if (!window.rect_changed_callback.is_null()) {
+ Variant size = Rect2i(window.last_pos.x, window.last_pos.y, window.width, window.height);
+ const Variant *args[] = { &size };
+ Variant ret;
+ Callable::CallError ce;
+ window.rect_changed_callback.call(args, 1, ret, ce);
}
- } else { // When the window has been minimized, preserve its size.
- windows[window_id].preserve_window_size = true;
}
- // Call windows rect change callback.
- if (!windows[window_id].rect_changed_callback.is_null()) {
- Variant size = Rect2i(windows[window_id].last_pos.x, windows[window_id].last_pos.y, windows[window_id].width, windows[window_id].height);
- Variant *size_ptr = &size;
- Variant ret;
- Callable::CallError ce;
- windows[window_id].rect_changed_callback.call((const Variant **)&size_ptr, 1, ret, ce);
- }
-
- // The window has been maximized.
- if (wParam == SIZE_MAXIMIZED) {
- windows[window_id].maximized = true;
- windows[window_id].minimized = false;
- }
- // The window has been minimized.
- else if (wParam == SIZE_MINIMIZED) {
- windows[window_id].maximized = false;
- windows[window_id].minimized = true;
- windows[window_id].preserve_window_size = false;
- }
- // The window has been resized, but neither the SIZE_MINIMIZED nor SIZE_MAXIMIZED value applies.
- else if (wParam == SIZE_RESTORED) {
- windows[window_id].maximized = false;
- windows[window_id].minimized = false;
- }
-#if 0
- if (is_layered_allowed() && layered_window) {
- DeleteObject(hBitmap);
-
- RECT r;
- GetWindowRect(hWnd, &r);
- dib_size = Size2i(r.right - r.left, r.bottom - r.top);
-
- BITMAPINFO bmi;
- ZeroMemory(&bmi, sizeof(BITMAPINFO));
- bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
- bmi.bmiHeader.biWidth = dib_size.x;
- bmi.bmiHeader.biHeight = dib_size.y;
- bmi.bmiHeader.biPlanes = 1;
- bmi.bmiHeader.biBitCount = 32;
- bmi.bmiHeader.biCompression = BI_RGB;
- bmi.bmiHeader.biSizeImage = dib_size.x * dib_size.y * 4;
- hBitmap = CreateDIBSection(hDC_dib, &bmi, DIB_RGB_COLORS, (void **)&dib_data, nullptr, 0x0);
- SelectObject(hDC_dib, hBitmap);
-
- ZeroMemory(dib_data, dib_size.x * dib_size.y * 4);
- }
-#endif
+ // Return here to prevent WM_MOVE and WM_SIZE from being sent
+ // See: https://docs.microsoft.com/en-us/windows/win32/winmsg/wm-windowposchanged#remarks
+ return 0;
+
} break;
+
case WM_ENTERSIZEMOVE: {
Input::get_singleton()->release_pressed_events();
windows[window_id].move_timer_id = SetTimer(windows[window_id].hWnd, 1, USER_TIMER_MINIMUM, (TIMERPROC) nullptr);
@@ -3088,7 +3111,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
DWORD dwExStyle;
DWORD dwStyle;
- _get_window_style(window_id_counter == MAIN_WINDOW_ID, p_mode == WINDOW_MODE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
+ _get_window_style(window_id_counter == MAIN_WINDOW_ID, (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN), p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN, p_flags & WINDOW_FLAG_BORDERLESS_BIT, !(p_flags & WINDOW_FLAG_RESIZE_DISABLED_BIT), p_mode == WINDOW_MODE_MAXIMIZED, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT), dwStyle, dwExStyle);
RECT WindowRect;
@@ -3097,7 +3120,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
WindowRect.top = p_rect.position.y;
WindowRect.bottom = p_rect.position.y + p_rect.size.y;
- if (p_mode == WINDOW_MODE_FULLSCREEN) {
+ if (p_mode == WINDOW_MODE_FULLSCREEN || p_mode == WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
int nearest_area = 0;
Rect2i screen_rect;
for (int i = 0; i < get_screen_count(); i++) {
@@ -3140,7 +3163,7 @@ DisplayServer::WindowID DisplayServerWindows::_create_window(WindowMode p_mode,
windows.erase(id);
return INVALID_WINDOW_ID;
}
- if (p_mode != WINDOW_MODE_FULLSCREEN) {
+ if (p_mode != WINDOW_MODE_FULLSCREEN && p_mode != WINDOW_MODE_EXCLUSIVE_FULLSCREEN) {
wd.pre_fs_valid = true;
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 803c2d4836..d36ca97ebe 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -326,12 +326,12 @@ class DisplayServerWindows : public DisplayServer {
Vector<Vector2> mpath;
- bool preserve_window_size = false;
bool pre_fs_valid = false;
RECT pre_fs_rect;
bool maximized = false;
bool minimized = false;
bool fullscreen = false;
+ bool multiwindow_fs = false;
bool borderless = false;
bool resizable = true;
bool window_focused = false;
@@ -401,7 +401,7 @@ class DisplayServerWindows : public DisplayServer {
WNDPROC user_proc = nullptr;
void _send_window_event(const WindowData &wd, WindowEvent p_event);
- void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
+ void _get_window_style(bool p_main_window, bool p_fullscreen, bool p_multiwindow_fs, bool p_borderless, bool p_resizable, bool p_maximized, bool p_no_activate_focus, DWORD &r_style, DWORD &r_style_ex);
MouseMode mouse_mode;
int restore_mouse_trails = 0;
@@ -458,6 +458,7 @@ public:
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
virtual void screen_set_orientation(ScreenOrientation p_orientation, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index 68762db3a9..d30d0afc5c 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -76,8 +76,8 @@ void EditorExportPlatformWindows::get_export_options(List<ExportOption> *r_optio
r_options->push_back(ExportOption(PropertyInfo(Variant::PACKED_STRING_ARRAY, "codesign/custom_options"), PackedStringArray()));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/icon", PROPERTY_HINT_FILE, "*.ico"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
+ r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_version", PROPERTY_HINT_PLACEHOLDER_TEXT, "1.0.0.0"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/company_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Company Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/product_name", PROPERTY_HINT_PLACEHOLDER_TEXT, "Game Name"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "application/file_description"), ""));
@@ -89,6 +89,7 @@ void EditorExportPlatformWindows::_rcedit_add_data(const Ref<EditorExportPreset>
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
if (rcedit_path.is_empty()) {
+ WARN_PRINT("The rcedit tool is not configured in the Editor Settings (Export > Windows > Rcedit). No custom icon or app information data will be embedded in the exported executable.");
return;
}
@@ -327,3 +328,46 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
return OK;
}
+
+bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+ String err = "";
+ bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
+
+ String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
+ if (rcedit_path.is_empty()) {
+ err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
+ }
+
+ String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
+ if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) {
+ err += TTR("Invalid icon path:") + " " + icon_path + "\n";
+ }
+
+ // Only non-negative integers can exist in the version string.
+
+ String file_version = p_preset->get("application/file_version");
+ if (!file_version.is_empty()) {
+ PackedStringArray version_array = file_version.split(".", false);
+ if (version_array.size() != 4 || !version_array[0].is_valid_int() ||
+ !version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||
+ !version_array[3].is_valid_int() || file_version.find("-") > -1) {
+ err += TTR("Invalid file version:") + " " + file_version + "\n";
+ }
+ }
+
+ String product_version = p_preset->get("application/product_version");
+ if (!product_version.is_empty()) {
+ PackedStringArray version_array = product_version.split(".", false);
+ if (version_array.size() != 4 || !version_array[0].is_valid_int() ||
+ !version_array[1].is_valid_int() || !version_array[2].is_valid_int() ||
+ !version_array[3].is_valid_int() || product_version.find("-") > -1) {
+ err += TTR("Invalid product version:") + " " + product_version + "\n";
+ }
+ }
+
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index 351333aa42..89e5b1b635 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -47,6 +47,7 @@ public:
virtual Error sign_shared_object(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) override;
virtual void get_export_options(List<ExportOption> *r_options) override;
virtual bool get_export_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const override;
+ virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
};
#endif
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 3957a1653f..b6b5920f69 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -506,7 +506,7 @@ int Skeleton3D::get_bone_axis_forward_enum(int p_bone) {
// Skeleton creation api
void Skeleton3D::add_bone(const String &p_name) {
- ERR_FAIL_COND(p_name.is_empty() || p_name.find(":") != -1 || p_name.find("/") != -1);
+ ERR_FAIL_COND(p_name.is_empty() || p_name.contains(":") || p_name.contains("/"));
for (int i = 0; i < bones.size(); i++) {
ERR_FAIL_COND(bones[i].name == p_name);
diff --git a/scene/SCsub b/scene/SCsub
index 92288211bb..a7b23af598 100644
--- a/scene/SCsub
+++ b/scene/SCsub
@@ -9,6 +9,7 @@ env.add_source_files(env.scene_sources, "*.cpp")
# Chain load SCsubs
SConscript("main/SCsub")
+SConscript("multiplayer/SCsub")
SConscript("gui/SCsub")
if not env["disable_3d"]:
SConscript("3d/SCsub")
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index 3b55dd4a42..3d0ac291b8 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -857,7 +857,7 @@ void AnimationNodeBlendTree::add_node(const StringName &p_name, Ref<AnimationNod
ERR_FAIL_COND(nodes.has(p_name));
ERR_FAIL_COND(p_node.is_null());
ERR_FAIL_COND(p_name == SceneStringNames::get_singleton()->output);
- ERR_FAIL_COND(String(p_name).find("/") != -1);
+ ERR_FAIL_COND(String(p_name).contains("/"));
Node n;
n.node = p_node;
diff --git a/scene/animation/animation_node_state_machine.cpp b/scene/animation/animation_node_state_machine.cpp
index a23e1b689c..a68f7e037d 100644
--- a/scene/animation/animation_node_state_machine.cpp
+++ b/scene/animation/animation_node_state_machine.cpp
@@ -50,7 +50,7 @@ bool AnimationNodeStateMachineTransition::has_auto_advance() const {
void AnimationNodeStateMachineTransition::set_advance_condition(const StringName &p_condition) {
String cs = p_condition;
- ERR_FAIL_COND(cs.find("/") != -1 || cs.find(":") != -1);
+ ERR_FAIL_COND(cs.contains("/") || cs.contains(":"));
advance_condition = p_condition;
if (!cs.is_empty()) {
advance_condition_name = "conditions/" + cs;
@@ -536,7 +536,7 @@ Variant AnimationNodeStateMachine::get_parameter_default_value(const StringName
void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<AnimationNode> p_node, const Vector2 &p_position) {
ERR_FAIL_COND(states.has(p_name));
ERR_FAIL_COND(p_node.is_null());
- ERR_FAIL_COND(String(p_name).find("/") != -1);
+ ERR_FAIL_COND(String(p_name).contains("/"));
State state;
state.node = p_node;
@@ -553,7 +553,7 @@ void AnimationNodeStateMachine::add_node(const StringName &p_name, Ref<Animation
void AnimationNodeStateMachine::replace_node(const StringName &p_name, Ref<AnimationNode> p_node) {
ERR_FAIL_COND(states.has(p_name) == false);
ERR_FAIL_COND(p_node.is_null());
- ERR_FAIL_COND(String(p_name).find("/") != -1);
+ ERR_FAIL_COND(String(p_name).contains("/"));
{
Ref<AnimationNode> node = states[p_name].node;
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 128c6cae8b..4a3fd72080 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -1171,7 +1171,7 @@ void AnimationPlayer::_animation_process(double p_delta) {
Error AnimationPlayer::add_animation(const StringName &p_name, const Ref<Animation> &p_animation) {
#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_V_MSG(String(p_name).find("/") != -1 || String(p_name).find(":") != -1 || String(p_name).find(",") != -1 || String(p_name).find("[") != -1, ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
+ ERR_FAIL_COND_V_MSG(String(p_name).contains("/") || String(p_name).contains(":") || String(p_name).contains(",") || String(p_name).contains("["), ERR_INVALID_PARAMETER, "Invalid animation name: " + String(p_name) + ".");
#endif
ERR_FAIL_COND_V(p_animation.is_null(), ERR_INVALID_PARAMETER);
@@ -1213,7 +1213,7 @@ void AnimationPlayer::_unref_anim(const Ref<Animation> &p_anim) {
void AnimationPlayer::rename_animation(const StringName &p_name, const StringName &p_new_name) {
ERR_FAIL_COND(!animation_set.has(p_name));
- ERR_FAIL_COND(String(p_new_name).find("/") != -1 || String(p_new_name).find(":") != -1);
+ ERR_FAIL_COND(String(p_new_name).contains("/") || String(p_new_name).contains(":"));
ERR_FAIL_COND(animation_set.has(p_new_name));
stop();
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index d153b882f9..f2f69fc439 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -314,7 +314,7 @@ void AnimationNode::add_input(const String &p_name) {
//root nodes can't add inputs
ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != nullptr);
Input input;
- ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+ ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/"));
input.name = p_name;
inputs.push_back(input);
emit_changed();
@@ -322,7 +322,7 @@ void AnimationNode::add_input(const String &p_name) {
void AnimationNode::set_input_name(int p_input, const String &p_name) {
ERR_FAIL_INDEX(p_input, inputs.size());
- ERR_FAIL_COND(p_name.find(".") != -1 || p_name.find("/") != -1);
+ ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/"));
inputs.write[p_input].name = p_name;
emit_changed();
}
diff --git a/scene/gui/base_button.cpp b/scene/gui/base_button.cpp
index ec451b07cf..da2ef6c5ec 100644
--- a/scene/gui/base_button.cpp
+++ b/scene/gui/base_button.cpp
@@ -403,7 +403,7 @@ bool BaseButton::_is_focus_owner_in_shorcut_context() const {
}
Node *ctx_node = get_shortcut_context();
- Control *vp_focus = get_focus_owner();
+ Control *vp_focus = get_viewport() ? get_viewport()->gui_get_focus_owner() : nullptr;
// If the context is valid and the viewport focus is valid, check if the context is the focus or is a parent of it.
return ctx_node && vp_focus && (ctx_node == vp_focus || ctx_node->is_ancestor_of(vp_focus));
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 22def607ed..8cb8a78e8d 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -34,14 +34,6 @@
#include "core/string/string_builder.h"
#include "core/string/ustring.h"
-static bool _is_whitespace(char32_t c) {
- return c == '\t' || c == ' ';
-}
-
-static bool _is_char(char32_t c) {
- return !is_symbol(c);
-}
-
void CodeEdit::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED:
@@ -232,7 +224,7 @@ void CodeEdit::_notification(int p_what) {
int begin = 0;
int end = 0;
- if (line.find(String::chr(0xFFFF)) != -1) {
+ if (line.contains(String::chr(0xFFFF))) {
begin = font->get_string_size(line.substr(0, line.find(String::chr(0xFFFF))), font_size).x;
end = font->get_string_size(line.substr(0, line.rfind(String::chr(0xFFFF))), font_size).x;
}
@@ -607,9 +599,9 @@ void CodeEdit::_handle_unicode_input_internal(const uint32_t p_unicode) {
int post_brace_pair = cc < get_line(cl).length() ? _get_auto_brace_pair_close_at_pos(cl, cc) : -1;
- if (has_string_delimiter(chr) && cc > 0 && _is_char(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
+ if (has_string_delimiter(chr) && cc > 0 && !is_symbol(get_line(cl)[cc - 1]) && post_brace_pair == -1) {
insert_text_at_caret(chr);
- } else if (cc < get_line(cl).length() && _is_char(get_line(cl)[cc])) {
+ } else if (cc < get_line(cl).length() && !is_symbol(get_line(cl)[cc])) {
insert_text_at_caret(chr);
} else if (post_brace_pair != -1 && auto_brace_completion_pairs[post_brace_pair].close_key[0] == chr[0]) {
caret_move_offset = auto_brace_completion_pairs[post_brace_pair].close_key.length();
@@ -1001,7 +993,7 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) {
}
/* Make sure this is the last char, trailing whitespace or comments are okay. */
- if (should_indent && (!_is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
+ if (should_indent && (!is_whitespace(c) && is_in_comment(cl, cc) == -1)) {
should_indent = false;
}
}
@@ -1817,7 +1809,7 @@ void CodeEdit::request_code_completion(bool p_force) {
String line = get_line(get_caret_line());
int ofs = CLAMP(get_caret_column(), 0, line.length());
- if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || _is_char(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) {
+ if (ofs > 0 && (is_in_string(get_caret_line(), ofs) != -1 || !is_symbol(line[ofs - 1]) || code_completion_prefixes.has(line[ofs - 1]))) {
emit_signal(SNAME("code_completion_requested"));
} else if (ofs > 1 && line[ofs - 1] == ' ' && code_completion_prefixes.has(line[ofs - 2])) {
emit_signal(SNAME("code_completion_requested"));
@@ -1926,7 +1918,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
if (merge_text) {
for (; caret_col < line.length(); caret_col++) {
- if (!_is_char(line[caret_col])) {
+ if (is_symbol(line[caret_col])) {
break;
}
}
@@ -2562,7 +2554,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c
region = E->value();
in_region = true;
for (int i = E->key() - 2; i >= 0; i--) {
- if (!_is_whitespace(line[i])) {
+ if (!is_whitespace(line[i])) {
return -1;
}
}
@@ -2581,7 +2573,7 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c
}
for (int i = end_col; i < line.length(); i++) {
- if (!_is_whitespace(line[i])) {
+ if (!is_whitespace(line[i])) {
return -1;
}
}
@@ -2797,11 +2789,11 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
while (ofs > 0 && line[ofs] == ' ') {
ofs--;
}
- prev_is_word = _is_char(line[ofs]);
+ prev_is_word = !is_symbol(line[ofs]);
/* Otherwise get current word and set cofs to the start. */
} else {
int start_cofs = cofs;
- while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || _is_char(line[cofs - 1]))) {
+ while (cofs > 0 && line[cofs - 1] > 32 && (line[cofs - 1] == '/' || !is_symbol(line[cofs - 1]))) {
cofs--;
}
string_to_complete = line.substr(cofs, start_cofs - cofs);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index b32b32e3b9..fdae8e2f1f 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -2599,11 +2599,6 @@ Control::MouseFilter Control::get_mouse_filter() const {
return data.mouse_filter;
}
-Control *Control::get_focus_owner() const {
- ERR_FAIL_COND_V(!is_inside_tree(), nullptr);
- return get_viewport()->gui_get_focus_owner();
-}
-
void Control::warp_mouse(const Point2 &p_to_pos) {
ERR_FAIL_COND(!is_inside_tree());
get_viewport()->warp_mouse(get_global_transform().xform(p_to_pos));
@@ -2893,7 +2888,6 @@ void Control::_bind_methods() {
ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus);
ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus);
ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus);
- ClassDB::bind_method(D_METHOD("get_focus_owner"), &Control::get_focus_owner);
ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags);
ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags);
diff --git a/scene/gui/control.h b/scene/gui/control.h
index bf79f790e7..962135280f 100644
--- a/scene/gui/control.h
+++ b/scene/gui/control.h
@@ -462,8 +462,6 @@ public:
void set_focus_previous(const NodePath &p_prev);
NodePath get_focus_previous() const;
- Control *get_focus_owner() const;
-
void set_mouse_filter(MouseFilter p_filter);
MouseFilter get_mouse_filter() const;
diff --git a/scene/gui/menu_button.cpp b/scene/gui/menu_button.cpp
index f7805136f9..a985a9d031 100644
--- a/scene/gui/menu_button.cpp
+++ b/scene/gui/menu_button.cpp
@@ -98,7 +98,13 @@ void MenuButton::pressed() {
popup->set_position(gp);
popup->set_parent_rect(Rect2(Point2(gp - popup->get_position()), size));
- popup->take_mouse_focus();
+ // If not triggered by the mouse, start the popup with its first item selected.
+ if (popup->get_item_count() > 0 &&
+ ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) ||
+ (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) {
+ popup->set_current_index(0);
+ }
+
popup->popup();
}
diff --git a/scene/gui/option_button.cpp b/scene/gui/option_button.cpp
index c80de04c01..9984ab240a 100644
--- a/scene/gui/option_button.cpp
+++ b/scene/gui/option_button.cpp
@@ -181,7 +181,14 @@ void OptionButton::pressed() {
Size2 size = get_size() * get_viewport()->get_canvas_transform().get_scale();
popup->set_position(get_screen_position() + Size2(0, size.height * get_global_transform().get_scale().y));
popup->set_size(Size2(size.width, 0));
- popup->set_current_index(current);
+
+ // If not triggered by the mouse, start the popup with the checked item selected.
+ if (popup->get_item_count() > 0 &&
+ ((get_action_mode() == ActionMode::ACTION_MODE_BUTTON_PRESS && Input::get_singleton()->is_action_just_pressed("ui_accept")) ||
+ (get_action_mode() == ActionMode::ACTION_MODE_BUTTON_RELEASE && Input::get_singleton()->is_action_just_released("ui_accept")))) {
+ popup->set_current_index(current > -1 ? current : 0);
+ }
+
popup->popup();
}
diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp
index fc853a3df4..812339dc19 100644
--- a/scene/gui/popup_menu.cpp
+++ b/scene/gui/popup_menu.cpp
@@ -192,7 +192,7 @@ void PopupMenu::_activate_submenu(int p_over) {
Popup *submenu_popup = Object::cast_to<Popup>(n);
ERR_FAIL_COND_MSG(!submenu_popup, "Item subnode is not a Popup: " + items[p_over].submenu + ".");
if (submenu_popup->is_visible()) {
- return; //already visible!
+ return; // Already visible.
}
Ref<StyleBox> style = get_theme_stylebox(SNAME("panel"));
@@ -223,24 +223,33 @@ void PopupMenu::_activate_submenu(int p_over) {
submenu_popup->set_close_on_parent_focus(false);
submenu_popup->set_position(submenu_pos);
submenu_popup->set_as_minsize(); // Shrink the popup size to its contents.
- submenu_popup->popup();
- // Set autohide areas
PopupMenu *submenu_pum = Object::cast_to<PopupMenu>(submenu_popup);
- if (submenu_pum) {
- submenu_pum->take_mouse_focus();
- // Make the position of the parent popup relative to submenu popup
- this_rect.position = this_rect.position - submenu_pum->get_position();
-
- // Autohide area above the submenu item
- submenu_pum->clear_autohide_areas();
- submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
-
- // If there is an area below the submenu item, add an autohide area there.
- if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
- int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
- submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
- }
+ if (!submenu_pum) {
+ submenu_popup->popup();
+ return;
+ }
+
+ // If not triggered by the mouse, start the popup with its first item selected.
+ if (submenu_pum->get_item_count() > 0 && Input::get_singleton()->is_action_just_pressed("ui_accept")) {
+ submenu_pum->set_current_index(0);
+ }
+
+ submenu_pum->popup();
+
+ // Set autohide areas.
+
+ // Make the position of the parent popup relative to submenu popup.
+ this_rect.position = this_rect.position - submenu_pum->get_position();
+
+ // Autohide area above the submenu item.
+ submenu_pum->clear_autohide_areas();
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y, this_rect.size.x, items[p_over]._ofs_cache + scroll_offset + style->get_offset().height - vsep / 2));
+
+ // If there is an area below the submenu item, add an autohide area there.
+ if (items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset <= control->get_size().height) {
+ int from = items[p_over]._ofs_cache + items[p_over]._height_cache + scroll_offset + vsep / 2 + style->get_offset().height;
+ submenu_pum->add_autohide_area(Rect2(this_rect.position.x, this_rect.position.y + from, this_rect.size.x, this_rect.size.y - from));
}
}
@@ -1747,6 +1756,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_tooltip", "index"), &PopupMenu::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_shortcut", "index"), &PopupMenu::get_item_shortcut);
+ ClassDB::bind_method(D_METHOD("set_current_index", "index"), &PopupMenu::set_current_index);
ClassDB::bind_method(D_METHOD("get_current_index"), &PopupMenu::get_current_index);
ClassDB::bind_method(D_METHOD("set_item_count", "count"), &PopupMenu::set_item_count);
ClassDB::bind_method(D_METHOD("get_item_count"), &PopupMenu::get_item_count);
diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp
index 8f4c063c58..4865b9770e 100644
--- a/scene/gui/rich_text_label.cpp
+++ b/scene/gui/rich_text_label.cpp
@@ -421,9 +421,24 @@ void RichTextLabel::_shape_line(ItemFrame *p_frame, int p_line, const Ref<Font>
Line &l = p_frame->lines.write[p_line];
+ uint16_t autowrap_flags = TextServer::BREAK_MANDATORY;
+ switch (autowrap_mode) {
+ case AUTOWRAP_WORD_SMART:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND_ADAPTIVE | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_WORD:
+ autowrap_flags = TextServer::BREAK_WORD_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_ARBITRARY:
+ autowrap_flags = TextServer::BREAK_GRAPHEME_BOUND | TextServer::BREAK_MANDATORY;
+ break;
+ case AUTOWRAP_OFF:
+ break;
+ }
+
// Clear cache.
l.text_buf->clear();
- l.text_buf->set_flags(TextServer::BREAK_MANDATORY | TextServer::BREAK_WORD_BOUND | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
+ l.text_buf->set_flags(autowrap_flags | TextServer::JUSTIFICATION_KASHIDA | TextServer::JUSTIFICATION_WORD_BOUND | TextServer::JUSTIFICATION_TRIM_EDGE_SPACES);
l.char_offset = *r_char_offset;
l.char_count = 0;
@@ -4077,6 +4092,19 @@ String RichTextLabel::get_language() const {
return language;
}
+void RichTextLabel::set_autowrap_mode(RichTextLabel::AutowrapMode p_mode) {
+ if (autowrap_mode != p_mode) {
+ autowrap_mode = p_mode;
+ main->first_invalid_line = 0; //invalidate ALL
+ _validate_line_caches(main);
+ update();
+ }
+}
+
+RichTextLabel::AutowrapMode RichTextLabel::get_autowrap_mode() const {
+ return autowrap_mode;
+}
+
void RichTextLabel::set_percent_visible(float p_percent) {
if (percent_visible != p_percent) {
if (p_percent < 0 || p_percent >= 1) {
@@ -4188,6 +4216,9 @@ void RichTextLabel::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_language", "language"), &RichTextLabel::set_language);
ClassDB::bind_method(D_METHOD("get_language"), &RichTextLabel::get_language);
+ ClassDB::bind_method(D_METHOD("set_autowrap_mode", "autowrap_mode"), &RichTextLabel::set_autowrap_mode);
+ ClassDB::bind_method(D_METHOD("get_autowrap_mode"), &RichTextLabel::get_autowrap_mode);
+
ClassDB::bind_method(D_METHOD("set_meta_underline", "enable"), &RichTextLabel::set_meta_underline);
ClassDB::bind_method(D_METHOD("is_meta_underlined"), &RichTextLabel::is_meta_underlined);
@@ -4280,6 +4311,8 @@ void RichTextLabel::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "text_direction", PROPERTY_HINT_ENUM, "Auto,Left-to-Right,Right-to-Left,Inherited"), "set_text_direction", "get_text_direction");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "language", PROPERTY_HINT_LOCALE_ID, ""), "set_language", "get_language");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "autowrap_mode", PROPERTY_HINT_ENUM, "Off,Arbitrary,Word,Word (Smart)"), "set_autowrap_mode", "get_autowrap_mode");
+
ADD_GROUP("Structured Text", "structured_text_");
ADD_PROPERTY(PropertyInfo(Variant::INT, "structured_text_bidi_override", PROPERTY_HINT_ENUM, "Default,URI,File,Email,List,None,Custom"), "set_structured_text_bidi_override", "get_structured_text_bidi_override");
ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "structured_text_bidi_override_options"), "set_structured_text_bidi_override_options", "get_structured_text_bidi_override_options");
@@ -4288,6 +4321,11 @@ void RichTextLabel::_bind_methods() {
ADD_SIGNAL(MethodInfo("meta_hover_started", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("meta_hover_ended", PropertyInfo(Variant::NIL, "meta", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
+ BIND_ENUM_CONSTANT(AUTOWRAP_OFF);
+ BIND_ENUM_CONSTANT(AUTOWRAP_ARBITRARY);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD);
+ BIND_ENUM_CONSTANT(AUTOWRAP_WORD_SMART);
+
BIND_ENUM_CONSTANT(LIST_NUMBERS);
BIND_ENUM_CONSTANT(LIST_LETTERS);
BIND_ENUM_CONSTANT(LIST_ROMAN);
diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h
index 2b24da4c37..e79244f2e4 100644
--- a/scene/gui/rich_text_label.h
+++ b/scene/gui/rich_text_label.h
@@ -39,6 +39,13 @@ class RichTextLabel : public Control {
GDCLASS(RichTextLabel, Control);
public:
+ enum AutowrapMode {
+ AUTOWRAP_OFF,
+ AUTOWRAP_ARBITRARY,
+ AUTOWRAP_WORD,
+ AUTOWRAP_WORD_SMART
+ };
+
enum ListType {
LIST_NUMBERS,
LIST_LETTERS,
@@ -346,6 +353,8 @@ private:
VScrollBar *vscroll = nullptr;
+ AutowrapMode autowrap_mode = AUTOWRAP_WORD_SMART;
+
bool scroll_visible = false;
bool scroll_follow = false;
bool scroll_following = false;
@@ -574,6 +583,9 @@ public:
void set_language(const String &p_language);
String get_language() const;
+ void set_autowrap_mode(AutowrapMode p_mode);
+ AutowrapMode get_autowrap_mode() const;
+
void set_structured_text_bidi_override(Control::StructuredTextParser p_parser);
Control::StructuredTextParser get_structured_text_bidi_override() const;
@@ -603,6 +615,7 @@ public:
~RichTextLabel();
};
+VARIANT_ENUM_CAST(RichTextLabel::AutowrapMode);
VARIANT_ENUM_CAST(RichTextLabel::ListType);
VARIANT_ENUM_CAST(RichTextLabel::ItemType);
VARIANT_ENUM_CAST(RichTextLabel::VisibleCharactersBehavior);
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 0ee4a6af4e..bb259843b8 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -43,18 +43,6 @@
#include "scene/main/window.h"
-static bool _is_text_char(char32_t c) {
- return !is_symbol(c);
-}
-
-static bool _is_whitespace(char32_t c) {
- return c == '\t' || c == ' ';
-}
-
-static bool _is_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
-}
-
///////////////////////////////////////////////////////////////////////////////
/// TEXT ///
///////////////////////////////////////////////////////////////////////////////
@@ -820,8 +808,8 @@ void TextEdit::_notification(int p_what) {
int xpos = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * j)) + tabs;
bool out_of_bounds = (xpos >= xmargin_end + minimap_width);
- bool is_whitespace = _is_whitespace(str[j]);
- if (!is_whitespace) {
+ bool whitespace = is_whitespace(str[j]);
+ if (!whitespace) {
characters++;
if (j < str.length() - 1 && color == previous_color && !out_of_bounds) {
@@ -843,7 +831,7 @@ void TextEdit::_notification(int p_what) {
if (characters > 0) {
previous_color.a *= 0.6;
// take one for zero indexing, and if we hit whitespace / the end of a word.
- int chars = MAX(0, (j - (characters - 1)) - (is_whitespace ? 1 : 0)) + 1;
+ int chars = MAX(0, (j - (characters - 1)) - (whitespace ? 1 : 0)) + 1;
int char_x_ofs = indent_px + ((xmargin_end + minimap_char_size.x) + (minimap_char_size.x * chars)) + tabs;
if (rtl) {
RenderingServer::get_singleton()->canvas_item_add_rect(ci, Rect2(Point2(size.width - char_x_ofs - minimap_char_size.x * characters, minimap_line_height * i), Point2(minimap_char_size.x * characters, minimap_char_size.y)), previous_color);
@@ -1144,7 +1132,7 @@ void TextEdit::_notification(int p_what) {
}
if (!clipped && lookup_symbol_word.length() != 0) { // Highlight word
- if (_is_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '.') {
+ if (is_ascii_char(lookup_symbol_word[0]) || lookup_symbol_word[0] == '_' || lookup_symbol_word[0] == '.') {
int highlighted_word_col = _get_column_pos_of_word(lookup_symbol_word, str, SEARCH_MATCH_CASE | SEARCH_WHOLE_WORDS, 0);
while (highlighted_word_col != -1) {
Vector<Vector2> sel = TS->shaped_text_get_selection(rid, highlighted_word_col + start, highlighted_word_col + lookup_symbol_word.length() + start);
@@ -3037,7 +3025,7 @@ int TextEdit::get_first_non_whitespace_column(int p_line) const {
ERR_FAIL_INDEX_V(p_line, text.size(), 0);
int col = 0;
- while (col < text[p_line].length() && _is_whitespace(text[p_line][col])) {
+ while (col < text[p_line].length() && is_whitespace(text[p_line][col])) {
col++;
}
return col;
@@ -3622,9 +3610,9 @@ Point2i TextEdit::search(const String &p_key, uint32_t p_search_flags, int p_fro
if (pos != -1 && (p_search_flags & SEARCH_WHOLE_WORDS)) {
// Validate for whole words.
- if (pos > 0 && _is_text_char(text_line[pos - 1])) {
+ if (pos > 0 && !is_symbol(text_line[pos - 1])) {
is_match = false;
- } else if (pos + p_key.length() < text_line.length() && _is_text_char(text_line[pos + p_key.length()])) {
+ } else if (pos + p_key.length() < text_line.length() && !is_symbol(text_line[pos + p_key.length()])) {
is_match = false;
}
}
@@ -5779,9 +5767,9 @@ int TextEdit::_get_column_pos_of_word(const String &p_key, const String &p_searc
if (col != -1 && p_search_flags & SEARCH_WHOLE_WORDS) {
p_from_column = col;
- if (col > 0 && _is_text_char(p_search[col - 1])) {
+ if (col > 0 && !is_symbol(p_search[col - 1])) {
col = -1;
- } else if ((col + p_key.length()) < p_search.length() && _is_text_char(p_search[col + p_key.length()])) {
+ } else if ((col + p_key.length()) < p_search.length() && !is_symbol(p_search[col + p_key.length()])) {
col = -1;
}
}
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index a2415442f8..6b9d8ab211 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -32,6 +32,7 @@
#include "core/core_string_names.h"
#include "core/io/resource_loader.h"
+#include "core/multiplayer/multiplayer_api.h"
#include "core/object/message_queue.h"
#include "core/string/print_string.h"
#include "instance_placeholder.h"
@@ -110,9 +111,6 @@ void Node::_notification(int p_notification) {
memdelete(data.path_cache);
data.path_cache = nullptr;
}
- if (data.scene_file_path.length()) {
- get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, false);
- }
} break;
case NOTIFICATION_PATH_RENAMED: {
if (data.path_cache) {
@@ -141,12 +139,6 @@ void Node::_notification(int p_notification) {
}
GDVIRTUAL_CALL(_ready);
-
- if (data.scene_file_path.length()) {
- ERR_FAIL_COND(!is_inside_tree());
- get_multiplayer()->scene_enter_exit_notify(data.scene_file_path, this, true);
- }
-
} break;
case NOTIFICATION_POSTINITIALIZE: {
data.in_constructor = false;
@@ -211,6 +203,12 @@ void Node::_propagate_enter_tree() {
data.tree->node_added(this);
+ if (data.parent) {
+ Variant c = this;
+ const Variant *cptr = &c;
+ data.parent->emit_signal(SNAME("child_entered_tree"), &cptr, 1);
+ }
+
data.blocked++;
//block while adding children
@@ -281,6 +279,12 @@ void Node::_propagate_exit_tree() {
data.tree->node_removed(this);
}
+ if (data.parent) {
+ Variant c = this;
+ const Variant *cptr = &c;
+ data.parent->emit_signal(SNAME("child_exited_tree"), &cptr, 1);
+ }
+
// exit groups
for (KeyValue<StringName, GroupData> &E : data.grouped) {
@@ -1049,7 +1053,7 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
String nums;
for (int i = name_string.length() - 1; i >= 0; i--) {
char32_t n = name_string[i];
- if (n >= '0' && n <= '9') {
+ if (is_digit(n)) {
nums = String::chr(name_string[i]) + nums;
} else {
break;
@@ -2865,6 +2869,8 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("tree_entered"));
ADD_SIGNAL(MethodInfo("tree_exiting"));
ADD_SIGNAL(MethodInfo("tree_exited"));
+ ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
+ ADD_SIGNAL(MethodInfo("child_exited_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "scene_file_path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_scene_file_path", "get_scene_file_path");
diff --git a/scene/main/node.h b/scene/main/node.h
index a1fc672a15..0ac10f4381 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -212,7 +212,6 @@ protected:
static String _get_name_num_separator();
friend class SceneState;
- friend class MultiplayerReplicator;
void _add_child_nocheck(Node *p_child, const StringName &p_name);
void _set_owner_nocheck(Node *p_owner);
@@ -467,7 +466,7 @@ public:
bool is_displayed_folded() const;
/* NETWORK */
- void set_multiplayer_authority(int p_peer_id, bool p_recursive = true);
+ virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true);
int get_multiplayer_authority() const;
bool is_multiplayer_authority() const;
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index 45f04b28b9..0e4a6a4b5c 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -36,6 +36,7 @@
#include "core/io/dir_access.h"
#include "core/io/marshalls.h"
#include "core/io/resource_loader.h"
+#include "core/multiplayer/multiplayer_api.h"
#include "core/object/message_queue.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 1dff1dab4f..a5cd52b4ca 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -31,7 +31,6 @@
#ifndef SCENE_TREE_H
#define SCENE_TREE_H
-#include "core/multiplayer/multiplayer_api.h"
#include "core/os/main_loop.h"
#include "core/os/thread_safe.h"
#include "core/templates/self_list.h"
@@ -46,6 +45,7 @@ class Node;
class Window;
class Material;
class Mesh;
+class MultiplayerAPI;
class SceneDebugger;
class Tween;
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 09880ad6cf..522997cdf5 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1231,7 +1231,7 @@ void Viewport::_gui_show_tooltip() {
base_tooltip->set_anchors_and_offsets_preset(Control::PRESET_WIDE);
- panel->set_transient(false);
+ panel->set_transient(true);
panel->set_flag(Window::FLAG_NO_FOCUS, true);
panel->set_wrap_controls(true);
panel->add_child(base_tooltip);
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index fbc0bc5301..f2ebe50fa3 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -1614,6 +1614,7 @@ void Window::_bind_methods() {
BIND_ENUM_CONSTANT(MODE_MINIMIZED);
BIND_ENUM_CONSTANT(MODE_MAXIMIZED);
BIND_ENUM_CONSTANT(MODE_FULLSCREEN);
+ BIND_ENUM_CONSTANT(MODE_EXCLUSIVE_FULLSCREEN);
BIND_ENUM_CONSTANT(FLAG_RESIZE_DISABLED);
BIND_ENUM_CONSTANT(FLAG_BORDERLESS);
diff --git a/scene/main/window.h b/scene/main/window.h
index 2dd1dd6601..f37689f905 100644
--- a/scene/main/window.h
+++ b/scene/main/window.h
@@ -46,6 +46,7 @@ public:
MODE_MINIMIZED = DisplayServer::WINDOW_MODE_MINIMIZED,
MODE_MAXIMIZED = DisplayServer::WINDOW_MODE_MAXIMIZED,
MODE_FULLSCREEN = DisplayServer::WINDOW_MODE_FULLSCREEN,
+ MODE_EXCLUSIVE_FULLSCREEN = DisplayServer::WINDOW_MODE_EXCLUSIVE_FULLSCREEN,
};
enum Flags {
diff --git a/scene/multiplayer/SCsub b/scene/multiplayer/SCsub
new file mode 100644
index 0000000000..fc61250247
--- /dev/null
+++ b/scene/multiplayer/SCsub
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+Import("env")
+
+env.add_source_files(env.scene_sources, "*.cpp")
diff --git a/scene/multiplayer/multiplayer_spawner.cpp b/scene/multiplayer/multiplayer_spawner.cpp
new file mode 100644
index 0000000000..4f2a9d9e83
--- /dev/null
+++ b/scene/multiplayer/multiplayer_spawner.cpp
@@ -0,0 +1,227 @@
+/*************************************************************************/
+/* multiplayer_spawner.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "multiplayer_spawner.h"
+
+#include "core/io/marshalls.h"
+#include "core/multiplayer/multiplayer_api.h"
+#include "scene/main/window.h"
+#include "scene/scene_string_names.h"
+
+void MultiplayerSpawner::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("spawn", "data"), &MultiplayerSpawner::spawn, DEFVAL(Variant()));
+
+ ClassDB::bind_method(D_METHOD("get_spawnable_scenes"), &MultiplayerSpawner::get_spawnable_scenes);
+ ClassDB::bind_method(D_METHOD("set_spawnable_scenes", "scenes"), &MultiplayerSpawner::set_spawnable_scenes);
+ ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "replication", PROPERTY_HINT_ARRAY_TYPE, vformat("%s/%s:%s", Variant::OBJECT, PROPERTY_HINT_RESOURCE_TYPE, "PackedScene"), (PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_SCRIPT_VARIABLE)), "set_spawnable_scenes", "get_spawnable_scenes");
+
+ ClassDB::bind_method(D_METHOD("get_spawn_path"), &MultiplayerSpawner::get_spawn_path);
+ ClassDB::bind_method(D_METHOD("set_spawn_path", "path"), &MultiplayerSpawner::set_spawn_path);
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "spawn_path", PROPERTY_HINT_NONE, ""), "set_spawn_path", "get_spawn_path");
+
+ ClassDB::bind_method(D_METHOD("get_spawn_limit"), &MultiplayerSpawner::get_spawn_limit);
+ ClassDB::bind_method(D_METHOD("set_spawn_limit", "limit"), &MultiplayerSpawner::set_spawn_limit);
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "spawn_limit", PROPERTY_HINT_RANGE, "0,1024,1,or_greater"), "set_spawn_limit", "get_spawn_limit");
+
+ ClassDB::bind_method(D_METHOD("set_auto_spawning", "enabled"), &MultiplayerSpawner::set_auto_spawning);
+ ClassDB::bind_method(D_METHOD("is_auto_spawning"), &MultiplayerSpawner::is_auto_spawning);
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "auto_spawn"), "set_auto_spawning", "is_auto_spawning");
+
+ GDVIRTUAL_BIND(_spawn_custom, "data");
+
+ ADD_SIGNAL(MethodInfo("despawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+ ADD_SIGNAL(MethodInfo("spawned", PropertyInfo(Variant::INT, "scene_id"), PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_RESOURCE_TYPE, "Node")));
+}
+
+void MultiplayerSpawner::_update_spawn_node() {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ if (spawn_node.is_valid()) {
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(spawn_node));
+ if (node && node->is_connected("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added))) {
+ node->disconnect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
+ }
+ Node *node = spawn_path.is_empty() && is_inside_tree() ? nullptr : get_node_or_null(spawn_path);
+ if (node) {
+ spawn_node = node->get_instance_id();
+ if (auto_spawn) {
+ node->connect("child_entered_tree", callable_mp(this, &MultiplayerSpawner::_node_added));
+ }
+ } else {
+ spawn_node = ObjectID();
+ }
+}
+
+void MultiplayerSpawner::_notification(int p_what) {
+ if (p_what == NOTIFICATION_POST_ENTER_TREE) {
+ _update_spawn_node();
+ } else if (p_what == NOTIFICATION_EXIT_TREE) {
+ _update_spawn_node();
+ const ObjectID *oid = nullptr;
+ while ((oid = tracked_nodes.next(oid))) {
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(*oid));
+ ERR_CONTINUE(!node);
+ node->disconnect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit));
+ // This is unlikely, but might still crash the engine.
+ if (node->is_connected(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready))) {
+ node->disconnect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready));
+ }
+ get_multiplayer()->despawn(node, this);
+ }
+ tracked_nodes.clear();
+ }
+}
+
+void MultiplayerSpawner::_node_added(Node *p_node) {
+ if (!get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority()) {
+ return;
+ }
+ if (tracked_nodes.has(p_node->get_instance_id())) {
+ return;
+ }
+ const Node *parent = get_spawn_node();
+ if (!parent || p_node->get_parent() != parent) {
+ return;
+ }
+ int id = get_scene_id(p_node->get_scene_file_path());
+ if (id == INVALID_ID) {
+ return;
+ }
+ const String name = p_node->get_name();
+ ERR_FAIL_COND_MSG(name.validate_node_name() != name, vformat("Unable to auto-spawn node with reserved name: %s. Make sure to add your replicated scenes via 'add_child(node, true)' to produce valid names.", name));
+ _track(p_node, Variant(), id);
+}
+
+void MultiplayerSpawner::set_auto_spawning(bool p_enabled) {
+ auto_spawn = p_enabled;
+ _update_spawn_node();
+}
+
+bool MultiplayerSpawner::is_auto_spawning() const {
+ return auto_spawn;
+}
+
+TypedArray<PackedScene> MultiplayerSpawner::get_spawnable_scenes() {
+ return spawnable_scenes;
+}
+
+void MultiplayerSpawner::set_spawnable_scenes(TypedArray<PackedScene> p_scenes) {
+ spawnable_scenes = p_scenes;
+}
+
+NodePath MultiplayerSpawner::get_spawn_path() const {
+ return spawn_path;
+}
+
+void MultiplayerSpawner::set_spawn_path(const NodePath &p_path) {
+ spawn_path = p_path;
+ _update_spawn_node();
+}
+
+void MultiplayerSpawner::_track(Node *p_node, const Variant &p_argument, int p_scene_id) {
+ ObjectID oid = p_node->get_instance_id();
+ if (!tracked_nodes.has(oid)) {
+ tracked_nodes[oid] = SpawnInfo(p_argument.duplicate(true), p_scene_id);
+ p_node->connect(SceneStringNames::get_singleton()->tree_exiting, callable_mp(this, &MultiplayerSpawner::_node_exit), varray(p_node->get_instance_id()), CONNECT_ONESHOT);
+ p_node->connect(SceneStringNames::get_singleton()->ready, callable_mp(this, &MultiplayerSpawner::_node_ready), varray(p_node->get_instance_id()), CONNECT_ONESHOT);
+ }
+}
+
+void MultiplayerSpawner::_node_ready(ObjectID p_id) {
+ get_multiplayer()->spawn(ObjectDB::get_instance(p_id), this);
+}
+
+void MultiplayerSpawner::_node_exit(ObjectID p_id) {
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+ ERR_FAIL_COND(!node);
+ if (tracked_nodes.has(p_id)) {
+ tracked_nodes.erase(p_id);
+ get_multiplayer()->despawn(node, this);
+ }
+}
+
+int MultiplayerSpawner::get_scene_id(const String &p_scene) const {
+ for (int i = 0; i < spawnable_scenes.size(); i++) {
+ Ref<PackedScene> ps = spawnable_scenes[i];
+ ERR_CONTINUE(ps.is_null());
+ if (ps->get_path() == p_scene) {
+ return i;
+ }
+ }
+ return INVALID_ID;
+}
+
+int MultiplayerSpawner::get_spawn_id(const ObjectID &p_id) const {
+ const SpawnInfo *info = tracked_nodes.getptr(p_id);
+ return info ? info->id : INVALID_ID;
+}
+
+const Variant MultiplayerSpawner::get_spawn_argument(const ObjectID &p_id) const {
+ const SpawnInfo *info = tracked_nodes.getptr(p_id);
+ return info ? info->args : Variant();
+}
+
+Node *MultiplayerSpawner::instantiate_scene(int p_id) {
+ ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
+ ERR_FAIL_INDEX_V(p_id, spawnable_scenes.size(), nullptr);
+ Ref<PackedScene> scene = spawnable_scenes[p_id];
+ ERR_FAIL_COND_V(scene.is_null(), nullptr);
+ return scene->instantiate();
+}
+
+Node *MultiplayerSpawner::instantiate_custom(const Variant &p_data) {
+ ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
+ Object *obj = nullptr;
+ Node *node = nullptr;
+ if (GDVIRTUAL_CALL(_spawn_custom, p_data, obj)) {
+ node = Object::cast_to<Node>(obj);
+ }
+ return node;
+}
+
+Node *MultiplayerSpawner::spawn(const Variant &p_data) {
+ ERR_FAIL_COND_V(!is_inside_tree() || !get_multiplayer()->has_multiplayer_peer() || !is_multiplayer_authority(), nullptr);
+ ERR_FAIL_COND_V_MSG(spawn_limit && spawn_limit <= tracked_nodes.size(), nullptr, "Spawn limit reached!");
+ ERR_FAIL_COND_V_MSG(!GDVIRTUAL_IS_OVERRIDDEN(_spawn_custom), nullptr, "Custom spawn requires the '_spawn_custom' virtual method to be implemented via script.");
+
+ Node *parent = get_spawn_node();
+ ERR_FAIL_COND_V_MSG(!parent, nullptr, "Cannot find spawn node.");
+
+ Node *node = instantiate_custom(p_data);
+ ERR_FAIL_COND_V_MSG(!node, nullptr, "The '_spawn_custom' implementation must return a valid Node.");
+
+ _track(node, p_data);
+ parent->add_child(node, true);
+ return node;
+}
diff --git a/scene/multiplayer/multiplayer_spawner.h b/scene/multiplayer/multiplayer_spawner.h
new file mode 100644
index 0000000000..63948e39a5
--- /dev/null
+++ b/scene/multiplayer/multiplayer_spawner.h
@@ -0,0 +1,101 @@
+/*************************************************************************/
+/* multiplayer_spawner.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef MULTIPLAYER_SPAWNER_H
+#define MULTIPLAYER_SPAWNER_H
+
+#include "scene/main/node.h"
+
+#include "core/variant/typed_array.h"
+#include "scene/resources/packed_scene.h"
+#include "scene/resources/scene_replication_config.h"
+
+class MultiplayerSpawner : public Node {
+ GDCLASS(MultiplayerSpawner, Node);
+
+public:
+ enum {
+ INVALID_ID = 0xFF,
+ };
+
+private:
+ TypedArray<PackedScene> spawnable_scenes;
+ Set<ResourceUID::ID> spawnable_ids;
+ NodePath spawn_path;
+
+ struct SpawnInfo {
+ Variant args;
+ int id = INVALID_ID;
+ SpawnInfo(Variant p_args, int p_id) {
+ id = p_id;
+ args = p_args;
+ }
+ SpawnInfo() {}
+ };
+
+ ObjectID spawn_node;
+ HashMap<ObjectID, SpawnInfo> tracked_nodes;
+ bool auto_spawn = false;
+ uint32_t spawn_limit = 0;
+
+ void _update_spawn_node();
+ void _track(Node *p_node, const Variant &p_argument, int p_scene_id = INVALID_ID);
+ void _node_added(Node *p_node);
+ void _node_exit(ObjectID p_id);
+ void _node_ready(ObjectID p_id);
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ Node *get_spawn_node() const { return spawn_node.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(spawn_node)) : nullptr; }
+ TypedArray<PackedScene> get_spawnable_scenes();
+ void set_spawnable_scenes(TypedArray<PackedScene> p_scenes);
+ NodePath get_spawn_path() const;
+ void set_spawn_path(const NodePath &p_path);
+ uint32_t get_spawn_limit() const { return spawn_limit; }
+ void set_spawn_limit(uint32_t p_limit) { spawn_limit = p_limit; }
+ bool is_auto_spawning() const;
+ void set_auto_spawning(bool p_enabled);
+
+ const Variant get_spawn_argument(const ObjectID &p_id) const;
+ int get_spawn_id(const ObjectID &p_id) const;
+ int get_scene_id(const String &p_path) const;
+ Node *spawn(const Variant &p_data = Variant());
+ Node *instantiate_custom(const Variant &p_data);
+ Node *instantiate_scene(int p_idx);
+
+ GDVIRTUAL1R(Object *, _spawn_custom, const Variant &);
+
+ MultiplayerSpawner() {}
+};
+
+#endif // MULTIPLAYER_SPAWNER_H
diff --git a/scene/multiplayer/multiplayer_synchronizer.cpp b/scene/multiplayer/multiplayer_synchronizer.cpp
new file mode 100644
index 0000000000..fbe1b99cc9
--- /dev/null
+++ b/scene/multiplayer/multiplayer_synchronizer.cpp
@@ -0,0 +1,158 @@
+/*************************************************************************/
+/* multiplayer_synchronizer.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "multiplayer_synchronizer.h"
+
+#include "core/config/engine.h"
+#include "core/multiplayer/multiplayer_api.h"
+
+Object *MultiplayerSynchronizer::_get_prop_target(Object *p_obj, const NodePath &p_path) {
+ if (p_path.get_name_count() == 0) {
+ return p_obj;
+ }
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V_MSG(!node || !node->has_node(p_path), nullptr, vformat("Node '%s' not found.", p_path));
+ return node->get_node(p_path);
+}
+
+void MultiplayerSynchronizer::_stop() {
+ Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
+ if (node) {
+ get_multiplayer()->replication_stop(node, this);
+ }
+}
+
+void MultiplayerSynchronizer::_start() {
+ Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
+ if (node) {
+ get_multiplayer()->replication_start(node, this);
+ }
+}
+
+Error MultiplayerSynchronizer::get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs) {
+ ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
+ r_variant.resize(p_properties.size());
+ r_variant_ptrs.resize(r_variant.size());
+ int i = 0;
+ for (const NodePath &prop : p_properties) {
+ bool valid = false;
+ const Object *obj = _get_prop_target(p_obj, prop);
+ ERR_FAIL_COND_V(!obj, FAILED);
+ r_variant.write[i] = obj->get(prop.get_concatenated_subnames(), &valid);
+ r_variant_ptrs.write[i] = &r_variant[i];
+ ERR_FAIL_COND_V_MSG(!valid, ERR_INVALID_DATA, vformat("Property '%s' not found.", prop));
+ i++;
+ }
+ return OK;
+}
+
+Error MultiplayerSynchronizer::set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state) {
+ ERR_FAIL_COND_V(!p_obj, ERR_INVALID_PARAMETER);
+ int i = 0;
+ for (const NodePath &prop : p_properties) {
+ Object *obj = _get_prop_target(p_obj, prop);
+ ERR_FAIL_COND_V(!obj, FAILED);
+ obj->set(prop.get_concatenated_subnames(), p_state[i]);
+ i += 1;
+ }
+ return OK;
+}
+
+void MultiplayerSynchronizer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_root_path", "path"), &MultiplayerSynchronizer::set_root_path);
+ ClassDB::bind_method(D_METHOD("get_root_path"), &MultiplayerSynchronizer::get_root_path);
+ ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
+
+ ClassDB::bind_method(D_METHOD("set_replication_interval", "milliseconds"), &MultiplayerSynchronizer::set_replication_interval);
+ ClassDB::bind_method(D_METHOD("get_replication_interval"), &MultiplayerSynchronizer::get_replication_interval);
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "replication_interval", PROPERTY_HINT_RANGE, "0,5,0.001"), "set_replication_interval", "get_replication_interval");
+
+ ClassDB::bind_method(D_METHOD("set_replication_config", "config"), &MultiplayerSynchronizer::set_replication_config);
+ ClassDB::bind_method(D_METHOD("get_replication_config"), &MultiplayerSynchronizer::get_replication_config);
+ ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "SceneReplicationConfig"), "set_replication_config", "get_replication_config");
+}
+
+void MultiplayerSynchronizer::_notification(int p_what) {
+#ifdef TOOLS_ENABLED
+ if (Engine::get_singleton()->is_editor_hint()) {
+ return;
+ }
+#endif
+ if (root_path.is_empty()) {
+ return;
+ }
+ if (p_what == NOTIFICATION_ENTER_TREE) {
+ _start();
+ } else if (p_what == NOTIFICATION_EXIT_TREE) {
+ _stop();
+ }
+}
+
+void MultiplayerSynchronizer::set_replication_interval(double p_interval) {
+ ERR_FAIL_COND_MSG(p_interval < 0, "Interval must be greater or equal to 0 (where 0 means default)");
+ interval_msec = uint64_t(p_interval * 1000);
+}
+
+double MultiplayerSynchronizer::get_replication_interval() const {
+ return double(interval_msec) / 1000.0;
+}
+
+uint64_t MultiplayerSynchronizer::get_replication_interval_msec() const {
+ return interval_msec;
+}
+
+void MultiplayerSynchronizer::set_replication_config(Ref<SceneReplicationConfig> p_config) {
+ replication_config = p_config;
+}
+
+Ref<SceneReplicationConfig> MultiplayerSynchronizer::get_replication_config() {
+ return replication_config;
+}
+
+void MultiplayerSynchronizer::set_root_path(const NodePath &p_path) {
+ _stop();
+ root_path = p_path;
+ _start();
+}
+
+NodePath MultiplayerSynchronizer::get_root_path() const {
+ return root_path;
+}
+
+void MultiplayerSynchronizer::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
+ Node *node = is_inside_tree() ? get_node_or_null(root_path) : nullptr;
+ if (!node) {
+ Node::set_multiplayer_authority(p_peer_id, p_recursive);
+ return;
+ }
+ get_multiplayer()->replication_stop(node, this);
+ Node::set_multiplayer_authority(p_peer_id, p_recursive);
+ get_multiplayer()->replication_start(node, this);
+}
diff --git a/scene/multiplayer/multiplayer_synchronizer.h b/scene/multiplayer/multiplayer_synchronizer.h
new file mode 100644
index 0000000000..e856745379
--- /dev/null
+++ b/scene/multiplayer/multiplayer_synchronizer.h
@@ -0,0 +1,72 @@
+/*************************************************************************/
+/* multiplayer_synchronizer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef MULTIPLAYER_SYNCHRONIZER_H
+#define MULTIPLAYER_SYNCHRONIZER_H
+
+#include "scene/main/node.h"
+
+#include "scene/resources/scene_replication_config.h"
+
+class MultiplayerSynchronizer : public Node {
+ GDCLASS(MultiplayerSynchronizer, Node);
+
+private:
+ Ref<SceneReplicationConfig> replication_config;
+ NodePath root_path;
+ uint64_t interval_msec = 0;
+
+ static Object *_get_prop_target(Object *p_obj, const NodePath &p_prop);
+ void _start();
+ void _stop();
+
+protected:
+ static void _bind_methods();
+ void _notification(int p_what);
+
+public:
+ static Error get_state(const List<NodePath> &p_properties, Object *p_obj, Vector<Variant> &r_variant, Vector<const Variant *> &r_variant_ptrs);
+ static Error set_state(const List<NodePath> &p_properties, Object *p_obj, const Vector<Variant> &p_state);
+
+ void set_replication_interval(double p_interval);
+ double get_replication_interval() const;
+ uint64_t get_replication_interval_msec() const;
+
+ void set_replication_config(Ref<SceneReplicationConfig> p_config);
+ Ref<SceneReplicationConfig> get_replication_config();
+
+ void set_root_path(const NodePath &p_path);
+ NodePath get_root_path() const;
+ virtual void set_multiplayer_authority(int p_peer_id, bool p_recursive = true) override;
+
+ MultiplayerSynchronizer() {}
+};
+
+#endif // MULTIPLAYER_SYNCHRONIZER_H
diff --git a/scene/multiplayer/scene_replication_interface.cpp b/scene/multiplayer/scene_replication_interface.cpp
new file mode 100644
index 0000000000..7155935084
--- /dev/null
+++ b/scene/multiplayer/scene_replication_interface.cpp
@@ -0,0 +1,415 @@
+/*************************************************************************/
+/* scene_replication_interface.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "scene_replication_interface.h"
+
+#include "core/io/marshalls.h"
+#include "scene/main/node.h"
+#include "scene/multiplayer/multiplayer_spawner.h"
+#include "scene/multiplayer/multiplayer_synchronizer.h"
+
+#define MAKE_ROOM(m_amount) \
+ if (packet_cache.size() < m_amount) \
+ packet_cache.resize(m_amount);
+
+MultiplayerReplicationInterface *SceneReplicationInterface::_create(MultiplayerAPI *p_multiplayer) {
+ return memnew(SceneReplicationInterface(p_multiplayer));
+}
+
+void SceneReplicationInterface::make_default() {
+ MultiplayerAPI::create_default_replication_interface = _create;
+}
+
+void SceneReplicationInterface::_free_remotes(int p_id) {
+ const HashMap<uint32_t, ObjectID> remotes = rep_state->peer_get_remotes(p_id);
+ const uint32_t *k = nullptr;
+ while ((k = remotes.next(k))) {
+ Node *node = rep_state->get_node(remotes.get(*k));
+ ERR_CONTINUE(!node);
+ node->queue_delete();
+ }
+}
+
+void SceneReplicationInterface::on_peer_change(int p_id, bool p_connected) {
+ if (p_connected) {
+ rep_state->on_peer_change(p_id, p_connected);
+ for (const ObjectID &oid : rep_state->get_spawned_nodes()) {
+ _send_spawn(rep_state->get_node(oid), rep_state->get_spawner(oid), p_id);
+ }
+ for (const ObjectID &oid : rep_state->get_path_only_nodes()) {
+ Node *node = rep_state->get_node(oid);
+ MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
+ ERR_CONTINUE(!node || !sync);
+ if (sync->is_multiplayer_authority()) {
+ rep_state->peer_add_node(p_id, oid);
+ }
+ }
+ } else {
+ _free_remotes(p_id);
+ rep_state->on_peer_change(p_id, p_connected);
+ }
+}
+
+void SceneReplicationInterface::on_reset() {
+ for (int pid : rep_state->get_peers()) {
+ _free_remotes(pid);
+ }
+ rep_state->reset();
+}
+
+void SceneReplicationInterface::on_network_process() {
+ uint64_t msec = OS::get_singleton()->get_ticks_msec();
+ for (int peer : rep_state->get_peers()) {
+ _send_sync(peer, msec);
+ }
+}
+
+Error SceneReplicationInterface::on_spawn(Object *p_obj, Variant p_config) {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
+ MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
+ ERR_FAIL_COND_V(!spawner, ERR_INVALID_PARAMETER);
+ Error err = rep_state->config_add_spawn(node, spawner);
+ ERR_FAIL_COND_V(err != OK, err);
+ return _send_spawn(node, spawner, 0);
+}
+
+Error SceneReplicationInterface::on_despawn(Object *p_obj, Variant p_config) {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
+ MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(p_config.get_validated_object());
+ ERR_FAIL_COND_V(!p_obj || !spawner, ERR_INVALID_PARAMETER);
+ Error err = rep_state->config_del_spawn(node, spawner);
+ ERR_FAIL_COND_V(err != OK, err);
+ return _send_despawn(node, 0);
+}
+
+Error SceneReplicationInterface::on_replication_start(Object *p_obj, Variant p_config) {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
+ MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
+ ERR_FAIL_COND_V(!sync, ERR_INVALID_PARAMETER);
+ rep_state->config_add_sync(node, sync);
+ // Try to apply initial state if spawning (hack to apply if before ready).
+ if (pending_spawn == p_obj->get_instance_id()) {
+ pending_spawn = ObjectID(); // Make sure this only happens once.
+ const List<NodePath> props = sync->get_replication_config()->get_spawn_properties();
+ Vector<Variant> vars;
+ vars.resize(props.size());
+ int consumed;
+ Error err = MultiplayerAPI::decode_and_decompress_variants(vars, pending_buffer, pending_buffer_size, consumed);
+ ERR_FAIL_COND_V(err, err);
+ err = MultiplayerSynchronizer::set_state(props, node, vars);
+ ERR_FAIL_COND_V(err, err);
+ } else if (multiplayer->has_multiplayer_peer() && sync->is_multiplayer_authority()) {
+ // Either it's a spawn or a static sync, in any case add it to the list of known nodes.
+ rep_state->peer_add_node(0, p_obj->get_instance_id());
+ }
+ return OK;
+}
+
+Error SceneReplicationInterface::on_replication_stop(Object *p_obj, Variant p_config) {
+ Node *node = Object::cast_to<Node>(p_obj);
+ ERR_FAIL_COND_V(!node || p_config.get_type() != Variant::OBJECT, ERR_INVALID_PARAMETER);
+ MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(p_config.get_validated_object());
+ ERR_FAIL_COND_V(!p_obj || !sync, ERR_INVALID_PARAMETER);
+ return rep_state->config_del_sync(node, sync);
+}
+
+Error SceneReplicationInterface::_send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable) {
+ ERR_FAIL_COND_V(!p_buffer || p_size < 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!multiplayer, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!multiplayer->has_multiplayer_peer(), ERR_UNCONFIGURED);
+ Ref<MultiplayerPeer> peer = multiplayer->get_multiplayer_peer();
+ peer->set_target_peer(p_peer);
+ peer->set_transfer_channel(0);
+ peer->set_transfer_mode(p_reliable ? Multiplayer::TRANSFER_MODE_RELIABLE : Multiplayer::TRANSFER_MODE_UNRELIABLE);
+ return peer->put_packet(p_buffer, p_size);
+}
+
+Error SceneReplicationInterface::_send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer) {
+ ERR_FAIL_COND_V(p_peer < 0, ERR_BUG);
+ ERR_FAIL_COND_V(!multiplayer, ERR_BUG);
+ ERR_FAIL_COND_V(!p_spawner || !p_node, ERR_BUG);
+
+ const ObjectID oid = p_node->get_instance_id();
+ uint32_t nid = rep_state->ensure_net_id(oid);
+
+ // Prepare custom arg and scene_id
+ uint8_t scene_id = p_spawner->get_spawn_id(oid);
+ bool is_custom = scene_id == MultiplayerSpawner::INVALID_ID;
+ Variant spawn_arg = p_spawner->get_spawn_argument(oid);
+ int spawn_arg_size = 0;
+ if (is_custom) {
+ Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, nullptr, spawn_arg_size, false);
+ ERR_FAIL_COND_V(err, err);
+ }
+
+ // Prepare spawn state.
+ int state_size = 0;
+ Vector<Variant> state_vars;
+ Vector<const Variant *> state_varp;
+ MultiplayerSynchronizer *synchronizer = rep_state->get_synchronizer(oid);
+ if (synchronizer && synchronizer->get_replication_config().is_valid()) {
+ const List<NodePath> props = synchronizer->get_replication_config()->get_spawn_properties();
+ Error err = MultiplayerSynchronizer::get_state(props, p_node, state_vars, state_varp);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to retrieve spawn state.");
+ err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), nullptr, state_size);
+ ERR_FAIL_COND_V_MSG(err != OK, err, "Unable to encode spawn state.");
+ }
+
+ // Prepare simplified path.
+ const Node *root_node = multiplayer->get_root_node();
+ ERR_FAIL_COND_V(!root_node, ERR_UNCONFIGURED);
+ NodePath rel_path = (root_node->get_path()).rel_path_to(p_spawner->get_path());
+
+ int path_id = 0;
+ multiplayer->send_confirm_path(p_spawner, rel_path, p_peer, path_id);
+
+ // Encode name and parent ID.
+ CharString cname = p_node->get_name().operator String().utf8();
+ int nlen = encode_cstring(cname.get_data(), nullptr);
+ MAKE_ROOM(1 + 1 + 4 + 4 + 4 + nlen + (is_custom ? 4 + spawn_arg_size : 0) + state_size);
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_SPAWN;
+ ptr[1] = scene_id;
+ int ofs = 2;
+ ofs += encode_uint32(path_id, &ptr[ofs]);
+ ofs += encode_uint32(nid, &ptr[ofs]);
+ ofs += encode_uint32(nlen, &ptr[ofs]);
+ ofs += encode_cstring(cname.get_data(), &ptr[ofs]);
+ // Write args
+ if (is_custom) {
+ ofs += encode_uint32(spawn_arg_size, &ptr[ofs]);
+ Error err = MultiplayerAPI::encode_and_compress_variant(spawn_arg, &ptr[ofs], spawn_arg_size, false);
+ ERR_FAIL_COND_V(err, err);
+ ofs += spawn_arg_size;
+ }
+ // Write state.
+ if (state_size) {
+ Error err = MultiplayerAPI::encode_and_compress_variants(state_varp.ptrw(), state_varp.size(), &ptr[ofs], state_size);
+ ERR_FAIL_COND_V(err, err);
+ ofs += state_size;
+ }
+ Error err = _send_raw(ptr, ofs, p_peer, true);
+ ERR_FAIL_COND_V(err, err);
+ return rep_state->peer_add_node(p_peer, oid);
+}
+
+Error SceneReplicationInterface::_send_despawn(Node *p_node, int p_peer) {
+ const ObjectID oid = p_node->get_instance_id();
+ MAKE_ROOM(5);
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = (uint8_t)MultiplayerAPI::NETWORK_COMMAND_DESPAWN;
+ int ofs = 1;
+ uint32_t nid = rep_state->get_net_id(oid);
+ ofs += encode_uint32(nid, &ptr[ofs]);
+ Error err = _send_raw(ptr, ofs, p_peer, true);
+ ERR_FAIL_COND_V(err, err);
+ return rep_state->peer_del_node(p_peer, oid);
+}
+
+Error SceneReplicationInterface::on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
+ ERR_FAIL_COND_V_MSG(p_buffer_len < 14, ERR_INVALID_DATA, "Invalid spawn packet received");
+ int ofs = 1; // The spawn/despawn command.
+ uint8_t scene_id = p_buffer[ofs];
+ ofs += 1;
+ uint32_t node_target = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ MultiplayerSpawner *spawner = Object::cast_to<MultiplayerSpawner>(multiplayer->get_cached_node(p_from, node_target));
+ ERR_FAIL_COND_V(!spawner, ERR_DOES_NOT_EXIST);
+ ERR_FAIL_COND_V(p_from != spawner->get_multiplayer_authority(), ERR_UNAUTHORIZED);
+
+ uint32_t net_id = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ uint32_t name_len = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ ERR_FAIL_COND_V_MSG(name_len > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA, vformat("Invalid spawn packet size: %d, wants: %d", p_buffer_len, ofs + name_len));
+ ERR_FAIL_COND_V_MSG(name_len < 1, ERR_INVALID_DATA, "Zero spawn name size.");
+
+ // We need to make sure no trickery happens here, but we want to allow autogenerated ("@") node names.
+ const String name = String::utf8((const char *)&p_buffer[ofs], name_len);
+ ERR_FAIL_COND_V_MSG(name.validate_node_name() != name, ERR_INVALID_DATA, vformat("Invalid node name received: '%s'. Make sure to add nodes via 'add_child(node, true)' remotely.", name));
+ ofs += name_len;
+
+ // Check that we can spawn.
+ Node *parent = spawner->get_node_or_null(spawner->get_spawn_path());
+ ERR_FAIL_COND_V(!parent, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(parent->has_node(name), ERR_INVALID_DATA);
+
+ Node *node = nullptr;
+ if (scene_id == MultiplayerSpawner::INVALID_ID) {
+ // Custom spawn.
+ ERR_FAIL_COND_V(p_buffer_len - ofs < 4, ERR_INVALID_DATA);
+ uint32_t arg_size = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ ERR_FAIL_COND_V(arg_size > uint32_t(p_buffer_len - ofs), ERR_INVALID_DATA);
+ Variant v;
+ Error err = MultiplayerAPI::decode_and_decompress_variant(v, &p_buffer[ofs], arg_size, nullptr, false);
+ ERR_FAIL_COND_V(err != OK, err);
+ ofs += arg_size;
+ node = spawner->instantiate_custom(v);
+ } else {
+ // Scene based spawn.
+ node = spawner->instantiate_scene(scene_id);
+ }
+ ERR_FAIL_COND_V(!node, ERR_UNAUTHORIZED);
+ node->set_name(name);
+ rep_state->peer_add_remote(p_from, net_id, node, spawner);
+ // The initial state will be applied during the sync config (i.e. before _ready).
+ int state_len = p_buffer_len - ofs;
+ if (state_len) {
+ pending_spawn = node->get_instance_id();
+ pending_buffer = &p_buffer[ofs];
+ pending_buffer_size = state_len;
+ }
+ parent->add_child(node);
+ pending_spawn = ObjectID();
+ pending_buffer = nullptr;
+ pending_buffer_size = 0;
+ return OK;
+}
+
+Error SceneReplicationInterface::on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
+ ERR_FAIL_COND_V_MSG(p_buffer_len < 5, ERR_INVALID_DATA, "Invalid spawn packet received");
+ int ofs = 1; // The spawn/despawn command.
+ uint32_t net_id = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ Node *node = nullptr;
+ Error err = rep_state->peer_del_remote(p_from, net_id, &node);
+ ERR_FAIL_COND_V(err != OK, err);
+ ERR_FAIL_COND_V(!node, ERR_BUG);
+ node->queue_delete();
+ return OK;
+}
+
+void SceneReplicationInterface::_send_sync(int p_peer, uint64_t p_msec) {
+ const Set<ObjectID> &known = rep_state->get_known_nodes(p_peer);
+ if (known.is_empty()) {
+ return;
+ }
+ MAKE_ROOM(sync_mtu);
+ uint8_t *ptr = packet_cache.ptrw();
+ ptr[0] = MultiplayerAPI::NETWORK_COMMAND_SYNC;
+ int ofs = 1;
+ ofs += encode_uint16(rep_state->peer_sync_next(p_peer), &ptr[1]);
+ // Can only send updates for already notified nodes.
+ // This is a lazy implementation, we could optimize much more here with by grouping by replication config.
+ for (const ObjectID &oid : known) {
+ if (!rep_state->update_sync_time(oid, p_msec)) {
+ continue; // nothing to sync.
+ }
+ MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
+ ERR_CONTINUE(!sync);
+ Node *node = rep_state->get_node(oid);
+ ERR_CONTINUE(!node);
+ int size;
+ Vector<Variant> vars;
+ Vector<const Variant *> varp;
+ const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
+ Error err = MultiplayerSynchronizer::get_state(props, node, vars, varp);
+ ERR_CONTINUE_MSG(err != OK, "Unable to retrieve sync state.");
+ err = MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), nullptr, size);
+ ERR_CONTINUE_MSG(err != OK, "Unable to encode sync state.");
+ // TODO Handle single state above MTU.
+ ERR_CONTINUE_MSG(size > 3 + 4 + 4 + sync_mtu, vformat("Node states bigger then MTU will not be sent (%d > %d): %s", size, sync_mtu, node->get_path()));
+ if (ofs + 4 + 4 + size > sync_mtu) {
+ // Send what we got, and reset write.
+ _send_raw(packet_cache.ptr(), ofs, p_peer, false);
+ ofs = 3;
+ }
+ if (size) {
+ uint32_t net_id = rep_state->get_net_id(oid);
+ if (net_id == 0) {
+ // First time path based ID.
+ const Node *root_node = multiplayer->get_root_node();
+ ERR_FAIL_COND(!root_node);
+ NodePath rel_path = (root_node->get_path()).rel_path_to(sync->get_path());
+ int path_id = 0;
+ multiplayer->send_confirm_path(sync, rel_path, p_peer, path_id);
+ net_id = path_id;
+ rep_state->set_net_id(oid, net_id | 0x80000000);
+ }
+ ofs += encode_uint32(rep_state->get_net_id(oid), &ptr[ofs]);
+ ofs += encode_uint32(size, &ptr[ofs]);
+ MultiplayerAPI::encode_and_compress_variants(varp.ptrw(), varp.size(), &ptr[ofs], size);
+ ofs += size;
+ }
+ }
+ if (ofs > 3) {
+ // Got some left over to send.
+ _send_raw(packet_cache.ptr(), ofs, p_peer, false);
+ }
+}
+
+Error SceneReplicationInterface::on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) {
+ ERR_FAIL_COND_V_MSG(p_buffer_len < 11, ERR_INVALID_DATA, "Invalid sync packet received");
+ uint16_t time = decode_uint16(&p_buffer[1]);
+ int ofs = 3;
+ rep_state->peer_sync_recv(p_from, time);
+ while (ofs + 8 < p_buffer_len) {
+ uint32_t net_id = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ uint32_t size = decode_uint32(&p_buffer[ofs]);
+ ofs += 4;
+ Node *node = nullptr;
+ if (net_id & 0x80000000) {
+ MultiplayerSynchronizer *sync = Object::cast_to<MultiplayerSynchronizer>(multiplayer->get_cached_node(p_from, net_id & 0x7FFFFFFF));
+ ERR_FAIL_COND_V(!sync || sync->get_multiplayer_authority() != p_from, ERR_UNAUTHORIZED);
+ node = sync->get_node(sync->get_root_path());
+ } else {
+ node = rep_state->peer_get_remote(p_from, net_id);
+ }
+ if (!node) {
+ // Not received yet.
+ ofs += size;
+ continue;
+ }
+ const ObjectID oid = node->get_instance_id();
+ if (!rep_state->update_last_node_sync(oid, time)) {
+ // State is too old.
+ ofs += size;
+ continue;
+ }
+ MultiplayerSynchronizer *sync = rep_state->get_synchronizer(oid);
+ ERR_FAIL_COND_V(!sync, ERR_BUG);
+ ERR_FAIL_COND_V(size > uint32_t(p_buffer_len - ofs), ERR_BUG);
+ const List<NodePath> props = sync->get_replication_config()->get_sync_properties();
+ Vector<Variant> vars;
+ vars.resize(props.size());
+ int consumed;
+ Error err = MultiplayerAPI::decode_and_decompress_variants(vars, &p_buffer[ofs], size, consumed);
+ ERR_FAIL_COND_V(err, err);
+ err = MultiplayerSynchronizer::set_state(props, node, vars);
+ ERR_FAIL_COND_V(err, err);
+ ofs += size;
+ }
+ return OK;
+}
diff --git a/scene/multiplayer/scene_replication_interface.h b/scene/multiplayer/scene_replication_interface.h
new file mode 100644
index 0000000000..855878d029
--- /dev/null
+++ b/scene/multiplayer/scene_replication_interface.h
@@ -0,0 +1,84 @@
+/*************************************************************************/
+/* scene_replication_interface.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SCENE_TREE_REPLICATOR_INTERFACE_H
+#define SCENE_TREE_REPLICATOR_INTERFACE_H
+
+#include "core/multiplayer/multiplayer_api.h"
+
+#include "scene/multiplayer/scene_replication_state.h"
+
+class SceneReplicationInterface : public MultiplayerReplicationInterface {
+ GDCLASS(SceneReplicationInterface, MultiplayerReplicationInterface);
+
+private:
+ void _send_sync(int p_peer, uint64_t p_msec);
+ Error _send_spawn(Node *p_node, MultiplayerSpawner *p_spawner, int p_peer);
+ Error _send_despawn(Node *p_node, int p_peer);
+ Error _send_raw(const uint8_t *p_buffer, int p_size, int p_peer, bool p_reliable);
+
+ void _free_remotes(int p_peer);
+
+ Ref<SceneReplicationState> rep_state;
+ MultiplayerAPI *multiplayer;
+ PackedByteArray packet_cache;
+ int sync_mtu = 1350; // Highly dependent on underlying protocol.
+
+ // An hack to apply the initial state before ready.
+ ObjectID pending_spawn;
+ const uint8_t *pending_buffer = nullptr;
+ int pending_buffer_size = 0;
+
+protected:
+ static MultiplayerReplicationInterface *_create(MultiplayerAPI *p_multiplayer);
+
+public:
+ static void make_default();
+
+ virtual void on_reset() override;
+ virtual void on_peer_change(int p_id, bool p_connected) override;
+
+ virtual Error on_spawn(Object *p_obj, Variant p_config) override;
+ virtual Error on_despawn(Object *p_obj, Variant p_config) override;
+ virtual Error on_replication_start(Object *p_obj, Variant p_config) override;
+ virtual Error on_replication_stop(Object *p_obj, Variant p_config) override;
+ virtual void on_network_process() override;
+
+ virtual Error on_spawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
+ virtual Error on_despawn_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
+ virtual Error on_sync_receive(int p_from, const uint8_t *p_buffer, int p_buffer_len) override;
+
+ SceneReplicationInterface(MultiplayerAPI *p_multiplayer) {
+ rep_state.instantiate();
+ multiplayer = p_multiplayer;
+ }
+};
+
+#endif // SCENE_TREE_REPLICATOR_INTERFACE_H
diff --git a/scene/multiplayer/scene_replication_state.cpp b/scene/multiplayer/scene_replication_state.cpp
new file mode 100644
index 0000000000..b8dadeff24
--- /dev/null
+++ b/scene/multiplayer/scene_replication_state.cpp
@@ -0,0 +1,258 @@
+/*************************************************************************/
+/* scene_replication_state.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "scene/multiplayer/scene_replication_state.h"
+
+#include "core/multiplayer/multiplayer_api.h"
+#include "scene/multiplayer/multiplayer_spawner.h"
+#include "scene/multiplayer/multiplayer_synchronizer.h"
+#include "scene/scene_string_names.h"
+
+SceneReplicationState::TrackedNode &SceneReplicationState::_track(const ObjectID &p_id) {
+ if (!tracked_nodes.has(p_id)) {
+ tracked_nodes[p_id] = TrackedNode(p_id);
+ Node *node = Object::cast_to<Node>(ObjectDB::get_instance(p_id));
+ node->connect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack), varray(p_id), Node::CONNECT_ONESHOT);
+ }
+ return tracked_nodes[p_id];
+}
+
+void SceneReplicationState::_untrack(const ObjectID &p_id) {
+ if (tracked_nodes.has(p_id)) {
+ uint32_t net_id = tracked_nodes[p_id].net_id;
+ uint32_t peer = tracked_nodes[p_id].remote_peer;
+ tracked_nodes.erase(p_id);
+ // If it was spawned by a remote, remove it from the received nodes.
+ if (peer && peers_info.has(peer)) {
+ peers_info[peer].recv_nodes.erase(net_id);
+ }
+ // If we spawned or synced it, we need to remove it from any peer it was sent to.
+ if (net_id || peer == 0) {
+ const int *k = nullptr;
+ while ((k = peers_info.next(k))) {
+ peers_info.get(*k).known_nodes.erase(p_id);
+ }
+ }
+ }
+}
+
+const HashMap<uint32_t, ObjectID> SceneReplicationState::peer_get_remotes(int p_peer) const {
+ return peers_info.has(p_peer) ? peers_info[p_peer].recv_nodes : HashMap<uint32_t, ObjectID>();
+}
+
+bool SceneReplicationState::update_last_node_sync(const ObjectID &p_id, uint16_t p_time) {
+ TrackedNode *tnode = tracked_nodes.getptr(p_id);
+ ERR_FAIL_COND_V(!tnode, false);
+ if (p_time <= tnode->last_sync && tnode->last_sync - p_time < 32767) {
+ return false;
+ }
+ tnode->last_sync = p_time;
+ return true;
+}
+
+bool SceneReplicationState::update_sync_time(const ObjectID &p_id, uint64_t p_msec) {
+ TrackedNode *tnode = tracked_nodes.getptr(p_id);
+ ERR_FAIL_COND_V(!tnode, false);
+ MultiplayerSynchronizer *sync = get_synchronizer(p_id);
+ if (!sync) {
+ return false;
+ }
+ if (tnode->last_sync_msec == p_msec) {
+ return true;
+ }
+ if (p_msec >= tnode->last_sync_msec + sync->get_replication_interval_msec()) {
+ tnode->last_sync_msec = p_msec;
+ return true;
+ }
+ return false;
+}
+
+const Set<ObjectID> SceneReplicationState::get_known_nodes(int p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), Set<ObjectID>());
+ return peers_info[p_peer].known_nodes;
+}
+
+uint32_t SceneReplicationState::get_net_id(const ObjectID &p_id) const {
+ const TrackedNode *tnode = tracked_nodes.getptr(p_id);
+ ERR_FAIL_COND_V(!tnode, 0);
+ return tnode->net_id;
+}
+
+void SceneReplicationState::set_net_id(const ObjectID &p_id, uint32_t p_net_id) {
+ TrackedNode *tnode = tracked_nodes.getptr(p_id);
+ ERR_FAIL_COND(!tnode);
+ tnode->net_id = p_net_id;
+}
+
+uint32_t SceneReplicationState::ensure_net_id(const ObjectID &p_id) {
+ TrackedNode *tnode = tracked_nodes.getptr(p_id);
+ ERR_FAIL_COND_V(!tnode, 0);
+ if (tnode->net_id == 0) {
+ tnode->net_id = ++last_net_id;
+ }
+ return tnode->net_id;
+}
+
+void SceneReplicationState::on_peer_change(int p_peer, bool p_connected) {
+ if (p_connected) {
+ peers_info[p_peer] = PeerInfo();
+ known_peers.insert(p_peer);
+ } else {
+ peers_info.erase(p_peer);
+ known_peers.erase(p_peer);
+ }
+}
+
+void SceneReplicationState::reset() {
+ peers_info.clear();
+ known_peers.clear();
+ // Tracked nodes are cleared on deletion, here we only reset the ids so they can be later re-assigned.
+ const ObjectID *oid = nullptr;
+ while ((oid = tracked_nodes.next(oid))) {
+ TrackedNode &tobj = tracked_nodes[*oid];
+ tobj.net_id = 0;
+ tobj.remote_peer = 0;
+ tobj.last_sync = 0;
+ }
+}
+
+Error SceneReplicationState::config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
+ const ObjectID oid = p_node->get_instance_id();
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.spawner != ObjectID(), ERR_ALREADY_IN_USE);
+ tobj.spawner = p_spawner->get_instance_id();
+ spawned_nodes.insert(oid);
+ // The spawner may be notified after the synchronizer.
+ path_only_nodes.erase(oid);
+ return OK;
+}
+
+Error SceneReplicationState::config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner) {
+ const ObjectID oid = p_node->get_instance_id();
+ ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.spawner != p_spawner->get_instance_id(), ERR_INVALID_PARAMETER);
+ tobj.spawner = ObjectID();
+ spawned_nodes.erase(oid);
+ return OK;
+}
+
+Error SceneReplicationState::config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
+ const ObjectID oid = p_node->get_instance_id();
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.synchronizer != ObjectID(), ERR_ALREADY_IN_USE);
+ tobj.synchronizer = p_sync->get_instance_id();
+ // If it doesn't have a spawner, we might need to assign ID for this node using it's path.
+ if (tobj.spawner.is_null()) {
+ path_only_nodes.insert(oid);
+ }
+ return OK;
+}
+
+Error SceneReplicationState::config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync) {
+ const ObjectID oid = p_node->get_instance_id();
+ ERR_FAIL_COND_V(!is_tracked(oid), ERR_INVALID_PARAMETER);
+ TrackedNode &tobj = _track(oid);
+ ERR_FAIL_COND_V(tobj.synchronizer != p_sync->get_instance_id(), ERR_INVALID_PARAMETER);
+ tobj.synchronizer = ObjectID();
+ if (path_only_nodes.has(oid)) {
+ p_node->disconnect(SceneStringNames::get_singleton()->tree_exited, callable_mp(this, &SceneReplicationState::_untrack));
+ _untrack(oid);
+ path_only_nodes.erase(oid);
+ }
+ return OK;
+}
+
+Error SceneReplicationState::peer_add_node(int p_peer, const ObjectID &p_id) {
+ if (p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].known_nodes.insert(p_id);
+ } else {
+ const int *pid = nullptr;
+ while ((pid = peers_info.next(pid))) {
+ peers_info.get(*pid).known_nodes.insert(p_id);
+ }
+ }
+ return OK;
+}
+
+Error SceneReplicationState::peer_del_node(int p_peer, const ObjectID &p_id) {
+ if (p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_INVALID_PARAMETER);
+ peers_info[p_peer].known_nodes.erase(p_id);
+ } else {
+ const int *pid = nullptr;
+ while ((pid = peers_info.next(pid))) {
+ peers_info.get(*pid).known_nodes.erase(p_id);
+ }
+ }
+ return OK;
+}
+
+Node *SceneReplicationState::peer_get_remote(int p_peer, uint32_t p_net_id) {
+ PeerInfo *info = peers_info.getptr(p_peer);
+ return info && info->recv_nodes.has(p_net_id) ? Object::cast_to<Node>(ObjectDB::get_instance(info->recv_nodes[p_net_id])) : nullptr;
+}
+
+Error SceneReplicationState::peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner) {
+ ERR_FAIL_COND_V(!p_node || !p_spawner, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAVAILABLE);
+ PeerInfo &pinfo = peers_info[p_peer];
+ ObjectID oid = p_node->get_instance_id();
+ TrackedNode &tobj = _track(oid);
+ tobj.spawner = p_spawner->get_instance_id();
+ tobj.net_id = p_net_id;
+ tobj.remote_peer = p_peer;
+ tobj.last_sync = pinfo.last_recv_sync;
+ // Also track as a remote.
+ ERR_FAIL_COND_V(pinfo.recv_nodes.has(p_net_id), ERR_ALREADY_IN_USE);
+ pinfo.recv_nodes[p_net_id] = oid;
+ return OK;
+}
+
+Error SceneReplicationState::peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), ERR_UNAUTHORIZED);
+ PeerInfo &info = peers_info[p_peer];
+ ERR_FAIL_COND_V(!info.recv_nodes.has(p_net_id), ERR_UNAUTHORIZED);
+ *r_node = Object::cast_to<Node>(ObjectDB::get_instance(info.recv_nodes[p_net_id]));
+ info.recv_nodes.erase(p_net_id);
+ return OK;
+}
+
+uint16_t SceneReplicationState::peer_sync_next(int p_peer) {
+ ERR_FAIL_COND_V(!peers_info.has(p_peer), 0);
+ PeerInfo &info = peers_info[p_peer];
+ return ++info.last_sent_sync;
+}
+
+void SceneReplicationState::peer_sync_recv(int p_peer, uint16_t p_time) {
+ ERR_FAIL_COND(!peers_info.has(p_peer));
+ peers_info[p_peer].last_recv_sync = p_time;
+}
diff --git a/scene/multiplayer/scene_replication_state.h b/scene/multiplayer/scene_replication_state.h
new file mode 100644
index 0000000000..18e4d9fa39
--- /dev/null
+++ b/scene/multiplayer/scene_replication_state.h
@@ -0,0 +1,121 @@
+/*************************************************************************/
+/* scene_replication_state.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SCENE_REPLICATON_STATE_H
+#define SCENE_REPLICATON_STATE_H
+
+#include "core/object/ref_counted.h"
+
+class MultiplayerSpawner;
+class MultiplayerSynchronizer;
+class Node;
+
+class SceneReplicationState : public RefCounted {
+private:
+ struct TrackedNode {
+ ObjectID id;
+ uint32_t net_id = 0;
+ uint32_t remote_peer = 0;
+ ObjectID spawner;
+ ObjectID synchronizer;
+ uint16_t last_sync = 0;
+ uint64_t last_sync_msec = 0;
+
+ bool operator==(const ObjectID &p_other) { return id == p_other; }
+
+ Node *get_node() const { return id.is_valid() ? Object::cast_to<Node>(ObjectDB::get_instance(id)) : nullptr; }
+ MultiplayerSpawner *get_spawner() const { return spawner.is_valid() ? Object::cast_to<MultiplayerSpawner>(ObjectDB::get_instance(spawner)) : nullptr; }
+ MultiplayerSynchronizer *get_synchronizer() const { return synchronizer.is_valid() ? Object::cast_to<MultiplayerSynchronizer>(ObjectDB::get_instance(synchronizer)) : nullptr; }
+ TrackedNode() {}
+ TrackedNode(const ObjectID &p_id) { id = p_id; }
+ TrackedNode(const ObjectID &p_id, uint32_t p_net_id) {
+ id = p_id;
+ net_id = p_net_id;
+ }
+ };
+
+ struct PeerInfo {
+ Set<ObjectID> known_nodes;
+ HashMap<uint32_t, ObjectID> recv_nodes;
+ uint16_t last_sent_sync = 0;
+ uint16_t last_recv_sync = 0;
+ };
+
+ Set<int> known_peers;
+ uint32_t last_net_id = 0;
+ HashMap<ObjectID, TrackedNode> tracked_nodes;
+ HashMap<int, PeerInfo> peers_info;
+ Set<ObjectID> spawned_nodes;
+ Set<ObjectID> path_only_nodes;
+
+ TrackedNode &_track(const ObjectID &p_id);
+ void _untrack(const ObjectID &p_id);
+ bool is_tracked(const ObjectID &p_id) const { return tracked_nodes.has(p_id); }
+
+public:
+ const Set<int> get_peers() const { return known_peers; }
+ const Set<ObjectID> get_spawned_nodes() const { return spawned_nodes; }
+ const Set<ObjectID> get_path_only_nodes() const { return path_only_nodes; }
+
+ MultiplayerSynchronizer *get_synchronizer(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_synchronizer() : nullptr; }
+ MultiplayerSpawner *get_spawner(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_spawner() : nullptr; }
+ Node *get_node(const ObjectID &p_id) { return tracked_nodes.has(p_id) ? tracked_nodes[p_id].get_node() : nullptr; }
+ bool update_last_node_sync(const ObjectID &p_id, uint16_t p_time);
+ bool update_sync_time(const ObjectID &p_id, uint64_t p_msec);
+
+ const Set<ObjectID> get_known_nodes(int p_peer);
+ uint32_t get_net_id(const ObjectID &p_id) const;
+ void set_net_id(const ObjectID &p_id, uint32_t p_net_id);
+ uint32_t ensure_net_id(const ObjectID &p_id);
+
+ void reset();
+ void on_peer_change(int p_peer, bool p_connected);
+
+ Error config_add_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
+ Error config_del_spawn(Node *p_node, MultiplayerSpawner *p_spawner);
+
+ Error config_add_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
+ Error config_del_sync(Node *p_node, MultiplayerSynchronizer *p_sync);
+
+ Error peer_add_node(int p_peer, const ObjectID &p_id);
+ Error peer_del_node(int p_peer, const ObjectID &p_id);
+
+ const HashMap<uint32_t, ObjectID> peer_get_remotes(int p_peer) const;
+ Node *peer_get_remote(int p_peer, uint32_t p_net_id);
+ Error peer_add_remote(int p_peer, uint32_t p_net_id, Node *p_node, MultiplayerSpawner *p_spawner);
+ Error peer_del_remote(int p_peer, uint32_t p_net_id, Node **r_node);
+
+ uint16_t peer_sync_next(int p_peer);
+ void peer_sync_recv(int p_peer, uint16_t p_time);
+
+ SceneReplicationState() {}
+};
+
+#endif // SCENE_REPLICATON_STATE_H
diff --git a/scene/property_utils.cpp b/scene/property_utils.cpp
index 2540a633a9..a9b7e9acbe 100644
--- a/scene/property_utils.cpp
+++ b/scene/property_utils.cpp
@@ -130,7 +130,7 @@ Variant PropertyUtils::get_property_default_value(const Object *p_object, const
if (p != -1 && p < prop_str.length() - 1) {
bool all_digits = true;
for (int i = p + 1; i < prop_str.length(); i++) {
- if (prop_str[i] < '0' || prop_str[i] > '9') {
+ if (!is_digit(prop_str[i])) {
all_digits = false;
break;
}
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 838cef824b..f19b018982 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -134,6 +134,9 @@
#include "scene/main/timer.h"
#include "scene/main/viewport.h"
#include "scene/main/window.h"
+#include "scene/multiplayer/multiplayer_spawner.h"
+#include "scene/multiplayer/multiplayer_synchronizer.h"
+#include "scene/multiplayer/scene_replication_interface.h"
#include "scene/resources/audio_stream_sample.h"
#include "scene/resources/bit_map.h"
#include "scene/resources/box_shape_3d.h"
@@ -301,6 +304,8 @@ void register_scene_types() {
GDREGISTER_CLASS(SubViewport);
GDREGISTER_CLASS(ViewportTexture);
GDREGISTER_CLASS(HTTPRequest);
+ GDREGISTER_CLASS(MultiplayerSpawner);
+ GDREGISTER_CLASS(MultiplayerSynchronizer);
GDREGISTER_CLASS(Timer);
GDREGISTER_CLASS(CanvasLayer);
GDREGISTER_CLASS(CanvasModulate);
@@ -822,6 +827,8 @@ void register_scene_types() {
GDREGISTER_CLASS(Font);
GDREGISTER_CLASS(Curve);
+ GDREGISTER_CLASS(SceneReplicationConfig);
+
GDREGISTER_CLASS(TextLine);
GDREGISTER_CLASS(TextParagraph);
@@ -1050,6 +1057,7 @@ void register_scene_types() {
}
SceneDebugger::initialize();
+ SceneReplicationInterface::make_default();
NativeExtensionManager::get_singleton()->initialize_extensions(NativeExtension::INITIALIZATION_LEVEL_SCENE);
}
diff --git a/scene/resources/box_shape_3d.cpp b/scene/resources/box_shape_3d.cpp
index b97d378e02..a1ec9a2230 100644
--- a/scene/resources/box_shape_3d.cpp
+++ b/scene/resources/box_shape_3d.cpp
@@ -95,5 +95,5 @@ void BoxShape3D::_bind_methods() {
BoxShape3D::BoxShape3D() :
Shape3D(PhysicsServer3D::get_singleton()->shape_create(PhysicsServer3D::SHAPE_BOX)) {
- set_size(Vector3(2, 2, 2));
+ set_size(Vector3(1, 1, 1));
}
diff --git a/scene/resources/capsule_shape_3d.h b/scene/resources/capsule_shape_3d.h
index 967a413da4..4c039ab326 100644
--- a/scene/resources/capsule_shape_3d.h
+++ b/scene/resources/capsule_shape_3d.h
@@ -35,8 +35,8 @@
class CapsuleShape3D : public Shape3D {
GDCLASS(CapsuleShape3D, Shape3D);
- float radius = 1.0;
- float height = 3.0;
+ float radius = 0.5;
+ float height = 2.0;
protected:
static void _bind_methods();
diff --git a/scene/resources/cylinder_shape_3d.h b/scene/resources/cylinder_shape_3d.h
index 0211f2b08f..65427423c8 100644
--- a/scene/resources/cylinder_shape_3d.h
+++ b/scene/resources/cylinder_shape_3d.h
@@ -35,7 +35,7 @@
class CylinderShape3D : public Shape3D {
GDCLASS(CylinderShape3D, Shape3D);
- float radius = 1.0;
+ float radius = 0.5;
float height = 2.0;
protected:
diff --git a/scene/resources/primitive_meshes.h b/scene/resources/primitive_meshes.h
index aa9682bd80..3fc5fd4a16 100644
--- a/scene/resources/primitive_meshes.h
+++ b/scene/resources/primitive_meshes.h
@@ -106,8 +106,8 @@ class CapsuleMesh : public PrimitiveMesh {
GDCLASS(CapsuleMesh, PrimitiveMesh);
private:
- float radius = 1.0;
- float height = 3.0;
+ float radius = 0.5;
+ float height = 2.0;
int radial_segments = 64;
int rings = 8;
@@ -138,7 +138,7 @@ class BoxMesh : public PrimitiveMesh {
GDCLASS(BoxMesh, PrimitiveMesh);
private:
- Vector3 size = Vector3(2.0, 2.0, 2.0);
+ Vector3 size = Vector3(1, 1, 1);
int subdivide_w = 0;
int subdivide_h = 0;
int subdivide_d = 0;
@@ -171,8 +171,8 @@ class CylinderMesh : public PrimitiveMesh {
GDCLASS(CylinderMesh, PrimitiveMesh);
private:
- float top_radius = 1.0;
- float bottom_radius = 1.0;
+ float top_radius = 0.5;
+ float bottom_radius = 0.5;
float height = 2.0;
int radial_segments = 64;
int rings = 4;
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 1b81455d4c..6bb710b1d9 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -430,7 +430,7 @@ Error ResourceLoaderText::load() {
}
}
- if (path.find("://") == -1 && path.is_relative_path()) {
+ if (!path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path));
}
@@ -774,7 +774,7 @@ void ResourceLoaderText::get_dependencies(FileAccess *p_f, List<String> *p_depen
}
}
- if (!using_uid && path.find("://") == -1 && path.is_relative_path()) {
+ if (!using_uid && !path.contains("://") && path.is_relative_path()) {
// path is relative to file being loaded, so convert to a resource path
path = ProjectSettings::get_singleton()->localize_path(local_path.get_base_dir().plus_file(path));
}
diff --git a/scene/resources/scene_replication_config.cpp b/scene/resources/scene_replication_config.cpp
new file mode 100644
index 0000000000..2acc0f1922
--- /dev/null
+++ b/scene/resources/scene_replication_config.cpp
@@ -0,0 +1,187 @@
+/*************************************************************************/
+/* scene_replication_config.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#include "scene_replication_config.h"
+
+#include "core/multiplayer/multiplayer_api.h"
+#include "scene/main/node.h"
+
+bool SceneReplicationConfig::_set(const StringName &p_name, const Variant &p_value) {
+ String name = p_name;
+
+ if (name.begins_with("properties/")) {
+ int idx = name.get_slicec('/', 1).to_int();
+ String what = name.get_slicec('/', 2);
+
+ if (properties.size() == idx && what == "path") {
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::NODE_PATH, false);
+ NodePath path = p_value;
+ ERR_FAIL_COND_V(path.is_empty() || path.get_subname_count() == 0, false);
+ add_property(path);
+ return true;
+ }
+ ERR_FAIL_COND_V(p_value.get_type() != Variant::BOOL, false);
+ ERR_FAIL_INDEX_V(idx, properties.size(), false);
+ ReplicationProperty &prop = properties[idx];
+ if (what == "sync") {
+ prop.sync = p_value;
+ sync_props.push_back(prop.name);
+ return true;
+ } else if (what == "spawn") {
+ prop.spawn = p_value;
+ spawn_props.push_back(prop.name);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SceneReplicationConfig::_get(const StringName &p_name, Variant &r_ret) const {
+ String name = p_name;
+
+ if (name.begins_with("properties/")) {
+ int idx = name.get_slicec('/', 1).to_int();
+ String what = name.get_slicec('/', 2);
+ ERR_FAIL_INDEX_V(idx, properties.size(), false);
+ const ReplicationProperty &prop = properties[idx];
+ if (what == "path") {
+ r_ret = prop.name;
+ return true;
+ } else if (what == "sync") {
+ r_ret = prop.sync;
+ return true;
+ } else if (what == "spawn") {
+ r_ret = prop.spawn;
+ return true;
+ }
+ }
+ return false;
+}
+
+void SceneReplicationConfig::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (int i = 0; i < properties.size(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/path", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/spawn", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::STRING, "properties/" + itos(i) + "/sync", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ }
+}
+
+TypedArray<NodePath> SceneReplicationConfig::get_properties() const {
+ TypedArray<NodePath> paths;
+ for (const ReplicationProperty &prop : properties) {
+ paths.push_back(prop.name);
+ }
+ return paths;
+}
+
+void SceneReplicationConfig::add_property(const NodePath &p_path, int p_index) {
+ ERR_FAIL_COND(properties.find(p_path));
+
+ if (p_index < 0 || p_index == properties.size()) {
+ properties.push_back(ReplicationProperty(p_path));
+ return;
+ }
+
+ ERR_FAIL_INDEX(p_index, properties.size());
+
+ List<ReplicationProperty>::Element *I = properties.front();
+ int c = 0;
+ while (c < p_index) {
+ I = I->next();
+ c++;
+ }
+ properties.insert_before(I, ReplicationProperty(p_path));
+}
+
+void SceneReplicationConfig::remove_property(const NodePath &p_path) {
+ properties.erase(p_path);
+}
+
+int SceneReplicationConfig::property_get_index(const NodePath &p_path) const {
+ for (int i = 0; i < properties.size(); i++) {
+ if (properties[i].name == p_path) {
+ return i;
+ }
+ }
+ ERR_FAIL_V(-1);
+}
+
+bool SceneReplicationConfig::property_get_spawn(const NodePath &p_path) {
+ List<ReplicationProperty>::Element *E = properties.find(p_path);
+ ERR_FAIL_COND_V(!E, false);
+ return E->get().spawn;
+}
+
+void SceneReplicationConfig::property_set_spawn(const NodePath &p_path, bool p_enabled) {
+ List<ReplicationProperty>::Element *E = properties.find(p_path);
+ ERR_FAIL_COND(!E);
+ if (E->get().spawn == p_enabled) {
+ return;
+ }
+ E->get().spawn = p_enabled;
+ spawn_props.clear();
+ for (const ReplicationProperty &prop : properties) {
+ if (prop.spawn) {
+ spawn_props.push_back(p_path);
+ }
+ }
+}
+
+bool SceneReplicationConfig::property_get_sync(const NodePath &p_path) {
+ List<ReplicationProperty>::Element *E = properties.find(p_path);
+ ERR_FAIL_COND_V(!E, false);
+ return E->get().sync;
+}
+
+void SceneReplicationConfig::property_set_sync(const NodePath &p_path, bool p_enabled) {
+ List<ReplicationProperty>::Element *E = properties.find(p_path);
+ ERR_FAIL_COND(!E);
+ if (E->get().sync == p_enabled) {
+ return;
+ }
+ E->get().sync = p_enabled;
+ sync_props.clear();
+ for (const ReplicationProperty &prop : properties) {
+ if (prop.sync) {
+ sync_props.push_back(p_path);
+ }
+ }
+}
+
+void SceneReplicationConfig::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_properties"), &SceneReplicationConfig::get_properties);
+ ClassDB::bind_method(D_METHOD("add_property", "path", "index"), &SceneReplicationConfig::add_property, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("remove_property", "path"), &SceneReplicationConfig::remove_property);
+ ClassDB::bind_method(D_METHOD("property_get_index", "path"), &SceneReplicationConfig::property_get_index);
+ ClassDB::bind_method(D_METHOD("property_get_spawn", "path"), &SceneReplicationConfig::property_get_spawn);
+ ClassDB::bind_method(D_METHOD("property_set_spawn", "path", "enabled"), &SceneReplicationConfig::property_set_spawn);
+ ClassDB::bind_method(D_METHOD("property_get_sync", "path"), &SceneReplicationConfig::property_get_sync);
+ ClassDB::bind_method(D_METHOD("property_set_sync", "path", "enabled"), &SceneReplicationConfig::property_set_sync);
+}
diff --git a/scene/resources/scene_replication_config.h b/scene/resources/scene_replication_config.h
new file mode 100644
index 0000000000..b791be9414
--- /dev/null
+++ b/scene/resources/scene_replication_config.h
@@ -0,0 +1,90 @@
+/*************************************************************************/
+/* scene_replication_config.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef SCENE_REPLICATION_CONFIG_H
+#define SCENE_REPLICATION_CONFIG_H
+
+#include "core/io/resource.h"
+
+#include "core/variant/typed_array.h"
+
+class SceneReplicationConfig : public Resource {
+ GDCLASS(SceneReplicationConfig, Resource);
+ OBJ_SAVE_TYPE(SceneReplicationConfig);
+ RES_BASE_EXTENSION("repl");
+
+private:
+ struct ReplicationProperty {
+ NodePath name;
+ bool spawn = true;
+ bool sync = true;
+
+ bool operator==(const ReplicationProperty &p_to) {
+ return name == p_to.name;
+ }
+
+ ReplicationProperty() {}
+
+ ReplicationProperty(const NodePath &p_name) {
+ name = p_name;
+ }
+ };
+
+ List<ReplicationProperty> properties;
+ List<NodePath> spawn_props;
+ List<NodePath> sync_props;
+
+protected:
+ static void _bind_methods();
+
+ bool _set(const StringName &p_name, const Variant &p_value);
+ bool _get(const StringName &p_name, Variant &r_ret) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
+
+public:
+ TypedArray<NodePath> get_properties() const;
+
+ void add_property(const NodePath &p_path, int p_index = -1);
+ void remove_property(const NodePath &p_path);
+
+ int property_get_index(const NodePath &p_path) const;
+ bool property_get_spawn(const NodePath &p_path);
+ void property_set_spawn(const NodePath &p_path, bool p_enabled);
+
+ bool property_get_sync(const NodePath &p_path);
+ void property_set_sync(const NodePath &p_path, bool p_enabled);
+
+ const List<NodePath> &get_spawn_properties() { return spawn_props; }
+ const List<NodePath> &get_sync_properties() { return sync_props; }
+
+ SceneReplicationConfig() {}
+};
+
+#endif // SCENE_REPLICATION_CONFIG_H
diff --git a/scene/resources/syntax_highlighter.cpp b/scene/resources/syntax_highlighter.cpp
index e0aa21ac37..f1eddd8ffc 100644
--- a/scene/resources/syntax_highlighter.cpp
+++ b/scene/resources/syntax_highlighter.cpp
@@ -116,14 +116,6 @@ void SyntaxHighlighter::_bind_methods() {
////////////////////////////////////////////////////////////////////////////////
-static bool _is_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_';
-}
-
-static bool _is_hex_symbol(char32_t c) {
- return ((c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
-}
-
Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
Dictionary color_map;
@@ -166,7 +158,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
color = font_color;
bool is_char = !is_symbol(str[j]);
bool is_a_symbol = is_symbol(str[j]);
- bool is_number = (str[j] >= '0' && str[j] <= '9');
+ bool is_number = is_digit(str[j]);
/* color regions */
if (is_a_symbol || in_region != -1) {
@@ -304,7 +296,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
}
// Allow ABCDEF in hex notation.
- if (is_hex_notation && (_is_hex_symbol(str[j]) || is_number)) {
+ if (is_hex_notation && (is_hex_digit(str[j]) || is_number)) {
is_number = true;
} else {
is_hex_notation = false;
@@ -321,7 +313,7 @@ Dictionary CodeHighlighter::_get_line_syntax_highlighting_impl(int p_line) {
}
}
- if (!in_word && _is_char(str[j]) && !is_number) {
+ if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !is_number) {
in_word = true;
}
diff --git a/scene/resources/theme.cpp b/scene/resources/theme.cpp
index 8da287042e..f962e55666 100644
--- a/scene/resources/theme.cpp
+++ b/scene/resources/theme.cpp
@@ -46,7 +46,7 @@ int Theme::fallback_font_size = 16;
bool Theme::_set(const StringName &p_name, const Variant &p_value) {
String sname = p_name;
- if (sname.find("/") != -1) {
+ if (sname.contains("/")) {
String type = sname.get_slicec('/', 1);
String theme_type = sname.get_slicec('/', 0);
String name = sname.get_slicec('/', 2);
@@ -78,7 +78,7 @@ bool Theme::_set(const StringName &p_name, const Variant &p_value) {
bool Theme::_get(const StringName &p_name, Variant &r_ret) const {
String sname = p_name;
- if (sname.find("/") != -1) {
+ if (sname.contains("/")) {
String type = sname.get_slicec('/', 1);
String theme_type = sname.get_slicec('/', 0);
String name = sname.get_slicec('/', 2);
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index f41058d59c..dd2f3d2202 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -1106,10 +1106,6 @@ String VisualShader::generate_preview_shader(Type p_type, int p_node, int p_port
return final_code;
}
-#define IS_INITIAL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z'))
-
-#define IS_SYMBOL_CHAR(m_d) (((m_d) >= 'a' && (m_d) <= 'z') || ((m_d) >= 'A' && (m_d) <= 'Z') || ((m_d) >= '0' && (m_d) <= '9') || (m_d) == '_')
-
String VisualShader::validate_port_name(const String &p_port_name, VisualShaderNode *p_node, int p_port_id, bool p_output) const {
String name = p_port_name;
@@ -1117,7 +1113,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
return String();
}
- while (name.length() && !IS_INITIAL_CHAR(name[0])) {
+ while (name.length() && !is_ascii_char(name[0])) {
name = name.substr(1, name.length() - 1);
}
@@ -1125,7 +1121,7 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
String valid_name;
for (int i = 0; i < name.length(); i++) {
- if (IS_SYMBOL_CHAR(name[i])) {
+ if (is_ascii_identifier_char(name[i])) {
valid_name += String::chr(name[i]);
} else if (name[i] == ' ') {
valid_name += "_";
@@ -1162,14 +1158,14 @@ String VisualShader::validate_port_name(const String &p_port_name, VisualShaderN
String VisualShader::validate_uniform_name(const String &p_name, const Ref<VisualShaderNodeUniform> &p_uniform) const {
String name = p_name; //validate name first
- while (name.length() && !IS_INITIAL_CHAR(name[0])) {
+ while (name.length() && !is_ascii_char(name[0])) {
name = name.substr(1, name.length() - 1);
}
if (!name.is_empty()) {
String valid_name;
for (int i = 0; i < name.length(); i++) {
- if (IS_SYMBOL_CHAR(name[i])) {
+ if (is_ascii_identifier_char(name[i])) {
valid_name += String::chr(name[i]);
} else if (name[i] == ' ') {
valid_name += "_";
@@ -1206,7 +1202,7 @@ String VisualShader::validate_uniform_name(const String &p_name, const Ref<Visua
if (exists) {
//remove numbers, put new and try again
attempt++;
- while (name.length() && name[name.length() - 1] >= '0' && name[name.length() - 1] <= '9') {
+ while (name.length() && is_digit(name[name.length() - 1])) {
name = name.substr(0, name.length() - 1);
}
ERR_FAIL_COND_V(name.is_empty(), String());
@@ -3312,7 +3308,7 @@ String VisualShaderNodeOutput::generate_code(Shader::Mode p_mode, VisualShader::
if (ports[idx].mode == shader_mode && ports[idx].shader_type == shader_type) {
if (!p_input_vars[count].is_empty()) {
String s = ports[idx].string;
- if (s.find(":") != -1) {
+ if (s.contains(":")) {
code += " " + s.get_slicec(':', 0) + " = " + p_input_vars[count] + "." + s.get_slicec(':', 1) + ";\n";
} else {
code += " " + s + " = " + p_input_vars[count] + ";\n";
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index 5ded5cf214..d880df2a9b 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -380,6 +380,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("screen_get_scale", "screen"), &DisplayServer::screen_get_scale, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_is_touchscreen", "screen"), &DisplayServer::screen_is_touchscreen, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_max_scale"), &DisplayServer::screen_get_max_scale);
+ ClassDB::bind_method(D_METHOD("screen_get_refresh_rate", "screen"), &DisplayServer::screen_get_refresh_rate, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_set_orientation", "orientation", "screen"), &DisplayServer::screen_set_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
ClassDB::bind_method(D_METHOD("screen_get_orientation", "screen"), &DisplayServer::screen_get_orientation, DEFVAL(SCREEN_OF_MAIN_WINDOW));
@@ -539,6 +540,7 @@ void DisplayServer::_bind_methods() {
BIND_ENUM_CONSTANT(WINDOW_MODE_MINIMIZED);
BIND_ENUM_CONSTANT(WINDOW_MODE_MAXIMIZED);
BIND_ENUM_CONSTANT(WINDOW_MODE_FULLSCREEN);
+ BIND_ENUM_CONSTANT(WINDOW_MODE_EXCLUSIVE_FULLSCREEN);
BIND_ENUM_CONSTANT(WINDOW_FLAG_RESIZE_DISABLED);
BIND_ENUM_CONSTANT(WINDOW_FLAG_BORDERLESS);
diff --git a/servers/display_server.h b/servers/display_server.h
index 8c6586dc20..19bb111094 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -53,7 +53,8 @@ public:
WINDOW_MODE_WINDOWED,
WINDOW_MODE_MINIMIZED,
WINDOW_MODE_MAXIMIZED,
- WINDOW_MODE_FULLSCREEN
+ WINDOW_MODE_FULLSCREEN,
+ WINDOW_MODE_EXCLUSIVE_FULLSCREEN,
};
// Keep the VSyncMode enum values in sync with the `display/window/vsync/vsync_mode`
@@ -174,6 +175,8 @@ public:
SCREEN_OF_MAIN_WINDOW = -1
};
+ const float SCREEN_REFRESH_RATE_FALLBACK = 60.0; // Returned by screen_get_refresh_rate if the method fails. Most screens are 60hz as of 2022.
+
virtual int get_screen_count() const = 0;
virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
@@ -188,6 +191,7 @@ public:
}
return scale;
}
+ virtual float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const = 0;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
// Keep the ScreenOrientation enum values in sync with the `display/window/handheld/orientation`
diff --git a/servers/display_server_headless.h b/servers/display_server_headless.h
index 4ef9dc622f..f74a8fad23 100644
--- a/servers/display_server_headless.h
+++ b/servers/display_server_headless.h
@@ -62,6 +62,7 @@ public:
int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override { return 96; /* 0 might cause issues */ }
float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override { return 1; }
float screen_get_max_scale() const override { return 1; }
+ float screen_get_refresh_rate(int p_screen = SCREEN_OF_MAIN_WINDOW) const override { return SCREEN_REFRESH_RATE_FALLBACK; }
Vector<DisplayServer::WindowID> get_window_list() const override { return Vector<DisplayServer::WindowID>(); }
diff --git a/servers/rendering/rendering_device_binds.cpp b/servers/rendering/rendering_device_binds.cpp
index 19cde610ba..e50ac42027 100644
--- a/servers/rendering/rendering_device_binds.cpp
+++ b/servers/rendering/rendering_device_binds.cpp
@@ -99,11 +99,11 @@ Error RDShaderFile::parse_versions_from_text(const String &p_text, const String
if (reading_versions) {
String l = line.strip_edges();
if (!l.is_empty()) {
- if (l.find("=") == -1) {
+ if (!l.contains("=")) {
base_error = "Missing `=` in '" + l + "'. Version syntax is `version = \"<defines with C escaping>\";`.";
break;
}
- if (l.find(";") == -1) {
+ if (!l.contains(";")) {
// We don't require a semicolon per se, but it's needed for clang-format to handle things properly.
base_error = "Missing `;` in '" + l + "'. Version syntax is `version = \"<defines with C escaping>\";`.";
break;
diff --git a/servers/rendering/shader_compiler.cpp b/servers/rendering/shader_compiler.cpp
index 78e81eac0b..5b43ca4bcd 100644
--- a/servers/rendering/shader_compiler.cpp
+++ b/servers/rendering/shader_compiler.cpp
@@ -182,7 +182,7 @@ static String _mkid(const String &p_id) {
static String f2sp0(float p_float) {
String num = rtoss(p_float);
- if (num.find(".") == -1 && num.find("e") == -1) {
+ if (!num.contains(".") && !num.contains("e")) {
num += ".0";
}
return num;
@@ -813,6 +813,9 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
if (bnode->statements[i]->type == SL::Node::TYPE_CONTROL_FLOW || bnode->single_statement) {
code += scode; //use directly
+ if (bnode->use_comma_between_statements && i + 1 < bnode->statements.size()) {
+ code += ",";
+ }
} else {
code += _mktab(p_level) + scode + ";\n";
}
@@ -1287,10 +1290,10 @@ String ShaderCompiler::_dump_node_code(const SL::Node *p_node, int p_level, Gene
code += _dump_node_code(cfnode->blocks[0], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning);
} else if (cfnode->flow_op == SL::FLOW_OP_FOR) {
String left = _dump_node_code(cfnode->blocks[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
- String middle = _dump_node_code(cfnode->expressions[0], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
- String right = _dump_node_code(cfnode->expressions[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ String middle = _dump_node_code(cfnode->blocks[1], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
+ String right = _dump_node_code(cfnode->blocks[2], p_level, r_gen_code, p_actions, p_default_actions, p_assigning);
code += _mktab(p_level) + "for (" + left + ";" + middle + ";" + right + ")\n";
- code += _dump_node_code(cfnode->blocks[1], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning);
+ code += _dump_node_code(cfnode->blocks[3], p_level + 1, r_gen_code, p_actions, p_default_actions, p_assigning);
} else if (cfnode->flow_op == SL::FLOW_OP_RETURN) {
if (cfnode->expressions.size()) {
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index cf77d2156f..a433c666f3 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -35,18 +35,6 @@
#define HAS_WARNING(flag) (warning_flags & flag)
-static bool _is_text_char(char32_t c) {
- return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';
-}
-
-static bool _is_number(char32_t c) {
- return (c >= '0' && c <= '9');
-}
-
-static bool _is_hex(char32_t c) {
- return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
-}
-
String ShaderLanguage::get_operator_text(Operator p_op) {
static const char *op_names[OP_MAX] = { "==",
"!=",
@@ -543,7 +531,7 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
default: {
char_idx--; //go back one, since we have no idea what this is
- if (_is_number(GETCHAR(0)) || (GETCHAR(0) == '.' && _is_number(GETCHAR(1)))) {
+ if (is_digit(GETCHAR(0)) || (GETCHAR(0) == '.' && is_digit(GETCHAR(1)))) {
// parse number
bool hexa_found = false;
bool period_found = false;
@@ -584,7 +572,7 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
const char32_t symbol = String::char_lowercase(GETCHAR(i));
bool error = false;
- if (_is_number(symbol)) {
+ if (is_digit(symbol)) {
if (end_suffix_found) {
error = true;
}
@@ -617,8 +605,8 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
break;
}
}
- } else if (!hexa_found || !_is_hex(symbol)) {
- if (_is_text_char(symbol)) {
+ } else if (!hexa_found || !is_hex_digit(symbol)) {
+ if (is_ascii_identifier_char(symbol)) {
error = true;
} else {
break;
@@ -649,7 +637,7 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
return _make_token(TK_ERROR, "Invalid (hexadecimal) numeric constant");
}
} else if (period_found || exponent_found || float_suffix_found) { // Float
- if (exponent_found && (!_is_number(last_char) && last_char != 'f')) { // checks for eg: "2E", "2E-", "2E+"
+ if (exponent_found && (!is_digit(last_char) && last_char != 'f')) { // checks for eg: "2E", "2E-", "2E+"
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
if (period_found) {
@@ -660,7 +648,7 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
}
} else {
//checks for eg. "1." or "1.99" notations
- if (last_char != '.' && !_is_number(last_char)) {
+ if (last_char != '.' && !is_digit(last_char)) {
return _make_token(TK_ERROR, "Invalid (float) numeric constant");
}
}
@@ -723,11 +711,11 @@ ShaderLanguage::Token ShaderLanguage::_get_token() {
return _make_token(TK_PERIOD);
}
- if (_is_text_char(GETCHAR(0))) {
+ if (is_ascii_identifier_char(GETCHAR(0))) {
// parse identifier
String str;
- while (_is_text_char(GETCHAR(0))) {
+ while (is_ascii_identifier_char(GETCHAR(0))) {
str += char32_t(GETCHAR(0));
char_idx++;
}
@@ -5252,21 +5240,32 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
expression.push_back(e);
continue;
} else {
- if (tk.type != TK_SEMICOLON) {
- _set_error(vformat(RTR("Expected expression, found: '%s'."), get_token_text(tk)));
- return nullptr;
- } else {
-#ifdef DEBUG_ENABLED
- if (check_warnings && HAS_WARNING(ShaderWarning::FORMATTING_ERROR_FLAG)) {
- _add_line_warning(ShaderWarning::FORMATTING_ERROR, RTR("Empty statement. Remove ';' to fix this warning."));
- }
-#endif // DEBUG_ENABLED
+ bool valid = false;
+ if (p_block && p_block->block_type == BlockNode::BLOCK_TYPE_FOR_EXPRESSION && tk.type == TK_PARENTHESIS_CLOSE) {
+ valid = true;
_set_tkpos(prepos);
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_EMPTY;
expr = func;
}
+ if (!valid) {
+ if (tk.type != TK_SEMICOLON) {
+ _set_error(vformat(RTR("Expected expression, found: '%s'."), get_token_text(tk)));
+ return nullptr;
+ } else {
+#ifdef DEBUG_ENABLED
+ if (check_warnings && HAS_WARNING(ShaderWarning::FORMATTING_ERROR_FLAG)) {
+ _add_line_warning(ShaderWarning::FORMATTING_ERROR, RTR("Empty statement. Remove ';' to fix this warning."));
+ }
+#endif // DEBUG_ENABLED
+ _set_tkpos(prepos);
+
+ OperatorNode *func = alloc_node<OperatorNode>();
+ func->op = OP_EMPTY;
+ expr = func;
+ }
+ }
}
ERR_FAIL_COND_V(!expr, nullptr);
@@ -6769,14 +6768,9 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
array_size = 0;
}
- if (tk.type == TK_COMMA) {
- if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR) {
- _set_error(vformat("Multiple declarations in '%s' loop are not supported.", "for"));
- return ERR_PARSE_ERROR;
- }
- } else if (tk.type == TK_SEMICOLON) {
+ if (tk.type == TK_SEMICOLON) {
break;
- } else {
+ } else if (tk.type != TK_COMMA) {
_set_expected_error(",", ";");
return ERR_PARSE_ERROR;
}
@@ -7138,43 +7132,35 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
cf->flow_op = FLOW_OP_FOR;
BlockNode *init_block = alloc_node<BlockNode>();
- init_block->block_type = BlockNode::BLOCK_TYPE_FOR;
+ init_block->block_type = BlockNode::BLOCK_TYPE_FOR_INIT;
init_block->parent_block = p_block;
init_block->single_statement = true;
cf->blocks.push_back(init_block);
- if (_parse_block(init_block, p_function_info, true, false, false) != OK) {
- return ERR_PARSE_ERROR;
- }
-
- Node *n = _parse_and_reduce_expression(init_block, p_function_info);
- if (!n) {
- return ERR_PARSE_ERROR;
- }
-
- if (n->get_datatype() != TYPE_BOOL) {
- _set_error(RTR("The middle expression is expected to be boolean."));
- return ERR_PARSE_ERROR;
- }
-
- tk = _get_token();
- if (tk.type != TK_SEMICOLON) {
- _set_expected_error(";");
- return ERR_PARSE_ERROR;
+ Error err = _parse_block(init_block, p_function_info, true, false, false);
+ if (err != OK) {
+ return err;
}
- cf->expressions.push_back(n);
-
- n = _parse_and_reduce_expression(init_block, p_function_info);
- if (!n) {
- return ERR_PARSE_ERROR;
+ BlockNode *condition_block = alloc_node<BlockNode>();
+ condition_block->block_type = BlockNode::BLOCK_TYPE_FOR_CONDITION;
+ condition_block->parent_block = init_block;
+ condition_block->single_statement = true;
+ condition_block->use_comma_between_statements = true;
+ cf->blocks.push_back(condition_block);
+ err = _parse_block(condition_block, p_function_info, true, false, false);
+ if (err != OK) {
+ return err;
}
- cf->expressions.push_back(n);
-
- tk = _get_token();
- if (tk.type != TK_PARENTHESIS_CLOSE) {
- _set_expected_error(")");
- return ERR_PARSE_ERROR;
+ BlockNode *expression_block = alloc_node<BlockNode>();
+ expression_block->block_type = BlockNode::BLOCK_TYPE_FOR_EXPRESSION;
+ expression_block->parent_block = init_block;
+ expression_block->single_statement = true;
+ expression_block->use_comma_between_statements = true;
+ cf->blocks.push_back(expression_block);
+ err = _parse_block(expression_block, p_function_info, true, false, false);
+ if (err != OK) {
+ return err;
}
BlockNode *block = alloc_node<BlockNode>();
@@ -7182,8 +7168,8 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
cf->blocks.push_back(block);
p_block->statements.push_back(cf);
- Error err = _parse_block(block, p_function_info, true, true, true);
- if (err) {
+ err = _parse_block(block, p_function_info, true, true, true);
+ if (err != OK) {
return err;
}
@@ -7333,10 +7319,48 @@ Error ShaderLanguage::_parse_block(BlockNode *p_block, const FunctionInfo &p_fun
if (!expr) {
return ERR_PARSE_ERROR;
}
+
+ bool empty = false;
+
+ if (expr->type == Node::TYPE_OPERATOR) {
+ OperatorNode *op = static_cast<OperatorNode *>(expr);
+ if (op->op == OP_EMPTY) {
+ empty = true;
+ }
+ }
+ if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR_INIT) {
+ if (!empty && expr->type != BlockNode::TYPE_VARIABLE_DECLARATION) {
+ _set_error(RTR("The left expression is expected to be a variable declaration."));
+ return ERR_PARSE_ERROR;
+ }
+ }
+ if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR_CONDITION) {
+ if (!empty && expr->get_datatype() != TYPE_BOOL) {
+ _set_error(RTR("The middle expression is expected to be boolean."));
+ return ERR_PARSE_ERROR;
+ }
+ }
+
p_block->statements.push_back(expr);
tk = _get_token();
- if (tk.type != TK_SEMICOLON) {
+ if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR_CONDITION) {
+ if (tk.type == TK_COMMA) {
+ continue;
+ }
+ if (tk.type != TK_SEMICOLON) {
+ _set_expected_error(",", ";");
+ return ERR_PARSE_ERROR;
+ }
+ } else if (p_block->block_type == BlockNode::BLOCK_TYPE_FOR_EXPRESSION) {
+ if (tk.type == TK_COMMA) {
+ continue;
+ }
+ if (tk.type != TK_PARENTHESIS_CLOSE) {
+ _set_expected_error(",", ")");
+ return ERR_PARSE_ERROR;
+ }
+ } else if (tk.type != TK_SEMICOLON) {
_set_expected_error(";");
return ERR_PARSE_ERROR;
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 1307eeac2b..f39b21621d 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -504,7 +504,9 @@ public:
enum BlockType {
BLOCK_TYPE_STANDART,
- BLOCK_TYPE_FOR,
+ BLOCK_TYPE_FOR_INIT,
+ BLOCK_TYPE_FOR_CONDITION,
+ BLOCK_TYPE_FOR_EXPRESSION,
BLOCK_TYPE_SWITCH,
BLOCK_TYPE_CASE,
BLOCK_TYPE_DEFAULT,
@@ -526,6 +528,7 @@ public:
Map<StringName, Variable> variables;
List<Node *> statements;
bool single_statement = false;
+ bool use_comma_between_statements = false;
BlockNode() :
Node(TYPE_BLOCK) {}
diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp
index db1f589334..3c5faa4ef7 100644
--- a/servers/text/text_server_extension.cpp
+++ b/servers/text/text_server_extension.cpp
@@ -230,8 +230,8 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_is_ready, "shaped");
- GDVIRTUAL_BIND(_shaped_text_get_glyphs, "shaped", "r_glyphs");
- GDVIRTUAL_BIND(_shaped_text_sort_logical, "shaped", "r_glyphs");
+ GDVIRTUAL_BIND(_shaped_text_get_glyphs, "shaped");
+ GDVIRTUAL_BIND(_shaped_text_sort_logical, "shaped");
GDVIRTUAL_BIND(_shaped_text_get_glyph_count, "shaped");
GDVIRTUAL_BIND(_shaped_text_get_range, "shaped");
@@ -243,7 +243,7 @@ void TextServerExtension::_bind_methods() {
GDVIRTUAL_BIND(_shaped_text_get_trim_pos, "shaped");
GDVIRTUAL_BIND(_shaped_text_get_ellipsis_pos, "shaped");
GDVIRTUAL_BIND(_shaped_text_get_ellipsis_glyph_count, "shaped");
- GDVIRTUAL_BIND(_shaped_text_get_ellipsis_glyphs, "shaped", "r_glyphs");
+ GDVIRTUAL_BIND(_shaped_text_get_ellipsis_glyphs, "shaped");
GDVIRTUAL_BIND(_shaped_text_overrun_trim_to_width, "shaped", "width", "trim_flags");
@@ -505,7 +505,7 @@ void TextServerExtension::font_set_hinting(RID p_font_rid, TextServer::Hinting p
}
TextServer::Hinting TextServerExtension::font_get_hinting(RID p_font_rid) const {
- int ret;
+ TextServer::Hinting ret;
if (GDVIRTUAL_CALL(_font_get_hinting, p_font_rid, ret)) {
return (TextServer::Hinting)ret;
}
@@ -955,7 +955,7 @@ void TextServerExtension::shaped_text_set_direction(RID p_shaped, TextServer::Di
}
TextServer::Direction TextServerExtension::shaped_text_get_direction(RID p_shaped) const {
- int ret;
+ TextServer::Direction ret;
if (GDVIRTUAL_CALL(_shaped_text_get_direction, p_shaped, ret)) {
return (TextServer::Direction)ret;
}
@@ -963,7 +963,7 @@ TextServer::Direction TextServerExtension::shaped_text_get_direction(RID p_shape
}
TextServer::Direction TextServerExtension::shaped_text_get_inferred_direction(RID p_shaped) const {
- int ret;
+ TextServer::Direction ret;
if (GDVIRTUAL_CALL(_shaped_text_get_inferred_direction, p_shaped, ret)) {
return (TextServer::Direction)ret;
}
@@ -975,7 +975,7 @@ void TextServerExtension::shaped_text_set_orientation(RID p_shaped, TextServer::
}
TextServer::Orientation TextServerExtension::shaped_text_get_orientation(RID p_shaped) const {
- int ret;
+ TextServer::Orientation ret;
if (GDVIRTUAL_CALL(_shaped_text_get_orientation, p_shaped, ret)) {
return (TextServer::Orientation)ret;
}
@@ -1139,16 +1139,16 @@ bool TextServerExtension::shaped_text_is_ready(RID p_shaped) const {
}
const Glyph *TextServerExtension::shaped_text_get_glyphs(RID p_shaped) const {
- const Glyph *ret;
- if (GDVIRTUAL_CALL(_shaped_text_get_glyphs, p_shaped, &ret)) {
+ GDNativePtr<Glyph> ret;
+ if (GDVIRTUAL_CALL(_shaped_text_get_glyphs, p_shaped, ret)) {
return ret;
}
return nullptr;
}
const Glyph *TextServerExtension::shaped_text_sort_logical(RID p_shaped) {
- const Glyph *ret;
- if (GDVIRTUAL_CALL(_shaped_text_sort_logical, p_shaped, &ret)) {
+ GDNativePtr<Glyph> ret;
+ if (GDVIRTUAL_CALL(_shaped_text_sort_logical, p_shaped, ret)) {
return ret;
}
return nullptr;
@@ -1211,8 +1211,8 @@ int TextServerExtension::shaped_text_get_ellipsis_pos(RID p_shaped) const {
}
const Glyph *TextServerExtension::shaped_text_get_ellipsis_glyphs(RID p_shaped) const {
- const Glyph *ret;
- if (GDVIRTUAL_CALL(_shaped_text_get_ellipsis_glyphs, p_shaped, &ret)) {
+ GDNativePtr<Glyph> ret;
+ if (GDVIRTUAL_CALL(_shaped_text_get_ellipsis_glyphs, p_shaped, ret)) {
return ret;
}
return nullptr;
diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h
index df1f1d7a70..9e7f666be1 100644
--- a/servers/text/text_server_extension.h
+++ b/servers/text/text_server_extension.h
@@ -132,7 +132,7 @@ public:
virtual void font_set_hinting(RID p_font_rid, Hinting p_hinting) override;
virtual Hinting font_get_hinting(RID p_font_rid) const override;
GDVIRTUAL2(_font_set_hinting, RID, Hinting);
- GDVIRTUAL1RC(/*Hinting*/ int, _font_get_hinting, RID);
+ GDVIRTUAL1RC(Hinting, _font_get_hinting, RID);
virtual void font_set_variation_coordinates(RID p_font_rid, const Dictionary &p_variation_coordinates) override;
virtual Dictionary font_get_variation_coordinates(RID p_font_rid) const override;
@@ -317,8 +317,8 @@ public:
virtual Direction shaped_text_get_direction(RID p_shaped) const override;
virtual Direction shaped_text_get_inferred_direction(RID p_shaped) const override;
GDVIRTUAL2(_shaped_text_set_direction, RID, Direction);
- GDVIRTUAL1RC(/*Direction*/ int, _shaped_text_get_direction, RID);
- GDVIRTUAL1RC(/*Direction*/ int, _shaped_text_get_inferred_direction, RID);
+ GDVIRTUAL1RC(Direction, _shaped_text_get_direction, RID);
+ GDVIRTUAL1RC(Direction, _shaped_text_get_inferred_direction, RID);
virtual void shaped_text_set_bidi_override(RID p_shaped, const Array &p_override) override;
GDVIRTUAL2(_shaped_text_set_bidi_override, RID, const Array &);
@@ -331,7 +331,7 @@ public:
virtual void shaped_text_set_orientation(RID p_shaped, Orientation p_orientation = ORIENTATION_HORIZONTAL) override;
virtual Orientation shaped_text_get_orientation(RID p_shaped) const override;
GDVIRTUAL2(_shaped_text_set_orientation, RID, Orientation);
- GDVIRTUAL1RC(/*Orientation*/ int, _shaped_text_get_orientation, RID);
+ GDVIRTUAL1RC(Orientation, _shaped_text_get_orientation, RID);
virtual void shaped_text_set_preserve_invalid(RID p_shaped, bool p_enabled) override;
virtual bool shaped_text_get_preserve_invalid(RID p_shaped) const override;
@@ -380,8 +380,8 @@ public:
virtual const Glyph *shaped_text_get_glyphs(RID p_shaped) const override;
virtual const Glyph *shaped_text_sort_logical(RID p_shaped) override;
virtual int shaped_text_get_glyph_count(RID p_shaped) const override;
- GDVIRTUAL2C(_shaped_text_get_glyphs, RID, GDNativePtr<const Glyph *>);
- GDVIRTUAL2(_shaped_text_sort_logical, RID, GDNativePtr<const Glyph *>);
+ GDVIRTUAL1RC(GDNativePtr<Glyph>, _shaped_text_get_glyphs, RID);
+ GDVIRTUAL1R(GDNativePtr<Glyph>, _shaped_text_sort_logical, RID);
GDVIRTUAL1RC(int, _shaped_text_get_glyph_count, RID);
virtual Vector2i shaped_text_get_range(RID p_shaped) const override;
@@ -400,7 +400,7 @@ public:
virtual int shaped_text_get_ellipsis_glyph_count(RID p_shaped) const override;
GDVIRTUAL1RC(int, _shaped_text_get_trim_pos, RID);
GDVIRTUAL1RC(int, _shaped_text_get_ellipsis_pos, RID);
- GDVIRTUAL2C(_shaped_text_get_ellipsis_glyphs, RID, GDNativePtr<const Glyph *>);
+ GDVIRTUAL1RC(GDNativePtr<Glyph>, _shaped_text_get_ellipsis_glyphs, RID);
GDVIRTUAL1RC(int, _shaped_text_get_ellipsis_glyph_count, RID);
virtual void shaped_text_overrun_trim_to_width(RID p_shaped, float p_width, uint16_t p_trim_flags) override;
diff --git a/servers/text_server.h b/servers/text_server.h
index 6724c02caf..629a633b9f 100644
--- a/servers/text_server.h
+++ b/servers/text_server.h
@@ -557,7 +557,6 @@ VARIANT_ENUM_CAST(TextServer::SpacingType);
VARIANT_ENUM_CAST(TextServer::FontStyle);
GDVIRTUAL_NATIVE_PTR(Glyph);
-GDVIRTUAL_NATIVE_PTR(Glyph *);
GDVIRTUAL_NATIVE_PTR(CaretInfo);
#endif // TEXT_SERVER_H
diff --git a/tests/core/math/test_math.cpp b/tests/core/math/test_math.cpp
index a24a8fde2b..4182455b7a 100644
--- a/tests/core/math/test_math.cpp
+++ b/tests/core/math/test_math.cpp
@@ -227,7 +227,7 @@ class GetClassAndNamespace {
return TK_SYMBOL;
}
- if (code[idx] == '-' || (code[idx] >= '0' && code[idx] <= '9')) {
+ if (code[idx] == '-' || is_digit(code[idx])) {
//a number
const char32_t *rptr;
double number = String::to_float(&code[idx], &rptr);
@@ -235,10 +235,10 @@ class GetClassAndNamespace {
value = number;
return TK_NUMBER;
- } else if ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+ } else if (is_ascii_char(code[idx]) || code[idx] > 127) {
String id;
- while ((code[idx] >= 'A' && code[idx] <= 'Z') || (code[idx] >= 'a' && code[idx] <= 'z') || code[idx] > 127) {
+ while (is_ascii_char(code[idx]) || code[idx] > 127) {
id += code[idx];
idx++;
}
diff --git a/tests/core/math/test_rect2.h b/tests/core/math/test_rect2.h
index d98a94b1b5..0b1106ac3c 100644
--- a/tests/core/math/test_rect2.h
+++ b/tests/core/math/test_rect2.h
@@ -32,15 +32,11 @@
#define TEST_RECT2_H
#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
#include "thirdparty/doctest/doctest.h"
namespace TestRect2 {
-// We also test Rect2i here, for consistency with the source code where Rect2
-// and Rect2i are defined in the same file.
-
-// Rect2
-
TEST_CASE("[Rect2] Constructor methods") {
const Rect2 rect = Rect2(0, 100, 1280, 720);
const Rect2 rect_vector = Rect2(Vector2(0, 100), Vector2(1280, 720));
@@ -304,278 +300,6 @@ TEST_CASE("[Rect2] Merging") {
Rect2(0, 100, 1280, 720).merge(Rect2(-4000, -4000, 100, 100)).is_equal_approx(Rect2(-4000, -4000, 5280, 4820)),
"merge() with non-enclosed Rect2 should return the expected result.");
}
-
-// Rect2i
-
-TEST_CASE("[Rect2i] Constructor methods") {
- Rect2i recti = Rect2i(0, 100, 1280, 720);
- Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720));
- Rect2i recti_copy_recti = Rect2i(recti);
- Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720));
-
- CHECK_MESSAGE(
- recti == recti_vector,
- "Rect2is created with the same dimensions but by different methods should be equal.");
- CHECK_MESSAGE(
- recti == recti_copy_recti,
- "Rect2is created with the same dimensions but by different methods should be equal.");
- CHECK_MESSAGE(
- recti == recti_copy_rect,
- "Rect2is created with the same dimensions but by different methods should be equal.");
-}
-
-TEST_CASE("[Rect2i] String conversion") {
- // Note: This also depends on the Vector2 string representation.
- CHECK_MESSAGE(
- String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
- "The string representation should match the expected value.");
-}
-
-TEST_CASE("[Rect2i] Basic getters") {
- const Rect2i rect = Rect2i(0, 100, 1280, 720);
- CHECK_MESSAGE(
- rect.get_position() == Vector2i(0, 100),
- "get_position() should return the expected value.");
- CHECK_MESSAGE(
- rect.get_size() == Vector2i(1280, 720),
- "get_size() should return the expected value.");
- CHECK_MESSAGE(
- rect.get_end() == Vector2i(1280, 820),
- "get_end() should return the expected value.");
- CHECK_MESSAGE(
- rect.get_center() == Vector2i(640, 460),
- "get_center() should return the expected value.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
- "get_center() should return the expected value.");
-}
-
-TEST_CASE("[Rect2i] Basic setters") {
- Rect2i rect = Rect2i(0, 100, 1280, 720);
- rect.set_end(Vector2i(4000, 4000));
- CHECK_MESSAGE(
- rect == Rect2i(0, 100, 4000, 3900),
- "set_end() should result in the expected Rect2i.");
-
- rect = Rect2i(0, 100, 1280, 720);
- rect.set_position(Vector2i(4000, 4000));
- CHECK_MESSAGE(
- rect == Rect2i(4000, 4000, 1280, 720),
- "set_position() should result in the expected Rect2i.");
-
- rect = Rect2i(0, 100, 1280, 720);
- rect.set_size(Vector2i(4000, 4000));
- CHECK_MESSAGE(
- rect == Rect2i(0, 100, 4000, 4000),
- "set_size() should result in the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Area getters") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).get_area() == 921'600,
- "get_area() should return the expected value.");
- CHECK_MESSAGE(
- Rect2i(0, 100, -1280, -720).get_area() == 921'600,
- "get_area() should return the expected value.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, -720).get_area() == -921'600,
- "get_area() should return the expected value.");
- CHECK_MESSAGE(
- Rect2i(0, 100, -1280, 720).get_area() == -921'600,
- "get_area() should return the expected value.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 0, 720).get_area() == 0,
- "get_area() should return the expected value.");
-
- CHECK_MESSAGE(
- !Rect2i(0, 100, 1280, 720).has_no_area(),
- "has_no_area() should return the expected value on Rect2i with an area.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 0, 500).has_no_area(),
- "has_no_area() should return the expected value on Rect2i with no area.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 500, 0).has_no_area(),
- "has_no_area() should return the expected value on Rect2i with no area.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 0, 0).has_no_area(),
- "has_no_area() should return the expected value on Rect2i with no area.");
-}
-
-TEST_CASE("[Rect2i] Absolute coordinates") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720),
- "abs() should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720),
- "abs() should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720),
- "abs() should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720),
- "abs() should return the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Intersection") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100),
- "intersection() with fully enclosed Rect2i should return the expected result.");
- // The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels.
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100),
- "intersection() with partially enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(),
- "intersection() with non-enclosed Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Enclosing") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)),
- "encloses() with fully contained Rect2i should return the expected result.");
- CHECK_MESSAGE(
- !Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)),
- "encloses() with partially contained Rect2i should return the expected result.");
- CHECK_MESSAGE(
- !Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)),
- "encloses() with non-contained Rect2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)),
- "encloses() with identical Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Expanding") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720),
- "expand() with contained Vector2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820),
- "expand() with non-contained Vector2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Growing") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920),
- "grow() with positive value should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520),
- "grow() with negative value should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280),
- "grow() with large negative value should return the expected Rect2i.");
-
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320),
- "grow_individual() with positive values should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520),
- "grow_individual() with positive and negative values should return the expected Rect2i.");
-
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220),
- "grow_side() with positive value should return the expected Rect2i.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220),
- "grow_side() with negative value should return the expected Rect2i.");
-}
-
-TEST_CASE("[Rect2i] Has point") {
- Rect2i rect = Rect2i(0, 100, 1280, 720);
- CHECK_MESSAGE(
- rect.has_point(Vector2i(500, 600)),
- "has_point() with contained Vector2i should return the expected result.");
- CHECK_MESSAGE(
- !rect.has_point(Vector2i(0, 0)),
- "has_point() with non-contained Vector2i should return the expected result.");
-
- CHECK_MESSAGE(
- rect.has_point(rect.position),
- "has_point() with positive size should include `position`.");
- CHECK_MESSAGE(
- rect.has_point(rect.position + Vector2i(1, 1)),
- "has_point() with positive size should include `position + (1, 1)`.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + Vector2i(1, -1)),
- "has_point() with positive size should not include `position + (1, -1)`.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + rect.size),
- "has_point() with positive size should not include `position + size`.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + rect.size + Vector2i(1, 1)),
- "has_point() with positive size should not include `position + size + (1, 1)`.");
- CHECK_MESSAGE(
- rect.has_point(rect.position + rect.size + Vector2i(-1, -1)),
- "has_point() with positive size should include `position + size + (-1, -1)`.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + rect.size + Vector2i(-1, 1)),
- "has_point() with positive size should not include `position + size + (-1, 1)`.");
-
- CHECK_MESSAGE(
- rect.has_point(rect.position + Vector2i(0, 10)),
- "has_point() with point located on left edge should return true.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
- "has_point() with point located on right edge should return false.");
- CHECK_MESSAGE(
- rect.has_point(rect.position + Vector2i(10, 0)),
- "has_point() with point located on top edge should return true.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + Vector2i(10, rect.size.y)),
- "has_point() with point located on bottom edge should return false.");
-
- /*
- // FIXME: Disabled for now until GH-37617 is fixed one way or another.
- // More tests should then be written like for the positive size case.
- rect = Rect2i(0, 100, -1280, -720);
- CHECK_MESSAGE(
- rect.has_point(rect.position),
- "has_point() with negative size should include `position`.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + rect.size),
- "has_point() with negative size should not include `position + size`.");
- */
-
- rect = Rect2i(-4000, -200, 1280, 720);
- CHECK_MESSAGE(
- rect.has_point(rect.position + Vector2i(0, 10)),
- "has_point() with negative position and point located on left edge should return true.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
- "has_point() with negative position and point located on right edge should return false.");
- CHECK_MESSAGE(
- rect.has_point(rect.position + Vector2i(10, 0)),
- "has_point() with negative position and point located on top edge should return true.");
- CHECK_MESSAGE(
- !rect.has_point(rect.position + Vector2i(10, rect.size.y)),
- "has_point() with negative position and point located on bottom edge should return false.");
-}
-
-TEST_CASE("[Rect2i] Intersection") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)),
- "intersects() with fully enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)),
- "intersects() with partially enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- !Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)),
- "intersects() with non-enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- !Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)),
- "intersects() with adjacent Rect2i should return the expected result.");
-}
-
-TEST_CASE("[Rect2i] Merging") {
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720),
- "merge() with fully enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720),
- "merge() with partially enclosed Rect2i should return the expected result.");
- CHECK_MESSAGE(
- Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820),
- "merge() with non-enclosed Rect2i should return the expected result.");
-}
} // namespace TestRect2
#endif // TEST_RECT2_H
diff --git a/tests/core/math/test_rect2i.h b/tests/core/math/test_rect2i.h
new file mode 100644
index 0000000000..0d1a088a66
--- /dev/null
+++ b/tests/core/math/test_rect2i.h
@@ -0,0 +1,311 @@
+/*************************************************************************/
+/* test_rect2i.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef TEST_RECT2I_H
+#define TEST_RECT2I_H
+
+#include "core/math/rect2.h"
+#include "core/math/rect2i.h"
+
+#include "thirdparty/doctest/doctest.h"
+
+namespace TestRect2i {
+TEST_CASE("[Rect2i] Constructor methods") {
+ Rect2i recti = Rect2i(0, 100, 1280, 720);
+ Rect2i recti_vector = Rect2i(Vector2i(0, 100), Vector2i(1280, 720));
+ Rect2i recti_copy_recti = Rect2i(recti);
+ Rect2i recti_copy_rect = Rect2i(Rect2(0, 100, 1280, 720));
+
+ CHECK_MESSAGE(
+ recti == recti_vector,
+ "Rect2is created with the same dimensions but by different methods should be equal.");
+ CHECK_MESSAGE(
+ recti == recti_copy_recti,
+ "Rect2is created with the same dimensions but by different methods should be equal.");
+ CHECK_MESSAGE(
+ recti == recti_copy_rect,
+ "Rect2is created with the same dimensions but by different methods should be equal.");
+}
+
+TEST_CASE("[Rect2i] String conversion") {
+ // Note: This also depends on the Vector2 string representation.
+ CHECK_MESSAGE(
+ String(Rect2i(0, 100, 1280, 720)) == "[P: (0, 100), S: (1280, 720)]",
+ "The string representation should match the expected value.");
+}
+
+TEST_CASE("[Rect2i] Basic getters") {
+ const Rect2i rect = Rect2i(0, 100, 1280, 720);
+ CHECK_MESSAGE(
+ rect.get_position() == Vector2i(0, 100),
+ "get_position() should return the expected value.");
+ CHECK_MESSAGE(
+ rect.get_size() == Vector2i(1280, 720),
+ "get_size() should return the expected value.");
+ CHECK_MESSAGE(
+ rect.get_end() == Vector2i(1280, 820),
+ "get_end() should return the expected value.");
+ CHECK_MESSAGE(
+ rect.get_center() == Vector2i(640, 460),
+ "get_center() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1281, 721).get_center() == Vector2i(640, 460),
+ "get_center() should return the expected value.");
+}
+
+TEST_CASE("[Rect2i] Basic setters") {
+ Rect2i rect = Rect2i(0, 100, 1280, 720);
+ rect.set_end(Vector2i(4000, 4000));
+ CHECK_MESSAGE(
+ rect == Rect2i(0, 100, 4000, 3900),
+ "set_end() should result in the expected Rect2i.");
+
+ rect = Rect2i(0, 100, 1280, 720);
+ rect.set_position(Vector2i(4000, 4000));
+ CHECK_MESSAGE(
+ rect == Rect2i(4000, 4000, 1280, 720),
+ "set_position() should result in the expected Rect2i.");
+
+ rect = Rect2i(0, 100, 1280, 720);
+ rect.set_size(Vector2i(4000, 4000));
+ CHECK_MESSAGE(
+ rect == Rect2i(0, 100, 4000, 4000),
+ "set_size() should result in the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Area getters") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).get_area() == 921'600,
+ "get_area() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, -1280, -720).get_area() == 921'600,
+ "get_area() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, -720).get_area() == -921'600,
+ "get_area() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, -1280, 720).get_area() == -921'600,
+ "get_area() should return the expected value.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 0, 720).get_area() == 0,
+ "get_area() should return the expected value.");
+
+ CHECK_MESSAGE(
+ !Rect2i(0, 100, 1280, 720).has_no_area(),
+ "has_no_area() should return the expected value on Rect2i with an area.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 0, 500).has_no_area(),
+ "has_no_area() should return the expected value on Rect2i with no area.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 500, 0).has_no_area(),
+ "has_no_area() should return the expected value on Rect2i with no area.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 0, 0).has_no_area(),
+ "has_no_area() should return the expected value on Rect2i with no area.");
+}
+
+TEST_CASE("[Rect2i] Absolute coordinates") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).abs() == Rect2i(0, 100, 1280, 720),
+ "abs() should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, -100, 1280, 720).abs() == Rect2i(0, -100, 1280, 720),
+ "abs() should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, -100, -1280, -720).abs() == Rect2i(-1280, -820, 1280, 720),
+ "abs() should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, -1280, 720).abs() == Rect2i(-1280, 100, 1280, 720),
+ "abs() should return the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Intersection") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).intersection(Rect2i(0, 300, 100, 100)) == Rect2i(0, 300, 100, 100),
+ "intersection() with fully enclosed Rect2i should return the expected result.");
+ // The resulting Rect2i is 100 pixels high because the first Rect2i is vertically offset by 100 pixels.
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).intersection(Rect2i(1200, 700, 100, 100)) == Rect2i(1200, 700, 80, 100),
+ "intersection() with partially enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).intersection(Rect2i(-4000, -4000, 100, 100)) == Rect2i(),
+ "intersection() with non-enclosed Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Enclosing") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 300, 100, 100)),
+ "encloses() with fully contained Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ !Rect2i(0, 100, 1280, 720).encloses(Rect2i(1200, 700, 100, 100)),
+ "encloses() with partially contained Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ !Rect2i(0, 100, 1280, 720).encloses(Rect2i(-4000, -4000, 100, 100)),
+ "encloses() with non-contained Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).encloses(Rect2i(0, 100, 1280, 720)),
+ "encloses() with identical Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Expanding") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).expand(Vector2i(500, 600)) == Rect2i(0, 100, 1280, 720),
+ "expand() with contained Vector2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).expand(Vector2i(0, 0)) == Rect2i(0, 0, 1280, 820),
+ "expand() with non-contained Vector2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Growing") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow(100) == Rect2i(-100, 0, 1480, 920),
+ "grow() with positive value should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow(-100) == Rect2i(100, 200, 1080, 520),
+ "grow() with negative value should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow(-4000) == Rect2i(4000, 4100, -6720, -7280),
+ "grow() with large negative value should return the expected Rect2i.");
+
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow_individual(100, 200, 300, 400) == Rect2i(-100, -100, 1680, 1320),
+ "grow_individual() with positive values should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow_individual(-100, 200, 300, -400) == Rect2i(100, -100, 1480, 520),
+ "grow_individual() with positive and negative values should return the expected Rect2i.");
+
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, 500) == Rect2i(0, -400, 1280, 1220),
+ "grow_side() with positive value should return the expected Rect2i.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).grow_side(SIDE_TOP, -500) == Rect2i(0, 600, 1280, 220),
+ "grow_side() with negative value should return the expected Rect2i.");
+}
+
+TEST_CASE("[Rect2i] Has point") {
+ Rect2i rect = Rect2i(0, 100, 1280, 720);
+ CHECK_MESSAGE(
+ rect.has_point(Vector2i(500, 600)),
+ "has_point() with contained Vector2i should return the expected result.");
+ CHECK_MESSAGE(
+ !rect.has_point(Vector2i(0, 0)),
+ "has_point() with non-contained Vector2i should return the expected result.");
+
+ CHECK_MESSAGE(
+ rect.has_point(rect.position),
+ "has_point() with positive size should include `position`.");
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + Vector2i(1, 1)),
+ "has_point() with positive size should include `position + (1, 1)`.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + Vector2i(1, -1)),
+ "has_point() with positive size should not include `position + (1, -1)`.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + rect.size),
+ "has_point() with positive size should not include `position + size`.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + rect.size + Vector2i(1, 1)),
+ "has_point() with positive size should not include `position + size + (1, 1)`.");
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + rect.size + Vector2i(-1, -1)),
+ "has_point() with positive size should include `position + size + (-1, -1)`.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + rect.size + Vector2i(-1, 1)),
+ "has_point() with positive size should not include `position + size + (-1, 1)`.");
+
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + Vector2i(0, 10)),
+ "has_point() with point located on left edge should return true.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
+ "has_point() with point located on right edge should return false.");
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + Vector2i(10, 0)),
+ "has_point() with point located on top edge should return true.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + Vector2i(10, rect.size.y)),
+ "has_point() with point located on bottom edge should return false.");
+
+ /*
+ // FIXME: Disabled for now until GH-37617 is fixed one way or another.
+ // More tests should then be written like for the positive size case.
+ rect = Rect2i(0, 100, -1280, -720);
+ CHECK_MESSAGE(
+ rect.has_point(rect.position),
+ "has_point() with negative size should include `position`.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + rect.size),
+ "has_point() with negative size should not include `position + size`.");
+ */
+
+ rect = Rect2i(-4000, -200, 1280, 720);
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + Vector2i(0, 10)),
+ "has_point() with negative position and point located on left edge should return true.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + Vector2i(rect.size.x, 10)),
+ "has_point() with negative position and point located on right edge should return false.");
+ CHECK_MESSAGE(
+ rect.has_point(rect.position + Vector2i(10, 0)),
+ "has_point() with negative position and point located on top edge should return true.");
+ CHECK_MESSAGE(
+ !rect.has_point(rect.position + Vector2i(10, rect.size.y)),
+ "has_point() with negative position and point located on bottom edge should return false.");
+}
+
+TEST_CASE("[Rect2i] Intersection") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).intersects(Rect2i(0, 300, 100, 100)),
+ "intersects() with fully enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).intersects(Rect2i(1200, 700, 100, 100)),
+ "intersects() with partially enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ !Rect2i(0, 100, 1280, 720).intersects(Rect2i(-4000, -4000, 100, 100)),
+ "intersects() with non-enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ !Rect2i(0, 0, 2, 2).intersects(Rect2i(2, 2, 2, 2)),
+ "intersects() with adjacent Rect2i should return the expected result.");
+}
+
+TEST_CASE("[Rect2i] Merging") {
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).merge(Rect2i(0, 300, 100, 100)) == Rect2i(0, 100, 1280, 720),
+ "merge() with fully enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).merge(Rect2i(1200, 700, 100, 100)) == Rect2i(0, 100, 1300, 720),
+ "merge() with partially enclosed Rect2i should return the expected result.");
+ CHECK_MESSAGE(
+ Rect2i(0, 100, 1280, 720).merge(Rect2i(-4000, -4000, 100, 100)) == Rect2i(-4000, -4000, 5280, 4820),
+ "merge() with non-enclosed Rect2i should return the expected result.");
+}
+} // namespace TestRect2i
+
+#endif // TEST_RECT2I_H
diff --git a/tests/core/math/test_vector2.h b/tests/core/math/test_vector2.h
index cb447acd17..ff60467bf4 100644
--- a/tests/core/math/test_vector2.h
+++ b/tests/core/math/test_vector2.h
@@ -32,6 +32,7 @@
#define TEST_VECTOR2_H
#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
#include "tests/test_macros.h"
namespace TestVector2 {
diff --git a/tests/core/math/test_vector2i.h b/tests/core/math/test_vector2i.h
index 86e254654d..841bb793a4 100644
--- a/tests/core/math/test_vector2i.h
+++ b/tests/core/math/test_vector2i.h
@@ -32,6 +32,7 @@
#define TEST_VECTOR2I_H
#include "core/math/vector2.h"
+#include "core/math/vector2i.h"
#include "tests/test_macros.h"
namespace TestVector2i {
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 0446f749cf..bf78298450 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -245,6 +245,19 @@ TEST_CASE("[String] Testing for empty string") {
CHECK(String("").is_empty());
}
+TEST_CASE("[String] Contains") {
+ String s = "C:\\Godot\\project\\string_test.tscn";
+ CHECK(s.contains(":\\"));
+ CHECK(s.contains("Godot"));
+ CHECK(s.contains(String("project\\string_test")));
+ CHECK(s.contains(String("\\string_test.tscn")));
+
+ CHECK(!s.contains("://"));
+ CHECK(!s.contains("Godoh"));
+ CHECK(!s.contains(String("project\\string test")));
+ CHECK(!s.contains(String("\\char_test.tscn")));
+}
+
TEST_CASE("[String] Test chr") {
CHECK(String::chr('H') == "H");
CHECK(String::chr(0x3012)[0] == 0x3012);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 0190fa5184..3b51a6d805 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -48,6 +48,7 @@
#include "tests/core/math/test_math.h"
#include "tests/core/math/test_random_number_generator.h"
#include "tests/core/math/test_rect2.h"
+#include "tests/core/math/test_rect2i.h"
#include "tests/core/math/test_vector2.h"
#include "tests/core/math/test_vector2i.h"
#include "tests/core/math/test_vector3.h"
diff --git a/thirdparty/README.md b/thirdparty/README.md
index ded8d0edb7..e1f911a9f9 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -309,7 +309,7 @@ Files extracted from upstream source:
## libwebp
- Upstream: https://chromium.googlesource.com/webm/libwebp/
-- Version: 1.2.1 (9ce5843dbabcfd3f7c39ec7ceba9cbeb213cbfdf, 2021)
+- Version: 1.2.2 (b0a860891dcd4c0c2d7c6149e5cccb6eb881cc21, 2022)
- License: BSD-3-Clause
Files extracted from upstream source:
@@ -317,10 +317,6 @@ Files extracted from upstream source:
- `src/*` except from: `.am`, `.rc` and `.in` files
- `AUTHORS`, `COPYING`, `PATENTS`
-Important: The files `utils/bit_reader_utils.{c,h}` have Godot-made
-changes to ensure they build for Javascript/HTML5. Those
-changes are marked with `// -- GODOT --` comments.
-
## mbedtls
diff --git a/thirdparty/libwebp/AUTHORS b/thirdparty/libwebp/AUTHORS
index 30abde0326..8307c2099d 100644
--- a/thirdparty/libwebp/AUTHORS
+++ b/thirdparty/libwebp/AUTHORS
@@ -32,6 +32,7 @@ Contributors:
- Pascal Massimino (pascal dot massimino at gmail dot com)
- Paweł Hajdan, Jr (phajdan dot jr at chromium dot org)
- Pierre Joye (pierre dot php at gmail dot com)
+- Roberto Alanis (alanisbaez at google dot com)
- Sam Clegg (sbc at chromium dot org)
- Scott Hancher (seh at google dot com)
- Scott LaVarnway (slavarnway at google dot com)
diff --git a/thirdparty/libwebp/src/dec/vp8_dec.c b/thirdparty/libwebp/src/dec/vp8_dec.c
index 5f405e4c2a..2003935ec4 100644
--- a/thirdparty/libwebp/src/dec/vp8_dec.c
+++ b/thirdparty/libwebp/src/dec/vp8_dec.c
@@ -403,7 +403,7 @@ static const uint8_t kZigzag[16] = {
0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15
};
-// See section 13-2: http://tools.ietf.org/html/rfc6386#section-13.2
+// See section 13-2: https://datatracker.ietf.org/doc/html/rfc6386#section-13.2
static int GetLargeValue(VP8BitReader* const br, const uint8_t* const p) {
int v;
if (!VP8GetBit(br, p[3], "coeffs")) {
diff --git a/thirdparty/libwebp/src/dec/vp8i_dec.h b/thirdparty/libwebp/src/dec/vp8i_dec.h
index 20526a87c4..9af22f8cc6 100644
--- a/thirdparty/libwebp/src/dec/vp8i_dec.h
+++ b/thirdparty/libwebp/src/dec/vp8i_dec.h
@@ -32,7 +32,7 @@ extern "C" {
// version numbers
#define DEC_MAJ_VERSION 1
#define DEC_MIN_VERSION 2
-#define DEC_REV_VERSION 1
+#define DEC_REV_VERSION 2
// YUV-cache parameters. Cache is 32-bytes wide (= one cacheline).
// Constraints are: We need to store one 16x16 block of luma samples (y),
diff --git a/thirdparty/libwebp/src/dec/vp8l_dec.c b/thirdparty/libwebp/src/dec/vp8l_dec.c
index 73c3b54fff..78db014030 100644
--- a/thirdparty/libwebp/src/dec/vp8l_dec.c
+++ b/thirdparty/libwebp/src/dec/vp8l_dec.c
@@ -84,7 +84,7 @@ static const uint8_t kCodeToPlane[CODE_TO_PLANE_CODES] = {
// to 256 (green component values) + 24 (length prefix values)
// + color_cache_size (between 0 and 2048).
// All values computed for 8-bit first level lookup with Mark Adler's tool:
-// http://www.hdfgroup.org/ftp/lib-external/zlib/zlib-1.2.5/examples/enough.c
+// https://github.com/madler/zlib/blob/v1.2.5/examples/enough.c
#define FIXED_TABLE_SIZE (630 * 3 + 410)
static const uint16_t kTableSize[12] = {
FIXED_TABLE_SIZE + 654,
diff --git a/thirdparty/libwebp/src/demux/anim_decode.c b/thirdparty/libwebp/src/demux/anim_decode.c
index 2bf4dcffe0..e077ffb536 100644
--- a/thirdparty/libwebp/src/demux/anim_decode.c
+++ b/thirdparty/libwebp/src/demux/anim_decode.c
@@ -23,6 +23,14 @@
#define NUM_CHANNELS 4
+// Channel extraction from a uint32_t representation of a uint8_t RGBA/BGRA
+// buffer.
+#ifdef WORDS_BIGENDIAN
+#define CHANNEL_SHIFT(i) (24 - (i) * 8)
+#else
+#define CHANNEL_SHIFT(i) ((i) * 8)
+#endif
+
typedef void (*BlendRowFunc)(uint32_t* const, const uint32_t* const, int);
static void BlendPixelRowNonPremult(uint32_t* const src,
const uint32_t* const dst, int num_pixels);
@@ -209,35 +217,35 @@ static uint8_t BlendChannelNonPremult(uint32_t src, uint8_t src_a,
const uint8_t dst_channel = (dst >> shift) & 0xff;
const uint32_t blend_unscaled = src_channel * src_a + dst_channel * dst_a;
assert(blend_unscaled < (1ULL << 32) / scale);
- return (blend_unscaled * scale) >> 24;
+ return (blend_unscaled * scale) >> CHANNEL_SHIFT(3);
}
// Blend 'src' over 'dst' assuming they are NOT pre-multiplied by alpha.
static uint32_t BlendPixelNonPremult(uint32_t src, uint32_t dst) {
- const uint8_t src_a = (src >> 24) & 0xff;
+ const uint8_t src_a = (src >> CHANNEL_SHIFT(3)) & 0xff;
if (src_a == 0) {
return dst;
} else {
- const uint8_t dst_a = (dst >> 24) & 0xff;
+ const uint8_t dst_a = (dst >> CHANNEL_SHIFT(3)) & 0xff;
// This is the approximate integer arithmetic for the actual formula:
// dst_factor_a = (dst_a * (255 - src_a)) / 255.
const uint8_t dst_factor_a = (dst_a * (256 - src_a)) >> 8;
const uint8_t blend_a = src_a + dst_factor_a;
const uint32_t scale = (1UL << 24) / blend_a;
- const uint8_t blend_r =
- BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 0);
- const uint8_t blend_g =
- BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 8);
- const uint8_t blend_b =
- BlendChannelNonPremult(src, src_a, dst, dst_factor_a, scale, 16);
+ const uint8_t blend_r = BlendChannelNonPremult(
+ src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(0));
+ const uint8_t blend_g = BlendChannelNonPremult(
+ src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(1));
+ const uint8_t blend_b = BlendChannelNonPremult(
+ src, src_a, dst, dst_factor_a, scale, CHANNEL_SHIFT(2));
assert(src_a + dst_factor_a < 256);
- return (blend_r << 0) |
- (blend_g << 8) |
- (blend_b << 16) |
- ((uint32_t)blend_a << 24);
+ return ((uint32_t)blend_r << CHANNEL_SHIFT(0)) |
+ ((uint32_t)blend_g << CHANNEL_SHIFT(1)) |
+ ((uint32_t)blend_b << CHANNEL_SHIFT(2)) |
+ ((uint32_t)blend_a << CHANNEL_SHIFT(3));
}
}
@@ -247,7 +255,7 @@ static void BlendPixelRowNonPremult(uint32_t* const src,
const uint32_t* const dst, int num_pixels) {
int i;
for (i = 0; i < num_pixels; ++i) {
- const uint8_t src_alpha = (src[i] >> 24) & 0xff;
+ const uint8_t src_alpha = (src[i] >> CHANNEL_SHIFT(3)) & 0xff;
if (src_alpha != 0xff) {
src[i] = BlendPixelNonPremult(src[i], dst[i]);
}
@@ -264,7 +272,7 @@ static WEBP_INLINE uint32_t ChannelwiseMultiply(uint32_t pix, uint32_t scale) {
// Blend 'src' over 'dst' assuming they are pre-multiplied by alpha.
static uint32_t BlendPixelPremult(uint32_t src, uint32_t dst) {
- const uint8_t src_a = (src >> 24) & 0xff;
+ const uint8_t src_a = (src >> CHANNEL_SHIFT(3)) & 0xff;
return src + ChannelwiseMultiply(dst, 256 - src_a);
}
@@ -274,7 +282,7 @@ static void BlendPixelRowPremult(uint32_t* const src, const uint32_t* const dst,
int num_pixels) {
int i;
for (i = 0; i < num_pixels; ++i) {
- const uint8_t src_alpha = (src[i] >> 24) & 0xff;
+ const uint8_t src_alpha = (src[i] >> CHANNEL_SHIFT(3)) & 0xff;
if (src_alpha != 0xff) {
src[i] = BlendPixelPremult(src[i], dst[i]);
}
diff --git a/thirdparty/libwebp/src/demux/demux.c b/thirdparty/libwebp/src/demux/demux.c
index 547a7725de..f04a2b8450 100644
--- a/thirdparty/libwebp/src/demux/demux.c
+++ b/thirdparty/libwebp/src/demux/demux.c
@@ -25,7 +25,7 @@
#define DMUX_MAJ_VERSION 1
#define DMUX_MIN_VERSION 2
-#define DMUX_REV_VERSION 1
+#define DMUX_REV_VERSION 2
typedef struct {
size_t start_; // start location of the data
diff --git a/thirdparty/libwebp/src/dsp/dsp.h b/thirdparty/libwebp/src/dsp/dsp.h
index 513e159bb3..c4f57e4d5b 100644
--- a/thirdparty/libwebp/src/dsp/dsp.h
+++ b/thirdparty/libwebp/src/dsp/dsp.h
@@ -119,7 +119,12 @@ extern "C" {
#define WEBP_USE_NEON
#endif
-#if defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM)
+// Note: ARM64 is supported in Visual Studio 2017, but requires the direct
+// inclusion of arm64_neon.h; Visual Studio 2019 includes this file in
+// arm_neon.h.
+#if defined(_MSC_VER) && \
+ ((_MSC_VER >= 1700 && defined(_M_ARM)) || \
+ (_MSC_VER >= 1920 && defined(_M_ARM64)))
#define WEBP_USE_NEON
#define WEBP_USE_INTRINSICS
#endif
diff --git a/thirdparty/libwebp/src/dsp/enc_neon.c b/thirdparty/libwebp/src/dsp/enc_neon.c
index 43bf1245c5..601962ba76 100644
--- a/thirdparty/libwebp/src/dsp/enc_neon.c
+++ b/thirdparty/libwebp/src/dsp/enc_neon.c
@@ -9,7 +9,7 @@
//
// ARM NEON version of speed-critical encoding functions.
//
-// adapted from libvpx (http://www.webmproject.org/code/)
+// adapted from libvpx (https://www.webmproject.org/code/)
#include "src/dsp/dsp.h"
diff --git a/thirdparty/libwebp/src/dsp/lossless.c b/thirdparty/libwebp/src/dsp/lossless.c
index d8bbb02b35..84a54296fd 100644
--- a/thirdparty/libwebp/src/dsp/lossless.c
+++ b/thirdparty/libwebp/src/dsp/lossless.c
@@ -107,63 +107,77 @@ static WEBP_INLINE uint32_t Select(uint32_t a, uint32_t b, uint32_t c) {
//------------------------------------------------------------------------------
// Predictors
-uint32_t VP8LPredictor0_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor0_C(const uint32_t* const left,
+ const uint32_t* const top) {
(void)top;
(void)left;
return ARGB_BLACK;
}
-uint32_t VP8LPredictor1_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor1_C(const uint32_t* const left,
+ const uint32_t* const top) {
(void)top;
- return left;
+ return *left;
}
-uint32_t VP8LPredictor2_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor2_C(const uint32_t* const left,
+ const uint32_t* const top) {
(void)left;
return top[0];
}
-uint32_t VP8LPredictor3_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor3_C(const uint32_t* const left,
+ const uint32_t* const top) {
(void)left;
return top[1];
}
-uint32_t VP8LPredictor4_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor4_C(const uint32_t* const left,
+ const uint32_t* const top) {
(void)left;
return top[-1];
}
-uint32_t VP8LPredictor5_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average3(left, top[0], top[1]);
+uint32_t VP8LPredictor5_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average3(*left, top[0], top[1]);
return pred;
}
-uint32_t VP8LPredictor6_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average2(left, top[-1]);
+uint32_t VP8LPredictor6_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average2(*left, top[-1]);
return pred;
}
-uint32_t VP8LPredictor7_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average2(left, top[0]);
+uint32_t VP8LPredictor7_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average2(*left, top[0]);
return pred;
}
-uint32_t VP8LPredictor8_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor8_C(const uint32_t* const left,
+ const uint32_t* const top) {
const uint32_t pred = Average2(top[-1], top[0]);
(void)left;
return pred;
}
-uint32_t VP8LPredictor9_C(uint32_t left, const uint32_t* const top) {
+uint32_t VP8LPredictor9_C(const uint32_t* const left,
+ const uint32_t* const top) {
const uint32_t pred = Average2(top[0], top[1]);
(void)left;
return pred;
}
-uint32_t VP8LPredictor10_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average4(left, top[-1], top[0], top[1]);
+uint32_t VP8LPredictor10_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average4(*left, top[-1], top[0], top[1]);
return pred;
}
-uint32_t VP8LPredictor11_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Select(top[0], left, top[-1]);
+uint32_t VP8LPredictor11_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Select(top[0], *left, top[-1]);
return pred;
}
-uint32_t VP8LPredictor12_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = ClampedAddSubtractFull(left, top[0], top[-1]);
+uint32_t VP8LPredictor12_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = ClampedAddSubtractFull(*left, top[0], top[-1]);
return pred;
}
-uint32_t VP8LPredictor13_C(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = ClampedAddSubtractHalf(left, top[0], top[-1]);
+uint32_t VP8LPredictor13_C(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = ClampedAddSubtractHalf(*left, top[0], top[-1]);
return pred;
}
diff --git a/thirdparty/libwebp/src/dsp/lossless.h b/thirdparty/libwebp/src/dsp/lossless.h
index ebd316d1ed..c26c6bca07 100644
--- a/thirdparty/libwebp/src/dsp/lossless.h
+++ b/thirdparty/libwebp/src/dsp/lossless.h
@@ -28,23 +28,38 @@ extern "C" {
//------------------------------------------------------------------------------
// Decoding
-typedef uint32_t (*VP8LPredictorFunc)(uint32_t left, const uint32_t* const top);
+typedef uint32_t (*VP8LPredictorFunc)(const uint32_t* const left,
+ const uint32_t* const top);
extern VP8LPredictorFunc VP8LPredictors[16];
-uint32_t VP8LPredictor0_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor1_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor2_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor3_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor4_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor5_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor6_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor7_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor8_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor9_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor10_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor11_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor12_C(uint32_t left, const uint32_t* const top);
-uint32_t VP8LPredictor13_C(uint32_t left, const uint32_t* const top);
+uint32_t VP8LPredictor0_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor1_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor2_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor3_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor4_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor5_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor6_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor7_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor8_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor9_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor10_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor11_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor12_C(const uint32_t* const left,
+ const uint32_t* const top);
+uint32_t VP8LPredictor13_C(const uint32_t* const left,
+ const uint32_t* const top);
// These Add/Sub function expects upper[-1] and out[-1] to be readable.
typedef void (*VP8LPredictorAddSubFunc)(const uint32_t* in,
diff --git a/thirdparty/libwebp/src/dsp/lossless_common.h b/thirdparty/libwebp/src/dsp/lossless_common.h
index 96a106f9ee..6a2f736b5e 100644
--- a/thirdparty/libwebp/src/dsp/lossless_common.h
+++ b/thirdparty/libwebp/src/dsp/lossless_common.h
@@ -179,7 +179,7 @@ static void PREDICTOR_ADD(const uint32_t* in, const uint32_t* upper, \
int x; \
assert(upper != NULL); \
for (x = 0; x < num_pixels; ++x) { \
- const uint32_t pred = (PREDICTOR)(out[x - 1], upper + x); \
+ const uint32_t pred = (PREDICTOR)(&out[x - 1], upper + x); \
out[x] = VP8LAddPixels(in[x], pred); \
} \
}
diff --git a/thirdparty/libwebp/src/dsp/lossless_enc.c b/thirdparty/libwebp/src/dsp/lossless_enc.c
index c3e8537ade..1580631e38 100644
--- a/thirdparty/libwebp/src/dsp/lossless_enc.c
+++ b/thirdparty/libwebp/src/dsp/lossless_enc.c
@@ -745,7 +745,7 @@ static void PredictorSub##PREDICTOR_I##_C(const uint32_t* in, \
assert(upper != NULL); \
for (x = 0; x < num_pixels; ++x) { \
const uint32_t pred = \
- VP8LPredictor##PREDICTOR_I##_C(in[x - 1], upper + x); \
+ VP8LPredictor##PREDICTOR_I##_C(&in[x - 1], upper + x); \
out[x] = VP8LSubPixels(in[x], pred); \
} \
}
diff --git a/thirdparty/libwebp/src/dsp/lossless_mips_dsp_r2.c b/thirdparty/libwebp/src/dsp/lossless_mips_dsp_r2.c
index 9888854d57..bfe5ea6b38 100644
--- a/thirdparty/libwebp/src/dsp/lossless_mips_dsp_r2.c
+++ b/thirdparty/libwebp/src/dsp/lossless_mips_dsp_r2.c
@@ -188,46 +188,51 @@ static WEBP_INLINE uint32_t Average4(uint32_t a0, uint32_t a1,
return Average2(Average2(a0, a1), Average2(a2, a3));
}
-static uint32_t Predictor5_MIPSdspR2(uint32_t left, const uint32_t* const top) {
- return Average3(left, top[0], top[1]);
+static uint32_t Predictor5_MIPSdspR2(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average3(*left, top[0], top[1]);
}
-static uint32_t Predictor6_MIPSdspR2(uint32_t left, const uint32_t* const top) {
- return Average2(left, top[-1]);
+static uint32_t Predictor6_MIPSdspR2(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average2(*left, top[-1]);
}
-static uint32_t Predictor7_MIPSdspR2(uint32_t left, const uint32_t* const top) {
- return Average2(left, top[0]);
+static uint32_t Predictor7_MIPSdspR2(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average2(*left, top[0]);
}
-static uint32_t Predictor8_MIPSdspR2(uint32_t left, const uint32_t* const top) {
+static uint32_t Predictor8_MIPSdspR2(const uint32_t* const left,
+ const uint32_t* const top) {
(void)left;
return Average2(top[-1], top[0]);
}
-static uint32_t Predictor9_MIPSdspR2(uint32_t left, const uint32_t* const top) {
+static uint32_t Predictor9_MIPSdspR2(const uint32_t* const left,
+ const uint32_t* const top) {
(void)left;
return Average2(top[0], top[1]);
}
-static uint32_t Predictor10_MIPSdspR2(uint32_t left,
+static uint32_t Predictor10_MIPSdspR2(const uint32_t* const left,
const uint32_t* const top) {
- return Average4(left, top[-1], top[0], top[1]);
+ return Average4(*left, top[-1], top[0], top[1]);
}
-static uint32_t Predictor11_MIPSdspR2(uint32_t left,
+static uint32_t Predictor11_MIPSdspR2(const uint32_t* const left,
const uint32_t* const top) {
- return Select(top[0], left, top[-1]);
+ return Select(top[0], *left, top[-1]);
}
-static uint32_t Predictor12_MIPSdspR2(uint32_t left,
+static uint32_t Predictor12_MIPSdspR2(const uint32_t* const left,
const uint32_t* const top) {
- return ClampedAddSubtractFull(left, top[0], top[-1]);
+ return ClampedAddSubtractFull(*left, top[0], top[-1]);
}
-static uint32_t Predictor13_MIPSdspR2(uint32_t left,
+static uint32_t Predictor13_MIPSdspR2(const uint32_t* const left,
const uint32_t* const top) {
- return ClampedAddSubtractHalf(left, top[0], top[-1]);
+ return ClampedAddSubtractHalf(*left, top[0], top[-1]);
}
// Add green to blue and red channels (i.e. perform the inverse transform of
diff --git a/thirdparty/libwebp/src/dsp/lossless_neon.c b/thirdparty/libwebp/src/dsp/lossless_neon.c
index 76a1b6f873..89e3e013a0 100644
--- a/thirdparty/libwebp/src/dsp/lossless_neon.c
+++ b/thirdparty/libwebp/src/dsp/lossless_neon.c
@@ -188,17 +188,21 @@ static WEBP_INLINE uint32_t Average3_NEON(uint32_t a0, uint32_t a1,
return avg;
}
-static uint32_t Predictor5_NEON(uint32_t left, const uint32_t* const top) {
- return Average3_NEON(left, top[0], top[1]);
+static uint32_t Predictor5_NEON(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average3_NEON(*left, top[0], top[1]);
}
-static uint32_t Predictor6_NEON(uint32_t left, const uint32_t* const top) {
- return Average2_NEON(left, top[-1]);
+static uint32_t Predictor6_NEON(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average2_NEON(*left, top[-1]);
}
-static uint32_t Predictor7_NEON(uint32_t left, const uint32_t* const top) {
- return Average2_NEON(left, top[0]);
+static uint32_t Predictor7_NEON(const uint32_t* const left,
+ const uint32_t* const top) {
+ return Average2_NEON(*left, top[0]);
}
-static uint32_t Predictor13_NEON(uint32_t left, const uint32_t* const top) {
- return ClampedAddSubtractHalf_NEON(left, top[0], top[-1]);
+static uint32_t Predictor13_NEON(const uint32_t* const left,
+ const uint32_t* const top) {
+ return ClampedAddSubtractHalf_NEON(*left, top[0], top[-1]);
}
// Batch versions of those functions.
diff --git a/thirdparty/libwebp/src/dsp/lossless_sse2.c b/thirdparty/libwebp/src/dsp/lossless_sse2.c
index 3a0eb440db..396cb0bdfc 100644
--- a/thirdparty/libwebp/src/dsp/lossless_sse2.c
+++ b/thirdparty/libwebp/src/dsp/lossless_sse2.c
@@ -138,42 +138,51 @@ static WEBP_INLINE uint32_t Average4_SSE2(uint32_t a0, uint32_t a1,
return output;
}
-static uint32_t Predictor5_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average3_SSE2(left, top[0], top[1]);
+static uint32_t Predictor5_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average3_SSE2(*left, top[0], top[1]);
return pred;
}
-static uint32_t Predictor6_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average2_SSE2(left, top[-1]);
+static uint32_t Predictor6_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average2_SSE2(*left, top[-1]);
return pred;
}
-static uint32_t Predictor7_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average2_SSE2(left, top[0]);
+static uint32_t Predictor7_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average2_SSE2(*left, top[0]);
return pred;
}
-static uint32_t Predictor8_SSE2(uint32_t left, const uint32_t* const top) {
+static uint32_t Predictor8_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
const uint32_t pred = Average2_SSE2(top[-1], top[0]);
(void)left;
return pred;
}
-static uint32_t Predictor9_SSE2(uint32_t left, const uint32_t* const top) {
+static uint32_t Predictor9_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
const uint32_t pred = Average2_SSE2(top[0], top[1]);
(void)left;
return pred;
}
-static uint32_t Predictor10_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Average4_SSE2(left, top[-1], top[0], top[1]);
+static uint32_t Predictor10_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Average4_SSE2(*left, top[-1], top[0], top[1]);
return pred;
}
-static uint32_t Predictor11_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = Select_SSE2(top[0], left, top[-1]);
+static uint32_t Predictor11_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = Select_SSE2(top[0], *left, top[-1]);
return pred;
}
-static uint32_t Predictor12_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = ClampedAddSubtractFull_SSE2(left, top[0], top[-1]);
+static uint32_t Predictor12_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = ClampedAddSubtractFull_SSE2(*left, top[0], top[-1]);
return pred;
}
-static uint32_t Predictor13_SSE2(uint32_t left, const uint32_t* const top) {
- const uint32_t pred = ClampedAddSubtractHalf_SSE2(left, top[0], top[-1]);
+static uint32_t Predictor13_SSE2(const uint32_t* const left,
+ const uint32_t* const top) {
+ const uint32_t pred = ClampedAddSubtractHalf_SSE2(*left, top[0], top[-1]);
return pred;
}
diff --git a/thirdparty/libwebp/src/dsp/msa_macro.h b/thirdparty/libwebp/src/dsp/msa_macro.h
index de026a1d9e..51f6c643ab 100644
--- a/thirdparty/libwebp/src/dsp/msa_macro.h
+++ b/thirdparty/libwebp/src/dsp/msa_macro.h
@@ -14,6 +14,10 @@
#ifndef WEBP_DSP_MSA_MACRO_H_
#define WEBP_DSP_MSA_MACRO_H_
+#include "src/dsp/dsp.h"
+
+#if defined(WEBP_USE_MSA)
+
#include <stdint.h>
#include <msa.h>
@@ -1389,4 +1393,5 @@ static WEBP_INLINE uint32_t func_hadd_uh_u32(v8u16 in) {
} while (0)
#define AVER_UB2_UB(...) AVER_UB2(v16u8, __VA_ARGS__)
+#endif // WEBP_USE_MSA
#endif // WEBP_DSP_MSA_MACRO_H_
diff --git a/thirdparty/libwebp/src/dsp/neon.h b/thirdparty/libwebp/src/dsp/neon.h
index aa1dea1301..c591f9b9a7 100644
--- a/thirdparty/libwebp/src/dsp/neon.h
+++ b/thirdparty/libwebp/src/dsp/neon.h
@@ -12,10 +12,12 @@
#ifndef WEBP_DSP_NEON_H_
#define WEBP_DSP_NEON_H_
-#include <arm_neon.h>
-
#include "src/dsp/dsp.h"
+#if defined(WEBP_USE_NEON)
+
+#include <arm_neon.h>
+
// Right now, some intrinsics functions seem slower, so we disable them
// everywhere except newer clang/gcc or aarch64 where the inline assembly is
// incompatible.
@@ -98,4 +100,5 @@ static WEBP_INLINE int32x4x4_t Transpose4x4_NEON(const int32x4x4_t rows) {
} while (0)
#endif
+#endif // WEBP_USE_NEON
#endif // WEBP_DSP_NEON_H_
diff --git a/thirdparty/libwebp/src/dsp/yuv.h b/thirdparty/libwebp/src/dsp/yuv.h
index c12be1d094..66a397d117 100644
--- a/thirdparty/libwebp/src/dsp/yuv.h
+++ b/thirdparty/libwebp/src/dsp/yuv.h
@@ -10,7 +10,7 @@
// inline YUV<->RGB conversion function
//
// The exact naming is Y'CbCr, following the ITU-R BT.601 standard.
-// More information at: http://en.wikipedia.org/wiki/YCbCr
+// More information at: https://en.wikipedia.org/wiki/YCbCr
// Y = 0.2569 * R + 0.5044 * G + 0.0979 * B + 16
// U = -0.1483 * R - 0.2911 * G + 0.4394 * B + 128
// V = 0.4394 * R - 0.3679 * G - 0.0715 * B + 128
diff --git a/thirdparty/libwebp/src/enc/frame_enc.c b/thirdparty/libwebp/src/enc/frame_enc.c
index af538d83ba..b93d9e5b99 100644
--- a/thirdparty/libwebp/src/enc/frame_enc.c
+++ b/thirdparty/libwebp/src/enc/frame_enc.c
@@ -778,6 +778,7 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
// Roughly refresh the proba eight times per pass
int max_count = (enc->mb_w_ * enc->mb_h_) >> 3;
int num_pass_left = enc->config_->pass;
+ int remaining_progress = 40; // percents
const int do_search = enc->do_search_;
VP8EncIterator it;
VP8EncProba* const proba = &enc->proba_;
@@ -805,6 +806,9 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
uint64_t size_p0 = 0;
uint64_t distortion = 0;
int cnt = max_count;
+ // The final number of passes is not trivial to know in advance.
+ const int pass_progress = remaining_progress / (2 + num_pass_left);
+ remaining_progress -= pass_progress;
VP8IteratorInit(enc, &it);
SetLoopParams(enc, stats.q);
if (is_last_pass) {
@@ -832,7 +836,7 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
StoreSideInfo(&it);
VP8StoreFilterStats(&it);
VP8IteratorExport(&it);
- ok = VP8IteratorProgress(&it, 20);
+ ok = VP8IteratorProgress(&it, pass_progress);
}
VP8IteratorSaveBoundary(&it);
} while (ok && VP8IteratorNext(&it));
@@ -878,7 +882,8 @@ int VP8EncTokenLoop(VP8Encoder* const enc) {
ok = VP8EmitTokens(&enc->tokens_, enc->parts_ + 0,
(const uint8_t*)proba->coeffs_, 1);
}
- ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + 20, &enc->percent_);
+ ok = ok && WebPReportProgress(enc->pic_, enc->percent_ + remaining_progress,
+ &enc->percent_);
return PostLoopFinalize(&it, ok);
}
diff --git a/thirdparty/libwebp/src/enc/predictor_enc.c b/thirdparty/libwebp/src/enc/predictor_enc.c
index 2e6762ea0d..2b5c767280 100644
--- a/thirdparty/libwebp/src/enc/predictor_enc.c
+++ b/thirdparty/libwebp/src/enc/predictor_enc.c
@@ -249,7 +249,7 @@ static WEBP_INLINE void GetResidual(
} else if (x == 0) {
predict = upper_row[x]; // Top.
} else {
- predict = pred_func(current_row[x - 1], upper_row + x);
+ predict = pred_func(&current_row[x - 1], upper_row + x);
}
#if (WEBP_NEAR_LOSSLESS == 1)
if (max_quantization == 1 || mode == 0 || y == 0 || y == height - 1 ||
diff --git a/thirdparty/libwebp/src/enc/quant_enc.c b/thirdparty/libwebp/src/enc/quant_enc.c
index 01eb565c7f..6cede28ab4 100644
--- a/thirdparty/libwebp/src/enc/quant_enc.c
+++ b/thirdparty/libwebp/src/enc/quant_enc.c
@@ -585,6 +585,9 @@ static WEBP_INLINE score_t RDScoreTrellis(int lambda, score_t rate,
return rate * lambda + RD_DISTO_MULT * distortion;
}
+// Coefficient type.
+enum { TYPE_I16_AC = 0, TYPE_I16_DC = 1, TYPE_CHROMA_A = 2, TYPE_I4_AC = 3 };
+
static int TrellisQuantizeBlock(const VP8Encoder* const enc,
int16_t in[16], int16_t out[16],
int ctx0, int coeff_type,
@@ -593,7 +596,7 @@ static int TrellisQuantizeBlock(const VP8Encoder* const enc,
const ProbaArray* const probas = enc->proba_.coeffs_[coeff_type];
CostArrayPtr const costs =
(CostArrayPtr)enc->proba_.remapped_costs_[coeff_type];
- const int first = (coeff_type == 0) ? 1 : 0;
+ const int first = (coeff_type == TYPE_I16_AC) ? 1 : 0;
Node nodes[16][NUM_NODES];
ScoreState score_states[2][NUM_NODES];
ScoreState* ss_cur = &SCORE_STATE(0, MIN_DELTA);
@@ -657,16 +660,17 @@ static int TrellisQuantizeBlock(const VP8Encoder* const enc,
// test all alternate level values around level0.
for (m = -MIN_DELTA; m <= MAX_DELTA; ++m) {
Node* const cur = &NODE(n, m);
- int level = level0 + m;
+ const int level = level0 + m;
const int ctx = (level > 2) ? 2 : level;
const int band = VP8EncBands[n + 1];
score_t base_score;
- score_t best_cur_score = MAX_COST;
- int best_prev = 0; // default, in case
+ score_t best_cur_score;
+ int best_prev;
+ score_t cost, score;
- ss_cur[m].score = MAX_COST;
ss_cur[m].costs = costs[n + 1][ctx];
if (level < 0 || level > thresh_level) {
+ ss_cur[m].score = MAX_COST;
// Node is dead.
continue;
}
@@ -682,18 +686,24 @@ static int TrellisQuantizeBlock(const VP8Encoder* const enc,
}
// Inspect all possible non-dead predecessors. Retain only the best one.
- for (p = -MIN_DELTA; p <= MAX_DELTA; ++p) {
+ // The base_score is added to all scores so it is only added for the final
+ // value after the loop.
+ cost = VP8LevelCost(ss_prev[-MIN_DELTA].costs, level);
+ best_cur_score =
+ ss_prev[-MIN_DELTA].score + RDScoreTrellis(lambda, cost, 0);
+ best_prev = -MIN_DELTA;
+ for (p = -MIN_DELTA + 1; p <= MAX_DELTA; ++p) {
// Dead nodes (with ss_prev[p].score >= MAX_COST) are automatically
// eliminated since their score can't be better than the current best.
- const score_t cost = VP8LevelCost(ss_prev[p].costs, level);
+ cost = VP8LevelCost(ss_prev[p].costs, level);
// Examine node assuming it's a non-terminal one.
- const score_t score =
- base_score + ss_prev[p].score + RDScoreTrellis(lambda, cost, 0);
+ score = ss_prev[p].score + RDScoreTrellis(lambda, cost, 0);
if (score < best_cur_score) {
best_cur_score = score;
best_prev = p;
}
}
+ best_cur_score += base_score;
// Store best finding in current node.
cur->sign = sign;
cur->level = level;
@@ -701,11 +711,11 @@ static int TrellisQuantizeBlock(const VP8Encoder* const enc,
ss_cur[m].score = best_cur_score;
// Now, record best terminal node (and thus best entry in the graph).
- if (level != 0) {
+ if (level != 0 && best_cur_score < best_score) {
const score_t last_pos_cost =
(n < 15) ? VP8BitCost(0, probas[band][ctx][0]) : 0;
const score_t last_pos_score = RDScoreTrellis(lambda, last_pos_cost, 0);
- const score_t score = best_cur_score + last_pos_score;
+ score = best_cur_score + last_pos_score;
if (score < best_score) {
best_score = score;
best_path[0] = n; // best eob position
@@ -717,10 +727,16 @@ static int TrellisQuantizeBlock(const VP8Encoder* const enc,
}
// Fresh start
- memset(in + first, 0, (16 - first) * sizeof(*in));
- memset(out + first, 0, (16 - first) * sizeof(*out));
+ // Beware! We must preserve in[0]/out[0] value for TYPE_I16_AC case.
+ if (coeff_type == TYPE_I16_AC) {
+ memset(in + 1, 0, 15 * sizeof(*in));
+ memset(out + 1, 0, 15 * sizeof(*out));
+ } else {
+ memset(in, 0, 16 * sizeof(*in));
+ memset(out, 0, 16 * sizeof(*out));
+ }
if (best_path[0] == -1) {
- return 0; // skip!
+ return 0; // skip!
}
{
@@ -775,9 +791,9 @@ static int ReconstructIntra16(VP8EncIterator* const it,
for (y = 0, n = 0; y < 4; ++y) {
for (x = 0; x < 4; ++x, ++n) {
const int ctx = it->top_nz_[x] + it->left_nz_[y];
- const int non_zero =
- TrellisQuantizeBlock(enc, tmp[n], rd->y_ac_levels[n], ctx, 0,
- &dqm->y1_, dqm->lambda_trellis_i16_);
+ const int non_zero = TrellisQuantizeBlock(
+ enc, tmp[n], rd->y_ac_levels[n], ctx, TYPE_I16_AC, &dqm->y1_,
+ dqm->lambda_trellis_i16_);
it->top_nz_[x] = it->left_nz_[y] = non_zero;
rd->y_ac_levels[n][0] = 0;
nz |= non_zero << n;
@@ -818,7 +834,7 @@ static int ReconstructIntra4(VP8EncIterator* const it,
if (DO_TRELLIS_I4 && it->do_trellis_) {
const int x = it->i4_ & 3, y = it->i4_ >> 2;
const int ctx = it->top_nz_[x] + it->left_nz_[y];
- nz = TrellisQuantizeBlock(enc, tmp, levels, ctx, 3, &dqm->y1_,
+ nz = TrellisQuantizeBlock(enc, tmp, levels, ctx, TYPE_I4_AC, &dqm->y1_,
dqm->lambda_trellis_i4_);
} else {
nz = VP8EncQuantizeBlock(tmp, levels, &dqm->y1_);
@@ -927,9 +943,9 @@ static int ReconstructUV(VP8EncIterator* const it, VP8ModeScore* const rd,
for (y = 0; y < 2; ++y) {
for (x = 0; x < 2; ++x, ++n) {
const int ctx = it->top_nz_[4 + ch + x] + it->left_nz_[4 + ch + y];
- const int non_zero =
- TrellisQuantizeBlock(enc, tmp[n], rd->uv_levels[n], ctx, 2,
- &dqm->uv_, dqm->lambda_trellis_uv_);
+ const int non_zero = TrellisQuantizeBlock(
+ enc, tmp[n], rd->uv_levels[n], ctx, TYPE_CHROMA_A, &dqm->uv_,
+ dqm->lambda_trellis_uv_);
it->top_nz_[4 + ch + x] = it->left_nz_[4 + ch + y] = non_zero;
nz |= non_zero << n;
}
diff --git a/thirdparty/libwebp/src/enc/vp8i_enc.h b/thirdparty/libwebp/src/enc/vp8i_enc.h
index 67e9509367..b4bba08f27 100644
--- a/thirdparty/libwebp/src/enc/vp8i_enc.h
+++ b/thirdparty/libwebp/src/enc/vp8i_enc.h
@@ -32,7 +32,7 @@ extern "C" {
// version numbers
#define ENC_MAJ_VERSION 1
#define ENC_MIN_VERSION 2
-#define ENC_REV_VERSION 1
+#define ENC_REV_VERSION 2
enum { MAX_LF_LEVELS = 64, // Maximum loop filter level
MAX_VARIABLE_LEVEL = 67, // last (inclusive) level with variable cost
diff --git a/thirdparty/libwebp/src/mux/muxi.h b/thirdparty/libwebp/src/mux/muxi.h
index 330da66754..d9bf9b3770 100644
--- a/thirdparty/libwebp/src/mux/muxi.h
+++ b/thirdparty/libwebp/src/mux/muxi.h
@@ -29,7 +29,7 @@ extern "C" {
#define MUX_MAJ_VERSION 1
#define MUX_MIN_VERSION 2
-#define MUX_REV_VERSION 1
+#define MUX_REV_VERSION 2
// Chunk object.
typedef struct WebPChunk WebPChunk;
diff --git a/thirdparty/libwebp/src/utils/huffman_encode_utils.c b/thirdparty/libwebp/src/utils/huffman_encode_utils.c
index fd7a47d8f7..585db91951 100644
--- a/thirdparty/libwebp/src/utils/huffman_encode_utils.c
+++ b/thirdparty/libwebp/src/utils/huffman_encode_utils.c
@@ -161,7 +161,7 @@ static void SetBitDepths(const HuffmanTree* const tree,
// especially when population counts are longer than 2**tree_limit, but
// we are not planning to use this with extremely long blocks.
//
-// See http://en.wikipedia.org/wiki/Huffman_coding
+// See https://en.wikipedia.org/wiki/Huffman_coding
static void GenerateOptimalTree(const uint32_t* const histogram,
int histogram_size,
HuffmanTree* tree, int tree_depth_limit,
diff --git a/thirdparty/libwebp/src/utils/quant_levels_dec_utils.c b/thirdparty/libwebp/src/utils/quant_levels_dec_utils.c
index f65b6cdbb6..97e7893704 100644
--- a/thirdparty/libwebp/src/utils/quant_levels_dec_utils.c
+++ b/thirdparty/libwebp/src/utils/quant_levels_dec_utils.c
@@ -30,7 +30,7 @@
#define DFIX 4 // extra precision for ordered dithering
#define DSIZE 4 // dithering size (must be a power of two)
-// cf. http://en.wikipedia.org/wiki/Ordered_dithering
+// cf. https://en.wikipedia.org/wiki/Ordered_dithering
static const uint8_t kOrderedDither[DSIZE][DSIZE] = {
{ 0, 8, 2, 10 }, // coefficients are in DFIX fixed-point precision
{ 12, 4, 14, 6 },
diff --git a/thirdparty/libwebp/src/utils/utils.c b/thirdparty/libwebp/src/utils/utils.c
index 9e464c16ce..a7c3a70fef 100644
--- a/thirdparty/libwebp/src/utils/utils.c
+++ b/thirdparty/libwebp/src/utils/utils.c
@@ -23,7 +23,7 @@
// alloc/free etc) is printed. For debugging/tuning purpose only (it's slow,
// and not multi-thread safe!).
// An interesting alternative is valgrind's 'massif' tool:
-// http://valgrind.org/docs/manual/ms-manual.html
+// https://valgrind.org/docs/manual/ms-manual.html
// Here is an example command line:
/* valgrind --tool=massif --massif-out-file=massif.out \
--stacks=yes --alloc-fn=WebPSafeMalloc --alloc-fn=WebPSafeCalloc
diff --git a/thirdparty/libwebp/src/webp/decode.h b/thirdparty/libwebp/src/webp/decode.h
index 44fcd64a84..d98247509a 100644
--- a/thirdparty/libwebp/src/webp/decode.h
+++ b/thirdparty/libwebp/src/webp/decode.h
@@ -85,7 +85,7 @@ WEBP_EXTERN uint8_t* WebPDecodeBGR(const uint8_t* data, size_t data_size,
// Upon return, the Y buffer has a stride returned as '*stride', while U and V
// have a common stride returned as '*uv_stride'.
// Return NULL in case of error.
-// (*) Also named Y'CbCr. See: http://en.wikipedia.org/wiki/YCbCr
+// (*) Also named Y'CbCr. See: https://en.wikipedia.org/wiki/YCbCr
WEBP_EXTERN uint8_t* WebPDecodeYUV(const uint8_t* data, size_t data_size,
int* width, int* height,
uint8_t** u, uint8_t** v,
diff --git a/thirdparty/misc/patches/polypartition-godot-types.patch b/thirdparty/misc/patches/polypartition-godot-types.patch
index 782f02e8dc..61737f9fd2 100644
--- a/thirdparty/misc/patches/polypartition-godot-types.patch
+++ b/thirdparty/misc/patches/polypartition-godot-types.patch
@@ -1,19 +1,16 @@
diff --git a/thirdparty/misc/polypartition.cpp b/thirdparty/misc/polypartition.cpp
-index 3a8a6efa83..5e94793b79 100644
+index 3a8a6efa83..8c5409bf24 100644
--- a/thirdparty/misc/polypartition.cpp
+++ b/thirdparty/misc/polypartition.cpp
-@@ -23,10 +23,7 @@
-
- #include "polypartition.h"
-
--#include <math.h>
--#include <string.h>
+@@ -26,7 +26,6 @@
+ #include <math.h>
+ #include <string.h>
#include <algorithm>
-#include <vector>
TPPLPoly::TPPLPoly() {
hole = false;
-@@ -186,7 +183,7 @@ int TPPLPartition::Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TP
+@@ -186,7 +185,7 @@ int TPPLPartition::Intersects(TPPLPoint &p11, TPPLPoint &p12, TPPLPoint &p21, TP
// Removes holes from inpolys by merging them with non-holes.
int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
TPPLPolyList polys;
@@ -22,7 +19,7 @@ index 3a8a6efa83..5e94793b79 100644
long i, i2, holepointindex, polypointindex;
TPPLPoint holepoint, polypoint, bestpolypoint;
TPPLPoint linep1, linep2;
-@@ -198,15 +195,15 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -198,15 +197,15 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
// Check for the trivial case of no holes.
hasholes = false;
@@ -42,7 +39,7 @@ index 3a8a6efa83..5e94793b79 100644
}
return 1;
}
-@@ -216,8 +213,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -216,8 +215,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
while (1) {
// Find the hole point with the largest x.
hasholes = false;
@@ -53,7 +50,7 @@ index 3a8a6efa83..5e94793b79 100644
continue;
}
-@@ -227,8 +224,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -227,8 +226,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
holepointindex = 0;
}
@@ -64,7 +61,7 @@ index 3a8a6efa83..5e94793b79 100644
holeiter = iter;
holepointindex = i;
}
-@@ -237,24 +234,24 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -237,24 +236,24 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
if (!hasholes) {
break;
}
@@ -98,7 +95,7 @@ index 3a8a6efa83..5e94793b79 100644
if (pointfound) {
v1 = Normalize(polypoint - holepoint);
v2 = Normalize(bestpolypoint - holepoint);
-@@ -263,13 +260,13 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -263,13 +262,13 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
}
}
pointvisible = true;
@@ -117,7 +114,7 @@ index 3a8a6efa83..5e94793b79 100644
if (Intersects(holepoint, polypoint, linep1, linep2)) {
pointvisible = false;
break;
-@@ -292,18 +289,18 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -292,18 +291,18 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
return 0;
}
@@ -142,7 +139,7 @@ index 3a8a6efa83..5e94793b79 100644
i2++;
}
-@@ -312,8 +309,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
+@@ -312,8 +311,8 @@ int TPPLPartition::RemoveHoles(TPPLPolyList *inpolys, TPPLPolyList *outpolys) {
polys.push_back(newpoly);
}
@@ -153,7 +150,7 @@ index 3a8a6efa83..5e94793b79 100644
}
return 1;
-@@ -524,13 +521,13 @@ int TPPLPartition::Triangulate_EC(TPPLPoly *poly, TPPLPolyList *triangles) {
+@@ -524,13 +523,13 @@ int TPPLPartition::Triangulate_EC(TPPLPoly *poly, TPPLPolyList *triangles) {
int TPPLPartition::Triangulate_EC(TPPLPolyList *inpolys, TPPLPolyList *triangles) {
TPPLPolyList outpolys;
@@ -170,7 +167,7 @@ index 3a8a6efa83..5e94793b79 100644
return 0;
}
}
-@@ -543,7 +540,7 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -543,7 +542,7 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
}
TPPLPolyList triangles;
@@ -179,7 +176,7 @@ index 3a8a6efa83..5e94793b79 100644
TPPLPoly *poly1 = NULL, *poly2 = NULL;
TPPLPoly newpoly;
TPPLPoint d1, d2, p1, p2, p3;
-@@ -578,19 +575,19 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -578,19 +577,19 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
return 0;
}
@@ -203,7 +200,7 @@ index 3a8a6efa83..5e94793b79 100644
for (i21 = 0; i21 < poly2->GetNumPoints(); i21++) {
if ((d2.x != poly2->GetPoint(i21).x) || (d2.y != poly2->GetPoint(i21).y)) {
-@@ -660,16 +657,16 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -660,16 +659,16 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
}
triangles.erase(iter2);
@@ -224,7 +221,7 @@ index 3a8a6efa83..5e94793b79 100644
}
return 1;
-@@ -677,13 +674,13 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -677,13 +676,13 @@ int TPPLPartition::ConvexPartition_HM(TPPLPoly *poly, TPPLPolyList *parts) {
int TPPLPartition::ConvexPartition_HM(TPPLPolyList *inpolys, TPPLPolyList *parts) {
TPPLPolyList outpolys;
@@ -241,7 +238,7 @@ index 3a8a6efa83..5e94793b79 100644
return 0;
}
}
-@@ -824,8 +821,8 @@ int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, TPPLPolyList *triangles) {
+@@ -824,8 +823,8 @@ int TPPLPartition::Triangulate_OPT(TPPLPoly *poly, TPPLPolyList *triangles) {
newdiagonal.index1 = 0;
newdiagonal.index2 = n - 1;
diagonals.push_back(newdiagonal);
@@ -252,7 +249,7 @@ index 3a8a6efa83..5e94793b79 100644
diagonals.pop_front();
bestvertex = dpstates[diagonal.index2][diagonal.index1].bestvertex;
if (bestvertex == -1) {
-@@ -873,10 +870,10 @@ void TPPLPartition::UpdateState(long a, long b, long w, long i, long j, DPState2
+@@ -873,10 +872,10 @@ void TPPLPartition::UpdateState(long a, long b, long w, long i, long j, DPState2
pairs->push_front(newdiagonal);
dpstates[a][b].weight = w;
} else {
@@ -265,7 +262,7 @@ index 3a8a6efa83..5e94793b79 100644
pairs->pop_front();
}
pairs->push_front(newdiagonal);
-@@ -885,7 +882,7 @@ void TPPLPartition::UpdateState(long a, long b, long w, long i, long j, DPState2
+@@ -885,7 +884,7 @@ void TPPLPartition::UpdateState(long a, long b, long w, long i, long j, DPState2
void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) {
DiagonalList *pairs = NULL;
@@ -274,7 +271,7 @@ index 3a8a6efa83..5e94793b79 100644
long top;
long w;
-@@ -902,23 +899,23 @@ void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPS
+@@ -902,23 +901,23 @@ void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPS
}
if (j - i > 1) {
pairs = &(dpstates[i][j].pairs);
@@ -305,7 +302,7 @@ index 3a8a6efa83..5e94793b79 100644
}
}
}
-@@ -927,7 +924,7 @@ void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPS
+@@ -927,7 +926,7 @@ void TPPLPartition::TypeA(long i, long j, long k, PartitionVertex *vertices, DPS
void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPState2 **dpstates) {
DiagonalList *pairs = NULL;
@@ -314,7 +311,7 @@ index 3a8a6efa83..5e94793b79 100644
long top;
long w;
-@@ -946,21 +943,21 @@ void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPS
+@@ -946,21 +945,21 @@ void TPPLPartition::TypeB(long i, long j, long k, PartitionVertex *vertices, DPS
if (k - j > 1) {
pairs = &(dpstates[j][k].pairs);
@@ -343,7 +340,7 @@ index 3a8a6efa83..5e94793b79 100644
}
} else {
w++;
-@@ -981,11 +978,11 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -981,11 +980,11 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
DiagonalList diagonals, diagonals2;
Diagonal diagonal, newdiagonal;
DiagonalList *pairs = NULL, *pairs2 = NULL;
@@ -358,7 +355,7 @@ index 3a8a6efa83..5e94793b79 100644
bool ijreal, jkreal;
n = poly->GetNumPoints();
-@@ -1110,35 +1107,35 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1110,35 +1109,35 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
newdiagonal.index1 = 0;
newdiagonal.index2 = n - 1;
diagonals.push_front(newdiagonal);
@@ -403,7 +400,7 @@ index 3a8a6efa83..5e94793b79 100644
pairs2->pop_back();
} else {
break;
-@@ -1153,21 +1150,21 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1153,21 +1152,21 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
diagonals.push_front(newdiagonal);
}
} else {
@@ -431,7 +428,7 @@ index 3a8a6efa83..5e94793b79 100644
pairs2->pop_front();
} else {
break;
-@@ -1197,8 +1194,8 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1197,8 +1196,8 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
newdiagonal.index1 = 0;
newdiagonal.index2 = n - 1;
diagonals.push_front(newdiagonal);
@@ -442,7 +439,7 @@ index 3a8a6efa83..5e94793b79 100644
diagonals.pop_front();
if ((diagonal.index2 - diagonal.index1) <= 1) {
continue;
-@@ -1210,8 +1207,8 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1210,8 +1209,8 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
indices.push_back(diagonal.index2);
diagonals2.push_front(diagonal);
@@ -453,7 +450,7 @@ index 3a8a6efa83..5e94793b79 100644
diagonals2.pop_front();
if ((diagonal.index2 - diagonal.index1) <= 1) {
continue;
-@@ -1220,16 +1217,16 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1220,16 +1219,16 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
jkreal = true;
pairs = &(dpstates[diagonal.index1][diagonal.index2].pairs);
if (!vertices[diagonal.index1].isConvex) {
@@ -476,7 +473,7 @@ index 3a8a6efa83..5e94793b79 100644
jkreal = false;
}
}
-@@ -1253,11 +1250,12 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1253,11 +1252,12 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
indices.push_back(j);
}
@@ -492,7 +489,7 @@ index 3a8a6efa83..5e94793b79 100644
k++;
}
parts->push_back(newpoly);
-@@ -1281,7 +1279,7 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
+@@ -1281,7 +1281,7 @@ int TPPLPartition::ConvexPartition_OPT(TPPLPoly *poly, TPPLPolyList *parts) {
// "Computational Geometry: Algorithms and Applications"
// by Mark de Berg, Otfried Cheong, Marc van Kreveld, and Mark Overmars.
int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monotonePolys) {
@@ -501,7 +498,7 @@ index 3a8a6efa83..5e94793b79 100644
MonotoneVertex *vertices = NULL;
long i, numvertices, vindex, vindex2, newnumvertices, maxnumvertices;
long polystartindex, polyendindex;
-@@ -1291,11 +1289,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1291,11 +1291,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
bool error = false;
numvertices = 0;
@@ -515,7 +512,7 @@ index 3a8a6efa83..5e94793b79 100644
}
maxnumvertices = numvertices * 3;
-@@ -1303,8 +1298,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1303,8 +1300,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
newnumvertices = numvertices;
polystartindex = 0;
@@ -526,7 +523,7 @@ index 3a8a6efa83..5e94793b79 100644
polyendindex = polystartindex + poly->GetNumPoints() - 1;
for (i = 0; i < poly->GetNumPoints(); i++) {
vertices[i + polystartindex].p = poly->GetPoint(i);
-@@ -1360,14 +1355,14 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1360,14 +1357,14 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
// Note that while set doesn't actually have to be implemented as
// a tree, complexity requirements for operations are the same as
// for the balanced binary search tree.
@@ -546,7 +543,7 @@ index 3a8a6efa83..5e94793b79 100644
}
// For each vertex.
-@@ -1387,13 +1382,14 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1387,13 +1384,14 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
newedge.p1 = v->p;
newedge.p2 = vertices[v->next].p;
newedge.index = vindex;
@@ -564,7 +561,7 @@ index 3a8a6efa83..5e94793b79 100644
error = true;
break;
}
-@@ -1412,29 +1408,30 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1412,29 +1410,30 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
newedge.p1 = v->p;
newedge.p2 = v->p;
edgeIter = edgeTree.lower_bound(newedge);
@@ -601,7 +598,7 @@ index 3a8a6efa83..5e94793b79 100644
error = true;
break;
}
-@@ -1452,25 +1449,25 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1452,25 +1451,25 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
newedge.p1 = v->p;
newedge.p2 = v->p;
edgeIter = edgeTree.lower_bound(newedge);
@@ -632,7 +629,7 @@ index 3a8a6efa83..5e94793b79 100644
error = true;
break;
}
-@@ -1488,27 +1485,28 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1488,27 +1487,28 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
newedge.p1 = v2->p;
newedge.p2 = vertices[v2->next].p;
newedge.index = vindex2;
@@ -668,7 +665,7 @@ index 3a8a6efa83..5e94793b79 100644
}
break;
}
-@@ -1569,8 +1567,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
+@@ -1569,8 +1569,8 @@ int TPPLPartition::MonotonePartition(TPPLPolyList *inpolys, TPPLPolyList *monoto
// Adds a diagonal to the doubly-connected list of vertices.
void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, long index1, long index2,
@@ -679,7 +676,7 @@ index 3a8a6efa83..5e94793b79 100644
long newindex1, newindex2;
newindex1 = *numvertices;
-@@ -1597,14 +1595,14 @@ void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, lon
+@@ -1597,14 +1597,14 @@ void TPPLPartition::AddDiagonal(MonotoneVertex *vertices, long *numvertices, lon
vertextypes[newindex1] = vertextypes[index1];
edgeTreeIterators[newindex1] = edgeTreeIterators[index1];
helpers[newindex1] = helpers[index1];
@@ -698,7 +695,7 @@ index 3a8a6efa83..5e94793b79 100644
}
}
-@@ -1830,13 +1828,13 @@ int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, TPPLPolyList *triangles
+@@ -1830,13 +1830,13 @@ int TPPLPartition::TriangulateMonotone(TPPLPoly *inPoly, TPPLPolyList *triangles
int TPPLPartition::Triangulate_MONO(TPPLPolyList *inpolys, TPPLPolyList *triangles) {
TPPLPolyList monotone;
diff --git a/thirdparty/misc/polypartition.cpp b/thirdparty/misc/polypartition.cpp
index 5e94793b79..8c5409bf24 100644
--- a/thirdparty/misc/polypartition.cpp
+++ b/thirdparty/misc/polypartition.cpp
@@ -23,6 +23,8 @@
#include "polypartition.h"
+#include <math.h>
+#include <string.h>
#include <algorithm>
TPPLPoly::TPPLPoly() {