extends SceneTree
## Gate H4 (docs/home-world-design.md §3): the main menu's Home button.
##
## 1. before the daemon / identity: disabled, and says why
## 2. daemon not running: disabled, says so
## 3. identity loaded: asks home.get once; enabled as "Home"
## 4. pressed: selects the home room with the OFFLINE peer — no mesh, no DHT
## (the mesh path waits for a DHT bootstrap, which offline never finishes,
## and only then starts the game)
## 5. pressed again at home: just closes the menu
## 6. the room list shows the home room as "Home", with no Purge button
## 7. home.get fails (HOME_ROOM_MISSING): disabled, the error in the tooltip
##
## The menu is taken out of game.tscn on its own (Game's _ready never runs) and
## KeetService.room_mgr is a recording stub. Run with no daemon:
## HYPERTPS_DAEMON_TCP=127.0.0.1:1 makes KeetService's client fail harmlessly.
const HOME := "gatehomeroom00000000000000000000000000000000000000000"
const OTHER := "gateotherroom0000000000000000000000000000000000000000"
var ok := true
var _ran := false
var _finished := false
func _p(c: bool, m: String) -> void:
print(("ok " if c else "FAIL: ") + m)
if not c:
ok = false
func _process(_delta: float) -> bool:
if _ran:
return true
_ran = true
_run()
# A SCRIPT ERROR aborts _run() without touching `ok`; only a run that reached
# its last line can pass.
_p(_finished, "the gate ran to the end (no script error aborted it)")
print("GATE home_button: %s" % ("PASS" if ok else "FAIL"))
quit(0 if ok else 1)
return true
func _menu() -> Node:
var game_scene := load("res://scenes/game/game.tscn") as PackedScene
var game: Node = game_scene.instantiate()
var menu: Node = game.get_node("MenuUI/MenuMain")
menu.get_parent().remove_child(menu)
game.free()
root.add_child(menu)
# Graphical behaviour under --headless: the gate is what says Home is wanted.
menu.home_enabled = true
menu._update_home_button()
# Never let the gate touch the network: a non-home room's _start_mesh returns
# early without HyperswarmMultiplayerPeer.
menu._has_hyperswarm = false
return menu
func _run() -> void:
var keet: Node = root.get_node_or_null("KeetService")
_p(keet != null, "KeetService autoload present")
if keet == null:
return
var stub := preload("res://scenes/test/_stub_keet_room_home.gd").new()
var real_mgr: Node = keet.room_mgr
keet.room_mgr = stub
# A failed assignment is a SCRIPT ERROR that aborts _run() with `ok` still
# true — the first run of this gate printed PASS that way. Check it took.
_p(keet.room_mgr == stub, "the stub room manager is installed")
if keet.room_mgr != stub:
return
stub.home_result = {"roomId": HOME, "created": false}
var menu := _menu()
var btn: Button = menu.get_node_or_null("MainPanel/HomeButton")
_p(btn != null, "the main panel has a HomeButton (Resume is gone: %s)"
% str(menu.get_node_or_null("MainPanel/ResumeButton") == null))
if btn == null:
_cleanup(menu, keet, real_mgr, stub)
return
# 1. nothing known yet
_p(btn.disabled and btn.text.contains("waiting for keet-daemon"),
"disabled + waiting before the daemon answers (text=%s)" % btn.text)
# 2. daemon not running
menu._on_daemon_status("disconnected")
_p(btn.disabled and btn.text.contains("not running"), "daemon down: disabled + says so (text=%s)" % btn.text)
_p(stub.home_calls == 0, "no home.get while the daemon is down")
# 3. daemon up, identity loaded -> one home.get, enabled
menu._on_daemon_status("running")
menu._on_identity_loaded({"displayName": "gate", "memberId": "gatemember"})
_p(stub.home_calls == 1, "identity loaded -> home.get asked once (calls=%d)" % stub.home_calls)
_p(not btn.disabled and btn.text == "Home", "enabled as \"Home\" (text=%s)" % btn.text)
_p(keet.home_room_id == HOME, "KeetService.home_room_id = the home room")
# 4. pressed -> home room, offline peer, no mesh, game started
menu._on_home_pressed()
_p(keet.get_current_room() == HOME, "pressing Home selects the home room (room=%s)" % keet.get_current_room())
_p(menu.multiplayer.multiplayer_peer is OfflineMultiplayerPeer,
"the home room runs on the offline peer (peer=%s)" % str(menu.multiplayer.multiplayer_peer))
_p(not menu._mesh_active and menu._peer == null, "no mesh / DHT for the home room")
_p(menu._game_started, "the game is started")
_p(not menu.visible, "the menu closes")
# The mesh overlay says it is home, and keeps the id clients compare.
var gs: Node = keet.game_state
var was_visible: bool = gs._diag_overlay.visible
gs._diag_overlay.visible = true
gs._process(0.0)
var hud: String = gs._mesh_label.text
gs._diag_overlay.visible = was_visible
_p(hud.contains("room=home(%s)" % HOME.substr(0, 12)), "HUD reads room=home(<id>) (hud=%s)" % hud)
# 5. pressed again at home: only closes the menu
menu.show_menu()
menu._on_home_pressed()
_p(keet.get_current_room() == HOME and not menu.visible, "pressed again at home: menu closes, room unchanged")
# 6. the room list
keet._room_cache[HOME] = "Home"
keet._room_cache[OTHER] = "Other Room"
menu._refresh_room_list()
var home_row := _row_for(menu, "Home")
var other_row := _row_for(menu, "Other Room")
_p(home_row != null, "the home room is listed as \"Home\"")
_p(home_row != null and not _has_button(home_row, "Purge"), "the home room has no Purge button")
_p(other_row != null and _has_button(other_row, "Purge"), "other rooms keep Purge")
# 6b. leaving home for another room ends the home session
keet.select_room(OTHER)
_p(keet.get_current_room() == OTHER, "another room can be selected from home")
_p(not menu._game_started, "leaving home ends the home session (game_started=%s)" % str(menu._game_started))
_p(not (menu.multiplayer.multiplayer_peer is OfflineMultiplayerPeer),
"the offline peer is gone (peer=%s)" % str(menu.multiplayer.multiplayer_peer))
# ...and Home takes you back
menu._on_home_pressed()
_p(keet.get_current_room() == HOME and menu._game_started
and menu.multiplayer.multiplayer_peer is OfflineMultiplayerPeer, "Home from another room returns home, offline")
# 7. home.get fails
menu.queue_free()
keet.home_room_id = ""
stub.home_result = {"error": "HOME_ROOM_MISSING: identity-0 homeRoomId abc is not a room on this identity"}
var menu2 := _menu()
var btn2: Button = menu2.get_node("MainPanel/HomeButton")
menu2._on_daemon_status("running")
menu2._on_identity_loaded({"displayName": "gate", "memberId": "gatemember"})
_p(btn2.disabled, "home.get error: disabled (text=%s)" % btn2.text)
_p(btn2.tooltip_text.contains("HOME_ROOM_MISSING"), "the error is in the tooltip (tooltip=%s)" % btn2.tooltip_text)
menu2._on_home_pressed()
_p(keet.get_current_room() == HOME, "pressing a disabled Home changes nothing")
_cleanup(menu2, keet, real_mgr, stub)
_finished = true
func _row_for(menu: Node, title: String) -> Node:
for row in menu.room_list.get_children():
if row.is_queued_for_deletion():
continue
for c in row.get_children():
if c is Button and (c.text == title or c.text == "> " + title):
return row
return null
func _has_button(row: Node, text: String) -> bool:
for c in row.get_children():
if c is Button and c.text == text:
return true
return false
func _cleanup(menu: Node, keet: Node, real_mgr: Node, stub: Node) -> void:
keet.room_mgr = real_mgr
keet._room_cache.erase(HOME)
keet._room_cache.erase(OTHER)
keet.home_room_id = ""
menu.queue_free()
stub.free()