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
161 lines
6.2 KiB
GDScript
161 lines
6.2 KiB
GDScript
extends RefCounted
|
|
|
|
## Bomb system tests — constants, state transitions, timing logic.
|
|
##
|
|
## Tests the pure constant checking and state machine logic of the
|
|
## Bomb system. Full plant/defuse timing requires NetworkTime.tick,
|
|
## so those are tested via integration tests instead.
|
|
|
|
# BombState enum values (mirrors Bomb.BombState)
|
|
enum BombState { IDLE = 0, CARRIED = 1, PLANTING = 2, DROPPED = 3, PLANTED = 4, DEFUSING = 5, EXPLODED = 6, DEFUSED = 7 }
|
|
|
|
# Default timing constants (mirrors Bomb @export defaults)
|
|
const PLANT_TIME := 3.0
|
|
const DEFUSE_TIME := 10.0
|
|
const DEFUSE_KIT_TIME := 5.0
|
|
const BOMB_TIMER := 40.0
|
|
|
|
func test_bomb_state_enum_values() -> String:
|
|
var e := ""
|
|
if int(BombState.IDLE) != 0: e += " IDLE should be 0"
|
|
if int(BombState.CARRIED) != 1: e += " CARRIED should be 1"
|
|
if int(BombState.PLANTING) != 2: e += " PLANTING should be 2"
|
|
if int(BombState.DROPPED) != 3: e += " DROPPED should be 3"
|
|
if int(BombState.PLANTED) != 4: e += " PLANTED should be 4"
|
|
if int(BombState.DEFUSING) != 5: e += " DEFUSING should be 5"
|
|
if int(BombState.EXPLODED) != 6: e += " EXPLODED should be 6"
|
|
if int(BombState.DEFUSED) != 7: e += " DEFUSED should be 7"
|
|
return e
|
|
|
|
func test_plant_time_constant() -> String:
|
|
if PLANT_TIME != 3.0:
|
|
return "Plant time should be 3.0s, got %.1f" % PLANT_TIME
|
|
return ""
|
|
|
|
func test_defuse_time_constant() -> String:
|
|
if DEFUSE_TIME != 10.0:
|
|
return "Default defuse time should be 10.0s, got %.1f" % DEFUSE_TIME
|
|
return ""
|
|
|
|
func test_defuse_kit_time_constant() -> String:
|
|
if DEFUSE_KIT_TIME != 5.0:
|
|
return "Defuse kit time should be 5.0s, got %.1f" % DEFUSE_KIT_TIME
|
|
return ""
|
|
|
|
func test_bomb_timer_constant() -> String:
|
|
if BOMB_TIMER != 40.0:
|
|
return "Bomb timer should be 40.0s, got %.1f" % BOMB_TIMER
|
|
return ""
|
|
|
|
func test_kit_defuse_is_half_price() -> String:
|
|
# Defuse kit halves the time: 10.0 → 5.0
|
|
if DEFUSE_KIT_TIME * 2 != DEFUSE_TIME:
|
|
return "Defuse kit time (%.1f) should be half of default (%.1f)" % [DEFUSE_KIT_TIME, DEFUSE_TIME]
|
|
return ""
|
|
|
|
func test_state_transition_carrier_can_drop() -> String:
|
|
# Carrier drop: CARRIED → DROPPED (also PLANTING → DROPPED)
|
|
var valid_drop_states := [BombState.CARRIED, BombState.PLANTING]
|
|
var e := ""
|
|
if not (BombState.CARRIED in valid_drop_states):
|
|
e += " CARRIED should be droppable"
|
|
if not (BombState.PLANTING in valid_drop_states):
|
|
e += " PLANTING should be droppable"
|
|
if BombState.PLANTED in valid_drop_states:
|
|
e += " PLANTED should NOT be droppable"
|
|
return e
|
|
|
|
func test_can_only_plant_from_carried() -> String:
|
|
# try_start_plant checks: state == CARRIED
|
|
var valid_plant_states := [BombState.CARRIED]
|
|
var e := ""
|
|
if not (BombState.CARRIED in valid_plant_states):
|
|
e += " CARRIED should be plantable"
|
|
if BombState.DROPPED in valid_plant_states:
|
|
e += " DROPPED should NOT be plantable"
|
|
if BombState.IDLE in valid_plant_states:
|
|
e += " IDLE should NOT be plantable"
|
|
return e
|
|
|
|
func test_can_only_defuse_from_planted() -> String:
|
|
# try_start_defuse checks: state == PLANTED
|
|
var valid_defuse_states := [BombState.PLANTED]
|
|
var e := ""
|
|
if not (BombState.PLANTED in valid_defuse_states):
|
|
e += " PLANTED should be defusable"
|
|
if BombState.DEFUSING in valid_defuse_states:
|
|
e += " DEFUSING should NOT be defusable (already defusing)"
|
|
return e
|
|
|
|
func test_can_only_pickup_dropped() -> String:
|
|
# try_pickup checks: state == DROPPED
|
|
var valid_pickup_states := [BombState.DROPPED]
|
|
var e := ""
|
|
if not (BombState.DROPPED in valid_pickup_states):
|
|
e += " DROPPED should be pickup-able"
|
|
return e
|
|
|
|
func test_bomb_timer_37_seconds_minimum() -> String:
|
|
# Even with bomb_timer=40s, once planted there should be at least
|
|
# 37 seconds of gameplay before explosion (plant takes 3s)
|
|
# Total: 40s from plant start, 3s to plant → 37s after planted
|
|
var remaining_after_plant := BOMB_TIMER - PLANT_TIME
|
|
if remaining_after_plant < 37.0:
|
|
return "After planting, bomb should tick for at least 37s, got %.1f" % remaining_after_plant
|
|
return ""
|
|
|
|
func test_defuse_within_bomb_timer() -> String:
|
|
# Must be able to defuse before bomb explodes
|
|
# Default defuse (10s) + plant (3s) = 13s out of 40s — tight but doable
|
|
if DEFUSE_TIME + PLANT_TIME >= BOMB_TIMER:
|
|
return "Defuse+plant time (%.1f+%.1f=%.1f) exceeds bomb timer (%.1f)! Impossible to defuse!" % [DEFUSE_TIME, PLANT_TIME, DEFUSE_TIME + PLANT_TIME, BOMB_TIMER]
|
|
return ""
|
|
|
|
func test_kit_defuse_within_bomb_timer() -> String:
|
|
# With kit: 5s defuse + 3s plant = 8s out of 40s — comfortable
|
|
if DEFUSE_KIT_TIME + PLANT_TIME >= BOMB_TIMER:
|
|
return "Kit defuse+plant time (%.1f+%.1f=%.1f) exceeds bomb timer!" % [DEFUSE_KIT_TIME, PLANT_TIME, DEFUSE_KIT_TIME + PLANT_TIME]
|
|
return ""
|
|
|
|
func test_state_order_is_linear() -> String:
|
|
# Verify state numbering follows logical gameplay order
|
|
var e := ""
|
|
if BombState.IDLE >= BombState.CARRIED: e += " IDLE < CARRIED"
|
|
if BombState.CARRIED >= BombState.PLANTING: e += " CARRIED < PLANTING"
|
|
if BombState.PLANTING >= BombState.PLANTED: e += " PLANTING < PLANTED"
|
|
if BombState.PLANTED >= BombState.EXPLODED: e += " PLANTED < EXPLODED"
|
|
if BombState.PLANTED >= BombState.DEFUSED: e += " PLANTED < DEFUSED"
|
|
return e
|
|
|
|
func test_reset_clears_all_state() -> String:
|
|
# reset() sets: IDLE, carrier=-1, position=ZERO, ...
|
|
var state := BombState.EXPLODED
|
|
var carrier_id := 5
|
|
var position := Vector3(10, 0, 20)
|
|
var planted_site := "A"
|
|
var planted_tick := 100
|
|
var plant_start := 50
|
|
var defuse_start := -1
|
|
var defuser_id := -1
|
|
|
|
# reset()
|
|
state = BombState.IDLE
|
|
carrier_id = -1
|
|
position = Vector3.ZERO
|
|
planted_site = ""
|
|
planted_tick = -1
|
|
plant_start = -1
|
|
defuse_start = -1
|
|
defuser_id = -1
|
|
|
|
var e := ""
|
|
if state != BombState.IDLE: e += " state should be IDLE"
|
|
if carrier_id != -1: e += " carrier_id should be -1"
|
|
if position != Vector3.ZERO: e += " position should be ZERO"
|
|
if planted_site != "": e += " planted_site should be empty"
|
|
if planted_tick != -1: e += " planted_tick should be -1"
|
|
if plant_start != -1: e += " _plant_start_tick should be -1"
|
|
if defuse_start != -1: e += " _defuse_start_tick should be -1"
|
|
if defuser_id != -1: e += " _defuser_id should be -1"
|
|
return e
|