012d038025
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
177 lines
5.2 KiB
GDScript
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"
|