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()