Defekte Sitzung gibt Mitspieler frei, nur echte Gäste beenden die Pause
Hält der Kern mit einem internen Fehler an, schließt ein Host seine Welt für Gäste, und ein Gast kehrt ins Menü zurück, statt dass die anderen ohne Nachricht warten. Eine Pause endet erst, wenn der Host einen Gast angenommen hat, nicht schon bei einer bloßen Verbindung. So kann ein Gast nicht mehr in einer pausierten Welt festhängen, wenn der Host zwischen Verbindung und Gruß pausiert. Eine Pause lässt sich immer beenden. Meldungen, die nach dem Ende einer Sitzung im selben Stapel folgen, bleiben unbeachtet. Vier neue Tests decken das ab. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
+33
-5
@@ -57,6 +57,9 @@ var _save_blocked := false
|
||||
var _figure_wait := 0.0
|
||||
## The address a guest is joining, for messages.
|
||||
var _joining := ""
|
||||
## The host's accepted guests when _process last looked. A new one ends a
|
||||
## pause.
|
||||
var _known_guests := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -233,12 +236,37 @@ func _leave(message: String) -> void:
|
||||
_show_menu(message)
|
||||
|
||||
|
||||
## A broken session runs no more ticks. Instead of leaving the others
|
||||
## waiting, a host closes its world to guests and a guest leaves.
|
||||
func _stop_sharing() -> void:
|
||||
if not session.is_host():
|
||||
_leave(tr("SIMULATION_STOPPED"))
|
||||
return
|
||||
link.link.close()
|
||||
link = null
|
||||
figures.clear()
|
||||
_update_status()
|
||||
|
||||
|
||||
## Ends a pause once the host accepted a guest: the guest joins in the next
|
||||
## tick, and a paused world would keep it waiting. A connection that has not
|
||||
## greeted yet is no guest.
|
||||
func _end_pause_for_new_guests() -> void:
|
||||
var guests := session.guest_count()
|
||||
if guests > _known_guests and clock.paused:
|
||||
_set_paused(false)
|
||||
_known_guests = guests
|
||||
|
||||
|
||||
func _process(delta: float) -> void:
|
||||
if link != null:
|
||||
for notice in link.pump(delta):
|
||||
_on_notice(notice)
|
||||
if session != null and session.is_broken() and link != null:
|
||||
_stop_sharing()
|
||||
if session == null or not session.is_running() or session.is_broken():
|
||||
return
|
||||
_end_pause_for_new_guests()
|
||||
_update_marker()
|
||||
if Input.is_action_pressed("dig") and not clock.paused:
|
||||
_try_dig()
|
||||
@@ -257,11 +285,10 @@ func _process(delta: float) -> void:
|
||||
|
||||
|
||||
func _on_notice(notice: Dictionary) -> void:
|
||||
# An earlier notice of the same batch may have ended the session.
|
||||
if session == null:
|
||||
return
|
||||
match notice["type"]:
|
||||
"guest_arrived":
|
||||
# Pausing is for playing alone; a guest ends the pause.
|
||||
if clock.paused:
|
||||
_set_paused(false)
|
||||
"world_received":
|
||||
if notice["resync"]:
|
||||
resyncs += 1
|
||||
@@ -304,7 +331,8 @@ func _unhandled_input(event: InputEvent) -> void:
|
||||
elif event.is_action_pressed("load_game"):
|
||||
_reload()
|
||||
elif event.is_action_pressed("toggle_pause"):
|
||||
if session.is_host() and session.guest_count() == 0:
|
||||
# Ending a pause is always allowed, starting one only alone.
|
||||
if clock.paused or (session.is_host() and session.guest_count() == 0):
|
||||
_set_paused(not clock.paused)
|
||||
else:
|
||||
hud.flash("PAUSE_ONLY_ALONE")
|
||||
|
||||
@@ -268,6 +268,34 @@ func test_only_the_host_saves_and_only_alone_one_may_pause_or_load() -> void:
|
||||
_clean_up([host_dir, guest_dir])
|
||||
|
||||
|
||||
func test_an_accepted_guest_ends_a_pause_and_a_bare_connection_does_not() -> void:
|
||||
var host_dir := TestFiles.temporary_dir("host")
|
||||
var guest_dir := TestFiles.temporary_dir("guest")
|
||||
var host := _start_host(host_dir)
|
||||
_press(host, "toggle_pause")
|
||||
check(host.clock.paused, "the host pauses while alone")
|
||||
|
||||
# A connection that never greets is no guest.
|
||||
var stranger := NetLink.new()
|
||||
stranger.join("127.0.0.1", host.link.link.local_port())
|
||||
var seen := func() -> bool:
|
||||
stranger.poll(1.0 / 60.0)
|
||||
return host.link.link._peers.size() == 1
|
||||
check(await _wait_until(seen), "the host sees a bare connection")
|
||||
check(host.clock.paused, "which leaves the pause alone")
|
||||
|
||||
var guest := _start_guest(guest_dir, host)
|
||||
check(await _wait_until(_joined.bind(guest)), "a guest joins the paused world")
|
||||
check(not host.clock.paused, "and ends the pause")
|
||||
|
||||
# Should a pause ever meet a guest, the host can still end it.
|
||||
host._set_paused(true)
|
||||
_press(host, "toggle_pause")
|
||||
check(not host.clock.paused, "the host can always end a pause")
|
||||
stranger.close()
|
||||
_clean_up([host_dir, guest_dir])
|
||||
|
||||
|
||||
func test_the_host_sees_a_guest_leave() -> void:
|
||||
var host_dir := TestFiles.temporary_dir("host")
|
||||
var guest_dir := TestFiles.temporary_dir("guest")
|
||||
@@ -295,6 +323,36 @@ func test_a_guest_goes_back_to_the_menu_when_the_host_closes() -> void:
|
||||
_clean_up([host_dir, guest_dir])
|
||||
|
||||
|
||||
func test_a_host_that_stops_sharing_lets_its_guests_go() -> void:
|
||||
var host_dir := TestFiles.temporary_dir("host")
|
||||
var guest_dir := TestFiles.temporary_dir("guest")
|
||||
var host := _start_host(host_dir)
|
||||
var guest := _start_guest(guest_dir, host)
|
||||
await _wait_until(_joined.bind(guest))
|
||||
# An internal error of the core cannot be caused from here. For a broken
|
||||
# session, _process calls what this test calls.
|
||||
host._stop_sharing()
|
||||
check(host.link == null, "the host no longer listens")
|
||||
check_eq(host.hud.status_text(), "", "and no longer says that it does")
|
||||
check(await _wait_until(func() -> bool: return guest.session == null), "the guest's session ends")
|
||||
check_eq(guest.menu.status(), tr("HOST_LEFT"), "and the guest learns why")
|
||||
_clean_up([host_dir, guest_dir])
|
||||
|
||||
|
||||
func test_a_guest_that_stops_sharing_leaves() -> void:
|
||||
var host_dir := TestFiles.temporary_dir("host")
|
||||
var guest_dir := TestFiles.temporary_dir("guest")
|
||||
var host := _start_host(host_dir)
|
||||
var guest := _start_guest(guest_dir, host)
|
||||
await _wait_until(_joined.bind(guest))
|
||||
# As above, for a guest whose own core stopped.
|
||||
guest._stop_sharing()
|
||||
check(guest.session == null, "the guest's session ends")
|
||||
check_eq(guest.menu.status(), tr("SIMULATION_STOPPED"), "the menu says why")
|
||||
check(await _wait_until(func() -> bool: return host.hud.message() == tr("PLAYER_LEFT") % "Grace"), "the host sees the guest go")
|
||||
_clean_up([host_dir, guest_dir])
|
||||
|
||||
|
||||
func test_a_guest_that_is_turned_away_learns_why() -> void:
|
||||
var host_dir := TestFiles.temporary_dir("host")
|
||||
var host := _start_host(host_dir)
|
||||
@@ -305,6 +363,17 @@ func test_a_guest_that_is_turned_away_learns_why() -> void:
|
||||
_clean_up([host_dir])
|
||||
|
||||
|
||||
func test_notices_after_the_end_of_a_session_are_ignored() -> void:
|
||||
var directory := TestFiles.temporary_dir("late")
|
||||
var main := _start_main(directory)
|
||||
# In one batch of notices, an earlier one may have ended the session.
|
||||
main._on_notice({"type": "world_received", "resync": true})
|
||||
main._on_notice({"type": "protocol_error"})
|
||||
check(main.session == null, "no session")
|
||||
check(main.menu.visible, "the menu stays")
|
||||
_clean_up([directory])
|
||||
|
||||
|
||||
func test_a_mistyped_address_is_reported() -> void:
|
||||
var directory := TestFiles.temporary_dir("typo")
|
||||
var main := _start_main(directory)
|
||||
|
||||
Reference in New Issue
Block a user