Co-op pass: host-disconnect handling, spectate, late-join wave sync
- Clients now handle the host closing the arena: bank winnings if still alive (retire), reset the HUD, and return to the camp menu instead of being stranded in a dead world - Death in co-op offers Spectate when a teammate is still fighting: closes the results overlay and watches the run; the escape menu gains a "Back to Camp" option for dead spectators to leave later - Late joiners receive the current wave number on connect so their HUD doesn't sit on "Prepare..." until the next wave starts
This commit is contained in:
@@ -45,6 +45,9 @@ func _ready():
|
|||||||
# Reshape the menu into the gladiator outfitting screen
|
# Reshape the menu into the gladiator outfitting screen
|
||||||
_setup_outfitting_menu()
|
_setup_outfitting_menu()
|
||||||
|
|
||||||
|
# Clients: if the host closes the arena, bank winnings and return to camp
|
||||||
|
Network.server_disconnected.connect(_on_server_closed)
|
||||||
|
|
||||||
# Create or find weapons container
|
# Create or find weapons container
|
||||||
if has_node("WeaponsContainer"):
|
if has_node("WeaponsContainer"):
|
||||||
weapons_container = get_node("WeaponsContainer")
|
weapons_container = get_node("WeaponsContainer")
|
||||||
@@ -81,6 +84,19 @@ func _on_connected_to_server():
|
|||||||
print("[Level] Connected to server! Cleaning up manual weapons")
|
print("[Level] Connected to server! Cleaning up manual weapons")
|
||||||
_cleanup_manual_weapons_on_client()
|
_cleanup_manual_weapons_on_client()
|
||||||
|
|
||||||
|
## The host closed the arena while we were connected (client side).
|
||||||
|
## Leaving alive counts as retiring - bank everything, then back to camp.
|
||||||
|
func _on_server_closed():
|
||||||
|
print("[Level] Server disconnected - returning to camp")
|
||||||
|
if GameState.run_active:
|
||||||
|
GameState.retire_run()
|
||||||
|
|
||||||
|
if has_node("/root/HUD"):
|
||||||
|
get_node("/root/HUD").reset()
|
||||||
|
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
get_tree().reload_current_scene()
|
||||||
|
|
||||||
## Called by Network when multiplayer peer is set up
|
## Called by Network when multiplayer peer is set up
|
||||||
func initialize_multiplayer():
|
func initialize_multiplayer():
|
||||||
print("[Level] initialize_multiplayer called. is_server: ", multiplayer.is_server())
|
print("[Level] initialize_multiplayer called. is_server: ", multiplayer.is_server())
|
||||||
@@ -235,6 +251,9 @@ func _on_player_connected(peer_id, player_info):
|
|||||||
for orb_id in _active_orbs.keys():
|
for orb_id in _active_orbs.keys():
|
||||||
rpc_id(peer_id, "_spawn_health_orb_local", orb_id, _active_orbs[orb_id])
|
rpc_id(peer_id, "_spawn_health_orb_local", orb_id, _active_orbs[orb_id])
|
||||||
|
|
||||||
|
# Sync the current wave number so the late joiner's HUD isn't stuck on "Prepare..."
|
||||||
|
GameState.rpc_id(peer_id, "sync_wave", GameState.current_wave)
|
||||||
|
|
||||||
# Sync equipped weapons for all existing players to the newly joined player
|
# Sync equipped weapons for all existing players to the newly joined player
|
||||||
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
|
print("[Server] Syncing equipped weapons to newly connected peer: ", peer_id)
|
||||||
for player_node in players_container.get_children():
|
for player_node in players_container.get_children():
|
||||||
|
|||||||
@@ -54,14 +54,19 @@ func toggle_menu():
|
|||||||
if player and player.is_multiplayer_authority():
|
if player and player.is_multiplayer_authority():
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
|
||||||
## Retiring is only possible while alive with an active run
|
## Alive with an active run: retire (bank winnings).
|
||||||
|
## Dead but spectating a live session: plain trip home (settlement already done).
|
||||||
func _update_retire_button():
|
func _update_retire_button():
|
||||||
if not _retire_button:
|
if not _retire_button:
|
||||||
return
|
return
|
||||||
var can_retire = GameState.run_active and player and not player.is_dead
|
if GameState.run_active and player and not player.is_dead:
|
||||||
_retire_button.visible = can_retire
|
_retire_button.visible = true
|
||||||
if can_retire:
|
|
||||||
_retire_button.text = "Retire to Camp (+%dg)" % GameState.run_gold
|
_retire_button.text = "Retire to Camp (+%dg)" % GameState.run_gold
|
||||||
|
elif multiplayer.multiplayer_peer != null and player and player.is_dead:
|
||||||
|
_retire_button.visible = true
|
||||||
|
_retire_button.text = "Back to Camp"
|
||||||
|
else:
|
||||||
|
_retire_button.visible = false
|
||||||
|
|
||||||
## Called when Resume button is clicked
|
## Called when Resume button is clicked
|
||||||
func _on_resume_pressed():
|
func _on_resume_pressed():
|
||||||
|
|||||||
@@ -80,6 +80,15 @@ func _ready():
|
|||||||
|
|
||||||
_add_spacer(vbox, 16)
|
_add_spacer(vbox, 16)
|
||||||
|
|
||||||
|
# Spectate option - only when a teammate is still fighting
|
||||||
|
if _teammates_still_alive():
|
||||||
|
var spectate_button = Button.new()
|
||||||
|
spectate_button.text = "Spectate"
|
||||||
|
spectate_button.custom_minimum_size = Vector2(0, 44)
|
||||||
|
spectate_button.pressed.connect(_on_spectate_pressed)
|
||||||
|
vbox.add_child(spectate_button)
|
||||||
|
_add_spacer(vbox, 6)
|
||||||
|
|
||||||
# Return button
|
# Return button
|
||||||
var return_button = Button.new()
|
var return_button = Button.new()
|
||||||
return_button.text = "Return to Camp"
|
return_button.text = "Return to Camp"
|
||||||
@@ -90,6 +99,25 @@ func _ready():
|
|||||||
# Make sure the mouse is usable on the overlay
|
# Make sure the mouse is usable on the overlay
|
||||||
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
|
||||||
|
|
||||||
|
## Any other living player in the arena?
|
||||||
|
func _teammates_still_alive() -> bool:
|
||||||
|
if multiplayer.multiplayer_peer == null:
|
||||||
|
return false
|
||||||
|
var level = get_tree().get_current_scene()
|
||||||
|
if not level or not level.has_node("PlayersContainer"):
|
||||||
|
return false
|
||||||
|
var my_id = multiplayer.get_unique_id()
|
||||||
|
for p in level.get_node("PlayersContainer").get_children():
|
||||||
|
if p is Character and not p.is_dead and str(p.name).to_int() != my_id:
|
||||||
|
return true
|
||||||
|
return false
|
||||||
|
|
||||||
|
## Close the overlay and watch the run play out (escape menu can leave later).
|
||||||
|
## For the host this also keeps the server alive for everyone else.
|
||||||
|
func _on_spectate_pressed():
|
||||||
|
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
|
||||||
|
queue_free()
|
||||||
|
|
||||||
func _add_header(parent: Control, text: String):
|
func _add_header(parent: Control, text: String):
|
||||||
var label = Label.new()
|
var label = Label.new()
|
||||||
label.text = text
|
label.text = text
|
||||||
|
|||||||
Reference in New Issue
Block a user