update
This commit is contained in:
192
scripts/net.gd
192
scripts/net.gd
@@ -1,19 +1,33 @@
|
||||
extends Node
|
||||
## Netzwerk-Verwaltung: Host/Client via ENet, Lobby und Spielstart.
|
||||
## Netzwerk: Räume liegen auf dem Spielserver, Beitritt läuft über einen Code.
|
||||
##
|
||||
## Der Server ist eine eigene headless Instanz und sitzt nicht mit am Tisch.
|
||||
## Er ist Peer 1 und damit die Autorität. Ein Spieler im Raum ist Eigentümer
|
||||
## und darf die Partie starten.
|
||||
|
||||
signal lobby_changed
|
||||
signal game_started(config: Dictionary)
|
||||
signal connection_failed(reason: String)
|
||||
signal server_closed
|
||||
signal room_opened(code: String)
|
||||
|
||||
const PORT := 8910
|
||||
const MAX_HUMANS := 8
|
||||
const STANDARD_SERVER := "https://pirate-dice.marha.de"
|
||||
## Ohne Spieler schliesst sich ein Raum von selbst, sonst stehen Leichen herum
|
||||
const LEERLAUF := 120.0
|
||||
|
||||
var player_name := "Spieler"
|
||||
## peer_id -> name (enthält auch den Host mit id 1)
|
||||
## peer_id -> Name. Nur Menschen; der Server steht nicht drin.
|
||||
var lobby: Dictionary = {}
|
||||
var is_host := false
|
||||
var owner_id := 0
|
||||
var is_owner := false
|
||||
var room_code := ""
|
||||
var in_game := false
|
||||
## true in der headless Instanz auf dem Server
|
||||
var dedicated := false
|
||||
|
||||
var _leer_seit := 0.0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -24,29 +38,77 @@ func _ready() -> void:
|
||||
multiplayer.server_disconnected.connect(_on_server_disconnected)
|
||||
|
||||
|
||||
func host_game(pname: String, port: int = PORT) -> bool:
|
||||
player_name = pname
|
||||
## Zum Entwickeln auf einen lokalen Matchmaker zeigen lassen:
|
||||
## PIRATE_DICE_SERVER=http://127.0.0.1:8020 godot
|
||||
func server_url() -> String:
|
||||
var eigen := OS.get_environment("PIRATE_DICE_SERVER")
|
||||
return eigen if eigen != "" else STANDARD_SERVER
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Räume
|
||||
|
||||
## Neuen Raum anlegen lassen und gleich betreten.
|
||||
func create_room(pname: String) -> void:
|
||||
var raum := await _api(HTTPClient.METHOD_POST, "/api/raum")
|
||||
if raum.is_empty():
|
||||
connection_failed.emit("Spielserver nicht erreichbar.")
|
||||
return
|
||||
room_code = String(raum.get("code", ""))
|
||||
room_opened.emit(room_code)
|
||||
join_game(String(raum.get("host", "")), pname, int(raum.get("port", PORT)))
|
||||
|
||||
|
||||
func join_room(code: String, pname: String) -> void:
|
||||
code = code.strip_edges().to_upper()
|
||||
if code == "":
|
||||
connection_failed.emit("Kein Code eingegeben.")
|
||||
return
|
||||
var raum := await _api(HTTPClient.METHOD_GET, "/api/raum/" + code)
|
||||
if raum.is_empty():
|
||||
connection_failed.emit("Den Code %s gibt es nicht." % code)
|
||||
return
|
||||
room_code = code
|
||||
join_game(String(raum.get("host", "")), pname, int(raum.get("port", PORT)))
|
||||
|
||||
|
||||
func _api(method: int, pfad: String) -> Dictionary:
|
||||
var req := HTTPRequest.new()
|
||||
add_child(req)
|
||||
req.timeout = 8.0
|
||||
if req.request(server_url() + pfad, PackedStringArray(), method) != OK:
|
||||
req.queue_free()
|
||||
return {}
|
||||
var antwort: Array = await req.request_completed
|
||||
req.queue_free()
|
||||
if int(antwort[1]) != 200:
|
||||
return {}
|
||||
var daten: Variant = JSON.parse_string(
|
||||
(antwort[3] as PackedByteArray).get_string_from_utf8())
|
||||
return daten if daten is Dictionary else {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Verbindung
|
||||
|
||||
## Nur die headless Instanz auf dem Server ruft das auf.
|
||||
func serve(port: int) -> bool:
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_server(port, MAX_HUMANS - 1)
|
||||
if err != OK:
|
||||
connection_failed.emit("Server konnte nicht gestartet werden (Port %d belegt?)" % port)
|
||||
if peer.create_server(port, MAX_HUMANS) != OK:
|
||||
push_error("Port %d ist belegt." % port)
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
is_host = true
|
||||
lobby = {1: pname}
|
||||
lobby_changed.emit()
|
||||
dedicated = true
|
||||
_leer_seit = _jetzt()
|
||||
print("Raum offen auf Port %d." % port)
|
||||
return true
|
||||
|
||||
|
||||
func join_game(ip: String, pname: String, port: int = PORT) -> bool:
|
||||
func join_game(host: String, pname: String, port: int = PORT) -> bool:
|
||||
player_name = pname
|
||||
var peer := ENetMultiplayerPeer.new()
|
||||
var err := peer.create_client(ip, port)
|
||||
if err != OK:
|
||||
connection_failed.emit("Verbindung zu %s:%d fehlgeschlagen" % [ip, port])
|
||||
if peer.create_client(host, port) != OK:
|
||||
connection_failed.emit("Verbindung zu %s:%d fehlgeschlagen." % [host, port])
|
||||
return false
|
||||
multiplayer.multiplayer_peer = peer
|
||||
is_host = false
|
||||
return true
|
||||
|
||||
|
||||
@@ -55,7 +117,9 @@ func leave() -> void:
|
||||
multiplayer.multiplayer_peer.close()
|
||||
multiplayer.multiplayer_peer = null
|
||||
lobby.clear()
|
||||
is_host = false
|
||||
owner_id = 0
|
||||
is_owner = false
|
||||
room_code = ""
|
||||
in_game = false
|
||||
lobby_changed.emit()
|
||||
|
||||
@@ -66,16 +130,36 @@ func is_online() -> bool:
|
||||
and peer.get_connection_status() != MultiplayerPeer.CONNECTION_DISCONNECTED
|
||||
|
||||
|
||||
## Ein leerer Raum kostet sonst dauerhaft Speicher auf dem Server.
|
||||
func _process(_delta: float) -> void:
|
||||
if not dedicated:
|
||||
return
|
||||
if lobby.is_empty():
|
||||
if _jetzt() - _leer_seit > LEERLAUF:
|
||||
print("Niemand mehr im Raum, wird geschlossen.")
|
||||
get_tree().quit()
|
||||
else:
|
||||
_leer_seit = _jetzt()
|
||||
|
||||
|
||||
func _jetzt() -> float:
|
||||
return Time.get_ticks_msec() / 1000.0
|
||||
|
||||
|
||||
func _on_peer_connected(id: int) -> void:
|
||||
if is_host:
|
||||
_push_lobby.rpc_id(id, lobby)
|
||||
if multiplayer.is_server():
|
||||
_push_lobby.rpc_id(id, lobby, owner_id)
|
||||
|
||||
|
||||
func _on_peer_disconnected(id: int) -> void:
|
||||
if is_host:
|
||||
lobby.erase(id)
|
||||
_push_lobby.rpc(lobby)
|
||||
lobby_changed.emit()
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
lobby.erase(id)
|
||||
# Geht der Eigentümer, rückt der nächste nach - sonst startet niemand mehr
|
||||
if id == owner_id:
|
||||
owner_id = 0 if lobby.is_empty() else int(lobby.keys()[0])
|
||||
_push_lobby.rpc(lobby, owner_id)
|
||||
lobby_changed.emit()
|
||||
|
||||
|
||||
func _on_connected() -> void:
|
||||
@@ -84,50 +168,94 @@ func _on_connected() -> void:
|
||||
|
||||
func _on_connect_fail() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
connection_failed.emit("Server nicht erreichbar.")
|
||||
connection_failed.emit("Raum nicht erreichbar.")
|
||||
|
||||
|
||||
func _on_server_disconnected() -> void:
|
||||
multiplayer.multiplayer_peer = null
|
||||
lobby.clear()
|
||||
owner_id = 0
|
||||
is_owner = false
|
||||
in_game = false
|
||||
server_closed.emit()
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _register(pname: String) -> void:
|
||||
if not is_host:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
var id := multiplayer.get_remote_sender_id()
|
||||
if in_game:
|
||||
_abweisen(id, "In dem Raum läuft schon eine Partie.")
|
||||
return
|
||||
if lobby.size() >= MAX_HUMANS:
|
||||
_abweisen(id, "Der Raum ist voll.")
|
||||
return
|
||||
lobby[id] = pname.substr(0, 16)
|
||||
_push_lobby.rpc(lobby)
|
||||
if owner_id == 0:
|
||||
owner_id = id
|
||||
_push_lobby.rpc(lobby, owner_id)
|
||||
lobby_changed.emit()
|
||||
|
||||
|
||||
## Erst die Begründung schicken, dann trennen - andersherum kommt sie nie an.
|
||||
func _abweisen(id: int, grund: String) -> void:
|
||||
_abgewiesen.rpc_id(id, grund)
|
||||
await get_tree().create_timer(0.5).timeout
|
||||
var peer := multiplayer.multiplayer_peer
|
||||
if peer is ENetMultiplayerPeer:
|
||||
peer.disconnect_peer(id)
|
||||
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _push_lobby(data: Dictionary) -> void:
|
||||
func _abgewiesen(grund: String) -> void:
|
||||
connection_failed.emit(grund)
|
||||
leave()
|
||||
|
||||
|
||||
@rpc("authority", "reliable")
|
||||
func _push_lobby(data: Dictionary, besitzer: int) -> void:
|
||||
lobby = data
|
||||
owner_id = besitzer
|
||||
is_owner = besitzer == multiplayer.get_unique_id()
|
||||
lobby_changed.emit()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- Start
|
||||
|
||||
## Einzelspieler: kein Netzwerk, nur der lokale Spieler und Bots.
|
||||
func start_singleplayer(pname: String, ai_count: int) -> void:
|
||||
player_name = pname
|
||||
multiplayer.multiplayer_peer = OfflineMultiplayerPeer.new()
|
||||
is_host = true
|
||||
lobby = {1: pname}
|
||||
owner_id = 1
|
||||
is_owner = true
|
||||
lobby_changed.emit()
|
||||
_begin({"peers": [1], "names": lobby.duplicate(), "ai": ai_count})
|
||||
|
||||
|
||||
## Nur der Host ruft das auf.
|
||||
## Der Eigentümer bittet den Server um den Start.
|
||||
func request_start(ai_count: int) -> void:
|
||||
_req_start.rpc_id(1, ai_count)
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _req_start(ai_count: int) -> void:
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
if multiplayer.get_remote_sender_id() != owner_id:
|
||||
return
|
||||
start_game(ai_count)
|
||||
|
||||
|
||||
func start_game(ai_count: int) -> void:
|
||||
if not is_host:
|
||||
if not multiplayer.is_server() or in_game:
|
||||
return
|
||||
var order: Array = lobby.keys()
|
||||
order.sort()
|
||||
var cfg := {"peers": order, "names": lobby.duplicate(), "ai": ai_count}
|
||||
_begin.rpc(cfg)
|
||||
if order.size() + ai_count < 2:
|
||||
return
|
||||
_begin.rpc({"peers": order, "names": lobby.duplicate(), "ai": ai_count})
|
||||
|
||||
|
||||
@rpc("authority", "call_local", "reliable")
|
||||
|
||||
Reference in New Issue
Block a user