Win, lose screen. Game version info. Matador a lot more aggressive/wins easier (tweaks needed). No more score/combo/waves
This commit is contained in:
@@ -0,0 +1,258 @@
|
||||
extends SceneTree
|
||||
## Headless difficulty simulator.
|
||||
##
|
||||
## Drives a kinematic "bot bull" against ONE real matador (the actual Matador.tscn
|
||||
## + matador.gd AI) across a few behaviour profiles, many trials each, and reports
|
||||
## how often the matador wins — i.e. gores the bull — versus how often the bull
|
||||
## wins by ramming the matador. It exists so matador difficulty can be tuned from
|
||||
## measurements instead of guesswork.
|
||||
##
|
||||
## godot --headless --script tests/difficulty_sim.gd
|
||||
##
|
||||
## Read the CHARGER row: that models a player who just charges in. If the matador
|
||||
## almost never wins there, blind charging is unpunished and the game is too easy.
|
||||
|
||||
const CHARGER_TRIALS := 16
|
||||
const KITER_TRIALS := 16
|
||||
const PASSIVE_TRIALS := 8
|
||||
const TRIAL_TIMEOUT := 5.0
|
||||
const SPAWN_SETTLE := 0.4
|
||||
|
||||
const CHARGE_SPEED := 42.0 # a fast, committed horn-charge
|
||||
const KITE_DIST := 12.0
|
||||
const KITE_SPEED := 17.0 # a real bull easily out-paces the matador; only throws threaten
|
||||
const START_DIST := 12.0
|
||||
|
||||
var _scene: Node
|
||||
var _dp: Node
|
||||
var _mat_scene: PackedScene
|
||||
var _player: CharacterBody3D
|
||||
var _matador: Node3D
|
||||
var _bull_dead := false
|
||||
var _mat_dead := false
|
||||
var _dt := 1.0 / 60.0
|
||||
|
||||
# Charger bull state
|
||||
var _charge_dir: Vector3 = Vector3.ZERO
|
||||
var _charge_timer: float = 0.0
|
||||
var _recover_timer: float = 0.0
|
||||
# Kiter juke state
|
||||
var _juke_dir: float = 1.0
|
||||
var _juke_timer: float = 0.0
|
||||
var _juke_range: float = KITE_DIST
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_run.call_deferred()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_dt = 1.0 / float(Engine.physics_ticks_per_second)
|
||||
_dp = root.get_node_or_null("/root/DP")
|
||||
_mat_scene = load("res://Matador.tscn") as PackedScene
|
||||
|
||||
_scene = (load("res://scene.tscn") as PackedScene).instantiate()
|
||||
root.add_child(_scene)
|
||||
current_scene = _scene
|
||||
await create_timer(0.5).timeout
|
||||
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if players.is_empty():
|
||||
push_error("no player in scene"); quit(1); return
|
||||
_player = players[0]
|
||||
_player.set_physics_process(false) # we drive the bull kinematically
|
||||
_player.died.connect(func(_cause: String) -> void: _bull_dead = true)
|
||||
|
||||
_strip_match_machinery() # no HUD pause / spawner interference
|
||||
|
||||
print("=".repeat(64))
|
||||
print("DIFFICULTY SIMULATION (physics %d Hz)" % Engine.physics_ticks_per_second)
|
||||
print("=".repeat(64))
|
||||
|
||||
var charger := await _run_profile("CHARGER (fast straight charges)", CHARGER_TRIALS, &"charger")
|
||||
var kiter := await _run_profile("KITER (circles at ~11 m)", KITER_TRIALS, &"kiter")
|
||||
var passive := await _run_profile("PASSIVE (holds at attack range)", PASSIVE_TRIALS, &"passive")
|
||||
|
||||
print("\n" + "=".repeat(64))
|
||||
print("SUMMARY — matador win %% (bull gored)")
|
||||
print("=".repeat(64))
|
||||
_print_row("CHARGER", charger)
|
||||
_print_row("KITER ", kiter)
|
||||
_print_row("PASSIVE", passive)
|
||||
print("=".repeat(64))
|
||||
quit(0)
|
||||
|
||||
|
||||
var _state_hist: Dictionary = {}
|
||||
var _throws: int = 0
|
||||
var _diag_printed: bool = false
|
||||
|
||||
|
||||
func _run_profile(label: String, trials: int, mode: StringName) -> Dictionary:
|
||||
var wins := 0
|
||||
var losses := 0
|
||||
var timeouts := 0
|
||||
var total_t := 0.0
|
||||
_state_hist = {}
|
||||
_throws = 0
|
||||
for _i in trials:
|
||||
var res := await _run_trial(mode)
|
||||
total_t += res[1] as float
|
||||
match res[0] as StringName:
|
||||
&"mat": wins += 1
|
||||
&"bull": losses += 1
|
||||
_: timeouts += 1
|
||||
print("\n-- %s --" % label)
|
||||
print(" matador wins: %d/%d bull wins: %d timeouts: %d avg %.2fs" % [
|
||||
wins, trials, losses, timeouts, total_t / maxf(float(trials), 1.0)])
|
||||
print(" swords thrown: %d states: %s" % [_throws, _fmt_hist()])
|
||||
return {"win": wins, "loss": losses, "timeout": timeouts, "n": trials}
|
||||
|
||||
|
||||
func _fmt_hist() -> String:
|
||||
var parts: Array[String] = []
|
||||
var total := 0
|
||||
for k: String in _state_hist:
|
||||
total += _state_hist[k] as int
|
||||
for k: String in _state_hist:
|
||||
parts.append("%s %.0f%%" % [k, 100.0 * float(_state_hist[k]) / maxf(float(total), 1.0)])
|
||||
return ", ".join(parts)
|
||||
|
||||
|
||||
func _count_thrown_swords() -> int:
|
||||
var n := 0
|
||||
for c: Node in _scene.get_children():
|
||||
if c is RigidBody3D:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
func _run_trial(mode: StringName) -> Array:
|
||||
if is_instance_valid(_matador):
|
||||
_matador.queue_free()
|
||||
await physics_frame
|
||||
|
||||
_matador = _mat_scene.instantiate()
|
||||
_scene.add_child(_matador)
|
||||
_matador.global_position = Vector3(0.0, 1.0, 0.0)
|
||||
_matador.killed.connect(func() -> void: _mat_dead = true)
|
||||
|
||||
var ang := randf() * TAU
|
||||
_player.global_position = Vector3(cos(ang), 0.0, sin(ang)) * START_DIST + Vector3(0.0, 1.0, 0.0)
|
||||
_player.velocity = Vector3.ZERO
|
||||
_player._dead = false
|
||||
_bull_dead = false
|
||||
_mat_dead = false
|
||||
_charge_dir = Vector3.ZERO
|
||||
_charge_timer = 0.0
|
||||
_recover_timer = 0.0
|
||||
_juke_dir = 1.0 if randf() < 0.5 else -1.0
|
||||
_juke_timer = 0.0
|
||||
_juke_range = KITE_DIST
|
||||
|
||||
# Let the matador's _ready (ragdoll rig, sword) settle before the duel counts.
|
||||
var settle := SPAWN_SETTLE
|
||||
while settle > 0.0:
|
||||
await physics_frame
|
||||
settle -= _dt
|
||||
_bull_dead = false
|
||||
_mat_dead = false
|
||||
_player._dead = false
|
||||
|
||||
if not _diag_printed:
|
||||
_diag_printed = true
|
||||
print(" [diag] matador has_sword=%s in_hand=%s throw_range=[%.0f,%.0f]" % [
|
||||
is_instance_valid(_matador._sword_node), _matador._sword_in_hand,
|
||||
_dp.f("mat_throw_min_dist"), _dp.f("mat_throw_range")])
|
||||
|
||||
var t := 0.0
|
||||
var max_swords := 0
|
||||
while t < TRIAL_TIMEOUT:
|
||||
_drive(mode)
|
||||
await physics_frame
|
||||
t += _dt
|
||||
var s: String = _matador.ai_state_name()
|
||||
_state_hist[s] = int(_state_hist.get(s, 0)) + 1
|
||||
max_swords = maxi(max_swords, _count_thrown_swords())
|
||||
if _bull_dead: # the bull was gored → matador win
|
||||
_throws += max_swords
|
||||
return [&"mat", t]
|
||||
if _mat_dead: # the matador was rammed → bull win
|
||||
_throws += max_swords
|
||||
return [&"bull", t]
|
||||
_throws += max_swords
|
||||
return [&"timeout", t]
|
||||
|
||||
|
||||
func _drive(mode: StringName) -> void:
|
||||
var to_mat := _matador.global_position - _player.global_position
|
||||
to_mat.y = 0.0
|
||||
var dist := to_mat.length()
|
||||
var aim := to_mat.normalized() if dist > 0.05 else Vector3.FORWARD
|
||||
var vel := Vector3.ZERO
|
||||
|
||||
match mode:
|
||||
&"charger":
|
||||
if _recover_timer > 0.0:
|
||||
_recover_timer -= _dt
|
||||
vel = -aim * 14.0
|
||||
else:
|
||||
if _charge_timer <= 0.0:
|
||||
_charge_dir = aim # commit a straight line (dodgeable)
|
||||
_charge_timer = 0.6
|
||||
_charge_timer -= _dt
|
||||
vel = _charge_dir * CHARGE_SPEED
|
||||
if dist < 0.8 or _charge_timer <= 0.0:
|
||||
_recover_timer = 0.35
|
||||
_charge_timer = 0.0
|
||||
&"kiter":
|
||||
# Juke: flip strafe direction and shift the hold distance at random so the
|
||||
# matador can't perfectly lead the throw — models an evasive player.
|
||||
_juke_timer -= _dt
|
||||
if _juke_timer <= 0.0:
|
||||
_juke_timer = randf_range(0.4, 0.9)
|
||||
_juke_dir = -_juke_dir if randf() < 0.6 else _juke_dir
|
||||
_juke_range = randf_range(8.0, 13.0)
|
||||
var inward := aim * clampf(dist - _juke_range, -1.0, 1.0)
|
||||
var tangent := Vector3(-aim.z, 0.0, aim.x) * _juke_dir
|
||||
vel = (tangent + inward).normalized() * KITE_SPEED
|
||||
&"passive":
|
||||
# Sit just inside the matador's attack range so it commits to a strike.
|
||||
if dist > 6.5:
|
||||
vel = aim * 6.0
|
||||
elif dist < 5.0:
|
||||
vel = -aim * 4.0
|
||||
|
||||
_player.velocity = Vector3(vel.x, 0.0, vel.z)
|
||||
_player.move_and_slide()
|
||||
var p := _player.global_position
|
||||
p.y = 1.0
|
||||
_player.global_position = p
|
||||
|
||||
|
||||
func _strip_match_machinery() -> void:
|
||||
for m: Node in get_nodes_in_group(&"matador"):
|
||||
m.queue_free()
|
||||
for s: Node in get_nodes_in_group(&"matador_spawn"):
|
||||
s.queue_free()
|
||||
var hud := _find_hud(_scene)
|
||||
if hud != null:
|
||||
hud.queue_free()
|
||||
|
||||
|
||||
func _find_hud(n: Node) -> Node:
|
||||
if n is CanvasLayer and n.has_method(&"_show_game_over"):
|
||||
return n
|
||||
for c: Node in n.get_children():
|
||||
var r := _find_hud(c)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
func _print_row(label: String, r: Dictionary) -> void:
|
||||
var n: int = r["n"]
|
||||
var win: int = r["win"]
|
||||
var pct := 100.0 * float(win) / maxf(float(n), 1.0)
|
||||
print(" %s %5.1f%% (%d win / %d loss / %d timeout)" % [
|
||||
label, pct, win, r["loss"], r["timeout"]])
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmrwponlq3eg6
|
||||
+121
-46
@@ -22,6 +22,15 @@ const TAIL_BONES: Array[StringName] = [
|
||||
var _passed: int = 0
|
||||
var _failed: int = 0
|
||||
var _report: PackedStringArray = []
|
||||
var _matador_scene: PackedScene = null
|
||||
|
||||
|
||||
# Loaded lazily (not preloaded): as a --script SceneTree entry, a top-level
|
||||
# preload of a matador scene compiles matador.gd before the DP autoload binds.
|
||||
func _mat_scene() -> PackedScene:
|
||||
if _matador_scene == null:
|
||||
_matador_scene = load("res://Matador.tscn")
|
||||
return _matador_scene
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
@@ -44,9 +53,21 @@ func _run() -> void:
|
||||
# Let physics, AI, and IK warm up.
|
||||
await create_timer(0.5).timeout
|
||||
|
||||
# The matador is lethal on contact now, so a wandering one could end the match
|
||||
# during the non-combat checks below. Park the bull on a temp pad far out in the
|
||||
# void, well out of any matador's reach (the roll tests use this spot too).
|
||||
_isolate_bull(scene)
|
||||
|
||||
_check_bull_animation()
|
||||
await _check_overlays()
|
||||
|
||||
# The overlay check needed live matadors; now remove them. The aggressive matador
|
||||
# pursues the bull, and the ragdoll check spawns its own fresh one, so none should
|
||||
# be roaming during the capture / tail / roll phases.
|
||||
for m: Node in get_nodes_in_group(&"matador"):
|
||||
m.queue_free()
|
||||
await physics_frame
|
||||
|
||||
# Capture 5 frames ~0.4 s apart (covers roughly one wander step and walk cycle).
|
||||
for i in range(5):
|
||||
_capture_frame("motion_%02d.png" % i)
|
||||
@@ -55,30 +76,54 @@ func _run() -> void:
|
||||
# After ~2.5 s the tail Verlet chain and IK should be stable.
|
||||
_check_tail_integrity()
|
||||
|
||||
# Trigger ragdoll on the first matador and check that bones don't explode.
|
||||
var matadors := get_nodes_in_group(&"matador")
|
||||
if matadors.size() > 0:
|
||||
var mat: Node3D = matadors[0] as Node3D
|
||||
# Roll first, in open space, before any matador dies (a kill ends the match).
|
||||
await _check_roll_ability(scene)
|
||||
await create_timer(0.1).timeout # let the roll pop-test's throwaway matador free
|
||||
|
||||
# Ragdoll check on a FRESH matador spawned at the origin (the isolated bull is far
|
||||
# away, so this one can't reach it). Not wired to the spawner, so its death won't
|
||||
# trip the win screen. Ragdoll it immediately and check the bones don't explode.
|
||||
var mat_scene := _mat_scene()
|
||||
if mat_scene != null:
|
||||
var mat: Node3D = mat_scene.instantiate()
|
||||
scene.add_child(mat)
|
||||
mat.global_position = Vector3(0.0, 1.0, 0.0)
|
||||
await create_timer(0.3).timeout
|
||||
var start_pos: Vector3 = mat.global_position
|
||||
mat._enter_ragdoll(Vector3(0.0, 0.0, 1.0), 10.0)
|
||||
_capture_frame("ragdoll_trigger.png")
|
||||
await create_timer(0.8).timeout
|
||||
_capture_frame("ragdoll_result.png")
|
||||
_check_ragdoll_sanity(mat, start_pos)
|
||||
_check_score_wiring(scene)
|
||||
else:
|
||||
_note("ragdoll check skipped — no matadors found in scene")
|
||||
_note("ragdoll check skipped — Matador.tscn missing")
|
||||
|
||||
# Last, so any matadors the roll bowls over don't perturb the score check above.
|
||||
await _check_roll_ability(scene)
|
||||
# Last — a bull hit raises the lose screen and pauses the tree.
|
||||
await _check_game_over_wiring(scene)
|
||||
|
||||
_finish()
|
||||
|
||||
|
||||
# ── Roll ability ──────────────────────────────────────────────────────────────
|
||||
# Roll into a synthetic wall and confirm the bank shot: the heading reflects and the
|
||||
# speed boosts past roll_max_speed (the natural ramp caps at max, so any excess is a
|
||||
# wall boost). A low roll_duration lets it end within the sample window.
|
||||
# Drop a small static floor at (-45, -45) and stand the bull on it — 60+ m from the
|
||||
# arena, so no matador can close the gap within the test window.
|
||||
func _isolate_bull(scene: Node) -> void:
|
||||
var pad := StaticBody3D.new()
|
||||
var cs := CollisionShape3D.new()
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(20, 1, 20)
|
||||
cs.shape = box
|
||||
pad.add_child(cs)
|
||||
scene.add_child(pad)
|
||||
pad.global_position = Vector3(-45, -0.5, -45) # top surface at y = 0
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if not players.is_empty():
|
||||
(players[0] as Node3D).global_position = Vector3(-45, 1, -45)
|
||||
|
||||
|
||||
# ── Roll ability (Rammus Powerball) ────────────────────────────────────────────
|
||||
# Two behaviours: the speed ramps up the longer the ball rolls (base → max), capped
|
||||
# at roll_max_speed with no wall-boost overshoot; and ramming a matador pops the ball
|
||||
# (the roll ends). Rolled in empty space so it doesn't touch the real match matadors.
|
||||
|
||||
func _check_roll_ability(scene: Node) -> void:
|
||||
print("\n-- check_roll_ability --")
|
||||
@@ -91,41 +136,62 @@ func _check_roll_ability(scene: Node) -> void:
|
||||
var ap: AnimationPlayer = _find_anim_player(player)
|
||||
_assert_true(player.ability_cd.size() == 4, "bull has 4 ability slots (roll added)")
|
||||
|
||||
var wall := StaticBody3D.new()
|
||||
var cs := CollisionShape3D.new()
|
||||
var box := BoxShape3D.new()
|
||||
box.size = Vector3(10, 5, 1)
|
||||
cs.shape = box
|
||||
wall.add_child(cs)
|
||||
scene.add_child(wall)
|
||||
wall.global_position = Vector3(0, 1, 8)
|
||||
|
||||
var restore_dur: float = dp.f("roll_duration")
|
||||
dp.set_value("roll_duration", 0.6)
|
||||
player.global_position = Vector3(0, 1, 0)
|
||||
player.cube_guy.rotation.y = 0.0 # face +Z, into the wall
|
||||
player.velocity = Vector3(0, 0, 40)
|
||||
var restore_ramp: float = dp.f("roll_rampup_time")
|
||||
dp.set_value("roll_duration", 1.2)
|
||||
dp.set_value("roll_rampup_time", 0.7)
|
||||
player.global_position = Vector3(-45, 1, -45) # empty void, away from the arena
|
||||
player.velocity = Vector3.ZERO
|
||||
player.cube_guy.rotation.y = 0.0
|
||||
player.ability_cd[3] = 0.0
|
||||
player._activate_roll()
|
||||
_assert_true(player._active_ability == 3, "roll activates in slot 3")
|
||||
if ap != null:
|
||||
_assert_true(ap.current_animation == &"Armature|ROLL", "roll plays the ROLL clip")
|
||||
|
||||
var early := -1.0
|
||||
var peak := 0.0
|
||||
var reflected := false
|
||||
for _n in 25:
|
||||
for n in 30:
|
||||
await create_timer(0.03).timeout
|
||||
peak = maxf(peak, Vector2(player.velocity.x, player.velocity.z).length())
|
||||
if player._roll_dir.z < -0.2:
|
||||
reflected = true
|
||||
_assert_true(reflected, "roll ricochets its heading off the wall")
|
||||
_assert_true(peak > dp.f("roll_max_speed") + 1.0, "wall ricochet boosts speed past roll_max_speed")
|
||||
_assert_true(peak <= dp.f("roll_wall_cap") + 1.0, "ricochet boost stays under the cap")
|
||||
if ap != null:
|
||||
_assert_true(ap.current_animation == &"Armature|IDLE", "roll returns to IDLE when it ends")
|
||||
var spd := Vector2(player.velocity.x, player.velocity.z).length()
|
||||
if n == 1:
|
||||
early = spd
|
||||
peak = maxf(peak, spd)
|
||||
_assert_true(peak > early + 5.0, "roll speed ramps up over time (Powerball)")
|
||||
_assert_true(peak <= dp.f("roll_max_speed") + 2.0, "roll speed caps at roll_max_speed (no wall boost)")
|
||||
|
||||
dp.set_value("roll_duration", restore_dur)
|
||||
wall.queue_free()
|
||||
dp.set_value("roll_rampup_time", restore_ramp)
|
||||
await _check_roll_pop(scene, player)
|
||||
|
||||
|
||||
# Roll into a throwaway matador (not one of the match spawns, so its death doesn't
|
||||
# end the game) and confirm the ball pops: the roll ability ends on contact.
|
||||
func _check_roll_pop(scene: Node, player: Node) -> void:
|
||||
var mat_scene := _mat_scene()
|
||||
if mat_scene == null:
|
||||
_note("roll pop: Matador.tscn missing")
|
||||
return
|
||||
var mat: Node3D = mat_scene.instantiate()
|
||||
scene.add_child(mat)
|
||||
mat.global_position = Vector3(-45, 1, -38) # ~7 m ahead of the bull along +Z
|
||||
await create_timer(0.2).timeout
|
||||
|
||||
player.global_position = Vector3(-45, 1, -45)
|
||||
player.velocity = Vector3.ZERO
|
||||
player.cube_guy.rotation.y = 0.0 # face +Z, toward the matador
|
||||
player.ability_cd[3] = 0.0
|
||||
player._activate_roll()
|
||||
|
||||
var popped := false
|
||||
for _n in 45:
|
||||
await create_timer(0.03).timeout
|
||||
if player._active_ability != 3:
|
||||
popped = true
|
||||
break
|
||||
_assert_true(popped, "roll pops (ends) on ramming a matador")
|
||||
if is_instance_valid(mat):
|
||||
mat.queue_free()
|
||||
|
||||
|
||||
# ── Bull animation ────────────────────────────────────────────────────────────
|
||||
@@ -160,22 +226,31 @@ func _find_anim_player(node: Node) -> AnimationPlayer:
|
||||
return null
|
||||
|
||||
|
||||
# ── Scoreboard wiring ─────────────────────────────────────────────────────────
|
||||
# The ragdoll above is one matador kill; confirm it flowed matador → spawner →
|
||||
# HUD and scored base × combo-1 = 100. Guards the whole kill→score signal chain.
|
||||
# ── Win / lose wiring ─────────────────────────────────────────────────────────
|
||||
# One clean sword hit must end the run in a loss, and the signal chain must be in
|
||||
# place for a win (spawner.all_defeated → HUD). Runs last: it pauses the tree.
|
||||
|
||||
func _check_score_wiring(scene: Node) -> void:
|
||||
print("\n-- check_score_wiring --")
|
||||
func _check_game_over_wiring(scene: Node) -> void:
|
||||
print("\n-- check_game_over_wiring --")
|
||||
var hud: Node = _find_hud(scene)
|
||||
if hud == null:
|
||||
_note("score check: HUD not found in scene")
|
||||
var players := get_nodes_in_group(&"player")
|
||||
if hud == null or players.is_empty():
|
||||
_note("game over check: HUD / player missing")
|
||||
return
|
||||
_assert_true(hud._score == 100, "one kill scores 100 (base × combo 1) — got %d" % hud._score)
|
||||
_assert_true(hud._combo == 1, "one kill sets combo to 1 — got %d" % hud._combo)
|
||||
var player: Node = players[0]
|
||||
_assert_true(player.has_signal(&"died"), "player exposes a died signal")
|
||||
var spawners := get_nodes_in_group(&"matador_spawn")
|
||||
_assert_true(spawners.is_empty() or spawners[0].has_signal(&"all_defeated"),
|
||||
"spawner exposes an all_defeated signal (win route)")
|
||||
|
||||
player.take_sword_hit()
|
||||
await create_timer(0.05).timeout
|
||||
_assert_true(hud._game_over, "a single sword hit raises the game-over screen")
|
||||
_assert_true(not hud._result_win, "a bull hit is a loss, not a win")
|
||||
|
||||
|
||||
func _find_hud(node: Node) -> Node:
|
||||
if node is CanvasLayer and node.has_method(&"_on_matador_killed"):
|
||||
if node is CanvasLayer and node.has_method(&"_show_game_over"):
|
||||
return node
|
||||
for child in node.get_children():
|
||||
var r := _find_hud(child)
|
||||
|
||||
@@ -47,11 +47,23 @@ func _run() -> void:
|
||||
root.add_child(scene_inst)
|
||||
current_scene = scene_inst # match runtime: game code (sword throw, overlays) uses current_scene
|
||||
|
||||
# The matadors now stab the (idle) player; drop the HUD so a resulting player
|
||||
# death doesn't raise the game-over screen and pause the tree mid-measurement.
|
||||
var hud := _find_hud(scene_inst)
|
||||
if hud != null:
|
||||
hud.queue_free()
|
||||
|
||||
await create_timer(WARMUP_SEC).timeout
|
||||
|
||||
var matadors := get_nodes_in_group(&"matador")
|
||||
print(" matadors alive: %d" % matadors.size())
|
||||
|
||||
# The mass-ragdoll below kills every matador; hold the spawner's alive count high
|
||||
# so that doesn't trip the win screen (which pauses the tree) mid-measurement.
|
||||
var spawners := get_nodes_in_group(&"matador_spawn")
|
||||
if not spawners.is_empty():
|
||||
spawners[0]._alive = 100000
|
||||
|
||||
var normal := await _sample_frames(SAMPLE_FRAMES)
|
||||
_report_phase("normal play", normal)
|
||||
|
||||
@@ -99,6 +111,16 @@ func _report_phase(label: String, sample: Array) -> void:
|
||||
"%s: peak %.2f ms under %.0f ms budget" % [label, max_ms, MAX_BUDGET_MS])
|
||||
|
||||
|
||||
func _find_hud(n: Node) -> Node:
|
||||
if n is CanvasLayer and n.has_method(&"_show_game_over"):
|
||||
return n
|
||||
for c: Node in n.get_children():
|
||||
var r := _find_hud(c)
|
||||
if r != null:
|
||||
return r
|
||||
return null
|
||||
|
||||
|
||||
func _assert_true(condition: bool, desc: String) -> void:
|
||||
if condition:
|
||||
_passed += 1
|
||||
|
||||
Reference in New Issue
Block a user