mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
98 lines
3.4 KiB
GDScript
98 lines
3.4 KiB
GDScript
extends Node
|
|
|
|
# Task 6.2 / 6.7: black-box client for the exported dedicated-server smoke.
|
|
# It contains no in-process server hook: two copies run in distinct containers,
|
|
# join through ENet, and pass only after an authoritative score RPC arrives.
|
|
|
|
const DEFAULT_PORT := 7777
|
|
const TIMEOUT_SECONDS := 55.0
|
|
|
|
var _address := "server"
|
|
var _port := DEFAULT_PORT
|
|
var _name := "ExportSmoke"
|
|
var _expected_goals := 1
|
|
var _goals_observed := 0
|
|
|
|
@onready var _network_manager: Node = get_node("/root/NetworkManager")
|
|
@onready var _match_net: Node = get_node("/root/MatchNet")
|
|
@onready var _match_sim: Node = get_node("/root/MatchSim")
|
|
|
|
|
|
func _ready() -> void:
|
|
# The client enters networked_match.tscn after the hello handshake. Keep
|
|
# this observer outside that scene so the scene transition cannot free it
|
|
# before the authoritative score RPC arrives. Deferring avoids reparenting
|
|
# while Godot is still adding the smoke scene to the tree.
|
|
call_deferred("_move_to_root")
|
|
for arg in OS.get_cmdline_user_args():
|
|
if arg.begins_with("--address="):
|
|
_address = arg.get_slice("=", 1)
|
|
elif arg.begins_with("--port="):
|
|
_port = int(arg.get_slice("=", 1))
|
|
elif arg.begins_with("--name="):
|
|
_name = arg.get_slice("=", 1)
|
|
elif arg.begins_with("--expected-goals="):
|
|
_expected_goals = maxi(1, int(arg.get_slice("=", 1)))
|
|
# Use node paths instead of autoload identifiers: this test deliberately
|
|
# runs from a clean source tree, before Godot has an editor-generated cache.
|
|
_match_net.set("local_player_name", _name)
|
|
_match_net.connect("welcomed", _on_welcomed)
|
|
_match_sim.connect("score_update_received", _on_score_update)
|
|
get_tree().create_timer(TIMEOUT_SECONDS).timeout.connect(_on_timeout)
|
|
# ENet's connection state machine handles a server still booting. Joining
|
|
# immediately also ensures MatchSim never runs a physics tick on an inactive
|
|
# MultiplayerPeer while a timer waits to make the first connection attempt.
|
|
_connect()
|
|
|
|
|
|
func _move_to_root() -> void:
|
|
var parent := get_parent()
|
|
if parent == null:
|
|
return
|
|
var tree := get_tree()
|
|
parent.remove_child(self)
|
|
tree.root.add_child(self)
|
|
|
|
|
|
func _process(_delta: float) -> void:
|
|
_network_manager.call("poll")
|
|
|
|
|
|
func _physics_process(_delta: float) -> void:
|
|
_network_manager.call("poll")
|
|
|
|
|
|
func _connect() -> void:
|
|
var err: int = _network_manager.call("join", _address, _port)
|
|
if err != OK:
|
|
_fail("join(%s:%d) failed: %s" % [_address, _port, error_string(err)])
|
|
|
|
|
|
func _on_welcomed() -> void:
|
|
_match_net.disconnect("welcomed", _on_welcomed)
|
|
get_tree().change_scene_to_file.call_deferred(ScenePaths.NETWORKED_MATCH)
|
|
|
|
|
|
func _on_score_update(score: Dictionary) -> void:
|
|
if int(score.get(0, 0)) + int(score.get(1, 0)) < 1:
|
|
return
|
|
_goals_observed += 1
|
|
if _goals_observed < _expected_goals:
|
|
print("EXPORT SMOKE: %s observed match %d/%d score %s" % [_name, _goals_observed, _expected_goals, str(score)])
|
|
return
|
|
print("EXPORT SMOKE PASS: %s observed %d authoritative goals" % [_name, _goals_observed])
|
|
# Give the reliable goal/state messages one beat to settle before this peer
|
|
# leaves, then let the server's zero-reservation test config abort/drain.
|
|
get_tree().create_timer(0.5).timeout.connect(func() -> void: get_tree().quit(0))
|
|
|
|
|
|
func _on_timeout() -> void:
|
|
if _goals_observed < _expected_goals:
|
|
_fail("%s timed out without an authoritative goal" % _name)
|
|
|
|
|
|
func _fail(message: String) -> void:
|
|
printerr("EXPORT SMOKE FAIL: " + message)
|
|
_network_manager.call("shutdown")
|
|
get_tree().quit(1)
|