aboutsummaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authortpart <tpart120@proton.me>2025-12-18 22:37:32 +0100
committertpart <tpart120@proton.me>2025-12-18 22:37:37 +0100
commit384973975e8d9760d4be6d25c3e8d3dab3758d6f (patch)
tree1dcc77c293db4613b73613104c9f0d0026f3ca67 /client
parent9018c2a56dce9f4552952444ebd3b2cbb98bc4f4 (diff)
downloadhurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar
hurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar.bz2
hurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar.zst
Implement fallback address support in client (Closes #537)
Diffstat (limited to 'client')
-rw-r--r--client/game.gd13
-rw-r--r--client/gui/menus/main/play.gd23
-rw-r--r--client/multiplayer.gd67
-rw-r--r--client/project.godot4
4 files changed, 75 insertions, 32 deletions
diff --git a/client/game.gd b/client/game.gd
index 13eedcda..d57099ed 100644
--- a/client/game.gd
+++ b/client/game.gd
@@ -78,10 +78,9 @@ var spectating_mode: SpectatingMode = SpectatingMode.CENTER
func _ready():
mp.packet.connect(handle_packet)
- mp.connection_closed.connect(func(reason: String):
- get_parent().replace_menu("res://gui/menus/error.tscn", [reason, menu.data])
- )
- mp.connect_to_url(menu.data)
+ mp.connection_closed.connect(func(reason: String): show_error(reason))
+ mp.connect_to_urls(menu.data)
+
text_message.connect(func(m):
text_message_history.push_back(m)
while text_message_history.size() > 64:
@@ -391,7 +390,7 @@ func handle_packet(p):
get_parent().replace_menu("res://gui/menus/game.tscn", p.uri[0])
"disconnect":
var m := MessageParser.new(p.reason, self)
- get_parent().replace_menu("res://gui/menus/error.tscn", [m.result, menu.data])
+ show_error(m.result)
"replay_start":
is_replay = true
"replay_stop":
@@ -404,6 +403,9 @@ func handle_packet(p):
pass # Only implemented in test client
_: push_warning("Unrecognized packet type: %s" % p.type)
+func show_error(message: String):
+ get_parent().replace_menu("res://gui/menus/error.tscn", [message, menu.data])
+
func system_message(s: String):
var message = TextMessage.new()
message.text = s
@@ -509,7 +511,6 @@ func spectate_center():
$Center.position = Vector3(map_center.x, 0.,map_center.y)
$FollowCamera.camera_distance_target = (extents[1] - extents[0]).length() / 2
-
func spectate_free():
var direction := Input.get_vector("left", "right", "forwards", "backwards")
direction = direction.rotated(-follow_camera.angle_target)
diff --git a/client/gui/menus/main/play.gd b/client/gui/menus/main/play.gd
index 4633ee1a..bb2ecf3c 100644
--- a/client/gui/menus/main/play.gd
+++ b/client/gui/menus/main/play.gd
@@ -81,12 +81,11 @@ func update_server_list(lists: Array[Array]):
var server_item: ServerListItem = server_list_item.instantiate()
server_list.add_child(server_item)
- var address: Array[String] = []
- address.append_array(i.address)
+ var urls: Array[String] = []
+ urls.append_array(i.address)
- # TODO: Implement fallback address correctly
- server_item.setup(i.name, roundi(i.players_online), i.version, address)
- server_item.button.pressed.connect(connect_to.bind(i.address[0]))
+ server_item.setup(i.name, roundi(i.players_online), i.version, urls)
+ server_item.button.pressed.connect(connect_to.bind(urls))
# Focus the same server with the same index as the previously focused one
if idx == prev_selected_idx:
server_item.button.grab_focus()
@@ -119,8 +118,9 @@ func _on_connect_pressed():
url = url + ":27032"
connect_uri.text = url
Profile.write("last_server_url", url)
- connect_to(url)
+ connect_to([url])
+# TODO unused code
func _on_quick_connect_pressed():
if OS.has_feature("web"):
connect_to(JavaScriptBridge.eval("""
@@ -129,11 +129,10 @@ func _on_quick_connect_pressed():
: `ws://${window.location.hostname}:27032/`
"""))
else:
- connect_to("wss://hurrycurry.metamuffin.org/")
+ connect_to(["wss://hurrycurry.metamuffin.org/"])
-func connect_to(url: String):
- print("Connecting to %s" % url)
- get_parent().replace_menu("res://gui/menus/game.tscn", url)
+func connect_to(urls: Array[String]):
+ get_parent().replace_menu("res://gui/menus/game.tscn", urls)
func _on_server_control_pressed():
match Server.state:
@@ -148,10 +147,10 @@ func _on_editor_control_pressed():
Service.State.FAILED: Editor.start()
func _on_server_connect_pressed():
- connect_to("ws://%s:%d" % [ServerService.connect_address(), Settings.read("server.bind_port")])
+ connect_to(["ws://%s:%d" % [ServerService.connect_address(), Settings.read("server.bind_port")]])
func _on_editor_connect_pressed():
- connect_to("ws://[::1]:27035/")
+ connect_to(["ws://[::1]:27035/"])
func _process(_delta):
server_control.disabled = false
diff --git a/client/multiplayer.gd b/client/multiplayer.gd
index 5ce5bb93..d5d31322 100644
--- a/client/multiplayer.gd
+++ b/client/multiplayer.gd
@@ -23,25 +23,66 @@ static var VERSION_MAJOR: int = 12
static var VERSION_MINOR: int = 0
var connected := false
-var socket := WebSocketPeer.new()
+var socket: WebSocketPeer
+var keep_alive := Timer.new()
func _ready():
- print("Multiplayer connect")
- socket.inbound_buffer_size = 1024 * 1024 * 4
-
- var keep_alive := Timer.new()
add_child(keep_alive)
keep_alive.wait_time = 1.
keep_alive.timeout.connect(send_keep_alive)
- keep_alive.start()
-func connect_to_url(url):
- socket.connect_to_url(url)
+func connect_to_urls(urls: Array[String]):
+ if urls.is_empty():
+ connection_closed.emit("No connection address available.")
+ return
+
+ var error_info: Dictionary[String, int] = {}
+
+ # Create a WebSocketPeer for each url
+ var peers: Array[WebSocketPeer] = []
+ for url: String in urls:
+ var ws := WebSocketPeer.new()
+ ws.inbound_buffer_size = 1024 * 1024 * 4
+ var err := ws.connect_to_url(url)
+ if err == OK: peers.append(ws)
+ else: error_info[url] = err
+
+ # Now keep polling until one of them is succesful, or we run out of peers.
+ # Peers are removed from the peers array when they fail to connect.
+ var open_peer_found := false
+ while not peers.is_empty() and not open_peer_found:
+ await get_tree().physics_frame
+ for peer: WebSocketPeer in peers:
+ peer.poll()
+ var state := peer.get_ready_state()
+ match state:
+ WebSocketPeer.STATE_CLOSED:
+ print("URL %s failed" % peer.get_requested_url())
+ error_info[peer.get_requested_url()] = peer.get_close_code()
+ peers.erase(peer)
+ WebSocketPeer.STATE_OPEN:
+ # We found a connection that works. Close all others.
+ print("URL %s connected!" % peer.get_requested_url())
+ socket = peer
+ var other_peers := peers.filter(func (p): return p != peer)
+ for p: WebSocketPeer in other_peers:
+ p.close()
+ open_peer_found = true
+ break
+ _: pass
+
+ if not open_peer_found:
+ var err_msg: String = tr("c.error.could_not_connect")
+ for url: String in error_info.keys():
+ err_msg += "\nURL %s failed with code %d" % [url, error_info[url]]
+ connection_closed.emit(err_msg)
+ return
+
connected = true
+ keep_alive.start()
func _notification(what):
- if what == NOTIFICATION_PREDELETE:
- print("Multiplayer disconnect");
+ if what == NOTIFICATION_PREDELETE and socket != null:
socket.close()
connected = false
@@ -52,10 +93,8 @@ func _process(_delta):
while socket.get_available_packet_count():
handle_packet(socket.get_packet())
if state == WebSocketPeer.STATE_CLOSED:
- var code = socket.get_close_code()
- var reason = socket.get_close_reason() if code == socket.STATE_CLOSED else tr("c.error.websocket.unavailable")
- connection_closed.emit(tr("c.error.websocket").format([code, reason, code != -1]))
- self.queue_free()
+ connection_closed.emit("c.error.connection_closed")
+ connected = false
func fix_packet_types(val):
match typeof(val):
diff --git a/client/project.godot b/client/project.godot
index f1827fb9..efc2abc6 100644
--- a/client/project.godot
+++ b/client/project.godot
@@ -284,6 +284,10 @@ toggle_first_person={
pointing/android/enable_pan_and_scale_gestures=true
+[network]
+
+limits/tcp/connect_timeout_seconds=10
+
[physics]
3d/physics_engine="Dummy"