diff options
| author | tpart <tpart120@proton.me> | 2025-12-18 22:37:32 +0100 |
|---|---|---|
| committer | tpart <tpart120@proton.me> | 2025-12-18 22:37:37 +0100 |
| commit | 384973975e8d9760d4be6d25c3e8d3dab3758d6f (patch) | |
| tree | 1dcc77c293db4613b73613104c9f0d0026f3ca67 | |
| parent | 9018c2a56dce9f4552952444ebd3b2cbb98bc4f4 (diff) | |
| download | hurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar hurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar.bz2 hurrycurry-384973975e8d9760d4be6d25c3e8d3dab3758d6f.tar.zst | |
Implement fallback address support in client (Closes #537)
| -rw-r--r-- | client/game.gd | 13 | ||||
| -rw-r--r-- | client/gui/menus/main/play.gd | 23 | ||||
| -rw-r--r-- | client/multiplayer.gd | 67 | ||||
| -rw-r--r-- | client/project.godot | 4 | ||||
| -rw-r--r-- | locale/en.ini | 4 |
5 files changed, 77 insertions, 34 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" diff --git a/locale/en.ini b/locale/en.ini index e33f338a..b44c5f82 100644 --- a/locale/en.ini +++ b/locale/en.ini @@ -50,8 +50,8 @@ c.error.placeholder=This should be the error message. c.error.select_hairstyle=You must select a hairstyle. c.error.server=Error from server: {0} c.error.version_mismatch=Server and client versions do not match. Server: {0}.{1}, Client: {2}.{3}.%nAre you sure the game is up to date? -c.error.websocket.unavailable=unavailable -c.error.websocket=WebSocket closed with code: {0}, reason {1}. Clean: {2} +c.error.could_not_connect=Could not connect to server. +c.error.connection_closed=The server closed the connection. c.error=Error c.legal.using_godot=This game uses Godot Engine, available under the following license: c.map.difficulty.0=Easy |