diff --git a/docker-compose.yml b/docker-compose.yml index 22bbec6..1dcffd8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,15 +4,10 @@ services: context: . container_name: pirate-dice restart: unless-stopped - healthcheck: - # Der Server spricht WebSocket, kein HTTP - also nur schauen, ob der - # Port jemanden zu Hause hat. - test: ["CMD", "bash", "-c", "exec 3<>/dev/tcp/127.0.0.1/8000"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 20s - start_interval: 2s + # Kein Healthcheck: der Server spricht ausschliesslich WebSocket. Eine + # nackte TCP-Verbindung darauf lehnt er ab ("Missing or invalid header + # 'upgrade'") - und dabei ging reproduzierbar ein Paket an einen echten + # Spieler verloren. Traefik braucht den Check nicht. networks: - web # Keine Portfreigabe nötig: alles läuft als WebSocket über Traefik auf 443. diff --git a/scripts/main.gd b/scripts/main.gd index b42e592..ded9d7f 100644 --- a/scripts/main.gd +++ b/scripts/main.gd @@ -51,15 +51,28 @@ func _raum_bauen(code: String, cfg: Dictionary) -> void: raum.name = code raeume.add_child(raum) - var behaelter := SubViewportContainer.new() + var behaelter: Node + var sicht: Node + if Net.dedicated: + # Auf dem Server laufen viele Partien nebeneinander. Jede braucht ihren + # eigenen Physikraum, sonst lägen alle Würfel im selben. + var c := SubViewportContainer.new() + c.stretch = true + c.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) + var v := SubViewport.new() + v.own_world_3d = true + behaelter = c + sicht = v + else: + # Der Client hat nur eine Partie und rendert direkt ins Fenster. Ein + # SubViewport würde hier nur Auflösung und Kantenglättung kosten. + # Die Knotennamen müssen trotzdem gleich heißen - daran hängt das + # Ziel jedes RPC. + behaelter = Node.new() + sicht = Node.new() behaelter.name = "Container" - behaelter.stretch = true - behaelter.set_anchors_and_offsets_preset(Control.PRESET_FULL_RECT) - raum.add_child(behaelter) - - var sicht := SubViewport.new() sicht.name = "Viewport" - sicht.own_world_3d = true + raum.add_child(behaelter) behaelter.add_child(sicht) var game := Node3D.new() @@ -75,5 +88,9 @@ func _raum_abbauen(code: String) -> void: raum.queue_free() +## Nur wenn wirklich eine Partie lief. Sonst würde ein Verbindungsabbruch im +## Menü die Szene neu laden und die Meldung dazu gleich wieder wegwerfen. func _back_to_menu() -> void: + if raeume.get_child_count() == 0: + return get_tree().change_scene_to_file("res://scenes/main.tscn") diff --git a/scripts/net.gd b/scripts/net.gd index 2303f13..dee0b95 100644 --- a/scripts/net.gd +++ b/scripts/net.gd @@ -152,7 +152,8 @@ func _req_create(pname: String) -> void: return var id := multiplayer.get_remote_sender_id() var code := _neuer_code() - raeume[code] = {"lobby": {id: pname.substr(0, 16)}, "owner": id, "in_game": false} + raeume[code] = {"lobby": {id: pname.substr(0, 16)}, "owner": id, "in_game": false, + "bestaetigt": {}} peer_raum[id] = code print("Raum %s aufgemacht (%d offen)." % [code, raeume.size()]) _lobby_senden(code) @@ -259,13 +260,45 @@ func _req_start(ai_count: int) -> void: return raum.in_game = true var cfg := {"peers": order, "names": raum.lobby.duplicate(), "ai": ai_count} - for peer: int in order: - _begin.rpc_id(peer, code, cfg) + _start_zustellen(code, cfg) game_started.emit(code, cfg) +## Ein verlorenes Startpaket lässt den Spieler in der Lobby sitzen, während +## die anderen spielen. Deshalb wird nachgelegt, bis jeder quittiert hat. +func _start_zustellen(code: String, cfg: Dictionary) -> void: + for versuch in 4: + var raum: Dictionary = raeume.get(code, {}) + if raum.is_empty(): + return + var offen: Array = [] + for peer: int in raum.lobby: + if not raum.bestaetigt.has(peer): + offen.append(peer) + if offen.is_empty(): + return + if versuch > 0: + print("Start an %s noch einmal (Versuch %d)." % [str(offen), versuch + 1]) + for peer: int in offen: + _begin.rpc_id(peer, code, cfg) + await get_tree().create_timer(1.5).timeout + + +@rpc("any_peer", "reliable") +func _start_erhalten() -> void: + if not multiplayer.is_server(): + return + var id := multiplayer.get_remote_sender_id() + var raum: Dictionary = raeume.get(peer_raum.get(id, ""), {}) + if not raum.is_empty(): + raum.bestaetigt[id] = true + + @rpc("authority", "reliable") func _begin(code: String, cfg: Dictionary) -> void: + _start_erhalten.rpc_id(1) + if in_game and room_code == code: + return # Nachzügler-Paket, die Partie läuft hier schon in_game = true room_code = code game_started.emit(code, cfg)