summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/crypto/crypto.cpp39
-rw-r--r--core/crypto/crypto.h34
-rw-r--r--core/extension/gdextension_interface.cpp8
-rw-r--r--core/extension/gdextension_interface.h1
-rw-r--r--core/io/dtls_server.cpp2
-rw-r--r--core/io/dtls_server.h2
-rw-r--r--core/io/http_client.cpp2
-rw-r--r--core/io/http_client.h3
-rw-r--r--core/io/http_client_tcp.cpp33
-rw-r--r--core/io/http_client_tcp.h7
-rw-r--r--core/io/packet_peer_dtls.cpp2
-rw-r--r--core/io/packet_peer_dtls.h2
-rw-r--r--core/io/resource.cpp36
-rw-r--r--core/io/stream_peer_tls.cpp20
-rw-r--r--core/io/stream_peer_tls.h11
-rw-r--r--core/math/transform_2d.cpp39
-rw-r--r--core/register_core_types.cpp1
-rw-r--r--core/variant/variant_call.cpp1
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/Animation.xml15
-rw-r--r--doc/classes/AnimationNode.xml21
-rw-r--r--doc/classes/AnimationNodeTransition.xml26
-rw-r--r--doc/classes/AnimationPlayer.xml4
-rw-r--r--doc/classes/AnimationTree.xml4
-rw-r--r--doc/classes/Array.xml12
-rw-r--r--doc/classes/AudioStreamPlayer.xml6
-rw-r--r--doc/classes/AudioStreamPlayer2D.xml6
-rw-r--r--doc/classes/AudioStreamPlayer3D.xml6
-rw-r--r--doc/classes/DTLSServer.xml6
-rw-r--r--doc/classes/Dictionary.xml2
-rw-r--r--doc/classes/EditorImportPlugin.xml4
-rw-r--r--doc/classes/EditorSettings.xml4
-rw-r--r--doc/classes/EditorTranslationParserPlugin.xml3
-rw-r--r--doc/classes/HTTPClient.xml7
-rw-r--r--doc/classes/HTTPRequest.xml17
-rw-r--r--doc/classes/HashingContext.xml11
-rw-r--r--doc/classes/Label3D.xml6
-rw-r--r--doc/classes/NavigationServer2D.xml8
-rw-r--r--doc/classes/NavigationServer3D.xml8
-rw-r--r--doc/classes/OS.xml2
-rw-r--r--doc/classes/PacketPeerDTLS.xml7
-rw-r--r--doc/classes/ProjectSettings.xml7
-rw-r--r--doc/classes/SpriteBase3D.xml9
-rw-r--r--doc/classes/StreamPeerTLS.xml18
-rw-r--r--doc/classes/TLSOptions.xml53
-rw-r--r--drivers/alsa/asound-so_wrap.c4
-rw-r--r--drivers/alsa/asound-so_wrap.h4
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.cpp155
-rw-r--r--drivers/gles3/rasterizer_canvas_gles3.h15
-rw-r--r--drivers/gles3/shaders/skeleton.glsl17
-rw-r--r--drivers/gles3/storage/mesh_storage.cpp25
-rw-r--r--drivers/gles3/storage/mesh_storage.h2
-rw-r--r--drivers/pulseaudio/pulse-so_wrap.c4
-rw-r--r--drivers/pulseaudio/pulse-so_wrap.h4
-rw-r--r--editor/animation_track_editor.cpp50
-rw-r--r--editor/animation_track_editor.h5
-rw-r--r--editor/code_editor.cpp4
-rw-r--r--editor/connections_dialog.cpp85
-rw-r--r--editor/connections_dialog.h3
-rw-r--r--editor/debugger/script_editor_debugger.cpp4
-rw-r--r--editor/editor_inspector.h8
-rw-r--r--editor/editor_node.cpp15
-rw-r--r--editor/editor_properties.cpp3
-rw-r--r--editor/editor_settings.cpp1
-rw-r--r--editor/editor_themes.cpp13
-rw-r--r--editor/icons/UseBlendDisable.svg1
-rw-r--r--editor/icons/UseBlendEnable.svg1
-rw-r--r--editor/import/resource_importer_scene.cpp1
-rw-r--r--editor/input_event_configuration_dialog.cpp2
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp19
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp12
-rw-r--r--editor/project_converter_3_to_4.cpp4
-rwxr-xr-xmisc/scripts/codespell.sh2
-rw-r--r--modules/enet/doc_classes/ENetConnection.xml12
-rw-r--r--modules/enet/enet_connection.cpp14
-rw-r--r--modules/enet/enet_connection.h4
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp2
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml15
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp2
-rw-r--r--modules/gdscript/gdscript.cpp2
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp312
-rw-r--r--modules/gdscript/gdscript_analyzer.h5
-rw-r--r--modules/gdscript/gdscript_parser.cpp48
-rw-r--r--modules/gdscript/gdscript_warning.cpp9
-rw-r--r--modules/gdscript/gdscript_warning.h3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out2
-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.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out2
-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.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.gd5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd16
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/const_conversions.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd32
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/hard_variants.out5
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd34
-rw-r--r--modules/gdscript/tests/scripts/analyzer/features/return_conversions.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd (renamed from modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd)0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out6
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd (renamed from modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd)0
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.gd15
-rw-r--r--modules/gdscript/tests/scripts/parser/features/export_enum.out7
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match_with_variables.gd22
-rw-r--r--modules/gdscript/tests/scripts/parser/features/match_with_variables.out5
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/enum_assign_int_without_casting.out8
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.gd6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.out6
-rw-r--r--modules/gltf/doc_classes/GLTFDocument.xml1
-rw-r--r--modules/gltf/editor/editor_scene_importer_blend.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_fbx.cpp2
-rw-r--r--modules/gltf/editor/editor_scene_importer_gltf.cpp16
-rw-r--r--modules/gltf/gltf_document.cpp65
-rw-r--r--modules/gltf/gltf_document.h4
-rw-r--r--modules/mbedtls/dtls_server_mbedtls.cpp20
-rw-r--r--modules/mbedtls/dtls_server_mbedtls.h8
-rw-r--r--modules/mbedtls/packet_peer_mbed_dtls.cpp16
-rw-r--r--modules/mbedtls/packet_peer_mbed_dtls.h4
-rw-r--r--modules/mbedtls/stream_peer_mbedtls.cpp43
-rw-r--r--modules/mbedtls/stream_peer_mbedtls.h4
-rw-r--r--modules/mbedtls/tls_context_mbedtls.cpp47
-rw-r--r--modules/mbedtls/tls_context_mbedtls.h10
-rw-r--r--modules/minimp3/doc_classes/AudioStreamMP3.xml8
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs20
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs30
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs15
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs15
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs65
-rw-r--r--modules/mono/utils/naming_utils.cpp2
-rw-r--r--modules/navigation/godot_navigation_server.cpp41
-rw-r--r--modules/navigation/godot_navigation_server.h7
-rw-r--r--modules/navigation/rvo_agent.cpp32
-rw-r--r--modules/navigation/rvo_agent.h13
-rw-r--r--modules/noise/doc_classes/Noise.xml4
-rw-r--r--modules/noise/doc_classes/NoiseTexture2D.xml4
-rw-r--r--modules/noise/noise.cpp77
-rw-r--r--modules/noise/noise.h4
-rw-r--r--modules/noise/noise_texture_2d.cpp20
-rw-r--r--modules/noise/noise_texture_2d.h4
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml10
-rw-r--r--modules/websocket/doc_classes/WebSocketPeer.xml5
-rw-r--r--modules/websocket/emws_peer.cpp6
-rw-r--r--modules/websocket/emws_peer.h2
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp27
-rw-r--r--modules/websocket/websocket_multiplayer_peer.h8
-rw-r--r--modules/websocket/websocket_peer.cpp2
-rw-r--r--modules/websocket/websocket_peer.h2
-rw-r--r--modules/websocket/wsl_peer.cpp15
-rw-r--r--modules/websocket/wsl_peer.h5
-rw-r--r--platform/android/export/export_plugin.cpp5
-rw-r--r--platform/android/export/gradle_export_util.cpp11
-rw-r--r--platform/android/java/app/AndroidManifest.xml9
-rw-r--r--platform/linuxbsd/dbus-so_wrap.c4
-rw-r--r--platform/linuxbsd/dbus-so_wrap.h4
-rw-r--r--platform/linuxbsd/fontconfig-so_wrap.c4
-rw-r--r--platform/linuxbsd/fontconfig-so_wrap.h4
-rw-r--r--platform/linuxbsd/libudev-so_wrap.c4
-rw-r--r--platform/linuxbsd/libudev-so_wrap.h4
-rw-r--r--platform/linuxbsd/speechd-so_wrap.c4
-rw-r--r--platform/linuxbsd/speechd-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/display_server_x11.cpp117
-rw-r--r--platform/linuxbsd/x11/display_server_x11.h5
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c4
-rw-r--r--platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h4
-rw-r--r--platform/linuxbsd/xkbcommon-so_wrap.c160
-rw-r--r--platform/linuxbsd/xkbcommon-so_wrap.h62
-rw-r--r--platform/web/export/editor_http_server.h3
-rw-r--r--platform/web/http_client_web.cpp10
-rw-r--r--platform/web/http_client_web.h2
-rw-r--r--scene/2d/animated_sprite_2d.cpp3
-rw-r--r--scene/2d/area_2d.cpp1
-rw-r--r--scene/2d/audio_listener_2d.cpp4
-rw-r--r--scene/2d/audio_listener_2d.h2
-rw-r--r--scene/2d/audio_stream_player_2d.cpp18
-rw-r--r--scene/2d/audio_stream_player_2d.h2
-rw-r--r--scene/2d/back_buffer_copy.cpp1
-rw-r--r--scene/2d/camera_2d.cpp1
-rw-r--r--scene/2d/collision_object_2d.cpp1
-rw-r--r--scene/2d/collision_polygon_2d.cpp16
-rw-r--r--scene/2d/collision_shape_2d.cpp1
-rw-r--r--scene/2d/gpu_particles_2d.cpp2
-rw-r--r--scene/2d/joint_2d.cpp1
-rw-r--r--scene/2d/light_2d.cpp2
-rw-r--r--scene/2d/marker_2d.cpp1
-rw-r--r--scene/2d/navigation_agent_2d.cpp8
-rw-r--r--scene/2d/navigation_link_2d.cpp1
-rw-r--r--scene/2d/navigation_region_2d.cpp1
-rw-r--r--scene/2d/ray_cast_2d.cpp1
-rw-r--r--scene/2d/remote_transform_2d.cpp1
-rw-r--r--scene/2d/shape_cast_2d.cpp4
-rw-r--r--scene/2d/shape_cast_2d.h2
-rw-r--r--scene/2d/skeleton_2d.cpp2
-rw-r--r--scene/2d/tile_map.cpp11
-rw-r--r--scene/2d/visible_on_screen_notifier_2d.cpp1
-rw-r--r--scene/3d/audio_stream_player_3d.cpp24
-rw-r--r--scene/3d/audio_stream_player_3d.h2
-rw-r--r--scene/3d/label_3d.cpp32
-rw-r--r--scene/3d/label_3d.h8
-rw-r--r--scene/3d/navigation_agent_3d.cpp8
-rw-r--r--scene/3d/sprite_3d.cpp58
-rw-r--r--scene/3d/sprite_3d.h12
-rw-r--r--scene/animation/animation_blend_tree.cpp174
-rw-r--r--scene/animation/animation_blend_tree.h30
-rw-r--r--scene/animation/animation_player.cpp249
-rw-r--r--scene/animation/animation_player.h29
-rw-r--r--scene/animation/animation_tree.cpp351
-rw-r--r--scene/animation/animation_tree.h40
-rw-r--r--scene/animation/root_motion_view.cpp3
-rw-r--r--scene/audio/audio_stream_player.cpp5
-rw-r--r--scene/audio/audio_stream_player.h1
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/graph_edit.cpp5
-rw-r--r--scene/main/canvas_item.cpp6
-rw-r--r--scene/main/canvas_item.h4
-rw-r--r--scene/main/http_request.cpp21
-rw-r--r--scene/main/http_request.h8
-rw-r--r--scene/main/node.cpp9
-rw-r--r--scene/resources/animation.cpp35
-rw-r--r--scene/resources/animation.h3
-rw-r--r--scene/resources/capsule_shape_2d.cpp2
-rw-r--r--scene/resources/circle_shape_2d.cpp1
-rw-r--r--scene/resources/convex_polygon_shape_2d.cpp9
-rw-r--r--scene/resources/material.cpp60
-rw-r--r--scene/resources/material.h2
-rw-r--r--scene/resources/rectangle_shape_2d.cpp6
-rw-r--r--scene/resources/style_box.cpp2
-rw-r--r--scene/resources/world_2d.cpp18
-rw-r--r--scene/resources/world_2d.h2
-rw-r--r--scene/resources/world_3d.cpp17
-rw-r--r--scene/resources/world_3d.h2
-rw-r--r--scene/resources/world_boundary_shape_2d.cpp7
-rw-r--r--servers/navigation_server_2d.cpp39
-rw-r--r--servers/navigation_server_2d.h2
-rw-r--r--servers/navigation_server_3d.cpp12
-rw-r--r--servers/navigation_server_3d.h2
-rw-r--r--servers/rendering/dummy/storage/mesh_storage.h1
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp1
-rw-r--r--servers/rendering/renderer_rd/shaders/skeleton.glsl17
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp21
-rw-r--r--servers/rendering/renderer_rd/storage_rd/mesh_storage.h10
-rw-r--r--servers/rendering/storage/mesh_storage.h1
-rw-r--r--servers/rendering_server.cpp2
-rw-r--r--tests/core/input/test_input_event_mouse.h81
-rw-r--r--tests/core/math/test_transform_2d.h13
-rw-r--r--tests/test_main.cpp1
-rw-r--r--thirdparty/enet/enet/godot_ext.h4
-rw-r--r--thirdparty/enet/godot.cpp22
274 files changed, 2903 insertions, 1469 deletions
diff --git a/core/crypto/crypto.cpp b/core/crypto/crypto.cpp
index cbb18a277f..939c1c298f 100644
--- a/core/crypto/crypto.cpp
+++ b/core/crypto/crypto.cpp
@@ -65,6 +65,45 @@ void X509Certificate::_bind_methods() {
ClassDB::bind_method(D_METHOD("load", "path"), &X509Certificate::load);
}
+/// TLSOptions
+
+Ref<TLSOptions> TLSOptions::client(Ref<X509Certificate> p_trusted_chain, const String &p_common_name_override) {
+ Ref<TLSOptions> opts;
+ opts.instantiate();
+ opts->trusted_ca_chain = p_trusted_chain;
+ opts->common_name = p_common_name_override;
+ opts->verify_mode = TLS_VERIFY_FULL;
+ return opts;
+}
+
+Ref<TLSOptions> TLSOptions::client_unsafe(Ref<X509Certificate> p_trusted_chain) {
+ Ref<TLSOptions> opts;
+ opts.instantiate();
+ opts->trusted_ca_chain = p_trusted_chain;
+ if (p_trusted_chain.is_null()) {
+ opts->verify_mode = TLS_VERIFY_NONE;
+ } else {
+ opts->verify_mode = TLS_VERIFY_CERT;
+ }
+ return opts;
+}
+
+Ref<TLSOptions> TLSOptions::server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate) {
+ Ref<TLSOptions> opts;
+ opts.instantiate();
+ opts->server_mode = true;
+ opts->own_certificate = p_own_certificate;
+ opts->private_key = p_own_key;
+ opts->verify_mode = TLS_VERIFY_NONE;
+ return opts;
+}
+
+void TLSOptions::_bind_methods() {
+ ClassDB::bind_static_method("TLSOptions", D_METHOD("client", "trusted_chain", "common_name_override"), &TLSOptions::client, DEFVAL(Ref<X509Certificate>()), DEFVAL(String()));
+ ClassDB::bind_static_method("TLSOptions", D_METHOD("client_unsafe", "trusted_chain"), &TLSOptions::client_unsafe, DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_static_method("TLSOptions", D_METHOD("server", "key", "certificate"), &TLSOptions::server);
+}
+
/// HMACContext
void HMACContext::_bind_methods() {
diff --git a/core/crypto/crypto.h b/core/crypto/crypto.h
index 981f67883c..999fe076d6 100644
--- a/core/crypto/crypto.h
+++ b/core/crypto/crypto.h
@@ -67,6 +67,40 @@ public:
virtual Error save(String p_path) = 0;
};
+class TLSOptions : public RefCounted {
+ GDCLASS(TLSOptions, RefCounted);
+
+public:
+ enum TLSVerifyMode {
+ TLS_VERIFY_NONE = 0,
+ TLS_VERIFY_CERT = 1,
+ TLS_VERIFY_FULL = 2,
+ };
+
+private:
+ bool server_mode = false;
+ String common_name;
+ TLSVerifyMode verify_mode = TLS_VERIFY_FULL;
+ Ref<X509Certificate> trusted_ca_chain;
+ Ref<X509Certificate> own_certificate;
+ Ref<CryptoKey> private_key;
+
+protected:
+ static void _bind_methods();
+
+public:
+ static Ref<TLSOptions> client(Ref<X509Certificate> p_trusted_chain = Ref<X509Certificate>(), const String &p_common_name_override = String());
+ static Ref<TLSOptions> client_unsafe(Ref<X509Certificate> p_trusted_chain);
+ static Ref<TLSOptions> server(Ref<CryptoKey> p_own_key, Ref<X509Certificate> p_own_certificate);
+
+ TLSVerifyMode get_verify_mode() const { return verify_mode; }
+ String get_common_name() const { return common_name; }
+ Ref<X509Certificate> get_trusted_ca_chain() const { return trusted_ca_chain; }
+ Ref<X509Certificate> get_own_certificate() const { return own_certificate; }
+ Ref<CryptoKey> get_private_key() const { return private_key; }
+ bool is_server() const { return server_mode; }
+};
+
class HMACContext : public RefCounted {
GDCLASS(HMACContext, RefCounted);
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index a9063c8b27..63ed60a710 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -856,6 +856,13 @@ static GDExtensionVariantPtr gdextension_array_operator_index_const(GDExtensionC
return (GDExtensionVariantPtr)&self->operator[](p_index);
}
+void gdextension_array_set_typed(GDExtensionTypePtr p_self, uint32_t p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script) {
+ Array *self = reinterpret_cast<Array *>(p_self);
+ const StringName *class_name = reinterpret_cast<const StringName *>(p_class_name);
+ const Variant *script = reinterpret_cast<const Variant *>(p_script);
+ self->set_typed(p_type, *class_name, *script);
+}
+
/* Dictionary functions */
static GDExtensionVariantPtr gdextension_dictionary_operator_index(GDExtensionTypePtr p_self, GDExtensionConstVariantPtr p_key) {
@@ -1129,6 +1136,7 @@ void gdextension_setup_interface(GDExtensionInterface *p_interface) {
gde_interface.array_operator_index = gdextension_array_operator_index;
gde_interface.array_operator_index_const = gdextension_array_operator_index_const;
+ gde_interface.array_set_typed = gdextension_array_set_typed;
/* Dictionary functions */
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 6e5dee8265..7f8f7374e9 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -551,6 +551,7 @@ typedef struct {
GDExtensionVariantPtr (*array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
GDExtensionVariantPtr (*array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
+ void (*array_set_typed)(GDExtensionTypePtr p_self, uint32_t p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); // p_self should be an Array ptr
/* Dictionary functions */
diff --git a/core/io/dtls_server.cpp b/core/io/dtls_server.cpp
index c542e394a1..07d62d3a8d 100644
--- a/core/io/dtls_server.cpp
+++ b/core/io/dtls_server.cpp
@@ -48,6 +48,6 @@ bool DTLSServer::is_available() {
}
void DTLSServer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("setup", "key", "certificate", "chain"), &DTLSServer::setup, DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("setup", "server_options"), &DTLSServer::setup);
ClassDB::bind_method(D_METHOD("take_connection", "udp_peer"), &DTLSServer::take_connection);
}
diff --git a/core/io/dtls_server.h b/core/io/dtls_server.h
index e749e6968b..f3fbde3c15 100644
--- a/core/io/dtls_server.h
+++ b/core/io/dtls_server.h
@@ -47,7 +47,7 @@ public:
static bool is_available();
static DTLSServer *create();
- virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0;
+ virtual Error setup(Ref<TLSOptions> p_options) = 0;
virtual void stop() = 0;
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer) = 0;
diff --git a/core/io/http_client.cpp b/core/io/http_client.cpp
index 829abdc614..190edbfb82 100644
--- a/core/io/http_client.cpp
+++ b/core/io/http_client.cpp
@@ -138,7 +138,7 @@ PackedStringArray HTTPClient::_get_response_headers() {
}
void HTTPClient::_bind_methods() {
- ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "use_tls", "verify_host"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(false), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("connect_to_host", "host", "port", "tls_options"), &HTTPClient::connect_to_host, DEFVAL(-1), DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("set_connection", "connection"), &HTTPClient::set_connection);
ClassDB::bind_method(D_METHOD("get_connection"), &HTTPClient::get_connection);
ClassDB::bind_method(D_METHOD("request_raw", "method", "url", "headers", "body"), &HTTPClient::_request_raw);
diff --git a/core/io/http_client.h b/core/io/http_client.h
index 853ea7f472..9e018182e3 100644
--- a/core/io/http_client.h
+++ b/core/io/http_client.h
@@ -31,6 +31,7 @@
#ifndef HTTP_CLIENT_H
#define HTTP_CLIENT_H
+#include "core/crypto/crypto.h"
#include "core/io/ip.h"
#include "core/io/stream_peer.h"
#include "core/io/stream_peer_tcp.h"
@@ -168,7 +169,7 @@ public:
Error verify_headers(const Vector<String> &p_headers);
virtual Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) = 0;
- virtual Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) = 0;
+ virtual Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) = 0;
virtual void set_connection(const Ref<StreamPeer> &p_connection) = 0;
virtual Ref<StreamPeer> get_connection() const = 0;
diff --git a/core/io/http_client_tcp.cpp b/core/io/http_client_tcp.cpp
index 5cdb13fa06..3788fa501e 100644
--- a/core/io/http_client_tcp.cpp
+++ b/core/io/http_client_tcp.cpp
@@ -39,29 +39,31 @@ HTTPClient *HTTPClientTCP::_create_func() {
return memnew(HTTPClientTCP);
}
-Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tls, bool p_verify_host) {
+Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_options) {
close();
conn_port = p_port;
conn_host = p_host;
+ tls_options = p_options;
ip_candidates.clear();
- tls = p_tls;
- tls_verify_host = p_verify_host;
-
String host_lower = conn_host.to_lower();
if (host_lower.begins_with("http://")) {
conn_host = conn_host.substr(7, conn_host.length() - 7);
+ tls_options.unref();
} else if (host_lower.begins_with("https://")) {
- tls = true;
+ if (tls_options.is_null()) {
+ tls_options = TLSOptions::client();
+ }
conn_host = conn_host.substr(8, conn_host.length() - 8);
}
+ ERR_FAIL_COND_V(tls_options.is_valid() && tls_options->is_server(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(conn_host.length() < HOST_MIN_LEN, ERR_INVALID_PARAMETER);
if (conn_port < 0) {
- if (tls) {
+ if (tls_options.is_valid()) {
conn_port = PORT_HTTPS;
} else {
conn_port = PORT_HTTP;
@@ -70,11 +72,11 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tl
connection = tcp_connection;
- if (tls && https_proxy_port != -1) {
+ if (tls_options.is_valid() && https_proxy_port != -1) {
proxy_client.instantiate(); // Needs proxy negotiation.
server_host = https_proxy_host;
server_port = https_proxy_port;
- } else if (!tls && http_proxy_port != -1) {
+ } else if (tls_options.is_null() && http_proxy_port != -1) {
server_host = http_proxy_host;
server_port = http_proxy_port;
} else {
@@ -107,7 +109,7 @@ Error HTTPClientTCP::connect_to_host(const String &p_host, int p_port, bool p_tl
void HTTPClientTCP::set_connection(const Ref<StreamPeer> &p_connection) {
ERR_FAIL_COND_MSG(p_connection.is_null(), "Connection is not a reference to a valid StreamPeer object.");
- if (tls) {
+ if (tls_options.is_valid()) {
ERR_FAIL_NULL_MSG(Object::cast_to<StreamPeerTLS>(p_connection.ptr()),
"Connection is not a reference to a valid StreamPeerTLS object.");
}
@@ -156,7 +158,7 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<
}
String uri = p_url;
- if (!tls && http_proxy_port != -1) {
+ if (tls_options.is_null() && http_proxy_port != -1) {
uri = vformat("http://%s:%d%s", conn_host, conn_port, p_url);
}
@@ -181,7 +183,7 @@ Error HTTPClientTCP::request(Method p_method, const String &p_url, const Vector<
}
}
if (add_host) {
- if ((tls && conn_port == PORT_HTTPS) || (!tls && conn_port == PORT_HTTP)) {
+ if ((tls_options.is_valid() && conn_port == PORT_HTTPS) || (tls_options.is_null() && conn_port == PORT_HTTP)) {
// Don't append the standard ports.
request += "Host: " + conn_host + "\r\n";
} else {
@@ -316,7 +318,7 @@ Error HTTPClientTCP::poll() {
return OK;
} break;
case StreamPeerTCP::STATUS_CONNECTED: {
- if (tls && proxy_client.is_valid()) {
+ if (tls_options.is_valid() && proxy_client.is_valid()) {
Error err = proxy_client->poll();
if (err == ERR_UNCONFIGURED) {
proxy_client->set_connection(tcp_connection);
@@ -357,13 +359,12 @@ Error HTTPClientTCP::poll() {
return ERR_CANT_CONNECT;
} break;
}
- } else if (tls) {
+ } else if (tls_options.is_valid()) {
Ref<StreamPeerTLS> tls_conn;
if (!handshaking) {
// Connect the StreamPeerTLS and start handshaking.
tls_conn = Ref<StreamPeerTLS>(StreamPeerTLS::create());
- tls_conn->set_blocking_handshake_enabled(false);
- Error err = tls_conn->connect_to_stream(tcp_connection, tls_verify_host, conn_host);
+ Error err = tls_conn->connect_to_stream(tcp_connection, conn_host, tls_options);
if (err != OK) {
close();
status = STATUS_TLS_HANDSHAKE_ERROR;
@@ -421,7 +422,7 @@ Error HTTPClientTCP::poll() {
case STATUS_BODY:
case STATUS_CONNECTED: {
// Check if we are still connected.
- if (tls) {
+ if (tls_options.is_valid()) {
Ref<StreamPeerTLS> tmp = connection;
tmp->poll();
if (tmp->get_status() != StreamPeerTLS::STATUS_CONNECTED) {
diff --git a/core/io/http_client_tcp.h b/core/io/http_client_tcp.h
index 97ac2d76a7..6060c975bc 100644
--- a/core/io/http_client_tcp.h
+++ b/core/io/http_client_tcp.h
@@ -33,6 +33,8 @@
#include "http_client.h"
+#include "core/crypto/crypto.h"
+
class HTTPClientTCP : public HTTPClient {
private:
Status status = STATUS_DISCONNECTED;
@@ -46,11 +48,10 @@ private:
String http_proxy_host;
int https_proxy_port = -1; // Proxy server for https requests.
String https_proxy_host;
- bool tls = false;
- bool tls_verify_host = false;
bool blocking = false;
bool handshaking = false;
bool head_request = false;
+ Ref<TLSOptions> tls_options;
Vector<uint8_t> response_str;
@@ -79,7 +80,7 @@ public:
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
- Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) override;
+ Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) override;
void set_connection(const Ref<StreamPeer> &p_connection) override;
Ref<StreamPeer> get_connection() const override;
void close() override;
diff --git a/core/io/packet_peer_dtls.cpp b/core/io/packet_peer_dtls.cpp
index c0998f10bc..18bef3ff3c 100644
--- a/core/io/packet_peer_dtls.cpp
+++ b/core/io/packet_peer_dtls.cpp
@@ -48,7 +48,7 @@ bool PacketPeerDTLS::is_available() {
void PacketPeerDTLS::_bind_methods() {
ClassDB::bind_method(D_METHOD("poll"), &PacketPeerDTLS::poll);
- ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "validate_certs", "for_hostname", "valid_certificate"), &PacketPeerDTLS::connect_to_peer, DEFVAL(true), DEFVAL(String()), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("connect_to_peer", "packet_peer", "hostname", "client_options"), &PacketPeerDTLS::connect_to_peer, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("get_status"), &PacketPeerDTLS::get_status);
ClassDB::bind_method(D_METHOD("disconnect_from_peer"), &PacketPeerDTLS::disconnect_from_peer);
diff --git a/core/io/packet_peer_dtls.h b/core/io/packet_peer_dtls.h
index 5ba1faed7c..3990a851f7 100644
--- a/core/io/packet_peer_dtls.h
+++ b/core/io/packet_peer_dtls.h
@@ -53,7 +53,7 @@ public:
};
virtual void poll() = 0;
- virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>()) = 0;
+ virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
virtual void disconnect_from_peer() = 0;
virtual Status get_status() const = 0;
diff --git a/core/io/resource.cpp b/core/io/resource.cpp
index e44bbc246b..4abcbffb61 100644
--- a/core/io/resource.cpp
+++ b/core/io/resource.cpp
@@ -260,15 +260,35 @@ Ref<Resource> Resource::duplicate(bool p_subresources) const {
}
Variant p = get(E.name);
- if ((p.get_type() == Variant::DICTIONARY || p.get_type() == Variant::ARRAY)) {
- r->set(E.name, p.duplicate(p_subresources));
- } else if (p.get_type() == Variant::OBJECT && !(E.usage & PROPERTY_USAGE_NEVER_DUPLICATE) && (p_subresources || (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE))) {
- Ref<Resource> sr = p;
- if (sr.is_valid()) {
- r->set(E.name, sr->duplicate(p_subresources));
+ switch (p.get_type()) {
+ case Variant::Type::DICTIONARY:
+ case Variant::Type::ARRAY:
+ case Variant::Type::PACKED_BYTE_ARRAY:
+ case Variant::Type::PACKED_COLOR_ARRAY:
+ case Variant::Type::PACKED_INT32_ARRAY:
+ case Variant::Type::PACKED_INT64_ARRAY:
+ case Variant::Type::PACKED_FLOAT32_ARRAY:
+ case Variant::Type::PACKED_FLOAT64_ARRAY:
+ case Variant::Type::PACKED_STRING_ARRAY:
+ case Variant::Type::PACKED_VECTOR2_ARRAY:
+ case Variant::Type::PACKED_VECTOR3_ARRAY: {
+ r->set(E.name, p.duplicate(p_subresources));
+ } break;
+
+ case Variant::Type::OBJECT: {
+ if (!(E.usage & PROPERTY_USAGE_NEVER_DUPLICATE) && (p_subresources || (E.usage & PROPERTY_USAGE_ALWAYS_DUPLICATE))) {
+ Ref<Resource> sr = p;
+ if (sr.is_valid()) {
+ r->set(E.name, sr->duplicate(p_subresources));
+ }
+ } else {
+ r->set(E.name, p);
+ }
+ } break;
+
+ default: {
+ r->set(E.name, p);
}
- } else {
- r->set(E.name, p);
}
}
diff --git a/core/io/stream_peer_tls.cpp b/core/io/stream_peer_tls.cpp
index 71fadd1d30..69877974e6 100644
--- a/core/io/stream_peer_tls.cpp
+++ b/core/io/stream_peer_tls.cpp
@@ -41,31 +41,17 @@ StreamPeerTLS *StreamPeerTLS::create() {
return nullptr;
}
-bool StreamPeerTLS::available = false;
-
bool StreamPeerTLS::is_available() {
- return available;
-}
-
-void StreamPeerTLS::set_blocking_handshake_enabled(bool p_enabled) {
- blocking_handshake = p_enabled;
-}
-
-bool StreamPeerTLS::is_blocking_handshake_enabled() const {
- return blocking_handshake;
+ return _create != nullptr;
}
void StreamPeerTLS::_bind_methods() {
ClassDB::bind_method(D_METHOD("poll"), &StreamPeerTLS::poll);
- ClassDB::bind_method(D_METHOD("accept_stream", "stream", "private_key", "certificate", "chain"), &StreamPeerTLS::accept_stream, DEFVAL(Ref<X509Certificate>()));
- ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "validate_certs", "for_hostname", "valid_certificate"), &StreamPeerTLS::connect_to_stream, DEFVAL(false), DEFVAL(String()), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("accept_stream", "stream", "server_options"), &StreamPeerTLS::accept_stream);
+ ClassDB::bind_method(D_METHOD("connect_to_stream", "stream", "common_name", "client_options"), &StreamPeerTLS::connect_to_stream, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("get_status"), &StreamPeerTLS::get_status);
ClassDB::bind_method(D_METHOD("get_stream"), &StreamPeerTLS::get_stream);
ClassDB::bind_method(D_METHOD("disconnect_from_stream"), &StreamPeerTLS::disconnect_from_stream);
- ClassDB::bind_method(D_METHOD("set_blocking_handshake_enabled", "enabled"), &StreamPeerTLS::set_blocking_handshake_enabled);
- ClassDB::bind_method(D_METHOD("is_blocking_handshake_enabled"), &StreamPeerTLS::is_blocking_handshake_enabled);
-
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "blocking_handshake"), "set_blocking_handshake_enabled", "is_blocking_handshake_enabled");
BIND_ENUM_CONSTANT(STATUS_DISCONNECTED);
BIND_ENUM_CONSTANT(STATUS_HANDSHAKING);
diff --git a/core/io/stream_peer_tls.h b/core/io/stream_peer_tls.h
index 6666107ad8..5894abb7a4 100644
--- a/core/io/stream_peer_tls.h
+++ b/core/io/stream_peer_tls.h
@@ -41,10 +41,6 @@ protected:
static StreamPeerTLS *(*_create)();
static void _bind_methods();
- static bool available;
-
- bool blocking_handshake = true;
-
public:
enum Status {
STATUS_DISCONNECTED,
@@ -54,12 +50,9 @@ public:
STATUS_ERROR_HOSTNAME_MISMATCH
};
- void set_blocking_handshake_enabled(bool p_enabled);
- bool is_blocking_handshake_enabled() const;
-
virtual void poll() = 0;
- virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>()) = 0;
- virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>()) = 0;
+ virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) = 0;
+ virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) = 0;
virtual Status get_status() const = 0;
virtual Ref<StreamPeer> get_stream() const = 0;
diff --git a/core/math/transform_2d.cpp b/core/math/transform_2d.cpp
index 910995d717..96010b4096 100644
--- a/core/math/transform_2d.cpp
+++ b/core/math/transform_2d.cpp
@@ -263,39 +263,12 @@ real_t Transform2D::basis_determinant() const {
return columns[0].x * columns[1].y - columns[0].y * columns[1].x;
}
-Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, const real_t p_c) const {
- //extract parameters
- Vector2 p1 = get_origin();
- Vector2 p2 = p_transform.get_origin();
-
- real_t r1 = get_rotation();
- real_t r2 = p_transform.get_rotation();
-
- Size2 s1 = get_scale();
- Size2 s2 = p_transform.get_scale();
-
- //slerp rotation
- Vector2 v1(Math::cos(r1), Math::sin(r1));
- Vector2 v2(Math::cos(r2), Math::sin(r2));
-
- real_t dot = v1.dot(v2);
-
- dot = CLAMP(dot, (real_t)-1.0, (real_t)1.0);
-
- Vector2 v;
-
- if (dot > 0.9995f) {
- v = v1.lerp(v2, p_c).normalized(); //linearly interpolate to avoid numerical precision issues
- } else {
- real_t angle = p_c * Math::acos(dot);
- Vector2 v3 = (v2 - v1 * dot).normalized();
- v = v1 * Math::cos(angle) + v3 * Math::sin(angle);
- }
-
- //construct matrix
- Transform2D res(v.angle(), p1.lerp(p2, p_c));
- res.scale_basis(s1.lerp(s2, p_c));
- return res;
+Transform2D Transform2D::interpolate_with(const Transform2D &p_transform, const real_t p_weight) const {
+ return Transform2D(
+ Math::lerp_angle(get_rotation(), p_transform.get_rotation(), p_weight),
+ get_scale().lerp(p_transform.get_scale(), p_weight),
+ Math::lerp_angle(get_skew(), p_transform.get_skew(), p_weight),
+ get_origin().lerp(p_transform.get_origin(), p_weight));
}
void Transform2D::operator*=(const real_t p_val) {
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index 700174bdae..a374e7c009 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -209,6 +209,7 @@ void register_core_types() {
GDREGISTER_CLASS(AESContext);
ClassDB::register_custom_instance_class<X509Certificate>();
ClassDB::register_custom_instance_class<CryptoKey>();
+ GDREGISTER_ABSTRACT_CLASS(TLSOptions);
ClassDB::register_custom_instance_class<HMACContext>();
ClassDB::register_custom_instance_class<Crypto>();
ClassDB::register_custom_instance_class<StreamPeerTLS>();
diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp
index 3b2d29734d..e7d1a2478f 100644
--- a/core/variant/variant_call.cpp
+++ b/core/variant/variant_call.cpp
@@ -2224,7 +2224,6 @@ static void _register_variant_builtin_methods() {
bind_method(Array, max, sarray(), varray());
bind_method(Array, min, sarray(), varray());
bind_method(Array, typed_assign, sarray("array"), varray());
- bind_method(Array, set_typed, sarray("type", "class_name", "script"), varray());
bind_method(Array, is_typed, sarray(), varray());
bind_method(Array, get_typed_builtin, sarray(), varray());
bind_method(Array, get_typed_class_name, sarray(), varray());
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 485c04da6d..0d6524ccbe 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -2647,7 +2647,7 @@
Additionally, other keywords can be included: [code]"exp"[/code] for exponential range editing, [code]"radians"[/code] for editing radian angles in degrees, [code]"degrees"[/code] to hint at an angle and [code]"hide_slider"[/code] to hide the slider.
</constant>
<constant name="PROPERTY_HINT_ENUM" value="2" enum="PropertyHint">
- Hints that an [int], [float], or [String] property is an enumerated value to pick in a list specified via a hint string.
+ Hints that an [int] or [String] property is an enumerated value to pick in a list specified via a hint string.
The hint string is a comma separated list of names such as [code]"Hello,Something,Else"[/code]. Whitespaces are [b]not[/b] removed from either end of a name. For integer and float properties, the first name in the list has value 0, the next 1, and so on. Explicit values can also be specified by appending [code]:integer[/code] to the name, e.g. [code]"Zero,One,Three:3,Four,Six:6"[/code].
</constant>
<constant name="PROPERTY_HINT_ENUM_SUGGESTION" value="3" enum="PropertyHint">
diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml
index c0626dcfe4..74ee13a3d2 100644
--- a/doc/classes/Animation.xml
+++ b/doc/classes/Animation.xml
@@ -104,6 +104,13 @@
[param stream] is the [AudioStream] resource to play. [param start_offset] is the number of seconds cut off at the beginning of the audio stream, while [param end_offset] is at the ending.
</description>
</method>
+ <method name="audio_track_is_use_blend" qualifiers="const">
+ <return type="bool" />
+ <param index="0" name="track_idx" type="int" />
+ <description>
+ Returns [code]true[/code] if the track at [code]idx[/code] will be blended with other animations.
+ </description>
+ </method>
<method name="audio_track_set_key_end_offset">
<return type="void" />
<param index="0" name="track_idx" type="int" />
@@ -131,6 +138,14 @@
Sets the stream of the key identified by [param key_idx] to value [param stream]. The [param track_idx] must be the index of an Audio Track.
</description>
</method>
+ <method name="audio_track_set_use_blend">
+ <return type="void" />
+ <param index="0" name="track_idx" type="int" />
+ <param index="1" name="enable" type="bool" />
+ <description>
+ Sets whether the track will be blended with other animations. If [code]true[/code], the audio playback volume changes depending on the blend value.
+ </description>
+ </method>
<method name="bezier_track_get_key_in_handle" qualifiers="const">
<return type="Vector2" />
<param index="0" name="track_idx" type="int" />
diff --git a/doc/classes/AnimationNode.xml b/doc/classes/AnimationNode.xml
index 6e3345b675..bc65e6013b 100644
--- a/doc/classes/AnimationNode.xml
+++ b/doc/classes/AnimationNode.xml
@@ -68,10 +68,10 @@
</description>
</method>
<method name="add_input">
- <return type="void" />
+ <return type="bool" />
<param index="0" name="name" type="String" />
<description>
- Adds an input to the node. This is only useful for nodes created for use in an [AnimationNodeBlendTree].
+ Adds an input to the node. This is only useful for nodes created for use in an [AnimationNodeBlendTree]. If the addition fails, returns [code]false[/code].
</description>
</method>
<method name="blend_animation">
@@ -115,13 +115,20 @@
Blend another animation node (in case this node contains children animation nodes). This function is only useful if you inherit from [AnimationRootNode] instead, else editors will not display your node for addition.
</description>
</method>
+ <method name="find_input" qualifiers="const">
+ <return type="int" />
+ <param index="0" name="name" type="String" />
+ <description>
+ Returns the input index which corresponds to [param name]. If not found, returns [code]-1[/code].
+ </description>
+ </method>
<method name="get_input_count" qualifiers="const">
<return type="int" />
<description>
Amount of inputs in this node, only useful for nodes that go into [AnimationNodeBlendTree].
</description>
</method>
- <method name="get_input_name">
+ <method name="get_input_name" qualifiers="const">
<return type="String" />
<param index="0" name="input" type="int" />
<description>
@@ -157,6 +164,14 @@
Adds or removes a path for the filter.
</description>
</method>
+ <method name="set_input_name">
+ <return type="bool" />
+ <param index="0" name="input" type="int" />
+ <param index="1" name="name" type="String" />
+ <description>
+ Sets the name of the input at the given [param input] index. If the setting fails, returns [code]false[/code].
+ </description>
+ </method>
<method name="set_parameter">
<return type="void" />
<param index="0" name="name" type="StringName" />
diff --git a/doc/classes/AnimationNodeTransition.xml b/doc/classes/AnimationNodeTransition.xml
index 90bae41586..a067b0a9ba 100644
--- a/doc/classes/AnimationNodeTransition.xml
+++ b/doc/classes/AnimationNodeTransition.xml
@@ -12,18 +12,11 @@
<link title="Third Person Shooter Demo">https://godotengine.org/asset-library/asset/678</link>
</tutorials>
<methods>
- <method name="find_input_caption" qualifiers="const">
- <return type="int" />
- <param index="0" name="caption" type="String" />
- <description>
- Returns the input index which corresponds to [param caption]. If not found, returns [code]-1[/code].
- </description>
- </method>
- <method name="get_input_caption" qualifiers="const">
- <return type="String" />
+ <method name="is_input_reset" qualifiers="const">
+ <return type="bool" />
<param index="0" name="input" type="int" />
<description>
- Returns the name of the input at the given [param input] index. This name is displayed in the editor next to the node input.
+ Returns whether the animation restarts when the animation transitions from the other animation.
</description>
</method>
<method name="is_input_set_as_auto_advance" qualifiers="const">
@@ -41,21 +34,18 @@
Enables or disables auto-advance for the given [param input] index. If enabled, state changes to the next input after playing the animation once. If enabled for the last input state, it loops to the first.
</description>
</method>
- <method name="set_input_caption">
+ <method name="set_input_reset">
<return type="void" />
<param index="0" name="input" type="int" />
- <param index="1" name="caption" type="String" />
+ <param index="1" name="enable" type="bool" />
<description>
- Sets the name of the input at the given [param input] index. This name is displayed in the editor next to the node input.
+ If [code]true[/code], the destination animation is restarted when the animation transitions.
</description>
</method>
</methods>
<members>
- <member name="enabled_inputs" type="int" setter="set_enabled_inputs" getter="get_enabled_inputs" default="0">
- The number of enabled input ports for this node. The maximum is [code]31[/code].
- </member>
- <member name="reset" type="bool" setter="set_reset" getter="is_reset" default="true">
- If [code]true[/code], the destination animation is played back from the beginning when switched.
+ <member name="input_count" type="int" setter="set_input_count" getter="get_input_count" default="0">
+ The number of enabled input ports for this node.
</member>
<member name="xfade_curve" type="Curve" setter="set_xfade_curve" getter="get_xfade_curve">
Determines how cross-fading between animations is eased. If empty, the transition will be linear.
diff --git a/doc/classes/AnimationPlayer.xml b/doc/classes/AnimationPlayer.xml
index 8982eeedb2..25e4a4549d 100644
--- a/doc/classes/AnimationPlayer.xml
+++ b/doc/classes/AnimationPlayer.xml
@@ -232,6 +232,10 @@
<member name="assigned_animation" type="String" setter="set_assigned_animation" getter="get_assigned_animation">
If playing, the the current animation's key, otherwise, the animation last played. When set, this changes the animation, but will not play it unless already playing. See also [member current_animation].
</member>
+ <member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+ For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+ </member>
<member name="autoplay" type="String" setter="set_autoplay" getter="get_autoplay" default="&quot;&quot;">
The key of the animation to play when the scene loads.
</member>
diff --git a/doc/classes/AnimationTree.xml b/doc/classes/AnimationTree.xml
index 3a3e8bb1fa..86562c340d 100644
--- a/doc/classes/AnimationTree.xml
+++ b/doc/classes/AnimationTree.xml
@@ -110,6 +110,10 @@
<member name="anim_player" type="NodePath" setter="set_animation_player" getter="get_animation_player" default="NodePath(&quot;&quot;)">
The path to the [AnimationPlayer] used for animating.
</member>
+ <member name="audio_max_polyphony" type="int" setter="set_audio_max_polyphony" getter="get_audio_max_polyphony" default="32">
+ The number of possible simultaneous sounds for each of the assigned AudioStreamPlayers.
+ For example, if this value is [code]32[/code] and the animation has two audio tracks, the two [AudioStreamPlayer]s assigned can play simultaneously up to [code]32[/code] voices each.
+ </member>
<member name="process_callback" type="int" setter="set_process_callback" getter="get_process_callback" enum="AnimationTree.AnimationProcessCallback" default="1">
The process mode of this [AnimationTree]. See [enum AnimationProcessCallback] for available modes.
</member>
diff --git a/doc/classes/Array.xml b/doc/classes/Array.xml
index 1ac1c0745e..9be5e60f4a 100644
--- a/doc/classes/Array.xml
+++ b/doc/classes/Array.xml
@@ -59,7 +59,7 @@
<param index="2" name="class_name" type="StringName" />
<param index="3" name="script" type="Variant" />
<description>
- Creates a typed array from the [param base] array. The base array can't be already typed. See [method set_typed] for more details.
+ Creates a typed array from the [param base] array. The base array can't be already typed.
</description>
</constructor>
<constructor name="Array">
@@ -530,16 +530,6 @@
Searches the array in reverse order. Optionally, a start search index can be passed. If negative, the start index is considered relative to the end of the array.
</description>
</method>
- <method name="set_typed">
- <return type="void" />
- <param index="0" name="type" type="int" />
- <param index="1" name="class_name" type="StringName" />
- <param index="2" name="script" type="Variant" />
- <description>
- Makes the [Array] typed. The [param type] should be one of the [enum Variant.Type] constants. [param class_name] is optional and can only be provided for [constant TYPE_OBJECT]. [param script] can only be provided if [param class_name] is not empty.
- The method fails if an array is already typed.
- </description>
- </method>
<method name="shuffle">
<return type="void" />
<description>
diff --git a/doc/classes/AudioStreamPlayer.xml b/doc/classes/AudioStreamPlayer.xml
index 06e183f4e2..9b3a4f7bba 100644
--- a/doc/classes/AudioStreamPlayer.xml
+++ b/doc/classes/AudioStreamPlayer.xml
@@ -28,6 +28,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/doc/classes/AudioStreamPlayer2D.xml b/doc/classes/AudioStreamPlayer2D.xml
index 81755b580f..18869e87cb 100644
--- a/doc/classes/AudioStreamPlayer2D.xml
+++ b/doc/classes/AudioStreamPlayer2D.xml
@@ -25,6 +25,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer2D].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/doc/classes/AudioStreamPlayer3D.xml b/doc/classes/AudioStreamPlayer3D.xml
index f711210ca1..d5a06dcad6 100644
--- a/doc/classes/AudioStreamPlayer3D.xml
+++ b/doc/classes/AudioStreamPlayer3D.xml
@@ -25,6 +25,12 @@
Returns the [AudioStreamPlayback] object associated with this [AudioStreamPlayer3D].
</description>
</method>
+ <method name="has_stream_playback">
+ <return type="bool" />
+ <description>
+ Returns whether the [AudioStreamPlayer] can return the [AudioStreamPlayback] object or not.
+ </description>
+ </method>
<method name="play">
<return type="void" />
<param index="0" name="from_position" type="float" default="0.0" />
diff --git a/doc/classes/DTLSServer.xml b/doc/classes/DTLSServer.xml
index 457513b8aa..a3a0b0456c 100644
--- a/doc/classes/DTLSServer.xml
+++ b/doc/classes/DTLSServer.xml
@@ -148,11 +148,9 @@
<methods>
<method name="setup">
<return type="int" enum="Error" />
- <param index="0" name="key" type="CryptoKey" />
- <param index="1" name="certificate" type="X509Certificate" />
- <param index="2" name="chain" type="X509Certificate" default="null" />
+ <param index="0" name="server_options" type="TLSOptions" />
<description>
- Setup the DTLS server to use the given [param key] and provide the given [param certificate] to clients. You can pass the optional [param chain] parameter to provide additional CA chain information along with the certificate.
+ Setup the DTLS server to use the given [param server_options]. See [method TLSOptions.server].
</description>
</method>
<method name="take_connection">
diff --git a/doc/classes/Dictionary.xml b/doc/classes/Dictionary.xml
index ea0bcc5cbb..776a9f6fc1 100644
--- a/doc/classes/Dictionary.xml
+++ b/doc/classes/Dictionary.xml
@@ -286,7 +286,7 @@
<method name="make_read_only">
<return type="void" />
<description>
- Makes the dictionary read-only, i.e. disabled modifying of the dictionary's contents. Does not apply to nested content, e.g. content of nested dicitonaries.
+ Makes the dictionary read-only, i.e. disables modification of the dictionary's contents. Does not apply to nested content, e.g. content of nested dictionaries.
</description>
</method>
<method name="merge">
diff --git a/doc/classes/EditorImportPlugin.xml b/doc/classes/EditorImportPlugin.xml
index c395815117..ec9efcc9c4 100644
--- a/doc/classes/EditorImportPlugin.xml
+++ b/doc/classes/EditorImportPlugin.xml
@@ -37,8 +37,8 @@
return [{"name": "my_option", "default_value": false}]
func _import(source_file, save_path, options, platform_variants, gen_files):
- var file = File.new()
- if file.open(source_file, File.READ) != OK:
+ var file = FileAccess.open(source_file, FileAccess.READ)
+ if file == null:
return FAILED
var mesh = ArrayMesh.new()
# Fill the Mesh with data read in "file", left as an exercise to the reader.
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index 9015a12b43..72843eb157 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -607,6 +607,10 @@
<member name="interface/theme/draw_extra_borders" type="bool" setter="" getter="">
If [code]true[/code], draws additional borders around interactive UI elements in the editor. This is automatically enabled when using the [b]Black (OLED)[/b] theme preset, as this theme preset uses a fully black background.
</member>
+ <member name="interface/theme/enable_touchscreen_touch_area" type="bool" setter="" getter="">
+ If [code]true[/code], increases the touch area for the UI elements to improve usability on touchscreen devices.
+ [b]Note:[/b] Defaults to [code]true[/code] on touchscreen devices.
+ </member>
<member name="interface/theme/icon_and_font_color" type="int" setter="" getter="">
The icon and font color scheme to use in the editor.
- [b]Auto[/b] determines the color scheme to use automatically based on [member interface/theme/base_color].
diff --git a/doc/classes/EditorTranslationParserPlugin.xml b/doc/classes/EditorTranslationParserPlugin.xml
index df10c645ef..89746363d8 100644
--- a/doc/classes/EditorTranslationParserPlugin.xml
+++ b/doc/classes/EditorTranslationParserPlugin.xml
@@ -15,8 +15,7 @@
extends EditorTranslationParserPlugin
func _parse_file(path, msgids, msgids_context_plural):
- var file = File.new()
- file.open(path, File.READ)
+ var file = FileAccess.open(path, FileAccess.READ)
var text = file.get_as_text()
var split_strs = text.split(",", false)
for s in split_strs:
diff --git a/doc/classes/HTTPClient.xml b/doc/classes/HTTPClient.xml
index b3ed38d250..b7a5cff694 100644
--- a/doc/classes/HTTPClient.xml
+++ b/doc/classes/HTTPClient.xml
@@ -30,13 +30,10 @@
<return type="int" enum="Error" />
<param index="0" name="host" type="String" />
<param index="1" name="port" type="int" default="-1" />
- <param index="2" name="use_tls" type="bool" default="false" />
- <param index="3" name="verify_host" type="bool" default="true" />
+ <param index="2" name="tls_options" type="TLSOptions" default="null" />
<description>
Connects to a host. This needs to be done before any requests are sent.
- The host should not have http:// prepended but will strip the protocol identifier if provided.
- If no [param port] is specified (or [code]-1[/code] is used), it is automatically set to 80 for HTTP and 443 for HTTPS (if [param use_tls] is enabled).
- [param verify_host] will check the TLS identity of the host if set to [code]true[/code].
+ If no [param port] is specified (or [code]-1[/code] is used), it is automatically set to 80 for HTTP and 443 for HTTPS. You can pass the optional [param tls_options] parameter to customize the trusted certification authorities, or the common name verification when using HTTPS. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="get_response_body_length" qualifiers="const">
diff --git a/doc/classes/HTTPRequest.xml b/doc/classes/HTTPRequest.xml
index 3dbc024b14..d403acf90c 100644
--- a/doc/classes/HTTPRequest.xml
+++ b/doc/classes/HTTPRequest.xml
@@ -187,9 +187,8 @@
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" />
- <param index="2" name="tls_validate_domain" type="bool" default="true" />
- <param index="3" name="method" type="int" enum="HTTPClient.Method" default="0" />
- <param index="4" name="request_data" type="String" default="&quot;&quot;" />
+ <param index="2" name="method" type="int" enum="HTTPClient.Method" default="0" />
+ <param index="3" name="request_data" type="String" default="&quot;&quot;" />
<description>
Creates request on the underlying [HTTPClient]. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request].
Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host.
@@ -201,9 +200,8 @@
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
<param index="1" name="custom_headers" type="PackedStringArray" default="PackedStringArray()" />
- <param index="2" name="tls_validate_domain" type="bool" default="true" />
- <param index="3" name="method" type="int" enum="HTTPClient.Method" default="0" />
- <param index="4" name="request_data_raw" type="PackedByteArray" default="PackedByteArray()" />
+ <param index="2" name="method" type="int" enum="HTTPClient.Method" default="0" />
+ <param index="3" name="request_data_raw" type="PackedByteArray" default="PackedByteArray()" />
<description>
Creates request on the underlying [HTTPClient] using a raw array of bytes for the request body. If there is no configuration errors, it tries to connect using [method HTTPClient.connect_to_host] and passes parameters onto [method HTTPClient.request].
Returns [constant OK] if request is successfully created. (Does not imply that the server has responded), [constant ERR_UNCONFIGURED] if not in the tree, [constant ERR_BUSY] if still processing previous request, [constant ERR_INVALID_PARAMETER] if given string is not a valid URL format, or [constant ERR_CANT_CONNECT] if not using thread and the [HTTPClient] cannot connect to host.
@@ -227,6 +225,13 @@
The proxy server is unset if [param host] is empty or [param port] is -1.
</description>
</method>
+ <method name="set_tls_options">
+ <return type="void" />
+ <param index="0" name="client_options" type="TLSOptions" />
+ <description>
+ Sets the [TLSOptions] to be used when connecting to an HTTPS server. See [method TLSOptions.client].
+ </description>
+ </method>
</methods>
<members>
<member name="accept_gzip" type="bool" setter="set_accept_gzip" getter="is_accepting_gzip" default="true">
diff --git a/doc/classes/HashingContext.xml b/doc/classes/HashingContext.xml
index 6e3092e618..7c2be6f817 100644
--- a/doc/classes/HashingContext.xml
+++ b/doc/classes/HashingContext.xml
@@ -11,15 +11,14 @@
const CHUNK_SIZE = 102
func hash_file(path):
- var ctx = HashingContext.new()
- var file = File.new()
- # Start a SHA-256 context.
- ctx.start(HashingContext.HASH_SHA256)
# Check that file exists.
- if not file.file_exists(path):
+ if not FileAccess.file_exists(path):
return
+ # Start a SHA-256 context.
+ var ctx = HashingContext.new()
+ ctx.start(HashingContext.HASH_SHA256)
# Open the file to hash.
- file.open(path, File.READ)
+ var file = FileAccess.open(path, FileAccess.READ)
# Update the context after reading each chunk.
while not file.eof_reached():
ctx.update(file.get_buffer(CHUNK_SIZE))
diff --git a/doc/classes/Label3D.xml b/doc/classes/Label3D.xml
index 0cbca2224e..fb3c949e48 100644
--- a/doc/classes/Label3D.xml
+++ b/doc/classes/Label3D.xml
@@ -35,6 +35,9 @@
<member name="alpha_cut" type="int" setter="set_alpha_cut_mode" getter="get_alpha_cut_mode" enum="Label3D.AlphaCutMode" default="0">
The alpha cutting mode to use for the sprite. See [enum AlphaCutMode] for possible values.
</member>
+ <member name="alpha_hash_scale" type="float" setter="set_alpha_hash_scale" getter="get_alpha_hash_scale" default="1.0">
+ The hashing scale for Alpha Hash. Recommended values between [code]0[/code] and [code]2[/code].
+ </member>
<member name="alpha_scissor_threshold" type="float" setter="set_alpha_scissor_threshold" getter="get_alpha_scissor_threshold" default="0.5">
Threshold at which the alpha scissor will discard values.
</member>
@@ -152,5 +155,8 @@
This mode draws fully opaque pixels in the depth prepass. This is slower than [constant ALPHA_CUT_DISABLED] or [constant ALPHA_CUT_DISCARD], but it allows displaying translucent areas and smooth edges while using proper sorting.
[b]Note:[/b] When using text with overlapping glyphs (e.g., cursive scripts), this mode might have transparency sorting issues between the main text and the outline.
</constant>
+ <constant name="ALPHA_CUT_HASH" value="3" enum="AlphaCutMode">
+ This mode draws cuts off all values below a spatially-deterministic threshold, the rest will remain opaque.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/NavigationServer2D.xml b/doc/classes/NavigationServer2D.xml
index 95e3fded36..16f6de5238 100644
--- a/doc/classes/NavigationServer2D.xml
+++ b/doc/classes/NavigationServer2D.xml
@@ -41,12 +41,10 @@
<method name="agent_set_callback">
<return type="void" />
<param index="0" name="agent" type="RID" />
- <param index="1" name="object_id" type="int" />
- <param index="2" name="method" type="StringName" />
- <param index="3" name="userdata" type="Variant" default="null" />
+ <param index="1" name="callback" type="Callable" />
<description>
- Sets the callback [param object_id] and [param method] that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be dispatched with a signal to the object just before the physics calculations.
- [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with a [code]0[/code] ObjectID as the [param object_id].
+ Sets the callback that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be passed as the first parameter just before the physics calculations.
+ [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with an empty [Callable].
</description>
</method>
<method name="agent_set_map">
diff --git a/doc/classes/NavigationServer3D.xml b/doc/classes/NavigationServer3D.xml
index fd20f780b3..340821d41e 100644
--- a/doc/classes/NavigationServer3D.xml
+++ b/doc/classes/NavigationServer3D.xml
@@ -41,12 +41,10 @@
<method name="agent_set_callback">
<return type="void" />
<param index="0" name="agent" type="RID" />
- <param index="1" name="object_id" type="int" />
- <param index="2" name="method" type="StringName" />
- <param index="3" name="userdata" type="Variant" default="null" />
+ <param index="1" name="callback" type="Callable" />
<description>
- Sets the callback [param object_id] and [param method] that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be dispatched with a signal to the object just before the physics calculations.
- [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with a [code]0[/code] ObjectID as the [param object_id].
+ Sets the callback that gets called after each avoidance processing step for the [param agent]. The calculated [code]safe_velocity[/code] will be passed as the first parameter just before the physics calculations.
+ [b]Note:[/b] Created callbacks are always processed independently of the SceneTree state as long as the agent is on a navigation map and not freed. To disable the dispatch of a callback from an agent use [method agent_set_callback] again with an empty [Callable].
</description>
</method>
<method name="agent_set_map">
diff --git a/doc/classes/OS.xml b/doc/classes/OS.xml
index 0058bf8a2f..04895c28a8 100644
--- a/doc/classes/OS.xml
+++ b/doc/classes/OS.xml
@@ -498,7 +498,7 @@
<description>
Returns [code]true[/code] if the Godot binary used to run the project is a [i]debug[/i] export template, or when running in the editor.
Returns [code]false[/code] if the Godot binary used to run the project is a [i]release[/i] export template.
- To check whether the Godot binary used to run the project is an export template (debug or release), use [code]OS.has_feature("standalone")[/code] instead.
+ To check whether the Godot binary used to run the project is an export template (debug or release), use [code]OS.has_feature("template")[/code] instead.
</description>
</method>
<method name="is_keycode_unicode" qualifiers="const">
diff --git a/doc/classes/PacketPeerDTLS.xml b/doc/classes/PacketPeerDTLS.xml
index db8403a56b..19c5d0e287 100644
--- a/doc/classes/PacketPeerDTLS.xml
+++ b/doc/classes/PacketPeerDTLS.xml
@@ -14,11 +14,10 @@
<method name="connect_to_peer">
<return type="int" enum="Error" />
<param index="0" name="packet_peer" type="PacketPeerUDP" />
- <param index="1" name="validate_certs" type="bool" default="true" />
- <param index="2" name="for_hostname" type="String" default="&quot;&quot;" />
- <param index="3" name="valid_certificate" type="X509Certificate" default="null" />
+ <param index="1" name="hostname" type="String" />
+ <param index="2" name="client_options" type="TLSOptions" default="null" />
<description>
- Connects a [param packet_peer] beginning the DTLS handshake using the underlying [PacketPeerUDP] which must be connected (see [method PacketPeerUDP.connect_to_host]). If [param validate_certs] is [code]true[/code], [PacketPeerDTLS] will validate that the certificate presented by the remote peer and match it with the [param for_hostname] argument. You can specify a custom [X509Certificate] to use for validation via the [param valid_certificate] argument.
+ Connects a [param packet_peer] beginning the DTLS handshake using the underlying [PacketPeerUDP] which must be connected (see [method PacketPeerUDP.connect_to_host]). You can optionally specify the [param client_options] to be used while verifying the TLS connections. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="disconnect_from_peer">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 430f1c60fd..fa381adbb3 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -408,8 +408,11 @@
<member name="debug/gdscript/warnings/incompatible_ternary" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a ternary operator may emit values with incompatible types.
</member>
- <member name="debug/gdscript/warnings/int_assigned_to_enum" type="int" setter="" getter="" default="1">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to assign an integer to a variable that expects an enum value.
+ <member name="debug/gdscript/warnings/int_as_enum_without_cast" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum without an explicit cast.
+ </member>
+ <member name="debug/gdscript/warnings/int_as_enum_without_match" type="int" setter="" getter="" default="1">
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when trying to use an integer as an enum when there is no matching enum member for that numeric value.
</member>
<member name="debug/gdscript/warnings/integer_division" type="int" setter="" getter="" default="1">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when dividing an integer by another integer (the decimal part will be discarded).
diff --git a/doc/classes/SpriteBase3D.xml b/doc/classes/SpriteBase3D.xml
index 5fa984e7a0..7ff45edcbc 100644
--- a/doc/classes/SpriteBase3D.xml
+++ b/doc/classes/SpriteBase3D.xml
@@ -41,6 +41,12 @@
<member name="alpha_cut" type="int" setter="set_alpha_cut_mode" getter="get_alpha_cut_mode" enum="SpriteBase3D.AlphaCutMode" default="0">
The alpha cutting mode to use for the sprite. See [enum AlphaCutMode] for possible values.
</member>
+ <member name="alpha_hash_scale" type="float" setter="set_alpha_hash_scale" getter="get_alpha_hash_scale" default="1.0">
+ The hashing scale for Alpha Hash. Recommended values between [code]0[/code] and [code]2[/code].
+ </member>
+ <member name="alpha_scissor_threshold" type="float" setter="set_alpha_scissor_threshold" getter="get_alpha_scissor_threshold" default="0.5">
+ Threshold at which the alpha scissor will discard values.
+ </member>
<member name="axis" type="int" setter="set_axis" getter="get_axis" enum="Vector3.Axis" default="2">
The direction in which the front of the texture faces.
</member>
@@ -118,5 +124,8 @@
<constant name="ALPHA_CUT_OPAQUE_PREPASS" value="2" enum="AlphaCutMode">
This mode draws fully opaque pixels in the depth prepass. This is slower than [constant ALPHA_CUT_DISABLED] or [constant ALPHA_CUT_DISCARD], but it allows displaying translucent areas and smooth edges while using proper sorting.
</constant>
+ <constant name="ALPHA_CUT_HASH" value="3" enum="AlphaCutMode">
+ This mode draws cuts off all values below a spatially-deterministic threshold, the rest will remain opaque.
+ </constant>
</constants>
</class>
diff --git a/doc/classes/StreamPeerTLS.xml b/doc/classes/StreamPeerTLS.xml
index d1ddb3d441..9e16dc8914 100644
--- a/doc/classes/StreamPeerTLS.xml
+++ b/doc/classes/StreamPeerTLS.xml
@@ -14,22 +14,18 @@
<method name="accept_stream">
<return type="int" enum="Error" />
<param index="0" name="stream" type="StreamPeer" />
- <param index="1" name="private_key" type="CryptoKey" />
- <param index="2" name="certificate" type="X509Certificate" />
- <param index="3" name="chain" type="X509Certificate" default="null" />
+ <param index="1" name="server_options" type="TLSOptions" />
<description>
- Accepts a peer connection as a server using the given [param private_key] and providing the given [param certificate] to the client. You can pass the optional [param chain] parameter to provide additional CA chain information along with the certificate.
+ Accepts a peer connection as a server using the given [param server_options]. See [method TLSOptions.server].
</description>
</method>
<method name="connect_to_stream">
<return type="int" enum="Error" />
<param index="0" name="stream" type="StreamPeer" />
- <param index="1" name="validate_certs" type="bool" default="false" />
- <param index="2" name="for_hostname" type="String" default="&quot;&quot;" />
- <param index="3" name="valid_certificate" type="X509Certificate" default="null" />
+ <param index="1" name="common_name" type="String" />
+ <param index="2" name="client_options" type="TLSOptions" default="null" />
<description>
- Connects to a peer using an underlying [StreamPeer] [param stream]. If [param validate_certs] is [code]true[/code], [StreamPeerTLS] will validate that the certificate presented by the peer matches the [param for_hostname].
- [b]Note:[/b] Specifying a custom [param valid_certificate] is not supported in Web exports due to browsers restrictions.
+ Connects to a peer using an underlying [StreamPeer] [param stream] and verifying the remote certificate is correctly signed for the given [param common_name]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="disconnect_from_stream">
@@ -57,10 +53,6 @@
</description>
</method>
</methods>
- <members>
- <member name="blocking_handshake" type="bool" setter="set_blocking_handshake_enabled" getter="is_blocking_handshake_enabled" default="true">
- </member>
- </members>
<constants>
<constant name="STATUS_DISCONNECTED" value="0" enum="Status">
A status representing a [StreamPeerTLS] that is disconnected.
diff --git a/doc/classes/TLSOptions.xml b/doc/classes/TLSOptions.xml
new file mode 100644
index 0000000000..0917bd9bce
--- /dev/null
+++ b/doc/classes/TLSOptions.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="TLSOptions" inherits="RefCounted" version="4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
+ <brief_description>
+ TLS configuration for clients and servers.
+ </brief_description>
+ <description>
+ TLSOptions abstracts the configuration options for the [StreamPeerTLS] and [PacketPeerDTLS] classes.
+ Objects of this class cannot be instantiated directly, and one of the static methods [method client], [method client_unsafe], or [method server] should be used instead.
+ [codeblocks]
+ [gdscript]
+ # Create a TLS client configuration which uses our custom trusted CA chain.
+ var client_trusted_cas = load("res://my_trusted_cas.crt")
+ var client_tls_options = TLSOptions.client(client_trusted_cas)
+
+ # Create a TLS server configuration.
+ var server_certs = load("res://my_server_cas.crt")
+ var server_key = load("res://my_server_key.key")
+ var server_tls_options = TLSOptions.server(server_certs, server_key)
+ [/gdscript]
+ [/codeblocks]
+ </description>
+ <tutorials>
+ </tutorials>
+ <methods>
+ <method name="client" qualifiers="static">
+ <return type="TLSOptions" />
+ <param index="0" name="trusted_chain" type="X509Certificate" default="null" />
+ <param index="1" name="common_name_override" type="String" default="&quot;&quot;" />
+ <description>
+ Creates a TLS client configuration which validates certificates and their common names (fully qualified domain names).
+ You can specify a custom [param trusted_chain] of certification authorities (the default CA list will be used if [code]null[/code]), and optionally provide a [param common_name_override] if you expect the certificate to have a common name other then the server FQDN.
+ Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
+ </description>
+ </method>
+ <method name="client_unsafe" qualifiers="static">
+ <return type="TLSOptions" />
+ <param index="0" name="trusted_chain" type="X509Certificate" default="null" />
+ <description>
+ Creates an [b]unsafe[/b] TLS client configuration where certificate validation is optional. You can optionally provide a valid [param trusted_chain], but the common name of the certififcates will never be checked. Using this configuration for purposes other than testing [b]is not recommended[/b].
+ Note: On the Web plafrom, TLS verification is always enforced against the CA list of the web browser. This is considered a security feature.
+ </description>
+ </method>
+ <method name="server" qualifiers="static">
+ <return type="TLSOptions" />
+ <param index="0" name="key" type="CryptoKey" />
+ <param index="1" name="certificate" type="X509Certificate" />
+ <description>
+ Creates a TLS server configuration using the provided [param key] and [param certificate].
+ Note: The [param certificate] should include the full certificate chain up to the signing CA (certificates file can be concatenated using a general purpose text editor).
+ </description>
+ </method>
+ </methods>
+</class>
diff --git a/drivers/alsa/asound-so_wrap.c b/drivers/alsa/asound-so_wrap.c
index 6f9c5408d5..ffe24d4313 100644
--- a/drivers/alsa/asound-so_wrap.c
+++ b/drivers/alsa/asound-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:13
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/alsa/asoundlib.h --sys-include "thirdparty/linuxbsd_headers/alsa/asoundlib.h" --soname libasound.so.2 --init-name asound --omit-prefix snd_pcm_sw_params_set_tstamp_type --omit-prefix snd_pcm_status_get_audio_htstamp_report --omit-prefix snd_pcm_sw_params_get_tstamp_type --omit-prefix snd_pcm_status_set_audio_htstamp_config --output-header ./drivers/alsa/asound-so_wrap.h --output-implementation ./drivers/alsa/asound-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:13
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/alsa/asoundlib.h --sys-include "thirdparty/linuxbsd_headers/alsa/asoundlib.h" --soname libasound.so.2 --init-name asound --omit-prefix snd_pcm_sw_params_set_tstamp_type --omit-prefix snd_pcm_status_get_audio_htstamp_report --omit-prefix snd_pcm_sw_params_get_tstamp_type --omit-prefix snd_pcm_status_set_audio_htstamp_config --output-header ./drivers/alsa/asound-so_wrap.h --output-implementation ./drivers/alsa/asound-so_wrap.c
//
#include <stdint.h>
diff --git a/drivers/alsa/asound-so_wrap.h b/drivers/alsa/asound-so_wrap.h
index ab54f4e8cf..8d12d15ba8 100644
--- a/drivers/alsa/asound-so_wrap.h
+++ b/drivers/alsa/asound-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_ASOUND
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:13
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/alsa/asoundlib.h --sys-include "thirdparty/linuxbsd_headers/alsa/asoundlib.h" --soname libasound.so.2 --init-name asound --omit-prefix snd_pcm_sw_params_set_tstamp_type --omit-prefix snd_pcm_status_get_audio_htstamp_report --omit-prefix snd_pcm_sw_params_get_tstamp_type --omit-prefix snd_pcm_status_set_audio_htstamp_config --output-header ./drivers/alsa/asound-so_wrap.h --output-implementation ./drivers/alsa/asound-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:13
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/alsa/asoundlib.h --sys-include "thirdparty/linuxbsd_headers/alsa/asoundlib.h" --soname libasound.so.2 --init-name asound --omit-prefix snd_pcm_sw_params_set_tstamp_type --omit-prefix snd_pcm_status_get_audio_htstamp_report --omit-prefix snd_pcm_sw_params_get_tstamp_type --omit-prefix snd_pcm_status_set_audio_htstamp_config --output-header ./drivers/alsa/asound-so_wrap.h --output-implementation ./drivers/alsa/asound-so_wrap.c
//
#include <stdint.h>
diff --git a/drivers/gles3/rasterizer_canvas_gles3.cpp b/drivers/gles3/rasterizer_canvas_gles3.cpp
index fbe00d22af..c65c396591 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.cpp
+++ b/drivers/gles3/rasterizer_canvas_gles3.cpp
@@ -113,16 +113,19 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
// Clear out any state that may have been left from the 3D pass.
reset_canvas();
- if (state.canvas_instance_data_buffers[state.current_buffer].fence != GLsync()) {
+ if (state.canvas_instance_data_buffers[state.current_data_buffer_index].fence != GLsync()) {
GLint syncStatus;
- glGetSynciv(state.canvas_instance_data_buffers[state.current_buffer].fence, GL_SYNC_STATUS, sizeof(GLint), nullptr, &syncStatus);
+ glGetSynciv(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence, GL_SYNC_STATUS, sizeof(GLint), nullptr, &syncStatus);
if (syncStatus == GL_UNSIGNALED) {
// If older than 2 frames, wait for sync OpenGL can have up to 3 frames in flight, any more and we need to sync anyway.
- if (state.canvas_instance_data_buffers[state.current_buffer].last_frame_used < RSG::rasterizer->get_frame_number() - 2) {
+ if (state.canvas_instance_data_buffers[state.current_data_buffer_index].last_frame_used < RSG::rasterizer->get_frame_number() - 2) {
#ifndef WEB_ENABLED
// On web, we do nothing as the glSubBufferData will force a sync anyway and WebGL does not like waiting.
- glClientWaitSync(state.canvas_instance_data_buffers[state.current_buffer].fence, 0, 100000000); // wait for up to 100ms
+ glClientWaitSync(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence, 0, 100000000); // wait for up to 100ms
#endif
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].last_frame_used = RSG::rasterizer->get_frame_number();
+ glDeleteSync(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence);
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].fence = GLsync();
} else {
// Used in last frame or frame before that. OpenGL can get up to two frames behind, so these buffers may still be in use
// Allocate a new buffer and use that.
@@ -130,9 +133,9 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
} else {
// Already finished all rendering commands, we can use it.
- state.canvas_instance_data_buffers[state.current_buffer].last_frame_used = RSG::rasterizer->get_frame_number();
- glDeleteSync(state.canvas_instance_data_buffers[state.current_buffer].fence);
- state.canvas_instance_data_buffers[state.current_buffer].fence = GLsync();
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].last_frame_used = RSG::rasterizer->get_frame_number();
+ glDeleteSync(state.canvas_instance_data_buffers[state.current_data_buffer_index].fence);
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].fence = GLsync();
}
}
@@ -279,7 +282,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
if (light_count > 0) {
- glBindBufferBase(GL_UNIFORM_BUFFER, LIGHT_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_buffer].light_ubo);
+ glBindBufferBase(GL_UNIFORM_BUFFER, LIGHT_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_data_buffer_index].light_ubo);
#ifdef WEB_ENABLED
glBufferSubData(GL_UNIFORM_BUFFER, 0, sizeof(LightUniform) * light_count, state.light_uniforms);
@@ -361,7 +364,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
state_buffer.tex_to_sdf = 1.0 / ((canvas_scale.x + canvas_scale.y) * 0.5);
- glBindBufferBase(GL_UNIFORM_BUFFER, BASE_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_buffer].state_ubo);
+ glBindBufferBase(GL_UNIFORM_BUFFER, BASE_UNIFORM_LOCATION, state.canvas_instance_data_buffers[state.current_data_buffer_index].state_ubo);
glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), &state_buffer, GL_STREAM_DRAW);
GLuint global_buffer = material_storage->global_shader_parameters_get_uniform_buffer();
@@ -395,7 +398,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
Item *ci = p_item_list;
Item *canvas_group_owner = nullptr;
- uint32_t starting_index = 0;
+ state.last_item_index = 0;
while (ci) {
if (ci->copy_back_buffer && canvas_group_owner == nullptr) {
@@ -440,6 +443,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
const Item::CommandMesh *cm = static_cast<const Item::CommandMesh *>(c);
if (cm->mesh_instance.is_valid()) {
mesh_storage->mesh_instance_check_for_update(cm->mesh_instance);
+ mesh_storage->mesh_instance_set_canvas_item_transform(cm->mesh_instance, canvas_transform_inverse * ci->final_transform);
update_skeletons = true;
}
}
@@ -454,7 +458,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
update_skeletons = false;
}
// Canvas group begins here, render until before this item
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used);
item_count = 0;
if (ci->canvas_group_owner->canvas_group->mode != RS::CANVAS_GROUP_MODE_TRANSPARENT) {
@@ -485,7 +489,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used, true);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used, true);
item_count = 0;
if (ci->canvas_group->blur_mipmaps) {
@@ -504,7 +508,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
}
//render anything pending, including clearing if no items
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used);
item_count = 0;
texture_storage->render_target_copy_to_back_buffer(p_to_render_target, back_buffer_rect, backbuffer_gen_mipmaps);
@@ -530,7 +534,7 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
mesh_storage->update_mesh_instances();
update_skeletons = false;
}
- _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, starting_index, r_sdf_used);
+ _render_items(p_to_render_target, item_count, canvas_transform_inverse, p_light_list, r_sdf_used);
//then reset
item_count = 0;
}
@@ -542,14 +546,15 @@ void RasterizerCanvasGLES3::canvas_render_items(RID p_to_render_target, Item *p_
RenderingServerDefault::redraw_request();
}
- state.canvas_instance_data_buffers[state.current_buffer].fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
// Clear out state used in 2D pass
reset_canvas();
- state.current_buffer = (state.current_buffer + 1) % state.canvas_instance_data_buffers.size();
+ state.current_data_buffer_index = (state.current_data_buffer_index + 1) % state.canvas_instance_data_buffers.size();
+ state.current_instance_buffer_index = 0;
}
-void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool &r_sdf_used, bool p_to_backbuffer) {
+void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer) {
GLES3::MaterialStorage *material_storage = GLES3::MaterialStorage::get_singleton();
canvas_begin(p_to_render_target, p_to_backbuffer);
@@ -565,17 +570,17 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
// Record Batches.
// First item always forms its own batch.
bool batch_broken = false;
- _new_batch(batch_broken, index);
+ _new_batch(batch_broken);
// Override the start position and index as we want to start from where we finished off last time.
- state.canvas_instance_batches[state.current_batch_index].start = r_last_index;
+ state.canvas_instance_batches[state.current_batch_index].start = state.last_item_index;
index = 0;
for (int i = 0; i < p_item_count; i++) {
Item *ci = items[i];
if (ci->final_clip_owner != state.canvas_instance_batches[state.current_batch_index].clip) {
- _new_batch(batch_broken, index);
+ _new_batch(batch_broken);
state.canvas_instance_batches[state.current_batch_index].clip = ci->final_clip_owner;
current_clip = ci->final_clip_owner;
}
@@ -599,7 +604,7 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
GLES3::CanvasShaderData *shader_data_cache = nullptr;
if (material != state.canvas_instance_batches[state.current_batch_index].material) {
- _new_batch(batch_broken, index);
+ _new_batch(batch_broken);
GLES3::CanvasMaterialData *material_data = nullptr;
if (material.is_valid()) {
@@ -629,12 +634,12 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
}
// Copy over all data needed for rendering.
- glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index]);
#ifdef WEB_ENABLED
- glBufferSubData(GL_ARRAY_BUFFER, r_last_index * sizeof(InstanceData), sizeof(InstanceData) * index, state.instance_data_array);
+ glBufferSubData(GL_ARRAY_BUFFER, state.last_item_index * sizeof(InstanceData), sizeof(InstanceData) * index, state.instance_data_array);
#else
// On Desktop and mobile we map the memory without synchronizing for maximum speed.
- void *buffer = glMapBufferRange(GL_ARRAY_BUFFER, r_last_index * sizeof(InstanceData), index * sizeof(InstanceData), GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+ void *buffer = glMapBufferRange(GL_ARRAY_BUFFER, state.last_item_index * sizeof(InstanceData), index * sizeof(InstanceData), GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
memcpy(buffer, state.instance_data_array, index * sizeof(InstanceData));
glUnmapBuffer(GL_ARRAY_BUFFER);
#endif
@@ -757,14 +762,14 @@ void RasterizerCanvasGLES3::_render_items(RID p_to_render_target, int p_item_cou
state.current_batch_index = 0;
state.canvas_instance_batches.clear();
- r_last_index += index;
+ state.last_item_index += index;
}
void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_batch_broken, bool &r_sdf_used) {
RenderingServer::CanvasItemTextureFilter texture_filter = p_item->texture_filter == RS::CANVAS_ITEM_TEXTURE_FILTER_DEFAULT ? state.default_filter : p_item->texture_filter;
if (texture_filter != state.canvas_instance_batches[state.current_batch_index].filter) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].filter = texture_filter;
}
@@ -772,7 +777,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
RenderingServer::CanvasItemTextureRepeat texture_repeat = p_item->texture_repeat == RS::CANVAS_ITEM_TEXTURE_REPEAT_DEFAULT ? state.default_repeat : p_item->texture_repeat;
if (texture_repeat != state.canvas_instance_batches[state.current_batch_index].repeat) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].repeat = texture_repeat;
}
@@ -816,7 +821,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
bool lights_disabled = light_count == 0 && !state.using_directional_lights;
if (lights_disabled != state.canvas_instance_batches[state.current_batch_index].lights_disabled) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].lights_disabled = lights_disabled;
}
@@ -864,7 +869,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
}
if (blend_mode != state.canvas_instance_batches[state.current_batch_index].blend_mode || blend_color != state.canvas_instance_batches[state.current_batch_index].blend_color) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].blend_mode = blend_mode;
state.canvas_instance_batches[state.current_batch_index].blend_color = blend_color;
}
@@ -874,12 +879,12 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
const Item::CommandRect *rect = static_cast<const Item::CommandRect *>(c);
if (rect->flags & CANVAS_RECT_TILE && state.canvas_instance_batches[state.current_batch_index].repeat != RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].repeat = RenderingServer::CanvasItemTextureRepeat::CANVAS_ITEM_TEXTURE_REPEAT_ENABLED;
}
if (rect->texture != state.canvas_instance_batches[state.current_batch_index].tex || state.canvas_instance_batches[state.current_batch_index].command_type != Item::Command::TYPE_RECT) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].tex = rect->texture;
state.canvas_instance_batches[state.current_batch_index].command_type = Item::Command::TYPE_RECT;
state.canvas_instance_batches[state.current_batch_index].command = c;
@@ -969,7 +974,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
const Item::CommandNinePatch *np = static_cast<const Item::CommandNinePatch *>(c);
if (np->texture != state.canvas_instance_batches[state.current_batch_index].tex || state.canvas_instance_batches[state.current_batch_index].command_type != Item::Command::TYPE_NINEPATCH) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].tex = np->texture;
state.canvas_instance_batches[state.current_batch_index].command_type = Item::Command::TYPE_NINEPATCH;
state.canvas_instance_batches[state.current_batch_index].command = c;
@@ -1034,7 +1039,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
const Item::CommandPolygon *polygon = static_cast<const Item::CommandPolygon *>(c);
// Polygon's can't be batched, so always create a new batch
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].tex = polygon->texture;
state.canvas_instance_batches[state.current_batch_index].command_type = Item::Command::TYPE_POLYGON;
@@ -1061,7 +1066,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
const Item::CommandPrimitive *primitive = static_cast<const Item::CommandPrimitive *>(c);
if (primitive->point_count != state.canvas_instance_batches[state.current_batch_index].primitive_points || state.canvas_instance_batches[state.current_batch_index].command_type != Item::Command::TYPE_PRIMITIVE) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
state.canvas_instance_batches[state.current_batch_index].tex = primitive->texture;
state.canvas_instance_batches[state.current_batch_index].primitive_points = primitive->point_count;
state.canvas_instance_batches[state.current_batch_index].command_type = Item::Command::TYPE_PRIMITIVE;
@@ -1086,10 +1091,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
if (primitive->point_count == 4) {
// Reset base data.
_update_transform_2d_to_mat2x3(base_transform * draw_transform, state.instance_data_array[r_index].world);
- state.instance_data_array[r_index].color_texture_pixel_size[0] = 0.0;
- state.instance_data_array[r_index].color_texture_pixel_size[1] = 0.0;
-
- state.instance_data_array[r_index].flags = base_flags | (state.instance_data_array[r_index - 1].flags & (FLAGS_DEFAULT_NORMAL_MAP_USED | FLAGS_DEFAULT_SPECULAR_MAP_USED)); //reset on each command for sanity, keep canvastexture binding config
+ _prepare_canvas_texture(state.canvas_instance_batches[state.current_batch_index].tex, state.canvas_instance_batches[state.current_batch_index].filter, state.canvas_instance_batches[state.current_batch_index].repeat, r_index, texpixel_size);
for (uint32_t j = 0; j < 3; j++) {
int offset = j == 0 ? 0 : 1;
@@ -1111,7 +1113,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
case Item::Command::TYPE_MULTIMESH:
case Item::Command::TYPE_PARTICLES: {
// Mesh's can't be batched, so always create a new batch
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
Color modulate(1, 1, 1, 1);
state.canvas_instance_batches[state.current_batch_index].shader_variant = CanvasShaderGLES3::MODE_ATTRIBUTES;
@@ -1183,7 +1185,7 @@ void RasterizerCanvasGLES3::_record_item_commands(const Item *p_item, RID p_rend
const Item::CommandClipIgnore *ci = static_cast<const Item::CommandClipIgnore *>(c);
if (current_clip) {
if (ci->ignore != reclip) {
- _new_batch(r_batch_broken, r_index);
+ _new_batch(r_batch_broken);
if (ci->ignore) {
state.canvas_instance_batches[state.current_batch_index].clip = nullptr;
reclip = true;
@@ -1227,7 +1229,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
case Item::Command::TYPE_RECT:
case Item::Command::TYPE_NINEPATCH: {
glBindVertexArray(data.indexed_quad_array);
- glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.canvas_instance_batches[p_index].instance_buffer_index]);
uint32_t range_start = state.canvas_instance_batches[p_index].start * sizeof(InstanceData);
_enable_attributes(range_start, false);
@@ -1243,7 +1245,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
ERR_FAIL_COND(!pb);
glBindVertexArray(pb->vertex_array);
- glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.canvas_instance_batches[p_index].instance_buffer_index]);
uint32_t range_start = state.canvas_instance_batches[p_index].start * sizeof(InstanceData);
_enable_attributes(range_start, false);
@@ -1267,7 +1269,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
case Item::Command::TYPE_PRIMITIVE: {
glBindVertexArray(data.canvas_quad_array);
- glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.canvas_instance_batches[p_index].instance_buffer_index]);
uint32_t range_start = state.canvas_instance_batches[p_index].start * sizeof(InstanceData);
_enable_attributes(range_start, true);
@@ -1370,7 +1372,7 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
index_array_gl = mesh_storage->mesh_surface_get_index_buffer(surface, 0);
bool use_index_buffer = false;
glBindVertexArray(vertex_array_gl);
- glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_buffer].buffer);
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.canvas_instance_batches[p_index].instance_buffer_index]);
uint32_t range_start = state.canvas_instance_batches[p_index].start * sizeof(InstanceData);
_enable_attributes(range_start, false, instance_count);
@@ -1427,20 +1429,30 @@ void RasterizerCanvasGLES3::_render_batch(Light *p_lights, uint32_t p_index) {
}
void RasterizerCanvasGLES3::_add_to_batch(uint32_t &r_index, bool &r_batch_broken) {
- if (r_index >= data.max_instances_per_buffer - 1) {
- ERR_PRINT_ONCE("Trying to draw too many items. Please increase maximum number of items in the project settings 'rendering/gl_compatibility/item_buffer_size'");
- return;
- }
-
- if (state.canvas_instance_batches[state.current_batch_index].instance_count >= data.max_instances_per_batch) {
- _new_batch(r_batch_broken, r_index);
- }
-
state.canvas_instance_batches[state.current_batch_index].instance_count++;
r_index++;
+ if (r_index >= data.max_instances_per_buffer) {
+ // Copy over all data needed for rendering right away
+ // then go back to recording item commands.
+ glBindBuffer(GL_ARRAY_BUFFER, state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers[state.current_instance_buffer_index]);
+#ifdef WEB_ENABLED
+ glBufferSubData(GL_ARRAY_BUFFER, state.last_item_index * sizeof(InstanceData), sizeof(InstanceData) * r_index, state.instance_data_array);
+#else
+ // On Desktop and mobile we map the memory without synchronizing for maximum speed.
+ void *buffer = glMapBufferRange(GL_ARRAY_BUFFER, state.last_item_index * sizeof(InstanceData), r_index * sizeof(InstanceData), GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
+ memcpy(buffer, state.instance_data_array, r_index * sizeof(InstanceData));
+ glUnmapBuffer(GL_ARRAY_BUFFER);
+#endif
+ _allocate_instance_buffer();
+ r_index = 0;
+ state.last_item_index = 0;
+ r_batch_broken = false; // Force a new batch to be created
+ _new_batch(r_batch_broken);
+ state.canvas_instance_batches[state.current_batch_index].start = 0;
+ }
}
-void RasterizerCanvasGLES3::_new_batch(bool &r_batch_broken, uint32_t &r_index) {
+void RasterizerCanvasGLES3::_new_batch(bool &r_batch_broken) {
if (state.canvas_instance_batches.size() == 0) {
state.canvas_instance_batches.push_back(Batch());
return;
@@ -1456,7 +1468,7 @@ void RasterizerCanvasGLES3::_new_batch(bool &r_batch_broken, uint32_t &r_index)
Batch new_batch = state.canvas_instance_batches[state.current_batch_index];
new_batch.instance_count = 0;
new_batch.start = state.canvas_instance_batches[state.current_batch_index].start + state.canvas_instance_batches[state.current_batch_index].instance_count;
-
+ new_batch.instance_buffer_index = state.current_instance_buffer_index;
state.current_batch_index++;
state.canvas_instance_batches.push_back(new_batch);
}
@@ -2433,17 +2445,35 @@ void RasterizerCanvasGLES3::_allocate_instance_data_buffer() {
glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[2]);
glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), nullptr, GL_STREAM_DRAW);
- state.current_buffer = (state.current_buffer + 1);
+ state.current_data_buffer_index = (state.current_data_buffer_index + 1);
DataBuffer db;
- db.buffer = new_buffers[0];
+ db.instance_buffers.push_back(new_buffers[0]);
db.light_ubo = new_buffers[1];
db.state_ubo = new_buffers[2];
db.last_frame_used = RSG::rasterizer->get_frame_number();
- state.canvas_instance_data_buffers.insert(state.current_buffer, db);
- state.current_buffer = state.current_buffer % state.canvas_instance_data_buffers.size();
+ state.canvas_instance_data_buffers.insert(state.current_data_buffer_index, db);
+ state.current_data_buffer_index = state.current_data_buffer_index % state.canvas_instance_data_buffers.size();
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_UNIFORM_BUFFER, 0);
}
+void RasterizerCanvasGLES3::_allocate_instance_buffer() {
+ state.current_instance_buffer_index++;
+
+ if (int(state.current_instance_buffer_index) < state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.size()) {
+ // We already allocated another buffer in a previous frame, so we can just use it.
+ return;
+ }
+
+ GLuint new_buffer;
+ glGenBuffers(1, &new_buffer);
+
+ glBindBuffer(GL_ARRAY_BUFFER, new_buffer);
+ glBufferData(GL_ARRAY_BUFFER, data.max_instance_buffer_size, nullptr, GL_STREAM_DRAW);
+
+ state.canvas_instance_data_buffers[state.current_data_buffer_index].instance_buffers.push_back(new_buffer);
+
+ glBindBuffer(GL_ARRAY_BUFFER, 0);
+}
void RasterizerCanvasGLES3::set_time(double p_time) {
state.time = p_time;
@@ -2586,14 +2616,12 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
int uniform_max_size = config->max_uniform_buffer_size;
if (uniform_max_size < 65536) {
data.max_lights_per_render = 64;
- data.max_instances_per_batch = 128;
} else {
data.max_lights_per_render = 256;
- data.max_instances_per_batch = 2048;
}
// Reserve 3 Uniform Buffers for instance data Frame N, N+1 and N+2
- data.max_instances_per_buffer = MAX(data.max_instances_per_batch, uint32_t(GLOBAL_GET("rendering/gl_compatibility/item_buffer_size")));
+ data.max_instances_per_buffer = uint32_t(GLOBAL_GET("rendering/gl_compatibility/item_buffer_size"));
data.max_instance_buffer_size = data.max_instances_per_buffer * sizeof(InstanceData); // 16,384 instances * 128 bytes = 2,097,152 bytes = 2,048 kb
state.canvas_instance_data_buffers.resize(3);
state.canvas_instance_batches.reserve(200);
@@ -2611,7 +2639,7 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
glBindBuffer(GL_UNIFORM_BUFFER, new_buffers[2]);
glBufferData(GL_UNIFORM_BUFFER, sizeof(StateBuffer), nullptr, GL_STREAM_DRAW);
DataBuffer db;
- db.buffer = new_buffers[0];
+ db.instance_buffers.push_back(new_buffers[0]);
db.light_ubo = new_buffers[1];
db.state_ubo = new_buffers[2];
db.last_frame_used = 0;
@@ -2638,7 +2666,6 @@ RasterizerCanvasGLES3::RasterizerCanvasGLES3() {
String global_defines;
global_defines += "#define MAX_GLOBAL_SHADER_UNIFORMS 256\n"; // TODO: this is arbitrary for now
global_defines += "#define MAX_LIGHTS " + itos(data.max_lights_per_render) + "\n";
- global_defines += "#define MAX_DRAW_DATA_INSTANCES " + itos(data.max_instances_per_batch) + "\n";
GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.initialize(global_defines, 1);
data.canvas_shader_default_version = GLES3::MaterialStorage::get_singleton()->shaders.canvas_shader.version_create();
diff --git a/drivers/gles3/rasterizer_canvas_gles3.h b/drivers/gles3/rasterizer_canvas_gles3.h
index 916e12057c..1c14d0b466 100644
--- a/drivers/gles3/rasterizer_canvas_gles3.h
+++ b/drivers/gles3/rasterizer_canvas_gles3.h
@@ -247,7 +247,6 @@ public:
uint32_t max_lights_per_render = 256;
uint32_t max_lights_per_item = 16;
- uint32_t max_instances_per_batch = 512;
uint32_t max_instances_per_buffer = 16384;
uint32_t max_instance_buffer_size = 16384 * 128;
} data;
@@ -256,6 +255,7 @@ public:
// Position in the UBO measured in bytes
uint32_t start = 0;
uint32_t instance_count = 0;
+ uint32_t instance_buffer_index = 0;
RID tex;
RS::CanvasItemTextureFilter filter = RS::CANVAS_ITEM_TEXTURE_FILTER_MAX;
@@ -281,7 +281,7 @@ public:
// We track them and ensure that they don't get reused until at least 2 frames have passed
// to avoid the GPU stalling to wait for a resource to become available.
struct DataBuffer {
- GLuint buffer = 0;
+ Vector<GLuint> instance_buffers;
GLuint light_ubo = 0;
GLuint state_ubo = 0;
uint64_t last_frame_used = -3;
@@ -291,9 +291,10 @@ public:
struct State {
LocalVector<DataBuffer> canvas_instance_data_buffers;
LocalVector<Batch> canvas_instance_batches;
- uint32_t current_buffer = 0;
- uint32_t current_buffer_index = 0;
+ uint32_t current_data_buffer_index = 0;
+ uint32_t current_instance_buffer_index = 0;
uint32_t current_batch_index = 0;
+ uint32_t last_item_index = 0;
InstanceData *instance_data_array = nullptr;
@@ -354,14 +355,14 @@ public:
void _prepare_canvas_texture(RID p_texture, RS::CanvasItemTextureFilter p_base_filter, RS::CanvasItemTextureRepeat p_base_repeat, uint32_t &r_index, Size2 &r_texpixel_size);
void canvas_render_items(RID p_to_render_target, Item *p_item_list, const Color &p_modulate, Light *p_light_list, Light *p_directional_list, const Transform2D &p_canvas_transform, RS::CanvasItemTextureFilter p_default_filter, RS::CanvasItemTextureRepeat p_default_repeat, bool p_snap_2d_vertices_to_pixel, bool &r_sdf_used) override;
- void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, uint32_t &r_last_index, bool &r_sdf_used, bool p_to_backbuffer = false);
+ void _render_items(RID p_to_render_target, int p_item_count, const Transform2D &p_canvas_transform_inverse, Light *p_lights, bool &r_sdf_used, bool p_to_backbuffer = false);
void _record_item_commands(const Item *p_item, RID p_render_target, const Transform2D &p_canvas_transform_inverse, Item *&current_clip, GLES3::CanvasShaderData::BlendMode p_blend_mode, Light *p_lights, uint32_t &r_index, bool &r_break_batch, bool &r_sdf_used);
void _render_batch(Light *p_lights, uint32_t p_index);
bool _bind_material(GLES3::CanvasMaterialData *p_material_data, CanvasShaderGLES3::ShaderVariant p_variant, uint64_t p_specialization);
- void _new_batch(bool &r_batch_broken, uint32_t &r_index);
+ void _new_batch(bool &r_batch_broken);
void _add_to_batch(uint32_t &r_index, bool &r_batch_broken);
void _allocate_instance_data_buffer();
- void _align_instance_data_buffer(uint32_t &r_index);
+ void _allocate_instance_buffer();
void _enable_attributes(uint32_t p_start, bool p_primitive, uint32_t p_rate = 1);
void set_time(double p_time);
diff --git a/drivers/gles3/shaders/skeleton.glsl b/drivers/gles3/shaders/skeleton.glsl
index a1e3c098f4..aad856a5a2 100644
--- a/drivers/gles3/shaders/skeleton.glsl
+++ b/drivers/gles3/shaders/skeleton.glsl
@@ -87,6 +87,16 @@ uniform highp float blend_weight;
uniform lowp float blend_shape_count;
#endif
+#ifdef USE_SKELETON
+uniform mediump vec2 skeleton_transform_x;
+uniform mediump vec2 skeleton_transform_y;
+uniform mediump vec2 skeleton_transform_offset;
+
+uniform mediump vec2 inverse_transform_x;
+uniform mediump vec2 inverse_transform_y;
+uniform mediump vec2 inverse_transform_offset;
+#endif
+
vec2 signNotZero(vec2 v) {
return mix(vec2(-1.0), vec2(1.0), greaterThanEqual(v.xy, vec2(0.0)));
}
@@ -164,10 +174,13 @@ void main() {
m += GET_BONE_MATRIX(bones.z, bones_a.z, in_weight_attrib.z);
m += GET_BONE_MATRIX(bones.w, bones_a.w, in_weight_attrib.w);
+ mat4 skeleton_matrix = mat4(vec4(skeleton_transform_x, 0.0, 0.0), vec4(skeleton_transform_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(skeleton_transform_offset, 0.0, 1.0));
+ mat4 inverse_matrix = mat4(vec4(inverse_transform_x, 0.0, 0.0), vec4(inverse_transform_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(inverse_transform_offset, 0.0, 1.0));
mat4 bone_matrix = mat4(m[0], m[1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
- //reverse order because its transposed
- out_vertex = (vec4(out_vertex, 0.0, 1.0) * bone_matrix).xy;
+ bone_matrix = skeleton_matrix * transpose(bone_matrix) * inverse_matrix;
+
+ out_vertex = (bone_matrix * vec4(out_vertex, 0.0, 1.0)).xy;
#endif // USE_SKELETON
#else // MODE_2D
diff --git a/drivers/gles3/storage/mesh_storage.cpp b/drivers/gles3/storage/mesh_storage.cpp
index 71f262af20..5ba0c5a09c 100644
--- a/drivers/gles3/storage/mesh_storage.cpp
+++ b/drivers/gles3/storage/mesh_storage.cpp
@@ -997,6 +997,11 @@ void MeshStorage::mesh_instance_check_for_update(RID p_mesh_instance) {
}
}
+void MeshStorage::mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) {
+ MeshInstance *mi = mesh_instance_owner.get_or_null(p_mesh_instance);
+ mi->canvas_item_transform_2d = p_transform;
+}
+
void MeshStorage::_blend_shape_bind_mesh_instance_buffer(MeshInstance *p_mi, uint32_t p_surface) {
glBindBuffer(GL_ARRAY_BUFFER, p_mi->surfaces[p_surface].vertex_buffers[0]);
@@ -1163,6 +1168,16 @@ void MeshStorage::update_mesh_instances() {
skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::BLEND_SHAPE_COUNT, float(mi->mesh->blend_shape_count), skeleton_shader.shader_version, variant, specialization);
if (can_use_skeleton) {
+ Transform2D transform = mi->canvas_item_transform_2d.affine_inverse() * sk->base_transform_2d;
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_X, transform[0], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_Y, transform[1], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_OFFSET, transform[2], skeleton_shader.shader_version, variant, specialization);
+
+ Transform2D inverse_transform = transform.affine_inverse();
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_X, inverse_transform[0], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_Y, inverse_transform[1], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_OFFSET, inverse_transform[2], skeleton_shader.shader_version, variant, specialization);
+
// Do last blendshape in the same pass as the Skeleton.
_compute_skeleton(mi, sk, i);
can_use_skeleton = false;
@@ -1201,6 +1216,16 @@ void MeshStorage::update_mesh_instances() {
continue;
}
+ Transform2D transform = mi->canvas_item_transform_2d.affine_inverse() * sk->base_transform_2d;
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_X, transform[0], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_Y, transform[1], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::SKELETON_TRANSFORM_OFFSET, transform[2], skeleton_shader.shader_version, variant, specialization);
+
+ Transform2D inverse_transform = transform.affine_inverse();
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_X, inverse_transform[0], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_Y, inverse_transform[1], skeleton_shader.shader_version, variant, specialization);
+ skeleton_shader.shader.version_set_uniform(SkeletonShaderGLES3::INVERSE_TRANSFORM_OFFSET, inverse_transform[2], skeleton_shader.shader_version, variant, specialization);
+
glBindVertexArray(mi->mesh->surfaces[i]->skeleton_vertex_array);
_compute_skeleton(mi, sk, i);
}
diff --git a/drivers/gles3/storage/mesh_storage.h b/drivers/gles3/storage/mesh_storage.h
index 2efc57462b..e1c2bc3f63 100644
--- a/drivers/gles3/storage/mesh_storage.h
+++ b/drivers/gles3/storage/mesh_storage.h
@@ -163,6 +163,7 @@ struct MeshInstance {
bool weights_dirty = false;
SelfList<MeshInstance> weight_update_list;
SelfList<MeshInstance> array_update_list;
+ Transform2D canvas_item_transform_2d;
MeshInstance() :
weight_update_list(this), array_update_list(this) {}
};
@@ -423,6 +424,7 @@ public:
virtual void mesh_instance_set_skeleton(RID p_mesh_instance, RID p_skeleton) override;
virtual void mesh_instance_set_blend_shape_weight(RID p_mesh_instance, int p_shape, float p_weight) override;
virtual void mesh_instance_check_for_update(RID p_mesh_instance) override;
+ virtual void mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) override;
virtual void update_mesh_instances() override;
// TODO: considering hashing versions with multimesh buffer RID.
diff --git a/drivers/pulseaudio/pulse-so_wrap.c b/drivers/pulseaudio/pulse-so_wrap.c
index 1765d7e7e6..8f477740ce 100644
--- a/drivers/pulseaudio/pulse-so_wrap.c
+++ b/drivers/pulseaudio/pulse-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:29
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/pulse/pulseaudio.h --sys-include "thirdparty/linuxbsd_headers/pulse/pulseaudio.h" --soname libpulse.so.0 --omit-prefix _pa_ --init-name pulse --output-header ./drivers/pulseaudio/pulse-so_wrap.h --output-implementation ./drivers/pulseaudio/pulse-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:29
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/pulse/pulseaudio.h --sys-include "thirdparty/linuxbsd_headers/pulse/pulseaudio.h" --soname libpulse.so.0 --omit-prefix _pa_ --init-name pulse --output-header ./drivers/pulseaudio/pulse-so_wrap.h --output-implementation ./drivers/pulseaudio/pulse-so_wrap.c
//
#include <stdint.h>
diff --git a/drivers/pulseaudio/pulse-so_wrap.h b/drivers/pulseaudio/pulse-so_wrap.h
index b3baf4c81a..bba8fd36a6 100644
--- a/drivers/pulseaudio/pulse-so_wrap.h
+++ b/drivers/pulseaudio/pulse-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_PULSE
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:29
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/pulse/pulseaudio.h --sys-include "thirdparty/linuxbsd_headers/pulse/pulseaudio.h" --soname libpulse.so.0 --omit-prefix _pa_ --init-name pulse --output-header ./drivers/pulseaudio/pulse-so_wrap.h --output-implementation ./drivers/pulseaudio/pulse-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:29
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/pulse/pulseaudio.h --sys-include "thirdparty/linuxbsd_headers/pulse/pulseaudio.h" --soname libpulse.so.0 --omit-prefix _pa_ --init-name pulse --output-header ./drivers/pulseaudio/pulse-so_wrap.h --output-implementation ./drivers/pulseaudio/pulse-so_wrap.c
//
#include <stdint.h>
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index 338a22c070..8dd087451c 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -1954,6 +1954,10 @@ void AnimationTrackEdit::_notification(int p_what) {
get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")),
get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons"))
};
+ Ref<Texture2D> blend_icon[2] = {
+ get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")),
+ get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")),
+ };
int ofs = get_size().width - timeline->get_buttons_width();
@@ -1982,6 +1986,11 @@ void AnimationTrackEdit::_notification(int p_what) {
if (!animation->track_is_compressed(track) && animation->track_get_type(track) == Animation::TYPE_VALUE) {
draw_texture(update_icon, update_mode_rect.position);
}
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ Ref<Texture2D> use_blend_icon = blend_icon[animation->audio_track_is_use_blend(track) ? 0 : 1];
+ Vector2 use_blend_icon_pos = update_mode_rect.position + (update_mode_rect.size - use_blend_icon->get_size()) / 2;
+ draw_texture(use_blend_icon, use_blend_icon_pos);
+ }
// Make it easier to click.
update_mode_rect.position.y = 0;
update_mode_rect.size.y = get_size().height;
@@ -1990,13 +1999,12 @@ void AnimationTrackEdit::_notification(int p_what) {
update_mode_rect.size.x += hsep / 2;
if (!read_only) {
- if (animation->track_get_type(track) == Animation::TYPE_VALUE) {
+ if (animation->track_get_type(track) == Animation::TYPE_VALUE || animation->track_get_type(track) == Animation::TYPE_AUDIO) {
draw_texture(down_icon, Vector2(ofs, int(get_size().height - down_icon->get_height()) / 2));
update_mode_rect.size.x += down_icon->get_width();
} else if (animation->track_get_type(track) == Animation::TYPE_BEZIER) {
Ref<Texture2D> bezier_icon = get_theme_icon(SNAME("EditBezier"), SNAME("EditorIcons"));
update_mode_rect.size.x += down_icon->get_width();
-
update_mode_rect = Rect2();
} else {
update_mode_rect = Rect2();
@@ -2439,7 +2447,11 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const {
}
if (update_mode_rect.has_point(p_pos)) {
- return TTR("Update Mode (How this property is set)");
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ return TTR("Use Blend");
+ } else {
+ return TTR("Update Mode (How this property is set)");
+ }
}
if (interp_mode_rect.has_point(p_pos)) {
@@ -2641,9 +2653,14 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->connect("id_pressed", callable_mp(this, &AnimationTrackEdit::_menu_selected));
}
menu->clear();
- menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
- menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
- menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+ if (animation->track_get_type(track) == Animation::TYPE_AUDIO) {
+ menu->add_icon_item(get_theme_icon(SNAME("UseBlendEnable"), SNAME("EditorIcons")), TTR("Use Blend"), MENU_USE_BLEND_ENABLED);
+ menu->add_icon_item(get_theme_icon(SNAME("UseBlendDisable"), SNAME("EditorIcons")), TTR("Don't Use Blend"), MENU_USE_BLEND_DISABLED);
+ } else {
+ menu->add_icon_item(get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), TTR("Continuous"), MENU_CALL_MODE_CONTINUOUS);
+ menu->add_icon_item(get_theme_icon(SNAME("TrackDiscrete"), SNAME("EditorIcons")), TTR("Discrete"), MENU_CALL_MODE_DISCRETE);
+ menu->add_icon_item(get_theme_icon(SNAME("TrackCapture"), SNAME("EditorIcons")), TTR("Capture"), MENU_CALL_MODE_CAPTURE);
+ }
menu->reset_size();
Vector2 popup_pos = get_screen_position() + update_mode_rect.position + Vector2(0, update_mode_rect.size.height);
@@ -2662,7 +2679,7 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
- // Check is angle property.
+ // Check whether it is angle property.
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
AnimationPlayer *ap = ape->get_player();
@@ -3055,6 +3072,16 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
emit_signal(SNAME("delete_request"));
} break;
+ case MENU_USE_BLEND_ENABLED:
+ case MENU_USE_BLEND_DISABLED: {
+ bool use_blend = p_index == MENU_USE_BLEND_ENABLED;
+ EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
+ undo_redo->create_action(TTR("Change Animation Use Blend"));
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", track, use_blend);
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", track, animation->audio_track_is_use_blend(track));
+ undo_redo->commit_action();
+ queue_redraw();
+ } break;
}
}
@@ -3488,6 +3515,9 @@ void AnimationTrackEditor::_animation_track_remove_request(int p_track, Ref<Anim
if (p_from_animation->track_get_type(idx) == Animation::TYPE_VALUE) {
undo_redo->add_undo_method(p_from_animation.ptr(), "value_track_set_update_mode", idx, p_from_animation->value_track_get_update_mode(idx));
}
+ if (animation->track_get_type(idx) == Animation::TYPE_AUDIO) {
+ undo_redo->add_undo_method(animation.ptr(), "audio_track_set_use_blend", idx, animation->audio_track_is_use_blend(idx));
+ }
undo_redo->commit_action();
}
@@ -5618,6 +5648,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
if (tc.track_type == Animation::TYPE_VALUE) {
tc.update_mode = animation->value_track_get_update_mode(idx);
}
+ if (tc.track_type == Animation::TYPE_AUDIO) {
+ tc.use_blend = animation->audio_track_is_use_blend(idx);
+ }
tc.loop_wrap = animation->track_get_interpolation_loop_wrap(idx);
tc.enabled = animation->track_is_enabled(idx);
for (int i = 0; i < animation->track_get_key_count(idx); i++) {
@@ -5662,6 +5695,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
if (track_clipboard[i].track_type == Animation::TYPE_VALUE) {
undo_redo->add_do_method(animation.ptr(), "value_track_set_update_mode", base_track, track_clipboard[i].update_mode);
}
+ if (track_clipboard[i].track_type == Animation::TYPE_AUDIO) {
+ undo_redo->add_do_method(animation.ptr(), "audio_track_set_use_blend", base_track, track_clipboard[i].use_blend);
+ }
for (int j = 0; j < track_clipboard[i].keys.size(); j++) {
undo_redo->add_do_method(animation.ptr(), "track_insert_key", base_track, track_clipboard[i].keys[j].time, track_clipboard[i].keys[j].value, track_clipboard[i].keys[j].transition);
diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h
index 8506d9b80d..2a59bda2a4 100644
--- a/editor/animation_track_editor.h
+++ b/editor/animation_track_editor.h
@@ -220,7 +220,9 @@ class AnimationTrackEdit : public Control {
MENU_KEY_INSERT,
MENU_KEY_DUPLICATE,
MENU_KEY_ADD_RESET,
- MENU_KEY_DELETE
+ MENU_KEY_DELETE,
+ MENU_USE_BLEND_ENABLED,
+ MENU_USE_BLEND_DISABLED,
};
AnimationTimelineEdit *timeline = nullptr;
@@ -566,6 +568,7 @@ class AnimationTrackEditor : public VBoxContainer {
Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
bool loop_wrap = false;
bool enabled = false;
+ bool use_blend = false;
struct Key {
float time = 0;
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 644735a4d8..28d687488c 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -143,7 +143,9 @@ void FindReplaceBar::unhandled_input(const Ref<InputEvent> &p_event) {
}
bool FindReplaceBar::_search(uint32_t p_flags, int p_from_line, int p_from_col) {
- text_editor->remove_secondary_carets();
+ if (!preserve_cursor) {
+ text_editor->remove_secondary_carets();
+ }
String text = get_search_text();
Point2i pos = text_editor->search(text, p_flags, p_from_line, p_from_col);
diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp
index dee130ed0f..aaa07e98c8 100644
--- a/editor/connections_dialog.cpp
+++ b/editor/connections_dialog.cpp
@@ -186,7 +186,7 @@ void ConnectDialog::_unbind_count_changed(double p_count) {
void ConnectDialog::_method_selected() {
TreeItem *selected_item = method_tree->get_selected();
- dst_method->set_text(selected_item->get_text(0));
+ dst_method->set_text(selected_item->get_metadata(0));
}
/*
@@ -260,12 +260,8 @@ StringName ConnectDialog::generate_method_callback_name(Node *p_source, String p
void ConnectDialog::_create_method_tree_items(const List<MethodInfo> &p_methods, TreeItem *p_parent_item) {
for (const MethodInfo &mi : p_methods) {
TreeItem *method_item = method_tree->create_item(p_parent_item);
- method_item->set_text(0, mi.name);
- if (mi.return_val.type == Variant::NIL) {
- method_item->set_icon(0, get_theme_icon(SNAME("Variant"), "EditorIcons"));
- } else {
- method_item->set_icon(0, get_theme_icon(Variant::get_type_name(mi.return_val.type), "EditorIcons"));
- }
+ method_item->set_text(0, get_signature(mi));
+ method_item->set_metadata(0, mi.name);
}
}
@@ -293,6 +289,11 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me
type_mismatch = true;
break;
}
+
+ if (stype == Variant::OBJECT && mtype == Variant::OBJECT && E->get().class_name != F->get().class_name) {
+ type_mismatch = true;
+ break;
+ }
}
if (type_mismatch) {
@@ -354,7 +355,7 @@ void ConnectDialog::_update_method_tree() {
return;
}
- // Get methods from each class in the heirarchy.
+ // Get methods from each class in the hierarchy.
StringName current_class = target->get_class_name();
do {
TreeItem *class_item = method_tree->create_item(root_item);
@@ -488,6 +489,34 @@ Vector<Variant> ConnectDialog::get_binds() const {
return cdbinds->params;
}
+String ConnectDialog::get_signature(const MethodInfo &p_method, PackedStringArray *r_arg_names) {
+ PackedStringArray signature;
+ signature.append(p_method.name);
+ signature.append("(");
+
+ for (int i = 0; i < p_method.arguments.size(); i++) {
+ if (i > 0) {
+ signature.append(", ");
+ }
+
+ const PropertyInfo &pi = p_method.arguments[i];
+ String tname = "var";
+ if (pi.type == Variant::OBJECT && pi.class_name != StringName()) {
+ tname = pi.class_name.operator String();
+ } else if (pi.type != Variant::NIL) {
+ tname = Variant::get_type_name(pi.type);
+ }
+
+ signature.append((pi.name.is_empty() ? String("arg " + itos(i)) : pi.name) + ": " + tname);
+ if (r_arg_names) {
+ r_arg_names->push_back(pi.name + ":" + tname);
+ }
+ }
+
+ signature.append(")");
+ return String().join(signature);
+}
+
bool ConnectDialog::get_deferred() const {
return deferred->is_pressed();
}
@@ -545,7 +574,7 @@ void ConnectDialog::init(const ConnectionData &p_cd, const PackedStringArray &p_
source_connection_data = p_cd;
}
-void ConnectDialog::popup_dialog(const String &p_for_signal) {
+void ConnectDialog::popup_dialog(const String p_for_signal) {
from_signal->set_text(p_for_signal);
error_label->add_theme_color_override("font_color", error_label->get_theme_color(SNAME("error_color"), SNAME("Editor")));
if (!advanced->is_pressed()) {
@@ -972,8 +1001,6 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
String signal_name = sinfo["name"];
PackedStringArray signal_args = sinfo["args"];
- const String &signal_name_ref = signal_name;
-
Node *dst_node = selected_node->get_owner() ? selected_node->get_owner() : selected_node;
if (!dst_node || dst_node->get_script().is_null()) {
dst_node = _find_first_script(get_tree()->get_edited_scene_root(), get_tree()->get_edited_scene_root());
@@ -981,10 +1008,10 @@ void ConnectionsDock::_open_connection_dialog(TreeItem &p_item) {
ConnectDialog::ConnectionData cd;
cd.source = selected_node;
- cd.signal = StringName(signal_name_ref);
+ cd.signal = StringName(signal_name);
cd.target = dst_node;
cd.method = ConnectDialog::generate_method_callback_name(cd.source, signal_name, cd.target);
- connect_dialog->popup_dialog(signal_name_ref);
+ connect_dialog->popup_dialog(signal_name + "(" + String(", ").join(signal_args) + ")");
connect_dialog->init(cd, signal_args);
connect_dialog->set_title(TTR("Connect a Signal to a Method"));
}
@@ -1235,37 +1262,15 @@ void ConnectionsDock::update_tree() {
}
for (MethodInfo &mi : node_signals2) {
- StringName signal_name = mi.name;
- String signaldesc = "(";
- PackedStringArray argnames;
-
- String filter_text = search_box->get_text();
- if (!filter_text.is_subsequence_ofn(signal_name)) {
+ const StringName signal_name = mi.name;
+ if (!search_box->get_text().is_subsequence_ofn(signal_name)) {
continue;
}
-
- if (mi.arguments.size()) {
- for (int i = 0; i < mi.arguments.size(); i++) {
- PropertyInfo &pi = mi.arguments[i];
-
- if (i > 0) {
- signaldesc += ", ";
- }
- String tname = "var";
- if (pi.type == Variant::OBJECT && pi.class_name != StringName()) {
- tname = pi.class_name.operator String();
- } else if (pi.type != Variant::NIL) {
- tname = Variant::get_type_name(pi.type);
- }
- signaldesc += (pi.name.is_empty() ? String("arg " + itos(i)) : pi.name) + ": " + tname;
- argnames.push_back(pi.name + ":" + tname);
- }
- }
- signaldesc += ")";
+ PackedStringArray argnames;
// Create the children of the subsection - the actual list of signals.
TreeItem *signal_item = tree->create_item(section_item);
- String signame = String(signal_name) + signaldesc;
+ String signame = connect_dialog->get_signature(mi, &argnames);
signal_item->set_text(0, signame);
if (signame == prev_selected) {
@@ -1313,7 +1318,7 @@ void ConnectionsDock::update_tree() {
}
// "::" separators used in make_custom_tooltip for formatting.
- signal_item->set_tooltip_text(0, String(signal_name) + "::" + signaldesc + "::" + descr);
+ signal_item->set_tooltip_text(0, String(signal_name) + "::" + signame.trim_prefix(mi.name) + "::" + descr);
}
// List existing connections.
diff --git a/editor/connections_dialog.h b/editor/connections_dialog.h
index f70970996d..277ea03cf7 100644
--- a/editor/connections_dialog.h
+++ b/editor/connections_dialog.h
@@ -172,6 +172,7 @@ public:
void set_dst_method(const StringName &p_method);
int get_unbinds() const;
Vector<Variant> get_binds() const;
+ String get_signature(const MethodInfo &p_method, PackedStringArray *r_arg_names = nullptr);
bool get_deferred() const;
bool get_one_shot() const;
@@ -179,7 +180,7 @@ public:
void init(const ConnectionData &p_cd, const PackedStringArray &p_signal_args, bool p_edit = false);
- void popup_dialog(const String &p_for_signal);
+ void popup_dialog(const String p_for_signal);
ConnectDialog();
~ConnectDialog();
};
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index 945a3ef932..32952a367d 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -548,7 +548,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
cpp_cond->set_text(0, "<" + vformat(TTR("%s Error"), source_language_name) + ">");
cpp_cond->set_text(1, oe.error);
cpp_cond->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT);
- tooltip += vformat(TTR("%s Error:"), source_language_name) + "\n";
+ tooltip += vformat(TTR("%s Error:"), source_language_name) + " " + oe.error + "\n";
if (source_is_project_file) {
cpp_cond->set_metadata(0, source_meta);
}
@@ -570,7 +570,7 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
cpp_source->set_text(0, "<" + vformat(TTR("%s Source"), source_language_name) + ">");
cpp_source->set_text(1, source_txt);
cpp_source->set_text_alignment(0, HORIZONTAL_ALIGNMENT_LEFT);
- tooltip += vformat(TTR("%s Source:"), source_language_name) + source_txt + "\n";
+ tooltip += vformat(TTR("%s Source:"), source_language_name) + " " + source_txt + "\n";
// Set metadata to highlight error line in scripts.
if (source_is_project_file) {
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 1690302e6e..6a1c77d376 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -47,11 +47,7 @@ class TextureRect;
class EditorPropertyRevert {
public:
- static bool get_instantiated_node_original_property(Node *p_node, const StringName &p_prop, Variant &value, bool p_check_class_default = true);
- static bool is_node_property_different(Node *p_node, const Variant &p_current, const Variant &p_orig);
- static bool is_property_value_different(const Variant &p_a, const Variant &p_b);
static Variant get_property_revert_value(Object *p_object, const StringName &p_property, bool *r_is_valid);
-
static bool can_property_revert(Object *p_object, const StringName &p_property, const Variant *p_custom_current_value = nullptr);
};
@@ -331,7 +327,7 @@ class EditorInspectorArray : public EditorInspectorSection {
AcceptDialog *resize_dialog = nullptr;
SpinBox *new_size_spin_box = nullptr;
- // Pagination
+ // Pagination.
int page_length = 5;
int page = 0;
int max_page = 0;
@@ -495,7 +491,7 @@ class EditorInspector : public ScrollContainer {
HashMap<ObjectID, int> scroll_cache;
- String property_prefix; //used for sectioned inspector
+ String property_prefix; // Used for sectioned inspector.
String object_class;
Variant property_clipboard;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 0d5c820a3c..a1028a14c5 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -2096,14 +2096,25 @@ void EditorNode::edit_item(Object *p_object, Object *p_editing_owner) {
if (!item_plugins.is_empty()) {
ObjectID owner_id = p_editing_owner->get_instance_id();
+ List<EditorPlugin *> to_remove;
for (EditorPlugin *plugin : active_plugins[owner_id]) {
if (!item_plugins.has(plugin)) {
+ // Remove plugins no longer used by this editing owner.
+ to_remove.push_back(plugin);
plugin->make_visible(false);
plugin->edit(nullptr);
}
}
+ for (EditorPlugin *plugin : to_remove) {
+ active_plugins[owner_id].erase(plugin);
+ }
+
for (EditorPlugin *plugin : item_plugins) {
+ if (active_plugins[owner_id].has(plugin)) {
+ continue;
+ }
+
for (KeyValue<ObjectID, HashSet<EditorPlugin *>> &kv : active_plugins) {
if (kv.key != owner_id) {
EditorPropertyResource *epres = Object::cast_to<EditorPropertyResource>(ObjectDB::get_instance(kv.key));
@@ -6195,7 +6206,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
List<PropertyInfo> pinfo;
modifiable_node->get_property_list(&pinfo);
- // Get names of all valid property names (TODO: make this more efficent).
+ // Get names of all valid property names (TODO: make this more efficient).
List<String> property_names;
for (const PropertyInfo &E2 : pinfo) {
if (E2.usage & PROPERTY_USAGE_STORAGE) {
@@ -6213,7 +6224,7 @@ void EditorNode::reload_instances_with_path_in_edited_scenes(const String &p_ins
for (const ConnectionWithNodePath &E2 : E.value.connections_to) {
Connection conn = E2.connection;
- // Get the node the callable is targetting.
+ // Get the node the callable is targeting.
Node *target_node = cast_to<Node>(conn.callable.get_object());
// If the callable object no longer exists or is marked for deletion,
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 152e77acb7..f022027e65 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -1323,10 +1323,12 @@ void EditorPropertyObjectID::update_property() {
ObjectID id = get_edited_object()->get(get_edited_property());
if (id.is_valid()) {
edit->set_text(type + " ID: " + uitos(id));
+ edit->set_tooltip_text(type + " ID: " + uitos(id));
edit->set_disabled(false);
edit->set_icon(EditorNode::get_singleton()->get_class_icon(type));
} else {
edit->set_text(TTR("<empty>"));
+ edit->set_tooltip_text("");
edit->set_disabled(true);
edit->set_icon(Ref<Texture2D>());
}
@@ -1343,6 +1345,7 @@ EditorPropertyObjectID::EditorPropertyObjectID() {
edit = memnew(Button);
add_child(edit);
add_focusable(edit);
+ edit->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
edit->connect("pressed", callable_mp(this, &EditorPropertyObjectID::_edit_pressed));
}
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 21e15bc996..1c988840ac 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -453,6 +453,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
// Theme
EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_ENUM, "interface/theme/preset", "Default", "Default,Breeze Dark,Godot 2,Gray,Light,Solarized (Dark),Solarized (Light),Black (OLED),Custom")
+ EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/theme/enable_touchscreen_touch_area", DisplayServer::get_singleton()->is_touchscreen_available(), "")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "interface/theme/icon_and_font_color", 0, "Auto,Dark,Light")
EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "interface/theme/base_color", Color(0.2, 0.23, 0.31), "")
EDITOR_SETTING(Variant::COLOR, PROPERTY_HINT_NONE, "interface/theme/accent_color", Color(0.41, 0.61, 0.91), "")
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 5cae3ef3fd..d2c82ad013 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -394,6 +394,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
Color accent_color = EDITOR_GET("interface/theme/accent_color");
Color base_color = EDITOR_GET("interface/theme/base_color");
float contrast = EDITOR_GET("interface/theme/contrast");
+ bool enable_touchscreen_touch_area = EDITOR_GET("interface/theme/enable_touchscreen_touch_area");
bool draw_extra_borders = EDITOR_GET("interface/theme/draw_extra_borders");
float icon_saturation = EDITOR_GET("interface/theme/icon_saturation");
float relationship_line_opacity = EDITOR_GET("interface/theme/relationship_line_opacity");
@@ -1492,7 +1493,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// HScrollBar
Ref<Texture2D> empty_icon = memnew(ImageTexture);
- theme->set_stylebox("scroll", "HScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
+ if (enable_touchscreen_touch_area) {
+ theme->set_stylebox("scroll", "HScrollBar", make_line_stylebox(separator_color, 50));
+ } else {
+ theme->set_stylebox("scroll", "HScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
+ }
theme->set_stylebox("scroll_focus", "HScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
theme->set_stylebox("grabber", "HScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollGrabber"), SNAME("EditorIcons")), 6, 6, 6, 6, 1, 1, 1, 1));
theme->set_stylebox("grabber_highlight", "HScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollGrabberHl"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
@@ -1506,7 +1511,11 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_icon("decrement_pressed", "HScrollBar", empty_icon);
// VScrollBar
- theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
+ if (enable_touchscreen_touch_area) {
+ theme->set_stylebox("scroll", "VScrollBar", make_line_stylebox(separator_color, 50, 1, 1, true));
+ } else {
+ theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
+ }
theme->set_stylebox("scroll_focus", "VScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollBg"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
theme->set_stylebox("grabber", "VScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollGrabber"), SNAME("EditorIcons")), 6, 6, 6, 6, 1, 1, 1, 1));
theme->set_stylebox("grabber_highlight", "VScrollBar", make_stylebox(theme->get_icon(SNAME("GuiScrollGrabberHl"), SNAME("EditorIcons")), 5, 5, 5, 5, 1, 1, 1, 1));
diff --git a/editor/icons/UseBlendDisable.svg b/editor/icons/UseBlendDisable.svg
new file mode 100644
index 0000000000..449dc2d0c8
--- /dev/null
+++ b/editor/icons/UseBlendDisable.svg
@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-591 421.5h7v14h-7z" fill="#a3e595" opacity=".5"/><g fill="none" stroke="#a3e595" stroke-linecap="square" stroke-width="2"><path d="m-585 434.5v-12"/><path d="m-590 422.5v12"/><path d="m-581.5 422.5h-12"/></g></svg>
diff --git a/editor/icons/UseBlendEnable.svg b/editor/icons/UseBlendEnable.svg
new file mode 100644
index 0000000000..5567b2d8c6
--- /dev/null
+++ b/editor/icons/UseBlendEnable.svg
@@ -0,0 +1 @@
+<svg enable-background="new -595.5 420.5 16 16" height="16" viewBox="-595.5 420.5 16 16" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-587.5 423.244c-3.995 2.354-7 6.775-7 11.256v1h14v-1c0-4.48-3.005-8.901-7-11.256z" fill="#95c6e8" opacity=".5"/><g fill="none" stroke="#95c6e8" stroke-linecap="square" stroke-width="2"><path d="m-581.5 422.5c-6 0-12 6-12 12"/><path d="m-581.5 434.5c0-6-6-12-12-12"/></g></svg>
diff --git a/editor/import/resource_importer_scene.cpp b/editor/import/resource_importer_scene.cpp
index 102fa903b8..aa5f9ff29a 100644
--- a/editor/import/resource_importer_scene.cpp
+++ b/editor/import/resource_importer_scene.cpp
@@ -1865,6 +1865,7 @@ void ResourceImporterScene::get_import_options(const String &p_path, List<Import
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/import"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::FLOAT, "animation/fps", PROPERTY_HINT_RANGE, "1,120,1"), 30));
r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/trimming"), false));
+ r_options->push_back(ImportOption(PropertyInfo(Variant::BOOL, "animation/remove_immutable_tracks"), true));
r_options->push_back(ImportOption(PropertyInfo(Variant::STRING, "import_script/path", PROPERTY_HINT_FILE, script_ext_hint), ""));
r_options->push_back(ImportOption(PropertyInfo(Variant::DICTIONARY, "_subresources", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), Dictionary()));
diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp
index 37c2fd5f1e..fb450a41d3 100644
--- a/editor/input_event_configuration_dialog.cpp
+++ b/editor/input_event_configuration_dialog.cpp
@@ -201,7 +201,7 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Ref<InputEven
}
if (k.is_valid()) {
- k->set_pressed(false); // To avoid serialisation of 'pressed' property - doesn't matter for actions anyway.
+ k->set_pressed(false); // To avoid serialization of 'pressed' property - doesn't matter for actions anyway.
if (key_mode->get_selected_id() == KEYMODE_KEYCODE) {
k->set_physical_keycode(Key::NONE);
k->set_key_label(Key::NONE);
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index 30e06bfcf4..b33ad67f23 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -1105,9 +1105,24 @@ void AnimationPlayerEditor::_animation_duplicate() {
return;
}
+ int count = 2;
String new_name = current;
- while (player->has_animation(new_name)) {
- new_name = new_name + " (copy)";
+ PackedStringArray split = new_name.split("_");
+ int last_index = split.size() - 1;
+ if (last_index > 0 && split[last_index].is_valid_int() && split[last_index].to_int() >= 0) {
+ count = split[last_index].to_int();
+ split.remove_at(last_index);
+ new_name = String("_").join(split);
+ }
+ while (true) {
+ String attempt = new_name;
+ attempt += vformat("_%d", count);
+ if (player->has_animation(attempt)) {
+ count++;
+ continue;
+ }
+ new_name = attempt;
+ break;
}
if (new_name.contains("/")) {
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index f1b7ed73b8..36d1e54246 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -1414,7 +1414,7 @@ Transform3D Node3DEditorViewport::_compute_transform(TransformMode p_mode, const
// Recalculate orthogonalized scale without moving origin.
if (p_orthogonal) {
- s.basis = p_original_local.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1));
+ s.basis = p_original.basis.scaled_orthogonal(p_motion + Vector3(1, 1, 1));
// The scaled_orthogonal() does not require orthogonal Basis,
// but it may make a bit skew by precision problems.
s.basis.orthogonalize();
@@ -4611,7 +4611,9 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
// TRANSLATORS: Refers to changing the scale of a node in the 3D editor.
set_message(TTR("Scaling:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
- motion = _edit.original.basis.inverse().xform(motion);
+ if (local_coords) {
+ motion = _edit.original.basis.inverse().xform(motion);
+ }
List<Node *> &selection = editor_selection->get_selected_node_list();
for (Node *E : selection) {
@@ -4639,7 +4641,7 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
se->gizmo->set_subgizmo_transform(GE.key, new_xform);
}
} else {
- Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS);
+ Transform3D new_xform = _compute_transform(TRANSFORM_SCALE, se->original, se->original_local, motion, snap, local_coords, sp->get_rotation_edit_mode() != Node3D::ROTATION_EDIT_MODE_BASIS && _edit.plane != TRANSFORM_VIEW);
_transform_gizmo_apply(se->sp, new_xform, local_coords);
}
}
@@ -4712,7 +4714,9 @@ void Node3DEditorViewport::update_transform(Point2 p_mousepos, bool p_shift) {
// TRANSLATORS: Refers to changing the position of a node in the 3D editor.
set_message(TTR("Translating:") + " (" + String::num(motion_snapped.x, snap_step_decimals) + ", " +
String::num(motion_snapped.y, snap_step_decimals) + ", " + String::num(motion_snapped.z, snap_step_decimals) + ")");
- motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);
+ if (local_coords) {
+ motion = spatial_editor->get_gizmo_transform().basis.inverse().xform(motion);
+ }
List<Node *> &selection = editor_selection->get_selected_node_list();
for (Node *E : selection) {
diff --git a/editor/project_converter_3_to_4.cpp b/editor/project_converter_3_to_4.cpp
index f38c8ee39d..dfbb8e728b 100644
--- a/editor/project_converter_3_to_4.cpp
+++ b/editor/project_converter_3_to_4.cpp
@@ -1367,6 +1367,10 @@ static const char *project_settings_renames[][2] = {
{ "rendering/vram_compression/import_etc2", "rendering/textures/vram_compression/import_etc2" },
{ "rendering/vram_compression/import_pvrtc", "rendering/textures/vram_compression/import_pvrtc" },
{ "rendering/vram_compression/import_s3tc", "rendering/textures/vram_compression/import_s3tc" },
+ { "window/size/width", "window/size/viewport_width" },
+ { "window/size/height", "window/size/viewport_height" },
+ { "window/size/test_width", "window/size/window_width_override" },
+ { "window/size/test_height", "window/size/window_height_override" },
{ nullptr, nullptr },
};
diff --git a/misc/scripts/codespell.sh b/misc/scripts/codespell.sh
index 34885016b6..44084b3348 100755
--- a/misc/scripts/codespell.sh
+++ b/misc/scripts/codespell.sh
@@ -1,5 +1,5 @@
#!/bin/sh
-SKIP_LIST="./.*,./bin,.platform/web/node_modules,./platform/android/java/lib/src/com,./thirdparty,*.gen.*,*.po,*.pot,*.rc,package-lock.json,./core/string/locales.h,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./misc/dist/linux/org.godotengine.Godot.desktop,./misc/scripts/codespell.sh"
+SKIP_LIST="./.*,./bin,./editor/project_converter_3_to_4.cpp,./platform/web/node_modules,./platform/android/java/lib/src/com,./thirdparty,*.gen.*,*.po,*.pot,*.rc,package-lock.json,./core/string/locales.h,./AUTHORS.md,./COPYRIGHT.txt,./DONORS.md,./misc/dist/linux/org.godotengine.Godot.desktop,./misc/scripts/codespell.sh"
IGNORE_LIST="alo,ba,childs,complies,curvelinear,doubleclick,expct,fave,findn,gird,gud,inout,lod,nd,numer,ois,readded,ro,sav,statics,te,varius,varn,wan"
codespell -w -q 3 -S "${SKIP_LIST}" -L "${IGNORE_LIST}" --builtin "clear,rare,en-GB_to_en-US"
diff --git a/modules/enet/doc_classes/ENetConnection.xml b/modules/enet/doc_classes/ENetConnection.xml
index 8c84fe87d7..dc832976d9 100644
--- a/modules/enet/doc_classes/ENetConnection.xml
+++ b/modules/enet/doc_classes/ENetConnection.xml
@@ -84,19 +84,17 @@
</method>
<method name="dtls_client_setup">
<return type="int" enum="Error" />
- <param index="0" name="certificate" type="X509Certificate" />
- <param index="1" name="hostname" type="String" />
- <param index="2" name="verify" type="bool" default="true" />
+ <param index="0" name="hostname" type="String" />
+ <param index="1" name="client_options" type="TLSOptions" default="null" />
<description>
- Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS with [code]certificate[/code] and [code]hostname[/code] verification. Verification can be optionally turned off via the [code]verify[/code] parameter.
+ Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet clients. Call this before [method connect_to_host] to have ENet connect using DTLS validating the server certificate against [code]hostname[/code]. You can pass the optional [param client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
</description>
</method>
<method name="dtls_server_setup">
<return type="int" enum="Error" />
- <param index="0" name="key" type="CryptoKey" />
- <param index="1" name="certificate" type="X509Certificate" />
+ <param index="0" name="server_options" type="TLSOptions" />
<description>
- Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS.
+ Configure this ENetHost to use the custom Godot extension allowing DTLS encryption for ENet servers. Call this right after [method create_host_bound] to have ENet expect peers to connect using DTLS. See [method TLSOptions.server].
</description>
</method>
<method name="flush">
diff --git a/modules/enet/enet_connection.cpp b/modules/enet/enet_connection.cpp
index d16e7d7c4a..804263186f 100644
--- a/modules/enet/enet_connection.cpp
+++ b/modules/enet/enet_connection.cpp
@@ -273,10 +273,11 @@ TypedArray<ENetPacketPeer> ENetConnection::_get_peers() {
return out;
}
-Error ENetConnection::dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
+Error ENetConnection::dtls_server_setup(const Ref<TLSOptions> &p_options) {
#ifdef GODOT_ENET
ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
- return enet_host_dtls_server_setup(host, p_key.ptr(), p_cert.ptr()) ? FAILED : OK;
+ ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
+ return enet_host_dtls_server_setup(host, const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
#else
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
#endif
@@ -291,10 +292,11 @@ void ENetConnection::refuse_new_connections(bool p_refuse) {
#endif
}
-Error ENetConnection::dtls_client_setup(Ref<X509Certificate> p_cert, const String &p_hostname, bool p_verify) {
+Error ENetConnection::dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options) {
#ifdef GODOT_ENET
ERR_FAIL_COND_V_MSG(!host, ERR_UNCONFIGURED, "The ENetConnection instance isn't currently active.");
- return enet_host_dtls_client_setup(host, p_cert.ptr(), p_verify, p_hostname.utf8().get_data()) ? FAILED : OK;
+ ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER);
+ return enet_host_dtls_client_setup(host, p_hostname.utf8().get_data(), const_cast<TLSOptions *>(p_options.ptr())) ? FAILED : OK;
#else
ERR_FAIL_V_MSG(ERR_UNAVAILABLE, "ENet DTLS support not available in this build.");
#endif
@@ -351,8 +353,8 @@ void ENetConnection::_bind_methods() {
ClassDB::bind_method(D_METHOD("channel_limit", "limit"), &ENetConnection::channel_limit);
ClassDB::bind_method(D_METHOD("broadcast", "channel", "packet", "flags"), &ENetConnection::_broadcast);
ClassDB::bind_method(D_METHOD("compress", "mode"), &ENetConnection::compress);
- ClassDB::bind_method(D_METHOD("dtls_server_setup", "key", "certificate"), &ENetConnection::dtls_server_setup);
- ClassDB::bind_method(D_METHOD("dtls_client_setup", "certificate", "hostname", "verify"), &ENetConnection::dtls_client_setup, DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("dtls_server_setup", "server_options"), &ENetConnection::dtls_server_setup);
+ ClassDB::bind_method(D_METHOD("dtls_client_setup", "hostname", "client_options"), &ENetConnection::dtls_client_setup, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("refuse_new_connections", "refuse"), &ENetConnection::refuse_new_connections);
ClassDB::bind_method(D_METHOD("pop_statistic", "statistic"), &ENetConnection::pop_statistic);
ClassDB::bind_method(D_METHOD("get_max_channels"), &ENetConnection::get_max_channels);
diff --git a/modules/enet/enet_connection.h b/modules/enet/enet_connection.h
index 9e444911cc..481afc48bb 100644
--- a/modules/enet/enet_connection.h
+++ b/modules/enet/enet_connection.h
@@ -128,8 +128,8 @@ public:
int get_local_port() const;
// Godot additions
- Error dtls_server_setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert);
- Error dtls_client_setup(Ref<X509Certificate> p_cert, const String &p_hostname, bool p_verify = true);
+ Error dtls_server_setup(const Ref<TLSOptions> &p_options);
+ Error dtls_client_setup(const String &p_hostname, const Ref<TLSOptions> &p_options);
void refuse_new_connections(bool p_refuse);
ENetConnection() {}
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index 50ea0dd37a..93a20ab1f8 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -363,7 +363,7 @@ Error ENetMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size
#ifdef DEBUG_ENABLED
if ((packet_flags & ENET_PACKET_FLAG_UNRELIABLE_FRAGMENT) && p_buffer_size > ENET_HOST_DEFAULT_MTU) {
- WARN_PRINT_ONCE(vformat("Sending %d bytes unrealiably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU));
+ WARN_PRINT_ONCE(vformat("Sending %d bytes unreliably which is above the MTU (%d), this will result in higher packet loss", p_buffer_size, ENET_HOST_DEFAULT_MTU));
}
#endif
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 5bed1b9da3..7f9d4ae253 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -316,12 +316,21 @@
<return type="void" />
<param index="0" name="names" type="String" />
<description>
- Export a [String] or integer property as an enumerated list of options. If the property is an integer field, then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon.
+ Export an [int] or [String] property as an enumerated list of options. If the property is an [int], then the index of the value is stored, in the same order the values are provided. You can add specific identifiers for allowed values using a colon. If the property is a [String], then the value is stored.
See also [constant PROPERTY_HINT_ENUM].
[codeblock]
- @export_enum("Rebecca", "Mary", "Leah") var character_name: String
@export_enum("Warrior", "Magician", "Thief") var character_class: int
@export_enum("Slow:30", "Average:60", "Very Fast:200") var character_speed: int
+ @export_enum("Rebecca", "Mary", "Leah") var character_name: String
+ [/codeblock]
+ If you want to set an initial value, you must specify it explicitly:
+ [codeblock]
+ @export_enum("Rebecca", "Mary", "Leah") var character_name: String = "Rebecca"
+ [/codeblock]
+ If you want to use named GDScript enums, then use [annotation @export] instead:
+ [codeblock]
+ enum CharacterName {REBECCA, MARY, LEAH}
+ @export var character_name: CharacterName
[/codeblock]
</description>
</annotation>
@@ -507,7 +516,7 @@
<param index="2" name="step" type="float" default="1.0" />
<param index="3" name="extra_hints" type="String" default="&quot;&quot;" />
<description>
- Export a numeric property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting.
+ Export an [int] or [float] property as a range value. The range must be defined by [param min] and [param max], as well as an optional [param step] and a variety of extra hints. The [param step] defaults to [code]1[/code] for integer properties. For floating-point numbers this value depends on your [code]EditorSettings.interface/inspector/default_float_step[/code] setting.
If hints [code]"or_greater"[/code] and [code]"or_less"[/code] are provided, the editor widget will not cap the value at range boundaries. The [code]"exp"[/code] hint will make the edited values on range to change exponentially. The [code]"hide_slider"[/code] hint will hide the slider element of the editor widget.
Hints also allow to indicate the units for the edited value. Using [code]"radians"[/code] you can specify that the actual value is in radians, but should be displayed in degrees in the Inspector dock. [code]"degrees"[/code] allows to add a degree sign as a unit suffix. Finally, a custom suffix can be provided using [code]"suffix:unit"[/code], where "unit" can be any string.
See also [constant PROPERTY_HINT_RANGE].
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index 23be913a24..5883ec863d 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -307,7 +307,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_number = true;
}
- if (!in_word && (is_ascii_char(str[j]) || is_underscore(str[j])) && !in_number) {
+ if (!in_word && is_unicode_identifier_start(str[j]) && !in_number) {
in_word = true;
}
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 4fc3929bbd..d9b8a540c0 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -98,7 +98,7 @@ Variant GDScriptNativeClass::callp(const StringName &p_method, const Variant **p
return Object::callp(p_method, p_args, p_argcount, r_error);
}
MethodBind *method = ClassDB::get_method(name, p_method);
- if (method) {
+ if (method && method->is_static()) {
// Native static method.
return method->call(nullptr, p_args, p_argcount, r_error);
}
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index f6385dd132..8dd65a700a 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -160,19 +160,6 @@ static GDScriptParser::DataType make_builtin_meta_type(Variant::Type p_type) {
return type;
}
-static StringName enum_get_value_name(const GDScriptParser::DataType p_type, int64_t p_val) {
- // Check that an enum has a given value, not key.
- // Make sure that implicit conversion to int64_t is sensible before calling!
- HashMap<StringName, int64_t>::ConstIterator i = p_type.enum_values.begin();
- while (i) {
- if (i->value == p_val) {
- return i->key;
- }
- ++i;
- }
- return StringName();
-}
-
bool GDScriptAnalyzer::has_member_name_conflict_in_script_class(const StringName &p_member_name, const GDScriptParser::ClassNode *p_class, const GDScriptParser::Node *p_member) {
if (p_class->members_indices.has(p_member_name)) {
int index = p_class->members_indices[p_member_name];
@@ -1062,7 +1049,7 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
resolve_class_body(base_class, p_class);
}
- // Do functions and properties now.
+ // Do functions, properties, and groups now.
for (int i = 0; i < p_class->members.size(); i++) {
GDScriptParser::ClassNode::Member member = p_class->members[i];
if (member.type == GDScriptParser::ClassNode::Member::FUNCTION) {
@@ -1102,6 +1089,10 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class, co
resolve_function_body(member.variable->setter);
}
}
+ } else if (member.type == GDScriptParser::ClassNode::Member::GROUP) {
+ // Apply annotation (`@export_{category,group,subgroup}`).
+ resolve_annotation(member.annotation);
+ member.annotation->apply(parser, nullptr);
}
}
@@ -1596,6 +1587,9 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
}
}
+ if (has_specified_type && p_assignable->initializer->is_constant) {
+ update_const_expression_builtin_type(p_assignable->initializer, specified_type, "assign");
+ }
GDScriptParser::DataType initializer_type = p_assignable->initializer->get_datatype();
if (p_assignable->infer_datatype) {
@@ -1630,7 +1624,7 @@ void GDScriptAnalyzer::resolve_assignable(GDScriptParser::AssignableNode *p_assi
downgrade_node_type_source(p_assignable->initializer);
}
} else if (!is_type_compatible(specified_type, initializer_type, true, p_assignable->initializer)) {
- if (!is_constant && is_type_compatible(initializer_type, specified_type, true, p_assignable->initializer)) {
+ if (!is_constant && is_type_compatible(initializer_type, specified_type)) {
mark_node_unsafe(p_assignable->initializer);
p_assignable->use_conversion_assign = true;
} else {
@@ -1901,11 +1895,22 @@ void GDScriptAnalyzer::resolve_match_pattern(GDScriptParser::PatternNode *p_matc
break;
case GDScriptParser::PatternNode::PT_EXPRESSION:
if (p_match_pattern->expression) {
- reduce_expression(p_match_pattern->expression);
- if (!p_match_pattern->expression->is_constant) {
- push_error(R"(Expression in match pattern must be a constant.)", p_match_pattern->expression);
+ GDScriptParser::ExpressionNode *expr = p_match_pattern->expression;
+ reduce_expression(expr);
+ result = expr->get_datatype();
+ if (!expr->is_constant) {
+ while (expr && expr->type == GDScriptParser::Node::SUBSCRIPT) {
+ GDScriptParser::SubscriptNode *sub = static_cast<GDScriptParser::SubscriptNode *>(expr);
+ if (!sub->is_attribute) {
+ expr = nullptr;
+ } else {
+ expr = sub->base;
+ }
+ }
+ if (!expr || expr->type != GDScriptParser::Node::IDENTIFIER) {
+ push_error(R"(Expression in match pattern must be a constant expression, an identifier, or an attribute access ("A.B").)", expr);
+ }
}
- result = p_match_pattern->expression->get_datatype();
}
break;
case GDScriptParser::PatternNode::PT_BIND:
@@ -1958,11 +1963,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
GDScriptParser::DataType result;
GDScriptParser::DataType expected_type;
- bool has_expected_type = false;
-
- if (parser->current_function != nullptr) {
+ bool has_expected_type = parser->current_function != nullptr;
+ if (has_expected_type) {
expected_type = parser->current_function->get_datatype();
- has_expected_type = true;
}
if (p_return->return_value != nullptr) {
@@ -1976,6 +1979,9 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
if (has_expected_type && expected_type.is_hard_type() && expected_type.kind == GDScriptParser::DataType::BUILTIN && expected_type.builtin_type == Variant::NIL) {
push_error("A void function cannot return a value.", p_return);
}
+ if (has_expected_type && expected_type.is_hard_type() && p_return->return_value->is_constant) {
+ update_const_expression_builtin_type(p_return->return_value, expected_type, "return");
+ }
result = p_return->return_value->get_datatype();
} else {
// Return type is null by default.
@@ -1985,24 +1991,21 @@ void GDScriptAnalyzer::resolve_return(GDScriptParser::ReturnNode *p_return) {
result.is_constant = true;
}
- if (has_expected_type) {
- expected_type.is_meta_type = false;
- if (expected_type.is_hard_type()) {
- if (!is_type_compatible(expected_type, result)) {
- // Try other way. Okay but not safe.
- if (!is_type_compatible(result, expected_type)) {
- push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), expected_type.to_string()), p_return);
- } else {
- // TODO: Add warning.
- mark_node_unsafe(p_return);
- }
+ if (has_expected_type && !expected_type.is_variant()) {
+ if (result.is_variant() || !result.is_hard_type()) {
+ mark_node_unsafe(p_return);
+ if (!is_type_compatible(expected_type, result, true, p_return)) {
+ downgrade_node_type_source(p_return);
+ }
+ } else if (!is_type_compatible(expected_type, result, true, p_return)) {
+ mark_node_unsafe(p_return);
+ if (!is_type_compatible(result, expected_type)) {
+ push_error(vformat(R"(Cannot return value of type "%s" because the function return type is "%s".)", result.to_string(), expected_type.to_string()), p_return);
+ }
#ifdef DEBUG_ENABLED
- } else if (expected_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
- parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
- } else if (result.is_variant()) {
- mark_node_unsafe(p_return);
+ } else if (expected_type.builtin_type == Variant::INT && result.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_return, GDScriptWarning::NARROWING_CONVERSION);
#endif
- }
}
}
@@ -2116,6 +2119,68 @@ void GDScriptAnalyzer::reduce_array(GDScriptParser::ArrayNode *p_array) {
p_array->set_datatype(arr_type);
}
+#ifdef DEBUG_ENABLED
+static bool enum_has_value(const GDScriptParser::DataType p_type, int64_t p_value) {
+ for (const KeyValue<StringName, int64_t> &E : p_type.enum_values) {
+ if (E.value == p_value) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void GDScriptAnalyzer::update_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast) {
+ if (p_expression->get_datatype() == p_type) {
+ return;
+ }
+ if (p_type.kind != GDScriptParser::DataType::BUILTIN && p_type.kind != GDScriptParser::DataType::ENUM) {
+ return;
+ }
+
+ GDScriptParser::DataType expression_type = p_expression->get_datatype();
+ bool is_enum_cast = p_is_cast && p_type.kind == GDScriptParser::DataType::ENUM && p_type.is_meta_type == false && expression_type.builtin_type == Variant::INT;
+ if (!is_enum_cast && !is_type_compatible(p_type, expression_type, true, p_expression)) {
+ push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, expression_type.to_string(), p_type.to_string()), p_expression);
+ return;
+ }
+
+ GDScriptParser::DataType value_type = type_from_variant(p_expression->reduced_value, p_expression);
+ if (expression_type.is_variant() && !is_enum_cast && !is_type_compatible(p_type, value_type, true, p_expression)) {
+ push_error(vformat(R"(Cannot %s a value of type "%s" as "%s".)", p_usage, value_type.to_string(), p_type.to_string()), p_expression);
+ return;
+ }
+
+#ifdef DEBUG_ENABLED
+ if (p_type.kind == GDScriptParser::DataType::ENUM && value_type.builtin_type == Variant::INT && !enum_has_value(p_type, p_expression->reduced_value)) {
+ parser->push_warning(p_expression, GDScriptWarning::INT_AS_ENUM_WITHOUT_MATCH, p_usage, p_expression->reduced_value.stringify(), p_type.to_string());
+ }
+#endif
+
+ if (value_type.builtin_type == p_type.builtin_type) {
+ p_expression->set_datatype(p_type);
+ return;
+ }
+
+ Variant converted_to;
+ const Variant *converted_from = &p_expression->reduced_value;
+ Callable::CallError call_error;
+ Variant::construct(p_type.builtin_type, converted_to, &converted_from, 1, call_error);
+ if (call_error.error) {
+ push_error(vformat(R"(Failed to convert a value of type "%s" to "%s".)", value_type.to_string(), p_type.to_string()), p_expression);
+ return;
+ }
+
+#ifdef DEBUG_ENABLED
+ if (p_type.builtin_type == Variant::INT && value_type.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_expression, GDScriptWarning::NARROWING_CONVERSION);
+ }
+#endif
+
+ p_expression->reduced_value = converted_to;
+ p_expression->set_datatype(p_type);
+}
+
// When an array literal is stored (or passed as function argument) to a typed context, we then assume the array is typed.
// This function determines which type is that (if any).
void GDScriptAnalyzer::update_array_literal_element_type(const GDScriptParser::DataType &p_base_type, GDScriptParser::ArrayNode *p_array_literal) {
@@ -2182,6 +2247,10 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
update_array_literal_element_type(assignee_type, static_cast<GDScriptParser::ArrayNode *>(p_assignment->assigned_value));
}
+ if (p_assignment->operation == GDScriptParser::AssignmentNode::OP_NONE && assignee_type.is_hard_type() && p_assignment->assigned_value->is_constant) {
+ update_const_expression_builtin_type(p_assignment->assigned_value, assignee_type, "assign");
+ }
+
GDScriptParser::DataType assigned_value_type = p_assignment->assigned_value->get_datatype();
bool assignee_is_variant = assignee_type.is_variant();
@@ -2242,7 +2311,7 @@ void GDScriptAnalyzer::reduce_assignment(GDScriptParser::AssignmentNode *p_assig
// non-variant assignee and incompatible result
mark_node_unsafe(p_assignment);
if (assignee_is_hard) {
- if (is_type_compatible(op_type, assignee_type, true, p_assignment->assigned_value)) {
+ if (is_type_compatible(op_type, assignee_type)) {
// hard non-variant assignee and maybe compatible result
p_assignment->use_conversion_assign = true;
} else {
@@ -2358,7 +2427,7 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
GDScriptParser::DataType test_type = right_type;
test_type.is_meta_type = false;
- if (!is_type_compatible(test_type, left_type, false)) {
+ if (!is_type_compatible(test_type, left_type)) {
push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)"), p_binary_op->left_operand);
p_binary_op->reduced_value = false;
} else {
@@ -2375,43 +2444,41 @@ void GDScriptAnalyzer::reduce_binary_op(GDScriptParser::BinaryOpNode *p_binary_o
GDScriptParser::DataType result;
- if (left_type.is_variant() || right_type.is_variant()) {
- // Cannot infer type because one operand can be anything.
- result.kind = GDScriptParser::DataType::VARIANT;
- mark_node_unsafe(p_binary_op);
- } else {
- if (p_binary_op->variant_op < Variant::OP_MAX) {
- bool valid = false;
- result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
+ if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
+ GDScriptParser::DataType test_type = right_type;
+ test_type.is_meta_type = false;
- if (!valid) {
- push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
- }
- } else {
- if (p_binary_op->operation == GDScriptParser::BinaryOpNode::OP_TYPE_TEST) {
- GDScriptParser::DataType test_type = right_type;
- test_type.is_meta_type = false;
-
- if (!is_type_compatible(test_type, left_type, false)) {
- // Test reverse as well to consider for subtypes.
- if (!is_type_compatible(left_type, test_type, false)) {
- if (left_type.is_hard_type()) {
- push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
- } else {
- // TODO: Warning.
- mark_node_unsafe(p_binary_op);
- }
- }
- }
-
- // "is" operator is always a boolean anyway.
- result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
- result.kind = GDScriptParser::DataType::BUILTIN;
- result.builtin_type = Variant::BOOL;
+ if (!is_type_compatible(test_type, left_type) && !is_type_compatible(left_type, test_type)) {
+ if (left_type.is_hard_type()) {
+ push_error(vformat(R"(Expression is of type "%s" so it can't be of type "%s".)", left_type.to_string(), test_type.to_string()), p_binary_op->left_operand);
} else {
- ERR_PRINT("Parser bug: unknown binary operation.");
+ // TODO: Warning.
+ mark_node_unsafe(p_binary_op);
}
}
+
+ // "is" operator is always a boolean anyway.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if ((p_binary_op->variant_op == Variant::OP_EQUAL || p_binary_op->variant_op == Variant::OP_NOT_EQUAL) &&
+ ((left_type.kind == GDScriptParser::DataType::BUILTIN && left_type.builtin_type == Variant::NIL) || (right_type.kind == GDScriptParser::DataType::BUILTIN && right_type.builtin_type == Variant::NIL))) {
+ // "==" and "!=" operators always return a boolean when comparing to null.
+ result.type_source = GDScriptParser::DataType::ANNOTATED_EXPLICIT;
+ result.kind = GDScriptParser::DataType::BUILTIN;
+ result.builtin_type = Variant::BOOL;
+ } else if (left_type.is_variant() || right_type.is_variant()) {
+ // Cannot infer type because one operand can be anything.
+ result.kind = GDScriptParser::DataType::VARIANT;
+ mark_node_unsafe(p_binary_op);
+ } else if (p_binary_op->variant_op < Variant::OP_MAX) {
+ bool valid = false;
+ result = get_operation_type(p_binary_op->variant_op, left_type, right_type, valid, p_binary_op);
+ if (!valid) {
+ push_error(vformat(R"(Invalid operands "%s" and "%s" for "%s" operator.)", left_type.to_string(), right_type.to_string(), Variant::get_operator_name(p_binary_op->variant_op)), p_binary_op);
+ }
+ } else {
+ ERR_PRINT("Parser bug: unknown binary operation.");
}
p_binary_op->set_datatype(result);
@@ -2557,6 +2624,11 @@ void GDScriptAnalyzer::reduce_call(GDScriptParser::CallNode *p_call, bool p_is_a
}
if (types_match) {
+ for (int i = 0; i < p_call->arguments.size(); i++) {
+ if (p_call->arguments[i]->is_constant) {
+ update_const_expression_builtin_type(p_call->arguments[i], type_from_property(info.arguments[i], true), "pass");
+ }
+ }
match = true;
call_type = type_from_property(info.return_val);
break;
@@ -2853,67 +2925,39 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
}
p_cast->set_datatype(cast_type);
+ if (p_cast->operand->is_constant) {
+ update_const_expression_builtin_type(p_cast->operand, cast_type, "cast", true);
+ if (cast_type.is_variant() || p_cast->operand->get_datatype() == cast_type) {
+ p_cast->is_constant = true;
+ p_cast->reduced_value = p_cast->operand->reduced_value;
+ }
+ }
if (!cast_type.is_variant()) {
GDScriptParser::DataType op_type = p_cast->operand->get_datatype();
- if (!op_type.is_variant()) {
+ if (op_type.is_variant() || !op_type.is_hard_type()) {
+ mark_node_unsafe(p_cast);
+#ifdef DEBUG_ENABLED
+ if (op_type.is_variant() && !op_type.is_hard_type()) {
+ parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
+ }
+#endif
+ } else {
bool valid = false;
- bool more_informative_error = false;
- if (op_type.kind == GDScriptParser::DataType::ENUM && cast_type.kind == GDScriptParser::DataType::ENUM) {
- // Enum casts are compatible when value from operand exists in target enum
- if (p_cast->operand->is_constant && p_cast->operand->reduced) {
- if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
- valid = true;
- } else {
- valid = false;
- more_informative_error = true;
- push_error(vformat(R"(Invalid cast. Enum "%s" does not have value corresponding to "%s.%s" (%d).)",
- cast_type.to_string(), op_type.enum_type,
- enum_get_value_name(op_type, p_cast->operand->reduced_value), // Can never be null
- p_cast->operand->reduced_value.operator uint64_t()),
- p_cast->cast_type);
- }
- } else {
- // Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
- mark_node_unsafe(p_cast);
- valid = true;
- }
- } else if (op_type.kind == GDScriptParser::DataType::BUILTIN && op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
- // Int assignment to enum not valid when exact int assigned is known but is not an enum value
- if (p_cast->operand->is_constant && p_cast->operand->reduced) {
- if (enum_get_value_name(cast_type, p_cast->operand->reduced_value) != StringName()) {
- valid = true;
- } else {
- valid = false;
- more_informative_error = true;
- push_error(vformat(R"(Invalid cast. Enum "%s" does not have enum value %d.)", cast_type.to_string(), p_cast->operand->reduced_value.operator uint64_t()), p_cast->cast_type);
- }
- } else {
- // Can't statically tell whether int has a corresponding enum value. Valid but dangerous!
- mark_node_unsafe(p_cast);
- valid = true;
- }
+ if (op_type.builtin_type == Variant::INT && cast_type.kind == GDScriptParser::DataType::ENUM) {
+ mark_node_unsafe(p_cast);
+ 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);
}
- if (!valid && !more_informative_error) {
+ if (!valid) {
push_error(vformat(R"(Invalid cast. Cannot convert from "%s" to "%s".)", op_type.to_string(), cast_type.to_string()), p_cast->cast_type);
}
}
- } else {
- mark_node_unsafe(p_cast);
- }
-#ifdef DEBUG_ENABLED
- if (p_cast->operand->get_datatype().is_variant()) {
- parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
- mark_node_unsafe(p_cast);
}
-#endif
-
- // TODO: Perform cast on constants.
}
void GDScriptAnalyzer::reduce_dictionary(GDScriptParser::DictionaryNode *p_dictionary) {
@@ -4212,26 +4256,22 @@ bool GDScriptAnalyzer::function_signature_from_info(const MethodInfo &p_info, GD
return true;
}
-bool GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) {
+void GDScriptAnalyzer::validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call) {
List<GDScriptParser::DataType> arg_types;
for (const PropertyInfo &E : p_method.arguments) {
arg_types.push_back(type_from_property(E, true));
}
- return validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call);
+ validate_call_arg(arg_types, p_method.default_arguments.size(), (p_method.flags & METHOD_FLAG_VARARG) != 0, p_call);
}
-bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) {
- bool valid = true;
-
+void GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call) {
if (p_call->arguments.size() < p_par_types.size() - p_default_args_count) {
push_error(vformat(R"*(Too few arguments for "%s()" call. Expected at least %d but received %d.)*", p_call->function_name, p_par_types.size() - p_default_args_count, p_call->arguments.size()), p_call);
- valid = false;
}
if (!p_is_vararg && p_call->arguments.size() > p_par_types.size()) {
push_error(vformat(R"*(Too many arguments for "%s()" call. Expected at most %d but received %d.)*", p_call->function_name, p_par_types.size(), p_call->arguments.size()), p_call->arguments[p_par_types.size()]);
- valid = false;
}
for (int i = 0; i < p_call->arguments.size(); i++) {
@@ -4240,9 +4280,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
break;
}
GDScriptParser::DataType par_type = p_par_types[i];
+
+ if (par_type.is_hard_type() && p_call->arguments[i]->is_constant) {
+ update_const_expression_builtin_type(p_call->arguments[i], par_type, "pass");
+ }
GDScriptParser::DataType arg_type = p_call->arguments[i]->get_datatype();
- if (arg_type.is_variant()) {
+ if ((arg_type.is_variant() || !arg_type.is_hard_type()) && !(par_type.is_hard_type() && par_type.is_variant())) {
// Argument can be anything, so this is unsafe.
mark_node_unsafe(p_call->arguments[i]);
} else if (par_type.is_hard_type() && !is_type_compatible(par_type, arg_type, true)) {
@@ -4252,17 +4296,13 @@ bool GDScriptAnalyzer::validate_call_arg(const List<GDScriptParser::DataType> &p
push_error(vformat(R"*(Invalid argument for "%s()" function: argument %d should be "%s" but is "%s".)*",
p_call->function_name, i + 1, par_type.to_string(), arg_type.to_string()),
p_call->arguments[i]);
- valid = false;
}
#ifdef DEBUG_ENABLED
- } else {
- if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
- parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
- }
+ } else if (par_type.kind == GDScriptParser::DataType::BUILTIN && par_type.builtin_type == Variant::INT && arg_type.kind == GDScriptParser::DataType::BUILTIN && arg_type.builtin_type == Variant::FLOAT) {
+ parser->push_warning(p_call, GDScriptWarning::NARROWING_CONVERSION, p_call->function_name);
#endif
}
}
- return valid;
}
#ifdef DEBUG_ENABLED
@@ -4414,7 +4454,7 @@ bool GDScriptAnalyzer::is_type_compatible(const GDScriptParser::DataType &p_targ
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);
+ parser->push_warning(p_source_node, GDScriptWarning::INT_AS_ENUM_WITHOUT_CAST);
}
#endif
return true;
diff --git a/modules/gdscript/gdscript_analyzer.h b/modules/gdscript/gdscript_analyzer.h
index 5397be33f0..cd2c4c6569 100644
--- a/modules/gdscript/gdscript_analyzer.h
+++ b/modules/gdscript/gdscript_analyzer.h
@@ -112,10 +112,11 @@ class GDScriptAnalyzer {
GDScriptParser::DataType make_global_class_meta_type(const StringName &p_class_name, const GDScriptParser::Node *p_source);
bool get_function_signature(GDScriptParser::Node *p_source, bool p_is_constructor, GDScriptParser::DataType base_type, const StringName &p_function, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
bool function_signature_from_info(const MethodInfo &p_info, GDScriptParser::DataType &r_return_type, List<GDScriptParser::DataType> &r_par_types, int &r_default_arg_count, bool &r_static, bool &r_vararg);
- bool validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
- bool validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
+ void validate_call_arg(const List<GDScriptParser::DataType> &p_par_types, int p_default_args_count, bool p_is_vararg, const GDScriptParser::CallNode *p_call);
+ void validate_call_arg(const MethodInfo &p_method, const GDScriptParser::CallNode *p_call);
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_const_expression_builtin_type(GDScriptParser::ExpressionNode *p_expression, const GDScriptParser::DataType &p_type, const char *p_usage, bool p_is_cast = false);
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 GDScriptParser::Node *p_source_node = nullptr);
void push_error(const String &p_message, const GDScriptParser::Node *p_origin = nullptr);
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index 1a744d59ba..06c66b155f 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -122,7 +122,7 @@ GDScriptParser::GDScriptParser() {
register_annotation(MethodInfo("@onready"), AnnotationInfo::VARIABLE, &GDScriptParser::onready_annotation);
// Export annotations.
register_annotation(MethodInfo("@export"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_NONE, Variant::NIL>);
- register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::INT>, varray(), true);
+ register_annotation(MethodInfo("@export_enum", PropertyInfo(Variant::STRING, "names")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_ENUM, Variant::NIL>, varray(), true);
register_annotation(MethodInfo("@export_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_FILE, Variant::STRING>, varray(""), true);
register_annotation(MethodInfo("@export_dir"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_DIR, Variant::STRING>);
register_annotation(MethodInfo("@export_global_file", PropertyInfo(Variant::STRING, "filter")), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations<PROPERTY_HINT_GLOBAL_FILE, Variant::STRING>, varray(""), true);
@@ -840,14 +840,19 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::ANNOTATION: {
advance();
- // Check for class-level annotations.
+ // Check for standalone and class-level annotations.
AnnotationNode *annotation = parse_annotation(AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
if (annotation != nullptr) {
if (annotation->applies_to(AnnotationInfo::STANDALONE)) {
if (previous.type != GDScriptTokenizer::Token::NEWLINE) {
push_error(R"(Expected newline after a standalone annotation.)");
}
- head->annotations.push_back(annotation);
+ if (annotation->name == "@export_category" || annotation->name == "@export_group" || annotation->name == "@export_subgroup") {
+ current_class->add_member_group(annotation);
+ } else {
+ // For potential non-group standalone annotations.
+ push_error(R"(Unexpected standalone annotation in class body.)");
+ }
} else {
annotation_stack.push_back(annotation);
}
@@ -1904,7 +1909,6 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
#ifdef DEBUG_ENABLED
bool all_have_return = true;
bool have_wildcard = false;
- bool have_wildcard_without_continue = false;
#endif
while (!check(GDScriptTokenizer::Token::DEDENT) && !is_at_end()) {
@@ -1915,19 +1919,12 @@ GDScriptParser::MatchNode *GDScriptParser::parse_match() {
}
#ifdef DEBUG_ENABLED
- if (have_wildcard_without_continue && !branch->patterns.is_empty()) {
+ if (have_wildcard && !branch->patterns.is_empty()) {
push_warning(branch->patterns[0], GDScriptWarning::UNREACHABLE_PATTERN);
}
- if (branch->has_wildcard) {
- have_wildcard = true;
- if (!branch->block->has_continue) {
- have_wildcard_without_continue = true;
- }
- }
- if (!branch->block->has_return) {
- all_have_return = false;
- }
+ have_wildcard = have_wildcard || branch->has_wildcard;
+ all_have_return = all_have_return && branch->block->has_return;
#endif
match->branches.push_back(branch);
}
@@ -3665,6 +3662,10 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
String hint_string;
for (int i = 0; i < p_annotation->resolved_arguments.size(); i++) {
+ if (p_annotation->name != SNAME("@export_placeholder") && String(p_annotation->resolved_arguments[i]).contains(",")) {
+ push_error(vformat(R"(Argument %d of annotation "%s" contains a comma. Use separate arguments instead.)", i + 1, p_annotation->name), p_annotation->arguments[i]);
+ return false;
+ }
if (i > 0) {
hint_string += ",";
}
@@ -3809,6 +3810,24 @@ bool GDScriptParser::export_annotations(const AnnotationNode *p_annotation, Node
variable->export_info.hint_string = hint_prefix + ":" + variable->export_info.hint_string;
variable->export_info.type = Variant::ARRAY;
}
+ } else if (p_annotation->name == SNAME("@export_enum")) {
+ Variant::Type enum_type = Variant::INT;
+
+ if (export_type.kind == DataType::BUILTIN && export_type.builtin_type == Variant::STRING) {
+ enum_type = Variant::STRING;
+ } else if (export_type.is_variant() && variable->initializer != nullptr) {
+ DataType initializer_type = variable->initializer->get_datatype();
+ if (initializer_type.kind == DataType::BUILTIN && initializer_type.builtin_type == Variant::STRING) {
+ enum_type = Variant::STRING;
+ }
+ }
+
+ variable->export_info.type = enum_type;
+
+ if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != enum_type)) {
+ push_error(vformat(R"("@export_enum" annotation requires a variable of type "int" or "String" but type "%s" was given instead.)", export_type.to_string()), variable);
+ return false;
+ }
} else {
// Validate variable type with export.
if (!export_type.is_variant() && (export_type.kind != DataType::BUILTIN || export_type.builtin_type != t_type)) {
@@ -3849,7 +3868,6 @@ bool GDScriptParser::export_group_annotations(const AnnotationNode *p_annotation
} break;
}
- current_class->add_member_group(annotation);
return true;
}
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index a6cbb7f6ae..024fed8517 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -148,9 +148,13 @@ 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: {
+ case INT_AS_ENUM_WITHOUT_CAST: {
return "Integer used when an enum value is expected. If this is intended cast the integer to the enum type.";
}
+ case INT_AS_ENUM_WITHOUT_MATCH: {
+ CHECK_SYMBOLS(3);
+ return vformat(R"(Cannot %s %s as Enum "%s": no enum member has matching value.)", symbols[0], symbols[1], symbols[2]);
+ } break;
case STATIC_CALLED_ON_INSTANCE: {
CHECK_SYMBOLS(2);
return vformat(R"(The function '%s()' is a static function but was called from an instance. Instead, it should be directly called from the type: '%s.%s()'.)", symbols[0], symbols[1], symbols[0]);
@@ -221,7 +225,8 @@ String GDScriptWarning::get_name_from_code(Code p_code) {
"REDUNDANT_AWAIT",
"EMPTY_FILE",
"SHADOWED_GLOBAL_IDENTIFIER",
- "INT_ASSIGNED_TO_ENUM",
+ "INT_AS_ENUM_WITHOUT_CAST",
+ "INT_AS_ENUM_WITHOUT_MATCH",
"STATIC_CALLED_ON_INSTANCE",
"CONFUSABLE_IDENTIFIER",
};
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index b485f02b9c..7492972c1a 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -76,7 +76,8 @@ 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.
+ INT_AS_ENUM_WITHOUT_CAST, // An integer value was used as an enum value without casting.
+ INT_AS_ENUM_WITHOUT_MATCH, // An integer value was used as an enum value without matching enum member.
STATIC_CALLED_ON_INSTANCE, // A static method was called on an instance of a class instead of on the class itself.
CONFUSABLE_IDENTIFIER, // The identifier contains misleading characters that can be confused. E.g. "usеr" (has Cyrillic "е" instead of Latin "e").
WARNING_MAX,
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd
new file mode 100644
index 0000000000..1b22d173c7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.gd
@@ -0,0 +1,3 @@
+func test():
+ var var_color: String = Color.RED
+ print('not ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out
new file mode 100644
index 0000000000..cc4b1d86bf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/assymetric_assignment_bad.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Cannot assign a value of type "Color" as "String".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
deleted file mode 100644
index 3a8d2a205a..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.out
+++ /dev/null
@@ -1,2 +0,0 @@
-GDTEST_ANALYZER_ERROR
-Invalid cast. Enum "cast_enum_bad_enum.gd::MyEnum" does not have value corresponding to "MyOtherEnum.OTHER_ENUM_VALUE_3" (2).
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
deleted file mode 100644
index bc0d8b7834..0000000000
--- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.out
+++ /dev/null
@@ -1,2 +0,0 @@
-GDTEST_ANALYZER_ERROR
-Invalid cast. Enum "cast_enum_bad_int.gd::MyEnum" does not have enum value 2.
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
index 02c4633586..84958f1aa2 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_class_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_assign_with_wrong_enum_type.gd::MyEnum".
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
index 441cccbf7b..e294f3496a 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "class_var" with specified type enum_class_var_init_with_wrong_enum_type.gd::MyEnum.
+Cannot assign a value of type "enum_class_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_class_var_init_with_wrong_enum_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
index e85f7d6f9f..a91189e2dd 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_parameter_wrong_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Invalid argument for "enum_func()" function: argument 1 should be "enum_function_parameter_wrong_type.gd::MyEnum" but is "enum_function_parameter_wrong_type.gd::MyOtherEnum".
+Cannot pass a value of type "enum_function_parameter_wrong_type.gd::MyOtherEnum" as "enum_function_parameter_wrong_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
index f7ea3267fa..6b4eba3740 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_function_return_wrong_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot return value of type "enum_function_return_wrong_type.gd::MyOtherEnum" because the function return type is "enum_function_return_wrong_type.gd::MyEnum".
+Cannot return a value of type "enum_function_return_wrong_type.gd::MyOtherEnum" as "enum_function_return_wrong_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
index 38df5a0cd8..616358bb61 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_local_var_assign_outer_with_wrong_enum_type.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" cannot be assigned to a variable of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_outer_with_wrong_enum_type.gd::InnerClass::MyEnum" as "enum_local_var_assign_outer_with_wrong_enum_type.gd::MyEnum".
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
index 2adcbd9edf..af3dde663f 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" cannot be assigned to a variable of type "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".
+Cannot assign a value of type "enum_local_var_assign_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_assign_with_wrong_enum_type.gd::MyEnum".
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
index 331113dd30..781b0bc85f 100644
--- 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
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum to variable "local_var" with specified type enum_local_var_init_with_wrong_enum_type.gd::MyEnum.
+Cannot assign a value of type "enum_local_var_init_with_wrong_enum_type.gd::MyOtherEnum" as "enum_local_var_init_with_wrong_enum_type.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
index 6298c026b4..e8c7f86c4f 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_preload_unnamed_assign_to_named.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Value of type "enum_value_from_parent.gd::<anonymous enum>" cannot be assigned to a variable of type "enum_preload_unnamed_assign_to_named.gd::MyEnum".
+Cannot assign a value of type "enum_value_from_parent.gd::<anonymous enum>" as "enum_preload_unnamed_assign_to_named.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
index b70121ed81..fb18c94d0b 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/enum_unnamed_assign_to_named.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot assign a value of type enum_unnamed_assign_to_named.gd::<anonymous enum> to variable "local_var" with specified type enum_unnamed_assign_to_named.gd::MyEnum.
+Cannot assign a value of type "enum_unnamed_assign_to_named.gd::<anonymous enum>" as "enum_unnamed_assign_to_named.gd::MyEnum".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out
index 53e2b012e6..69af0984f7 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/lambda_wrong_return.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Cannot return value of type "String" because the function return type is "int".
+Cannot return a value of type "String" as "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.gd
new file mode 100644
index 0000000000..6d92db34c1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.gd
@@ -0,0 +1,5 @@
+func test():
+ var dict = { a = 1 }
+ match 2:
+ dict["a"]:
+ print("not allowed")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.out b/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.out
new file mode 100644
index 0000000000..b7385a50d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_with_subscript.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Expression in match pattern must be a constant expression, an identifier, or an attribute access ("A.B").
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.gd b/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.gd
new file mode 100644
index 0000000000..4df44d9ea2
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.gd
@@ -0,0 +1,5 @@
+func test():
+ var a = 1
+ match 2:
+ a + 2:
+ print("not allowed")
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.out b/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.out
new file mode 100644
index 0000000000..b7385a50d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/match_with_variable_expression.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Expression in match pattern must be a constant expression, an identifier, or an attribute access ("A.B").
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
index 5e3c446bf6..08a973503f 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
+++ b/modules/gdscript/tests/scripts/analyzer/errors/preload_enum_error.out
@@ -1,2 +1,2 @@
GDTEST_ANALYZER_ERROR
-Value of type "enum_from_outer.gd::Named" cannot be assigned to a variable of type "preload_enum_error.gd::LocalNamed".
+Cannot assign a value of type "enum_from_outer.gd::Named" as "preload_enum_error.gd::LocalNamed".
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
new file mode 100644
index 0000000000..efd8ad6edb
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.gd
@@ -0,0 +1,16 @@
+const const_color: Color = 'red'
+
+func func_color(arg_color: Color = 'blue') -> bool:
+ return arg_color == Color.BLUE
+
+@warning_ignore("assert_always_true")
+func test():
+ assert(const_color == Color.RED)
+
+ assert(func_color() == true)
+ assert(func_color('blue') == true)
+
+ var var_color: Color = 'green'
+ assert(var_color == Color.GREEN)
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/assymetric_assignment_good.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
new file mode 100644
index 0000000000..bed9dd0e96
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.gd
@@ -0,0 +1,24 @@
+const const_float_int: float = 19
+const const_float_plus: float = 12 + 22
+const const_float_cast: float = 76 as float
+
+const const_packed_empty: PackedFloat64Array = []
+const const_packed_ints: PackedFloat64Array = [52]
+
+@warning_ignore("assert_always_true")
+func test():
+ assert(typeof(const_float_int) == TYPE_FLOAT)
+ assert(str(const_float_int) == '19')
+ assert(typeof(const_float_plus) == TYPE_FLOAT)
+ assert(str(const_float_plus) == '34')
+ assert(typeof(const_float_cast) == TYPE_FLOAT)
+ assert(str(const_float_cast) == '76')
+
+ assert(typeof(const_packed_empty) == TYPE_PACKED_FLOAT64_ARRAY)
+ assert(str(const_packed_empty) == '[]')
+ assert(typeof(const_packed_ints) == TYPE_PACKED_FLOAT64_ARRAY)
+ assert(str(const_packed_ints) == '[52]')
+ assert(typeof(const_packed_ints[0]) == TYPE_FLOAT)
+ assert(str(const_packed_ints[0]) == '52')
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/const_conversions.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
new file mode 100644
index 0000000000..48a804ff54
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.gd
@@ -0,0 +1,32 @@
+func variant() -> Variant: return null
+
+var member_weak = variant()
+var member_typed: Variant = variant()
+var member_inferred := variant()
+
+func param_weak(param = variant()) -> void: print(param)
+func param_typed(param: Variant = variant()) -> void: print(param)
+func param_inferred(param := variant()) -> void: print(param)
+
+func return_untyped(): return variant()
+func return_typed() -> Variant: return variant()
+
+@warning_ignore("unused_variable")
+func test() -> void:
+ var weak = variant()
+ var typed: Variant = variant()
+ var inferred := variant()
+
+ weak = variant()
+ typed = variant()
+ inferred = variant()
+
+ param_weak(typed)
+ param_typed(typed)
+ param_inferred(typed)
+
+ if typed == null: pass
+ if typed != null: pass
+ if typed is Node: pass
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
new file mode 100644
index 0000000000..08491efa07
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/hard_variants.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+<null>
+<null>
+<null>
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
new file mode 100644
index 0000000000..0b1576e66e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.gd
@@ -0,0 +1,34 @@
+func convert_literal_int_to_float() -> float: return 76
+func convert_arg_int_to_float(arg: int) -> float: return arg
+func convert_var_int_to_float() -> float: var number := 59; return number
+
+func convert_literal_array_to_packed() -> PackedStringArray: return ['46']
+func convert_arg_array_to_packed(arg: Array) -> PackedStringArray: return arg
+func convert_var_array_to_packed() -> PackedStringArray: var array := ['79']; return array
+
+func test():
+ var converted_literal_int := convert_literal_int_to_float()
+ assert(typeof(converted_literal_int) == TYPE_FLOAT)
+ assert(converted_literal_int == 76.0)
+
+ var converted_arg_int := convert_arg_int_to_float(36)
+ assert(typeof(converted_arg_int) == TYPE_FLOAT)
+ assert(converted_arg_int == 36.0)
+
+ var converted_var_int := convert_var_int_to_float()
+ assert(typeof(converted_var_int) == TYPE_FLOAT)
+ assert(converted_var_int == 59.0)
+
+ var converted_literal_array := convert_literal_array_to_packed()
+ assert(typeof(converted_literal_array) == TYPE_PACKED_STRING_ARRAY)
+ assert(str(converted_literal_array) == '["46"]')
+
+ var converted_arg_array := convert_arg_array_to_packed(['91'])
+ assert(typeof(converted_arg_array) == TYPE_PACKED_STRING_ARRAY)
+ assert(str(converted_arg_array) == '["91"]')
+
+ var converted_var_array := convert_var_array_to_packed()
+ assert(typeof(converted_var_array) == TYPE_PACKED_STRING_ARRAY)
+ assert(str(converted_var_array) == '["79"]')
+
+ print('ok')
diff --git a/modules/gdscript/tests/scripts/analyzer/features/return_conversions.out b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/features/return_conversions.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd
index 71616ea3af..71616ea3af 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_enum.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.gd
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
new file mode 100644
index 0000000000..6e086a0918
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_enum.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> INT_AS_ENUM_WITHOUT_MATCH
+>> Cannot cast 2 as Enum "cast_enum_bad_enum.gd::MyEnum": no enum member has matching value.
+2
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd
index 60a31fb318..60a31fb318 100644
--- a/modules/gdscript/tests/scripts/analyzer/errors/cast_enum_bad_int.gd
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.gd
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
new file mode 100644
index 0000000000..c19d57f98e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/cast_enum_bad_int.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+>> WARNING
+>> Line: 4
+>> INT_AS_ENUM_WITHOUT_MATCH
+>> Cannot cast 2 as Enum "cast_enum_bad_int.gd::MyEnum": no enum member has matching value.
+2
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.gd b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
new file mode 100644
index 0000000000..9b2c22dea1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.gd
@@ -0,0 +1,15 @@
+@export_enum("Red", "Green", "Blue") var untyped
+
+@export_enum("Red", "Green", "Blue") var weak_int = 0
+@export_enum("Red", "Green", "Blue") var weak_string = ""
+
+@export_enum("Red", "Green", "Blue") var hard_int: int
+@export_enum("Red", "Green", "Blue") var hard_string: String
+
+@export_enum("Red:10", "Green:20", "Blue:30") var with_values
+
+func test():
+ for property in get_property_list():
+ if property.name in ["untyped", "weak_int", "weak_string", "hard_int",
+ "hard_string", "with_values"]:
+ prints(property.name, property.type, property.hint_string)
diff --git a/modules/gdscript/tests/scripts/parser/features/export_enum.out b/modules/gdscript/tests/scripts/parser/features/export_enum.out
new file mode 100644
index 0000000000..330b7eaf01
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/export_enum.out
@@ -0,0 +1,7 @@
+GDTEST_OK
+untyped 2 Red,Green,Blue
+weak_int 2 Red,Green,Blue
+weak_string 4 Red,Green,Blue
+hard_int 2 Red,Green,Blue
+hard_string 4 Red,Green,Blue
+with_values 2 Red:10,Green:20,Blue:30
diff --git a/modules/gdscript/tests/scripts/parser/features/match_with_variables.gd b/modules/gdscript/tests/scripts/parser/features/match_with_variables.gd
new file mode 100644
index 0000000000..aa38c3bf41
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/match_with_variables.gd
@@ -0,0 +1,22 @@
+func test():
+ var a = 1
+ match 1:
+ a:
+ print("reach 1")
+
+ var dict = { b = 2 }
+ match 2:
+ dict.b:
+ print("reach 2")
+
+ var nested_dict = {
+ sub = { c = 3 }
+ }
+ match 3:
+ nested_dict.sub.c:
+ print("reach 3")
+
+ var sub_pattern = { d = 4 }
+ match [4]:
+ [sub_pattern.d]:
+ print("reach 4")
diff --git a/modules/gdscript/tests/scripts/parser/features/match_with_variables.out b/modules/gdscript/tests/scripts/parser/features/match_with_variables.out
new file mode 100644
index 0000000000..de1dcb0d40
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/match_with_variables.out
@@ -0,0 +1,5 @@
+GDTEST_OK
+reach 1
+reach 2
+reach 3
+reach 4
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
index eef13bbff8..b8e243769f 100644
--- 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
@@ -1,19 +1,19 @@
GDTEST_OK
>> WARNING
>> Line: 5
->> INT_ASSIGNED_TO_ENUM
+>> INT_AS_ENUM_WITHOUT_CAST
>> 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
+>> INT_AS_ENUM_WITHOUT_CAST
>> 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
+>> INT_AS_ENUM_WITHOUT_CAST
>> 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
+>> INT_AS_ENUM_WITHOUT_CAST
>> Integer used when an enum value is expected. If this is intended cast the integer to the enum type.
0
1
diff --git a/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.gd b/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.gd
new file mode 100644
index 0000000000..0c15701364
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.gd
@@ -0,0 +1,6 @@
+# https://github.com/godotengine/godot/issues/66675
+func test():
+ example(Node2D)
+
+func example(thing):
+ print(thing.has_method('asdf'))
diff --git a/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.out b/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.out
new file mode 100644
index 0000000000..3a90f98d99
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/non_static_method_call_on_native_class.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: example()
+>> runtime/errors/non_static_method_call_on_native_class.gd
+>> 6
+>> Invalid call. Nonexistent function 'has_method' in base 'Node2D'.
diff --git a/modules/gltf/doc_classes/GLTFDocument.xml b/modules/gltf/doc_classes/GLTFDocument.xml
index d7e8141eb1..d6657cad5d 100644
--- a/modules/gltf/doc_classes/GLTFDocument.xml
+++ b/modules/gltf/doc_classes/GLTFDocument.xml
@@ -51,6 +51,7 @@
<param index="0" name="state" type="GLTFState" />
<param index="1" name="bake_fps" type="float" default="30" />
<param index="2" name="trimming" type="bool" default="false" />
+ <param index="3" name="remove_immutable_tracks" type="bool" default="true" />
<description>
Takes a [GLTFState] object through the [param state] parameter and returns a Godot Engine scene node.
</description>
diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp
index 0075558dfc..5415c5818f 100644
--- a/modules/gltf/editor/editor_scene_importer_blend.cpp
+++ b/modules/gltf/editor/editor_scene_importer_blend.cpp
@@ -233,7 +233,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_
}
return nullptr;
}
- return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
+ return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
}
Variant EditorSceneFormatImporterBlend::get_option_visibility(const String &p_path, bool p_for_animation, const String &p_option,
diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp
index 6c6ab7cd03..d829630032 100644
--- a/modules/gltf/editor/editor_scene_importer_fbx.cpp
+++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp
@@ -100,7 +100,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t
}
return nullptr;
}
- return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
+ return gltf->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
}
Variant EditorSceneFormatImporterFBX::get_option_visibility(const String &p_path, bool p_for_animation,
diff --git a/modules/gltf/editor/editor_scene_importer_gltf.cpp b/modules/gltf/editor/editor_scene_importer_gltf.cpp
index 7c40afc8e7..67bbf8dd15 100644
--- a/modules/gltf/editor/editor_scene_importer_gltf.cpp
+++ b/modules/gltf/editor/editor_scene_importer_gltf.cpp
@@ -66,11 +66,23 @@ Node *EditorSceneFormatImporterGLTF::import_scene(const String &p_path, uint32_t
state->set_create_animations(bool(p_options["animation/import"]));
}
+#ifndef DISABLE_DEPRECATED
if (p_options.has("animation/trimming")) {
- return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"]);
+ if (p_options.has("animation/remove_immutable_tracks")) {
+ return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
+ } else {
+ return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], true);
+ }
} else {
- return doc->generate_scene(state, (float)p_options["animation/fps"], false);
+ if (p_options.has("animation/remove_immutable_tracks")) {
+ return doc->generate_scene(state, (float)p_options["animation/fps"], false, (bool)p_options["animation/remove_immutable_tracks"]);
+ } else {
+ return doc->generate_scene(state, (float)p_options["animation/fps"], false, true);
+ }
}
+#else
+ return doc->generate_scene(state, (float)p_options["animation/fps"], (bool)p_options["animation/trimming"], (bool)p_options["animation/remove_immutable_tracks"]);
+#endif
}
void EditorSceneFormatImporterGLTF::get_import_options(const String &p_path,
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index bc50e74cd3..0b519bd6a3 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -5961,7 +5961,7 @@ T GLTFDocument::_interpolate_track(const Vector<real_t> &p_times, const Vector<T
ERR_FAIL_V(p_values[0]);
}
-void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming) {
+void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player, const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks) {
Ref<GLTFAnimation> anim = p_state->animations[p_index];
String anim_name = anim->get_name();
@@ -6064,35 +6064,38 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
int scale_idx = -1;
if (track.position_track.values.size()) {
- Vector3 base_pos = p_state->nodes[track_i.key]->position;
- bool not_default = false; //discard the track if all it contains is default values
- for (int i = 0; i < track.position_track.times.size(); i++) {
- Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i];
- if (!value.is_equal_approx(base_pos)) {
- not_default = true;
- break;
+ bool is_default = true; //discard the track if all it contains is default values
+ if (p_remove_immutable_tracks) {
+ Vector3 base_pos = p_state->nodes[track_i.key]->position;
+ for (int i = 0; i < track.position_track.times.size(); i++) {
+ Vector3 value = track.position_track.values[track.position_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i];
+ if (!value.is_equal_approx(base_pos)) {
+ is_default = false;
+ break;
+ }
}
}
- if (not_default) {
+ if (!p_remove_immutable_tracks || !is_default) {
position_idx = base_idx;
animation->add_track(Animation::TYPE_POSITION_3D);
animation->track_set_path(position_idx, transform_node_path);
animation->track_set_imported(position_idx, true); //helps merging later
-
base_idx++;
}
}
if (track.rotation_track.values.size()) {
- Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized();
- bool not_default = false; //discard the track if all it contains is default values
- for (int i = 0; i < track.rotation_track.times.size(); i++) {
- Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized();
- if (!value.is_equal_approx(base_rot)) {
- not_default = true;
- break;
+ bool is_default = true; //discard the track if all it contains is default values
+ if (p_remove_immutable_tracks) {
+ Quaternion base_rot = p_state->nodes[track_i.key]->rotation.normalized();
+ for (int i = 0; i < track.rotation_track.times.size(); i++) {
+ Quaternion value = track.rotation_track.values[track.rotation_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i].normalized();
+ if (!value.is_equal_approx(base_rot)) {
+ is_default = false;
+ break;
+ }
}
}
- if (not_default) {
+ if (!p_remove_immutable_tracks || !is_default) {
rotation_idx = base_idx;
animation->add_track(Animation::TYPE_ROTATION_3D);
animation->track_set_path(rotation_idx, transform_node_path);
@@ -6101,16 +6104,18 @@ void GLTFDocument::_import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_
}
}
if (track.scale_track.values.size()) {
- Vector3 base_scale = p_state->nodes[track_i.key]->scale;
- bool not_default = false; //discard the track if all it contains is default values
- for (int i = 0; i < track.scale_track.times.size(); i++) {
- Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i];
- if (!value.is_equal_approx(base_scale)) {
- not_default = true;
- break;
+ bool is_default = true; //discard the track if all it contains is default values
+ if (p_remove_immutable_tracks) {
+ Vector3 base_scale = p_state->nodes[track_i.key]->scale;
+ for (int i = 0; i < track.scale_track.times.size(); i++) {
+ Vector3 value = track.scale_track.values[track.scale_track.interpolation == GLTFAnimation::INTERP_CUBIC_SPLINE ? (1 + i * 3) : i];
+ if (!value.is_equal_approx(base_scale)) {
+ is_default = false;
+ break;
+ }
}
}
- if (not_default) {
+ if (!p_remove_immutable_tracks || !is_default) {
scale_idx = base_idx;
animation->add_track(Animation::TYPE_SCALE_3D);
animation->track_set_path(scale_idx, transform_node_path);
@@ -6895,8 +6900,8 @@ void GLTFDocument::_bind_methods() {
&GLTFDocument::append_from_buffer, DEFVAL(0));
ClassDB::bind_method(D_METHOD("append_from_scene", "node", "state", "flags"),
&GLTFDocument::append_from_scene, DEFVAL(0));
- ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming"),
- &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false));
+ ClassDB::bind_method(D_METHOD("generate_scene", "state", "bake_fps", "trimming", "remove_immutable_tracks"),
+ &GLTFDocument::generate_scene, DEFVAL(30), DEFVAL(false), DEFVAL(true));
ClassDB::bind_method(D_METHOD("generate_buffer", "state"),
&GLTFDocument::generate_buffer);
ClassDB::bind_method(D_METHOD("write_to_filesystem", "state", "path"),
@@ -7005,7 +7010,7 @@ Error GLTFDocument::write_to_filesystem(Ref<GLTFState> p_state, const String &p_
return OK;
}
-Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming) {
+Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, bool p_trimming, bool p_remove_immutable_tracks) {
ERR_FAIL_NULL_V(p_state, nullptr);
ERR_FAIL_INDEX_V(0, p_state->root_nodes.size(), nullptr);
Error err = OK;
@@ -7019,7 +7024,7 @@ Node *GLTFDocument::generate_scene(Ref<GLTFState> p_state, float p_bake_fps, boo
root->add_child(ap, true);
ap->set_owner(root);
for (int i = 0; i < p_state->animations.size(); i++) {
- _import_animation(p_state, ap, i, p_bake_fps, p_trimming);
+ _import_animation(p_state, ap, i, p_bake_fps, p_trimming, p_remove_immutable_tracks);
}
}
for (KeyValue<GLTFNodeIndex, Node *> E : p_state->scene_nodes) {
diff --git a/modules/gltf/gltf_document.h b/modules/gltf/gltf_document.h
index 73128c13aa..b8b989bf89 100644
--- a/modules/gltf/gltf_document.h
+++ b/modules/gltf/gltf_document.h
@@ -296,7 +296,7 @@ public:
Error append_from_scene(Node *p_node, Ref<GLTFState> r_state, uint32_t p_flags = 0);
public:
- Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false);
+ Node *generate_scene(Ref<GLTFState> p_state, float p_bake_fps = 30.0f, bool p_trimming = false, bool p_remove_immutable_tracks = true);
PackedByteArray generate_buffer(Ref<GLTFState> p_state);
Error write_to_filesystem(Ref<GLTFState> p_state, const String &p_path);
@@ -309,7 +309,7 @@ public:
const GLTFNodeIndex p_node_index);
void _generate_skeleton_bone_node(Ref<GLTFState> p_state, Node *p_scene_parent, Node3D *p_scene_root, const GLTFNodeIndex p_node_index);
void _import_animation(Ref<GLTFState> p_state, AnimationPlayer *p_animation_player,
- const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming);
+ const GLTFAnimationIndex p_index, const float p_bake_fps, const bool p_trimming, const bool p_remove_immutable_tracks);
void _convert_mesh_instances(Ref<GLTFState> p_state);
GLTFCameraIndex _convert_camera(Ref<GLTFState> p_state, Camera3D *p_camera);
void _convert_light_to_gltf(Light3D *p_light, Ref<GLTFState> p_state, Ref<GLTFNode> p_gltf_node);
diff --git a/modules/mbedtls/dtls_server_mbedtls.cpp b/modules/mbedtls/dtls_server_mbedtls.cpp
index c54ab8ef6e..62513929ea 100644
--- a/modules/mbedtls/dtls_server_mbedtls.cpp
+++ b/modules/mbedtls/dtls_server_mbedtls.cpp
@@ -31,25 +31,25 @@
#include "dtls_server_mbedtls.h"
#include "packet_peer_mbed_dtls.h"
-Error DTLSServerMbedTLS::setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) {
- ERR_FAIL_COND_V(_cookies->setup() != OK, ERR_ALREADY_IN_USE);
- _key = p_key;
- _cert = p_cert;
- _ca_chain = p_ca_chain;
+Error DTLSServerMbedTLS::setup(Ref<TLSOptions> p_options) {
+ ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(cookies->setup() != OK, ERR_ALREADY_IN_USE);
+ tls_options = p_options;
return OK;
}
void DTLSServerMbedTLS::stop() {
- _cookies->clear();
+ cookies->clear();
}
Ref<PacketPeerDTLS> DTLSServerMbedTLS::take_connection(Ref<PacketPeerUDP> p_udp_peer) {
Ref<PacketPeerMbedDTLS> out;
- out.instantiate();
- ERR_FAIL_COND_V(!out.is_valid(), out);
+ ERR_FAIL_COND_V(tls_options.is_null(), out);
ERR_FAIL_COND_V(!p_udp_peer.is_valid(), out);
- out->accept_peer(p_udp_peer, _key, _cert, _ca_chain, _cookies);
+
+ out.instantiate();
+ out->accept_peer(p_udp_peer, tls_options, cookies);
return out;
}
@@ -68,7 +68,7 @@ void DTLSServerMbedTLS::finalize() {
}
DTLSServerMbedTLS::DTLSServerMbedTLS() {
- _cookies.instantiate();
+ cookies.instantiate();
}
DTLSServerMbedTLS::~DTLSServerMbedTLS() {
diff --git a/modules/mbedtls/dtls_server_mbedtls.h b/modules/mbedtls/dtls_server_mbedtls.h
index e4612d01ef..d5841a45fa 100644
--- a/modules/mbedtls/dtls_server_mbedtls.h
+++ b/modules/mbedtls/dtls_server_mbedtls.h
@@ -37,16 +37,14 @@
class DTLSServerMbedTLS : public DTLSServer {
private:
static DTLSServer *_create_func();
- Ref<CryptoKey> _key;
- Ref<X509Certificate> _cert;
- Ref<X509Certificate> _ca_chain;
- Ref<CookieContextMbedTLS> _cookies;
+ Ref<TLSOptions> tls_options;
+ Ref<CookieContextMbedTLS> cookies;
public:
static void initialize();
static void finalize();
- virtual Error setup(Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>());
+ virtual Error setup(Ref<TLSOptions> p_options);
virtual void stop();
virtual Ref<PacketPeerDTLS> take_connection(Ref<PacketPeerUDP> p_peer);
diff --git a/modules/mbedtls/packet_peer_mbed_dtls.cpp b/modules/mbedtls/packet_peer_mbed_dtls.cpp
index 16450e151e..e8eb32f88d 100644
--- a/modules/mbedtls/packet_peer_mbed_dtls.cpp
+++ b/modules/mbedtls/packet_peer_mbed_dtls.cpp
@@ -114,16 +114,14 @@ Error PacketPeerMbedDTLS::_do_handshake() {
return OK;
}
-Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) {
+Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER);
- base = p_base;
- int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE;
-
- Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, authmode, p_ca_certs);
+ Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_hostname, p_options.is_valid() ? p_options : TLSOptions::client());
ERR_FAIL_COND_V(err != OK, err);
- mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data());
+ base = p_base;
+
mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
mbedtls_ssl_set_timer_cb(tls_ctx->get_context(), &timer, mbedtls_timing_set_delay, mbedtls_timing_get_delay);
@@ -137,8 +135,10 @@ Error PacketPeerMbedDTLS::connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_vali
return OK;
}
-Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain, Ref<CookieContextMbedTLS> p_cookies) {
- Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert, p_cookies);
+Error PacketPeerMbedDTLS::accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies) {
+ ERR_FAIL_COND_V(!p_base.is_valid() || !p_base->is_socket_connected(), ERR_INVALID_PARAMETER);
+
+ Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_DATAGRAM, p_options, p_cookies);
ERR_FAIL_COND_V(err != OK, err);
base = p_base;
diff --git a/modules/mbedtls/packet_peer_mbed_dtls.h b/modules/mbedtls/packet_peer_mbed_dtls.h
index 744ef81524..05decec783 100644
--- a/modules/mbedtls/packet_peer_mbed_dtls.h
+++ b/modules/mbedtls/packet_peer_mbed_dtls.h
@@ -64,8 +64,8 @@ protected:
public:
virtual void poll();
- virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert = Ref<X509Certificate>(), Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>(), Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
- virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, bool p_validate_certs = true, const String &p_for_hostname = String(), Ref<X509Certificate> p_ca_certs = Ref<X509Certificate>());
+ virtual Error accept_peer(Ref<PacketPeerUDP> p_base, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
+ virtual Error connect_to_peer(Ref<PacketPeerUDP> p_base, const String &p_hostname, Ref<TLSOptions> p_options = Ref<TLSOptions>());
virtual Status get_status() const;
virtual void disconnect_from_peer();
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index 1d17fb9441..a9d187bd64 100644
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -80,38 +80,30 @@ void StreamPeerMbedTLS::_cleanup() {
}
Error StreamPeerMbedTLS::_do_handshake() {
- int ret = 0;
- while ((ret = mbedtls_ssl_handshake(tls_ctx->get_context())) != 0) {
- if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
- // An error occurred.
- ERR_PRINT("TLS handshake error: " + itos(ret));
- TLSContextMbedTLS::print_mbedtls_error(ret);
- disconnect_from_stream();
- status = STATUS_ERROR;
- return FAILED;
- }
-
- // Handshake is still in progress.
- if (!blocking_handshake) {
- // Will retry via poll later
- return OK;
- }
+ int ret = mbedtls_ssl_handshake(tls_ctx->get_context());
+ if (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
+ // Handshake is still in progress, will retry via poll later.
+ return OK;
+ } else if (ret != 0) {
+ // An error occurred.
+ ERR_PRINT("TLS handshake error: " + itos(ret));
+ TLSContextMbedTLS::print_mbedtls_error(ret);
+ disconnect_from_stream();
+ status = STATUS_ERROR;
+ return FAILED;
}
status = STATUS_CONNECTED;
return OK;
}
-Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs, const String &p_for_hostname, Ref<X509Certificate> p_ca_certs) {
+Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER);
- base = p_base;
- int authmode = p_validate_certs ? MBEDTLS_SSL_VERIFY_REQUIRED : MBEDTLS_SSL_VERIFY_NONE;
-
- Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, authmode, p_ca_certs);
+ Error err = tls_ctx->init_client(MBEDTLS_SSL_TRANSPORT_STREAM, p_common_name, p_options.is_valid() ? p_options : TLSOptions::client());
ERR_FAIL_COND_V(err != OK, err);
- mbedtls_ssl_set_hostname(tls_ctx->get_context(), p_for_hostname.utf8().get_data());
+ base = p_base;
mbedtls_ssl_set_bio(tls_ctx->get_context(), this, bio_send, bio_recv, nullptr);
status = STATUS_HANDSHAKING;
@@ -124,10 +116,11 @@ Error StreamPeerMbedTLS::connect_to_stream(Ref<StreamPeer> p_base, bool p_valida
return OK;
}
-Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain) {
+Error StreamPeerMbedTLS::accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(p_base.is_null(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
- Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, MBEDTLS_SSL_VERIFY_NONE, p_key, p_cert);
+ Error err = tls_ctx->init_server(MBEDTLS_SSL_TRANSPORT_STREAM, p_options);
ERR_FAIL_COND_V(err != OK, err);
base = p_base;
@@ -308,10 +301,8 @@ StreamPeerTLS *StreamPeerMbedTLS::_create_func() {
void StreamPeerMbedTLS::initialize_tls() {
_create = _create_func;
- available = true;
}
void StreamPeerMbedTLS::finalize_tls() {
- available = false;
_create = nullptr;
}
diff --git a/modules/mbedtls/stream_peer_mbedtls.h b/modules/mbedtls/stream_peer_mbedtls.h
index 8a36a7ea9a..ec0446c380 100644
--- a/modules/mbedtls/stream_peer_mbedtls.h
+++ b/modules/mbedtls/stream_peer_mbedtls.h
@@ -54,8 +54,8 @@ protected:
public:
virtual void poll();
- virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert, Ref<X509Certificate> p_ca_chain = Ref<X509Certificate>());
- virtual Error connect_to_stream(Ref<StreamPeer> p_base, bool p_validate_certs = false, const String &p_for_hostname = String(), Ref<X509Certificate> p_valid_cert = Ref<X509Certificate>());
+ virtual Error accept_stream(Ref<StreamPeer> p_base, Ref<TLSOptions> p_options);
+ virtual Error connect_to_stream(Ref<StreamPeer> p_base, const String &p_common_name, Ref<TLSOptions> p_options);
virtual Status get_status() const;
virtual Ref<StreamPeer> get_stream() const;
diff --git a/modules/mbedtls/tls_context_mbedtls.cpp b/modules/mbedtls/tls_context_mbedtls.cpp
index a01137f262..aab082f488 100644
--- a/modules/mbedtls/tls_context_mbedtls.cpp
+++ b/modules/mbedtls/tls_context_mbedtls.cpp
@@ -110,22 +110,20 @@ Error TLSContextMbedTLS::_setup(int p_endpoint, int p_transport, int p_authmode)
return OK;
}
-Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies) {
- ERR_FAIL_COND_V(!p_pkey.is_valid(), ERR_INVALID_PARAMETER);
- ERR_FAIL_COND_V(!p_cert.is_valid(), ERR_INVALID_PARAMETER);
+Error TLSContextMbedTLS::init_server(int p_transport, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies) {
+ ERR_FAIL_COND_V(p_options.is_null() || !p_options->is_server(), ERR_INVALID_PARAMETER);
- Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, p_authmode);
+ // Check key and certificate(s)
+ pkey = p_options->get_private_key();
+ certs = p_options->get_own_certificate();
+ ERR_FAIL_COND_V(pkey.is_null() || certs.is_null(), ERR_INVALID_PARAMETER);
+
+ Error err = _setup(MBEDTLS_SSL_IS_SERVER, p_transport, MBEDTLS_SSL_VERIFY_NONE); // TODO client auth.
ERR_FAIL_COND_V(err != OK, err);
// Locking key and certificate(s)
- pkey = p_pkey;
- certs = p_cert;
- if (pkey.is_valid()) {
- pkey->lock();
- }
- if (certs.is_valid()) {
- certs->lock();
- }
+ pkey->lock();
+ certs->lock();
// Adding key and certificate
int ret = mbedtls_ssl_conf_own_cert(&conf, &(certs->cert), &(pkey->pkey));
@@ -150,15 +148,32 @@ Error TLSContextMbedTLS::init_server(int p_transport, int p_authmode, Ref<Crypto
return OK;
}
-Error TLSContextMbedTLS::init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas) {
- Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, p_authmode);
+Error TLSContextMbedTLS::init_client(int p_transport, const String &p_hostname, Ref<TLSOptions> p_options) {
+ ERR_FAIL_COND_V(p_options.is_null() || p_options->is_server(), ERR_INVALID_PARAMETER);
+
+ int authmode = MBEDTLS_SSL_VERIFY_REQUIRED;
+ if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_NONE) {
+ authmode = MBEDTLS_SSL_VERIFY_NONE;
+ }
+
+ Error err = _setup(MBEDTLS_SSL_IS_CLIENT, p_transport, authmode);
ERR_FAIL_COND_V(err != OK, err);
+ if (p_options->get_verify_mode() == TLSOptions::TLS_VERIFY_FULL) {
+ String cn = p_options->get_common_name();
+ if (cn.is_empty()) {
+ cn = p_hostname;
+ }
+ mbedtls_ssl_set_hostname(&tls, cn.utf8().get_data());
+ } else {
+ mbedtls_ssl_set_hostname(&tls, nullptr);
+ }
+
X509CertificateMbedTLS *cas = nullptr;
- if (p_valid_cas.is_valid()) {
+ if (p_options->get_trusted_ca_chain().is_valid()) {
// Locking CA certificates
- certs = p_valid_cas;
+ certs = p_options->get_trusted_ca_chain();
certs->lock();
cas = certs.ptr();
} else {
diff --git a/modules/mbedtls/tls_context_mbedtls.h b/modules/mbedtls/tls_context_mbedtls.h
index 574e80e199..f1bad6a40c 100644
--- a/modules/mbedtls/tls_context_mbedtls.h
+++ b/modules/mbedtls/tls_context_mbedtls.h
@@ -71,17 +71,17 @@ public:
static void print_mbedtls_error(int p_ret);
Ref<X509CertificateMbedTLS> certs;
+ Ref<CryptoKeyMbedTLS> pkey;
+ Ref<CookieContextMbedTLS> cookies;
+
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ssl_context tls;
mbedtls_ssl_config conf;
- Ref<CookieContextMbedTLS> cookies;
- Ref<CryptoKeyMbedTLS> pkey;
-
Error _setup(int p_endpoint, int p_transport, int p_authmode);
- Error init_server(int p_transport, int p_authmode, Ref<CryptoKeyMbedTLS> p_pkey, Ref<X509CertificateMbedTLS> p_cert, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
- Error init_client(int p_transport, int p_authmode, Ref<X509CertificateMbedTLS> p_valid_cas);
+ Error init_server(int p_transport, Ref<TLSOptions> p_options, Ref<CookieContextMbedTLS> p_cookies = Ref<CookieContextMbedTLS>());
+ Error init_client(int p_transport, const String &p_hostname, Ref<TLSOptions> p_options);
void clear();
mbedtls_ssl_context *get_context();
diff --git a/modules/minimp3/doc_classes/AudioStreamMP3.xml b/modules/minimp3/doc_classes/AudioStreamMP3.xml
index 8f03681c06..a88ff23b6b 100644
--- a/modules/minimp3/doc_classes/AudioStreamMP3.xml
+++ b/modules/minimp3/doc_classes/AudioStreamMP3.xml
@@ -21,21 +21,17 @@
[codeblocks]
[gdscript]
func load_mp3(path):
- var file = File.new()
- file.open(path, File.READ)
+ var file = FileAccess.open(path, FileAccess.READ)
var sound = AudioStreamMP3.new()
sound.data = file.get_buffer(file.get_length())
- file.close()
return sound
[/gdscript]
[csharp]
public AudioStreamMP3 LoadMP3(string path)
{
- var file = new File();
- file.Open(path, File.READ);
+ using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
var sound = new AudioStreamMP3();
sound.Data = file.GetBuffer(file.GetLength());
- file.Close();
return sound;
}
[/csharp]
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
index 0e92f4331d..af83cc24bf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Aabb.cs
@@ -50,6 +50,15 @@ namespace Godot
}
/// <summary>
+ /// The volume of this <see cref="Aabb"/>.
+ /// See also <see cref="HasVolume"/>.
+ /// </summary>
+ public readonly real_t Volume
+ {
+ get { return _size.X * _size.Y * _size.Z; }
+ }
+
+ /// <summary>
/// Returns an <see cref="Aabb"/> with equivalent position and size, modified so that
/// the most-negative corner is the origin and the size is positive.
/// </summary>
@@ -312,15 +321,6 @@ namespace Godot
}
/// <summary>
- /// Returns the volume of the <see cref="Aabb"/>.
- /// </summary>
- /// <returns>The volume.</returns>
- public readonly real_t GetVolume()
- {
- return _size.X * _size.Y * _size.Z;
- }
-
- /// <summary>
/// Returns a copy of the <see cref="Aabb"/> grown a given amount of units towards all the sides.
/// </summary>
/// <param name="by">The amount to grow by.</param>
@@ -383,7 +383,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Aabb"/> has
/// area, and <see langword="false"/> if the <see cref="Aabb"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetVolume"/>.
+ /// See also <see cref="Volume"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Aabb"/> has volume.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
index 859b47c276..ec2728140e 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Bridge/ScriptManagerBridge.cs
@@ -243,9 +243,33 @@ namespace Godot.Bridge
if (wrapperType == null)
{
- wrapperType = AppDomain.CurrentDomain.GetAssemblies()
- .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor")?
- .GetType("Godot." + nativeTypeNameStr);
+ wrapperType = GetTypeByGodotClassAttr(typeof(GodotObject).Assembly, nativeTypeNameStr);
+ }
+
+ if (wrapperType == null)
+ {
+ var editorAssembly = AppDomain.CurrentDomain.GetAssemblies()
+ .FirstOrDefault(a => a.GetName().Name == "GodotSharpEditor");
+ wrapperType = editorAssembly?.GetType("Godot." + nativeTypeNameStr);
+
+ if (wrapperType == null)
+ {
+ wrapperType = GetTypeByGodotClassAttr(editorAssembly, nativeTypeNameStr);
+ }
+ }
+
+ static Type? GetTypeByGodotClassAttr(Assembly assembly, string nativeTypeNameStr)
+ {
+ var types = assembly.GetTypes();
+ foreach (var type in types)
+ {
+ var attr = type.GetCustomAttribute<GodotClassNameAttribute>();
+ if (attr?.Name == nativeTypeNameStr)
+ {
+ return type;
+ }
+ }
+ return null;
}
static bool IsStatic(Type type) => type.IsAbstract && type.IsSealed;
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
index be4004a0f9..69444f8035 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2.cs
@@ -51,11 +51,11 @@ namespace Godot
/// <summary>
/// The area of this <see cref="Rect2"/>.
+ /// See also <see cref="HasArea"/>.
/// </summary>
- /// <value>Equivalent to <see cref="GetArea()"/>.</value>
public readonly real_t Area
{
- get { return GetArea(); }
+ get { return _size.X * _size.Y; }
}
/// <summary>
@@ -161,15 +161,6 @@ namespace Godot
}
/// <summary>
- /// Returns the area of the <see cref="Rect2"/>.
- /// </summary>
- /// <returns>The area.</returns>
- public readonly real_t GetArea()
- {
- return _size.X * _size.Y;
- }
-
- /// <summary>
/// Returns the center of the <see cref="Rect2"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// </summary>
@@ -247,7 +238,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Rect2"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetArea"/>.
+ /// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2"/> has area.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
index 5b06101db5..2099d0abca 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Rect2I.cs
@@ -51,11 +51,11 @@ namespace Godot
/// <summary>
/// The area of this <see cref="Rect2I"/>.
+ /// See also <see cref="HasArea"/>.
/// </summary>
- /// <value>Equivalent to <see cref="GetArea()"/>.</value>
public readonly int Area
{
- get { return GetArea(); }
+ get { return _size.X * _size.Y; }
}
/// <summary>
@@ -151,15 +151,6 @@ namespace Godot
}
/// <summary>
- /// Returns the area of the <see cref="Rect2I"/>.
- /// </summary>
- /// <returns>The area.</returns>
- public readonly int GetArea()
- {
- return _size.X * _size.Y;
- }
-
- /// <summary>
/// Returns the center of the <see cref="Rect2I"/>, which is equal
/// to <see cref="Position"/> + (<see cref="Size"/> / 2).
/// If <see cref="Size"/> is an odd number, the returned center
@@ -239,7 +230,7 @@ namespace Godot
/// Returns <see langword="true"/> if the <see cref="Rect2I"/> has
/// area, and <see langword="false"/> if the <see cref="Rect2I"/>
/// is linear, empty, or has a negative <see cref="Size"/>.
- /// See also <see cref="GetArea"/>.
+ /// See also <see cref="Area"/>.
/// </summary>
/// <returns>
/// A <see langword="bool"/> for whether or not the <see cref="Rect2I"/> has area.
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
index e939396926..5283dc7ec6 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Transform2D.cs
@@ -50,6 +50,18 @@ namespace Godot
}
/// <summary>
+ /// Returns the transform's skew (in radians).
+ /// </summary>
+ public readonly real_t Skew
+ {
+ get
+ {
+ real_t detSign = Mathf.Sign(BasisDeterminant());
+ return Mathf.Acos(X.Normalized().Dot(detSign * Y.Normalized())) - Mathf.Pi * 0.5f;
+ }
+ }
+
+ /// <summary>
/// Access whole columns in the form of <see cref="Vector2"/>.
/// The third column is the <see cref="Origin"/> vector.
/// </summary>
@@ -190,48 +202,13 @@ namespace Godot
/// <returns>The interpolated transform.</returns>
public readonly Transform2D InterpolateWith(Transform2D transform, real_t weight)
{
- real_t r1 = Rotation;
- real_t r2 = transform.Rotation;
-
- Vector2 s1 = Scale;
- Vector2 s2 = transform.Scale;
-
- // Slerp rotation
- (real_t sin1, real_t cos1) = Mathf.SinCos(r1);
- (real_t sin2, real_t cos2) = Mathf.SinCos(r2);
- var v1 = new Vector2(cos1, sin1);
- var v2 = new Vector2(cos2, sin2);
-
- real_t dot = v1.Dot(v2);
-
- dot = Mathf.Clamp(dot, -1.0f, 1.0f);
-
- Vector2 v;
-
- if (dot > 0.9995f)
- {
- // Linearly interpolate to avoid numerical precision issues
- v = v1.Lerp(v2, weight).Normalized();
- }
- else
- {
- real_t angle = weight * Mathf.Acos(dot);
- Vector2 v3 = (v2 - (v1 * dot)).Normalized();
- (real_t sine, real_t cos) = Mathf.SinCos(angle);
- v = (v1 * sine) + (v3 * cos);
- }
-
- // Extract parameters
- Vector2 p1 = Origin;
- Vector2 p2 = transform.Origin;
-
- // Construct matrix
- var res = new Transform2D(Mathf.Atan2(v.Y, v.X), p1.Lerp(p2, weight));
- Vector2 scale = s1.Lerp(s2, weight);
- res.X *= scale;
- res.Y *= scale;
-
- return res;
+ return new Transform2D
+ (
+ Mathf.LerpAngle(Rotation, transform.Rotation, weight),
+ Scale.Lerp(transform.Scale, weight),
+ Mathf.LerpAngle(Skew, transform.Skew, weight),
+ Origin.Lerp(transform.Origin, weight)
+ );
}
/// <summary>
@@ -294,7 +271,7 @@ namespace Godot
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform2D Rotated(real_t angle)
{
- return this * new Transform2D(angle, new Vector2());
+ return new Transform2D(angle, new Vector2()) * this;
}
/// <summary>
@@ -306,7 +283,7 @@ namespace Godot
/// <returns>The rotated transformation matrix.</returns>
public readonly Transform2D RotatedLocal(real_t angle)
{
- return new Transform2D(angle, new Vector2()) * this;
+ return this * new Transform2D(angle, new Vector2());
}
/// <summary>
diff --git a/modules/mono/utils/naming_utils.cpp b/modules/mono/utils/naming_utils.cpp
index dc9bc3485a..62fbf815f8 100644
--- a/modules/mono/utils/naming_utils.cpp
+++ b/modules/mono/utils/naming_utils.cpp
@@ -109,7 +109,7 @@ Vector<String> _split_pascal_case(const String &p_identifier) {
if (!is_digit(p_identifier[i])) {
// These conditions only apply when the separator is not a digit.
if (i - current_part_start == 1) {
- // Upper character was only the beggining of a word.
+ // Upper character was only the beginning of a word.
prev_was_upper = false;
continue;
}
diff --git a/modules/navigation/godot_navigation_server.cpp b/modules/navigation/godot_navigation_server.cpp
index 5baf6db2e8..d546c5d3ba 100644
--- a/modules/navigation/godot_navigation_server.cpp
+++ b/modules/navigation/godot_navigation_server.cpp
@@ -81,36 +81,6 @@ using namespace NavigationUtilities;
} \
void GodotNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
-#define COMMAND_4(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3) \
- struct MERGE(F_NAME, _command) : public SetCommand { \
- T_0 d_0; \
- T_1 d_1; \
- T_2 d_2; \
- T_3 d_3; \
- MERGE(F_NAME, _command) \
- ( \
- T_0 p_d_0, \
- T_1 p_d_1, \
- T_2 p_d_2, \
- T_3 p_d_3) : \
- d_0(p_d_0), \
- d_1(p_d_1), \
- d_2(p_d_2), \
- d_3(p_d_3) {} \
- virtual void exec(GodotNavigationServer *server) override { \
- server->MERGE(_cmd_, F_NAME)(d_0, d_1, d_2, d_3); \
- } \
- }; \
- void GodotNavigationServer::F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) { \
- auto cmd = memnew(MERGE(F_NAME, _command)( \
- D_0, \
- D_1, \
- D_2, \
- D_3)); \
- add_command(cmd); \
- } \
- void GodotNavigationServer::MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3)
-
GodotNavigationServer::GodotNavigationServer() {}
GodotNavigationServer::~GodotNavigationServer() {
@@ -711,17 +681,17 @@ bool GodotNavigationServer::agent_is_map_changed(RID p_agent) const {
return agent->is_map_changed();
}
-COMMAND_4(agent_set_callback, RID, p_agent, ObjectID, p_object_id, StringName, p_method, Variant, p_udata) {
+COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback) {
RvoAgent *agent = agent_owner.get_or_null(p_agent);
ERR_FAIL_COND(agent == nullptr);
- agent->set_callback(p_object_id, p_method, p_udata);
+ agent->set_callback(p_callback);
if (agent->get_map()) {
- if (p_object_id == ObjectID()) {
- agent->get_map()->remove_agent_as_controlled(agent);
- } else {
+ if (p_callback.is_valid()) {
agent->get_map()->set_agent_as_controlled(agent);
+ } else {
+ agent->get_map()->remove_agent_as_controlled(agent);
}
}
}
@@ -946,4 +916,3 @@ int GodotNavigationServer::get_process_info(ProcessInfo p_info) const {
#undef COMMAND_1
#undef COMMAND_2
-#undef COMMAND_4
diff --git a/modules/navigation/godot_navigation_server.h b/modules/navigation/godot_navigation_server.h
index efefa60de8..eea5713c40 100644
--- a/modules/navigation/godot_navigation_server.h
+++ b/modules/navigation/godot_navigation_server.h
@@ -54,10 +54,6 @@
virtual void F_NAME(T_0 D_0, T_1 D_1) override; \
void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1)
-#define COMMAND_4_DEF(F_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, D_3_DEF) \
- virtual void F_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3 = D_3_DEF) override; \
- void MERGE(_cmd_, F_NAME)(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3)
-
class GodotNavigationServer;
struct SetCommand {
@@ -182,7 +178,7 @@ public:
COMMAND_2(agent_set_position, RID, p_agent, Vector3, p_position);
COMMAND_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore);
virtual bool agent_is_map_changed(RID p_agent) const override;
- COMMAND_4_DEF(agent_set_callback, RID, p_agent, ObjectID, p_object_id, StringName, p_method, Variant, p_udata, Variant());
+ COMMAND_2(agent_set_callback, RID, p_agent, Callable, p_callback);
COMMAND_1(free, RID, p_object);
@@ -198,6 +194,5 @@ public:
#undef COMMAND_1
#undef COMMAND_2
-#undef COMMAND_4_DEF
#endif // GODOT_NAVIGATION_SERVER_H
diff --git a/modules/navigation/rvo_agent.cpp b/modules/navigation/rvo_agent.cpp
index 979ef0d917..40f1e925be 100644
--- a/modules/navigation/rvo_agent.cpp
+++ b/modules/navigation/rvo_agent.cpp
@@ -32,10 +32,6 @@
#include "nav_map.h"
-RvoAgent::RvoAgent() {
- callback.id = ObjectID();
-}
-
void RvoAgent::set_map(NavMap *p_map) {
map = p_map;
}
@@ -50,31 +46,25 @@ bool RvoAgent::is_map_changed() {
}
}
-void RvoAgent::set_callback(ObjectID p_id, const StringName p_method, const Variant p_udata) {
- callback.id = p_id;
- callback.method = p_method;
- callback.udata = p_udata;
+void RvoAgent::set_callback(Callable p_callback) {
+ callback = p_callback;
}
bool RvoAgent::has_callback() const {
- return callback.id.is_valid();
+ return callback.is_valid();
}
void RvoAgent::dispatch_callback() {
- if (callback.id.is_null()) {
+ if (!callback.is_valid()) {
return;
}
- Object *obj = ObjectDB::get_instance(callback.id);
- if (!obj) {
- callback.id = ObjectID();
- return;
- }
-
- Callable::CallError responseCallError;
- callback.new_velocity = Vector3(agent.newVelocity_.x(), agent.newVelocity_.y(), agent.newVelocity_.z());
+ Vector3 new_velocity = Vector3(agent.newVelocity_.x(), agent.newVelocity_.y(), agent.newVelocity_.z());
- const Variant *vp[2] = { &callback.new_velocity, &callback.udata };
- int argc = (callback.udata.get_type() == Variant::NIL) ? 1 : 2;
- obj->callp(callback.method, vp, argc, responseCallError);
+ // Invoke the callback with the new velocity.
+ Variant args[] = { new_velocity };
+ const Variant *args_p[] = { &args[0] };
+ Variant return_value;
+ Callable::CallError call_error;
+ callback.callp(args_p, 1, return_value, call_error);
}
diff --git a/modules/navigation/rvo_agent.h b/modules/navigation/rvo_agent.h
index 7b19907b2b..5f377b6079 100644
--- a/modules/navigation/rvo_agent.h
+++ b/modules/navigation/rvo_agent.h
@@ -39,21 +39,12 @@
class NavMap;
class RvoAgent : public NavRid {
- struct AvoidanceComputedCallback {
- ObjectID id;
- StringName method;
- Variant udata;
- Variant new_velocity;
- };
-
NavMap *map = nullptr;
RVO::Agent agent;
- AvoidanceComputedCallback callback;
+ Callable callback = Callable();
uint32_t map_update_id = 0;
public:
- RvoAgent();
-
void set_map(NavMap *p_map);
NavMap *get_map() {
return map;
@@ -65,7 +56,7 @@ public:
bool is_map_changed();
- void set_callback(ObjectID p_id, const StringName p_method, const Variant p_udata = Variant());
+ void set_callback(Callable p_callback);
bool has_callback() const;
void dispatch_callback();
diff --git a/modules/noise/doc_classes/Noise.xml b/modules/noise/doc_classes/Noise.xml
index 735ca388de..78d7d6a15b 100644
--- a/modules/noise/doc_classes/Noise.xml
+++ b/modules/noise/doc_classes/Noise.xml
@@ -17,8 +17,10 @@
<param index="1" name="height" type="int" />
<param index="2" name="invert" type="bool" default="false" />
<param index="3" name="in_3d_space" type="bool" default="false" />
+ <param index="4" name="normalize" type="bool" default="true" />
<description>
Returns a 2D [Image] noise image.
+ Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code].
</description>
</method>
<method name="get_noise_1d" qualifiers="const">
@@ -66,8 +68,10 @@
<param index="2" name="invert" type="bool" default="false" />
<param index="3" name="in_3d_space" type="bool" default="false" />
<param index="4" name="skirt" type="float" default="0.1" />
+ <param index="5" name="normalize" type="bool" default="true" />
<description>
Returns a seamless 2D [Image] noise image.
+ Note: With [param normalize] set to [code]false[/code] the default implementation expects the noise generator to return values in the range [code]-1.0[/code] to [code]1.0[/code].
</description>
</method>
</methods>
diff --git a/modules/noise/doc_classes/NoiseTexture2D.xml b/modules/noise/doc_classes/NoiseTexture2D.xml
index 0a800a143b..0f10a3f32f 100644
--- a/modules/noise/doc_classes/NoiseTexture2D.xml
+++ b/modules/noise/doc_classes/NoiseTexture2D.xml
@@ -44,6 +44,10 @@
<member name="noise" type="Noise" setter="set_noise" getter="get_noise">
The instance of the [Noise] object.
</member>
+ <member name="normalize" type="bool" setter="set_normalize" getter="is_normalized" default="true">
+ If [code]true[/code], the noise image coming from the noise generator is normalized to the range [code]0.0[/code] to [code]1.0[/code].
+ Turning normalization off can affect the contrast and allows you to generate non repeating tileable noise textures.
+ </member>
<member name="resource_local_to_scene" type="bool" setter="set_local_to_scene" getter="is_local_to_scene" overrides="Resource" default="false" />
<member name="seamless" type="bool" setter="set_seamless" getter="get_seamless" default="false">
If [code]true[/code], a seamless texture is requested from the [Noise] resource.
diff --git a/modules/noise/noise.cpp b/modules/noise/noise.cpp
index 5a901cb6e1..e95788b863 100644
--- a/modules/noise/noise.cpp
+++ b/modules/noise/noise.cpp
@@ -32,7 +32,7 @@
#include <float.h>
-Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt) const {
+Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, real_t p_blend_skirt, bool p_normalize) const {
ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>());
int skirt_width = MAX(1, p_width * p_blend_skirt);
@@ -40,7 +40,7 @@ Ref<Image> Noise::get_seamless_image(int p_width, int p_height, bool p_invert, b
int src_width = p_width + skirt_width;
int src_height = p_height + skirt_height;
- Ref<Image> src = get_image(src_width, src_height, p_invert, p_in_3d_space);
+ Ref<Image> src = get_image(src_width, src_height, p_invert, p_in_3d_space, p_normalize);
bool grayscale = (src->get_format() == Image::FORMAT_L8);
if (grayscale) {
return _generate_seamless_image<uint8_t>(src, p_width, p_height, p_invert, p_blend_skirt);
@@ -58,7 +58,7 @@ uint8_t Noise::_alpha_blend<uint8_t>(uint8_t p_bg, uint8_t p_fg, int p_alpha) co
return (uint8_t)((alpha * p_fg + inv_alpha * p_bg) >> 8);
}
-Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space) const {
+Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_3d_space, bool p_normalize) const {
ERR_FAIL_COND_V(p_width <= 0 || p_height <= 0, Ref<Image>());
Vector<uint8_t> data;
@@ -66,38 +66,49 @@ Ref<Image> Noise::get_image(int p_width, int p_height, bool p_invert, bool p_in_
uint8_t *wd8 = data.ptrw();
- // Get all values and identify min/max values.
- Vector<real_t> values;
- values.resize(p_width * p_height);
- real_t min_val = FLT_MAX;
- real_t max_val = -FLT_MAX;
-
- for (int y = 0, i = 0; y < p_height; y++) {
- for (int x = 0; x < p_width; x++, i++) {
- values.set(i, p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y));
- if (values[i] > max_val) {
- max_val = values[i];
- }
- if (values[i] < min_val) {
- min_val = values[i];
+ if (p_normalize) {
+ // Get all values and identify min/max values.
+ Vector<real_t> values;
+ values.resize(p_width * p_height);
+ real_t min_val = FLT_MAX;
+ real_t max_val = -FLT_MAX;
+ for (int y = 0, i = 0; y < p_height; y++) {
+ for (int x = 0; x < p_width; x++, i++) {
+ values.set(i, p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y));
+ if (values[i] > max_val) {
+ max_val = values[i];
+ }
+ if (values[i] < min_val) {
+ min_val = values[i];
+ }
}
}
- }
-
- // Normalize values and write to texture.
- uint8_t value;
- for (int i = 0, x = 0; i < p_height; i++) {
- for (int j = 0; j < p_width; j++, x++) {
- if (max_val == min_val) {
- value = 0;
- } else {
- value = uint8_t(CLAMP((values[x] - min_val) / (max_val - min_val) * 255.f, 0, 255));
+ // Normalize values and write to texture.
+ uint8_t ivalue;
+ for (int i = 0, x = 0; i < p_height; i++) {
+ for (int j = 0; j < p_width; j++, x++) {
+ if (max_val == min_val) {
+ ivalue = 0;
+ } else {
+ ivalue = static_cast<uint8_t>(CLAMP((values[x] - min_val) / (max_val - min_val) * 255.f, 0, 255));
+ }
+
+ if (p_invert) {
+ ivalue = 255 - ivalue;
+ }
+
+ wd8[x] = ivalue;
}
- if (p_invert) {
- value = 255 - value;
+ }
+ } else {
+ // Without normalization, the expected range of the noise function is [-1, 1].
+ uint8_t ivalue;
+ for (int y = 0, i = 0; y < p_height; y++) {
+ for (int x = 0; x < p_width; x++, i++) {
+ float value = (p_in_3d_space ? get_noise_3d(x, y, 0.0) : get_noise_2d(x, y));
+ ivalue = static_cast<uint8_t>(CLAMP(value * 127.5f + 127.5f, 0.0f, 255.0f));
+ wd8[i] = p_invert ? (255 - ivalue) : ivalue;
}
-
- wd8[x] = value;
}
}
@@ -113,6 +124,6 @@ void Noise::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_noise_3dv", "v"), &Noise::get_noise_3dv);
// Textures.
- ClassDB::bind_method(D_METHOD("get_image", "width", "height", "invert", "in_3d_space"), &Noise::get_image, DEFVAL(false), DEFVAL(false));
- ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "invert", "in_3d_space", "skirt"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1));
+ ClassDB::bind_method(D_METHOD("get_image", "width", "height", "invert", "in_3d_space", "normalize"), &Noise::get_image, DEFVAL(false), DEFVAL(false), DEFVAL(true));
+ ClassDB::bind_method(D_METHOD("get_seamless_image", "width", "height", "invert", "in_3d_space", "skirt", "normalize"), &Noise::get_seamless_image, DEFVAL(false), DEFVAL(false), DEFVAL(0.1), DEFVAL(true));
}
diff --git a/modules/noise/noise.h b/modules/noise/noise.h
index 8f8ecf29a5..f7e615c2aa 100644
--- a/modules/noise/noise.h
+++ b/modules/noise/noise.h
@@ -233,8 +233,8 @@ public:
virtual real_t get_noise_3dv(Vector3 p_v) const = 0;
virtual real_t get_noise_3d(real_t p_x, real_t p_y, real_t p_z) const = 0;
- virtual Ref<Image> get_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false) const;
- virtual Ref<Image> get_seamless_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1) const;
+ virtual Ref<Image> get_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, bool p_normalize = true) const;
+ virtual Ref<Image> get_seamless_image(int p_width, int p_height, bool p_invert = false, bool p_in_3d_space = false, real_t p_blend_skirt = 0.1, bool p_normalize = true) const;
};
#endif // NOISE_H
diff --git a/modules/noise/noise_texture_2d.cpp b/modules/noise/noise_texture_2d.cpp
index 0eedb286bd..0d5e778875 100644
--- a/modules/noise/noise_texture_2d.cpp
+++ b/modules/noise/noise_texture_2d.cpp
@@ -76,6 +76,9 @@ void NoiseTexture2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_bump_strength", "bump_strength"), &NoiseTexture2D::set_bump_strength);
ClassDB::bind_method(D_METHOD("get_bump_strength"), &NoiseTexture2D::get_bump_strength);
+ ClassDB::bind_method(D_METHOD("set_normalize", "normalize"), &NoiseTexture2D::set_normalize);
+ ClassDB::bind_method(D_METHOD("is_normalized"), &NoiseTexture2D::is_normalized);
+
ClassDB::bind_method(D_METHOD("set_color_ramp", "gradient"), &NoiseTexture2D::set_color_ramp);
ClassDB::bind_method(D_METHOD("get_color_ramp"), &NoiseTexture2D::get_color_ramp);
@@ -91,6 +94,7 @@ void NoiseTexture2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "seamless_blend_skirt", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_seamless_blend_skirt", "get_seamless_blend_skirt");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "as_normal_map"), "set_as_normal_map", "is_normal_map");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "bump_strength", PROPERTY_HINT_RANGE, "0,32,0.1,or_greater"), "set_bump_strength", "get_bump_strength");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "normalize"), "set_normalize", "is_normalized");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "color_ramp", PROPERTY_HINT_RESOURCE_TYPE, "Gradient"), "set_color_ramp", "get_color_ramp");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "noise", PROPERTY_HINT_RESOURCE_TYPE, "Noise"), "set_noise", "get_noise");
}
@@ -156,9 +160,9 @@ Ref<Image> NoiseTexture2D::_generate_texture() {
Ref<Image> new_image;
if (seamless) {
- new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt);
+ new_image = ref_noise->get_seamless_image(size.x, size.y, invert, in_3d_space, seamless_blend_skirt, normalize);
} else {
- new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space);
+ new_image = ref_noise->get_image(size.x, size.y, invert, in_3d_space, normalize);
}
if (color_ramp.is_valid()) {
new_image = _modulate_with_gradient(new_image, color_ramp);
@@ -349,6 +353,18 @@ void NoiseTexture2D::set_color_ramp(const Ref<Gradient> &p_gradient) {
_queue_update();
}
+void NoiseTexture2D::set_normalize(bool p_normalize) {
+ if (normalize == p_normalize) {
+ return;
+ }
+ normalize = p_normalize;
+ _queue_update();
+}
+
+bool NoiseTexture2D::is_normalized() const {
+ return normalize;
+}
+
Ref<Gradient> NoiseTexture2D::get_color_ramp() const {
return color_ramp;
}
diff --git a/modules/noise/noise_texture_2d.h b/modules/noise/noise_texture_2d.h
index cda14df6c2..f53670b690 100644
--- a/modules/noise/noise_texture_2d.h
+++ b/modules/noise/noise_texture_2d.h
@@ -59,6 +59,7 @@ private:
real_t seamless_blend_skirt = 0.1;
bool as_normal_map = false;
float bump_strength = 8.0;
+ bool normalize = true;
Ref<Gradient> color_ramp;
Ref<Noise> noise;
@@ -105,6 +106,9 @@ public:
void set_bump_strength(float p_bump_strength);
float get_bump_strength();
+ void set_normalize(bool p_normalize);
+ bool is_normalized() const;
+
void set_color_ramp(const Ref<Gradient> &p_gradient);
Ref<Gradient> get_color_ramp() const;
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index 7e896a0ca3..aaeb2025ee 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -13,10 +13,9 @@
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
- <param index="1" name="verify_tls" type="bool" default="true" />
- <param index="2" name="tls_certificate" type="X509Certificate" default="null" />
+ <param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
- Starts a new multiplayer client connecting to the given [param url]. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param tls_certificate] will be used to verify the TLS host.
+ Starts a new multiplayer client connecting to the given [param url]. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
[b]Note[/b]: It is recommended to specify the scheme part of the URL, i.e. the [param url] should start with either [code]ws://[/code] or [code]wss://[/code].
</description>
</method>
@@ -24,10 +23,9 @@
<return type="int" enum="Error" />
<param index="0" name="port" type="int" />
<param index="1" name="bind_address" type="String" default="&quot;*&quot;" />
- <param index="2" name="tls_key" type="CryptoKey" default="null" />
- <param index="3" name="tls_certificate" type="X509Certificate" default="null" />
+ <param index="2" name="tls_server_options" type="TLSOptions" default="null" />
<description>
- Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide a [param tls_key] and [param tls_certificate] to use TLS.
+ Starts a new multiplayer server listening on the given [param port]. You can optionally specify a [param bind_address], and provide valiid [param tls_server_options] to use TLS. See [method TLSOptions.server].
</description>
</method>
<method name="get_peer" qualifiers="const">
diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml
index 41d166a0f5..0f8c27c4cc 100644
--- a/modules/websocket/doc_classes/WebSocketPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketPeer.xml
@@ -58,10 +58,9 @@
<method name="connect_to_url">
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
- <param index="1" name="verify_tls" type="bool" default="true" />
- <param index="2" name="trusted_tls_certificate" type="X509Certificate" default="null" />
+ <param index="1" name="tls_client_options" type="TLSOptions" default="null" />
<description>
- Connects to the given URL. If [param verify_tls] is [code]false[/code] certificate validation will be disabled. If specified, the [param trusted_tls_certificate] will be the only one accepted when connecting to a TLS host.
+ Connects to the given URL. TLS certificates will be verified against the hostname when connecting using the [code]wss://[/code] protocol. You can pass the optional [param tls_client_options] parameter to customize the trusted certification authorities, or disable the common name verification. See [method TLSOptions.client] and [method TLSOptions.client_unsafe].
[b]Note:[/b] To avoid mixed content warnings or errors in Web, you may have to use a [code]url[/code] that starts with [code]wss://[/code] (secure) instead of [code]ws://[/code]. When doing so, make sure to use the fully qualified domain name that matches the one defined in the server's TLS certificate. Do not connect directly via the IP address for [code]wss://[/code] connections, as it won't match with the TLS certificate.
</description>
</method>
diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp
index 1ec557427f..7b14a3a61d 100644
--- a/modules/websocket/emws_peer.cpp
+++ b/modules/websocket/emws_peer.cpp
@@ -58,7 +58,8 @@ void EMWSPeer::_esws_on_close(void *p_obj, int p_code, const char *p_reason, int
peer->ready_state = STATE_CLOSED;
}
-Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) {
+Error EMWSPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_options) {
+ ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER);
ERR_FAIL_COND_V(ready_state != STATE_CLOSED, ERR_ALREADY_IN_USE);
_clear();
@@ -85,9 +86,6 @@ Error EMWSPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509C
if (handshake_headers.size()) {
WARN_PRINT_ONCE("Custom headers are not supported in Web platform.");
}
- if (p_tls_certificate.is_valid()) {
- WARN_PRINT_ONCE("Custom SSL certificates are not supported in Web platform.");
- }
requested_url = scheme + host;
diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h
index 66c5283d5c..08b2dad977 100644
--- a/modules/websocket/emws_peer.h
+++ b/modules/websocket/emws_peer.h
@@ -86,7 +86,7 @@ public:
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
- virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override;
+ virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_tls_client_options) override;
virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
virtual void poll() override;
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 36b4215f8c..389d8c56ad 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -54,11 +54,9 @@ void WebSocketMultiplayerPeer::_clear() {
connection_status = CONNECTION_DISCONNECTED;
unique_id = 0;
peers_map.clear();
- use_tls = false;
tcp_server.unref();
pending_peers.clear();
- tls_certificate.unref();
- tls_key.unref();
+ tls_server_options.unref();
if (current_packet.data != nullptr) {
memfree(current_packet.data);
current_packet.data = nullptr;
@@ -73,8 +71,8 @@ void WebSocketMultiplayerPeer::_clear() {
}
void WebSocketMultiplayerPeer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("create_client", "url", "verify_tls", "tls_certificate"), &WebSocketMultiplayerPeer::create_client, DEFVAL(true), DEFVAL(Ref<X509Certificate>()));
- ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_key", "tls_certificate"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<CryptoKey>()), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("create_client", "url", "tls_client_options"), &WebSocketMultiplayerPeer::create_client, DEFVAL(Ref<TLSOptions>()));
+ ClassDB::bind_method(D_METHOD("create_server", "port", "bind_address", "tls_server_options"), &WebSocketMultiplayerPeer::create_server, DEFVAL("*"), DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer);
ClassDB::bind_method(D_METHOD("get_peer_address", "id"), &WebSocketMultiplayerPeer::get_peer_address);
@@ -179,8 +177,9 @@ int WebSocketMultiplayerPeer::get_max_packet_size() const {
return get_outbound_buffer_size() - PROTO_SIZE;
}
-Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate) {
+Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(p_options.is_valid() && !p_options->is_server(), ERR_INVALID_PARAMETER);
_clear();
tcp_server.instantiate();
Error err = tcp_server->listen(p_port, p_bind_ip);
@@ -190,20 +189,16 @@ Error WebSocketMultiplayerPeer::create_server(int p_port, IPAddress p_bind_ip, R
}
unique_id = 1;
connection_status = CONNECTION_CONNECTED;
- // TLS config
- tls_key = p_tls_key;
- tls_certificate = p_tls_certificate;
- if (tls_key.is_valid() && tls_certificate.is_valid()) {
- use_tls = true;
- }
+ tls_server_options = p_options;
return OK;
}
-Error WebSocketMultiplayerPeer::create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate) {
+Error WebSocketMultiplayerPeer::create_client(const String &p_url, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(get_connection_status() != CONNECTION_DISCONNECTED, ERR_ALREADY_IN_USE);
+ ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER);
_clear();
Ref<WebSocketPeer> peer = _create_peer();
- Error err = peer->connect_to_url(p_url, p_verify_tls, p_tls_certificate);
+ Error err = peer->connect_to_url(p_url, p_options);
if (err != OK) {
return err;
}
@@ -334,14 +329,14 @@ void WebSocketMultiplayerPeer::_poll_server() {
to_remove.insert(id); // Error.
continue;
}
- if (!use_tls) {
+ if (tls_server_options.is_null()) {
peer.ws = _create_peer();
peer.ws->accept_stream(peer.tcp);
continue;
} else {
if (peer.connection == peer.tcp) {
Ref<StreamPeerTLS> tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
- Error err = tls->accept_stream(peer.tcp, tls_key, tls_certificate);
+ Error err = tls->accept_stream(peer.tcp, tls_server_options);
if (err != OK) {
to_remove.insert(id);
continue;
diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h
index ea10e8799f..22f1bc939b 100644
--- a/modules/websocket/websocket_multiplayer_peer.h
+++ b/modules/websocket/websocket_multiplayer_peer.h
@@ -71,9 +71,7 @@ protected:
Ref<WebSocketPeer> peer_config;
HashMap<int, PendingPeer> pending_peers;
Ref<TCPServer> tcp_server;
- bool use_tls = false;
- Ref<X509Certificate> tls_certificate;
- Ref<CryptoKey> tls_key;
+ Ref<TLSOptions> tls_server_options;
ConnectionStatus connection_status = CONNECTION_DISCONNECTED;
@@ -115,8 +113,8 @@ public:
/* WebSocketPeer */
virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const;
- Error create_client(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_tls_certificate);
- Error create_server(int p_port, IPAddress p_bind_ip, Ref<CryptoKey> p_tls_key, Ref<X509Certificate> p_tls_certificate);
+ Error create_client(const String &p_url, Ref<TLSOptions> p_options);
+ Error create_server(int p_port, IPAddress p_bind_ip, Ref<TLSOptions> p_options);
void set_supported_protocols(const Vector<String> &p_protocols);
Vector<String> get_supported_protocols() const;
diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp
index d10315f64c..3c0d316bc9 100644
--- a/modules/websocket/websocket_peer.cpp
+++ b/modules/websocket/websocket_peer.cpp
@@ -39,7 +39,7 @@ WebSocketPeer::~WebSocketPeer() {
}
void WebSocketPeer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("connect_to_url", "url", "verify_tls", "trusted_tls_certificate"), &WebSocketPeer::connect_to_url, DEFVAL(true), DEFVAL(Ref<X509Certificate>()));
+ ClassDB::bind_method(D_METHOD("connect_to_url", "url", "tls_client_options"), &WebSocketPeer::connect_to_url, DEFVAL(Ref<TLSOptions>()));
ClassDB::bind_method(D_METHOD("accept_stream", "stream"), &WebSocketPeer::accept_stream);
ClassDB::bind_method(D_METHOD("send", "message", "write_mode"), &WebSocketPeer::_send_bind, DEFVAL(WRITE_MODE_BINARY));
ClassDB::bind_method(D_METHOD("send_text", "message"), &WebSocketPeer::send_text);
diff --git a/modules/websocket/websocket_peer.h b/modules/websocket/websocket_peer.h
index 3a1527b769..3110e87071 100644
--- a/modules/websocket/websocket_peer.h
+++ b/modules/websocket/websocket_peer.h
@@ -81,7 +81,7 @@ public:
return _create();
}
- virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) { return ERR_UNAVAILABLE; };
+ virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) = 0;
virtual Error accept_stream(Ref<StreamPeer> p_stream) = 0;
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) = 0;
diff --git a/modules/websocket/wsl_peer.cpp b/modules/websocket/wsl_peer.cpp
index 9ba286d5ee..8a150c8561 100644
--- a/modules/websocket/wsl_peer.cpp
+++ b/modules/websocket/wsl_peer.cpp
@@ -333,8 +333,7 @@ void WSLPeer::_do_client_handshake() {
// Start SSL handshake
tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
ERR_FAIL_COND_MSG(tls.is_null(), "SSL is not available in this build.");
- tls->set_blocking_handshake_enabled(false);
- if (tls->connect_to_stream(tcp, verify_tls, requested_host, tls_cert) != OK) {
+ if (tls->connect_to_stream(tcp, requested_host, tls_options) != OK) {
close(-1);
return; // Error.
}
@@ -476,9 +475,10 @@ bool WSLPeer::_verify_server_response() {
return true;
}
-Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Certificate> p_cert) {
+Error WSLPeer::connect_to_url(const String &p_url, Ref<TLSOptions> p_options) {
ERR_FAIL_COND_V(wsl_ctx || tcp.is_valid(), ERR_ALREADY_IN_USE);
ERR_FAIL_COND_V(p_url.is_empty(), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_options.is_valid() && p_options->is_server(), ERR_INVALID_PARAMETER);
_clear();
@@ -506,8 +506,13 @@ Error WSLPeer::connect_to_url(const String &p_url, bool p_verify_tls, Ref<X509Ce
requested_url = p_url;
requested_host = host;
- verify_tls = p_verify_tls;
- tls_cert = p_cert;
+
+ if (p_options.is_valid()) {
+ tls_options = p_options;
+ } else {
+ tls_options = TLSOptions::client();
+ }
+
tcp.instantiate();
resolver.start(host, port);
diff --git a/modules/websocket/wsl_peer.h b/modules/websocket/wsl_peer.h
index fc81d39a37..c06e768b6d 100644
--- a/modules/websocket/wsl_peer.h
+++ b/modules/websocket/wsl_peer.h
@@ -102,8 +102,7 @@ private:
// WebSocket configuration.
bool use_tls = true;
- bool verify_tls = true;
- Ref<X509Certificate> tls_cert;
+ Ref<TLSOptions> tls_options;
// Packet buffers.
Vector<uint8_t> packet_buffer;
@@ -132,7 +131,7 @@ public:
// WebSocketPeer
virtual Error send(const uint8_t *p_buffer, int p_buffer_size, WriteMode p_mode) override;
- virtual Error connect_to_url(const String &p_url, bool p_verify_tls = true, Ref<X509Certificate> p_cert = Ref<X509Certificate>()) override;
+ virtual Error connect_to_url(const String &p_url, Ref<TLSOptions> p_options = Ref<TLSOptions>()) override;
virtual Error accept_stream(Ref<StreamPeer> p_stream) override;
virtual void close(int p_code = 1000, String p_reason = "") override;
virtual void poll() override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 587caf81bf..a23f7d1d02 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -1076,11 +1076,6 @@ void EditorExportPlatformAndroid::_fix_manifest(const Ref<EditorExportPreset> &p
Vector<int> feature_versions;
if (xr_mode_index == XR_MODE_OPENXR) {
- // Set degrees of freedom
- feature_names.push_back("android.hardware.vr.headtracking");
- feature_required_list.push_back(true);
- feature_versions.push_back(1);
-
// Check for hand tracking
if (hand_tracking_index > XR_HAND_TRACKING_NONE) {
feature_names.push_back("oculus.software.handtracking");
diff --git a/platform/android/export/gradle_export_util.cpp b/platform/android/export/gradle_export_util.cpp
index e450f3edb3..4fdcca68e9 100644
--- a/platform/android/export/gradle_export_util.cpp
+++ b/platform/android/export/gradle_export_util.cpp
@@ -259,8 +259,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
bool uses_xr = xr_mode_index == XR_MODE_OPENXR;
if (uses_xr) {
- manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
-
int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
if (hand_tracking_index == XR_HAND_TRACKING_OPTIONAL) {
manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
@@ -279,8 +277,6 @@ String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
}
String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
- int xr_mode_index = (int)(p_preset->get("xr_features/xr_mode"));
- bool uses_xr = xr_mode_index == XR_MODE_OPENXR;
String orientation = _get_android_orientation_label(DisplayServer::ScreenOrientation(int(GLOBAL_GET("display/window/handheld/orientation"))));
String manifest_activity_text = vformat(
" <activity android:name=\"com.godot.game.GodotApp\" "
@@ -291,11 +287,6 @@ String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
bool_to_string(p_preset->get("package/exclude_from_recents")),
orientation,
bool_to_string(bool(GLOBAL_GET("display/window/size/resizable"))));
- if (uses_xr) {
- manifest_activity_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"true\" />\n";
- } else {
- manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
- }
manifest_activity_text += " </activity>\n";
return manifest_activity_text;
}
@@ -335,8 +326,6 @@ String _get_application_tag(const Ref<EditorExportPreset> &p_preset, bool p_has_
hand_tracking_frequency);
manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.oculus.handtracking.version\" android:value=\"V2.0\" />\n";
}
- } else {
- manifest_application_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.supportedDevices\" />\n";
}
manifest_application_text += _get_activity_tag(p_preset);
manifest_application_text += " </application>\n";
diff --git a/platform/android/java/app/AndroidManifest.xml b/platform/android/java/app/AndroidManifest.xml
index 8c8608cbbb..1969f9c814 100644
--- a/platform/android/java/app/AndroidManifest.xml
+++ b/platform/android/java/app/AndroidManifest.xml
@@ -48,12 +48,6 @@
android:name="xr_hand_tracking_version_name"
android:value="xr_hand_tracking_version_value"/>
- <!-- Supported Meta devices -->
- <!-- This is removed by the exporter if the xr mode is not VR. -->
- <meta-data
- android:name="com.oculus.supportedDevices"
- android:value="all" />
-
<activity
android:name=".GodotApp"
android:label="@string/godot_project_name_string"
@@ -66,9 +60,6 @@
android:resizeableActivity="false"
tools:ignore="UnusedAttribute" >
- <!-- Focus awareness metadata is removed at export time if the xr mode is not VR. -->
- <meta-data android:name="com.oculus.vr.focusaware" android:value="true" />
-
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/platform/linuxbsd/dbus-so_wrap.c b/platform/linuxbsd/dbus-so_wrap.c
index d03a9166a3..4aec9dc48f 100644
--- a/platform/linuxbsd/dbus-so_wrap.c
+++ b/platform/linuxbsd/dbus-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:35
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/dbus/dbus.h --sys-include "thirdparty/linuxbsd_headers/dbus/dbus.h" --soname libdbus-1.so.3 --init-name dbus --output-header ./platform/linuxbsd/dbus-so_wrap.h --output-implementation ./platform/linuxbsd/dbus-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:35
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/dbus/dbus.h --sys-include "thirdparty/linuxbsd_headers/dbus/dbus.h" --soname libdbus-1.so.3 --init-name dbus --output-header ./platform/linuxbsd/dbus-so_wrap.h --output-implementation ./platform/linuxbsd/dbus-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/dbus-so_wrap.h b/platform/linuxbsd/dbus-so_wrap.h
index 6981912a12..2c63757932 100644
--- a/platform/linuxbsd/dbus-so_wrap.h
+++ b/platform/linuxbsd/dbus-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_DBUS
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:26:35
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/dbus/dbus.h --sys-include "thirdparty/linuxbsd_headers/dbus/dbus.h" --soname libdbus-1.so.3 --init-name dbus --output-header ./platform/linuxbsd/dbus-so_wrap.h --output-implementation ./platform/linuxbsd/dbus-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:26:35
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/dbus/dbus.h --sys-include "thirdparty/linuxbsd_headers/dbus/dbus.h" --soname libdbus-1.so.3 --init-name dbus --output-header ./platform/linuxbsd/dbus-so_wrap.h --output-implementation ./platform/linuxbsd/dbus-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/fontconfig-so_wrap.c b/platform/linuxbsd/fontconfig-so_wrap.c
index 6278522b69..86aacbc647 100644
--- a/platform/linuxbsd/fontconfig-so_wrap.c
+++ b/platform/linuxbsd/fontconfig-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:15:54
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/fontconfig/fontconfig.h --sys-include "thirdparty/linuxbsd_headers/fontconfig/fontconfig.h" --soname libfontconfig.so.1 --init-name fontconfig --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext --output-header ./platform/linuxbsd/fontconfig-so_wrap.h --output-implementation ./platform/linuxbsd/fontconfig-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:15:54
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/fontconfig/fontconfig.h --sys-include "thirdparty/linuxbsd_headers/fontconfig/fontconfig.h" --soname libfontconfig.so.1 --init-name fontconfig --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext --output-header ./platform/linuxbsd/fontconfig-so_wrap.h --output-implementation ./platform/linuxbsd/fontconfig-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/fontconfig-so_wrap.h b/platform/linuxbsd/fontconfig-so_wrap.h
index 021b2148ae..956c094711 100644
--- a/platform/linuxbsd/fontconfig-so_wrap.h
+++ b/platform/linuxbsd/fontconfig-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_FONTCONFIG
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:15:54
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/fontconfig/fontconfig.h --sys-include "thirdparty/linuxbsd_headers/fontconfig/fontconfig.h" --soname libfontconfig.so.1 --init-name fontconfig --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext --output-header ./platform/linuxbsd/fontconfig-so_wrap.h --output-implementation ./platform/linuxbsd/fontconfig-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:15:54
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/fontconfig/fontconfig.h --sys-include "thirdparty/linuxbsd_headers/fontconfig/fontconfig.h" --soname libfontconfig.so.1 --init-name fontconfig --omit-prefix FcCharSetFirst --omit-prefix FcCharSetNext --output-header ./platform/linuxbsd/fontconfig-so_wrap.h --output-implementation ./platform/linuxbsd/fontconfig-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/libudev-so_wrap.c b/platform/linuxbsd/libudev-so_wrap.c
index 9593b5fb0f..5455c1ab4e 100644
--- a/platform/linuxbsd/libudev-so_wrap.c
+++ b/platform/linuxbsd/libudev-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:23:01
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/udev/libudev.h --sys-include "thirdparty/linuxbsd_headers/udev/libudev.h" --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header ./platform/linuxbsd/libudev-so_wrap.h --output-implementation ./platform/linuxbsd/libudev-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:23:01
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/udev/libudev.h --sys-include "thirdparty/linuxbsd_headers/udev/libudev.h" --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header ./platform/linuxbsd/libudev-so_wrap.h --output-implementation ./platform/linuxbsd/libudev-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/libudev-so_wrap.h b/platform/linuxbsd/libudev-so_wrap.h
index 9b9627877a..174a3663d4 100644
--- a/platform/linuxbsd/libudev-so_wrap.h
+++ b/platform/linuxbsd/libudev-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_LIBUDEV
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:23:01
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/udev/libudev.h --sys-include "thirdparty/linuxbsd_headers/udev/libudev.h" --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header ./platform/linuxbsd/libudev-so_wrap.h --output-implementation ./platform/linuxbsd/libudev-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:23:01
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/udev/libudev.h --sys-include "thirdparty/linuxbsd_headers/udev/libudev.h" --soname libudev.so.1 --init-name libudev --omit-prefix gnu_ --output-header ./platform/linuxbsd/libudev-so_wrap.h --output-implementation ./platform/linuxbsd/libudev-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/speechd-so_wrap.c b/platform/linuxbsd/speechd-so_wrap.c
index c860c686f0..1dc5f08c10 100644
--- a/platform/linuxbsd/speechd-so_wrap.c
+++ b/platform/linuxbsd/speechd-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:07:46
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/speechd/libspeechd.h --sys-include "thirdparty/linuxbsd_headers/speechd/libspeechd.h" --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header ./platform/linuxbsd/speechd-so_wrap.h --output-implementation ./platform/linuxbsd/speechd-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:07:46
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/speechd/libspeechd.h --sys-include "thirdparty/linuxbsd_headers/speechd/libspeechd.h" --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header ./platform/linuxbsd/speechd-so_wrap.h --output-implementation ./platform/linuxbsd/speechd-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/speechd-so_wrap.h b/platform/linuxbsd/speechd-so_wrap.h
index 8e0762041a..2967cfa929 100644
--- a/platform/linuxbsd/speechd-so_wrap.h
+++ b/platform/linuxbsd/speechd-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_SPEECHD
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-12 10:07:46
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/speechd/libspeechd.h --sys-include "thirdparty/linuxbsd_headers/speechd/libspeechd.h" --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header ./platform/linuxbsd/speechd-so_wrap.h --output-implementation ./platform/linuxbsd/speechd-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-12 10:07:46
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/speechd/libspeechd.h --sys-include "thirdparty/linuxbsd_headers/speechd/libspeechd.h" --soname libspeechd.so.2 --init-name speechd --omit-prefix spd_get_client_list --output-header ./platform/linuxbsd/speechd-so_wrap.h --output-implementation ./platform/linuxbsd/speechd-so_wrap.c
//
#include <stdint.h>
diff --git a/platform/linuxbsd/x11/display_server_x11.cpp b/platform/linuxbsd/x11/display_server_x11.cpp
index 428cf3a145..c09da2f7b3 100644
--- a/platform/linuxbsd/x11/display_server_x11.cpp
+++ b/platform/linuxbsd/x11/display_server_x11.cpp
@@ -1324,14 +1324,20 @@ void DisplayServerX11::delete_sub_window(WindowID p_id) {
}
#endif
- XDestroyWindow(x11_display, wd.x11_xim_window);
-
- XUnmapWindow(x11_display, wd.x11_window);
- XDestroyWindow(x11_display, wd.x11_window);
if (wd.xic) {
XDestroyIC(wd.xic);
wd.xic = nullptr;
}
+ XDestroyWindow(x11_display, wd.x11_xim_window);
+ if (xkb_loaded) {
+ if (wd.xkb_state) {
+ xkb_compose_state_unref(wd.xkb_state);
+ wd.xkb_state = nullptr;
+ }
+ }
+
+ XUnmapWindow(x11_display, wd.x11_window);
+ XDestroyWindow(x11_display, wd.x11_window);
windows.erase(p_id);
}
@@ -1610,7 +1616,7 @@ void DisplayServerX11::window_set_transient(WindowID p_window, WindowID p_parent
// a subwindow and its parent are both destroyed.
if (!wd_window.no_focus && !wd_window.is_popup && wd_window.focused) {
if ((xwa.map_state == IsViewable) && !wd_parent.no_focus && !wd_window.is_popup) {
- XSetInputFocus(x11_display, wd_parent.x11_window, RevertToParent, CurrentTime);
+ XSetInputFocus(x11_display, wd_parent.x11_window, RevertToPointerRoot, CurrentTime);
}
}
} else {
@@ -2515,7 +2521,7 @@ void DisplayServerX11::window_set_ime_active(const bool p_active, WindowID p_win
XSync(x11_display, False);
XGetWindowAttributes(x11_display, wd.x11_xim_window, &xwa);
if (xwa.map_state == IsViewable) {
- XSetInputFocus(x11_display, wd.x11_xim_window, RevertToPointerRoot, CurrentTime);
+ XSetInputFocus(x11_display, wd.x11_xim_window, RevertToParent, CurrentTime);
}
XSetICFocus(wd.xic);
} else {
@@ -2936,7 +2942,7 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_keycode, nullptr);
String keysym;
- if (xkb_keysym_to_utf32 && xkb_keysym_to_upper) {
+ if (xkb_loaded) {
KeySym keysym_unicode_nm = 0; // keysym used to find unicode
XLookupString(&xkeyevent_no_mod, nullptr, 0, &keysym_unicode_nm, nullptr);
keysym = String::chr(xkb_keysym_to_utf32(xkb_keysym_to_upper(keysym_unicode_nm)));
@@ -3029,6 +3035,64 @@ void DisplayServerX11::_handle_key_event(WindowID p_window, XKeyEvent *p_event,
}
} while (status == XBufferOverflow);
#endif
+ } else if (xkeyevent->type == KeyPress && wd.xkb_state && xkb_loaded) {
+ xkb_compose_feed_result res = xkb_compose_state_feed(wd.xkb_state, keysym_unicode);
+ if (res == XKB_COMPOSE_FEED_ACCEPTED) {
+ if (xkb_compose_state_get_status(wd.xkb_state) == XKB_COMPOSE_COMPOSED) {
+ bool keypress = xkeyevent->type == KeyPress;
+ Key keycode = KeyMappingX11::get_keycode(keysym_keycode);
+ Key physical_keycode = KeyMappingX11::get_scancode(xkeyevent->keycode);
+
+ if (keycode >= Key::A + 32 && keycode <= Key::Z + 32) {
+ keycode -= 'a' - 'A';
+ }
+
+ char str_xkb[256] = {};
+ int str_xkb_size = xkb_compose_state_get_utf8(wd.xkb_state, str_xkb, 255);
+
+ String tmp;
+ tmp.parse_utf8(str_xkb, str_xkb_size);
+ for (int i = 0; i < tmp.length(); i++) {
+ Ref<InputEventKey> k;
+ k.instantiate();
+ if (physical_keycode == Key::NONE && keycode == Key::NONE && tmp[i] == 0) {
+ continue;
+ }
+
+ if (keycode == Key::NONE) {
+ keycode = (Key)physical_keycode;
+ }
+
+ _get_key_modifier_state(xkeyevent->state, k);
+
+ k->set_window_id(p_window);
+ k->set_pressed(keypress);
+
+ k->set_keycode(keycode);
+ k->set_physical_keycode(physical_keycode);
+ if (!keysym.is_empty()) {
+ k->set_key_label(fix_key_label(keysym[0], keycode));
+ } else {
+ k->set_key_label(keycode);
+ }
+ if (keypress) {
+ k->set_unicode(fix_unicode(tmp[i]));
+ }
+
+ k->set_echo(false);
+
+ if (k->get_keycode() == Key::BACKTAB) {
+ //make it consistent across platforms.
+ k->set_keycode(Key::TAB);
+ k->set_physical_keycode(Key::TAB);
+ k->set_shift_pressed(true);
+ }
+
+ Input::get_singleton()->parse_input_event(k);
+ }
+ return;
+ }
+ }
}
/* Phase 2, obtain a Godot keycode from the keysym */
@@ -4866,6 +4930,10 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, V
wd.x11_xim_window = XCreateWindow(x11_display, wd.x11_window, 0, 0, 1, 1, 0, CopyFromParent, InputOnly, CopyFromParent, CWEventMask, &window_attributes_ime);
+ if (dead_tbl && xkb_loaded) {
+ wd.xkb_state = xkb_compose_state_new(dead_tbl, XKB_COMPOSE_STATE_NO_FLAGS);
+ }
+
// Enable receiving notification when the window is initialized (MapNotify)
// so the focus can be set at the right time.
if (!wd.no_focus && !wd.is_popup) {
@@ -5145,7 +5213,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
ERR_FAIL_MSG("Can't load XCursor dynamically.");
}
- initialize_xkbcommon(dylibloader_verbose); // Optional, used for key_label.
+ xkb_loaded = (initialize_xkbcommon(dylibloader_verbose) == 0);
if (initialize_xext(dylibloader_verbose) != 0) {
r_error = ERR_UNAVAILABLE;
@@ -5172,6 +5240,23 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
ERR_FAIL_MSG("Can't load Xinput2 dynamically.");
}
+ if (xkb_loaded) {
+ xkb_ctx = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ if (xkb_ctx) {
+ const char *locale = getenv("LC_ALL");
+ if (!locale || !*locale) {
+ locale = getenv("LC_CTYPE");
+ }
+ if (!locale || !*locale) {
+ locale = getenv("LANG");
+ }
+ if (!locale || !*locale) {
+ locale = "C";
+ }
+ dead_tbl = xkb_compose_table_new_from_locale(xkb_ctx, locale, XKB_COMPOSE_COMPILE_NO_FLAGS);
+ }
+ }
+
Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
r_error = OK;
@@ -5612,10 +5697,26 @@ DisplayServerX11::~DisplayServerX11() {
XDestroyIC(wd.xic);
wd.xic = nullptr;
}
+ XDestroyWindow(x11_display, wd.x11_xim_window);
+ if (xkb_loaded) {
+ if (wd.xkb_state) {
+ xkb_compose_state_unref(wd.xkb_state);
+ wd.xkb_state = nullptr;
+ }
+ }
XUnmapWindow(x11_display, wd.x11_window);
XDestroyWindow(x11_display, wd.x11_window);
}
+ if (xkb_loaded) {
+ if (dead_tbl) {
+ xkb_compose_table_unref(dead_tbl);
+ }
+ if (xkb_ctx) {
+ xkb_context_unref(xkb_ctx);
+ }
+ }
+
//destroy drivers
#if defined(VULKAN_ENABLED)
if (rendering_device_vulkan) {
diff --git a/platform/linuxbsd/x11/display_server_x11.h b/platform/linuxbsd/x11/display_server_x11.h
index bfb97ae44c..ea54b42262 100644
--- a/platform/linuxbsd/x11/display_server_x11.h
+++ b/platform/linuxbsd/x11/display_server_x11.h
@@ -142,6 +142,7 @@ class DisplayServerX11 : public DisplayServer {
bool ime_active = false;
bool ime_in_progress = false;
bool ime_suppress_next_keyup = false;
+ xkb_compose_state *xkb_state = nullptr;
Size2i min_size;
Size2i max_size;
@@ -185,6 +186,10 @@ class DisplayServerX11 : public DisplayServer {
Point2i im_selection;
String im_text;
+ bool xkb_loaded = false;
+ xkb_context *xkb_ctx = nullptr;
+ xkb_compose_table *dead_tbl = nullptr;
+
HashMap<WindowID, WindowData> windows;
unsigned int last_mouse_monitor_mask = 0;
diff --git a/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
index ecd2c25662..bba21b9cb7 100644
--- a/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:09:53
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h --sys-include "thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h" --soname libXcursor.so.1 --init-name xcursor --output-header ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:09:53
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h --sys-include "thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h" --soname libXcursor.so.1 --init-name xcursor --output-header ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
//
// NOTE: Generated from Xcursor 1.2.0.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h
index dc3684ff09..9f8d8bbca2 100644
--- a/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XCURSOR
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:09:53
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h --sys-include "thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h" --soname libXcursor.so.1 --init-name xcursor --output-header ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:09:53
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h --sys-include "thirdparty/linuxbsd_headers/X11/Xcursor/Xcursor.h" --soname libXcursor.so.1 --init-name xcursor --output-header ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xcursor-so_wrap.c
//
// NOTE: Generated from Xcursor 1.2.0.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
index e9af9033a3..4e3349c574 100644
--- a/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:11:29
-// flags: ../dynload-wrapper/generate-wrapper.py --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xext.h" --include ./thirdparty/linuxbsd_headers/X11/extensions/shape.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/shape.h" --soname libXext.so.6 --init-name xext --output-header ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:11:29
+// flags: generate-wrapper.py --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xext.h" --include ./thirdparty/linuxbsd_headers/X11/extensions/shape.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/shape.h" --soname libXext.so.6 --init-name xext --output-header ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
//
// NOTE: Generated from Xext 1.3.5.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h
index bbf23fff34..e535756d82 100644
--- a/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XEXT
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:11:29
-// flags: ../dynload-wrapper/generate-wrapper.py --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xext.h" --include ./thirdparty/linuxbsd_headers/X11/extensions/shape.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/shape.h" --soname libXext.so.6 --init-name xext --output-header ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:11:29
+// flags: generate-wrapper.py --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xext.h" --include ./thirdparty/linuxbsd_headers/X11/extensions/shape.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/shape.h" --soname libXext.so.6 --init-name xext --output-header ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xext-so_wrap.c
//
// NOTE: Generated from Xext 1.3.5.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
index ab53b232d6..850ed1fc6b 100644
--- a/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:11:35
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h" --soname libXinerama.so.1 --init-name xinerama --output-header ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:11:35
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h" --soname libXinerama.so.1 --init-name xinerama --output-header ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
//
// NOTE: Generated from Xinerama 1.1.4.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h
index 48d5cc44b7..e3cedfc8ad 100644
--- a/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XINERAMA
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:11:35
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h" --soname libXinerama.so.1 --init-name xinerama --output-header ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:11:35
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xinerama.h" --soname libXinerama.so.1 --init-name xinerama --output-header ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinerama-so_wrap.c
//
// NOTE: Generated from Xinerama 1.1.4.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
index d37f2b4d94..fc08b97e3c 100644
--- a/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:12:16
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/XInput2.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/XInput2.h" --soname libXi.so.6 --init-name xinput2 --output-header ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:12:16
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/XInput2.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/XInput2.h" --soname libXi.so.6 --init-name xinput2 --output-header ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
//
// NOTE: Generated from Xi 1.7.10.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h
index e39661ffb9..571072c3cd 100644
--- a/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XINPUT2
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:12:16
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/XInput2.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/XInput2.h" --soname libXi.so.6 --init-name xinput2 --output-header ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:12:16
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/XInput2.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/XInput2.h" --soname libXi.so.6 --init-name xinput2 --output-header ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xinput2-so_wrap.c
//
// NOTE: Generated from Xi 1.7.10.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c
index a746b6c526..d2838569b0 100644
--- a/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:13:26
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xlib.h --include ./thirdparty/linuxbsd_headers/X11/Xutil.h --include ./thirdparty/linuxbsd_headers/X11/XKBlib.h --sys-include "thirdparty/linuxbsd_headers/X11/Xlib.h" --sys-include "thirdparty/linuxbsd_headers/X11/Xutil.h" --sys-include "thirdparty/linuxbsd_headers/X11/XKBlib.h" --soname libX11.so.6 --init-name xlib --omit-prefix XkbGetDeviceIndicatorState --omit-prefix XkbAddSymInterpret --output-header ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c~
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:13:26
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xlib.h --include ./thirdparty/linuxbsd_headers/X11/Xutil.h --include ./thirdparty/linuxbsd_headers/X11/XKBlib.h --sys-include "thirdparty/linuxbsd_headers/X11/Xlib.h" --sys-include "thirdparty/linuxbsd_headers/X11/Xutil.h" --sys-include "thirdparty/linuxbsd_headers/X11/XKBlib.h" --soname libX11.so.6 --init-name xlib --omit-prefix XkbGetDeviceIndicatorState --omit-prefix XkbAddSymInterpret --output-header ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c~
//
// NOTE: Generated from Xlib 1.6.9.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h
index b40a25f601..5bad21002d 100644
--- a/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XLIB
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:13:26
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xlib.h --include ./thirdparty/linuxbsd_headers/X11/Xutil.h --include ./thirdparty/linuxbsd_headers/X11/XKBlib.h --sys-include "thirdparty/linuxbsd_headers/X11/Xlib.h" --sys-include "thirdparty/linuxbsd_headers/X11/Xutil.h" --sys-include "thirdparty/linuxbsd_headers/X11/XKBlib.h" --soname libX11.so.6 --init-name xlib --omit-prefix XkbGetDeviceIndicatorState --omit-prefix XkbAddSymInterpret --output-header ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c~
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:13:26
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/Xlib.h --include ./thirdparty/linuxbsd_headers/X11/Xutil.h --include ./thirdparty/linuxbsd_headers/X11/XKBlib.h --sys-include "thirdparty/linuxbsd_headers/X11/Xlib.h" --sys-include "thirdparty/linuxbsd_headers/X11/Xutil.h" --sys-include "thirdparty/linuxbsd_headers/X11/XKBlib.h" --soname libX11.so.6 --init-name xlib --omit-prefix XkbGetDeviceIndicatorState --omit-prefix XkbAddSymInterpret --output-header ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xlib-so_wrap.c~
//
// NOTE: Generated from Xlib 1.6.9.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
index 21e30a03de..05f98d2506 100644
--- a/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:13:54
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h" --soname libXrandr.so.2 --init-name xrandr --output-header ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:13:54
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h" --soname libXrandr.so.2 --init-name xrandr --output-header ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
//
// NOTE: Generated from Xrandr 1.5.2.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h
index f301234b53..db5d44203d 100644
--- a/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XRANDR
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:13:54
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h" --soname libXrandr.so.2 --init-name xrandr --output-header ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:13:54
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrandr.h" --soname libXrandr.so.2 --init-name xrandr --output-header ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrandr-so_wrap.c
//
// NOTE: Generated from Xrandr 1.5.2.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c b/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c
index 5c720bee21..7421f94601 100644
--- a/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c
+++ b/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:14:14
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrender.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrender.h" --soname libXrender.so.1 --init-name xrender --output-header ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c~
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:14:14
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrender.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrender.h" --soname libXrender.so.1 --init-name xrender --output-header ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c~
//
// NOTE: Generated from Xrender 0.9.10.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h b/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h
index 29a8430476..5d3f695959 100644
--- a/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h
+++ b/platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XRENDER
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:14:14
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrender.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrender.h" --soname libXrender.so.1 --init-name xrender --output-header ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c~
+// generated by generate-wrapper.py 0.3 on 2023-01-23 15:14:14
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/X11/extensions/Xrender.h --sys-include "thirdparty/linuxbsd_headers/X11/extensions/Xrender.h" --soname libXrender.so.1 --init-name xrender --output-header ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xrender-so_wrap.c~
//
// NOTE: Generated from Xrender 0.9.10.
// This has been handpatched to workaround some issues with the generator that
diff --git a/platform/linuxbsd/xkbcommon-so_wrap.c b/platform/linuxbsd/xkbcommon-so_wrap.c
index 601d4c5052..3382e2e553 100644
--- a/platform/linuxbsd/xkbcommon-so_wrap.c
+++ b/platform/linuxbsd/xkbcommon-so_wrap.c
@@ -1,7 +1,7 @@
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:14:21
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h" --soname libxkbcommon.so.0 --init-name xkbcommon --output-header ./platform/linuxbsd/x11/dynwrappers/xkbcommon-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xkbcommon-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-30 10:40:26
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h" --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h" --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h" --soname libxkbcommon.so.0 --init-name xkbcommon --output-header ./platform/linuxbsd/xkbcommon-so_wrap.h --output-implementation ./platform/linuxbsd/xkbcommon-so_wrap.c
//
#include <stdint.h>
@@ -81,7 +81,23 @@
#define xkb_state_layout_index_is_active xkb_state_layout_index_is_active_dylibloader_orig_xkbcommon
#define xkb_state_led_name_is_active xkb_state_led_name_is_active_dylibloader_orig_xkbcommon
#define xkb_state_led_index_is_active xkb_state_led_index_is_active_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_locale xkb_compose_table_new_from_locale_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_file xkb_compose_table_new_from_file_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_buffer xkb_compose_table_new_from_buffer_dylibloader_orig_xkbcommon
+#define xkb_compose_table_ref xkb_compose_table_ref_dylibloader_orig_xkbcommon
+#define xkb_compose_table_unref xkb_compose_table_unref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_new xkb_compose_state_new_dylibloader_orig_xkbcommon
+#define xkb_compose_state_ref xkb_compose_state_ref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_unref xkb_compose_state_unref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_compose_table xkb_compose_state_get_compose_table_dylibloader_orig_xkbcommon
+#define xkb_compose_state_feed xkb_compose_state_feed_dylibloader_orig_xkbcommon
+#define xkb_compose_state_reset xkb_compose_state_reset_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_status xkb_compose_state_get_status_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_utf8 xkb_compose_state_get_utf8_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_one_sym xkb_compose_state_get_one_sym_dylibloader_orig_xkbcommon
#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h"
+#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h"
+#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h"
#undef xkb_keysym_get_name
#undef xkb_keysym_from_name
#undef xkb_keysym_to_utf8
@@ -158,6 +174,20 @@
#undef xkb_state_layout_index_is_active
#undef xkb_state_led_name_is_active
#undef xkb_state_led_index_is_active
+#undef xkb_compose_table_new_from_locale
+#undef xkb_compose_table_new_from_file
+#undef xkb_compose_table_new_from_buffer
+#undef xkb_compose_table_ref
+#undef xkb_compose_table_unref
+#undef xkb_compose_state_new
+#undef xkb_compose_state_ref
+#undef xkb_compose_state_unref
+#undef xkb_compose_state_get_compose_table
+#undef xkb_compose_state_feed
+#undef xkb_compose_state_reset
+#undef xkb_compose_state_get_status
+#undef xkb_compose_state_get_utf8
+#undef xkb_compose_state_get_one_sym
#include <dlfcn.h>
#include <stdio.h>
int (*xkb_keysym_get_name_dylibloader_wrapper_xkbcommon)( xkb_keysym_t, char*, size_t);
@@ -236,6 +266,20 @@ int (*xkb_state_layout_name_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_
int (*xkb_state_layout_index_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*, xkb_layout_index_t,enum xkb_state_component);
int (*xkb_state_led_name_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*,const char*);
int (*xkb_state_led_index_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*, xkb_led_index_t);
+struct xkb_compose_table* (*xkb_compose_table_new_from_locale_dylibloader_wrapper_xkbcommon)(struct xkb_context*,const char*,enum xkb_compose_compile_flags);
+struct xkb_compose_table* (*xkb_compose_table_new_from_file_dylibloader_wrapper_xkbcommon)(struct xkb_context*, FILE*,const char*,enum xkb_compose_format,enum xkb_compose_compile_flags);
+struct xkb_compose_table* (*xkb_compose_table_new_from_buffer_dylibloader_wrapper_xkbcommon)(struct xkb_context*,const char*, size_t,const char*,enum xkb_compose_format,enum xkb_compose_compile_flags);
+struct xkb_compose_table* (*xkb_compose_table_ref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*);
+void (*xkb_compose_table_unref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*);
+struct xkb_compose_state* (*xkb_compose_state_new_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*,enum xkb_compose_state_flags);
+struct xkb_compose_state* (*xkb_compose_state_ref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+void (*xkb_compose_state_unref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+struct xkb_compose_table* (*xkb_compose_state_get_compose_table_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+enum xkb_compose_feed_result (*xkb_compose_state_feed_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*, xkb_keysym_t);
+void (*xkb_compose_state_reset_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+enum xkb_compose_status (*xkb_compose_state_get_status_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+int (*xkb_compose_state_get_utf8_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*, char*, size_t);
+xkb_keysym_t (*xkb_compose_state_get_one_sym_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
int initialize_xkbcommon(int verbose) {
void *handle;
char *error;
@@ -855,5 +899,117 @@ int initialize_xkbcommon(int verbose) {
fprintf(stderr, "%s\n", error);
}
}
+// xkb_compose_table_new_from_locale
+ *(void **) (&xkb_compose_table_new_from_locale_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_table_new_from_locale");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_table_new_from_file
+ *(void **) (&xkb_compose_table_new_from_file_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_table_new_from_file");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_table_new_from_buffer
+ *(void **) (&xkb_compose_table_new_from_buffer_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_table_new_from_buffer");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_table_ref
+ *(void **) (&xkb_compose_table_ref_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_table_ref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_table_unref
+ *(void **) (&xkb_compose_table_unref_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_table_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_new
+ *(void **) (&xkb_compose_state_new_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_new");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_ref
+ *(void **) (&xkb_compose_state_ref_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_ref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_unref
+ *(void **) (&xkb_compose_state_unref_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_unref");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_get_compose_table
+ *(void **) (&xkb_compose_state_get_compose_table_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_get_compose_table");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_feed
+ *(void **) (&xkb_compose_state_feed_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_feed");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_reset
+ *(void **) (&xkb_compose_state_reset_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_reset");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_get_status
+ *(void **) (&xkb_compose_state_get_status_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_get_status");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_get_utf8
+ *(void **) (&xkb_compose_state_get_utf8_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_get_utf8");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
+// xkb_compose_state_get_one_sym
+ *(void **) (&xkb_compose_state_get_one_sym_dylibloader_wrapper_xkbcommon) = dlsym(handle, "xkb_compose_state_get_one_sym");
+ if (verbose) {
+ error = dlerror();
+ if (error != NULL) {
+ fprintf(stderr, "%s\n", error);
+ }
+ }
return 0;
}
diff --git a/platform/linuxbsd/xkbcommon-so_wrap.h b/platform/linuxbsd/xkbcommon-so_wrap.h
index f7e6f4c4cf..4ae69fdf1f 100644
--- a/platform/linuxbsd/xkbcommon-so_wrap.h
+++ b/platform/linuxbsd/xkbcommon-so_wrap.h
@@ -2,8 +2,8 @@
#define DYLIBLOAD_WRAPPER_XKBCOMMON
// This file is generated. Do not edit!
// see https://github.com/hpvb/dynload-wrapper for details
-// generated by ../dynload-wrapper/generate-wrapper.py 0.3 on 2023-01-23 15:14:21
-// flags: ../dynload-wrapper/generate-wrapper.py --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h" --soname libxkbcommon.so.0 --init-name xkbcommon --output-header ./platform/linuxbsd/x11/dynwrappers/xkbcommon-so_wrap.h --output-implementation ./platform/linuxbsd/x11/dynwrappers/xkbcommon-so_wrap.c
+// generated by generate-wrapper.py 0.3 on 2023-01-30 10:40:26
+// flags: generate-wrapper.py --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h --include ./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h" --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h" --sys-include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h" --soname libxkbcommon.so.0 --init-name xkbcommon --output-header ./platform/linuxbsd/xkbcommon-so_wrap.h --output-implementation ./platform/linuxbsd/xkbcommon-so_wrap.c
//
#include <stdint.h>
@@ -83,7 +83,23 @@
#define xkb_state_layout_index_is_active xkb_state_layout_index_is_active_dylibloader_orig_xkbcommon
#define xkb_state_led_name_is_active xkb_state_led_name_is_active_dylibloader_orig_xkbcommon
#define xkb_state_led_index_is_active xkb_state_led_index_is_active_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_locale xkb_compose_table_new_from_locale_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_file xkb_compose_table_new_from_file_dylibloader_orig_xkbcommon
+#define xkb_compose_table_new_from_buffer xkb_compose_table_new_from_buffer_dylibloader_orig_xkbcommon
+#define xkb_compose_table_ref xkb_compose_table_ref_dylibloader_orig_xkbcommon
+#define xkb_compose_table_unref xkb_compose_table_unref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_new xkb_compose_state_new_dylibloader_orig_xkbcommon
+#define xkb_compose_state_ref xkb_compose_state_ref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_unref xkb_compose_state_unref_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_compose_table xkb_compose_state_get_compose_table_dylibloader_orig_xkbcommon
+#define xkb_compose_state_feed xkb_compose_state_feed_dylibloader_orig_xkbcommon
+#define xkb_compose_state_reset xkb_compose_state_reset_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_status xkb_compose_state_get_status_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_utf8 xkb_compose_state_get_utf8_dylibloader_orig_xkbcommon
+#define xkb_compose_state_get_one_sym xkb_compose_state_get_one_sym_dylibloader_orig_xkbcommon
#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon.h"
+#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-compose.h"
+#include "./thirdparty/linuxbsd_headers/xkbcommon/xkbcommon-keysyms.h"
#undef xkb_keysym_get_name
#undef xkb_keysym_from_name
#undef xkb_keysym_to_utf8
@@ -160,6 +176,20 @@
#undef xkb_state_layout_index_is_active
#undef xkb_state_led_name_is_active
#undef xkb_state_led_index_is_active
+#undef xkb_compose_table_new_from_locale
+#undef xkb_compose_table_new_from_file
+#undef xkb_compose_table_new_from_buffer
+#undef xkb_compose_table_ref
+#undef xkb_compose_table_unref
+#undef xkb_compose_state_new
+#undef xkb_compose_state_ref
+#undef xkb_compose_state_unref
+#undef xkb_compose_state_get_compose_table
+#undef xkb_compose_state_feed
+#undef xkb_compose_state_reset
+#undef xkb_compose_state_get_status
+#undef xkb_compose_state_get_utf8
+#undef xkb_compose_state_get_one_sym
#ifdef __cplusplus
extern "C" {
#endif
@@ -239,6 +269,20 @@ extern "C" {
#define xkb_state_layout_index_is_active xkb_state_layout_index_is_active_dylibloader_wrapper_xkbcommon
#define xkb_state_led_name_is_active xkb_state_led_name_is_active_dylibloader_wrapper_xkbcommon
#define xkb_state_led_index_is_active xkb_state_led_index_is_active_dylibloader_wrapper_xkbcommon
+#define xkb_compose_table_new_from_locale xkb_compose_table_new_from_locale_dylibloader_wrapper_xkbcommon
+#define xkb_compose_table_new_from_file xkb_compose_table_new_from_file_dylibloader_wrapper_xkbcommon
+#define xkb_compose_table_new_from_buffer xkb_compose_table_new_from_buffer_dylibloader_wrapper_xkbcommon
+#define xkb_compose_table_ref xkb_compose_table_ref_dylibloader_wrapper_xkbcommon
+#define xkb_compose_table_unref xkb_compose_table_unref_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_new xkb_compose_state_new_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_ref xkb_compose_state_ref_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_unref xkb_compose_state_unref_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_get_compose_table xkb_compose_state_get_compose_table_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_feed xkb_compose_state_feed_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_reset xkb_compose_state_reset_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_get_status xkb_compose_state_get_status_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_get_utf8 xkb_compose_state_get_utf8_dylibloader_wrapper_xkbcommon
+#define xkb_compose_state_get_one_sym xkb_compose_state_get_one_sym_dylibloader_wrapper_xkbcommon
extern int (*xkb_keysym_get_name_dylibloader_wrapper_xkbcommon)( xkb_keysym_t, char*, size_t);
extern xkb_keysym_t (*xkb_keysym_from_name_dylibloader_wrapper_xkbcommon)(const char*,enum xkb_keysym_flags);
extern int (*xkb_keysym_to_utf8_dylibloader_wrapper_xkbcommon)( xkb_keysym_t, char*, size_t);
@@ -315,6 +359,20 @@ extern int (*xkb_state_layout_name_is_active_dylibloader_wrapper_xkbcommon)(stru
extern int (*xkb_state_layout_index_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*, xkb_layout_index_t,enum xkb_state_component);
extern int (*xkb_state_led_name_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*,const char*);
extern int (*xkb_state_led_index_is_active_dylibloader_wrapper_xkbcommon)(struct xkb_state*, xkb_led_index_t);
+extern struct xkb_compose_table* (*xkb_compose_table_new_from_locale_dylibloader_wrapper_xkbcommon)(struct xkb_context*,const char*,enum xkb_compose_compile_flags);
+extern struct xkb_compose_table* (*xkb_compose_table_new_from_file_dylibloader_wrapper_xkbcommon)(struct xkb_context*, FILE*,const char*,enum xkb_compose_format,enum xkb_compose_compile_flags);
+extern struct xkb_compose_table* (*xkb_compose_table_new_from_buffer_dylibloader_wrapper_xkbcommon)(struct xkb_context*,const char*, size_t,const char*,enum xkb_compose_format,enum xkb_compose_compile_flags);
+extern struct xkb_compose_table* (*xkb_compose_table_ref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*);
+extern void (*xkb_compose_table_unref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*);
+extern struct xkb_compose_state* (*xkb_compose_state_new_dylibloader_wrapper_xkbcommon)(struct xkb_compose_table*,enum xkb_compose_state_flags);
+extern struct xkb_compose_state* (*xkb_compose_state_ref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+extern void (*xkb_compose_state_unref_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+extern struct xkb_compose_table* (*xkb_compose_state_get_compose_table_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+extern enum xkb_compose_feed_result (*xkb_compose_state_feed_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*, xkb_keysym_t);
+extern void (*xkb_compose_state_reset_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+extern enum xkb_compose_status (*xkb_compose_state_get_status_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
+extern int (*xkb_compose_state_get_utf8_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*, char*, size_t);
+extern xkb_keysym_t (*xkb_compose_state_get_one_sym_dylibloader_wrapper_xkbcommon)(struct xkb_compose_state*);
int initialize_xkbcommon(int verbose);
#ifdef __cplusplus
}
diff --git a/platform/web/export/editor_http_server.h b/platform/web/export/editor_http_server.h
index ce6b0be713..3f87288537 100644
--- a/platform/web/export/editor_http_server.h
+++ b/platform/web/export/editor_http_server.h
@@ -205,8 +205,7 @@ public:
if (tls.is_null()) {
tls = Ref<StreamPeerTLS>(StreamPeerTLS::create());
peer = tls;
- tls->set_blocking_handshake_enabled(false);
- if (tls->accept_stream(tcp, key, cert) != OK) {
+ if (tls->accept_stream(tcp, TLSOptions::server(key, cert)) != OK) {
_clear_client();
return;
}
diff --git a/platform/web/http_client_web.cpp b/platform/web/http_client_web.cpp
index 31f54dad9f..3e4ba5a2ae 100644
--- a/platform/web/http_client_web.cpp
+++ b/platform/web/http_client_web.cpp
@@ -37,20 +37,20 @@ void HTTPClientWeb::_parse_headers(int p_len, const char **p_headers, void *p_re
}
}
-Error HTTPClientWeb::connect_to_host(const String &p_host, int p_port, bool p_tls, bool p_verify_host) {
+Error HTTPClientWeb::connect_to_host(const String &p_host, int p_port, Ref<TLSOptions> p_tls_options) {
+ ERR_FAIL_COND_V(p_tls_options.is_valid() && p_tls_options->is_server(), ERR_INVALID_PARAMETER);
+
close();
- if (p_tls && !p_verify_host) {
- WARN_PRINT("Disabling HTTPClientWeb's host verification is not supported for the Web platform, host will be verified");
- }
port = p_port;
- use_tls = p_tls;
+ use_tls = p_tls_options.is_valid();
host = p_host;
String host_lower = host.to_lower();
if (host_lower.begins_with("http://")) {
host = host.substr(7, host.length() - 7);
+ use_tls = false;
} else if (host_lower.begins_with("https://")) {
use_tls = true;
host = host.substr(8, host.length() - 8);
diff --git a/platform/web/http_client_web.h b/platform/web/http_client_web.h
index 993ec6c0e2..def7837a27 100644
--- a/platform/web/http_client_web.h
+++ b/platform/web/http_client_web.h
@@ -86,7 +86,7 @@ public:
Error request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_size) override;
- Error connect_to_host(const String &p_host, int p_port = -1, bool p_tls = false, bool p_verify_host = true) override;
+ Error connect_to_host(const String &p_host, int p_port = -1, Ref<TLSOptions> p_tls_options = Ref<TLSOptions>()) override;
void set_connection(const Ref<StreamPeer> &p_connection) override;
Ref<StreamPeer> get_connection() const override;
void close() override;
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index ccf9696d82..8f7006caca 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -475,8 +475,9 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
}
}
- notify_property_list_changed();
set_process_internal(true);
+ notify_property_list_changed();
+ queue_redraw();
}
void AnimatedSprite2D::play_backwards(const StringName &p_name) {
diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp
index f80da6e9ab..2dcf7c3a11 100644
--- a/scene/2d/area_2d.cpp
+++ b/scene/2d/area_2d.cpp
@@ -652,6 +652,7 @@ Area2D::Area2D() :
set_gravity_direction(Vector2(0, 1));
set_monitoring(true);
set_monitorable(true);
+ set_hide_clip_children(true);
}
Area2D::~Area2D() {
diff --git a/scene/2d/audio_listener_2d.cpp b/scene/2d/audio_listener_2d.cpp
index 5b8833ce62..b4484694a5 100644
--- a/scene/2d/audio_listener_2d.cpp
+++ b/scene/2d/audio_listener_2d.cpp
@@ -110,3 +110,7 @@ void AudioListener2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("clear_current"), &AudioListener2D::clear_current);
ClassDB::bind_method(D_METHOD("is_current"), &AudioListener2D::is_current);
}
+
+AudioListener2D::AudioListener2D() {
+ set_hide_clip_children(true);
+}
diff --git a/scene/2d/audio_listener_2d.h b/scene/2d/audio_listener_2d.h
index 12a44f26ae..abada06971 100644
--- a/scene/2d/audio_listener_2d.h
+++ b/scene/2d/audio_listener_2d.h
@@ -54,6 +54,8 @@ public:
void make_current();
void clear_current();
bool is_current() const;
+
+ AudioListener2D();
};
#endif // AUDIO_LISTENER_2D_H
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index 7b681eac7a..902fba38bf 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -72,12 +72,10 @@ void AudioStreamPlayer2D::_notification(int p_what) {
_update_panning();
}
- if (setplay.get() >= 0 && stream.is_valid()) {
+ if (setplayback.is_valid() && setplay.get() >= 0) {
active.set();
- Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
- ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
- AudioServer::get_singleton()->start_playback_stream(new_playback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
- stream_playbacks.push_back(new_playback);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, _get_actual_bus(), volume_vector, setplay.get(), pitch_scale);
+ setplayback.unref();
setplay.set(-1);
}
@@ -255,7 +253,11 @@ void AudioStreamPlayer2D::play(float p_from_pos) {
if (stream->is_monophonic() && is_playing()) {
stop();
}
+ Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+ ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
+ stream_playbacks.push_back(stream_playback);
+ setplayback = stream_playback;
setplay.set(p_from_pos);
active.set();
set_physics_process_internal(true);
@@ -390,6 +392,10 @@ bool AudioStreamPlayer2D::get_stream_paused() const {
return false;
}
+bool AudioStreamPlayer2D::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer2D::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -458,6 +464,7 @@ void AudioStreamPlayer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer2D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer2D::get_panning_strength);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer2D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer2D::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
@@ -479,6 +486,7 @@ void AudioStreamPlayer2D::_bind_methods() {
AudioStreamPlayer2D::AudioStreamPlayer2D() {
AudioServer::get_singleton()->connect("bus_layout_changed", callable_mp(this, &AudioStreamPlayer2D::_bus_layout_changed));
cached_global_panning_strength = GLOBAL_GET("audio/general/2d_panning_strength");
+ set_hide_clip_children(true);
}
AudioStreamPlayer2D::~AudioStreamPlayer2D() {
diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h
index a5fd584513..79a026fed2 100644
--- a/scene/2d/audio_stream_player_2d.h
+++ b/scene/2d/audio_stream_player_2d.h
@@ -56,6 +56,7 @@ private:
SafeFlag active{ false };
SafeNumeric<float> setplay{ -1.0 };
+ Ref<AudioStreamPlayback> setplayback;
Vector<AudioFrame> volume_vector;
@@ -129,6 +130,7 @@ public:
void set_panning_strength(float p_panning_strength);
float get_panning_strength() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer2D();
diff --git a/scene/2d/back_buffer_copy.cpp b/scene/2d/back_buffer_copy.cpp
index ab048f0cd7..60b344b002 100644
--- a/scene/2d/back_buffer_copy.cpp
+++ b/scene/2d/back_buffer_copy.cpp
@@ -101,6 +101,7 @@ void BackBufferCopy::_bind_methods() {
BackBufferCopy::BackBufferCopy() {
_update_copy_mode();
+ set_hide_clip_children(true);
}
BackBufferCopy::~BackBufferCopy() {
diff --git a/scene/2d/camera_2d.cpp b/scene/2d/camera_2d.cpp
index c4647ae9ff..71b8fdb539 100644
--- a/scene/2d/camera_2d.cpp
+++ b/scene/2d/camera_2d.cpp
@@ -832,4 +832,5 @@ Camera2D::Camera2D() {
drag_margin[SIDE_BOTTOM] = 0.2;
set_notify_transform(true);
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/collision_object_2d.cpp b/scene/2d/collision_object_2d.cpp
index caea753d99..ba3b0cec5c 100644
--- a/scene/2d/collision_object_2d.cpp
+++ b/scene/2d/collision_object_2d.cpp
@@ -651,6 +651,7 @@ CollisionObject2D::CollisionObject2D(RID p_rid, bool p_area) {
area = p_area;
pickable = true;
set_notify_transform(true);
+ set_hide_clip_children(true);
total_subshapes = 0;
only_update_transform_changes = false;
diff --git a/scene/2d/collision_polygon_2d.cpp b/scene/2d/collision_polygon_2d.cpp
index 0e18f77b8a..32dea80650 100644
--- a/scene/2d/collision_polygon_2d.cpp
+++ b/scene/2d/collision_polygon_2d.cpp
@@ -131,15 +131,7 @@ void CollisionPolygon2D::_notification(int p_what) {
break;
}
- int polygon_count = polygon.size();
- for (int i = 0; i < polygon_count; i++) {
- Vector2 p = polygon[i];
- Vector2 n = polygon[(i + 1) % polygon_count];
- // draw line with width <= 1, so it does not scale with zoom and break pixel exact editing
- draw_line(p, n, Color(0.9, 0.2, 0.0, 0.8), 1);
- }
-
- if (polygon_count > 2) {
+ if (polygon.size() > 2) {
#define DEBUG_DECOMPOSE
#if defined(TOOLS_ENABLED) && defined(DEBUG_DECOMPOSE)
Vector<Vector<Vector2>> decomp = _decompose_in_convex();
@@ -152,6 +144,11 @@ void CollisionPolygon2D::_notification(int p_what) {
#else
draw_colored_polygon(polygon, get_tree()->get_debug_collisions_color());
#endif
+
+ const Color stroke_color = Color(0.9, 0.2, 0.0);
+ draw_polyline(polygon, stroke_color);
+ // Draw the last segment.
+ draw_line(polygon[polygon.size() - 1], polygon[0], stroke_color);
}
if (one_way_collision) {
@@ -323,4 +320,5 @@ void CollisionPolygon2D::_bind_methods() {
CollisionPolygon2D::CollisionPolygon2D() {
set_notify_local_transform(true);
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/collision_shape_2d.cpp b/scene/2d/collision_shape_2d.cpp
index 6ff789cad2..5951405bbe 100644
--- a/scene/2d/collision_shape_2d.cpp
+++ b/scene/2d/collision_shape_2d.cpp
@@ -288,5 +288,6 @@ void CollisionShape2D::_bind_methods() {
CollisionShape2D::CollisionShape2D() {
set_notify_local_transform(true);
+ set_hide_clip_children(true);
debug_color = _get_default_debug_color();
}
diff --git a/scene/2d/gpu_particles_2d.cpp b/scene/2d/gpu_particles_2d.cpp
index 9a3b7c8687..00d13c59b9 100644
--- a/scene/2d/gpu_particles_2d.cpp
+++ b/scene/2d/gpu_particles_2d.cpp
@@ -638,7 +638,7 @@ void GPUParticles2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "interpolate"), "set_interpolate", "get_interpolate");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "fract_delta"), "set_fractional_delta", "get_fractional_delta");
ADD_GROUP("Collision", "collision_");
- ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater,suffix:px"), "set_collision_base_size", "get_collision_base_size");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "collision_base_size", PROPERTY_HINT_RANGE, "0,128,0.01,or_greater"), "set_collision_base_size", "get_collision_base_size");
ADD_GROUP("Drawing", "");
ADD_PROPERTY(PropertyInfo(Variant::RECT2, "visibility_rect", PROPERTY_HINT_NONE, "suffix:px"), "set_visibility_rect", "get_visibility_rect");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "local_coords"), "set_use_local_coordinates", "get_use_local_coordinates");
diff --git a/scene/2d/joint_2d.cpp b/scene/2d/joint_2d.cpp
index 47d0ac6e35..ce427d47aa 100644
--- a/scene/2d/joint_2d.cpp
+++ b/scene/2d/joint_2d.cpp
@@ -243,6 +243,7 @@ void Joint2D::_bind_methods() {
Joint2D::Joint2D() {
joint = PhysicsServer2D::get_singleton()->joint_create();
+ set_hide_clip_children(true);
}
Joint2D::~Joint2D() {
diff --git a/scene/2d/light_2d.cpp b/scene/2d/light_2d.cpp
index fb53400fd6..15b638ed92 100644
--- a/scene/2d/light_2d.cpp
+++ b/scene/2d/light_2d.cpp
@@ -443,6 +443,7 @@ void PointLight2D::_bind_methods() {
PointLight2D::PointLight2D() {
RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_POINT);
+ set_hide_clip_children(true);
}
//////////
@@ -467,4 +468,5 @@ void DirectionalLight2D::_bind_methods() {
DirectionalLight2D::DirectionalLight2D() {
RS::get_singleton()->canvas_light_set_mode(_get_light(), RS::CANVAS_LIGHT_MODE_DIRECTIONAL);
set_max_distance(max_distance); // Update RenderingServer.
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/marker_2d.cpp b/scene/2d/marker_2d.cpp
index 512875833c..9595fcfffe 100644
--- a/scene/2d/marker_2d.cpp
+++ b/scene/2d/marker_2d.cpp
@@ -117,4 +117,5 @@ void Marker2D::_bind_methods() {
}
Marker2D::Marker2D() {
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/navigation_agent_2d.cpp b/scene/2d/navigation_agent_2d.cpp
index 907fb7e6a9..e73b6e7e23 100644
--- a/scene/2d/navigation_agent_2d.cpp
+++ b/scene/2d/navigation_agent_2d.cpp
@@ -205,9 +205,9 @@ NavigationAgent2D::~NavigationAgent2D() {
void NavigationAgent2D::set_avoidance_enabled(bool p_enabled) {
avoidance_enabled = p_enabled;
if (avoidance_enabled) {
- NavigationServer2D::get_singleton()->agent_set_callback(agent, get_instance_id(), "_avoidance_done");
+ NavigationServer2D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent2D::_avoidance_done));
} else {
- NavigationServer2D::get_singleton()->agent_set_callback(agent, ObjectID(), "_avoidance_done");
+ NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable());
}
}
@@ -217,7 +217,8 @@ bool NavigationAgent2D::get_avoidance_enabled() const {
void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) {
// remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map
- NavigationServer2D::get_singleton()->agent_set_callback(agent, ObjectID(), "_avoidance_done");
+ NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable());
+
if (Object::cast_to<Node2D>(p_agent_parent) != nullptr) {
// place agent on navigation map first or else the RVO agent callback creation fails silently later
agent_parent = Object::cast_to<Node2D>(p_agent_parent);
@@ -226,6 +227,7 @@ void NavigationAgent2D::set_agent_parent(Node *p_agent_parent) {
} else {
NavigationServer2D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_2d()->get_navigation_map());
}
+
// create new avoidance callback if enabled
set_avoidance_enabled(avoidance_enabled);
} else {
diff --git a/scene/2d/navigation_link_2d.cpp b/scene/2d/navigation_link_2d.cpp
index 65bf178cc5..26dca40176 100644
--- a/scene/2d/navigation_link_2d.cpp
+++ b/scene/2d/navigation_link_2d.cpp
@@ -308,6 +308,7 @@ NavigationLink2D::NavigationLink2D() {
NavigationServer2D::get_singleton()->link_set_owner_id(link, get_instance_id());
set_notify_transform(true);
+ set_hide_clip_children(true);
}
NavigationLink2D::~NavigationLink2D() {
diff --git a/scene/2d/navigation_region_2d.cpp b/scene/2d/navigation_region_2d.cpp
index fe6af8dad2..3484a9de65 100644
--- a/scene/2d/navigation_region_2d.cpp
+++ b/scene/2d/navigation_region_2d.cpp
@@ -330,6 +330,7 @@ bool NavigationRegion2D::_get(const StringName &p_name, Variant &r_ret) const {
NavigationRegion2D::NavigationRegion2D() {
set_notify_transform(true);
+ set_hide_clip_children(true);
region = NavigationServer2D::get_singleton()->region_create();
NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id());
diff --git a/scene/2d/ray_cast_2d.cpp b/scene/2d/ray_cast_2d.cpp
index 39f88a0b5e..988ea87054 100644
--- a/scene/2d/ray_cast_2d.cpp
+++ b/scene/2d/ray_cast_2d.cpp
@@ -370,4 +370,5 @@ void RayCast2D::_bind_methods() {
}
RayCast2D::RayCast2D() {
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/remote_transform_2d.cpp b/scene/2d/remote_transform_2d.cpp
index e9ce9560d3..c6730f7ab2 100644
--- a/scene/2d/remote_transform_2d.cpp
+++ b/scene/2d/remote_transform_2d.cpp
@@ -219,4 +219,5 @@ void RemoteTransform2D::_bind_methods() {
RemoteTransform2D::RemoteTransform2D() {
set_notify_transform(true);
+ set_hide_clip_children(true);
}
diff --git a/scene/2d/shape_cast_2d.cpp b/scene/2d/shape_cast_2d.cpp
index 24821858cf..bafb83361a 100644
--- a/scene/2d/shape_cast_2d.cpp
+++ b/scene/2d/shape_cast_2d.cpp
@@ -472,3 +472,7 @@ void ShapeCast2D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_areas", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_areas", "is_collide_with_areas_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collide_with_bodies", PROPERTY_HINT_LAYERS_3D_PHYSICS), "set_collide_with_bodies", "is_collide_with_bodies_enabled");
}
+
+ShapeCast2D::ShapeCast2D() {
+ set_hide_clip_children(true);
+}
diff --git a/scene/2d/shape_cast_2d.h b/scene/2d/shape_cast_2d.h
index 182614a721..8a62b799f8 100644
--- a/scene/2d/shape_cast_2d.h
+++ b/scene/2d/shape_cast_2d.h
@@ -119,6 +119,8 @@ public:
void clear_exceptions();
PackedStringArray get_configuration_warnings() const override;
+
+ ShapeCast2D();
};
#endif // SHAPE_CAST_2D_H
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 83cfffc333..96711bbe72 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -519,6 +519,7 @@ Bone2D::Bone2D() {
bone_angle = 0;
autocalculate_length_and_angle = true;
set_notify_local_transform(true);
+ set_hide_clip_children(true);
//this is a clever hack so the bone knows no rest has been set yet, allowing to show an error.
for (int i = 0; i < 3; i++) {
rest[i] = Vector2(0, 0);
@@ -801,6 +802,7 @@ void Skeleton2D::_bind_methods() {
Skeleton2D::Skeleton2D() {
skeleton = RS::get_singleton()->skeleton_create();
set_notify_transform(true);
+ set_hide_clip_children(true);
}
Skeleton2D::~Skeleton2D() {
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index a1304ab991..95bf67d38d 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -3969,6 +3969,17 @@ PackedStringArray TileMap::get_configuration_warnings() const {
}
}
+ // Check if Y-sort is enabled on a layer but not on the node.
+ if (!is_y_sort_enabled()) {
+ for (const TileMapLayer &layer : layers) {
+ if (layer.y_sort_enabled) {
+ warnings.push_back(RTR("A TileMap layer is set as Y-sorted, but Y-sort is not enabled on the TileMap node itself."));
+ break;
+ }
+ }
+ }
+
+ // Check if we are in isometric mode without Y-sort enabled.
if (tile_set.is_valid() && tile_set->get_tile_shape() == TileSet::TILE_SHAPE_ISOMETRIC) {
bool warn = !is_y_sort_enabled();
if (!warn) {
diff --git a/scene/2d/visible_on_screen_notifier_2d.cpp b/scene/2d/visible_on_screen_notifier_2d.cpp
index 237eb3d987..1177cdb811 100644
--- a/scene/2d/visible_on_screen_notifier_2d.cpp
+++ b/scene/2d/visible_on_screen_notifier_2d.cpp
@@ -110,6 +110,7 @@ void VisibleOnScreenNotifier2D::_bind_methods() {
VisibleOnScreenNotifier2D::VisibleOnScreenNotifier2D() {
rect = Rect2(-10, -10, 20, 20);
+ set_hide_clip_children(true);
}
//////////////////////////////////////
diff --git a/scene/3d/audio_stream_player_3d.cpp b/scene/3d/audio_stream_player_3d.cpp
index 7ed18d2d41..436b936586 100644
--- a/scene/3d/audio_stream_player_3d.cpp
+++ b/scene/3d/audio_stream_player_3d.cpp
@@ -284,14 +284,12 @@ void AudioStreamPlayer3D::_notification(int p_what) {
volume_vector = _update_panning();
}
- if (setplay.get() >= 0 && stream.is_valid()) {
+ if (setplayback.is_valid() && setplay.get() >= 0) {
active.set();
- Ref<AudioStreamPlayback> new_playback = stream->instantiate_playback();
- ERR_FAIL_COND_MSG(new_playback.is_null(), "Failed to instantiate playback.");
HashMap<StringName, Vector<AudioFrame>> bus_map;
bus_map[_get_actual_bus()] = volume_vector;
- AudioServer::get_singleton()->start_playback_stream(new_playback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
- stream_playbacks.push_back(new_playback);
+ AudioServer::get_singleton()->start_playback_stream(setplayback, bus_map, setplay.get(), actual_pitch_scale, linear_attenuation, attenuation_filter_cutoff_hz);
+ setplayback.unref();
setplay.set(-1);
}
@@ -580,14 +578,21 @@ void AudioStreamPlayer3D::play(float p_from_pos) {
if (stream->is_monophonic() && is_playing()) {
stop();
}
+ Ref<AudioStreamPlayback> stream_playback = stream->instantiate_playback();
+ ERR_FAIL_COND_MSG(stream_playback.is_null(), "Failed to instantiate playback.");
+
+ stream_playbacks.push_back(stream_playback);
+ setplayback = stream_playback;
setplay.set(p_from_pos);
active.set();
set_physics_process_internal(true);
}
void AudioStreamPlayer3D::seek(float p_seconds) {
- stop();
- play(p_seconds);
+ if (is_playing()) {
+ stop();
+ play(p_seconds);
+ }
}
void AudioStreamPlayer3D::stop() {
@@ -783,6 +788,10 @@ bool AudioStreamPlayer3D::get_stream_paused() const {
return false;
}
+bool AudioStreamPlayer3D::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer3D::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -875,6 +884,7 @@ void AudioStreamPlayer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_panning_strength", "panning_strength"), &AudioStreamPlayer3D::set_panning_strength);
ClassDB::bind_method(D_METHOD("get_panning_strength"), &AudioStreamPlayer3D::get_panning_strength);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer3D::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer3D::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
diff --git a/scene/3d/audio_stream_player_3d.h b/scene/3d/audio_stream_player_3d.h
index 67cec91896..bba8f10761 100644
--- a/scene/3d/audio_stream_player_3d.h
+++ b/scene/3d/audio_stream_player_3d.h
@@ -69,6 +69,7 @@ private:
SafeFlag active{ false };
SafeNumeric<float> setplay{ -1.0 };
+ Ref<AudioStreamPlayback> setplayback;
AttenuationModel attenuation_model = ATTENUATION_INVERSE_DISTANCE;
float volume_db = 0.0;
@@ -188,6 +189,7 @@ public:
void set_panning_strength(float p_panning_strength);
float get_panning_strength() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer3D();
diff --git a/scene/3d/label_3d.cpp b/scene/3d/label_3d.cpp
index d0f71768d2..6a9b8c9ac4 100644
--- a/scene/3d/label_3d.cpp
+++ b/scene/3d/label_3d.cpp
@@ -109,6 +109,9 @@ void Label3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alpha_scissor_threshold", "threshold"), &Label3D::set_alpha_scissor_threshold);
ClassDB::bind_method(D_METHOD("get_alpha_scissor_threshold"), &Label3D::get_alpha_scissor_threshold);
+ ClassDB::bind_method(D_METHOD("set_alpha_hash_scale", "threshold"), &Label3D::set_alpha_hash_scale);
+ ClassDB::bind_method(D_METHOD("get_alpha_hash_scale"), &Label3D::get_alpha_hash_scale);
+
ClassDB::bind_method(D_METHOD("set_texture_filter", "mode"), &Label3D::set_texture_filter);
ClassDB::bind_method(D_METHOD("get_texture_filter"), &Label3D::get_texture_filter);
@@ -127,8 +130,9 @@ void Label3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "double_sided"), "set_draw_flag", "get_draw_flag", FLAG_DOUBLE_SIDED);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_draw_flag", "get_draw_flag", FLAG_DISABLE_DEPTH_TEST);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_draw_flag", "get_draw_flag", FLAG_FIXED_SIZE);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass"), "set_alpha_cut_mode", "get_alpha_cut_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass,Alpha Hash"), "set_alpha_cut_mode", "get_alpha_cut_mode");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter");
ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority");
ADD_PROPERTY(PropertyInfo(Variant::INT, "outline_render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_outline_render_priority", "get_outline_render_priority");
@@ -162,6 +166,7 @@ void Label3D::_bind_methods() {
BIND_ENUM_CONSTANT(ALPHA_CUT_DISABLED);
BIND_ENUM_CONSTANT(ALPHA_CUT_DISCARD);
BIND_ENUM_CONSTANT(ALPHA_CUT_OPAQUE_PREPASS);
+ BIND_ENUM_CONSTANT(ALPHA_CUT_HASH);
}
void Label3D::_validate_property(PropertyInfo &p_property) const {
@@ -350,13 +355,23 @@ void Label3D::_generate_glyph_surfaces(const Glyph &p_glyph, Vector2 &r_offset,
RS::get_singleton()->material_set_param(surf.material, "uv2_offset", Vector3(0, 0, 0));
RS::get_singleton()->material_set_param(surf.material, "uv2_scale", Vector3(1, 1, 1));
RS::get_singleton()->material_set_param(surf.material, "alpha_scissor_threshold", alpha_scissor_threshold);
+ RS::get_singleton()->material_set_param(surf.material, "alpha_hash_scale", alpha_hash_scale);
if (msdf) {
RS::get_singleton()->material_set_param(surf.material, "msdf_pixel_range", TS->font_get_msdf_pixel_range(p_glyph.font_rid));
RS::get_singleton()->material_set_param(surf.material, "msdf_outline_size", p_outline_size);
}
+ BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
+ if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
+ } else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
+ } else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
+ }
+
RID shader_rid;
- StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), true, get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, msdf, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), texture_filter, &shader_rid);
+ StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), mat_transparency, get_draw_flag(FLAG_DOUBLE_SIDED), get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, msdf, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), texture_filter, &shader_rid);
RS::get_singleton()->material_set_shader(surf.material, shader_rid);
RS::get_singleton()->material_set_param(surf.material, "texture_albedo", tex);
@@ -906,7 +921,7 @@ StandardMaterial3D::BillboardMode Label3D::get_billboard_mode() const {
}
void Label3D::set_alpha_cut_mode(AlphaCutMode p_mode) {
- ERR_FAIL_INDEX(p_mode, 3);
+ ERR_FAIL_INDEX(p_mode, ALPHA_CUT_MAX);
if (alpha_cut != p_mode) {
alpha_cut = p_mode;
_queue_update();
@@ -929,6 +944,17 @@ Label3D::AlphaCutMode Label3D::get_alpha_cut_mode() const {
return alpha_cut;
}
+void Label3D::set_alpha_hash_scale(float p_hash_scale) {
+ if (alpha_hash_scale != p_hash_scale) {
+ alpha_hash_scale = p_hash_scale;
+ _queue_update();
+ }
+}
+
+float Label3D::get_alpha_hash_scale() const {
+ return alpha_hash_scale;
+}
+
void Label3D::set_alpha_scissor_threshold(float p_threshold) {
if (alpha_scissor_threshold != p_threshold) {
alpha_scissor_threshold = p_threshold;
diff --git a/scene/3d/label_3d.h b/scene/3d/label_3d.h
index 96cc941209..576735840a 100644
--- a/scene/3d/label_3d.h
+++ b/scene/3d/label_3d.h
@@ -51,7 +51,9 @@ public:
enum AlphaCutMode {
ALPHA_CUT_DISABLED,
ALPHA_CUT_DISCARD,
- ALPHA_CUT_OPAQUE_PREPASS
+ ALPHA_CUT_OPAQUE_PREPASS,
+ ALPHA_CUT_HASH,
+ ALPHA_CUT_MAX
};
private:
@@ -59,6 +61,7 @@ private:
bool flags[FLAG_MAX] = {};
AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED;
float alpha_scissor_threshold = 0.5;
+ float alpha_hash_scale = 1.0;
AABB aabb;
@@ -228,6 +231,9 @@ public:
void set_alpha_scissor_threshold(float p_threshold);
float get_alpha_scissor_threshold() const;
+ void set_alpha_hash_scale(float p_hash_scale);
+ float get_alpha_hash_scale() const;
+
void set_billboard_mode(StandardMaterial3D::BillboardMode p_mode);
StandardMaterial3D::BillboardMode get_billboard_mode() const;
diff --git a/scene/3d/navigation_agent_3d.cpp b/scene/3d/navigation_agent_3d.cpp
index fe7ddcb06e..4aa6e61ec5 100644
--- a/scene/3d/navigation_agent_3d.cpp
+++ b/scene/3d/navigation_agent_3d.cpp
@@ -212,9 +212,9 @@ NavigationAgent3D::~NavigationAgent3D() {
void NavigationAgent3D::set_avoidance_enabled(bool p_enabled) {
avoidance_enabled = p_enabled;
if (avoidance_enabled) {
- NavigationServer3D::get_singleton()->agent_set_callback(agent, get_instance_id(), "_avoidance_done");
+ NavigationServer3D::get_singleton()->agent_set_callback(agent, callable_mp(this, &NavigationAgent3D::_avoidance_done));
} else {
- NavigationServer3D::get_singleton()->agent_set_callback(agent, ObjectID(), "_avoidance_done");
+ NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable());
}
}
@@ -224,7 +224,8 @@ bool NavigationAgent3D::get_avoidance_enabled() const {
void NavigationAgent3D::set_agent_parent(Node *p_agent_parent) {
// remove agent from any avoidance map before changing parent or there will be leftovers on the RVO map
- NavigationServer3D::get_singleton()->agent_set_callback(agent, ObjectID(), "_avoidance_done");
+ NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable());
+
if (Object::cast_to<Node3D>(p_agent_parent) != nullptr) {
// place agent on navigation map first or else the RVO agent callback creation fails silently later
agent_parent = Object::cast_to<Node3D>(p_agent_parent);
@@ -233,6 +234,7 @@ void NavigationAgent3D::set_agent_parent(Node *p_agent_parent) {
} else {
NavigationServer3D::get_singleton()->agent_set_map(get_rid(), agent_parent->get_world_3d()->get_navigation_map());
}
+
// create new avoidance callback if enabled
set_avoidance_enabled(avoidance_enabled);
} else {
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index 635c566ef8..041ca7707b 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -245,8 +245,25 @@ void SpriteBase3D::draw_texture_rect(Ref<Texture2D> p_texture, Rect2 p_dst_rect,
RS::get_singleton()->mesh_set_custom_aabb(mesh_new, aabb_new);
set_aabb(aabb_new);
+ RS::get_singleton()->material_set_param(get_material(), "alpha_scissor_threshold", alpha_scissor_threshold);
+ RS::get_singleton()->material_set_param(get_material(), "alpha_hash_scale", alpha_hash_scale);
+
+ BaseMaterial3D::Transparency mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_DISABLED;
+ if (get_draw_flag(FLAG_TRANSPARENT)) {
+ if (get_alpha_cut_mode() == ALPHA_CUT_DISCARD) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_SCISSOR;
+ } else if (get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_DEPTH_PRE_PASS;
+ } else if (get_alpha_cut_mode() == ALPHA_CUT_HASH) {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA_HASH;
+ } else {
+ mat_transparency = BaseMaterial3D::Transparency::TRANSPARENCY_ALPHA;
+ }
+ }
+
RID shader_rid;
- StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), get_draw_flag(FLAG_TRANSPARENT), get_draw_flag(FLAG_DOUBLE_SIDED), get_alpha_cut_mode() == ALPHA_CUT_DISCARD, get_alpha_cut_mode() == ALPHA_CUT_OPAQUE_PREPASS, get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid);
+ StandardMaterial3D::get_material_for_2d(get_draw_flag(FLAG_SHADED), mat_transparency, get_draw_flag(FLAG_DOUBLE_SIDED), get_billboard_mode() == StandardMaterial3D::BILLBOARD_ENABLED, get_billboard_mode() == StandardMaterial3D::BILLBOARD_FIXED_Y, false, get_draw_flag(FLAG_DISABLE_DEPTH_TEST), get_draw_flag(FLAG_FIXED_SIZE), get_texture_filter(), &shader_rid);
+
if (last_shader != shader_rid) {
RS::get_singleton()->material_set_shader(get_material(), shader_rid);
last_shader = shader_rid;
@@ -433,7 +450,7 @@ bool SpriteBase3D::get_draw_flag(DrawFlags p_flag) const {
}
void SpriteBase3D::set_alpha_cut_mode(AlphaCutMode p_mode) {
- ERR_FAIL_INDEX(p_mode, 3);
+ ERR_FAIL_INDEX(p_mode, ALPHA_CUT_MAX);
alpha_cut = p_mode;
_queue_redraw();
}
@@ -442,6 +459,28 @@ SpriteBase3D::AlphaCutMode SpriteBase3D::get_alpha_cut_mode() const {
return alpha_cut;
}
+void SpriteBase3D::set_alpha_hash_scale(float p_hash_scale) {
+ if (alpha_hash_scale != p_hash_scale) {
+ alpha_hash_scale = p_hash_scale;
+ _queue_redraw();
+ }
+}
+
+float SpriteBase3D::get_alpha_hash_scale() const {
+ return alpha_hash_scale;
+}
+
+void SpriteBase3D::set_alpha_scissor_threshold(float p_threshold) {
+ if (alpha_scissor_threshold != p_threshold) {
+ alpha_scissor_threshold = p_threshold;
+ _queue_redraw();
+ }
+}
+
+float SpriteBase3D::get_alpha_scissor_threshold() const {
+ return alpha_scissor_threshold;
+}
+
void SpriteBase3D::set_billboard_mode(StandardMaterial3D::BillboardMode p_mode) {
ERR_FAIL_INDEX(p_mode, 3); // Cannot use BILLBOARD_PARTICLES.
billboard_mode = p_mode;
@@ -494,6 +533,12 @@ void SpriteBase3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_alpha_cut_mode", "mode"), &SpriteBase3D::set_alpha_cut_mode);
ClassDB::bind_method(D_METHOD("get_alpha_cut_mode"), &SpriteBase3D::get_alpha_cut_mode);
+ ClassDB::bind_method(D_METHOD("set_alpha_scissor_threshold", "threshold"), &SpriteBase3D::set_alpha_scissor_threshold);
+ ClassDB::bind_method(D_METHOD("get_alpha_scissor_threshold"), &SpriteBase3D::get_alpha_scissor_threshold);
+
+ ClassDB::bind_method(D_METHOD("set_alpha_hash_scale", "threshold"), &SpriteBase3D::set_alpha_hash_scale);
+ ClassDB::bind_method(D_METHOD("get_alpha_hash_scale"), &SpriteBase3D::get_alpha_hash_scale);
+
ClassDB::bind_method(D_METHOD("set_billboard_mode", "mode"), &SpriteBase3D::set_billboard_mode);
ClassDB::bind_method(D_METHOD("get_billboard_mode"), &SpriteBase3D::get_billboard_mode);
@@ -519,7 +564,9 @@ void SpriteBase3D::_bind_methods() {
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "double_sided"), "set_draw_flag", "get_draw_flag", FLAG_DOUBLE_SIDED);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "no_depth_test"), "set_draw_flag", "get_draw_flag", FLAG_DISABLE_DEPTH_TEST);
ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "fixed_size"), "set_draw_flag", "get_draw_flag", FLAG_FIXED_SIZE);
- ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass"), "set_alpha_cut_mode", "get_alpha_cut_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "alpha_cut", PROPERTY_HINT_ENUM, "Disabled,Discard,Opaque Pre-Pass,Alpha Hash"), "set_alpha_cut_mode", "get_alpha_cut_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_scissor_threshold", PROPERTY_HINT_RANGE, "0,1,0.001"), "set_alpha_scissor_threshold", "get_alpha_scissor_threshold");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "alpha_hash_scale", PROPERTY_HINT_RANGE, "0,2,0.01"), "set_alpha_hash_scale", "get_alpha_hash_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_filter", PROPERTY_HINT_ENUM, "Nearest,Linear,Nearest Mipmap,Linear Mipmap,Nearest Mipmap Anisotropic,Linear Mipmap Anisotropic"), "set_texture_filter", "get_texture_filter");
ADD_PROPERTY(PropertyInfo(Variant::INT, "render_priority", PROPERTY_HINT_RANGE, itos(RS::MATERIAL_RENDER_PRIORITY_MIN) + "," + itos(RS::MATERIAL_RENDER_PRIORITY_MAX) + ",1"), "set_render_priority", "get_render_priority");
@@ -533,6 +580,7 @@ void SpriteBase3D::_bind_methods() {
BIND_ENUM_CONSTANT(ALPHA_CUT_DISABLED);
BIND_ENUM_CONSTANT(ALPHA_CUT_DISCARD);
BIND_ENUM_CONSTANT(ALPHA_CUT_OPAQUE_PREPASS);
+ BIND_ENUM_CONSTANT(ALPHA_CUT_HASH);
}
SpriteBase3D::SpriteBase3D() {
@@ -550,7 +598,6 @@ SpriteBase3D::SpriteBase3D() {
RS::get_singleton()->material_set_param(material, "uv1_scale", Vector3(1, 1, 1));
RS::get_singleton()->material_set_param(material, "uv2_offset", Vector3(0, 0, 0));
RS::get_singleton()->material_set_param(material, "uv2_scale", Vector3(1, 1, 1));
- RS::get_singleton()->material_set_param(material, "alpha_scissor_threshold", 0.5);
mesh = RenderingServer::get_singleton()->mesh_create();
@@ -1182,8 +1229,9 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
}
}
- notify_property_list_changed();
set_process_internal(true);
+ notify_property_list_changed();
+ _queue_redraw();
}
void AnimatedSprite3D::play_backwards(const StringName &p_name) {
diff --git a/scene/3d/sprite_3d.h b/scene/3d/sprite_3d.h
index d0fd767d89..873c321878 100644
--- a/scene/3d/sprite_3d.h
+++ b/scene/3d/sprite_3d.h
@@ -53,7 +53,9 @@ public:
enum AlphaCutMode {
ALPHA_CUT_DISABLED,
ALPHA_CUT_DISCARD,
- ALPHA_CUT_OPAQUE_PREPASS
+ ALPHA_CUT_OPAQUE_PREPASS,
+ ALPHA_CUT_HASH,
+ ALPHA_CUT_MAX
};
private:
@@ -85,6 +87,8 @@ private:
bool flags[FLAG_MAX] = {};
AlphaCutMode alpha_cut = ALPHA_CUT_DISABLED;
+ float alpha_scissor_threshold = 0.5;
+ float alpha_hash_scale = 1.0;
StandardMaterial3D::BillboardMode billboard_mode = StandardMaterial3D::BILLBOARD_DISABLED;
StandardMaterial3D::TextureFilter texture_filter = StandardMaterial3D::TEXTURE_FILTER_LINEAR_WITH_MIPMAPS;
bool pending_update = false;
@@ -143,6 +147,12 @@ public:
void set_alpha_cut_mode(AlphaCutMode p_mode);
AlphaCutMode get_alpha_cut_mode() const;
+ void set_alpha_scissor_threshold(float p_threshold);
+ float get_alpha_scissor_threshold() const;
+
+ void set_alpha_hash_scale(float p_hash_scale);
+ float get_alpha_hash_scale() const;
+
void set_billboard_mode(StandardMaterial3D::BillboardMode p_mode);
StandardMaterial3D::BillboardMode get_billboard_mode() const;
diff --git a/scene/animation/animation_blend_tree.cpp b/scene/animation/animation_blend_tree.cpp
index a00b1d8ee1..45f4f690b9 100644
--- a/scene/animation/animation_blend_tree.cpp
+++ b/scene/animation/animation_blend_tree.cpp
@@ -644,9 +644,66 @@ AnimationNodeTimeSeek::AnimationNodeTimeSeek() {
/////////////////////////////////////////////////
+bool AnimationNodeTransition::_set(const StringName &p_path, const Variant &p_value) {
+ String path = p_path;
+
+ if (!path.begins_with("input_")) {
+ return false;
+ }
+
+ int which = path.get_slicec('/', 0).get_slicec('_', 1).to_int();
+ String what = path.get_slicec('/', 1);
+
+ if (which == get_input_count() && what == "name") {
+ if (add_input(p_value)) {
+ return true;
+ }
+ return false;
+ }
+
+ ERR_FAIL_INDEX_V(which, get_input_count(), false);
+
+ if (what == "name") {
+ set_input_name(which, p_value);
+ } else if (what == "auto_advance") {
+ set_input_as_auto_advance(which, p_value);
+ } else if (what == "reset") {
+ set_input_reset(which, p_value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+bool AnimationNodeTransition::_get(const StringName &p_path, Variant &r_ret) const {
+ String path = p_path;
+
+ if (!path.begins_with("input_")) {
+ return false;
+ }
+
+ int which = path.get_slicec('/', 0).get_slicec('_', 1).to_int();
+ String what = path.get_slicec('/', 1);
+
+ ERR_FAIL_INDEX_V(which, get_input_count(), false);
+
+ if (what == "name") {
+ r_ret = get_input_name(which);
+ } else if (what == "auto_advance") {
+ r_ret = is_input_set_as_auto_advance(which);
+ } else if (what == "reset") {
+ r_ret = is_input_reset(which);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
void AnimationNodeTransition::get_parameter_list(List<PropertyInfo> *r_list) const {
String anims;
- for (int i = 0; i < enabled_inputs; i++) {
+ for (int i = 0; i < get_input_count(); i++) {
if (i > 0) {
anims += ",";
}
@@ -684,56 +741,47 @@ String AnimationNodeTransition::get_caption() const {
return "Transition";
}
-void AnimationNodeTransition::_update_inputs() {
- while (get_input_count() < enabled_inputs) {
- add_input(inputs[get_input_count()].name);
+void AnimationNodeTransition::set_input_count(int p_inputs) {
+ for (int i = get_input_count(); i < p_inputs; i++) {
+ add_input("state_" + itos(i));
}
-
- while (get_input_count() > enabled_inputs) {
+ while (get_input_count() > p_inputs) {
remove_input(get_input_count() - 1);
}
+ notify_property_list_changed();
}
-void AnimationNodeTransition::set_enabled_inputs(int p_inputs) {
- ERR_FAIL_INDEX(p_inputs, MAX_INPUTS);
- enabled_inputs = p_inputs;
- _update_inputs();
+bool AnimationNodeTransition::add_input(const String &p_name) {
+ if (AnimationNode::add_input(p_name)) {
+ input_data.push_back(InputData());
+ return true;
+ }
+ return false;
}
-int AnimationNodeTransition::get_enabled_inputs() {
- return enabled_inputs;
+void AnimationNodeTransition::remove_input(int p_index) {
+ input_data.remove_at(p_index);
+ AnimationNode::remove_input(p_index);
}
void AnimationNodeTransition::set_input_as_auto_advance(int p_input, bool p_enable) {
- ERR_FAIL_INDEX(p_input, MAX_INPUTS);
- inputs[p_input].auto_advance = p_enable;
+ ERR_FAIL_INDEX(p_input, get_input_count());
+ input_data.write[p_input].auto_advance = p_enable;
}
bool AnimationNodeTransition::is_input_set_as_auto_advance(int p_input) const {
- ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, false);
- return inputs[p_input].auto_advance;
-}
-
-void AnimationNodeTransition::set_input_caption(int p_input, const String &p_name) {
- ERR_FAIL_INDEX(p_input, MAX_INPUTS);
- inputs[p_input].name = p_name;
- set_input_name(p_input, p_name);
+ ERR_FAIL_INDEX_V(p_input, get_input_count(), false);
+ return input_data[p_input].auto_advance;
}
-String AnimationNodeTransition::get_input_caption(int p_input) const {
- ERR_FAIL_INDEX_V(p_input, MAX_INPUTS, String());
- return inputs[p_input].name;
+void AnimationNodeTransition::set_input_reset(int p_input, bool p_enable) {
+ ERR_FAIL_INDEX(p_input, get_input_count());
+ input_data.write[p_input].reset = p_enable;
}
-int AnimationNodeTransition::find_input_caption(const String &p_name) const {
- int idx = -1;
- for (int i = 0; i < MAX_INPUTS; i++) {
- if (inputs[i].name == p_name) {
- idx = i;
- break;
- }
- }
- return idx;
+bool AnimationNodeTransition::is_input_reset(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, get_input_count(), true);
+ return input_data[p_input].reset;
}
void AnimationNodeTransition::set_xfade_time(double p_fade) {
@@ -752,14 +800,6 @@ Ref<Curve> AnimationNodeTransition::get_xfade_curve() const {
return xfade_curve;
}
-void AnimationNodeTransition::set_reset(bool p_reset) {
- reset = p_reset;
-}
-
-bool AnimationNodeTransition::is_reset() const {
- return reset;
-}
-
double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_external_seeking) {
String cur_transition_request = get_parameter(transition_request);
int cur_current_index = get_parameter(current_index);
@@ -772,11 +812,11 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
bool restart = false;
if (!cur_transition_request.is_empty()) {
- int new_idx = find_input_caption(cur_transition_request);
+ int new_idx = find_input(cur_transition_request);
if (new_idx >= 0) {
if (cur_current_index == new_idx) {
// Transition to same state.
- restart = reset;
+ restart = input_data[cur_current_index].reset;
cur_prev_xfading = 0;
set_parameter(prev_xfading, 0);
cur_prev_index = -1;
@@ -807,14 +847,14 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
cur_time = 0;
}
- if (cur_current_index < 0 || cur_current_index >= enabled_inputs || cur_prev_index >= enabled_inputs) {
+ if (cur_current_index < 0 || cur_current_index >= get_input_count() || cur_prev_index >= get_input_count()) {
return 0;
}
double rem = 0.0;
if (sync) {
- for (int i = 0; i < enabled_inputs; i++) {
+ for (int i = 0; i < get_input_count(); i++) {
if (i != cur_current_index && i != cur_prev_index) {
blend_input(i, p_time, p_seek, p_is_external_seeking, 0, FILTER_IGNORE, true);
}
@@ -831,8 +871,8 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
cur_time += p_time;
}
- if (inputs[cur_current_index].auto_advance && rem <= xfade_time) {
- set_parameter(transition_request, get_input_caption((cur_current_index + 1) % enabled_inputs));
+ if (input_data[cur_current_index].auto_advance && rem <= xfade_time) {
+ set_parameter(transition_request, get_input_name((cur_current_index + 1) % get_input_count()));
}
} else { // cross-fading from prev to current
@@ -844,7 +884,7 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
// Blend values must be more than CMP_EPSILON to process discrete keys in edge.
real_t blend_inv = 1.0 - blend;
- if (reset && !p_seek && switched) { //just switched, seek to start of current
+ if (input_data[cur_current_index].reset && !p_seek && switched) { //just switched, seek to start of current
rem = blend_input(cur_current_index, 0, true, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true);
} else {
rem = blend_input(cur_current_index, p_time, p_seek, p_is_external_seeking, Math::is_zero_approx(blend_inv) ? CMP_EPSILON : blend_inv, FILTER_IGNORE, true);
@@ -869,28 +909,22 @@ double AnimationNodeTransition::process(double p_time, bool p_seek, bool p_is_ex
return rem;
}
-void AnimationNodeTransition::_validate_property(PropertyInfo &p_property) const {
- if (p_property.name.begins_with("input_")) {
- String n = p_property.name.get_slicec('/', 0).get_slicec('_', 1);
- if (n != "count") {
- int idx = n.to_int();
- if (idx >= enabled_inputs) {
- p_property.usage = PROPERTY_USAGE_NONE;
- }
- }
+void AnimationNodeTransition::_get_property_list(List<PropertyInfo> *p_list) const {
+ for (int i = 0; i < get_input_count(); i++) {
+ p_list->push_back(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
+ p_list->push_back(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/reset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL));
}
}
void AnimationNodeTransition::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_enabled_inputs", "amount"), &AnimationNodeTransition::set_enabled_inputs);
- ClassDB::bind_method(D_METHOD("get_enabled_inputs"), &AnimationNodeTransition::get_enabled_inputs);
+ ClassDB::bind_method(D_METHOD("set_input_count", "input_count"), &AnimationNodeTransition::set_input_count);
ClassDB::bind_method(D_METHOD("set_input_as_auto_advance", "input", "enable"), &AnimationNodeTransition::set_input_as_auto_advance);
ClassDB::bind_method(D_METHOD("is_input_set_as_auto_advance", "input"), &AnimationNodeTransition::is_input_set_as_auto_advance);
- ClassDB::bind_method(D_METHOD("set_input_caption", "input", "caption"), &AnimationNodeTransition::set_input_caption);
- ClassDB::bind_method(D_METHOD("get_input_caption", "input"), &AnimationNodeTransition::get_input_caption);
- ClassDB::bind_method(D_METHOD("find_input_caption", "caption"), &AnimationNodeTransition::find_input_caption);
+ ClassDB::bind_method(D_METHOD("set_input_reset", "input", "enable"), &AnimationNodeTransition::set_input_reset);
+ ClassDB::bind_method(D_METHOD("is_input_reset", "input"), &AnimationNodeTransition::is_input_reset);
ClassDB::bind_method(D_METHOD("set_xfade_time", "time"), &AnimationNodeTransition::set_xfade_time);
ClassDB::bind_method(D_METHOD("get_xfade_time"), &AnimationNodeTransition::get_xfade_time);
@@ -898,24 +932,12 @@ void AnimationNodeTransition::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_xfade_curve", "curve"), &AnimationNodeTransition::set_xfade_curve);
ClassDB::bind_method(D_METHOD("get_xfade_curve"), &AnimationNodeTransition::get_xfade_curve);
- ClassDB::bind_method(D_METHOD("set_reset", "reset"), &AnimationNodeTransition::set_reset);
- ClassDB::bind_method(D_METHOD("is_reset"), &AnimationNodeTransition::is_reset);
-
- ADD_PROPERTY(PropertyInfo(Variant::INT, "enabled_inputs", PROPERTY_HINT_RANGE, "0,31,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED), "set_enabled_inputs", "get_enabled_inputs");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "xfade_time", PROPERTY_HINT_RANGE, "0,120,0.01,suffix:s"), "set_xfade_time", "get_xfade_time");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "xfade_curve", PROPERTY_HINT_RESOURCE_TYPE, "Curve"), "set_xfade_curve", "get_xfade_curve");
- ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reset"), "set_reset", "is_reset");
-
- for (int i = 0; i < MAX_INPUTS; i++) {
- ADD_PROPERTYI(PropertyInfo(Variant::STRING, "input_" + itos(i) + "/name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_caption", "get_input_caption", i);
- ADD_PROPERTYI(PropertyInfo(Variant::BOOL, "input_" + itos(i) + "/auto_advance", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_INTERNAL), "set_input_as_auto_advance", "is_input_set_as_auto_advance", i);
- }
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "input_count", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_EDITOR | PROPERTY_USAGE_ARRAY | PROPERTY_USAGE_UPDATE_ALL_IF_MODIFIED, "Inputs,input_"), "set_input_count", "get_input_count");
}
AnimationNodeTransition::AnimationNodeTransition() {
- for (int i = 0; i < MAX_INPUTS; i++) {
- inputs[i].name = "state " + itos(i);
- }
}
/////////////////////
diff --git a/scene/animation/animation_blend_tree.h b/scene/animation/animation_blend_tree.h
index a1969bb621..3c27edbf70 100644
--- a/scene/animation/animation_blend_tree.h
+++ b/scene/animation/animation_blend_tree.h
@@ -276,16 +276,11 @@ public:
class AnimationNodeTransition : public AnimationNodeSync {
GDCLASS(AnimationNodeTransition, AnimationNodeSync);
- enum {
- MAX_INPUTS = 32
- };
struct InputData {
- String name;
bool auto_advance = false;
+ bool reset = true;
};
-
- InputData inputs[MAX_INPUTS];
- int enabled_inputs = 0;
+ Vector<InputData> input_data;
StringName time = "time";
StringName prev_xfading = "prev_xfading";
@@ -299,13 +294,12 @@ class AnimationNodeTransition : public AnimationNodeSync {
double xfade_time = 0.0;
Ref<Curve> xfade_curve;
- bool reset = true;
-
- void _update_inputs();
protected:
+ bool _get(const StringName &p_path, Variant &r_ret) const;
+ bool _set(const StringName &p_path, const Variant &p_value);
static void _bind_methods();
- void _validate_property(PropertyInfo &p_property) const;
+ void _get_property_list(List<PropertyInfo> *p_list) const;
public:
virtual void get_parameter_list(List<PropertyInfo> *r_list) const override;
@@ -314,15 +308,16 @@ public:
virtual String get_caption() const override;
- void set_enabled_inputs(int p_inputs);
- int get_enabled_inputs();
+ void set_input_count(int p_inputs);
+
+ virtual bool add_input(const String &p_name) override;
+ virtual void remove_input(int p_index) override;
void set_input_as_auto_advance(int p_input, bool p_enable);
bool is_input_set_as_auto_advance(int p_input) const;
- void set_input_caption(int p_input, const String &p_name);
- String get_input_caption(int p_input) const;
- int find_input_caption(const String &p_name) const;
+ void set_input_reset(int p_input, bool p_enable);
+ bool is_input_reset(int p_input) const;
void set_xfade_time(double p_fade);
double get_xfade_time() const;
@@ -330,9 +325,6 @@ public:
void set_xfade_curve(const Ref<Curve> &p_curve);
Ref<Curve> get_xfade_curve() const;
- void set_reset(bool p_reset);
- bool is_reset() const;
-
double process(double p_time, bool p_seek, bool p_is_external_seeking) override;
AnimationNodeTransition();
diff --git a/scene/animation/animation_player.cpp b/scene/animation/animation_player.cpp
index 63e0fb6935..2e25d685d6 100644
--- a/scene/animation/animation_player.cpp
+++ b/scene/animation/animation_player.cpp
@@ -431,6 +431,17 @@ void AnimationPlayer::_ensure_node_caches(AnimationData *p_anim, Node *p_root_ov
}
}
+ if (a->track_get_type(i) == Animation::TYPE_AUDIO) {
+ if (!node_cache->audio_anim.has(a->track_get_path(i).get_concatenated_names())) {
+ TrackNodeCache::AudioAnim aa;
+ aa.object = (Object *)child;
+ aa.audio_stream.instantiate();
+ aa.audio_stream->set_polyphony(audio_max_polyphony);
+
+ node_cache->audio_anim[a->track_get_path(i).get_concatenated_names()] = aa;
+ }
+ }
+
node_cache->last_setup_pass = setup_pass;
}
}
@@ -820,52 +831,40 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
if (!nc->node || is_stopping) {
continue;
}
-
- if (p_seeked) {
#ifdef TOOLS_ENABLED
- if (!can_call) {
- continue; // To avoid spamming the preview in editor.
- }
+ if (p_seeked && !can_call) {
+ continue; // To avoid spamming the preview in editor.
+ }
#endif // TOOLS_ENABLED
- int idx = a->track_find_key(i, p_time);
- if (idx < 0) {
- continue;
- }
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- } else {
- float start_ofs = a->audio_track_get_key_start_offset(i, idx);
- start_ofs += p_time - a->track_get_key_time(i, idx);
- float end_ofs = a->audio_track_get_key_end_offset(i, idx);
- float len = stream->get_length();
-
- if (start_ofs > len - end_ofs) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- continue;
- }
-
- nc->node->call(SNAME("set_stream"), stream);
- nc->node->call(SNAME("play"), start_ofs);
+ HashMap<StringName, TrackNodeCache::AudioAnim>::Iterator E = nc->audio_anim.find(a->track_get_path(i).get_concatenated_names());
+ ERR_CONTINUE(!E); //should it continue, or create a new one?
- nc->audio_playing = true;
- playing_caches.insert(nc);
- if (len && end_ofs > 0) { //force an end at a time
- nc->audio_len = len - start_ofs - end_ofs;
- } else {
- nc->audio_len = 0;
- }
+ TrackNodeCache::AudioAnim *aa = &E->value;
+ Node *asp = Object::cast_to<Node>(aa->object);
+ if (!asp) {
+ continue;
+ }
+ aa->length = a->get_length();
+ aa->time = p_time;
+ aa->loop = a->get_loop_mode() != Animation::LOOP_NONE;
+ aa->backward = backward;
+ if (aa->accum_pass != accum_pass) {
+ ERR_CONTINUE(cache_update_audio_size >= NODE_CACHE_UPDATE_MAX);
+ cache_update_audio[cache_update_audio_size++] = aa;
+ aa->accum_pass = accum_pass;
+ }
- nc->audio_start = p_time;
+ HashMap<int, TrackNodeCache::PlayingAudioStreamInfo> &map = aa->playing_streams;
+ // Find stream.
+ int idx = -1;
+ if (p_seeked) {
+ idx = a->track_find_key(i, p_time);
+ // Discard previous stream when seeking.
+ if (map.has(idx)) {
+ aa->audio_stream_playback->stop_stream(map[idx].index);
+ map.erase(idx);
}
-
} else {
- //find stuff to play
List<int> to_play;
if (p_started) {
int first_key = a->track_find_key(i, p_prev_time, Animation::FIND_MODE_EXACT);
@@ -875,55 +874,47 @@ void AnimationPlayer::_animation_process_animation(AnimationData *p_anim, double
}
a->track_get_key_indices_in_range(i, p_time, p_delta, &to_play, p_looped_flag);
if (to_play.size()) {
- int idx = to_play.back()->get();
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- } else {
- float start_ofs = a->audio_track_get_key_start_offset(i, idx);
- float end_ofs = a->audio_track_get_key_end_offset(i, idx);
- float len = stream->get_length();
-
- nc->node->call(SNAME("set_stream"), stream);
- nc->node->call(SNAME("play"), start_ofs);
-
- nc->audio_playing = true;
- playing_caches.insert(nc);
- if (len && end_ofs > 0) { //force an end at a time
- nc->audio_len = len - start_ofs - end_ofs;
- } else {
- nc->audio_len = 0;
- }
-
- nc->audio_start = p_time;
- }
- } else if (nc->audio_playing) {
- bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
- bool stop = false;
-
- if (!loop) {
- if ((p_time < nc->audio_start && !backward) || (p_time > nc->audio_start && backward)) {
- stop = true;
- }
- } else if (nc->audio_len > 0) {
- float len = nc->audio_start > p_time ? (a->get_length() - nc->audio_start) + p_time : p_time - nc->audio_start;
+ idx = to_play.back()->get();
+ }
+ }
+ if (idx < 0) {
+ continue;
+ }
- if (len > nc->audio_len) {
- stop = true;
- }
+ // Play stream.
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (stream.is_valid()) {
+ double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ double len = stream->get_length();
+
+ if (aa->object->call(SNAME("get_stream")) != aa->audio_stream) {
+ aa->object->call(SNAME("set_stream"), aa->audio_stream);
+ aa->audio_stream_playback.unref();
+ if (!playing_audio_stream_players.has(asp)) {
+ playing_audio_stream_players.push_back(asp);
}
+ }
+ if (!aa->object->call(SNAME("is_playing"))) {
+ aa->object->call(SNAME("play"));
+ }
+ if (!aa->object->call(SNAME("has_stream_playback"))) {
+ aa->audio_stream_playback.unref();
+ continue;
+ }
+ if (aa->audio_stream_playback.is_null()) {
+ aa->audio_stream_playback = aa->object->call(SNAME("get_stream_playback"));
+ }
- if (stop) {
- //time to stop
- nc->node->call(SNAME("stop"));
- nc->audio_playing = false;
- playing_caches.erase(nc);
- }
+ TrackNodeCache::PlayingAudioStreamInfo pasi;
+ pasi.index = aa->audio_stream_playback->play_stream(stream, start_ofs);
+ pasi.start = p_time;
+ if (len && end_ofs > 0) { // Force an end at a time.
+ pasi.len = len - start_ofs - end_ofs;
+ } else {
+ pasi.len = 0;
}
+ map[idx] = pasi;
}
} break;
@@ -1223,6 +1214,53 @@ void AnimationPlayer::_animation_update_transforms() {
ERR_CONTINUE(ba->accum_pass != accum_pass);
ba->object->set_indexed(ba->bezier_property, ba->bezier_accum);
}
+
+ for (int i = 0; i < cache_update_audio_size; i++) {
+ TrackNodeCache::AudioAnim *aa = cache_update_audio[i];
+
+ ERR_CONTINUE(aa->accum_pass != accum_pass);
+
+ // Audio ending process.
+ LocalVector<int> erase_list;
+ for (const KeyValue<int, TrackNodeCache::PlayingAudioStreamInfo> &K : aa->playing_streams) {
+ TrackNodeCache::PlayingAudioStreamInfo pasi = K.value;
+
+ bool stop = false;
+ if (!aa->audio_stream_playback->is_stream_playing(pasi.index)) {
+ stop = true;
+ }
+ if (!aa->loop) {
+ if (!aa->backward) {
+ if (aa->time < pasi.start) {
+ stop = true;
+ }
+ } else if (aa->backward) {
+ if (aa->time > pasi.start) {
+ stop = true;
+ }
+ }
+ }
+ if (pasi.len > 0) {
+ double len = 0.0;
+ if (!aa->backward) {
+ len = pasi.start > aa->time ? (aa->length - pasi.start) + aa->time : aa->time - pasi.start;
+ } else {
+ len = pasi.start < aa->time ? (aa->length - aa->time) + pasi.start : pasi.start - aa->time;
+ }
+ if (len > pasi.len) {
+ stop = true;
+ }
+ }
+ if (stop) {
+ // Time to stop.
+ aa->audio_stream_playback->stop_stream(pasi.index);
+ erase_list.push_back(K.key);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_list.size(); erase_idx++) {
+ aa->playing_streams.erase(erase_list[erase_idx]);
+ }
+ }
}
void AnimationPlayer::_animation_process(double p_delta) {
@@ -1238,6 +1276,7 @@ void AnimationPlayer::_animation_process(double p_delta) {
cache_update_size = 0;
cache_update_prop_size = 0;
cache_update_bezier_size = 0;
+ cache_update_audio_size = 0;
AnimationData *prev_from = playback.current.from;
_animation_process2(p_delta, started);
@@ -1675,6 +1714,7 @@ void AnimationPlayer::play(const StringName &p_name, double p_custom_blend, floa
}
if (get_current_animation() != p_name) {
+ _clear_audio_streams();
_stop_playing_caches(false);
}
@@ -1776,15 +1816,18 @@ float AnimationPlayer::get_playing_speed() const {
}
void AnimationPlayer::seek(double p_time, bool p_update) {
+ playback.current.pos = p_time;
+
if (!playback.current.from) {
if (playback.assigned) {
ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned));
playback.current.from = &animation_set[playback.assigned];
}
- ERR_FAIL_COND(!playback.current.from);
+ if (!playback.current.from) {
+ return; // There is no animation.
+ }
}
- playback.current.pos = p_time;
playback.seeked = true;
if (p_update) {
_animation_process(0);
@@ -1792,20 +1835,22 @@ void AnimationPlayer::seek(double p_time, bool p_update) {
}
void AnimationPlayer::seek_delta(double p_time, double p_delta) {
+ playback.current.pos = p_time - p_delta;
+
if (!playback.current.from) {
if (playback.assigned) {
ERR_FAIL_COND_MSG(!animation_set.has(playback.assigned), vformat("Animation not found: %s.", playback.assigned));
playback.current.from = &animation_set[playback.assigned];
}
- ERR_FAIL_COND(!playback.current.from);
+ if (!playback.current.from) {
+ return; // There is no animation.
+ }
}
- playback.current.pos = p_time - p_delta;
if (speed_scale != 0.0) {
p_delta /= speed_scale;
}
_animation_process(p_delta);
- //playback.current.pos=p_time;
}
bool AnimationPlayer::is_valid() const {
@@ -1856,6 +1901,7 @@ void AnimationPlayer::_node_removed(Node *p_node) {
}
void AnimationPlayer::clear_caches() {
+ _clear_audio_streams();
_stop_playing_caches(true);
node_cache_map.clear();
@@ -1867,10 +1913,19 @@ void AnimationPlayer::clear_caches() {
cache_update_size = 0;
cache_update_prop_size = 0;
cache_update_bezier_size = 0;
+ cache_update_audio_size = 0;
emit_signal(SNAME("caches_cleared"));
}
+void AnimationPlayer::_clear_audio_streams() {
+ for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+ playing_audio_stream_players[i]->call(SNAME("stop"));
+ playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+ }
+ playing_audio_stream_players.clear();
+}
+
void AnimationPlayer::set_active(bool p_active) {
if (active == p_active) {
return;
@@ -1950,6 +2005,15 @@ AnimationPlayer::AnimationMethodCallMode AnimationPlayer::get_method_call_mode()
return method_call_mode;
}
+void AnimationPlayer::set_audio_max_polyphony(int p_audio_max_polyphony) {
+ ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+ audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationPlayer::get_audio_max_polyphony() const {
+ return audio_max_polyphony;
+}
+
void AnimationPlayer::set_movie_quit_on_finish_enabled(bool p_enabled) {
movie_quit_on_finish = p_enabled;
}
@@ -1978,6 +2042,7 @@ void AnimationPlayer::_set_process(bool p_process, bool p_force) {
}
void AnimationPlayer::_stop_internal(bool p_reset, bool p_keep_state) {
+ _clear_audio_streams();
_stop_playing_caches(p_reset);
Playback &c = playback;
c.blend.clear();
@@ -2198,6 +2263,9 @@ void AnimationPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_method_call_mode", "mode"), &AnimationPlayer::set_method_call_mode);
ClassDB::bind_method(D_METHOD("get_method_call_mode"), &AnimationPlayer::get_method_call_mode);
+ ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationPlayer::set_audio_max_polyphony);
+ ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationPlayer::get_audio_max_polyphony);
+
ClassDB::bind_method(D_METHOD("set_movie_quit_on_finish_enabled", "enabled"), &AnimationPlayer::set_movie_quit_on_finish_enabled);
ClassDB::bind_method(D_METHOD("is_movie_quit_on_finish_enabled"), &AnimationPlayer::is_movie_quit_on_finish_enabled);
@@ -2223,6 +2291,7 @@ void AnimationPlayer::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "playback_active", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "speed_scale", PROPERTY_HINT_RANGE, "-64,64,0.01"), "set_speed_scale", "get_speed_scale");
ADD_PROPERTY(PropertyInfo(Variant::INT, "method_call_mode", PROPERTY_HINT_ENUM, "Deferred,Immediate"), "set_method_call_mode", "get_method_call_mode");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "movie_quit_on_finish"), "set_movie_quit_on_finish_enabled", "is_movie_quit_on_finish_enabled");
diff --git a/scene/animation/animation_player.h b/scene/animation/animation_player.h
index 2901c43dcf..b0975fbead 100644
--- a/scene/animation/animation_player.h
+++ b/scene/animation/animation_player.h
@@ -37,6 +37,7 @@
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
#include "scene/resources/animation_library.h"
+#include "scene/resources/audio_stream_polyphonic.h"
#ifdef TOOLS_ENABLED
class AnimatedValuesBackup : public RefCounted {
@@ -147,6 +148,26 @@ private:
HashMap<StringName, BezierAnim> bezier_anim;
+ struct PlayingAudioStreamInfo {
+ AudioStreamPlaybackPolyphonic::ID index = -1;
+ double start = 0.0;
+ double len = 0.0;
+ };
+
+ struct AudioAnim {
+ Ref<AudioStreamPolyphonic> audio_stream;
+ Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+ HashMap<int, PlayingAudioStreamInfo> playing_streams;
+ Object *object = nullptr;
+ uint64_t accum_pass = 0;
+ double length = 0.0;
+ double time = 0.0;
+ bool loop = false;
+ bool backward = false;
+ };
+
+ HashMap<StringName, AudioAnim> audio_anim;
+
uint32_t last_setup_pass = 0;
TrackNodeCache() {}
};
@@ -187,7 +208,10 @@ private:
int cache_update_prop_size = 0;
TrackNodeCache::BezierAnim *cache_update_bezier[NODE_CACHE_UPDATE_MAX];
int cache_update_bezier_size = 0;
+ TrackNodeCache::AudioAnim *cache_update_audio[NODE_CACHE_UPDATE_MAX];
+ int cache_update_audio_size = 0;
HashSet<TrackNodeCache *> playing_caches;
+ Vector<Node *> playing_audio_stream_players;
uint64_t accum_pass = 1;
float speed_scale = 1.0;
@@ -263,6 +287,7 @@ private:
bool reset_on_save = true;
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
AnimationMethodCallMode method_call_mode = ANIMATION_METHOD_CALL_DEFERRED;
+ int audio_max_polyphony = 32;
bool movie_quit_on_finish = false;
bool processing = false;
bool active = true;
@@ -278,6 +303,7 @@ private:
void _animation_process(double p_delta);
void _node_removed(Node *p_node);
+ void _clear_audio_streams();
void _stop_playing_caches(bool p_reset);
// bind helpers
@@ -377,6 +403,9 @@ public:
void set_method_call_mode(AnimationMethodCallMode p_mode);
AnimationMethodCallMode get_method_call_mode() const;
+ void set_audio_max_polyphony(int p_audio_max_polyphony);
+ int get_audio_max_polyphony() const;
+
void set_movie_quit_on_finish_enabled(bool p_enabled);
bool is_movie_quit_on_finish_enabled() const;
diff --git a/scene/animation/animation_tree.cpp b/scene/animation/animation_tree.cpp
index 05b6c72cbd..1c1f94c986 100644
--- a/scene/animation/animation_tree.cpp
+++ b/scene/animation/animation_tree.cpp
@@ -303,36 +303,21 @@ double AnimationNode::_blend_node(const StringName &p_subpath, const Vector<Stri
return p_node->_pre_process(new_path, new_parent, state, p_time, p_seek, p_is_external_seeking, p_connections);
}
-int AnimationNode::get_input_count() const {
- return inputs.size();
-}
-
-String AnimationNode::get_input_name(int p_input) {
- ERR_FAIL_INDEX_V(p_input, inputs.size(), String());
- return inputs[p_input].name;
-}
-
String AnimationNode::get_caption() const {
String ret = "Node";
GDVIRTUAL_CALL(_get_caption, ret);
return ret;
}
-void AnimationNode::add_input(const String &p_name) {
+bool AnimationNode::add_input(const String &p_name) {
//root nodes can't add inputs
- ERR_FAIL_COND(Object::cast_to<AnimationRootNode>(this) != nullptr);
+ ERR_FAIL_COND_V(Object::cast_to<AnimationRootNode>(this) != nullptr, false);
Input input;
- ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/"));
+ ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false);
input.name = p_name;
inputs.push_back(input);
emit_changed();
-}
-
-void AnimationNode::set_input_name(int p_input, const String &p_name) {
- ERR_FAIL_INDEX(p_input, inputs.size());
- ERR_FAIL_COND(p_name.contains(".") || p_name.contains("/"));
- inputs.write[p_input].name = p_name;
- emit_changed();
+ return true;
}
void AnimationNode::remove_input(int p_index) {
@@ -341,6 +326,34 @@ void AnimationNode::remove_input(int p_index) {
emit_changed();
}
+bool AnimationNode::set_input_name(int p_input, const String &p_name) {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), false);
+ ERR_FAIL_COND_V(p_name.contains(".") || p_name.contains("/"), false);
+ inputs.write[p_input].name = p_name;
+ emit_changed();
+ return true;
+}
+
+String AnimationNode::get_input_name(int p_input) const {
+ ERR_FAIL_INDEX_V(p_input, inputs.size(), String());
+ return inputs[p_input].name;
+}
+
+int AnimationNode::get_input_count() const {
+ return inputs.size();
+}
+
+int AnimationNode::find_input(const String &p_name) const {
+ int idx = -1;
+ for (int i = 0; i < inputs.size(); i++) {
+ if (inputs[i].name == p_name) {
+ idx = i;
+ break;
+ }
+ }
+ return idx;
+}
+
double AnimationNode::process(double p_time, bool p_seek, bool p_is_external_seeking) {
double ret = 0;
GDVIRTUAL_CALL(_process, p_time, p_seek, p_is_external_seeking, ret);
@@ -404,11 +417,12 @@ Ref<AnimationNode> AnimationNode::get_child_by_name(const StringName &p_name) {
}
void AnimationNode::_bind_methods() {
- ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count);
- ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name);
-
ClassDB::bind_method(D_METHOD("add_input", "name"), &AnimationNode::add_input);
ClassDB::bind_method(D_METHOD("remove_input", "index"), &AnimationNode::remove_input);
+ ClassDB::bind_method(D_METHOD("set_input_name", "input", "name"), &AnimationNode::set_input_name);
+ ClassDB::bind_method(D_METHOD("get_input_name", "input"), &AnimationNode::get_input_name);
+ ClassDB::bind_method(D_METHOD("get_input_count"), &AnimationNode::get_input_count);
+ ClassDB::bind_method(D_METHOD("find_input", "name"), &AnimationNode::find_input);
ClassDB::bind_method(D_METHOD("set_filter_path", "path", "enable"), &AnimationNode::set_filter_path);
ClassDB::bind_method(D_METHOD("is_path_filtered", "path"), &AnimationNode::is_path_filtered);
@@ -486,13 +500,7 @@ void AnimationTree::set_active(bool p_active) {
}
if (!active && is_inside_tree()) {
- for (const TrackCache *E : playing_caches) {
- if (ObjectDB::get_instance(E->object_id)) {
- E->object->call(SNAME("stop"));
- }
- }
-
- playing_caches.clear();
+ _clear_caches();
}
}
@@ -531,6 +539,7 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
if (!player->has_node(player->get_root())) {
ERR_PRINT("AnimationTree: AnimationPlayer root is invalid.");
set_active(false);
+ _clear_caches();
return false;
}
Node *parent = player->get_node(player->get_root());
@@ -763,6 +772,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
track_audio->object = child;
track_audio->object_id = track_audio->object->get_instance_id();
+ track_audio->audio_stream.instantiate();
+ track_audio->audio_stream->set_polyphony(audio_max_polyphony);
track = track_audio;
@@ -860,14 +871,32 @@ void AnimationTree::_animation_player_changed() {
}
void AnimationTree::_clear_caches() {
+ _clear_audio_streams();
+ _clear_playing_caches();
for (KeyValue<NodePath, TrackCache *> &K : track_cache) {
memdelete(K.value);
}
- playing_caches.clear();
track_cache.clear();
cache_valid = false;
}
+void AnimationTree::_clear_audio_streams() {
+ for (int i = 0; i < playing_audio_stream_players.size(); i++) {
+ playing_audio_stream_players[i]->call(SNAME("stop"));
+ playing_audio_stream_players[i]->call(SNAME("set_stream"), Ref<AudioStream>());
+ }
+ playing_audio_stream_players.clear();
+}
+
+void AnimationTree::_clear_playing_caches() {
+ for (const TrackCache *E : playing_caches) {
+ if (ObjectDB::get_instance(E->object_id)) {
+ E->object->call(SNAME("stop"));
+ }
+ }
+ playing_caches.clear();
+}
+
static void _call_object(Object *p_object, const StringName &p_method, const Vector<Variant> &p_params, bool p_deferred) {
// Separate function to use alloca() more efficiently
const Variant **argptrs = (const Variant **)alloca(sizeof(const Variant **) * p_params.size());
@@ -1007,6 +1036,13 @@ void AnimationTree::_process_graph(double p_delta) {
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
t->value = t->init_value;
} break;
+ case Animation::TYPE_AUDIO: {
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+ for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+ PlayingAudioTrackInfo &track_info = L.value;
+ track_info.volume = 0.0;
+ }
+ } break;
default: {
} break;
}
@@ -1026,8 +1062,8 @@ void AnimationTree::_process_graph(double p_delta) {
bool seeked = as.seeked;
Animation::LoopedFlag looped_flag = as.looped_flag;
bool is_external_seeking = as.is_external_seeking;
+ bool backward = signbit(delta); // This flag is used by the root motion calculates or detecting the end of audio stream.
#ifndef _3D_DISABLED
- bool backward = signbit(delta); // This flag is required only for the root motion since it calculates the difference between the previous and current frames.
bool calc_root = !seeked || is_external_seeking;
#endif // _3D_DISABLED
@@ -1046,9 +1082,6 @@ void AnimationTree::_process_graph(double p_delta) {
int blend_idx = state.track_map[path];
ERR_CONTINUE(blend_idx < 0 || blend_idx >= state.track_count);
real_t blend = (*as.track_blends)[blend_idx] * weight;
- if (Math::is_zero_approx(blend)) {
- continue; // Nothing to blend.
- }
Animation::TrackType ttype = a->track_get_type(i);
if (ttype != Animation::TYPE_POSITION_3D && ttype != Animation::TYPE_ROTATION_3D && ttype != Animation::TYPE_SCALE_3D && track->type != ttype) {
@@ -1060,6 +1093,9 @@ void AnimationTree::_process_graph(double p_delta) {
switch (ttype) {
case Animation::TYPE_POSITION_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1151,6 +1187,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_ROTATION_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1241,6 +1280,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_SCALE_3D: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheTransform *t = static_cast<TrackCacheTransform *>(track);
if (track->root_motion && calc_root) {
double prev_time = time - delta;
@@ -1332,6 +1374,9 @@ void AnimationTree::_process_graph(double p_delta) {
} break;
case Animation::TYPE_BLEND_SHAPE: {
#ifndef _3D_DISABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheBlendShape *t = static_cast<TrackCacheBlendShape *>(track);
float value;
@@ -1348,6 +1393,9 @@ void AnimationTree::_process_graph(double p_delta) {
#endif // _3D_DISABLED
} break;
case Animation::TYPE_VALUE: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheValue *t = static_cast<TrackCacheValue *>(track);
Animation::UpdateMode update_mode = a->value_track_get_update_mode(i);
@@ -1414,6 +1462,9 @@ void AnimationTree::_process_graph(double p_delta) {
continue;
}
#endif // TOOLS_ENABLED
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheMethod *t = static_cast<TrackCacheMethod *>(track);
if (seeked) {
@@ -1435,6 +1486,9 @@ void AnimationTree::_process_graph(double p_delta) {
}
} break;
case Animation::TYPE_BEZIER: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheBezier *t = static_cast<TrackCacheBezier *>(track);
real_t bezier = a->bezier_track_interpolate(i, time);
@@ -1445,110 +1499,87 @@ void AnimationTree::_process_graph(double p_delta) {
case Animation::TYPE_AUDIO: {
TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
- if (seeked) {
- int idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
- if (idx < 0) {
- continue;
- }
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- } else {
- double start_ofs = a->audio_track_get_key_start_offset(i, idx);
- start_ofs += time - a->track_get_key_time(i, idx);
- double end_ofs = a->audio_track_get_key_end_offset(i, idx);
- double len = stream->get_length();
-
- if (start_ofs > len - end_ofs) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- continue;
- }
-
- t->object->call(SNAME("set_stream"), stream);
- t->object->call(SNAME("play"), start_ofs);
-
- t->playing = true;
- playing_caches.insert(t);
- if (len && end_ofs > 0) { //force an end at a time
- t->len = len - start_ofs - end_ofs;
- } else {
- t->len = 0;
- }
+ Node *asp = Object::cast_to<Node>(t->object);
+ if (!asp) {
+ t->playing_streams.clear();
+ continue;
+ }
- t->start = time;
+ ObjectID oid = a->get_instance_id();
+ if (!t->playing_streams.has(oid)) {
+ t->playing_streams[oid] = PlayingAudioTrackInfo();
+ }
+ // The end of audio should be observed even if the blend value is 0, build up the information and store to the cache for that.
+ PlayingAudioTrackInfo &track_info = t->playing_streams[oid];
+ track_info.length = a->get_length();
+ track_info.time = time;
+ track_info.volume += blend;
+ track_info.loop = a->get_loop_mode() != Animation::LOOP_NONE;
+ track_info.backward = backward;
+ track_info.use_blend = a->audio_track_is_use_blend(i);
+
+ HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ // Find stream.
+ int idx = -1;
+ if (seeked) {
+ idx = a->track_find_key(i, time, is_external_seeking ? Animation::FIND_MODE_NEAREST : Animation::FIND_MODE_EXACT);
+ // Discard previous stream when seeking.
+ if (map.has(idx)) {
+ t->audio_stream_playback->stop_stream(map[idx].index);
+ map.erase(idx);
}
-
} else {
- //find stuff to play
List<int> to_play;
a->track_get_key_indices_in_range(i, time, delta, &to_play, looped_flag);
if (to_play.size()) {
- int idx = to_play.back()->get();
-
- Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
- if (!stream.is_valid()) {
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- } else {
- double start_ofs = a->audio_track_get_key_start_offset(i, idx);
- double end_ofs = a->audio_track_get_key_end_offset(i, idx);
- double len = stream->get_length();
-
- t->object->call(SNAME("set_stream"), stream);
- t->object->call(SNAME("play"), start_ofs);
-
- t->playing = true;
- playing_caches.insert(t);
- if (len && end_ofs > 0) { //force an end at a time
- t->len = len - start_ofs - end_ofs;
- } else {
- t->len = 0;
- }
-
- t->start = time;
- }
- } else if (t->playing) {
- bool loop = a->get_loop_mode() != Animation::LOOP_NONE;
-
- bool stop = false;
-
- if (!loop) {
- if (delta > 0) {
- if (time < t->start) {
- stop = true;
- }
- } else if (delta < 0) {
- if (time > t->start) {
- stop = true;
- }
- }
- } else if (t->len > 0) {
- double len = t->start > time ? (a->get_length() - t->start) + time : time - t->start;
+ idx = to_play.back()->get();
+ }
+ }
+ if (idx < 0) {
+ continue;
+ }
- if (len > t->len) {
- stop = true;
- }
+ // Play stream.
+ Ref<AudioStream> stream = a->audio_track_get_key_stream(i, idx);
+ if (stream.is_valid()) {
+ double start_ofs = a->audio_track_get_key_start_offset(i, idx);
+ double end_ofs = a->audio_track_get_key_end_offset(i, idx);
+ double len = stream->get_length();
+
+ if (t->object->call(SNAME("get_stream")) != t->audio_stream) {
+ t->object->call(SNAME("set_stream"), t->audio_stream);
+ t->audio_stream_playback.unref();
+ if (!playing_audio_stream_players.has(asp)) {
+ playing_audio_stream_players.push_back(asp);
}
+ }
+ if (!t->object->call(SNAME("is_playing"))) {
+ t->object->call(SNAME("play"));
+ }
+ if (!t->object->call(SNAME("has_stream_playback"))) {
+ t->audio_stream_playback.unref();
+ continue;
+ }
+ if (t->audio_stream_playback.is_null()) {
+ t->audio_stream_playback = t->object->call(SNAME("get_stream_playback"));
+ }
- if (stop) {
- //time to stop
- t->object->call(SNAME("stop"));
- t->playing = false;
- playing_caches.erase(t);
- }
+ PlayingAudioStreamInfo pasi;
+ pasi.index = t->audio_stream_playback->play_stream(stream, start_ofs);
+ pasi.start = time;
+ if (len && end_ofs > 0) { // Force an end at a time.
+ pasi.len = len - start_ofs - end_ofs;
+ } else {
+ pasi.len = 0;
}
+ map[idx] = pasi;
}
- real_t db = Math::linear_to_db(MAX(blend, 0.00001));
- t->object->call(SNAME("set_volume_db"), db);
} break;
case Animation::TYPE_ANIMATION: {
+ if (Math::is_zero_approx(blend)) {
+ continue; // Nothing to blend.
+ }
TrackCacheAnimation *t = static_cast<TrackCacheAnimation *>(track);
AnimationPlayer *player2 = Object::cast_to<AnimationPlayer>(t->object);
@@ -1694,6 +1725,64 @@ void AnimationTree::_process_graph(double p_delta) {
t->object->set_indexed(t->subpath, t->value);
} break;
+ case Animation::TYPE_AUDIO: {
+ TrackCacheAudio *t = static_cast<TrackCacheAudio *>(track);
+
+ // Audio ending process.
+ LocalVector<ObjectID> erase_maps;
+ for (KeyValue<ObjectID, PlayingAudioTrackInfo> &L : t->playing_streams) {
+ PlayingAudioTrackInfo &track_info = L.value;
+ float db = Math::linear_to_db(track_info.use_blend ? track_info.volume : 1.0);
+ LocalVector<int> erase_streams;
+ HashMap<int, PlayingAudioStreamInfo> &map = track_info.stream_info;
+ for (const KeyValue<int, PlayingAudioStreamInfo> &M : map) {
+ PlayingAudioStreamInfo pasi = M.value;
+
+ bool stop = false;
+ if (!t->audio_stream_playback->is_stream_playing(pasi.index)) {
+ stop = true;
+ }
+ if (!track_info.loop) {
+ if (!track_info.backward) {
+ if (track_info.time < pasi.start) {
+ stop = true;
+ }
+ } else if (track_info.backward) {
+ if (track_info.time > pasi.start) {
+ stop = true;
+ }
+ }
+ }
+ if (pasi.len > 0) {
+ double len = 0.0;
+ if (!track_info.backward) {
+ len = pasi.start > track_info.time ? (track_info.length - pasi.start) + track_info.time : track_info.time - pasi.start;
+ } else {
+ len = pasi.start < track_info.time ? (track_info.length - track_info.time) + pasi.start : pasi.start - track_info.time;
+ }
+ if (len > pasi.len) {
+ stop = true;
+ }
+ }
+ if (stop) {
+ // Time to stop.
+ t->audio_stream_playback->stop_stream(pasi.index);
+ erase_streams.push_back(M.key);
+ } else {
+ t->audio_stream_playback->set_stream_volume(pasi.index, db);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_streams.size(); erase_idx++) {
+ map.erase(erase_streams[erase_idx]);
+ }
+ if (map.size() == 0) {
+ erase_maps.push_back(L.key);
+ }
+ }
+ for (uint32_t erase_idx = 0; erase_idx < erase_maps.size(); erase_idx++) {
+ t->playing_streams.erase(erase_maps[erase_idx]);
+ }
+ } break;
default: {
} //the rest don't matter
}
@@ -1819,6 +1908,15 @@ NodePath AnimationTree::get_advance_expression_base_node() const {
return advance_expression_base_node;
}
+void AnimationTree::set_audio_max_polyphony(int p_audio_max_polyphony) {
+ ERR_FAIL_COND(p_audio_max_polyphony < 0 || p_audio_max_polyphony > 128);
+ audio_max_polyphony = p_audio_max_polyphony;
+}
+
+int AnimationTree::get_audio_max_polyphony() const {
+ return audio_max_polyphony;
+}
+
bool AnimationTree::is_state_invalid() const {
return !state.valid;
}
@@ -2034,6 +2132,9 @@ void AnimationTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_motion_track", "path"), &AnimationTree::set_root_motion_track);
ClassDB::bind_method(D_METHOD("get_root_motion_track"), &AnimationTree::get_root_motion_track);
+ ClassDB::bind_method(D_METHOD("set_audio_max_polyphony", "max_polyphony"), &AnimationTree::set_audio_max_polyphony);
+ ClassDB::bind_method(D_METHOD("get_audio_max_polyphony"), &AnimationTree::get_audio_max_polyphony);
+
ClassDB::bind_method(D_METHOD("get_root_motion_position"), &AnimationTree::get_root_motion_position);
ClassDB::bind_method(D_METHOD("get_root_motion_rotation"), &AnimationTree::get_root_motion_rotation);
ClassDB::bind_method(D_METHOD("get_root_motion_scale"), &AnimationTree::get_root_motion_scale);
@@ -2052,6 +2153,8 @@ void AnimationTree::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "active"), "set_active", "is_active");
ADD_PROPERTY(PropertyInfo(Variant::INT, "process_callback", PROPERTY_HINT_ENUM, "Physics,Idle,Manual"), "set_process_callback", "get_process_callback");
+ ADD_GROUP("Audio", "audio_");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "audio_max_polyphony", PROPERTY_HINT_RANGE, "1,127,1"), "set_audio_max_polyphony", "get_audio_max_polyphony");
ADD_GROUP("Root Motion", "root_motion_");
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_motion_track"), "set_root_motion_track", "get_root_motion_track");
diff --git a/scene/animation/animation_tree.h b/scene/animation/animation_tree.h
index ab538feb58..a6fb4a8430 100644
--- a/scene/animation/animation_tree.h
+++ b/scene/animation/animation_tree.h
@@ -35,6 +35,7 @@
#include "scene/3d/node_3d.h"
#include "scene/3d/skeleton_3d.h"
#include "scene/resources/animation.h"
+#include "scene/resources/audio_stream_polyphonic.h"
class AnimationNodeBlendTree;
class AnimationNodeStartState;
@@ -140,12 +141,12 @@ public:
virtual double process(double p_time, bool p_seek, bool p_is_external_seeking);
virtual String get_caption() const;
+ virtual bool add_input(const String &p_name);
+ virtual void remove_input(int p_index);
+ virtual bool set_input_name(int p_input, const String &p_name);
+ virtual String get_input_name(int p_input) const;
int get_input_count() const;
- String get_input_name(int p_input);
-
- void add_input(const String &p_name);
- void set_input_name(int p_input, const String &p_name);
- void remove_input(int p_index);
+ int find_input(const String &p_name) const;
void set_filter_path(const NodePath &p_path, bool p_enable);
bool is_path_filtered(const NodePath &p_path) const;
@@ -252,10 +253,28 @@ private:
}
};
- struct TrackCacheAudio : public TrackCache {
- bool playing = false;
+ // Audio stream information for each audio stream placed on the track.
+ struct PlayingAudioStreamInfo {
+ AudioStreamPlaybackPolyphonic::ID index = -1; // ID retrieved from AudioStreamPlaybackPolyphonic.
double start = 0.0;
double len = 0.0;
+ };
+
+ // Audio track information for mixng and ending.
+ struct PlayingAudioTrackInfo {
+ HashMap<int, PlayingAudioStreamInfo> stream_info;
+ double length = 0.0;
+ double time = 0.0;
+ real_t volume = 0.0;
+ bool loop = false;
+ bool backward = false;
+ bool use_blend = false;
+ };
+
+ struct TrackCacheAudio : public TrackCache {
+ Ref<AudioStreamPolyphonic> audio_stream;
+ Ref<AudioStreamPlaybackPolyphonic> audio_stream_playback;
+ HashMap<ObjectID, PlayingAudioTrackInfo> playing_streams; // Key is Animation resource ObjectID.
TrackCacheAudio() {
type = Animation::TYPE_AUDIO;
@@ -272,6 +291,7 @@ private:
HashMap<NodePath, TrackCache *> track_cache;
HashSet<TrackCache *> playing_caches;
+ Vector<Node *> playing_audio_stream_players;
Ref<AnimationNode> root;
NodePath advance_expression_base_node = NodePath(String("."));
@@ -279,6 +299,7 @@ private:
AnimationProcessCallback process_callback = ANIMATION_PROCESS_IDLE;
bool active = false;
NodePath animation_player;
+ int audio_max_polyphony = 32;
AnimationNode::State state;
bool cache_valid = false;
@@ -287,6 +308,8 @@ private:
void _setup_animation_player();
void _animation_player_changed();
void _clear_caches();
+ void _clear_playing_caches();
+ void _clear_audio_streams();
bool _update_caches(AnimationPlayer *player);
void _process_graph(double p_delta);
@@ -348,6 +371,9 @@ public:
void set_advance_expression_base_node(const NodePath &p_advance_expression_base_node);
NodePath get_advance_expression_base_node() const;
+ void set_audio_max_polyphony(int p_audio_max_polyphony);
+ int get_audio_max_polyphony() const;
+
PackedStringArray get_configuration_warnings() const override;
bool is_state_invalid() const;
diff --git a/scene/animation/root_motion_view.cpp b/scene/animation/root_motion_view.cpp
index e6b258df3e..3d8d451c70 100644
--- a/scene/animation/root_motion_view.cpp
+++ b/scene/animation/root_motion_view.cpp
@@ -80,7 +80,8 @@ bool RootMotionView::get_zero_y() const {
void RootMotionView::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
- immediate_material = StandardMaterial3D::get_material_for_2d(false, true, false, false, false);
+ immediate_material = StandardMaterial3D::get_material_for_2d(false, BaseMaterial3D::TRANSPARENCY_ALPHA, false);
+
first = true;
} break;
diff --git a/scene/audio/audio_stream_player.cpp b/scene/audio/audio_stream_player.cpp
index d40fc10441..7533a56b59 100644
--- a/scene/audio/audio_stream_player.cpp
+++ b/scene/audio/audio_stream_player.cpp
@@ -307,6 +307,10 @@ void AudioStreamPlayer::_bus_layout_changed() {
notify_property_list_changed();
}
+bool AudioStreamPlayer::has_stream_playback() {
+ return !stream_playbacks.is_empty();
+}
+
Ref<AudioStreamPlayback> AudioStreamPlayer::get_stream_playback() {
ERR_FAIL_COND_V_MSG(stream_playbacks.is_empty(), Ref<AudioStreamPlayback>(), "Player is inactive. Call play() before requesting get_stream_playback().");
return stream_playbacks[stream_playbacks.size() - 1];
@@ -347,6 +351,7 @@ void AudioStreamPlayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_max_polyphony", "max_polyphony"), &AudioStreamPlayer::set_max_polyphony);
ClassDB::bind_method(D_METHOD("get_max_polyphony"), &AudioStreamPlayer::get_max_polyphony);
+ ClassDB::bind_method(D_METHOD("has_stream_playback"), &AudioStreamPlayer::has_stream_playback);
ClassDB::bind_method(D_METHOD("get_stream_playback"), &AudioStreamPlayer::get_stream_playback);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "stream", PROPERTY_HINT_RESOURCE_TYPE, "AudioStream"), "set_stream", "get_stream");
diff --git a/scene/audio/audio_stream_player.h b/scene/audio/audio_stream_player.h
index 5368391073..d1f6fca2ee 100644
--- a/scene/audio/audio_stream_player.h
+++ b/scene/audio/audio_stream_player.h
@@ -107,6 +107,7 @@ public:
void set_stream_paused(bool p_pause);
bool get_stream_paused() const;
+ bool has_stream_playback();
Ref<AudioStreamPlayback> get_stream_playback();
AudioStreamPlayer();
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index c977d9d2fb..b084cb5bea 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -3088,6 +3088,8 @@ void CodeEdit::_filter_code_completion_candidates_impl() {
}
code_completion_options.append_array(completion_options_casei);
+ code_completion_options.append_array(completion_options_substr);
+ code_completion_options.append_array(completion_options_substr_casei);
code_completion_options.append_array(completion_options_subseq);
code_completion_options.append_array(completion_options_subseq_casei);
diff --git a/scene/gui/graph_edit.cpp b/scene/gui/graph_edit.cpp
index af52f6664a..dc23bcb14a 100644
--- a/scene/gui/graph_edit.cpp
+++ b/scene/gui/graph_edit.cpp
@@ -920,7 +920,8 @@ void GraphEdit::_draw_connection_line(CanvasItem *p_where, const Vector2 &p_from
scaled_points.push_back(points[i] * p_zoom);
}
- p_where->draw_polyline_colors(scaled_points, colors, Math::floor(p_width * get_theme_default_base_scale()), lines_antialiased);
+ // Thickness below 0.5 doesn't look good on the graph or its minimap.
+ p_where->draw_polyline_colors(scaled_points, colors, MAX(0.5, Math::floor(p_width * get_theme_default_base_scale())), lines_antialiased);
}
void GraphEdit::_connections_layer_draw() {
@@ -1088,7 +1089,7 @@ void GraphEdit::_minimap_draw() {
from_color = from_color.lerp(activity_color, E.activity);
to_color = to_color.lerp(activity_color, E.activity);
}
- _draw_connection_line(minimap, from_position, to_position, from_color, to_color, 0.1, minimap->_convert_from_graph_position(Vector2(zoom, zoom)).length());
+ _draw_connection_line(minimap, from_position, to_position, from_color, to_color, 0.5, minimap->_convert_from_graph_position(Vector2(zoom, zoom)).length());
}
// Draw the "camera" viewport.
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 3323c0f848..35176f0edd 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -917,6 +917,12 @@ void CanvasItem::force_update_transform() {
notification(NOTIFICATION_TRANSFORM_CHANGED);
}
+void CanvasItem::_validate_property(PropertyInfo &p_property) const {
+ if (hide_clip_children && p_property.name == "clip_children") {
+ p_property.usage = PROPERTY_USAGE_NONE;
+ }
+}
+
void CanvasItem::_bind_methods() {
ClassDB::bind_method(D_METHOD("_top_level_raise_self"), &CanvasItem::_top_level_raise_self);
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 644fe856ec..1c84ea338a 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -106,6 +106,7 @@ private:
bool use_parent_material = false;
bool notify_local_transform = false;
bool notify_transform = false;
+ bool hide_clip_children = false;
ClipChildrenMode clip_children_mode = CLIP_CHILDREN_DISABLED;
@@ -155,6 +156,9 @@ protected:
void _notification(int p_what);
static void _bind_methods();
+ void _validate_property(PropertyInfo &p_property) const;
+
+ _FORCE_INLINE_ void set_hide_clip_children(bool p_value) { hide_clip_children = p_value; }
GDVIRTUAL0(_draw)
public:
diff --git a/scene/main/http_request.cpp b/scene/main/http_request.cpp
index 46ba7e67eb..0d53f740db 100644
--- a/scene/main/http_request.cpp
+++ b/scene/main/http_request.cpp
@@ -33,7 +33,7 @@
#include "scene/main/timer.h"
Error HTTPRequest::_request() {
- return client->connect_to_host(url, port, use_tls, validate_tls);
+ return client->connect_to_host(url, port, use_tls ? tls_options : nullptr);
}
Error HTTPRequest::_parse_url(const String &p_url) {
@@ -96,7 +96,7 @@ String HTTPRequest::get_header_value(const PackedStringArray &p_headers, const S
return value;
}
-Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, bool p_tls_validate_domain, HTTPClient::Method p_method, const String &p_request_data) {
+Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const String &p_request_data) {
// Copy the string into a raw buffer.
Vector<uint8_t> raw_data;
@@ -108,10 +108,10 @@ Error HTTPRequest::request(const String &p_url, const Vector<String> &p_custom_h
memcpy(w, charstr.ptr(), len);
}
- return request_raw(p_url, p_custom_headers, p_tls_validate_domain, p_method, raw_data);
+ return request_raw(p_url, p_custom_headers, p_method, raw_data);
}
-Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_custom_headers, bool p_tls_validate_domain, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) {
+Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_custom_headers, HTTPClient::Method p_method, const Vector<uint8_t> &p_request_data_raw) {
ERR_FAIL_COND_V(!is_inside_tree(), ERR_UNCONFIGURED);
ERR_FAIL_COND_V_MSG(requesting, ERR_BUSY, "HTTPRequest is processing a request. Wait for completion or cancel it before attempting a new one.");
@@ -127,8 +127,6 @@ Error HTTPRequest::request_raw(const String &p_url, const Vector<String> &p_cust
return err;
}
- validate_tls = p_tls_validate_domain;
-
headers = p_custom_headers;
if (accept_gzip) {
@@ -590,10 +588,16 @@ void HTTPRequest::_timeout() {
_defer_done(RESULT_TIMEOUT, 0, PackedStringArray(), PackedByteArray());
}
+void HTTPRequest::set_tls_options(const Ref<TLSOptions> &p_options) {
+ ERR_FAIL_COND(p_options.is_null() || p_options->is_server());
+ tls_options = p_options;
+}
+
void HTTPRequest::_bind_methods() {
- ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "tls_validate_domain", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String()));
- ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "tls_validate_domain", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(true), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray()));
+ ClassDB::bind_method(D_METHOD("request", "url", "custom_headers", "method", "request_data"), &HTTPRequest::request, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(String()));
+ ClassDB::bind_method(D_METHOD("request_raw", "url", "custom_headers", "method", "request_data_raw"), &HTTPRequest::request_raw, DEFVAL(PackedStringArray()), DEFVAL(HTTPClient::METHOD_GET), DEFVAL(PackedByteArray()));
ClassDB::bind_method(D_METHOD("cancel_request"), &HTTPRequest::cancel_request);
+ ClassDB::bind_method(D_METHOD("set_tls_options", "client_options"), &HTTPRequest::set_tls_options);
ClassDB::bind_method(D_METHOD("get_http_client_status"), &HTTPRequest::get_http_client_status);
@@ -654,6 +658,7 @@ void HTTPRequest::_bind_methods() {
HTTPRequest::HTTPRequest() {
client = Ref<HTTPClient>(HTTPClient::create());
+ tls_options = TLSOptions::client();
timer = memnew(Timer);
timer->set_one_shot(true);
timer->connect("timeout", callable_mp(this, &HTTPRequest::_timeout));
diff --git a/scene/main/http_request.h b/scene/main/http_request.h
index add4e9538d..9a91171eaf 100644
--- a/scene/main/http_request.h
+++ b/scene/main/http_request.h
@@ -68,8 +68,8 @@ private:
String url;
int port = 80;
Vector<String> headers;
- bool validate_tls = false;
bool use_tls = false;
+ Ref<TLSOptions> tls_options;
HTTPClient::Method method;
Vector<uint8_t> request_data;
@@ -125,8 +125,8 @@ protected:
static void _bind_methods();
public:
- Error request(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), bool p_tls_validate_domain = true, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const String &p_request_data = ""); //connects to a full url and perform request
- Error request_raw(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), bool p_tls_validate_domain = true, HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector<uint8_t> &p_request_data_raw = Vector<uint8_t>()); //connects to a full url and perform request
+ Error request(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const String &p_request_data = ""); //connects to a full url and perform request
+ Error request_raw(const String &p_url, const Vector<String> &p_custom_headers = Vector<String>(), HTTPClient::Method p_method = HTTPClient::METHOD_GET, const Vector<uint8_t> &p_request_data_raw = Vector<uint8_t>()); //connects to a full url and perform request
void cancel_request();
HTTPClient::Status get_http_client_status() const;
@@ -161,6 +161,8 @@ public:
void set_http_proxy(const String &p_host, int p_port);
void set_https_proxy(const String &p_host, int p_port);
+ void set_tls_options(const Ref<TLSOptions> &p_options);
+
HTTPRequest();
};
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index de486094fe..ba75c92c85 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -1987,7 +1987,16 @@ String Node::get_scene_file_path() const {
}
void Node::set_editor_description(const String &p_editor_description) {
+ if (data.editor_description == p_editor_description) {
+ return;
+ }
+
data.editor_description = p_editor_description;
+
+ if (Engine::get_singleton()->is_editor_hint() && is_inside_tree()) {
+ // Update tree so the tooltip in the Scene tree dock is also updated in the editor.
+ get_tree()->tree_changed();
+ }
}
String Node::get_editor_description() const {
diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp
index cb3c865f05..bfbc92a8d4 100644
--- a/scene/resources/animation.cpp
+++ b/scene/resources/animation.cpp
@@ -127,6 +127,10 @@ bool Animation::_set(const StringName &p_name, const Variant &p_value) {
}
}
return true;
+ } else if (what == "use_blend") {
+ if (track_get_type(track) == TYPE_AUDIO) {
+ audio_track_set_use_blend(track, p_value);
+ }
} else if (what == "interp") {
track_set_interpolation_type(track, InterpolationType(p_value.operator int()));
} else if (what == "loop_wrap") {
@@ -528,7 +532,10 @@ bool Animation::_get(const StringName &p_name, Variant &r_ret) const {
}
return true;
-
+ } else if (what == "use_blend") {
+ if (track_get_type(track) == TYPE_AUDIO) {
+ r_ret = audio_track_is_use_blend(track);
+ }
} else if (what == "interp") {
r_ret = track_get_interpolation_type(track);
} else if (what == "loop_wrap") {
@@ -834,6 +841,9 @@ void Animation::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/loop_wrap", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
p_list->push_back(PropertyInfo(Variant::ARRAY, "tracks/" + itos(i) + "/keys", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
}
+ if (track_get_type(i) == TYPE_AUDIO) {
+ p_list->push_back(PropertyInfo(Variant::BOOL, "tracks/" + itos(i) + "/use_blend", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL));
+ }
}
}
@@ -3581,6 +3591,27 @@ real_t Animation::audio_track_get_key_end_offset(int p_track, int p_key) const {
return at->values[p_key].value.end_offset;
}
+void Animation::audio_track_set_use_blend(int p_track, bool p_enable) {
+ ERR_FAIL_INDEX(p_track, tracks.size());
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND(t->type != TYPE_AUDIO);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ at->use_blend = p_enable;
+ emit_changed();
+}
+
+bool Animation::audio_track_is_use_blend(int p_track) const {
+ ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+ Track *t = tracks[p_track];
+ ERR_FAIL_COND_V(t->type != TYPE_AUDIO, false);
+
+ AudioTrack *at = static_cast<AudioTrack *>(t);
+
+ return at->use_blend;
+}
+
//
int Animation::animation_track_insert_key(int p_track, double p_time, const StringName &p_animation) {
@@ -3813,6 +3844,8 @@ void Animation::_bind_methods() {
ClassDB::bind_method(D_METHOD("audio_track_get_key_stream", "track_idx", "key_idx"), &Animation::audio_track_get_key_stream);
ClassDB::bind_method(D_METHOD("audio_track_get_key_start_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_start_offset);
ClassDB::bind_method(D_METHOD("audio_track_get_key_end_offset", "track_idx", "key_idx"), &Animation::audio_track_get_key_end_offset);
+ ClassDB::bind_method(D_METHOD("audio_track_set_use_blend", "track_idx", "enable"), &Animation::audio_track_set_use_blend);
+ ClassDB::bind_method(D_METHOD("audio_track_is_use_blend", "track_idx"), &Animation::audio_track_is_use_blend);
ClassDB::bind_method(D_METHOD("animation_track_insert_key", "track_idx", "time", "animation"), &Animation::animation_track_insert_key);
ClassDB::bind_method(D_METHOD("animation_track_set_key_animation", "track_idx", "key_idx", "animation"), &Animation::animation_track_set_key_animation);
diff --git a/scene/resources/animation.h b/scene/resources/animation.h
index 00a0761e0a..2c2ddb7095 100644
--- a/scene/resources/animation.h
+++ b/scene/resources/animation.h
@@ -213,6 +213,7 @@ private:
struct AudioTrack : public Track {
Vector<TKey<AudioKey>> values;
+ bool use_blend = true;
AudioTrack() {
type = TYPE_AUDIO;
@@ -447,6 +448,8 @@ public:
Ref<Resource> audio_track_get_key_stream(int p_track, int p_key) const;
real_t audio_track_get_key_start_offset(int p_track, int p_key) const;
real_t audio_track_get_key_end_offset(int p_track, int p_key) const;
+ void audio_track_set_use_blend(int p_track, bool p_enable);
+ bool audio_track_is_use_blend(int p_track) const;
int animation_track_insert_key(int p_track, double p_time, const StringName &p_animation);
void animation_track_set_key_animation(int p_track, int p_key, const StringName &p_animation);
diff --git a/scene/resources/capsule_shape_2d.cpp b/scene/resources/capsule_shape_2d.cpp
index 5dc4e64c2e..5309e54846 100644
--- a/scene/resources/capsule_shape_2d.cpp
+++ b/scene/resources/capsule_shape_2d.cpp
@@ -88,8 +88,10 @@ void CapsuleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector<Vector2> points = _get_points();
Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
+
if (is_collision_outline_enabled()) {
points.push_back(points[0]);
+ col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
}
}
diff --git a/scene/resources/circle_shape_2d.cpp b/scene/resources/circle_shape_2d.cpp
index a15dfc0a54..0b207c33ca 100644
--- a/scene/resources/circle_shape_2d.cpp
+++ b/scene/resources/circle_shape_2d.cpp
@@ -84,6 +84,7 @@ void CircleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
if (is_collision_outline_enabled()) {
points.push_back(points[0]);
+ col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
}
}
diff --git a/scene/resources/convex_polygon_shape_2d.cpp b/scene/resources/convex_polygon_shape_2d.cpp
index e51b48a4b1..7f19dd63e6 100644
--- a/scene/resources/convex_polygon_shape_2d.cpp
+++ b/scene/resources/convex_polygon_shape_2d.cpp
@@ -76,13 +76,14 @@ void ConvexPolygonShape2D::draw(const RID &p_to_rid, const Color &p_color) {
return;
}
- Vector<Color> col;
- col.push_back(p_color);
+ Vector<Color> col = { p_color };
RenderingServer::get_singleton()->canvas_item_add_polygon(p_to_rid, points, col);
+
if (is_collision_outline_enabled()) {
+ col = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, points, col);
- // Draw the last segment as it's not drawn by `canvas_item_add_polyline()`.
- RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], p_color, 1.0);
+ // Draw the last segment.
+ RenderingServer::get_singleton()->canvas_item_add_line(p_to_rid, points[points.size() - 1], points[0], Color(p_color, 1.0));
}
}
diff --git a/scene/resources/material.cpp b/scene/resources/material.cpp
index f4aa0637f7..d6393966b1 100644
--- a/scene/resources/material.cpp
+++ b/scene/resources/material.cpp
@@ -2316,52 +2316,30 @@ BaseMaterial3D::TextureChannel BaseMaterial3D::get_refraction_texture_channel()
return refraction_texture_channel;
}
-Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard, bool p_billboard_y, bool p_msdf, bool p_no_depth, bool p_fixed_size, TextureFilter p_filter, RID *r_shader_rid) {
- int64_t hash = 0;
- if (p_shaded) {
- hash |= 1 << 0;
- }
- if (p_transparent) {
- hash |= 1 << 1;
- }
- if (p_cut_alpha) {
- hash |= 1 << 2;
- }
- if (p_opaque_prepass) {
- hash |= 1 << 3;
- }
- if (p_double_sided) {
- hash |= 1 << 4;
- }
- if (p_billboard) {
- hash |= 1 << 5;
- }
- if (p_billboard_y) {
- hash |= 1 << 6;
- }
- if (p_msdf) {
- hash |= 1 << 7;
- }
- if (p_no_depth) {
- hash |= 1 << 8;
- }
- if (p_fixed_size) {
- hash |= 1 << 9;
- }
- hash = hash_murmur3_one_64(p_filter, hash);
-
- if (materials_for_2d.has(hash)) {
+Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, Transparency p_transparency, bool p_double_sided, bool p_billboard, bool p_billboard_y, bool p_msdf, bool p_no_depth, bool p_fixed_size, TextureFilter p_filter, RID *r_shader_rid) {
+ uint64_t key = 0;
+ key |= ((int8_t)p_shaded & 0x01) << 0;
+ key |= ((int8_t)p_transparency & 0x07) << 1; // Bits 1-3.
+ key |= ((int8_t)p_double_sided & 0x01) << 4;
+ key |= ((int8_t)p_billboard & 0x01) << 5;
+ key |= ((int8_t)p_billboard_y & 0x01) << 6;
+ key |= ((int8_t)p_msdf & 0x01) << 7;
+ key |= ((int8_t)p_no_depth & 0x01) << 8;
+ key |= ((int8_t)p_fixed_size & 0x01) << 9;
+ key |= ((int8_t)p_filter & 0x07) << 10; // Bits 10-13.
+
+ if (materials_for_2d.has(key)) {
if (r_shader_rid) {
- *r_shader_rid = materials_for_2d[hash]->get_shader_rid();
+ *r_shader_rid = materials_for_2d[key]->get_shader_rid();
}
- return materials_for_2d[hash];
+ return materials_for_2d[key];
}
Ref<StandardMaterial3D> material;
material.instantiate();
material->set_shading_mode(p_shaded ? SHADING_MODE_PER_PIXEL : SHADING_MODE_UNSHADED);
- material->set_transparency(p_transparent ? (p_opaque_prepass ? TRANSPARENCY_ALPHA_DEPTH_PRE_PASS : (p_cut_alpha ? TRANSPARENCY_ALPHA_SCISSOR : TRANSPARENCY_ALPHA)) : TRANSPARENCY_DISABLED);
+ material->set_transparency(p_transparency);
material->set_cull_mode(p_double_sided ? CULL_DISABLED : CULL_BACK);
material->set_flag(FLAG_SRGB_VERTEX_COLOR, true);
material->set_flag(FLAG_ALBEDO_FROM_VERTEX_COLOR, true);
@@ -2374,13 +2352,13 @@ Ref<Material> BaseMaterial3D::get_material_for_2d(bool p_shaded, bool p_transpar
material->set_billboard_mode(p_billboard_y ? BILLBOARD_FIXED_Y : BILLBOARD_ENABLED);
}
- materials_for_2d[hash] = material;
+ materials_for_2d[key] = material;
if (r_shader_rid) {
- *r_shader_rid = materials_for_2d[hash]->get_shader_rid();
+ *r_shader_rid = materials_for_2d[key]->get_shader_rid();
}
- return materials_for_2d[hash];
+ return materials_for_2d[key];
}
void BaseMaterial3D::set_on_top_of_alpha() {
diff --git a/scene/resources/material.h b/scene/resources/material.h
index fe1448cd4c..23f3a8824d 100644
--- a/scene/resources/material.h
+++ b/scene/resources/material.h
@@ -760,7 +760,7 @@ public:
static void finish_shaders();
static void flush_changes();
- static Ref<Material> get_material_for_2d(bool p_shaded, bool p_transparent, bool p_double_sided, bool p_cut_alpha, bool p_opaque_prepass, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RID *r_shader_rid = nullptr);
+ static Ref<Material> get_material_for_2d(bool p_shaded, Transparency p_transparency, bool p_double_sided, bool p_billboard = false, bool p_billboard_y = false, bool p_msdf = false, bool p_no_depth = false, bool p_fixed_size = false, TextureFilter p_filter = TEXTURE_FILTER_LINEAR_WITH_MIPMAPS, RID *r_shader_rid = nullptr);
virtual RID get_shader_rid() const override;
diff --git a/scene/resources/rectangle_shape_2d.cpp b/scene/resources/rectangle_shape_2d.cpp
index 84c147b4b4..65b1653293 100644
--- a/scene/resources/rectangle_shape_2d.cpp
+++ b/scene/resources/rectangle_shape_2d.cpp
@@ -79,11 +79,7 @@ void RectangleShape2D::draw(const RID &p_to_rid, const Color &p_color) {
stroke_points.write[3] = Vector2(-size.x, size.y) * 0.5;
stroke_points.write[4] = -size * 0.5;
- Vector<Color> stroke_colors;
- stroke_colors.resize(5);
- for (int i = 0; i < 5; i++) {
- stroke_colors.write[i] = (p_color);
- }
+ Vector<Color> stroke_colors = { Color(p_color, 1.0) };
RenderingServer::get_singleton()->canvas_item_add_polyline(p_to_rid, stroke_points, stroke_colors);
}
diff --git a/scene/resources/style_box.cpp b/scene/resources/style_box.cpp
index fbbe98d022..9e0b856ecd 100644
--- a/scene/resources/style_box.cpp
+++ b/scene/resources/style_box.cpp
@@ -1007,7 +1007,7 @@ void StyleBoxLine::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::COLOR, "color"), "set_color", "get_color");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_begin", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_begin", "get_grow_begin");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "grow_end", PROPERTY_HINT_RANGE, "-300,300,1,suffix:px"), "set_grow_end", "get_grow_end");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,10,suffix:px"), "set_thickness", "get_thickness");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "thickness", PROPERTY_HINT_RANGE, "0,100,suffix:px"), "set_thickness", "get_thickness");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical");
}
diff --git a/scene/resources/world_2d.cpp b/scene/resources/world_2d.cpp
index c35f7360b5..2a70139bcb 100644
--- a/scene/resources/world_2d.cpp
+++ b/scene/resources/world_2d.cpp
@@ -47,6 +47,13 @@ RID World2D::get_space() const {
}
RID World2D::get_navigation_map() const {
+ if (navigation_map.is_null()) {
+ navigation_map = NavigationServer2D::get_singleton()->map_create();
+ NavigationServer2D::get_singleton()->map_set_active(navigation_map, true);
+ NavigationServer2D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/2d/default_cell_size"));
+ NavigationServer2D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/2d/default_edge_connection_margin"));
+ NavigationServer2D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/2d/default_link_connection_radius"));
+ }
return navigation_map;
}
@@ -77,13 +84,6 @@ World2D::World2D() {
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/2d/default_gravity_vector", Vector2(0, 1)));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_linear_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 0.1));
PhysicsServer2D::get_singleton()->area_set_param(space, PhysicsServer2D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/default_angular_damp", PROPERTY_HINT_RANGE, "-1,100,0.001,or_greater"), 1.0));
-
- // Create and configure the navigation_map to be more friendly with pixels than meters.
- navigation_map = NavigationServer2D::get_singleton()->map_create();
- NavigationServer2D::get_singleton()->map_set_active(navigation_map, true);
- NavigationServer2D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/2d/default_cell_size", 1));
- NavigationServer2D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 1));
- NavigationServer2D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_DEF("navigation/2d/default_link_connection_radius", 4));
}
World2D::~World2D() {
@@ -92,5 +92,7 @@ World2D::~World2D() {
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
RenderingServer::get_singleton()->free(canvas);
PhysicsServer2D::get_singleton()->free(space);
- NavigationServer2D::get_singleton()->free(navigation_map);
+ if (navigation_map.is_valid()) {
+ NavigationServer2D::get_singleton()->free(navigation_map);
+ }
}
diff --git a/scene/resources/world_2d.h b/scene/resources/world_2d.h
index 92239ed167..f02dddd2fe 100644
--- a/scene/resources/world_2d.h
+++ b/scene/resources/world_2d.h
@@ -44,7 +44,7 @@ class World2D : public Resource {
RID canvas;
RID space;
- RID navigation_map;
+ mutable RID navigation_map;
HashSet<Viewport *> viewports;
diff --git a/scene/resources/world_3d.cpp b/scene/resources/world_3d.cpp
index 536edd334b..cc4d261c0d 100644
--- a/scene/resources/world_3d.cpp
+++ b/scene/resources/world_3d.cpp
@@ -55,6 +55,13 @@ RID World3D::get_space() const {
}
RID World3D::get_navigation_map() const {
+ if (navigation_map.is_null()) {
+ navigation_map = NavigationServer3D::get_singleton()->map_create();
+ NavigationServer3D::get_singleton()->map_set_active(navigation_map, true);
+ NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_GET("navigation/3d/default_cell_size"));
+ NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_GET("navigation/3d/default_edge_connection_margin"));
+ NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_GET("navigation/3d/default_link_connection_radius"));
+ }
return navigation_map;
}
@@ -146,12 +153,6 @@ World3D::World3D() {
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_GRAVITY_VECTOR, GLOBAL_DEF_BASIC("physics/3d/default_gravity_vector", Vector3(0, -1, 0)));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_LINEAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_linear_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1));
PhysicsServer3D::get_singleton()->area_set_param(space, PhysicsServer3D::AREA_PARAM_ANGULAR_DAMP, GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/default_angular_damp", PROPERTY_HINT_RANGE, "0,100,0.001,or_greater"), 0.1));
-
- navigation_map = NavigationServer3D::get_singleton()->map_create();
- NavigationServer3D::get_singleton()->map_set_active(navigation_map, true);
- NavigationServer3D::get_singleton()->map_set_cell_size(navigation_map, GLOBAL_DEF("navigation/3d/default_cell_size", 0.25));
- NavigationServer3D::get_singleton()->map_set_edge_connection_margin(navigation_map, GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.25));
- NavigationServer3D::get_singleton()->map_set_link_connection_radius(navigation_map, GLOBAL_DEF("navigation/3d/default_link_connection_radius", 1.0));
}
World3D::~World3D() {
@@ -160,5 +161,7 @@ World3D::~World3D() {
ERR_FAIL_NULL(NavigationServer3D::get_singleton());
PhysicsServer3D::get_singleton()->free(space);
RenderingServer::get_singleton()->free(scenario);
- NavigationServer3D::get_singleton()->free(navigation_map);
+ if (navigation_map.is_valid()) {
+ NavigationServer3D::get_singleton()->free(navigation_map);
+ }
}
diff --git a/scene/resources/world_3d.h b/scene/resources/world_3d.h
index 7cbc9f08c9..ad17daf466 100644
--- a/scene/resources/world_3d.h
+++ b/scene/resources/world_3d.h
@@ -46,8 +46,8 @@ class World3D : public Resource {
private:
RID space;
- RID navigation_map;
RID scenario;
+ mutable RID navigation_map;
Ref<Environment> environment;
Ref<Environment> fallback_environment;
diff --git a/scene/resources/world_boundary_shape_2d.cpp b/scene/resources/world_boundary_shape_2d.cpp
index 49f0873a3e..35cb8ef13d 100644
--- a/scene/resources/world_boundary_shape_2d.cpp
+++ b/scene/resources/world_boundary_shape_2d.cpp
@@ -76,11 +76,12 @@ real_t WorldBoundaryShape2D::get_distance() const {
void WorldBoundaryShape2D::draw(const RID &p_to_rid, const Color &p_color) {
Vector2 point = get_distance() * get_normal();
+ real_t line_width = 3.0;
Vector2 l1[2] = { point - get_normal().orthogonal() * 100, point + get_normal().orthogonal() * 100 };
- RS::get_singleton()->canvas_item_add_line(p_to_rid, l1[0], l1[1], p_color, 3);
- Vector2 l2[2] = { point, point + get_normal() * 30 };
- RS::get_singleton()->canvas_item_add_line(p_to_rid, l2[0], l2[1], p_color, 3);
+ RS::get_singleton()->canvas_item_add_line(p_to_rid, l1[0], l1[1], p_color, line_width);
+ Vector2 l2[2] = { point + get_normal().normalized() * (0.5 * line_width), point + get_normal() * 30 };
+ RS::get_singleton()->canvas_item_add_line(p_to_rid, l2[0], l2[1], p_color, line_width);
}
Rect2 WorldBoundaryShape2D::get_rect() const {
diff --git a/servers/navigation_server_2d.cpp b/servers/navigation_server_2d.cpp
index 88178d5c7d..2e4087c1de 100644
--- a/servers/navigation_server_2d.cpp
+++ b/servers/navigation_server_2d.cpp
@@ -81,35 +81,12 @@ NavigationServer2D *NavigationServer2D::singleton = nullptr;
return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1))); \
}
-#define FORWARD_4(FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, CONV_0, CONV_1, CONV_2, CONV_3) \
- NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) { \
- return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3)); \
- }
-
-#define FORWARD_4_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, CONV_0, CONV_1, CONV_2, CONV_3) \
- NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) \
- const { \
- return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3))); \
- }
-
-#define FORWARD_4_C(FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, CONV_0, CONV_1, CONV_2, CONV_3) \
- NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3) \
- const { \
- return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3)); \
- }
-
#define FORWARD_5_R_C(CONV_R, FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \
NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \
const { \
return CONV_R(NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4))); \
}
-#define FORWARD_5_C(FUNC_NAME, T_0, D_0, T_1, D_1, T_2, D_2, T_3, D_3, T_4, D_4, CONV_0, CONV_1, CONV_2, CONV_3, CONV_4) \
- NavigationServer2D::FUNC_NAME(T_0 D_0, T_1 D_1, T_2 D_2, T_3 D_3, T_4 D_4) \
- const { \
- return NavigationServer3D::get_singleton()->FUNC_NAME(CONV_0(D_0), CONV_1(D_1), CONV_2(D_2), CONV_3(D_3), CONV_4(D_4)); \
- }
-
static RID rid_to_rid(const RID d) {
return d;
}
@@ -155,18 +132,14 @@ static Transform3D trf2_to_trf3(const Transform2D &d) {
return Transform3D(b, o);
}
-static StringName sn_to_sn(const StringName &d) {
- return d;
-}
-
-static Variant var_to_var(const Variant &d) {
- return d;
-}
-
static ObjectID id_to_id(const ObjectID &id) {
return id;
}
+static Callable callable_to_callable(const Callable &c) {
+ return c;
+}
+
static Ref<NavigationMesh> poly_to_mesh(Ref<NavigationPolygon> d) {
if (d.is_valid()) {
return d->get_navigation_mesh();
@@ -308,7 +281,7 @@ void NavigationServer2D::_bind_methods() {
ClassDB::bind_method(D_METHOD("agent_set_target_velocity", "agent", "target_velocity"), &NavigationServer2D::agent_set_target_velocity);
ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer2D::agent_set_position);
ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer2D::agent_is_map_changed);
- ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "object_id", "method", "userdata"), &NavigationServer2D::agent_set_callback, DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "callback"), &NavigationServer2D::agent_set_callback);
ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer2D::free);
@@ -420,7 +393,7 @@ void FORWARD_2(agent_set_target_velocity, RID, p_agent, Vector2, p_velocity, rid
void FORWARD_2(agent_set_position, RID, p_agent, Vector2, p_position, rid_to_rid, v2_to_v3);
void FORWARD_2(agent_set_ignore_y, RID, p_agent, bool, p_ignore, rid_to_rid, bool_to_bool);
bool FORWARD_1_C(agent_is_map_changed, RID, p_agent, rid_to_rid);
-void FORWARD_4(agent_set_callback, RID, p_agent, ObjectID, p_object_id, StringName, p_method, Variant, p_udata, rid_to_rid, id_to_id, sn_to_sn, var_to_var);
+void FORWARD_2(agent_set_callback, RID, p_agent, Callable, p_callback, rid_to_rid, callable_to_callable);
void FORWARD_1(free, RID, p_object, rid_to_rid);
diff --git a/servers/navigation_server_2d.h b/servers/navigation_server_2d.h
index b50808cddf..cd18476182 100644
--- a/servers/navigation_server_2d.h
+++ b/servers/navigation_server_2d.h
@@ -223,7 +223,7 @@ public:
virtual bool agent_is_map_changed(RID p_agent) const;
/// Callback called at the end of the RVO process
- virtual void agent_set_callback(RID p_agent, ObjectID p_object_id, StringName p_method, Variant p_udata = Variant());
+ virtual void agent_set_callback(RID p_agent, Callable p_callback);
virtual void query_path(const Ref<NavigationPathQueryParameters2D> &p_query_parameters, Ref<NavigationPathQueryResult2D> p_query_result) const;
diff --git a/servers/navigation_server_3d.cpp b/servers/navigation_server_3d.cpp
index 07256badef..8f2cff0e04 100644
--- a/servers/navigation_server_3d.cpp
+++ b/servers/navigation_server_3d.cpp
@@ -30,9 +30,7 @@
#include "navigation_server_3d.h"
-#ifdef DEBUG_ENABLED
#include "core/config/project_settings.h"
-#endif // DEBUG_ENABLED
NavigationServer3D *NavigationServer3D::singleton = nullptr;
@@ -113,7 +111,7 @@ void NavigationServer3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("agent_set_target_velocity", "agent", "target_velocity"), &NavigationServer3D::agent_set_target_velocity);
ClassDB::bind_method(D_METHOD("agent_set_position", "agent", "position"), &NavigationServer3D::agent_set_position);
ClassDB::bind_method(D_METHOD("agent_is_map_changed", "agent"), &NavigationServer3D::agent_is_map_changed);
- ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "object_id", "method", "userdata"), &NavigationServer3D::agent_set_callback, DEFVAL(Variant()));
+ ClassDB::bind_method(D_METHOD("agent_set_callback", "agent", "callback"), &NavigationServer3D::agent_set_callback);
ClassDB::bind_method(D_METHOD("free_rid", "rid"), &NavigationServer3D::free);
@@ -145,6 +143,14 @@ NavigationServer3D::NavigationServer3D() {
ERR_FAIL_COND(singleton != nullptr);
singleton = this;
+ GLOBAL_DEF("navigation/2d/default_cell_size", 1);
+ GLOBAL_DEF("navigation/2d/default_edge_connection_margin", 1);
+ GLOBAL_DEF("navigation/2d/default_link_connection_radius", 4);
+
+ GLOBAL_DEF("navigation/3d/default_cell_size", 0.25);
+ GLOBAL_DEF("navigation/3d/default_edge_connection_margin", 0.25);
+ GLOBAL_DEF("navigation/3d/default_link_connection_radius", 1.0);
+
#ifdef DEBUG_ENABLED
debug_navigation_edge_connection_color = GLOBAL_DEF("debug/shapes/navigation/edge_connection_color", Color(1.0, 0.0, 1.0, 1.0));
debug_navigation_geometry_edge_color = GLOBAL_DEF("debug/shapes/navigation/geometry_edge_color", Color(0.5, 1.0, 1.0, 1.0));
diff --git a/servers/navigation_server_3d.h b/servers/navigation_server_3d.h
index 4d81726fc3..9c468d1b10 100644
--- a/servers/navigation_server_3d.h
+++ b/servers/navigation_server_3d.h
@@ -238,7 +238,7 @@ public:
virtual bool agent_is_map_changed(RID p_agent) const = 0;
/// Callback called at the end of the RVO process
- virtual void agent_set_callback(RID p_agent, ObjectID p_object_id, StringName p_method, Variant p_udata = Variant()) = 0;
+ virtual void agent_set_callback(RID p_agent, Callable p_callback) = 0;
/// Destroy the `RID`
virtual void free(RID p_object) = 0;
diff --git a/servers/rendering/dummy/storage/mesh_storage.h b/servers/rendering/dummy/storage/mesh_storage.h
index 2d66c225e8..aba362c956 100644
--- a/servers/rendering/dummy/storage/mesh_storage.h
+++ b/servers/rendering/dummy/storage/mesh_storage.h
@@ -126,6 +126,7 @@ public:
virtual void mesh_instance_set_skeleton(RID p_mesh_instance, RID p_skeleton) override {}
virtual void mesh_instance_set_blend_shape_weight(RID p_mesh_instance, int p_shape, float p_weight) override {}
virtual void mesh_instance_check_for_update(RID p_mesh_instance) override {}
+ virtual void mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) override {}
virtual void update_mesh_instances() override {}
/* MULTIMESH API */
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index 638fe44266..0f4fa1b9c4 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -1431,6 +1431,7 @@ void RendererCanvasRenderRD::canvas_render_items(RID p_to_render_target, Item *p
const Item::CommandMesh *cm = static_cast<const Item::CommandMesh *>(c);
if (cm->mesh_instance.is_valid()) {
mesh_storage->mesh_instance_check_for_update(cm->mesh_instance);
+ mesh_storage->mesh_instance_set_canvas_item_transform(cm->mesh_instance, canvas_transform_inverse * ci->final_transform);
update_skeletons = true;
}
}
diff --git a/servers/rendering/renderer_rd/shaders/skeleton.glsl b/servers/rendering/renderer_rd/shaders/skeleton.glsl
index f5b233cca0..59c161548c 100644
--- a/servers/rendering/renderer_rd/shaders/skeleton.glsl
+++ b/servers/rendering/renderer_rd/shaders/skeleton.glsl
@@ -51,6 +51,15 @@ layout(push_constant, std430) uniform Params {
bool normalized_blend_shapes;
uint pad0;
uint pad1;
+
+ vec2 skeleton_transform_x;
+ vec2 skeleton_transform_y;
+
+ vec2 skeleton_transform_offset;
+ vec2 inverse_transform_x;
+
+ vec2 inverse_transform_y;
+ vec2 inverse_transform_offset;
}
params;
@@ -158,8 +167,12 @@ void main() {
m += mat4(bone_transforms.data[bones_23.x], bone_transforms.data[bones_23.x + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.x;
m += mat4(bone_transforms.data[bones_23.y], bone_transforms.data[bones_23.y + 1], vec4(0.0, 0.0, 1.0, 0.0), vec4(0.0, 0.0, 0.0, 1.0)) * weights_23.y;
- //reverse order because its transposed
- vertex = (vec4(vertex, 0.0, 1.0) * m).xy;
+ mat4 skeleton_matrix = mat4(vec4(params.skeleton_transform_x, 0.0, 0.0), vec4(params.skeleton_transform_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(params.skeleton_transform_offset, 0.0, 1.0));
+ mat4 inverse_matrix = mat4(vec4(params.inverse_transform_x, 0.0, 0.0), vec4(params.inverse_transform_y, 0.0, 0.0), vec4(0.0, 0.0, 1.0, 0.0), vec4(params.inverse_transform_offset, 0.0, 1.0));
+
+ m = skeleton_matrix * transpose(m) * inverse_matrix;
+
+ vertex = (m * vec4(vertex, 0.0, 1.0)).xy;
}
uint dst_offset = index * params.vertex_stride;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
index 2015a2fca0..9124886764 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.cpp
@@ -930,6 +930,11 @@ void MeshStorage::mesh_instance_check_for_update(RID p_mesh_instance) {
}
}
+void MeshStorage::mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) {
+ MeshInstance *mi = mesh_instance_owner.get_or_null(p_mesh_instance);
+ mi->canvas_item_transform_2d = p_transform;
+}
+
void MeshStorage::update_mesh_instances() {
while (dirty_mesh_instance_weights.first()) {
MeshInstance *mi = dirty_mesh_instance_weights.first()->self();
@@ -981,6 +986,22 @@ void MeshStorage::update_mesh_instances() {
push_constant.skin_stride = (mi->mesh->surfaces[i]->skin_buffer_size / mi->mesh->surfaces[i]->vertex_count) / 4;
push_constant.skin_weight_offset = (mi->mesh->surfaces[i]->format & RS::ARRAY_FLAG_USE_8_BONE_WEIGHTS) ? 4 : 2;
+ Transform2D transform = mi->canvas_item_transform_2d.affine_inverse() * sk->base_transform_2d;
+ push_constant.skeleton_transform_x[0] = transform.columns[0][0];
+ push_constant.skeleton_transform_x[1] = transform.columns[0][1];
+ push_constant.skeleton_transform_y[0] = transform.columns[1][0];
+ push_constant.skeleton_transform_y[1] = transform.columns[1][1];
+ push_constant.skeleton_transform_offset[0] = transform.columns[2][0];
+ push_constant.skeleton_transform_offset[1] = transform.columns[2][1];
+
+ Transform2D inverse_transform = transform.affine_inverse();
+ push_constant.inverse_transform_x[0] = inverse_transform.columns[0][0];
+ push_constant.inverse_transform_x[1] = inverse_transform.columns[0][1];
+ push_constant.inverse_transform_y[0] = inverse_transform.columns[1][0];
+ push_constant.inverse_transform_y[1] = inverse_transform.columns[1][1];
+ push_constant.inverse_transform_offset[0] = inverse_transform.columns[2][0];
+ push_constant.inverse_transform_offset[1] = inverse_transform.columns[2][1];
+
push_constant.blend_shape_count = mi->mesh->blend_shape_count;
push_constant.normalized_blend_shapes = mi->mesh->blend_shape_mode == RS::BLEND_SHAPE_MODE_NORMALIZED;
push_constant.pad0 = 0;
diff --git a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
index b62da5fd7b..c921523941 100644
--- a/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
+++ b/servers/rendering/renderer_rd/storage_rd/mesh_storage.h
@@ -178,6 +178,7 @@ private:
bool weights_dirty = false;
SelfList<MeshInstance> weight_update_list;
SelfList<MeshInstance> array_update_list;
+ Transform2D canvas_item_transform_2d;
MeshInstance() :
weight_update_list(this), array_update_list(this) {}
};
@@ -256,6 +257,14 @@ private:
uint32_t normalized_blend_shapes;
uint32_t pad0;
uint32_t pad1;
+ float skeleton_transform_x[2];
+ float skeleton_transform_y[2];
+
+ float skeleton_transform_offset[2];
+ float inverse_transform_x[2];
+
+ float inverse_transform_y[2];
+ float inverse_transform_offset[2];
};
enum {
@@ -548,6 +557,7 @@ public:
virtual void mesh_instance_set_skeleton(RID p_mesh_instance, RID p_skeleton) override;
virtual void mesh_instance_set_blend_shape_weight(RID p_mesh_instance, int p_shape, float p_weight) override;
virtual void mesh_instance_check_for_update(RID p_mesh_instance) override;
+ virtual void mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) override;
virtual void update_mesh_instances() override;
/* MULTIMESH API */
diff --git a/servers/rendering/storage/mesh_storage.h b/servers/rendering/storage/mesh_storage.h
index 704c2e015c..76e46a696a 100644
--- a/servers/rendering/storage/mesh_storage.h
+++ b/servers/rendering/storage/mesh_storage.h
@@ -83,6 +83,7 @@ public:
virtual void mesh_instance_set_skeleton(RID p_mesh_instance, RID p_skeleton) = 0;
virtual void mesh_instance_set_blend_shape_weight(RID p_mesh_instance, int p_shape, float p_weight) = 0;
virtual void mesh_instance_check_for_update(RID p_mesh_instance) = 0;
+ virtual void mesh_instance_set_canvas_item_transform(RID p_mesh_instance, const Transform2D &p_transform) = 0;
virtual void update_mesh_instances() = 0;
/* MULTIMESH API */
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 4ef562adcb..97907bed48 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2875,7 +2875,7 @@ void RenderingServer::init() {
GLOBAL_DEF("rendering/2d/shadow_atlas/size", 2048);
// Number of commands that can be drawn per frame.
- GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/gl_compatibility/item_buffer_size", PROPERTY_HINT_RANGE, "1024,1048576,1"), 16384);
+ GLOBAL_DEF_RST(PropertyInfo(Variant::INT, "rendering/gl_compatibility/item_buffer_size", PROPERTY_HINT_RANGE, "128,1048576,1"), 16384);
GLOBAL_DEF("rendering/shader_compiler/shader_cache/enabled", true);
GLOBAL_DEF("rendering/shader_compiler/shader_cache/compress", true);
diff --git a/tests/core/input/test_input_event_mouse.h b/tests/core/input/test_input_event_mouse.h
new file mode 100644
index 0000000000..0da4f14160
--- /dev/null
+++ b/tests/core/input/test_input_event_mouse.h
@@ -0,0 +1,81 @@
+/**************************************************************************/
+/* test_input_event_mouse.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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_INPUT_EVENT_MOUSE_H
+#define TEST_INPUT_EVENT_MOUSE_H
+
+#include "core/input/input_event.h"
+#include "tests/test_macros.h"
+
+namespace TestInputEventMouse {
+
+TEST_CASE("[InputEventMouse] Mouse button mask is set correctly") {
+ InputEventMouse mousekey;
+
+ mousekey.set_button_mask(MouseButtonMask::LEFT);
+ CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::LEFT));
+
+ mousekey.set_button_mask(MouseButtonMask::MB_XBUTTON1);
+ CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON1));
+
+ mousekey.set_button_mask(MouseButtonMask::MB_XBUTTON2);
+ CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MB_XBUTTON2));
+
+ mousekey.set_button_mask(MouseButtonMask::MIDDLE);
+ CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::MIDDLE));
+
+ mousekey.set_button_mask(MouseButtonMask::RIGHT);
+ CHECK(mousekey.get_button_mask().has_flag(MouseButtonMask::RIGHT));
+}
+
+TEST_CASE("[InputEventMouse] Setting the mouse position works correctly") {
+ InputEventMouse mousekey;
+
+ mousekey.set_position(Vector2{ 10, 10 });
+ CHECK(mousekey.get_position() == Vector2{ 10, 10 });
+
+ mousekey.set_position(Vector2{ -1, -1 });
+ CHECK(mousekey.get_position() == Vector2{ -1, -1 });
+}
+
+TEST_CASE("[InputEventMouse] Setting the global mouse position works correctly") {
+ InputEventMouse mousekey;
+
+ mousekey.set_global_position(Vector2{ 10, 10 });
+ CHECK(mousekey.get_global_position() == Vector2{ 10, 10 });
+ CHECK(mousekey.get_global_position() != Vector2{ 1, 1 });
+
+ mousekey.set_global_position(Vector2{ -1, -1 });
+ CHECK(mousekey.get_global_position() == Vector2{ -1, -1 });
+ CHECK(mousekey.get_global_position() != Vector2{ 1, 1 });
+}
+} // namespace TestInputEventMouse
+
+#endif // TEST_INPUT_EVENT_MOUSE_H
diff --git a/tests/core/math/test_transform_2d.h b/tests/core/math/test_transform_2d.h
index dc2b6e2ba8..ca27776180 100644
--- a/tests/core/math/test_transform_2d.h
+++ b/tests/core/math/test_transform_2d.h
@@ -84,6 +84,19 @@ TEST_CASE("[Transform2D] rotation") {
CHECK(orig.rotated_local(phi) == orig * R);
}
+TEST_CASE("[Transform2D] Interpolation") {
+ Transform2D rotate_scale_skew_pos = Transform2D(Math::deg_to_rad(170.0), Vector2(3.6, 8.0), Math::deg_to_rad(20.0), Vector2(2.4, 6.8));
+ Transform2D rotate_scale_skew_pos_halfway = Transform2D(Math::deg_to_rad(85.0), Vector2(2.3, 4.5), Math::deg_to_rad(10.0), Vector2(1.2, 3.4));
+ Transform2D interpolated = Transform2D().interpolate_with(rotate_scale_skew_pos, 0.5);
+ CHECK(interpolated.get_origin().is_equal_approx(rotate_scale_skew_pos_halfway.get_origin()));
+ CHECK(interpolated.get_rotation() == doctest::Approx(rotate_scale_skew_pos_halfway.get_rotation()));
+ CHECK(interpolated.get_scale().is_equal_approx(rotate_scale_skew_pos_halfway.get_scale()));
+ CHECK(interpolated.get_skew() == doctest::Approx(rotate_scale_skew_pos_halfway.get_skew()));
+ CHECK(interpolated.is_equal_approx(rotate_scale_skew_pos_halfway));
+ interpolated = rotate_scale_skew_pos.interpolate_with(Transform2D(), 0.5);
+ CHECK(interpolated.is_equal_approx(rotate_scale_skew_pos_halfway));
+}
+
TEST_CASE("[Transform2D] Finite number checks") {
const Vector2 x(0, 1);
const Vector2 infinite(NAN, NAN);
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index 8c563f94ac..c7f2d4cbfb 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -32,6 +32,7 @@
#include "tests/core/config/test_project_settings.h"
#include "tests/core/input/test_input_event_key.h"
+#include "tests/core/input/test_input_event_mouse.h"
#include "tests/core/input/test_shortcut.h"
#include "tests/core/io/test_config_file.h"
#include "tests/core/io/test_file_access.h"
diff --git a/thirdparty/enet/enet/godot_ext.h b/thirdparty/enet/enet/godot_ext.h
index 648f3d2f24..06a621b790 100644
--- a/thirdparty/enet/enet/godot_ext.h
+++ b/thirdparty/enet/enet/godot_ext.h
@@ -11,8 +11,8 @@
*/
ENET_API void enet_address_set_ip(ENetAddress * address, const uint8_t * ip, size_t size);
-ENET_API int enet_host_dtls_server_setup (ENetHost *, void *, void *);
-ENET_API int enet_host_dtls_client_setup (ENetHost *, void *, uint8_t, const char *);
+ENET_API int enet_host_dtls_server_setup (ENetHost *, void *);
+ENET_API int enet_host_dtls_client_setup (ENetHost *, const char *, void *);
ENET_API void enet_host_refuse_new_connections (ENetHost *, int);
#endif // __ENET_GODOT_EXT_H__
diff --git a/thirdparty/enet/godot.cpp b/thirdparty/enet/godot.cpp
index 47298dcf6a..ea7f4957a2 100644
--- a/thirdparty/enet/godot.cpp
+++ b/thirdparty/enet/godot.cpp
@@ -164,16 +164,14 @@ class ENetDTLSClient : public ENetGodotSocket {
bool connected = false;
Ref<PacketPeerUDP> udp;
Ref<PacketPeerDTLS> dtls;
- bool verify = false;
+ Ref<TLSOptions> tls_options;
String for_hostname;
- Ref<X509Certificate> cert;
IPAddress local_address;
public:
- ENetDTLSClient(ENetUDP *p_base, Ref<X509Certificate> p_cert, bool p_verify, String p_for_hostname) {
- verify = p_verify;
+ ENetDTLSClient(ENetUDP *p_base, String p_for_hostname, Ref<TLSOptions> p_options) {
for_hostname = p_for_hostname;
- cert = p_cert;
+ tls_options = p_options;
udp.instantiate();
dtls = Ref<PacketPeerDTLS>(PacketPeerDTLS::create());
if (p_base->bound) {
@@ -205,7 +203,7 @@ public:
Error sendto(const uint8_t *p_buffer, int p_len, int &r_sent, IPAddress p_ip, uint16_t p_port) {
if (!connected) {
udp->connect_to_host(p_ip, p_port);
- if (dtls->connect_to_peer(udp, verify, for_hostname, cert)) {
+ if (dtls->connect_to_peer(udp, for_hostname, tls_options)) {
return FAILED;
}
connected = true;
@@ -265,7 +263,7 @@ class ENetDTLSServer : public ENetGodotSocket {
IPAddress local_address;
public:
- ENetDTLSServer(ENetUDP *p_base, Ref<CryptoKey> p_key, Ref<X509Certificate> p_cert) {
+ ENetDTLSServer(ENetUDP *p_base, Ref<TLSOptions> p_options) {
udp_server.instantiate();
if (p_base->bound) {
uint16_t port;
@@ -274,7 +272,7 @@ public:
bind(local_address, port);
}
server = Ref<DTLSServer>(DTLSServer::create());
- server->setup(p_key, p_cert);
+ server->setup(p_options);
}
~ENetDTLSServer() {
@@ -437,22 +435,22 @@ ENetSocket enet_socket_create(ENetSocketType type) {
return socket;
}
-int enet_host_dtls_server_setup(ENetHost *host, void *p_key, void *p_cert) {
+int enet_host_dtls_server_setup(ENetHost *host, void *p_options) {
ENetGodotSocket *sock = (ENetGodotSocket *)host->socket;
if (!sock->can_upgrade()) {
return -1;
}
- host->socket = memnew(ENetDTLSServer((ENetUDP *)sock, Ref<CryptoKey>((CryptoKey *)p_key), Ref<X509Certificate>((X509Certificate *)p_cert)));
+ host->socket = memnew(ENetDTLSServer(static_cast<ENetUDP *>(sock), Ref<TLSOptions>(static_cast<TLSOptions *>(p_options))));
memdelete(sock);
return 0;
}
-int enet_host_dtls_client_setup(ENetHost *host, void *p_cert, uint8_t p_verify, const char *p_for_hostname) {
+int enet_host_dtls_client_setup(ENetHost *host, const char *p_for_hostname, void *p_options) {
ENetGodotSocket *sock = (ENetGodotSocket *)host->socket;
if (!sock->can_upgrade()) {
return -1;
}
- host->socket = memnew(ENetDTLSClient((ENetUDP *)sock, Ref<X509Certificate>((X509Certificate *)p_cert), p_verify, String::utf8(p_for_hostname)));
+ host->socket = memnew(ENetDTLSClient(static_cast<ENetUDP *>(sock), String::utf8(p_for_hostname), Ref<TLSOptions>(static_cast<TLSOptions *>(p_options))));
memdelete(sock);
return 0;
}