Randoloot/tests/abilities/tactical_extensions_test.gd
2026-10-18 14:46:34 +02:00

167 lines
7.6 KiB
GDScript

extends Node2D
const PLAYER := preload("res://scenes/game/player_ship.tscn")
const ENEMY := preload("res://scenes/game/enemy_ship.tscn")
const GENERATOR := preload("res://scripts/items/item_generator.gd")
const IDS := ["teleport", "cloak", "mine_layer", "flares", "solar_shield_charge"]
var ship: CharacterBody2D
var inventory: ShipInventory
func equip(id: String, slot: int) -> void:
var item := GENERATOR.generate_for_definition(load("res://resources/items/%s.tres" % id), 1)
assert(item.additional_modifiers.is_empty())
assert(item.definition.icon != null)
assert(inventory.add_item(item))
assert(inventory.move_item("cargo", inventory.cargo.find(item), "extension", slot))
func _ready() -> void:
GameState.start_new_run()
ship = PLAYER.instantiate() as CharacterBody2D
ship.name = "PlayerShip"
add_child(ship)
ship.set_physics_process(false)
ship.get_node("WeaponController").set_physics_process(false)
var systems := ship.get_node("ExtensionSystems")
systems.set_physics_process(false)
inventory = ship.get_node("ShipInventory") as ShipInventory
inventory.unlocked_extension_slots = ShipInventory.EXTENSION_SLOT_COUNT # Full-loadout ability fixture.
inventory.unlocked_weapon_slots = ShipInventory.WEAPON_SLOT_COUNT
for index in IDS.size():
equip(IDS[index], index)
var enemy := ENEMY.instantiate() as CharacterBody2D
enemy.position = Vector2(300, 0)
add_child(enemy)
enemy.set("_player", ship)
enemy.set_physics_process(false)
await get_tree().physics_frame
await get_tree().process_frame
# Teleport: partial energy is fully consumed; landing stays inside the playable area.
var origin := ship.position
ship.set("energy", 37.0)
assert(ship.call("activate_ability", 0))
assert(ship.position.distance_to(origin) >= 400.0)
assert(absf(ship.position.x) <= 2440 and absf(ship.position.y) <= 1640)
assert(ship.get("energy") == 0.0)
assert(ship.call("get_ability_cooldown", 0) == 60.0)
assert(not ship.call("activate_ability", 0))
# Moving a module or equipping a duplicate cannot bypass its cooldown.
assert(inventory.move_item("extension", 0, "extension", 1))
assert(ship.call("get_ability_cooldown", 1) == 60.0)
equip("teleport", 4)
assert(ship.call("get_ability_cooldown", 4) == 60.0)
assert(not ship.call("activate_ability", 4))
equip("solar_shield_charge", 4)
assert(inventory.move_item("extension", 1, "extension", 0))
ship.position = origin
enemy.position = origin + Vector2(300, 0)
assert(enemy.call("select_combat_target") == ship)
# Cloak removes targeting, lasts ten seconds and restores the original artwork.
var original_material: Material = ship.get_node("Sprite2D").material
assert(ship.call("activate_ability", 1))
assert(ship.call("is_cloaked"))
assert(enemy.call("select_combat_target") == null)
assert(ship.get_node("Sprite2D").material != original_material)
assert(not ship.call("activate_ability", 1))
# Flares attract enemies even while the player is invisible, and expire in ten seconds.
assert(ship.call("activate_ability", 3))
assert(ship.call("get_ability_cooldown", 3) == 30.0)
var flares := get_tree().get_nodes_in_group("player_flares")
assert(flares.size() == 3)
assert(flares.has(enemy.call("select_combat_target")))
assert(not ship.call("activate_ability", 3))
for flare in flares:
flare.set_physics_process(false)
flare.call("_physics_process", 10.0)
assert(flare.is_queued_for_deletion())
assert(enemy.call("select_combat_target") == null)
systems.call("_physics_process", 9.9)
assert(ship.call("is_cloaked"))
systems.call("_physics_process", 0.11)
assert(not ship.call("is_cloaked"))
assert(ship.get_node("Sprite2D").material == original_material)
assert(enemy.call("select_combat_target") == ship)
assert(not ship.call("activate_ability", 1))
# Mine: 5% maximum hull damage despite armor; slow lasts five seconds; one hit only.
var weapons := ship.get_node("WeaponController")
for weapon_id in ["plasma_cannon", "machine_gun", "gamma_cannon", "missile_launcher", "beam_laser"]:
systems.get("cooldowns").clear()
assert(ship.call("activate_ability", 1))
var weapon := GENERATOR.generate_for_definition(load("res://resources/items/%s.tres" % weapon_id), 1)
if weapon_id == "beam_laser":
ship.set("energy", 0.0)
weapons.call("_fire_laser", 0, weapon)
assert(ship.call("is_cloaked"), "Failed shot must preserve cloak")
ship.set("energy", ship.get("max_energy"))
weapons.call("_fire_laser", 0, weapon)
elif weapon_id == "missile_launcher":
inventory.weapon_slots[2] = weapon
inventory.missile_ammunition = 0
assert(not weapons.call("try_fire_missile", 2))
assert(ship.call("is_cloaked"), "Empty launcher must preserve cloak")
inventory.missile_ammunition = 1
assert(weapons.call("try_fire_missile", 2))
else:
weapons.call("_fire_projectile", weapon)
assert(not ship.call("is_cloaked"), "Every fired weapon must break cloak")
assert(ship.get_node("Sprite2D").material == original_material)
assert(ship.call("get_ability_cooldown", 1) == 60.0)
assert(enemy.call("select_combat_target") == ship)
assert(ship.call("activate_ability", 2))
var mine: Node2D = get_tree().get_nodes_in_group("player_mines")[0]
mine.set_physics_process(false)
enemy.position = mine.position
enemy.set("shields", 0.0)
enemy.set("absorption", Vector3(75, 0, 0))
var maximum := float(enemy.get("max_hull"))
mine.call("_physics_process", 0.8)
assert(mine.is_queued_for_deletion())
assert(is_equal_approx(float(enemy.get("hull")), maximum * 0.95))
mine.call("_physics_process", 0.1)
assert(is_equal_approx(float(enemy.get("hull")), maximum * 0.95))
assert(enemy.call("get_slow_multiplier") == 0.5)
enemy.set("_player", null)
enemy.call("_physics_process", 5.1)
assert(enemy.call("get_slow_multiplier") == 1.0)
# Solar shields: blocked away from stars and at full shields; fills in five seconds.
ship.set("shields", 0.0)
assert(not ship.call("activate_ability", 4))
var star := Node2D.new()
star.position = ship.position
add_child(star)
ship.set("charging_star", star)
assert(ship.call("activate_ability", 4))
assert(ship.call("get_ability_cooldown", 4) == 30.0)
systems.call("_physics_process", 2.5)
assert(is_equal_approx(float(ship.get("shields")), float(ship.get("max_shields")) * 0.5))
systems.call("_physics_process", 2.5)
assert(is_equal_approx(float(ship.get("shields")), float(ship.get("max_shields"))))
systems.get("cooldowns").clear()
assert(not ship.call("activate_ability", 4))
ship.set("shields", 0.0)
assert(ship.call("activate_ability", 4))
ship.position += Vector2(1000, 0)
systems.call("_physics_process", 1.0)
assert(ship.get("shields") == 0.0 and systems.get("solar_remaining") == 0.0)
# Docking, defeat and pause prevent activation; expired cooldowns allow reuse.
systems.call("_physics_process", 61.0)
ship.set("is_docked", true)
assert(not ship.call("activate_ability", 3))
ship.set("is_docked", false)
get_tree().paused = true
assert(not ship.call("activate_ability", 3))
get_tree().paused = false
ship.set("_is_destroyed", true)
assert(not ship.call("activate_ability", 3))
ship.set("_is_destroyed", false)
assert(ship.call("activate_ability", 3))
inventory.preserve_for_restart()
assert(not GameState.restart_inventory.has("extension_cooldowns"))
var restored := PLAYER.instantiate() as CharacterBody2D
add_child(restored)
restored.set_physics_process(false)
restored.get_node("WeaponController").set_physics_process(false)
assert(restored.call("get_ability_cooldown", 3) == 0.0)
assert(restored.call("activate_ability", 3))
print("TACTICAL_EXTENSIONS_TEST: PASS (five modules, icons, loot, teleport safety/energy, cloak duration/AI/restoration, flares targeting/lifetime, mine damage/slow/single hit, solar recharge/range, shared cooldowns, pause/dock/defeat, cooldown reset on reload)")
get_tree().quit()