extends SceneTree ## A taunting matador must answer a charge instead of posing through it: hurl a spear at a bull ## barrelling in from range, or switch to the sword (brace/cite) to meet a closer rush. Drives ## _tick_wander on a matador mid-taunt (_idle_timer > 0) against a bull charging straight at it and ## asserts the state it breaks into by range; a slow / non-aimed bull leaves it taunting. ## Run: godot --headless --script res://tests/matador_taunt_react_test.gd var _pass := 0 var _fail := 0 func _init() -> void: _run.call_deferred() func _ok(c: bool, m: String) -> void: if c: _pass += 1 print(" PASS: ", m) else: _fail += 1 print(" FAIL: ", m) func _make_bull() -> CharacterBody3D: var b := CharacterBody3D.new() b.collision_layer = 2 b.collision_mask = 0 b.add_to_group(&"player") var cs := CollisionShape3D.new() var shape := CapsuleShape3D.new() shape.radius = 0.75 shape.height = 1.845 cs.shape = shape b.add_child(cs) root.add_child(b) return b func _make_taunting_matador(_bull: CharacterBody3D) -> CharacterBody3D: var mat := (load("res://Matador.tscn") as PackedScene).instantiate() as CharacterBody3D root.add_child(mat) mat.global_position = Vector3.ZERO mat.set_physics_process(false) # we drive _tick_wander ourselves, no brain await physics_frame mat.call("_acquire_bull") mat.set("_idle_timer", 5.0) # mid-taunt: standing and posturing mat.set("_throw_aim_cd", 0.0) # spear ready return mat # Park the bull at `dist` straight ahead (+Z) of the matador, charging back toward it at `speed`. func _charge_from(bull: CharacterBody3D, dist: float, speed: float) -> void: bull.global_position = Vector3(0.0, 0.0, dist) bull.velocity = Vector3(0.0, 0.0, -speed) func _run() -> void: var bull := _make_bull() # (a) Far charge (10 m, past brace range but within throw range) → get the spear off. var mat_a: CharacterBody3D = await _make_taunting_matador(bull) _charge_from(bull, 10.0, 30.0) mat_a.call("_tick_wander", 1.0 / 60.0) _ok(mat_a.call("ai_state_name") == "THROW", "a taunting matador hurls a spear at a charge from range") # (b) Closer rush (3 m, inside brace range) → switch to the sword and cite it. var mat_b: CharacterBody3D = await _make_taunting_matador(bull) _charge_from(bull, 3.0, 30.0) mat_b.call("_tick_wander", 1.0 / 60.0) _ok(mat_b.call("ai_state_name") == "BRACE", "a taunting matador switches to the sword for a closer rush") # (c) A slow bull (below charge speed) isn't a charge — keep taunting. var mat_c: CharacterBody3D = await _make_taunting_matador(bull) _charge_from(bull, 10.0, 3.0) mat_c.call("_tick_wander", 1.0 / 60.0) _ok(mat_c.call("ai_state_name") == "WANDER", "a taunting matador ignores a slow (non-charging) bull") # (d) A fast bull aimed AWAY from the matador isn't charging at it — keep taunting. var mat_d: CharacterBody3D = await _make_taunting_matador(bull) bull.global_position = Vector3(0.0, 0.0, 10.0) bull.velocity = Vector3(0.0, 0.0, 30.0) # heading further away, not at the matador mat_d.call("_tick_wander", 1.0 / 60.0) _ok(mat_d.call("ai_state_name") == "WANDER", "a taunting matador ignores a charge not aimed at it") _finish() func _finish() -> void: print("Results: %d passed, %d failed" % [_pass, _fail]) quit(1 if _fail > 0 else 0)