summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-rw-r--r--modules/enet/doc_classes/ENetMultiplayerPeer.xml11
-rw-r--r--modules/enet/enet_multiplayer_peer.cpp242
-rw-r--r--modules/enet/enet_multiplayer_peer.h11
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp29
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd60
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out1
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd77
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out1
-rw-r--r--modules/gltf/gltf_document.cpp2
-rw-r--r--modules/mbedtls/stream_peer_mbedtls.cpp2
-rw-r--r--modules/mobile_vr/doc_classes/MobileVRInterface.xml2
-rw-r--r--modules/mobile_vr/mobile_vr_interface.cpp2
-rw-r--r--modules/mobile_vr/register_types.cpp2
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs43
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs88
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Variant.cs56
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSpawner.xml6
-rw-r--r--modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml3
-rw-r--r--modules/multiplayer/doc_classes/SceneMultiplayer.xml47
-rw-r--r--modules/multiplayer/editor/replication_editor_plugin.cpp4
-rw-r--r--modules/multiplayer/scene_multiplayer.cpp236
-rw-r--r--modules/multiplayer/scene_multiplayer.h26
-rw-r--r--modules/multiplayer/scene_replication_interface.cpp2
-rw-r--r--modules/multiplayer/scene_replication_interface.h2
-rw-r--r--modules/openxr/doc_classes/OpenXRAction.xml4
-rw-r--r--modules/openxr/doc_classes/OpenXRActionSet.xml2
-rw-r--r--modules/openxr/doc_classes/OpenXRInterface.xml2
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.cpp10
-rw-r--r--modules/openxr/extensions/openxr_hand_tracking_extension.h2
-rw-r--r--modules/openxr/openxr_interface.cpp6
-rw-r--r--modules/openxr/scene/openxr_hand.cpp2
-rw-r--r--modules/regex/doc_classes/RegEx.xml11
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/tga/image_loader_tga.cpp8
-rw-r--r--modules/upnp/doc_classes/UPNP.xml4
-rw-r--r--modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml8
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.cpp18
-rw-r--r--modules/webrtc/webrtc_multiplayer_peer.h29
-rw-r--r--modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml16
-rw-r--r--modules/websocket/doc_classes/WebSocketPeer.xml2
-rw-r--r--modules/websocket/websocket_multiplayer_peer.cpp17
-rw-r--r--modules/websocket/websocket_multiplayer_peer.h5
-rw-r--r--modules/webxr/register_types.cpp2
-rw-r--r--modules/zip/zip_packer.cpp1
48 files changed, 756 insertions, 360 deletions
diff --git a/modules/enet/doc_classes/ENetMultiplayerPeer.xml b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
index f02fc893fd..2e0d1f5079 100644
--- a/modules/enet/doc_classes/ENetMultiplayerPeer.xml
+++ b/modules/enet/doc_classes/ENetMultiplayerPeer.xml
@@ -21,13 +21,6 @@
[b]Note:[/b] The [code]host[/code] must have exactly one peer in the [constant ENetPacketPeer.STATE_CONNECTED] state.
</description>
</method>
- <method name="close_connection">
- <return type="void" />
- <param index="0" name="wait_usec" type="int" default="100" />
- <description>
- Closes the connection. Ignored if no connection is currently established. If this is a server it tries to notify all clients before forcibly disconnecting them. If this is a client it simply closes the connection to the server.
- </description>
- </method>
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="address" type="String" />
@@ -37,7 +30,7 @@
<param index="4" name="out_bandwidth" type="int" default="0" />
<param index="5" name="local_port" type="int" default="0" />
<description>
- Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [code]port[/code] is the port the server is listening on. The [code]channel_count[/code] parameter can be used to specify the number of ENet channels allocated for the connection. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [code]local_port[/code] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques.
+ Create client that connects to a server at [code]address[/code] using specified [code]port[/code]. The given address needs to be either a fully qualified domain name (e.g. [code]"www.example.com"[/code]) or an IP address in IPv4 or IPv6 format (e.g. [code]"192.168.1.1"[/code]). The [code]port[/code] is the port the server is listening on. The [code]channel_count[/code] parameter can be used to specify the number of ENet channels allocated for the connection. The [code]in_bandwidth[/code] and [code]out_bandwidth[/code] parameters can be used to limit the incoming and outgoing bandwidth to the given number of bytes per second. The default of 0 means unlimited bandwidth. Note that ENet will strategically drop packets on specific sides of a connection between peers to ensure the peer's bandwidth is not overwhelmed. The bandwidth parameters also determine the window size of a connection which limits the amount of reliable packets that may be in transit at any given time. Returns [constant OK] if a client was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the client could not be created. If [code]local_port[/code] is specified, the client will also listen to the given port; this is useful for some NAT traversal techniques.
</description>
</method>
<method name="create_mesh">
@@ -55,7 +48,7 @@
<param index="3" name="in_bandwidth" type="int" default="0" />
<param index="4" name="out_bandwidth" type="int" default="0" />
<description>
- Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method close_connection] first) or [constant ERR_CANT_CREATE] if the server could not be created.
+ Create server that listens to connections via [code]port[/code]. The port needs to be an available, unused port between 0 and 65535. Note that ports below 1024 are privileged and may require elevated permissions depending on the platform. To change the interface the server listens on, use [method set_bind_ip]. The default IP is the wildcard [code]"*"[/code], which listens on all available interfaces. [code]max_clients[/code] is the maximum number of clients that are allowed at once, any number up to 4095 may be used, although the achievable number of simultaneous clients may be far lower and depends on the application. For additional details on the bandwidth parameters, see [method create_client]. Returns [constant OK] if a server was created, [constant ERR_ALREADY_IN_USE] if this ENetMultiplayerPeer instance already has an open connection (in which case you need to call [method MultiplayerPeer.close] first) or [constant ERR_CANT_CREATE] if the server could not be created.
</description>
</method>
<method name="get_peer" qualifiers="const">
diff --git a/modules/enet/enet_multiplayer_peer.cpp b/modules/enet/enet_multiplayer_peer.cpp
index c93db3b972..43c512ae16 100644
--- a/modules/enet/enet_multiplayer_peer.cpp
+++ b/modules/enet/enet_multiplayer_peer.cpp
@@ -146,92 +146,21 @@ void ENetMultiplayerPeer::_store_packet(int32_t p_source, ENetConnection::Event
incoming_packets.push_back(packet);
}
-bool ENetMultiplayerPeer::_parse_server_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
- switch (p_type) {
- case ENetConnection::EVENT_CONNECT: {
- if (is_refusing_new_connections()) {
- p_event.peer->reset();
- return false;
- }
- // Client joined with invalid ID, probably trying to exploit us.
- if (p_event.data < 2 || peers.has((int)p_event.data)) {
- p_event.peer->reset();
- return false;
- }
- int id = p_event.data;
- p_event.peer->set_meta(SNAME("_net_id"), id);
- peers[id] = p_event.peer;
-
- emit_signal(SNAME("peer_connected"), id);
- return false;
- }
- case ENetConnection::EVENT_DISCONNECT: {
- int id = p_event.peer->get_meta(SNAME("_net_id"));
- if (!peers.has(id)) {
- // Never fully connected.
- return false;
- }
-
- emit_signal(SNAME("peer_disconnected"), id);
- peers.erase(id);
- return false;
- }
- case ENetConnection::EVENT_RECEIVE: {
- int32_t source = p_event.peer->get_meta(SNAME("_net_id"));
- _store_packet(source, p_event);
- return false;
+void ENetMultiplayerPeer::_disconnect_inactive_peers() {
+ HashSet<int> to_drop;
+ for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
+ if (E.value->is_active()) {
+ continue;
}
- default:
- return true;
+ to_drop.insert(E.key);
}
-}
-
-bool ENetMultiplayerPeer::_parse_client_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event) {
- switch (p_type) {
- case ENetConnection::EVENT_CONNECT: {
- connection_status = CONNECTION_CONNECTED;
- emit_signal(SNAME("peer_connected"), 1);
- emit_signal(SNAME("connection_succeeded"));
- return false;
+ for (const int &P : to_drop) {
+ peers.erase(P);
+ if (hosts.has(P)) {
+ hosts.erase(P);
}
- case ENetConnection::EVENT_DISCONNECT: {
- if (connection_status == CONNECTION_CONNECTED) {
- // Client just disconnected from server.
- emit_signal(SNAME("server_disconnected"));
- } else {
- emit_signal(SNAME("connection_failed"));
- }
- close_connection();
- return true;
- }
- case ENetConnection::EVENT_RECEIVE: {
- _store_packet(1, p_event);
- return false;
- }
- default:
- return true;
- }
-}
-
-bool ENetMultiplayerPeer::_parse_mesh_event(ENetConnection::EventType p_type, ENetConnection::Event &p_event, int p_peer_id) {
- switch (p_type) {
- case ENetConnection::EVENT_CONNECT:
- p_event.peer->reset();
- return false;
- case ENetConnection::EVENT_DISCONNECT:
- if (peers.has(p_peer_id)) {
- emit_signal(SNAME("peer_disconnected"), p_peer_id);
- peers.erase(p_peer_id);
- }
- hosts.erase(p_peer_id);
- return true;
- case ENetConnection::EVENT_RECEIVE: {
- _store_packet(p_peer_id, p_event);
- return false;
- } break;
- default:
- // Nothing to do
- return true;
+ ERR_CONTINUE(active_mode == MODE_CLIENT && P != TARGET_PEER_SERVER);
+ emit_signal(SNAME("peer_disconnected"), P);
}
}
@@ -240,74 +169,92 @@ void ENetMultiplayerPeer::poll() {
_pop_current_packet();
+ _disconnect_inactive_peers();
+
switch (active_mode) {
case MODE_CLIENT: {
- if (peers.has(1) && !peers[1]->is_active()) {
- if (connection_status == CONNECTION_CONNECTED) {
- // Client just disconnected from server.
- emit_signal(SNAME("server_disconnected"));
- } else {
- emit_signal(SNAME("connection_failed"));
- }
- close_connection();
+ if (!peers.has(1)) {
+ close();
return;
}
ENetConnection::Event event;
ENetConnection::EventType ret = hosts[0]->service(0, event);
- if (ret == ENetConnection::EVENT_ERROR) {
- return;
- }
do {
- if (_parse_client_event(ret, event)) {
- return;
+ if (ret == ENetConnection::EVENT_CONNECT) {
+ connection_status = CONNECTION_CONNECTED;
+ emit_signal(SNAME("peer_connected"), 1);
+ } else if (ret == ENetConnection::EVENT_DISCONNECT) {
+ if (connection_status == CONNECTION_CONNECTED) {
+ // Client just disconnected from server.
+ emit_signal(SNAME("peer_disconnected"), 1);
+ }
+ close();
+ } else if (ret == ENetConnection::EVENT_RECEIVE) {
+ _store_packet(1, event);
+ } else if (ret != ENetConnection::EVENT_NONE) {
+ close(); // Error.
}
- } while (hosts[0]->check_events(ret, event) > 0);
+ } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
} break;
case MODE_SERVER: {
- for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (!(E.value->is_active())) {
- emit_signal(SNAME("peer_disconnected"), E.value->get_meta(SNAME("_net_id")));
- peers.erase(E.key);
- }
- }
ENetConnection::Event event;
ENetConnection::EventType ret = hosts[0]->service(0, event);
- if (ret == ENetConnection::EVENT_ERROR) {
- return;
- }
do {
- if (_parse_server_event(ret, event)) {
- return;
+ if (ret == ENetConnection::EVENT_CONNECT) {
+ if (is_refusing_new_connections()) {
+ event.peer->reset();
+ continue;
+ }
+ // Client joined with invalid ID, probably trying to exploit us.
+ if (event.data < 2 || peers.has((int)event.data)) {
+ event.peer->reset();
+ continue;
+ }
+ int id = event.data;
+ event.peer->set_meta(SNAME("_net_id"), id);
+ peers[id] = event.peer;
+ emit_signal(SNAME("peer_connected"), id);
+ } else if (ret == ENetConnection::EVENT_DISCONNECT) {
+ int id = event.peer->get_meta(SNAME("_net_id"));
+ if (!peers.has(id)) {
+ // Never fully connected.
+ continue;
+ }
+ emit_signal(SNAME("peer_disconnected"), id);
+ peers.erase(id);
+ } else if (ret == ENetConnection::EVENT_RECEIVE) {
+ int32_t source = event.peer->get_meta(SNAME("_net_id"));
+ _store_packet(source, event);
+ } else if (ret != ENetConnection::EVENT_NONE) {
+ close(); // Error
}
- } while (hosts[0]->check_events(ret, event) > 0);
+ } while (hosts.has(0) && hosts[0]->check_events(ret, event) > 0);
} break;
case MODE_MESH: {
- for (const KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
- if (!(E.value->is_active())) {
- emit_signal(SNAME("peer_disconnected"), E.key);
- peers.erase(E.key);
- if (hosts.has(E.key)) {
- hosts.erase(E.key);
- }
- }
- }
+ HashSet<int> to_drop;
for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
ENetConnection::Event event;
ENetConnection::EventType ret = E.value->service(0, event);
- if (ret == ENetConnection::EVENT_ERROR) {
- if (peers.has(E.key)) {
- emit_signal(SNAME("peer_disconnected"), E.key);
- peers.erase(E.key);
- }
- hosts.erase(E.key);
- continue;
- }
do {
- if (_parse_mesh_event(ret, event, E.key)) {
+ if (ret == ENetConnection::EVENT_CONNECT) {
+ event.peer->reset();
+ } else if (ret == ENetConnection::EVENT_RECEIVE) {
+ _store_packet(E.key, event);
+ } else if (ret == ENetConnection::EVENT_NONE) {
+ break; // Keep polling the others.
+ } else {
+ to_drop.insert(E.key); // Error or disconnect.
break; // Keep polling the others.
}
} while (E.value->check_events(ret, event) > 0);
}
+ for (const int &P : to_drop) {
+ if (peers.has(P)) {
+ emit_signal(SNAME("peer_disconnected"), P);
+ peers.erase(P);
+ }
+ hosts.erase(P);
+ }
} break;
default:
return;
@@ -322,29 +269,41 @@ bool ENetMultiplayerPeer::is_server_relay_supported() const {
return active_mode == MODE_SERVER || active_mode == MODE_CLIENT;
}
-void ENetMultiplayerPeer::close_connection(uint32_t wait_usec) {
+void ENetMultiplayerPeer::disconnect_peer(int p_peer, bool p_force) {
+ ERR_FAIL_COND(!_is_active() || !peers.has(p_peer));
+ peers[p_peer]->peer_disconnect(0); // Will be removed during next poll.
+ if (active_mode == MODE_CLIENT || active_mode == MODE_SERVER) {
+ hosts[0]->flush();
+ } else {
+ ERR_FAIL_COND(!hosts.has(p_peer));
+ hosts[p_peer]->flush();
+ }
+ if (p_force) {
+ peers.erase(p_peer);
+ if (hosts.has(p_peer)) {
+ hosts.erase(p_peer);
+ }
+ if (active_mode == MODE_CLIENT) {
+ hosts.clear(); // Avoid flushing again.
+ close();
+ }
+ }
+}
+
+void ENetMultiplayerPeer::close() {
if (!_is_active()) {
return;
}
_pop_current_packet();
- bool peers_disconnected = false;
for (KeyValue<int, Ref<ENetPacketPeer>> &E : peers) {
if (E.value.is_valid() && E.value->get_state() == ENetPacketPeer::STATE_CONNECTED) {
- E.value->peer_disconnect_now(unique_id);
- peers_disconnected = true;
+ E.value->peer_disconnect_now(0);
}
}
-
- if (peers_disconnected) {
- for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
- E.value->flush();
- }
-
- if (wait_usec > 0) {
- OS::get_singleton()->delay_usec(wait_usec); // Wait for disconnection packets to send
- }
+ for (KeyValue<int, Ref<ENetConnection>> &E : hosts) {
+ E.value->flush();
}
active_mode = MODE_NONE;
@@ -516,7 +475,6 @@ void ENetMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_client", "address", "port", "channel_count", "in_bandwidth", "out_bandwidth", "local_port"), &ENetMultiplayerPeer::create_client, DEFVAL(0), DEFVAL(0), DEFVAL(0), DEFVAL(0));
ClassDB::bind_method(D_METHOD("create_mesh", "unique_id"), &ENetMultiplayerPeer::create_mesh);
ClassDB::bind_method(D_METHOD("add_mesh_peer", "peer_id", "host"), &ENetMultiplayerPeer::add_mesh_peer);
- ClassDB::bind_method(D_METHOD("close_connection", "wait_usec"), &ENetMultiplayerPeer::close_connection, DEFVAL(100));
ClassDB::bind_method(D_METHOD("set_bind_ip", "ip"), &ENetMultiplayerPeer::set_bind_ip);
ClassDB::bind_method(D_METHOD("get_host"), &ENetMultiplayerPeer::get_host);
@@ -531,7 +489,7 @@ ENetMultiplayerPeer::ENetMultiplayerPeer() {
ENetMultiplayerPeer::~ENetMultiplayerPeer() {
if (_is_active()) {
- close_connection();
+ close();
}
}
diff --git a/modules/enet/enet_multiplayer_peer.h b/modules/enet/enet_multiplayer_peer.h
index 85692e8eee..2665b69669 100644
--- a/modules/enet/enet_multiplayer_peer.h
+++ b/modules/enet/enet_multiplayer_peer.h
@@ -83,9 +83,7 @@ private:
void _store_packet(int32_t p_source, ENetConnection::Event &p_event);
void _pop_current_packet();
- bool _parse_server_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
- bool _parse_client_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event);
- bool _parse_mesh_event(ENetConnection::EventType p_event_type, ENetConnection::Event &p_event, int p_peer_id);
+ void _disconnect_inactive_peers();
void _destroy_unused(ENetPacket *p_packet);
_FORCE_INLINE_ bool _is_active() const { return active_mode != MODE_NONE; }
@@ -102,6 +100,9 @@ public:
virtual int get_packet_channel() const override;
virtual void poll() override;
+ virtual void close() override;
+ virtual void disconnect_peer(int p_peer, bool p_force = false) override;
+
virtual bool is_server() const override;
virtual bool is_server_relay_supported() const override;
@@ -122,10 +123,6 @@ public:
Error create_mesh(int p_id);
Error add_mesh_peer(int p_id, Ref<ENetConnection> p_host);
- void close_connection(uint32_t wait_usec = 100);
-
- void disconnect_peer(int p_peer, bool now = false);
-
void set_bind_ip(const IPAddress &p_ip);
Ref<ENetConnection> get_host() const;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index 1401e4b94b..704dda8045 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -985,21 +985,26 @@ void GDScriptAnalyzer::resolve_class_body(GDScriptParser::ClassNode *p_class) {
if (getter_function == nullptr) {
push_error(vformat(R"(Getter "%s" not found.)", member.variable->getter_pointer->name), member.variable);
-
- } else if (getter_function->parameters.size() != 0 || getter_function->datatype.has_no_type()) {
- push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable);
-
- } else if (!is_type_compatible(member.variable->datatype, getter_function->datatype, true)) {
- push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", getter_function->datatype.to_string(), member.variable->datatype.to_string()), member.variable);
-
} else {
- has_valid_getter = true;
+ GDScriptParser::DataType return_datatype = getter_function->datatype;
+ if (getter_function->return_type != nullptr) {
+ return_datatype = getter_function->return_type->datatype;
+ return_datatype.is_meta_type = false;
+ }
+
+ if (getter_function->parameters.size() != 0 || return_datatype.has_no_type()) {
+ push_error(vformat(R"(Function "%s" cannot be used as getter because of its signature.)", getter_function->identifier->name), member.variable);
+ } else if (!is_type_compatible(member.variable->datatype, return_datatype, true)) {
+ push_error(vformat(R"(Function with return type "%s" cannot be used as getter for a property of type "%s".)", return_datatype.to_string(), member.variable->datatype.to_string()), member.variable);
+ } else {
+ has_valid_getter = true;
#ifdef DEBUG_ENABLED
- if (member.variable->datatype.builtin_type == Variant::INT && getter_function->datatype.builtin_type == Variant::FLOAT) {
- parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION);
- }
+ if (member.variable->datatype.builtin_type == Variant::INT && return_datatype.builtin_type == Variant::FLOAT) {
+ parser->push_warning(member.variable, GDScriptWarning::NARROWING_CONVERSION);
+ }
#endif
+ }
}
}
@@ -1377,7 +1382,7 @@ void GDScriptAnalyzer::resolve_for(GDScriptParser::ForNode *p_for) {
if (all_is_constant) {
switch (args.size()) {
case 1:
- reduced = args[0];
+ reduced = (int32_t)args[0];
break;
case 2:
reduced = Vector2i(args[0], args[1]);
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
index c6645c2c34..809d0d28a9 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.gd
@@ -69,6 +69,10 @@ func test():
value = Transform3D()
print(value == null)
+ # Projection
+ value = Projection()
+ print(value == null)
+
# Color
value = Color()
print(value == null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
index 639f6027b9..27423ab8e7 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-equals-null.out
@@ -33,3 +33,4 @@ false
false
false
false
+false
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
index ee622bf22f..f46afb0f18 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.gd
@@ -69,6 +69,10 @@ func test():
value = Transform3D()
print(value != null)
+ # Projection
+ value = Projection()
+ print(value != null)
+
# Color
value = Color()
print(value != null)
diff --git a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
index d1e332afba..a11c47854a 100644
--- a/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
+++ b/modules/gdscript/tests/scripts/runtime/features/compare-builtin-not-equals-null.out
@@ -33,3 +33,4 @@ true
true
true
true
+true
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd
new file mode 100644
index 0000000000..e24137a20d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.gd
@@ -0,0 +1,60 @@
+func test():
+ # All combinations of 1/2/3 arguments, each being int/float.
+
+ for number in range(5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ for number in range(1, 5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ for number in range(1, 5, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1, 5.2, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2, 2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ for number in range(1.2, 5.2, 2.2):
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_optimized_in_for_has_int_iterator.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd
new file mode 100644
index 0000000000..63c3b84305
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.gd
@@ -0,0 +1,77 @@
+func test():
+ # All combinations of 1/2/3 arguments, each being int/float.
+ # Store result in variable to ensure actual array is created (avoid `for` + `range` optimization).
+
+ var result
+
+ result = range(5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ result = range(1, 5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+
+ result = range(1, 5, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1, 5.2, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2, 2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
+
+ result = range(1.2, 5.2, 2.2)
+ for number in result:
+ if typeof(number) != TYPE_INT:
+ print("Number returned from `range` was not an int!")
diff --git a/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out
new file mode 100644
index 0000000000..d73c5eb7cd
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/range_returns_ints.out
@@ -0,0 +1 @@
+GDTEST_OK
diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp
index cb148463a7..99803ed05d 100644
--- a/modules/gltf/gltf_document.cpp
+++ b/modules/gltf/gltf_document.cpp
@@ -6289,7 +6289,7 @@ GLTFAnimation::Track GLTFDocument::_convert_animation_track(Ref<GLTFState> state
for (int32_t key_i = 0; key_i < key_count; key_i++) {
Vector3 rotation_radian = p_animation->track_get_key_value(p_track_i, key_i);
- p_track.rotation_track.values.write[key_i] = Quaternion(rotation_radian);
+ p_track.rotation_track.values.write[key_i] = Quaternion::from_euler(rotation_radian);
}
} else if (path.contains(":scale")) {
p_track.scale_track.times = times;
diff --git a/modules/mbedtls/stream_peer_mbedtls.cpp b/modules/mbedtls/stream_peer_mbedtls.cpp
index a97c6bd916..26cd3bc3f9 100644
--- a/modules/mbedtls/stream_peer_mbedtls.cpp
+++ b/modules/mbedtls/stream_peer_mbedtls.cpp
@@ -242,7 +242,7 @@ void StreamPeerMbedTLS::poll() {
return;
}
- // We could pass nullptr as second parameter, but some behaviour sanitizers don't seem to like that.
+ // We could pass nullptr as second parameter, but some behavior sanitizers don't seem to like that.
// Passing a 1 byte buffer to workaround it.
uint8_t byte;
int ret = mbedtls_ssl_read(tls_ctx->get_context(), &byte, 0);
diff --git a/modules/mobile_vr/doc_classes/MobileVRInterface.xml b/modules/mobile_vr/doc_classes/MobileVRInterface.xml
index db186079b0..63592042c7 100644
--- a/modules/mobile_vr/doc_classes/MobileVRInterface.xml
+++ b/modules/mobile_vr/doc_classes/MobileVRInterface.xml
@@ -6,7 +6,7 @@
<description>
This is a generic mobile VR implementation where you need to provide details about the phone and HMD used. It does not rely on any existing framework. This is the most basic interface we have. For the best effect, you need a mobile phone with a gyroscope and accelerometer.
Note that even though there is no positional tracking, the camera will assume the headset is at a height of 1.85 meters. You can change this by setting [member eye_height].
- You can initialise this interface as follows:
+ You can initialize this interface as follows:
[codeblock]
var interface = XRServer.find_interface("Native mobile")
if interface and interface.initialize():
diff --git a/modules/mobile_vr/mobile_vr_interface.cpp b/modules/mobile_vr/mobile_vr_interface.cpp
index b14f5f469c..582be98d61 100644
--- a/modules/mobile_vr/mobile_vr_interface.cpp
+++ b/modules/mobile_vr/mobile_vr_interface.cpp
@@ -155,7 +155,7 @@ void MobileVRInterface::set_position_from_sensors() {
last_magnetometer_data = magneto;
if (grav.length() < 0.1) {
- // not ideal but use our accelerometer, this will contain shaky user behaviour
+ // not ideal but use our accelerometer, this will contain shaky user behavior
// maybe look into some math but I'm guessing that if this isn't available, it's because we lack the gyro sensor to actually work out
// what a stable gravity vector is
grav = acc;
diff --git a/modules/mobile_vr/register_types.cpp b/modules/mobile_vr/register_types.cpp
index 4df8af9009..dd35b3d164 100644
--- a/modules/mobile_vr/register_types.cpp
+++ b/modules/mobile_vr/register_types.cpp
@@ -53,7 +53,7 @@ void uninitialize_mobile_vr_module(ModuleInitializationLevel p_level) {
}
if (mobile_vr.is_valid()) {
- // uninitialise our interface if it is initialised
+ // uninitialize our interface if it is initialized
if (mobile_vr->is_initialized()) {
mobile_vr->uninitialize();
}
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
index 9c3bc51c44..bb1d6e1661 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Basis.cs
@@ -4,21 +4,6 @@ using System.Runtime.InteropServices;
namespace Godot
{
/// <summary>
- /// Specifies which order Euler angle rotations should be in.
- /// When composing, the order is the same as the letters. When decomposing,
- /// the order is reversed (ex: YXZ decomposes Z first, then X, and Y last).
- /// </summary>
- public enum EulerOrder
- {
- XYZ,
- XZY,
- YXZ,
- YZX,
- ZXY,
- ZYX
- };
-
- /// <summary>
/// 3×3 matrix used for 3D rotation and scale.
/// Almost always used as an orthogonal basis for a Transform.
///
@@ -270,11 +255,11 @@ namespace Godot
/// </summary>
/// <param name="order">The Euler order to use. By default, use YXZ order (most common).</param>
/// <returns>A <see cref="Vector3"/> representing the basis rotation in Euler angles.</returns>
- public Vector3 GetEuler(EulerOrder order = EulerOrder.YXZ)
+ public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz)
{
switch (order)
{
- case EulerOrder.XYZ:
+ case EulerOrder.Xyz:
{
// Euler angles in XYZ convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -318,7 +303,7 @@ namespace Godot
}
return euler;
}
- case EulerOrder.XZY:
+ case EulerOrder.Xzy:
{
// Euler angles in XZY convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -353,7 +338,7 @@ namespace Godot
}
return euler;
}
- case EulerOrder.YXZ:
+ case EulerOrder.Yxz:
{
// Euler angles in YXZ convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -398,7 +383,7 @@ namespace Godot
return euler;
}
- case EulerOrder.YZX:
+ case EulerOrder.Yzx:
{
// Euler angles in YZX convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -433,7 +418,7 @@ namespace Godot
}
return euler;
}
- case EulerOrder.ZXY:
+ case EulerOrder.Zxy:
{
// Euler angles in ZXY convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -468,7 +453,7 @@ namespace Godot
}
return euler;
}
- case EulerOrder.ZYX:
+ case EulerOrder.Zyx:
{
// Euler angles in ZYX convention.
// See https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix
@@ -998,7 +983,7 @@ namespace Godot
/// </summary>
/// <param name="euler">The Euler angles to use.</param>
/// <param name="order">The order to compose the Euler angles.</param>
- public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.YXZ)
+ public static Basis FromEuler(Vector3 euler, EulerOrder order = EulerOrder.Yxz)
{
real_t c, s;
@@ -1016,17 +1001,17 @@ namespace Godot
switch (order)
{
- case EulerOrder.XYZ:
+ case EulerOrder.Xyz:
return xmat * ymat * zmat;
- case EulerOrder.XZY:
+ case EulerOrder.Xzy:
return xmat * zmat * ymat;
- case EulerOrder.YXZ:
+ case EulerOrder.Yxz:
return ymat * xmat * zmat;
- case EulerOrder.YZX:
+ case EulerOrder.Yzx:
return ymat * zmat * xmat;
- case EulerOrder.ZXY:
+ case EulerOrder.Zxy:
return zmat * xmat * ymat;
- case EulerOrder.ZYX:
+ case EulerOrder.Zyx:
return zmat * ymat * xmat;
default:
throw new ArgumentOutOfRangeException(nameof(order));
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
index d459fe8c96..f01f0be985 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/Quaternion.cs
@@ -312,7 +312,7 @@ namespace Godot
/// the rotation angles in the format (X angle, Y angle, Z angle).
/// </summary>
/// <returns>The Euler angle representation of this quaternion.</returns>
- public Vector3 GetEuler()
+ public Vector3 GetEuler(EulerOrder order = EulerOrder.Yxz)
{
#if DEBUG
if (!IsNormalized())
@@ -321,7 +321,7 @@ namespace Godot
}
#endif
var basis = new Basis(this);
- return basis.GetEuler();
+ return basis.GetEuler(order);
}
/// <summary>
@@ -507,35 +507,6 @@ namespace Godot
}
/// <summary>
- /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
- /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
- /// given in the vector format as (X angle, Y angle, Z angle).
- /// </summary>
- /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
- public Quaternion(Vector3 eulerYXZ)
- {
- real_t halfA1 = eulerYXZ.y * 0.5f;
- real_t halfA2 = eulerYXZ.x * 0.5f;
- real_t halfA3 = eulerYXZ.z * 0.5f;
-
- // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
- // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
- // a3 is the angle of the first rotation, following the notation in this reference.
-
- real_t cosA1 = Mathf.Cos(halfA1);
- real_t sinA1 = Mathf.Sin(halfA1);
- real_t cosA2 = Mathf.Cos(halfA2);
- real_t sinA2 = Mathf.Sin(halfA2);
- real_t cosA3 = Mathf.Cos(halfA3);
- real_t sinA3 = Mathf.Sin(halfA3);
-
- x = (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3);
- y = (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3);
- z = (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3);
- w = (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3);
- }
-
- /// <summary>
/// Constructs a <see cref="Quaternion"/> that will rotate around the given axis
/// by the specified angle. The axis must be a normalized vector.
/// </summary>
@@ -572,6 +543,61 @@ namespace Godot
}
}
+ public Quaternion(Vector3 arcFrom, Vector3 arcTo)
+ {
+ Vector3 c = arcFrom.Cross(arcTo);
+ real_t d = arcFrom.Dot(arcTo);
+
+ if (d < -1.0f + Mathf.Epsilon)
+ {
+ x = 0f;
+ y = 1f;
+ z = 0f;
+ w = 0f;
+ }
+ else
+ {
+ real_t s = Mathf.Sqrt((1.0f + d) * 2.0f);
+ real_t rs = 1.0f / s;
+
+ x = c.x * rs;
+ y = c.y * rs;
+ z = c.z * rs;
+ w = s * 0.5f;
+ }
+ }
+
+ /// <summary>
+ /// Constructs a <see cref="Quaternion"/> that will perform a rotation specified by
+ /// Euler angles (in the YXZ convention: when decomposing, first Z, then X, and Y last),
+ /// given in the vector format as (X angle, Y angle, Z angle).
+ /// </summary>
+ /// <param name="eulerYXZ">Euler angles that the quaternion will be rotated by.</param>
+ public static Quaternion FromEuler(Vector3 eulerYXZ)
+ {
+ real_t halfA1 = eulerYXZ.y * 0.5f;
+ real_t halfA2 = eulerYXZ.x * 0.5f;
+ real_t halfA3 = eulerYXZ.z * 0.5f;
+
+ // R = Y(a1).X(a2).Z(a3) convention for Euler angles.
+ // Conversion to quaternion as listed in https://ntrs.nasa.gov/archive/nasa/casi.ntrs.nasa.gov/19770024290.pdf (page A-6)
+ // a3 is the angle of the first rotation, following the notation in this reference.
+
+ real_t cosA1 = Mathf.Cos(halfA1);
+ real_t sinA1 = Mathf.Sin(halfA1);
+ real_t cosA2 = Mathf.Cos(halfA2);
+ real_t sinA2 = Mathf.Sin(halfA2);
+ real_t cosA3 = Mathf.Cos(halfA3);
+ real_t sinA3 = Mathf.Sin(halfA3);
+
+ return new Quaternion(
+ (sinA1 * cosA2 * sinA3) + (cosA1 * sinA2 * cosA3),
+ (sinA1 * cosA2 * cosA3) - (cosA1 * sinA2 * sinA3),
+ (cosA1 * cosA2 * sinA3) - (sinA1 * sinA2 * cosA3),
+ (sinA1 * sinA2 * sinA3) + (cosA1 * cosA2 * cosA3)
+ );
+ }
+
/// <summary>
/// Composes these two quaternions by multiplying them together.
/// This has the effect of rotating the second quaternion
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs
index 1f37694995..d354509dbf 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Variant.cs
@@ -766,6 +766,58 @@ public partial struct Variant : IDisposable
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSignalInfo(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(byte[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(int[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(long[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(float[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(double[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(string[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(Vector2[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(Vector3[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(Color[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(Godot.Object[] from) =>
+ CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfGodotObject(from));
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(StringName[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(NodePath[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Variant(RID[] from) =>
+ (Variant)from.AsSpan();
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<byte> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedByteArray(from));
@@ -802,10 +854,6 @@ public partial struct Variant : IDisposable
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromPackedColorArray(from));
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static implicit operator Variant(Godot.Object[] from) =>
- CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfGodotObject(from));
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Variant(Span<StringName> from) =>
CreateTakingOwnershipOfDisposableValue(VariantUtils.CreateFromSystemArrayOfStringName(from));
diff --git a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
index c0265c9161..a3ca2d6486 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSpawner.xml
@@ -6,7 +6,6 @@
<description>
Spawnable scenes can be configured in the editor or through code (see [method add_spawnable_scene]).
Also supports custom node spawns through [method spawn], calling [method _spawn_custom] on all peers.
-
Internally, [MultiplayerSpawner] uses [method MultiplayerAPI.object_configuration_add] to notify spawns passing the spawned node as the [code]object[/code] and itself as the [code]configuration[/code], and [method MultiplayerAPI.object_configuration_remove] to notify despawns in a similar way.
</description>
<tutorials>
@@ -17,8 +16,7 @@
<param index="0" name="data" type="Variant" />
<description>
Method called on all peers when a custom spawn was requested by the authority using [method spawn]. Should return a [Node] that is not in the scene tree.
-
- [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with `add_child`. This is done automatically.
+ [b]Note:[/b] Spawned nodes should [b]not[/b] be added to the scene with [method Node.add_child]. This is done automatically.
</description>
</method>
<method name="add_spawnable_scene">
@@ -52,7 +50,6 @@
<param index="0" name="data" type="Variant" default="null" />
<description>
Requests a custom spawn, with [code]data[/code] passed to [method _spawn_custom] on all peers. Returns the locally spawned node instance already inside the scene tree, and added as a child of the node pointed by [member spawn_path].
-
[b]Note:[/b] Spawnable scenes are spawned automatically. [method spawn] is only needed for custom spawns.
</description>
</method>
@@ -60,7 +57,6 @@
<members>
<member name="spawn_limit" type="int" setter="set_spawn_limit" getter="get_spawn_limit" default="0">
Maximum nodes that is allowed to be spawned by this spawner. Includes both spawnable scenes and custom spawns.
-
When set to [code]0[/code] (the default), there is no limit.
</member>
<member name="spawn_path" type="NodePath" setter="set_spawn_path" getter="get_spawn_path" default="NodePath(&quot;&quot;)">
diff --git a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
index 42c190f504..7ed6255a62 100644
--- a/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
+++ b/modules/multiplayer/doc_classes/MultiplayerSynchronizer.xml
@@ -6,9 +6,7 @@
<description>
By default, [MultiplayerSynchronizer] synchronizes configured properties to all peers.
Visibility can be handled directly with [method set_visibility_for] or as-needed with [method add_visibility_filter] and [method update_visibility].
-
[MultiplayerSpawner]s will handle nodes according to visibility of synchronizers as long as the node at [member root_path] was spawned by one.
-
Internally, [MultiplayerSynchronizer] uses [method MultiplayerAPI.object_configuration_add] to notify synchronization start passing the [Node] at [member root_path] as the [code]object[/code] and itself as the [code]configuration[/code], and uses [method MultiplayerAPI.object_configuration_remove] to notify synchronization end in a similar way.
</description>
<tutorials>
@@ -19,7 +17,6 @@
<param index="0" name="filter" type="Callable" />
<description>
Adds a peer visibility filter for this synchronizer.
-
[code]filter[/code] should take a peer id [int] and return a [bool].
</description>
</method>
diff --git a/modules/multiplayer/doc_classes/SceneMultiplayer.xml b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
index a7b89f58ac..e4e2b4f631 100644
--- a/modules/multiplayer/doc_classes/SceneMultiplayer.xml
+++ b/modules/multiplayer/doc_classes/SceneMultiplayer.xml
@@ -19,6 +19,35 @@
Clears the current SceneMultiplayer network state (you shouldn't call this unless you know what you are doing).
</description>
</method>
+ <method name="complete_auth">
+ <return type="int" enum="Error" />
+ <param index="0" name="id" type="int" />
+ <description>
+ Mark the authentication step as completed for the remote peer identified by [param id]. The [signal MultiplayerAPI.peer_connected] signal will be emitted for this peer once the remote side also completes the authentication. No further authentication messages are expected to be received from this peer.
+ If a peer disconnects before completing authentication, either due to a network issue, the [member auth_timeout] expiring, or manually calling [method disconnect_peer], the [signal peer_authentication_failed] signal will be emitted instead of [signal MultiplayerAPI.peer_disconnected].
+ </description>
+ </method>
+ <method name="disconnect_peer">
+ <return type="void" />
+ <param index="0" name="id" type="int" />
+ <description>
+ Disconnects the peer identified by [param id], removing it from the list of connected peers, and closing the underlying connection with it.
+ </description>
+ </method>
+ <method name="get_authenticating_peers">
+ <return type="PackedInt32Array" />
+ <description>
+ Returns the IDs of the peers currently trying to authenticate with this [MultiplayerAPI].
+ </description>
+ </method>
+ <method name="send_auth">
+ <return type="int" enum="Error" />
+ <param index="0" name="id" type="int" />
+ <param index="1" name="data" type="PackedByteArray" />
+ <description>
+ Sends the specified [param data] to the remote peer identified by [param id] as part of an authentication message. This can be used to authenticate peers, and control when [signal MultiplayerAPI.peer_connected] is emitted (and the remote peer accepted as one of the connected peers).
+ </description>
+ </method>
<method name="send_bytes">
<return type="int" enum="Error" />
<param index="0" name="bytes" type="PackedByteArray" />
@@ -35,6 +64,12 @@
If [code]true[/code], the MultiplayerAPI will allow encoding and decoding of object during RPCs.
[b]Warning:[/b] Deserialized objects can contain code which gets executed. Do not use this option if the serialized object comes from untrusted sources to avoid potential security threat such as remote code execution.
</member>
+ <member name="auth_callback" type="Callable" setter="set_auth_callback" getter="get_auth_callback">
+ The callback to execute when when receiving authentication data sent via [method send_auth]. If the [Callable] is empty (default), peers will be automatically accepted as soon as they connect.
+ </member>
+ <member name="auth_timeout" type="float" setter="set_auth_timeout" getter="get_auth_timeout" default="3.0">
+ If set to a value greater than [code]0.0[/code], the maximum amount of time peers can stay in the authenticating state, after which the authentication will automatically fail. See the [signal peer_authenticating] and [signal peer_authentication_failed] signals.
+ </member>
<member name="refuse_new_connections" type="bool" setter="set_refuse_new_connections" getter="is_refusing_new_connections" default="false">
If [code]true[/code], the MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] refuses new incoming connections.
</member>
@@ -48,6 +83,18 @@
</member>
</members>
<signals>
+ <signal name="peer_authenticating">
+ <param index="0" name="id" type="int" />
+ <description>
+ Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] connects to a new peer and a valid [member auth_callback] is set. In this case, the [signal MultiplayerAPI.peer_connected] will not be emitted until [method complete_auth] is called with given peer [param id]. While in this state, the peer will not be included in the list returned by [method MultiplayerAPI.get_peers] (but in the one returned by [method get_authenticating_peers]), and only authentication data will be sent or received. See [method send_auth] for sending authentication data.
+ </description>
+ </signal>
+ <signal name="peer_authentication_failed">
+ <param index="0" name="id" type="int" />
+ <description>
+ Emitted when this MultiplayerAPI's [member MultiplayerAPI.multiplayer_peer] disconnects from a peer for which authentication had not yet completed. See [signal peer_authenticating].
+ </description>
+ </signal>
<signal name="peer_packet">
<param index="0" name="id" type="int" />
<param index="1" name="packet" type="PackedByteArray" />
diff --git a/modules/multiplayer/editor/replication_editor_plugin.cpp b/modules/multiplayer/editor/replication_editor_plugin.cpp
index f045018f25..aee5f5b483 100644
--- a/modules/multiplayer/editor/replication_editor_plugin.cpp
+++ b/modules/multiplayer/editor/replication_editor_plugin.cpp
@@ -355,7 +355,7 @@ void ReplicationEditor::_tree_item_edited() {
int column = tree->get_edited_column();
ERR_FAIL_COND(column < 1 || column > 2);
const NodePath prop = ti->get_metadata(0);
- Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo();
+ Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
bool value = ti->is_checked(column);
String method;
if (column == 1) {
@@ -395,7 +395,7 @@ void ReplicationEditor::_dialog_closed(bool p_confirmed) {
int idx = config->property_get_index(prop);
bool spawn = config->property_get_spawn(prop);
bool sync = config->property_get_sync(prop);
- Ref<EditorUndoRedoManager> undo_redo = EditorNode::get_undo_redo();
+ Ref<EditorUndoRedoManager> &undo_redo = EditorNode::get_undo_redo();
undo_redo->create_action(TTR("Remove Property"));
undo_redo->add_do_method(config.ptr(), "remove_property", prop);
undo_redo->add_undo_method(config.ptr(), "add_property", prop, idx);
diff --git a/modules/multiplayer/scene_multiplayer.cpp b/modules/multiplayer/scene_multiplayer.cpp
index f94f8ef658..db7c5037cd 100644
--- a/modules/multiplayer/scene_multiplayer.cpp
+++ b/modules/multiplayer/scene_multiplayer.cpp
@@ -51,14 +51,32 @@ void SceneMultiplayer::profile_bandwidth(const String &p_inout, int p_size) {
}
#endif
+void SceneMultiplayer::_update_status() {
+ MultiplayerPeer::ConnectionStatus status = multiplayer_peer.is_valid() ? multiplayer_peer->get_connection_status() : MultiplayerPeer::CONNECTION_DISCONNECTED;
+ if (last_connection_status != status) {
+ if (status == MultiplayerPeer::CONNECTION_DISCONNECTED) {
+ if (last_connection_status == MultiplayerPeer::CONNECTION_CONNECTING) {
+ emit_signal(SNAME("connection_failed"));
+ } else {
+ emit_signal(SNAME("server_disconnected"));
+ }
+ clear();
+ }
+ last_connection_status = status;
+ }
+}
+
Error SceneMultiplayer::poll() {
- if (!multiplayer_peer.is_valid() || multiplayer_peer->get_connection_status() == MultiplayerPeer::CONNECTION_DISCONNECTED) {
- return ERR_UNCONFIGURED;
+ _update_status();
+ if (last_connection_status == MultiplayerPeer::CONNECTION_DISCONNECTED) {
+ return OK;
}
multiplayer_peer->poll();
- if (!multiplayer_peer.is_valid()) { // It's possible that polling might have resulted in a disconnection, so check here.
+ _update_status();
+ if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) {
+ // We might be still connecting, or polling might have resulted in a disconnection.
return OK;
}
@@ -73,8 +91,48 @@ Error SceneMultiplayer::poll() {
Error err = multiplayer_peer->get_packet(&packet, len);
ERR_FAIL_COND_V_MSG(err != OK, err, vformat("Error getting packet! %d", err));
+ if (pending_peers.has(sender)) {
+ if (pending_peers[sender].local) {
+ // If the auth is over, admit the peer at the first packet.
+ pending_peers.erase(sender);
+ _admit_peer(sender);
+ } else {
+ ERR_CONTINUE(len < 2 || (packet[0] & CMD_MASK) != NETWORK_COMMAND_SYS || packet[1] != SYS_COMMAND_AUTH);
+ // Auth message.
+ PackedByteArray pba;
+ pba.resize(len - 2);
+ if (pba.size()) {
+ memcpy(pba.ptrw(), &packet[2], len - 2);
+ // User callback
+ const Variant sv = sender;
+ const Variant pbav = pba;
+ const Variant *argv[2] = { &sv, &pbav };
+ Variant ret;
+ Callable::CallError ce;
+ auth_callback.callp(argv, 2, ret, ce);
+ ERR_CONTINUE_MSG(ce.error != Callable::CallError::CALL_OK, "Failed to call authentication callback");
+ } else {
+ // Remote complete notification.
+ pending_peers[sender].remote = true;
+ if (pending_peers[sender].local) {
+ pending_peers.erase(sender);
+ _admit_peer(sender);
+ }
+ }
+ continue; // Auth in progress.
+ }
+ }
+
+ ERR_CONTINUE(!connected_peers.has(sender));
+
if (len && (packet[0] & CMD_MASK) == NETWORK_COMMAND_SYS) {
// Sys messages are processed separately since they might call _process_packet themselves.
+ if (len > 1 && packet[1] == SYS_COMMAND_AUTH) {
+ ERR_CONTINUE(len != 2);
+ // If we are here, we already admitted the peer locally, and this is just a confirmation packet.
+ continue;
+ }
+
_process_sys(sender, packet, len, mode, channel);
} else {
remote_sender_id = sender;
@@ -82,17 +140,42 @@ Error SceneMultiplayer::poll() {
remote_sender_id = 0;
}
- if (!multiplayer_peer.is_valid()) {
- return OK; // It's also possible that a packet or RPC caused a disconnection, so also check here.
+ _update_status();
+ if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // It's possible that processing a packet might have resulted in a disconnection, so check here.
+ return OK;
+ }
+ }
+ if (pending_peers.size() && auth_timeout) {
+ HashSet<int> to_drop;
+ uint64_t time = OS::get_singleton()->get_ticks_msec();
+ for (const KeyValue<int, PendingPeer> &pending : pending_peers) {
+ if (pending.value.time + auth_timeout <= time) {
+ multiplayer_peer->disconnect_peer(pending.key);
+ to_drop.insert(pending.key);
+ }
+ }
+ for (const int &P : to_drop) {
+ // Each signal might trigger a disconnection.
+ pending_peers.erase(P);
+ emit_signal(SNAME("peer_authentication_failed"), P);
}
}
+
+ _update_status();
+ if (last_connection_status != MultiplayerPeer::CONNECTION_CONNECTED) { // Signals might have triggered disconnection.
+ return OK;
+ }
+
replicator->on_network_process();
return OK;
}
void SceneMultiplayer::clear() {
+ last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED;
+ pending_peers.clear();
connected_peers.clear();
packet_cache.clear();
+ replicator->on_reset();
cache->clear();
relay_buffer->clear();
}
@@ -117,9 +200,6 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer)
if (multiplayer_peer.is_valid()) {
multiplayer_peer->disconnect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->disconnect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
- multiplayer_peer->disconnect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
- multiplayer_peer->disconnect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
- multiplayer_peer->disconnect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
clear();
}
@@ -128,11 +208,8 @@ void SceneMultiplayer::set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer)
if (multiplayer_peer.is_valid()) {
multiplayer_peer->connect("peer_connected", callable_mp(this, &SceneMultiplayer::_add_peer));
multiplayer_peer->connect("peer_disconnected", callable_mp(this, &SceneMultiplayer::_del_peer));
- multiplayer_peer->connect("connection_succeeded", callable_mp(this, &SceneMultiplayer::_connected_to_server));
- multiplayer_peer->connect("connection_failed", callable_mp(this, &SceneMultiplayer::_connection_failed));
- multiplayer_peer->connect("server_disconnected", callable_mp(this, &SceneMultiplayer::_server_disconnected));
}
- replicator->on_reset();
+ _update_status();
}
Ref<MultiplayerPeer> SceneMultiplayer::get_multiplayer_peer() {
@@ -193,18 +270,19 @@ Error SceneMultiplayer::send_command(int p_to, const uint8_t *p_packet, int p_pa
const Vector<uint8_t> data = relay_buffer->get_data_array();
return multiplayer_peer->put_packet(data.ptr(), relay_buffer->get_position());
}
- if (p_to < 0) {
+ if (p_to > 0) {
+ ERR_FAIL_COND_V(!connected_peers.has(p_to), ERR_BUG);
+ multiplayer_peer->set_target_peer(p_to);
+ return multiplayer_peer->put_packet(p_packet, p_packet_len);
+ } else {
for (const int &pid : connected_peers) {
- if (pid == -p_to) {
+ if (p_to && pid == -p_to) {
continue;
}
multiplayer_peer->set_target_peer(pid);
multiplayer_peer->put_packet(p_packet, p_packet_len);
}
return OK;
- } else {
- multiplayer_peer->set_target_peer(p_to);
- return multiplayer_peer->put_packet(p_packet, p_packet_len);
}
}
@@ -215,7 +293,7 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
switch (sys_cmd_type) {
case SYS_COMMAND_ADD_PEER: {
ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
- _add_peer(peer);
+ _admit_peer(peer); // Relayed peers are automatically accepted.
} break;
case SYS_COMMAND_DEL_PEER: {
ERR_FAIL_COND(!server_relay || !multiplayer_peer->is_server_relay_supported() || get_unique_id() == 1 || p_from != 1);
@@ -273,6 +351,17 @@ void SceneMultiplayer::_process_sys(int p_from, const uint8_t *p_packet, int p_p
}
void SceneMultiplayer::_add_peer(int p_id) {
+ if (auth_callback.is_valid()) {
+ pending_peers[p_id] = PendingPeer();
+ pending_peers[p_id].time = OS::get_singleton()->get_ticks_msec();
+ emit_signal(SNAME("peer_authenticating"), p_id);
+ return;
+ } else {
+ _admit_peer(p_id);
+ }
+}
+
+void SceneMultiplayer::_admit_peer(int p_id) {
if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
// Notify others of connection, and send connected peers to newly connected one.
uint8_t buf[SYS_CMD_SIZE];
@@ -295,10 +384,21 @@ void SceneMultiplayer::_add_peer(int p_id) {
connected_peers.insert(p_id);
cache->on_peer_change(p_id, true);
replicator->on_peer_change(p_id, true);
+ if (p_id == 1) {
+ emit_signal(SNAME("connected_to_server"));
+ }
emit_signal(SNAME("peer_connected"), p_id);
}
void SceneMultiplayer::_del_peer(int p_id) {
+ if (pending_peers.has(p_id)) {
+ pending_peers.erase(p_id);
+ emit_signal(SNAME("peer_authentication_failed"), p_id);
+ return;
+ } else if (!connected_peers.has(p_id)) {
+ return;
+ }
+
if (server_relay && get_unique_id() == 1 && multiplayer_peer->is_server_relay_supported()) {
// Notify others of disconnection.
uint8_t buf[SYS_CMD_SIZE];
@@ -322,17 +422,14 @@ void SceneMultiplayer::_del_peer(int p_id) {
emit_signal(SNAME("peer_disconnected"), p_id);
}
-void SceneMultiplayer::_connected_to_server() {
- emit_signal(SNAME("connected_to_server"));
-}
-
-void SceneMultiplayer::_connection_failed() {
- emit_signal(SNAME("connection_failed"));
-}
-
-void SceneMultiplayer::_server_disconnected() {
- replicator->on_reset();
- emit_signal(SNAME("server_disconnected"));
+void SceneMultiplayer::disconnect_peer(int p_id) {
+ ERR_FAIL_COND(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED);
+ if (pending_peers.has(p_id)) {
+ pending_peers.erase(p_id);
+ } else if (connected_peers.has(p_id)) {
+ connected_peers.has(p_id);
+ }
+ multiplayer_peer->disconnect_peer(p_id);
}
Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, MultiplayerPeer::TransferMode p_mode, int p_channel) {
@@ -353,6 +450,61 @@ Error SceneMultiplayer::send_bytes(Vector<uint8_t> p_data, int p_to, Multiplayer
return send_command(p_to, packet_cache.ptr(), p_data.size() + 1);
}
+Error SceneMultiplayer::send_auth(int p_to, Vector<uint8_t> p_data) {
+ ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!pending_peers.has(p_to), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(p_data.size() < 1, ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(pending_peers[p_to].local, ERR_FILE_CANT_WRITE, "The authentication session was previously marked as completed, no more authentication data can be sent.");
+ ERR_FAIL_COND_V_MSG(pending_peers[p_to].remote, ERR_FILE_CANT_WRITE, "The remote peer notified that the authentication session was completed, no more authentication data can be sent.");
+
+ if (packet_cache.size() < p_data.size() + 2) {
+ packet_cache.resize(p_data.size() + 2);
+ }
+
+ packet_cache.write[0] = NETWORK_COMMAND_SYS;
+ packet_cache.write[1] = SYS_COMMAND_AUTH;
+ memcpy(&packet_cache.write[2], p_data.ptr(), p_data.size());
+
+ multiplayer_peer->set_target_peer(p_to);
+ multiplayer_peer->set_transfer_channel(0);
+ multiplayer_peer->set_transfer_mode(MultiplayerPeer::TRANSFER_MODE_RELIABLE);
+ return multiplayer_peer->put_packet(packet_cache.ptr(), p_data.size() + 2);
+}
+
+Error SceneMultiplayer::complete_auth(int p_peer) {
+ ERR_FAIL_COND_V(multiplayer_peer.is_null() || multiplayer_peer->get_connection_status() != MultiplayerPeer::CONNECTION_CONNECTED, ERR_UNCONFIGURED);
+ ERR_FAIL_COND_V(!pending_peers.has(p_peer), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V_MSG(pending_peers[p_peer].local, ERR_FILE_CANT_WRITE, "The authentication session was already marked as completed.");
+ pending_peers[p_peer].local = true;
+ // Notify the remote peer that the authentication has completed.
+ uint8_t buf[2] = { NETWORK_COMMAND_SYS, SYS_COMMAND_AUTH };
+ Error err = multiplayer_peer->put_packet(buf, 2);
+ // The remote peer already reported the authentication as completed, so admit the peer.
+ // May generate new packets, so it must happen after sending confirmation.
+ if (pending_peers[p_peer].remote) {
+ pending_peers.erase(p_peer);
+ _admit_peer(p_peer);
+ }
+ return err;
+}
+
+void SceneMultiplayer::set_auth_callback(Callable p_callback) {
+ auth_callback = p_callback;
+}
+
+Callable SceneMultiplayer::get_auth_callback() const {
+ return auth_callback;
+}
+
+void SceneMultiplayer::set_auth_timeout(double p_timeout) {
+ ERR_FAIL_COND_MSG(p_timeout < 0, "Timeout must be greater or equal to 0 (where 0 means no timeout)");
+ auth_timeout = uint64_t(p_timeout * 1000);
+}
+
+double SceneMultiplayer::get_auth_timeout() const {
+ return double(auth_timeout) / 1000.0;
+}
+
void SceneMultiplayer::_process_raw(int p_from, const uint8_t *p_packet, int p_packet_len) {
ERR_FAIL_COND_MSG(p_packet_len < 2, "Invalid packet received. Size too small.");
@@ -392,6 +544,16 @@ Vector<int> SceneMultiplayer::get_peer_ids() {
return ret;
}
+Vector<int> SceneMultiplayer::get_authenticating_peer_ids() {
+ Vector<int> out;
+ out.resize(pending_peers.size());
+ int idx = 0;
+ for (const KeyValue<int, PendingPeer> &E : pending_peers) {
+ out.write[idx++] = E.key;
+ }
+ return out;
+}
+
void SceneMultiplayer::set_allow_object_decoding(bool p_enable) {
allow_object_decoding = p_enable;
}
@@ -453,6 +615,18 @@ void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_root_path", "path"), &SceneMultiplayer::set_root_path);
ClassDB::bind_method(D_METHOD("get_root_path"), &SceneMultiplayer::get_root_path);
ClassDB::bind_method(D_METHOD("clear"), &SceneMultiplayer::clear);
+
+ ClassDB::bind_method(D_METHOD("disconnect_peer", "id"), &SceneMultiplayer::disconnect_peer);
+
+ ClassDB::bind_method(D_METHOD("get_authenticating_peers"), &SceneMultiplayer::get_authenticating_peer_ids);
+ ClassDB::bind_method(D_METHOD("send_auth", "id", "data"), &SceneMultiplayer::send_auth);
+ ClassDB::bind_method(D_METHOD("complete_auth", "id"), &SceneMultiplayer::complete_auth);
+
+ ClassDB::bind_method(D_METHOD("set_auth_callback", "callback"), &SceneMultiplayer::set_auth_callback);
+ ClassDB::bind_method(D_METHOD("get_auth_callback"), &SceneMultiplayer::get_auth_callback);
+ ClassDB::bind_method(D_METHOD("set_auth_timeout", "timeout"), &SceneMultiplayer::set_auth_timeout);
+ ClassDB::bind_method(D_METHOD("get_auth_timeout"), &SceneMultiplayer::get_auth_timeout);
+
ClassDB::bind_method(D_METHOD("set_refuse_new_connections", "refuse"), &SceneMultiplayer::set_refuse_new_connections);
ClassDB::bind_method(D_METHOD("is_refusing_new_connections"), &SceneMultiplayer::is_refusing_new_connections);
ClassDB::bind_method(D_METHOD("set_allow_object_decoding", "enable"), &SceneMultiplayer::set_allow_object_decoding);
@@ -462,12 +636,16 @@ void SceneMultiplayer::_bind_methods() {
ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode", "channel"), &SceneMultiplayer::send_bytes, DEFVAL(MultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(MultiplayerPeer::TRANSFER_MODE_RELIABLE), DEFVAL(0));
ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "root_path"), "set_root_path", "get_root_path");
+ ADD_PROPERTY(PropertyInfo(Variant::CALLABLE, "auth_callback"), "set_auth_callback", "get_auth_callback");
+ ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "auth_timeout", PROPERTY_HINT_RANGE, "0,30,0.1,or_greater,suffix:s"), "set_auth_timeout", "get_auth_timeout");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "allow_object_decoding"), "set_allow_object_decoding", "is_object_decoding_allowed");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "refuse_new_connections"), "set_refuse_new_connections", "is_refusing_new_connections");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "server_relay"), "set_server_relay_enabled", "is_server_relay_enabled");
ADD_PROPERTY_DEFAULT("refuse_new_connections", false);
+ ADD_SIGNAL(MethodInfo("peer_authenticating", PropertyInfo(Variant::INT, "id")));
+ ADD_SIGNAL(MethodInfo("peer_authentication_failed", PropertyInfo(Variant::INT, "id")));
ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::PACKED_BYTE_ARRAY, "packet")));
}
diff --git a/modules/multiplayer/scene_multiplayer.h b/modules/multiplayer/scene_multiplayer.h
index 06d7b2c7a7..b0ecc48f8c 100644
--- a/modules/multiplayer/scene_multiplayer.h
+++ b/modules/multiplayer/scene_multiplayer.h
@@ -53,6 +53,7 @@ public:
};
enum SysCommands {
+ SYS_COMMAND_AUTH,
SYS_COMMAND_ADD_PEER,
SYS_COMMAND_DEL_PEER,
SYS_COMMAND_RELAY,
@@ -76,7 +77,17 @@ public:
};
private:
+ struct PendingPeer {
+ bool local = false;
+ bool remote = false;
+ uint64_t time = 0;
+ };
+
Ref<MultiplayerPeer> multiplayer_peer;
+ MultiplayerPeer::ConnectionStatus last_connection_status = MultiplayerPeer::CONNECTION_DISCONNECTED;
+ HashMap<int, PendingPeer> pending_peers; // true if locally finalized.
+ Callable auth_callback;
+ uint64_t auth_timeout = 3000;
HashSet<int> connected_peers;
int remote_sender_id = 0;
int remote_sender_override = 0;
@@ -100,10 +111,9 @@ protected:
void _process_sys(int p_from, const uint8_t *p_packet, int p_packet_len, MultiplayerPeer::TransferMode p_mode, int p_channel);
void _add_peer(int p_id);
+ void _admit_peer(int p_id);
void _del_peer(int p_id);
- void _connected_to_server();
- void _connection_failed();
- void _server_disconnected();
+ void _update_status();
public:
virtual void set_multiplayer_peer(const Ref<MultiplayerPeer> &p_peer) override;
@@ -125,6 +135,16 @@ public:
void set_root_path(const NodePath &p_path);
NodePath get_root_path() const;
+ void disconnect_peer(int p_id);
+
+ Error send_auth(int p_to, Vector<uint8_t> p_bytes);
+ Error complete_auth(int p_peer);
+ void set_auth_callback(Callable p_callback);
+ Callable get_auth_callback() const;
+ void set_auth_timeout(double p_timeout);
+ double get_auth_timeout() const;
+ Vector<int> get_authenticating_peer_ids();
+
Error send_command(int p_to, const uint8_t *p_packet, int p_packet_len); // Used internally to relay packets when needed.
Error send_bytes(Vector<uint8_t> p_data, int p_to = MultiplayerPeer::TARGET_PEER_BROADCAST, MultiplayerPeer::TransferMode p_mode = MultiplayerPeer::TRANSFER_MODE_RELIABLE, int p_channel = 0);
String get_rpc_md5(const Object *p_obj);
diff --git a/modules/multiplayer/scene_replication_interface.cpp b/modules/multiplayer/scene_replication_interface.cpp
index 659ce7316a..f1bab7327a 100644
--- a/modules/multiplayer/scene_replication_interface.cpp
+++ b/modules/multiplayer/scene_replication_interface.cpp
@@ -325,7 +325,7 @@ Error SceneReplicationInterface::_update_spawn_visibility(int p_peer, const Obje
// Check visibility for each peers.
for (const KeyValue<int, PeerInfo> &E : peers_info) {
if (is_visible) {
- // This is fast, since the the object is visibile to everyone, we don't need to check each peer.
+ // This is fast, since the the object is visible to everyone, we don't need to check each peer.
if (E.value.spawn_nodes.has(p_oid)) {
// Already spawned.
continue;
diff --git a/modules/multiplayer/scene_replication_interface.h b/modules/multiplayer/scene_replication_interface.h
index ee454f604e..c8bd96eb87 100644
--- a/modules/multiplayer/scene_replication_interface.h
+++ b/modules/multiplayer/scene_replication_interface.h
@@ -75,7 +75,7 @@ private:
HashSet<ObjectID> spawned_nodes;
HashSet<ObjectID> sync_nodes;
- // Pending spawn informations.
+ // Pending spawn information.
ObjectID pending_spawn;
int pending_spawn_remote = 0;
const uint8_t *pending_buffer = nullptr;
diff --git a/modules/openxr/doc_classes/OpenXRAction.xml b/modules/openxr/doc_classes/OpenXRAction.xml
index d1a2ce2d2e..a3a45ebb4c 100644
--- a/modules/openxr/doc_classes/OpenXRAction.xml
+++ b/modules/openxr/doc_classes/OpenXRAction.xml
@@ -6,7 +6,7 @@
<description>
This resource defines an OpenXR action. Actions can be used both for inputs (buttons/joystick/trigger/etc) and outputs (haptics).
OpenXR performs automatic conversion between action type and input type whenever possible. An analogue trigger bound to a boolean action will thus return [code]false[/code] if the trigger is depressed and [code]true[/code] if pressed fully.
- Actions are not directly bound to specific devices, instead OpenXR recognises a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths.
+ Actions are not directly bound to specific devices, instead OpenXR recognizes a limited number of top level paths that identify devices by usage. We can restrict which devices an action can be bound to by these top level paths. For instance an action that should only be used for hand held controllers can have the top level paths "/user/hand/left" and "/user/hand/right" associated with them. See the [url=https://www.khronos.org/registry/OpenXR/specs/1.0/html/xrspec.html#semantic-path-reserved]reserved path section in the OpenXR specification[/url] for more info on the top level paths.
Note that the name of the resource is used to register the action with.
</description>
<tutorials>
@@ -16,7 +16,7 @@
The type of action.
</member>
<member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default="&quot;&quot;">
- The localised description of this action.
+ The localized description of this action.
</member>
<member name="toplevel_paths" type="PackedStringArray" setter="set_toplevel_paths" getter="get_toplevel_paths" default="PackedStringArray()">
A collections of toplevel paths to which this action can be bound.
diff --git a/modules/openxr/doc_classes/OpenXRActionSet.xml b/modules/openxr/doc_classes/OpenXRActionSet.xml
index db3259ec07..39e518750a 100644
--- a/modules/openxr/doc_classes/OpenXRActionSet.xml
+++ b/modules/openxr/doc_classes/OpenXRActionSet.xml
@@ -36,7 +36,7 @@
Collection of actions for this action set.
</member>
<member name="localized_name" type="String" setter="set_localized_name" getter="get_localized_name" default="&quot;&quot;">
- The localised name of this action set.
+ The localized name of this action set.
</member>
<member name="priority" type="int" setter="set_priority" getter="get_priority" default="0">
The priority for this action set.
diff --git a/modules/openxr/doc_classes/OpenXRInterface.xml b/modules/openxr/doc_classes/OpenXRInterface.xml
index f089fd066e..7251a4a9bd 100644
--- a/modules/openxr/doc_classes/OpenXRInterface.xml
+++ b/modules/openxr/doc_classes/OpenXRInterface.xml
@@ -5,7 +5,7 @@
</brief_description>
<description>
The OpenXR interface allows Godot to interact with OpenXR runtimes and make it possible to create XR experiences and games.
- Due to the needs of OpenXR this interface works slightly different than other plugin based XR interfaces. It needs to be initialised when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset.
+ Due to the needs of OpenXR this interface works slightly different than other plugin based XR interfaces. It needs to be initialized when Godot starts. You need to enable OpenXR, settings for this can be found in your games project settings under the XR heading. You do need to mark a viewport for use with XR in order for Godot to know which render result should be output to the headset.
</description>
<tutorials>
<link title="Setting up XR">$DOCS_URL/tutorials/xr/setting_up_xr.html</link>
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
index 4b30965ce5..85e2ee4903 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.cpp
@@ -102,7 +102,7 @@ void OpenXRHandTrackingExtension::on_state_ready() {
// Setup our hands and reset data
for (int i = 0; i < MAX_OPENXR_TRACKED_HANDS; i++) {
// we'll do this later
- hand_trackers[i].is_initialised = false;
+ hand_trackers[i].is_initialized = false;
hand_trackers[i].hand_tracker = XR_NULL_HANDLE;
hand_trackers[i].aimState.aimPose = { { 0.0, 0.0, 0.0, 0.0 }, { 0.0, 0.0, 0.0 } };
@@ -144,7 +144,7 @@ void OpenXRHandTrackingExtension::on_process() {
if (XR_FAILED(result)) {
// not successful? then we do nothing.
print_line("OpenXR: Failed to obtain hand tracking information [", openxr_api->get_error_string(result), "]");
- hand_trackers[i].is_initialised = false;
+ hand_trackers[i].is_initialized = false;
} else {
void *next_pointer = nullptr;
if (hand_tracking_aim_state_ext) {
@@ -172,11 +172,11 @@ void OpenXRHandTrackingExtension::on_process() {
hand_trackers[i].locations.jointCount = XR_HAND_JOINT_COUNT_EXT;
hand_trackers[i].locations.jointLocations = hand_trackers[i].joint_locations;
- hand_trackers[i].is_initialised = true;
+ hand_trackers[i].is_initialized = true;
}
}
- if (hand_trackers[i].is_initialised) {
+ if (hand_trackers[i].is_initialized) {
void *next_pointer = nullptr;
XrHandJointsMotionRangeInfoEXT motionRangeInfo;
@@ -240,7 +240,7 @@ void OpenXRHandTrackingExtension::cleanup_hand_tracking() {
if (hand_trackers[i].hand_tracker != XR_NULL_HANDLE) {
xrDestroyHandTrackerEXT(hand_trackers[i].hand_tracker);
- hand_trackers[i].is_initialised = false;
+ hand_trackers[i].is_initialized = false;
hand_trackers[i].hand_tracker = XR_NULL_HANDLE;
}
}
diff --git a/modules/openxr/extensions/openxr_hand_tracking_extension.h b/modules/openxr/extensions/openxr_hand_tracking_extension.h
index f8c26339b0..0eca80bcfb 100644
--- a/modules/openxr/extensions/openxr_hand_tracking_extension.h
+++ b/modules/openxr/extensions/openxr_hand_tracking_extension.h
@@ -40,7 +40,7 @@
class OpenXRHandTrackingExtension : public OpenXRExtensionWrapper {
public:
struct HandTracker {
- bool is_initialised = false;
+ bool is_initialized = false;
XrHandJointsMotionRangeEXT motion_range = XR_HAND_JOINTS_MOTION_RANGE_UNOBSTRUCTED_EXT;
XrHandTrackerEXT hand_tracker = XR_NULL_HANDLE;
diff --git a/modules/openxr/openxr_interface.cpp b/modules/openxr/openxr_interface.cpp
index bdf437b0b7..be6b7e4411 100644
--- a/modules/openxr/openxr_interface.cpp
+++ b/modules/openxr/openxr_interface.cpp
@@ -96,7 +96,7 @@ void OpenXRInterface::_load_action_map() {
// This may seem a bit duplicitous to a little bit of background info here.
// OpenXRActionMap (with all its sub resource classes) is a class that allows us to configure and store an action map in.
- // This gives the user the ability to edit the action map in a UI and customise the actions.
+ // This gives the user the ability to edit the action map in a UI and customize the actions.
// OpenXR however requires us to submit an action map and it takes over from that point and we can no longer change it.
// This system does that push and we store the info needed to then work with this action map going forward.
@@ -166,7 +166,7 @@ void OpenXRInterface::_load_action_map() {
}
}
- // Only add our action if we have atleast one valid toplevel path
+ // Only add our action if we have at least one valid toplevel path
if (trackers_for_action.size() > 0) {
Action *action = create_action(action_set, xr_action->get_name(), xr_action->get_localized_name(), xr_action->get_action_type(), trackers_for_action);
if (action) {
@@ -355,7 +355,7 @@ OpenXRInterface::Tracker *OpenXRInterface::find_tracker(const String &p_tracker_
Ref<XRPositionalTracker> positional_tracker;
positional_tracker.instantiate();
- // We have standardised some names to make things nicer to the user so lets recognise the toplevel paths related to these.
+ // We have standardized some names to make things nicer to the user so lets recognize the toplevel paths related to these.
if (p_tracker_name == "/user/hand/left") {
positional_tracker->set_tracker_type(XRServer::TRACKER_CONTROLLER);
positional_tracker->set_tracker_name("left_hand");
diff --git a/modules/openxr/scene/openxr_hand.cpp b/modules/openxr/scene/openxr_hand.cpp
index 2ae13a1026..588b818148 100644
--- a/modules/openxr/scene/openxr_hand.cpp
+++ b/modules/openxr/scene/openxr_hand.cpp
@@ -206,7 +206,7 @@ void OpenXRHand::_update_skeleton() {
const OpenXRHandTrackingExtension::HandTracker *hand_tracker = hand_tracking_ext->get_hand_tracker(hand);
const float ws = XRServer::get_singleton()->get_world_scale();
- if (hand_tracker->is_initialised && hand_tracker->locations.isActive) {
+ if (hand_tracker->is_initialized && hand_tracker->locations.isActive) {
for (int i = 0; i < XR_HAND_JOINT_COUNT_EXT; i++) {
confidences[i] = XRPose::XR_TRACKING_CONFIDENCE_NONE;
quaternions[i] = Quaternion();
diff --git a/modules/regex/doc_classes/RegEx.xml b/modules/regex/doc_classes/RegEx.xml
index 2abfc93722..02260c837e 100644
--- a/modules/regex/doc_classes/RegEx.xml
+++ b/modules/regex/doc_classes/RegEx.xml
@@ -4,7 +4,7 @@
Class for searching text for patterns using regular expressions.
</brief_description>
<description>
- A regular expression (or regex) is a compact language that can be used to recognise strings that follow a specific pattern, such as URLs, email addresses, complete sentences, etc. For example, a regex of [code]ab[0-9][/code] would find any string that is [code]ab[/code] followed by any number from [code]0[/code] to [code]9[/code]. For a more in-depth look, you can easily find various tutorials and detailed explanations on the Internet.
+ A regular expression (or regex) is a compact language that can be used to recognize strings that follow a specific pattern, such as URLs, email addresses, complete sentences, etc. For example, a regex of [code]ab[0-9][/code] would find any string that is [code]ab[/code] followed by any number from [code]0[/code] to [code]9[/code]. For a more in-depth look, you can easily find various tutorials and detailed explanations on the Internet.
To begin, the RegEx object needs to be compiled with the search pattern using [method compile] before it can be used.
[codeblock]
var regex = RegEx.new()
@@ -99,7 +99,8 @@
<param index="1" name="offset" type="int" default="0" />
<param index="2" name="end" type="int" default="-1" />
<description>
- Searches the text for the compiled pattern. Returns a [RegExMatch] container of the first matching result if found, otherwise [code]null[/code]. The region to search within can be specified without modifying where the start and end anchor would be.
+ Searches the text for the compiled pattern. Returns a [RegExMatch] container of the first matching result if found, otherwise [code]null[/code].
+ The region to search within can be specified with [param offset] and [param end]. This is useful when searching for another match in the same [param subject] by calling this method again after a previous success. Note that setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [param offset], and the character before [param offset] will be checked for the word boundary [code]\b[/code].
</description>
</method>
<method name="search_all" qualifiers="const">
@@ -108,7 +109,8 @@
<param index="1" name="offset" type="int" default="0" />
<param index="2" name="end" type="int" default="-1" />
<description>
- Searches the text for the compiled pattern. Returns an array of [RegExMatch] containers for each non-overlapping result. If no results were found, an empty array is returned instead. The region to search within can be specified without modifying where the start and end anchor would be.
+ Searches the text for the compiled pattern. Returns an array of [RegExMatch] containers for each non-overlapping result. If no results were found, an empty array is returned instead.
+ The region to search within can be specified with [param offset] and [param end]. This is useful when searching for another match in the same [param subject] by calling this method again after a previous success. Note that setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [param offset], and the character before [param offset] will be checked for the word boundary [code]\b[/code].
</description>
</method>
<method name="sub" qualifiers="const">
@@ -119,7 +121,8 @@
<param index="3" name="offset" type="int" default="0" />
<param index="4" name="end" type="int" default="-1" />
<description>
- Searches the text for the compiled pattern and replaces it with the specified string. Escapes and backreferences such as [code]$1[/code] and [code]$name[/code] are expanded and resolved. By default, only the first instance is replaced, but it can be changed for all instances (global replacement). The region to search within can be specified without modifying where the start and end anchor would be.
+ Searches the text for the compiled pattern and replaces it with the specified string. Escapes and backreferences such as [code]$1[/code] and [code]$name[/code] are expanded and resolved. By default, only the first instance is replaced, but it can be changed for all instances (global replacement).
+ The region to search within can be specified with [param offset] and [param end]. This is useful when searching for another match in the same [param subject] by calling this method again after a previous success. Note that setting these parameters differs from passing over a shortened string. For example, the start anchor [code]^[/code] is not affected by [param offset], and the character before [param offset] will be checked for the word boundary [code]\b[/code].
</description>
</method>
</methods>
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index f310f46af9..155da3c67b 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -5072,6 +5072,8 @@ void TextServerAdvanced::_shape_run(ShapedTextDataAdvanced *p_sd, int64_t p_star
p_sd->ascent = MAX(p_sd->ascent, get_hex_code_box_size(fs, gl.index).y);
} else {
gl.advance = get_hex_code_box_size(fs, gl.index).y;
+ gl.y_off = get_hex_code_box_size(fs, gl.index).y;
+ gl.x_off = -Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5);
p_sd->ascent = MAX(p_sd->ascent, Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5));
p_sd->descent = MAX(p_sd->descent, Math::round(get_hex_code_box_size(fs, gl.index).x * 0.5));
}
diff --git a/modules/tga/image_loader_tga.cpp b/modules/tga/image_loader_tga.cpp
index a6fc650414..8adde3c5d3 100644
--- a/modules/tga/image_loader_tga.cpp
+++ b/modules/tga/image_loader_tga.cpp
@@ -284,14 +284,21 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
err = FAILED;
}
+ uint64_t color_map_size;
if (has_color_map) {
if (tga_header.color_map_length > 256 || (tga_header.color_map_depth != 24) || tga_header.color_map_type != 1) {
err = FAILED;
}
+ color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3);
} else {
if (tga_header.color_map_type) {
err = FAILED;
}
+ color_map_size = 0;
+ }
+
+ if ((src_image_len - f->get_position()) < (tga_header.id_length + color_map_size)) {
+ err = FAILED; // TGA data appears to be truncated (fewer bytes than expected).
}
if (tga_header.image_width <= 0 || tga_header.image_height <= 0) {
@@ -308,7 +315,6 @@ Error ImageLoaderTGA::load_image(Ref<Image> p_image, Ref<FileAccess> f, BitField
Vector<uint8_t> palette;
if (has_color_map) {
- size_t color_map_size = tga_header.color_map_length * (tga_header.color_map_depth >> 3);
err = palette.resize(color_map_size);
if (err == OK) {
uint8_t *palette_w = palette.ptrw();
diff --git a/modules/upnp/doc_classes/UPNP.xml b/modules/upnp/doc_classes/UPNP.xml
index d4054948f6..92e25efbe0 100644
--- a/modules/upnp/doc_classes/UPNP.xml
+++ b/modules/upnp/doc_classes/UPNP.xml
@@ -74,7 +74,7 @@
<param index="3" name="proto" type="String" default="&quot;UDP&quot;" />
<param index="4" name="duration" type="int" default="0" />
<description>
- Adds a mapping to forward the external [code]port[/code] (between 1 and 65535, although recommended to use port 1024 or above) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]TCP[/code] or [code]UDP[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. Note that forwarding a well-known port (below 1024) with UPnP may fail depending on the device.
+ Adds a mapping to forward the external [code]port[/code] (between 1 and 65535, although recommended to use port 1024 or above) on the default gateway (see [method get_gateway]) to the [code]internal_port[/code] on the local machine for the given protocol [code]proto[/code] (either [code]"TCP"[/code] or [code]"UDP"[/code], with UDP being the default). If a port mapping for the given port and protocol combination already exists on that gateway device, this method tries to overwrite it. If that is not desired, you can retrieve the gateway manually with [method get_gateway] and call [method add_port_mapping] on it, if any. Note that forwarding a well-known port (below 1024) with UPnP may fail depending on the device.
Depending on the gateway device, if a mapping for that port already exists, it will either be updated or it will refuse this command due to that conflict, especially if the existing mapping for that port wasn't created via UPnP or points to a different network address (or device) than this one.
If [code]internal_port[/code] is [code]0[/code] (the default), the same port number is used for both the external and the internal port (the [code]port[/code] value).
The description ([code]desc[/code]) is shown in some routers management UIs and can be used to point out which application added the mapping.
@@ -93,7 +93,7 @@
<param index="0" name="port" type="int" />
<param index="1" name="proto" type="String" default="&quot;UDP&quot;" />
<description>
- Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]TCP[/code] or [code]UDP[/code]. May be refused for mappings pointing to addresses other than this one, for well-known ports (below 1024), or for mappings not added via UPnP. See [enum UPNPResult] for possible return values.
+ Deletes the port mapping for the given port and protocol combination on the default gateway (see [method get_gateway]) if one exists. [code]port[/code] must be a valid port between 1 and 65535, [code]proto[/code] can be either [code]"TCP"[/code] or [code]"UDP"[/code]. May be refused for mappings pointing to addresses other than this one, for well-known ports (below 1024), or for mappings not added via UPnP. See [enum UPNPResult] for possible return values.
</description>
</method>
<method name="discover">
diff --git a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
index 0b42c6ed35..5266a36637 100644
--- a/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
+++ b/modules/webrtc/doc_classes/WebRTCMultiplayerPeer.xml
@@ -6,7 +6,7 @@
<description>
This class constructs a full mesh of [WebRTCPeerConnection] (one connection for each peer) that can be used as a [member MultiplayerAPI.multiplayer_peer].
You can add each [WebRTCPeerConnection] via [method add_peer] or remove them via [method remove_peer]. Peers must be added in [constant WebRTCPeerConnection.STATE_NEW] state to allow it to create the appropriate channels. This class will not create offers nor set descriptions, it will only poll them, and notify connections and disconnections.
- [signal MultiplayerPeer.connection_succeeded] and [signal MultiplayerPeer.server_disconnected] will not be emitted unless the peer is created using [method create_client]. Beside that data transfer works like in a [MultiplayerPeer].
+ When creating the peer via [method create_client] or [method create_server] the [method MultiplayerPeer.is_server_relay_supported] method will return [code]true[/code] enabling peer exchange and packet relaying when supported by the [MultiplayerAPI] implementation.
[b]Note:[/b] When exporting to Android, make sure to enable the [code]INTERNET[/code] permission in the Android export preset before exporting the project or using one-click deploy. Otherwise, network communication of any kind will be blocked by Android.
</description>
<tutorials>
@@ -22,12 +22,6 @@
Three channels will be created for reliable, unreliable, and ordered transport. The value of [code]unreliable_lifetime[/code] will be passed to the [code]maxPacketLifetime[/code] option when creating unreliable and ordered channels (see [method WebRTCPeerConnection.create_data_channel]).
</description>
</method>
- <method name="close">
- <return type="void" />
- <description>
- Close all the add peer connections and channels, freeing all resources.
- </description>
- </method>
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="peer_id" type="int" />
diff --git a/modules/webrtc/webrtc_multiplayer_peer.cpp b/modules/webrtc/webrtc_multiplayer_peer.cpp
index 5ea81d5a1b..38c33a2dbc 100644
--- a/modules/webrtc/webrtc_multiplayer_peer.cpp
+++ b/modules/webrtc/webrtc_multiplayer_peer.cpp
@@ -42,7 +42,6 @@ void WebRTCMultiplayerPeer::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_peer", "peer_id"), &WebRTCMultiplayerPeer::has_peer);
ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebRTCMultiplayerPeer::get_peer);
ClassDB::bind_method(D_METHOD("get_peers"), &WebRTCMultiplayerPeer::get_peers);
- ClassDB::bind_method(D_METHOD("close"), &WebRTCMultiplayerPeer::close);
}
void WebRTCMultiplayerPeer::set_target_peer(int p_peer_id) {
@@ -342,16 +341,23 @@ void WebRTCMultiplayerPeer::remove_peer(int p_peer_id) {
peer->connected = false;
emit_signal(SNAME("peer_disconnected"), p_peer_id);
if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) {
- if (connection_status == CONNECTION_CONNECTING) {
- emit_signal(SNAME("connection_failed"));
- } else {
- emit_signal(SNAME("server_disconnected"));
- }
connection_status = CONNECTION_DISCONNECTED;
}
}
}
+void WebRTCMultiplayerPeer::disconnect_peer(int p_peer_id, bool p_force) {
+ ERR_FAIL_COND(!peer_map.has(p_peer_id));
+ if (p_force) {
+ peer_map.erase(p_peer_id);
+ if (network_mode == MODE_CLIENT && p_peer_id == TARGET_PEER_SERVER) {
+ connection_status = CONNECTION_DISCONNECTED;
+ }
+ } else {
+ peer_map[p_peer_id]->connection->close(); // Will be removed during next poll.
+ }
+}
+
Error WebRTCMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) {
// Peer not available
if (next_packet_peer == 0 || !peer_map.has(next_packet_peer)) {
diff --git a/modules/webrtc/webrtc_multiplayer_peer.h b/modules/webrtc/webrtc_multiplayer_peer.h
index 3f608200fd..0556ef029c 100644
--- a/modules/webrtc/webrtc_multiplayer_peer.h
+++ b/modules/webrtc/webrtc_multiplayer_peer.h
@@ -98,28 +98,29 @@ public:
bool has_peer(int p_peer_id);
Dictionary get_peer(int p_peer_id);
Dictionary get_peers();
- void close();
// PacketPeer
- Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet
- Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
- int get_available_packet_count() const override;
- int get_max_packet_size() const override;
+ virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) override; ///< buffer is GONE after next get_packet
+ virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) override;
+ virtual int get_available_packet_count() const override;
+ virtual int get_max_packet_size() const override;
// MultiplayerPeer
- void set_target_peer(int p_peer_id) override;
+ virtual void set_target_peer(int p_peer_id) override;
- int get_unique_id() const override;
- int get_packet_peer() const override;
- int get_packet_channel() const override;
- TransferMode get_packet_mode() const override;
+ virtual int get_unique_id() const override;
+ virtual int get_packet_peer() const override;
+ virtual int get_packet_channel() const override;
+ virtual TransferMode get_packet_mode() const override;
- bool is_server() const override;
- bool is_server_relay_supported() const override;
+ virtual bool is_server() const override;
+ virtual bool is_server_relay_supported() const override;
- void poll() override;
+ virtual void poll() override;
+ virtual void close() override;
+ virtual void disconnect_peer(int p_peer_id, bool p_force = false) override;
- ConnectionStatus get_connection_status() const override;
+ virtual ConnectionStatus get_connection_status() const override;
};
#endif // WEBRTC_MULTIPLAYER_PEER_H
diff --git a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
index c4481b046b..7e896a0ca3 100644
--- a/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketMultiplayerPeer.xml
@@ -10,13 +10,6 @@
<tutorials>
</tutorials>
<methods>
- <method name="close">
- <return type="void" />
- <description>
- Closes this [MultiplayerPeer], resetting the state to [constant MultiplayerPeer.CONNECTION_CONNECTED].
- [b]Note:[/b] To make sure remote peers receive a clean close prefer disconnecting clients via [method disconnect_peer].
- </description>
- </method>
<method name="create_client">
<return type="int" enum="Error" />
<param index="0" name="url" type="String" />
@@ -37,15 +30,6 @@
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.
</description>
</method>
- <method name="disconnect_peer">
- <return type="void" />
- <param index="0" name="id" type="int" />
- <param index="1" name="code" type="int" default="1000" />
- <param index="2" name="reason" type="String" default="&quot;&quot;" />
- <description>
- Disconnects the peer identified by [code]id[/code] from the server. See [method WebSocketPeer.close] for more information.
- </description>
- </method>
<method name="get_peer" qualifiers="const">
<return type="WebSocketPeer" />
<param index="0" name="peer_id" type="int" />
diff --git a/modules/websocket/doc_classes/WebSocketPeer.xml b/modules/websocket/doc_classes/WebSocketPeer.xml
index fe0aae412e..41d166a0f5 100644
--- a/modules/websocket/doc_classes/WebSocketPeer.xml
+++ b/modules/websocket/doc_classes/WebSocketPeer.xml
@@ -133,7 +133,7 @@
<return type="int" enum="Error" />
<param index="0" name="message" type="String" />
<description>
- Sends the given [param message] using WebSocket text mode. Perfer this method over [method PacketPeer.put_packet] when interacting with third-party text-based API (e.g. when using [JSON] formatted messages).
+ Sends the given [param message] using WebSocket text mode. Prefer this method over [method PacketPeer.put_packet] when interacting with third-party text-based API (e.g. when using [JSON] formatted messages).
</description>
</method>
<method name="set_no_delay">
diff --git a/modules/websocket/websocket_multiplayer_peer.cpp b/modules/websocket/websocket_multiplayer_peer.cpp
index 0202c5bac7..14f9c0ba4d 100644
--- a/modules/websocket/websocket_multiplayer_peer.cpp
+++ b/modules/websocket/websocket_multiplayer_peer.cpp
@@ -75,12 +75,10 @@ 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("close"), &WebSocketMultiplayerPeer::close);
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);
ClassDB::bind_method(D_METHOD("get_peer_port", "id"), &WebSocketMultiplayerPeer::get_peer_port);
- ClassDB::bind_method(D_METHOD("disconnect_peer", "id", "code", "reason"), &WebSocketMultiplayerPeer::disconnect_peer, DEFVAL(1000), DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_supported_protocols"), &WebSocketMultiplayerPeer::get_supported_protocols);
ClassDB::bind_method(D_METHOD("set_supported_protocols", "protocols"), &WebSocketMultiplayerPeer::set_supported_protocols);
@@ -266,9 +264,7 @@ void WebSocketMultiplayerPeer::_poll_client() {
}
} else if (peer->get_ready_state() == WebSocketPeer::STATE_CLOSED) {
if (connection_status == CONNECTION_CONNECTED) {
- emit_signal(SNAME("server_disconnected"));
- } else {
- emit_signal(SNAME("connection_failed"));
+ emit_signal(SNAME("peer_disconnected"), 1);
}
_clear();
return;
@@ -278,7 +274,6 @@ void WebSocketMultiplayerPeer::_poll_client() {
ERR_FAIL_COND(!pending_peers.has(1)); // Bug.
if (OS::get_singleton()->get_ticks_msec() - pending_peers[1].time > handshake_timeout) {
print_verbose(vformat("WebSocket handshake timed out after %.3f seconds.", handshake_timeout * 0.001));
- emit_signal(SNAME("connection_failed"));
_clear();
return;
}
@@ -488,9 +483,15 @@ int WebSocketMultiplayerPeer::get_peer_port(int p_peer_id) const {
return peers_map[p_peer_id]->get_connected_port();
}
-void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, int p_code, String p_reason) {
+void WebSocketMultiplayerPeer::disconnect_peer(int p_peer_id, bool p_force) {
ERR_FAIL_COND(!peers_map.has(p_peer_id));
- peers_map[p_peer_id]->close(p_code, p_reason);
+ peers_map[p_peer_id]->close();
+ if (p_force) {
+ peers_map.erase(p_peer_id);
+ if (!is_server()) {
+ _clear();
+ }
+ }
}
void WebSocketMultiplayerPeer::close() {
diff --git a/modules/websocket/websocket_multiplayer_peer.h b/modules/websocket/websocket_multiplayer_peer.h
index ebe013a7bf..78a58162ab 100644
--- a/modules/websocket/websocket_multiplayer_peer.h
+++ b/modules/websocket/websocket_multiplayer_peer.h
@@ -102,6 +102,9 @@ public:
virtual int get_max_packet_size() const override;
virtual bool is_server() const override;
virtual void poll() override;
+ virtual void close() override;
+ virtual void disconnect_peer(int p_peer_id, bool p_force = false) override;
+
virtual ConnectionStatus get_connection_status() const override;
/* PacketPeer */
@@ -132,8 +135,6 @@ public:
IPAddress get_peer_address(int p_peer_id) const;
int get_peer_port(int p_peer_id) const;
- void disconnect_peer(int p_peer_id, int p_code = 1000, String p_reason = "");
- void close();
void set_max_queued_packets(int p_max_queued_packets);
int get_max_queued_packets() const;
diff --git a/modules/webxr/register_types.cpp b/modules/webxr/register_types.cpp
index f4959c482f..8d30f4bd8c 100644
--- a/modules/webxr/register_types.cpp
+++ b/modules/webxr/register_types.cpp
@@ -57,7 +57,7 @@ void uninitialize_webxr_module(ModuleInitializationLevel p_level) {
#ifdef WEB_ENABLED
if (webxr.is_valid()) {
- // uninitialise our interface if it is initialised
+ // uninitialize our interface if it is initialized
if (webxr->is_initialized()) {
webxr->uninitialize();
}
diff --git a/modules/zip/zip_packer.cpp b/modules/zip/zip_packer.cpp
index c37fc0945e..e62700f191 100644
--- a/modules/zip/zip_packer.cpp
+++ b/modules/zip/zip_packer.cpp
@@ -50,7 +50,6 @@ Error ZIPPacker::close() {
}
Error ZIPPacker::start_file(String p_path) {
- ERR_FAIL_COND_V_MSG(zf != NULL, FAILED, "ZIPPacker is already in use.");
ERR_FAIL_COND_V_MSG(fa.is_null(), FAILED, "ZIPPacker must be opened before use.");
zip_fileinfo zipfi;