big
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
extends SceneTree
|
||||
|
||||
## Verifies the out-of-bounds contract on a real map, in a real match:
|
||||
##
|
||||
## 1. leaving the play volume registers the player and starts the countdown
|
||||
## 2. the warning UI appears, with a timer
|
||||
## 3. the red overlay deepens as the timer runs down
|
||||
## 4. the timer is FIVE seconds
|
||||
## 5. reaching zero kills the player
|
||||
## 6. coming back inside before zero cancels it
|
||||
##
|
||||
## godot --path . --windowed --resolution 800x600 \
|
||||
## -s res://debug/out_of_bounds_test.gd
|
||||
##
|
||||
## It runs windowed rather than headless because the overlay is a real Control
|
||||
## and the check on its alpha is the only way to know the screen actually reds
|
||||
## out rather than merely that a number is counting down somewhere.
|
||||
|
||||
## Defaults to Sakura Crossing; pass a scene path to check another map, e.g.
|
||||
## -s res://debug/out_of_bounds_test.gd -- res://scenes/maps/neon_alley/neon_alley.tscn
|
||||
var MAP := "res://scenes/maps/sakura_crossing/sakura_crossing.tscn"
|
||||
## How far outside to teleport. Must clear the widest play volume in the game —
|
||||
## Neon Alley's is 666 m across.
|
||||
const FAR := 2000.0
|
||||
|
||||
var _frames := 0
|
||||
var _fails := 0
|
||||
var _phase := "boot"
|
||||
var _player: Node3D = null
|
||||
var _area: CombatArea = null
|
||||
var _alpha_early := -1.0
|
||||
var _alpha_late := -1.0
|
||||
var _t_start := 0.0
|
||||
|
||||
|
||||
## By TYPE, not by node name — the overlay is created in code and its name is
|
||||
## whatever Godot assigns.
|
||||
func _ui_layer() -> CanvasLayer:
|
||||
if _area == null:
|
||||
return null
|
||||
for c in _area.get_children():
|
||||
if c is CanvasLayer:
|
||||
return c
|
||||
return null
|
||||
|
||||
|
||||
func _check(ok: bool, msg: String) -> void:
|
||||
if ok:
|
||||
print(" OK: ", msg)
|
||||
else:
|
||||
_fails += 1
|
||||
print(" FAIL: ", msg)
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
var args := OS.get_cmdline_user_args()
|
||||
if args.size() > 0:
|
||||
MAP = args[0]
|
||||
print("OOB: testing ", MAP)
|
||||
change_scene_to_file("res://ui/main_menu/main_menu.tscn")
|
||||
|
||||
|
||||
func _find() -> bool:
|
||||
if current_scene == null:
|
||||
return false
|
||||
var areas := current_scene.find_children("*", "Area3D", true, false)
|
||||
for a in areas:
|
||||
if a is CombatArea:
|
||||
_area = a
|
||||
for p in current_scene.find_children("*", "CharacterBody3D", true, false):
|
||||
if str(p.name).is_valid_int():
|
||||
_player = p
|
||||
return _area != null and _player != null
|
||||
|
||||
|
||||
func _process(delta: float) -> bool:
|
||||
_frames += 1
|
||||
match _phase:
|
||||
"boot":
|
||||
if _frames == 60:
|
||||
var nm = root.get_node_or_null("NetworkManager")
|
||||
if nm and nm.has_method("start_singleplayer_match"):
|
||||
nm.start_singleplayer_match(GameMode.DEATHMATCH)
|
||||
change_scene_to_file(MAP)
|
||||
_phase = "settle"
|
||||
"settle":
|
||||
if _frames > 200:
|
||||
if not _find():
|
||||
print("OOB: could not find CombatArea or player")
|
||||
_fails += 1
|
||||
return _done()
|
||||
_check(true, "map has a CombatArea and a player")
|
||||
# Sideways, not upward. Teleporting straight up puts the player
|
||||
# back INSIDE the volume a few seconds later as they fall, which
|
||||
# cancels the countdown and makes the test look like a bug in
|
||||
# the countdown. Far out on X they stay outside however far they
|
||||
# drop — which is also the real case being tested.
|
||||
_player.global_position = Vector3(FAR, 30, 0)
|
||||
var mp := _area.multiplayer
|
||||
print("OOB: peer=%s uid=%d authority=%d is_server=%s" % [
|
||||
mp.has_multiplayer_peer(), mp.get_unique_id(),
|
||||
_player.get_multiplayer_authority(), mp.is_server()])
|
||||
_phase = "left"
|
||||
_t_start = 0.0
|
||||
"left":
|
||||
_t_start += delta
|
||||
if _t_start > 0.4:
|
||||
_check(_area.out_of_bounds_players.has(_player),
|
||||
"leaving the volume registers the player")
|
||||
var t: float = 0.0
|
||||
if _area.out_of_bounds_players.has(_player):
|
||||
t = _area.out_of_bounds_players[_player].time_left
|
||||
_check(t > 4.0 and t <= 5.0,
|
||||
"countdown starts at five seconds (%.2f left after %.1fs)" % [t, _t_start])
|
||||
var lay := _ui_layer()
|
||||
_check(lay != null and lay.visible, "warning UI is on screen")
|
||||
if lay:
|
||||
for c in lay.get_children():
|
||||
if c is Label:
|
||||
_check(c.text.contains("WARNING"),
|
||||
"warning text reads '%s'" % c.text.split("\n")[0])
|
||||
if c is ColorRect:
|
||||
_alpha_early = c.color.a
|
||||
_phase = "reddening"
|
||||
"reddening":
|
||||
_t_start += delta
|
||||
if _t_start > 3.6:
|
||||
var lay := _ui_layer()
|
||||
if lay:
|
||||
for c in lay.get_children():
|
||||
if c is ColorRect:
|
||||
_alpha_late = c.color.a
|
||||
_check(_alpha_late > _alpha_early,
|
||||
"screen reddens as the timer runs down (%.2f -> %.2f)"
|
||||
% [_alpha_early, _alpha_late])
|
||||
_phase = "dying"
|
||||
"dying":
|
||||
_t_start += delta
|
||||
if _t_start > 6.5:
|
||||
var dead: bool = "is_dead" in _player and _player.is_dead
|
||||
var hurt: bool = "health" in _player and float(_player.health) <= 0.0
|
||||
_check(dead or hurt,
|
||||
"player is killed by the environment at zero (dead=%s)" % dead)
|
||||
_check(not _area.out_of_bounds_players.has(_player),
|
||||
"player is dropped from the countdown once dead")
|
||||
return _done()
|
||||
return false
|
||||
|
||||
|
||||
func _done() -> bool:
|
||||
print("=== OUT OF BOUNDS: %s ===" %
|
||||
("ALL PASSED" if _fails == 0 else "%d FAILED" % _fails))
|
||||
return true
|
||||
Reference in New Issue
Block a user