Files
Blockline/tests/arsenal_shield.gd
T

140 lines
5.1 KiB
GDScript

extends SceneTree
var game: Node3D
var a: Fighter
var b: Fighter
var checks := 0
var failures := 0
func _initialize() -> void:
call_deferred("run")
func check(ok: bool, title: String) -> void:
checks += 1
if ok: print("PASS ", title)
else:
failures += 1
printerr("FAIL ", title)
func reset() -> void:
a.reset_at(Vector3(0, 0.1, 28))
b.reset_at(Vector3(0, 0.1, 18))
a.protection = 0
b.protection = 0
a.yaw = 0
a.pitch = -0.045
a.aiming = true
b.yaw = PI
b.team = 1 - a.team
a.set_physics_process(false)
b.set_physics_process(false)
await physics_frame
await physics_frame
func projectiles() -> void:
for frame in 20:
for bullet in game.projectiles.get_children(): bullet._physics_process(0.01)
await process_frame
func run() -> void:
create_timer(40).timeout.connect(func(): quit(1))
game = load("res://scenes/main.tscn").instantiate()
root.add_child(game)
game.port = 27998
var settings: Dictionary = game.config.duplicate()
settings.bots = 0
game.host(settings)
game.set_physics_process(false)
a = game.players[1]
b = game.add_player(-1, "Shield target", true, 0)
await reset()
check(Arsenal.DATA.size() == 10 and Arsenal.PRIMARY_IDS.size() == 9, "ten weapons with nine selectable primaries")
for weapon in Arsenal.PRIMARY_IDS:
await reset()
a.weapon = weapon
game.combat.shoot(a)
await projectiles()
check(a.magazines[weapon] == Arsenal.DATA[weapon].mag - 1, "weapon %s consumes its own magazine" % weapon)
check(b.hp < 100, "weapon %s deals real damage" % weapon)
var model := WeaponModels.build(weapon)
check(model.has_node("Magazine") and model.has_node("Bolt") and model.get_node("Frame").get_child_count() > 0, "weapon %s has a complete animated model" % weapon)
model.free()
await reset()
a.weapon = 3
game.combat.shoot(a)
await projectiles()
check(b.hp == 0, "sniper one-shots an unprotected torso")
await reset()
a.weapon = 5
a.input_data = {"fire": true}
game.combat.actions(a)
a.cooldown = 0
game.combat.actions(a)
check(a.magazines[5] == 14, "semi-auto DMR fires only once while trigger is held")
a.input_data = {"fire": false}
game.combat.actions(a)
a.input_data = {"fire": true}
game.combat.actions(a)
check(a.magazines[5] == 13, "DMR fires again after a fresh trigger press")
await reset()
EnergyShield.activate(b)
check(b.shield_left == 45 and b.shield_charges == 0, "shield activates for 45 seconds and consumes one charge")
b.shield_left = 20
EnergyShield.activate(b)
check(b.shield_left == 20, "shield cannot be refreshed by spamming activation")
for kind in ["AR", "M77", "MELEE", "FRAG", "FRAG / FIRE", "WRECK FIRE"]:
check(not game.combat.damage(b, a, 200, kind, false, a.eye()) and b.hp == 100, "shield blocks frontal %s damage" % kind)
check(game.combat.damage(b, a, 20, "AR", false, b.eye() + Vector3.FORWARD * 3) and b.hp == 80, "rear damage bypasses front shield")
check(game.combat.damage(b, a, 10, "AR", false, b.eye() + Vector3.RIGHT * 3), "side damage bypasses shield")
b.hp = 100
game.combat.flash(a.eye())
check(b.flash_left == 0, "front shield blocks flashbang exposure")
game.combat.flash(b.eye() + Vector3.FORWARD * 2)
check(b.flash_left > 0, "flash behind player remains effective")
game.combat.shoot(a)
check(b.hp == 100, "actual hitscan is intercepted by shield plane")
a.weapon = 3
game.combat.shoot(a)
await projectiles()
check(b.hp == 100 and game.projectiles.get_child_count() == 0, "shield intercepts and removes sniper projectile")
var ray := EnergyShield.clip_ray(game, a, a.eye(), b.eye(), {})
check(ray.has("shield") and ray.position.distance_to(b.eye()) > 0.5, "bullet stops at shield surface before reaching fighter")
var outgoing := EnergyShield.clip_ray(game, b, b.eye(), a.eye(), {})
check(outgoing.is_empty(), "shield does not block its owner's outgoing shots")
b.shield_left = 0.01
b.input_age = 0
b.input_data = {}
b.bot = false
b._physics_process(0.02)
check(b.shield_left == 0, "shield expires on authoritative physics clock")
check(game.combat.damage(b, a, 10, "AR", false, a.eye()), "damage resumes after expiration")
b.reset_at(b.position)
check(b.shield_left == 0 and b.shield_charges == 1, "respawn resets active field and restores one charge")
b.shield_left = 31
a.apply_snapshot(b.snapshot())
check(a.shield_left == 31, "shield remaining duration transfers in snapshots")
await reset()
a.weapon = 0
var origin := ShotOrigin.muzzle(a)
check(origin.y > a.position.y + 1.3 and origin.z < a.position.z - 0.5, "shot origin is at raised weapon rather than pelvis")
var wall := StaticBody3D.new()
wall.collision_layer = 1
var shape := CollisionShape3D.new()
var box := BoxShape3D.new()
box.size = Vector3(2, 3, 0.1)
shape.shape = box
wall.add_child(shape)
game.add_child(wall)
wall.position = a.position + Vector3(0, 1.4, -0.3)
await physics_frame
await physics_frame
check(ShotOrigin.path(a, Arsenal.direction(a.yaw, a.pitch), 70).blocked, "wall between camera and muzzle prevents barrel-through-wall shooting")
game.combat.shoot(a)
check(b.hp == 100, "barrel obstruction prevents damage behind wall")
wall.queue_free()
game.leave()
game.queue_free()
await process_frame
print("ARSENAL SHIELD RESULT ", checks - failures, "/", checks)
quit(1 if failures else 0)