Files
shawn 012d038025 Integrate Kenney Starter Kit FPS assets + comprehensive test suite
Assets (CC0 license):
- 3D models: Blaster gun, walls, platforms, grass, clouds, enemy
- Sounds: blaster fire, impacts, footsteps, jumps, weapon change
- Sprites: crosshair, hit marker, muzzle flash, skybox, blob shadow
- Font: Lilita One

Code changes:
- weapon_manager.gd: load blaster.glb as gun model (replaces box mesh)
- player.tscn: use Kenney sounds (blaster.ogg, enemy_hurt/destroy) and crosshair

Test suite (100 unit tests + 4 integration scenarios):
- weapon_data.gd: 27 tests — all 6 weapons every stat verified
- economy.gd: 19 tests — constants, loss streak, buy thresholds
- bomb.gd: 15 tests — state machine, timing constants
- round_manager.gd: 10 tests — win conditions, elimination logic
- team_manager.gd: 8 tests — auto-balance, team assignment
- headless_test_bot.gd: integration bot with movement/idle/rounds scenarios
- run_multi_bot.sh: multi-client launcher
2026-07-03 17:13:36 -04:00

177 lines
5.2 KiB
GDScript

extends RefCounted
## Round manager tests — state machine logic, constants, win conditions.
##
## Tests pure logic functions and constants. The state machine itself
## requires NetworkTime.tick and a multiplayer peer for server checks,
## so we test the isolated decision functions here.
const ROUNDS_TO_WIN := 13
const MAX_ROUNDS := 24
# RoundState enum values (mirrors RoundManager.RoundState)
enum RoundState { WARMUP = 0, FREEZE_TIME = 1, ACTIVE = 2, ROUND_END = 3 }
func test_round_state_enum_values() -> String:
var e := ""
var w = RoundState.WARMUP
if w != 0: e += " WARMUP should be 0"
if int(RoundState.FREEZE_TIME) != 1: e += " FREEZE_TIME should be 1"
if int(RoundState.ACTIVE) != 2: e += " ACTIVE should be 2"
if int(RoundState.ROUND_END) != 3: e += " ROUND_END should be 3"
return e
func test_check_match_over_by_score() -> String:
# _check_match_over() returns true if any team reaches rounds_to_win
var e := ""
# Scenario: T has 13, CT has 5 → match over
var t_score := ROUNDS_TO_WIN
var ct_score := 5
if not (t_score >= ROUNDS_TO_WIN):
e += " T should win at %d rounds" % ROUNDS_TO_WIN
# Scenario: CT has 13, T has 7 → match over
t_score = 7
ct_score = ROUNDS_TO_WIN
if not (ct_score >= ROUNDS_TO_WIN):
e += " CT should win at %d rounds" % ROUNDS_TO_WIN
# Scenario: Both below threshold → match not over
t_score = 8
ct_score = 9
if (t_score >= ROUNDS_TO_WIN) or (ct_score >= ROUNDS_TO_WIN):
e += " Neither team should have won yet"
return e
func test_check_match_over_by_round_limit() -> String:
# If round_number >= max_rounds → match over
var e := ""
# Round 24 of 24 → over
var round_num := MAX_ROUNDS
if not (round_num >= MAX_ROUNDS):
e += " Round %d should end the match" % MAX_ROUNDS
# Round 12 of 24 → not over
round_num = 12
if round_num >= MAX_ROUNDS:
e += " Round 12 should not end the match"
return e
func test_check_match_over_8_8_tie() -> String:
# 8-8 at round 16 → match not over (no one hit 13)
var t_score := 8
var ct_score := 8
var round_num := 16
var over := (t_score >= ROUNDS_TO_WIN) or (ct_score >= ROUNDS_TO_WIN) or (round_num >= MAX_ROUNDS)
if over:
return "8-8 at round 16 should not end the match"
return ""
func test_check_match_over_12_12_overtime_possible() -> String:
# 12-12 at round 24 → match over (max_rounds reached)
var t_score := 12
var ct_score := 12
var round_num := 24
var over := (t_score >= ROUNDS_TO_WIN) or (ct_score >= ROUNDS_TO_WIN) or (round_num >= MAX_ROUNDS)
if not over:
return "12-12 at round 24 should end the match (max rounds)"
return ""
func test_count_alive_basic() -> String:
# Simulate alive tracking
var alive := { 101: true, 102: true, 103: false, 104: true }
var team_assignments := { 101: 0, 102: 0, 103: 0, 104: 1 }
var t_count := 0
var ct_count := 0
for pid in team_assignments:
var is_alive = alive.get(pid, false)
if is_alive:
if team_assignments[pid] == 0: # T
t_count += 1
elif team_assignments[pid] == 1: # CT
ct_count += 1
var e := ""
if t_count != 2: e += " T should have 2 alive, got %d" % t_count
if ct_count != 1: e += " CT should have 1 alive, got %d" % ct_count
return e
func test_count_alive_all_dead() -> String:
var alive := { 101: false, 102: false }
var team_assignments := { 101: 0, 102: 1 }
var t_count := 0
var ct_count := 0
for pid in team_assignments:
var is_alive = alive.get(pid, false)
if is_alive:
if team_assignments[pid] == 0:
t_count += 1
elif team_assignments[pid] == 1:
ct_count += 1
var e := ""
if t_count != 0: e += " T alive should be 0"
if ct_count != 0: e += " CT alive should be 0"
return e
func test_elimination_win() -> String:
# CT all dead → T wins by elimination
var alive := { 101: true, 102: true, 103: false, 104: false }
var team_assignments := { 101: 0, 102: 0, 103: 1, 104: 1 }
var t_alive := 0
var ct_alive := 0
for pid in team_assignments:
var is_alive = alive.get(pid, false)
if is_alive:
if team_assignments[pid] == 0: t_alive += 1
elif team_assignments[pid] == 1: ct_alive += 1
# T wins if CT alive == 0
if ct_alive != 0:
return "CT should be eliminated"
if t_alive <= 0:
return "T should have survivors"
return ""
func test_bomb_prevents_elimination_win() -> String:
# All Ts dead BUT bomb is planted → round continues (T can still win via explosion)
var alive := { 101: false, 102: false, 103: true, 104: true }
var team_assignments := { 101: 0, 102: 0, 103: 1, 104: 1 }
var bomb_planted := true
var t_alive := 0
var ct_alive := 0
for pid in team_assignments:
var is_alive = alive.get(pid, false)
if is_alive:
if team_assignments[pid] == 0: t_alive += 1
elif team_assignments[pid] == 1: ct_alive += 1
# Logic from round-manager.gd:108
var should_end := false
if ct_alive == 0:
should_end = true # T wins
elif t_alive == 0 and not bomb_planted:
should_end = true # CT wins
if should_end:
return "Round should NOT end — bomb is planted"
return ""
func test_timeout_win() -> String:
# Bomb not planted, time runs out → CT win
var bomb_planted := false
var time_expired := true
if time_expired and not bomb_planted:
# CT wins
return "" # pass
return "CT should win on timeout if bomb not planted"