Randoloot/tests/distant_integration.gd
2026-10-25 22:38:13 +01:00

176 lines
9.7 KiB
GDScript

extends Node
var is_runner := false
var failures: Array[String] = []
const GENERATOR := preload("res://scripts/levels/distant_sector_generator.gd")
const ORBS := preload("res://scripts/items/special_orbs.gd")
const LOOT := preload("res://scripts/game/distant_loot.gd")
func _ready() -> void:
if not is_runner:
var runner = get_script().new()
runner.is_runner = true
get_tree().root.add_child.call_deferred(runner)
else:
_run.call_deferred()
func check(value: bool, message: String) -> void:
if not value: failures.append(message); push_error("DISTANT_TEST: "+message)
func frames(count: int = 4) -> void:
for index in count: await get_tree().process_frame
func freeze_game(game: Node) -> void:
game.get_node("PlayerShip").set_physics_process(false)
for enemy in get_tree().get_nodes_in_group("enemies"): enemy.set_physics_process(false)
func _run() -> void:
var map_checks := preload("res://tests/distant_expeditions_checks.gd").new()
var map_report: Dictionary = map_checks.run_checks()
for failure in map_report.failures: check(false, str(failure))
map_checks.free()
for level in [1,60,120,125,500]:
var ordinary := preload("res://scripts/levels/level_balance.gd").enemy_tiers(level)
var tiers := GENERATOR.enemy_tiers(level)
check(tiers.count(2)==1,"One boss")
check(tiers.count(0)==ordinary.count(0)*2,"Double normals")
check(tiers.count(1)==ordinary.count(1)*2,"Double elites")
check(preload("res://scripts/levels/level_balance.gd").enemy_stat_scale(125)>preload("res://scripts/levels/level_balance.gd").enemy_stat_scale(120),"Stats beyond 120")
check(LOOT.roll(125,false,2,0.149,0.0).size()==1,"Map 15 percent")
check(LOOT.roll(125,false,2,0.15,0.0).is_empty(),"Map boundary and no regular orb")
for seed_value in 100:
for chance in [0.0,0.15,1.0]:
var boss_drops := LOOT.roll(125,true,seed_value,chance,1.0)
check(boss_drops.size()==2 and boss_drops[0].has("map") and boss_drops[1].has("orb"),"Exactly one guaranteed boss map and orb")
var next_map: Dictionary = LOOT.roll(125,false,2,0.0,1.0)[0].map
check(int(next_map.level)>125 and int(next_map.level)<=130,"Map chain")
GameState.start_new_run()
GameState.system_level = 7
GameState.system_seed = 777
GameState.remember_system()
GameState.captain.expedition.distant_maps["integration"] = {"id":"integration","level":125,"seed":123456,"name":"Sektor testowy","source_planet":"Test","source_level":120}
GameState.captain.expedition.deuter = 124999
check(not GameState.depart_distant("integration"),"No flight without fuel")
check(GameState.system_level==7 and GameState.captain.expedition.deuter==124999,"Failed departure preserved state")
GameState.captain.expedition.deuter = 125000
get_tree().change_scene_to_file("res://scenes/game/game.tscn")
await frames()
var game := get_tree().current_scene
freeze_game(game)
var player := game.get_node("PlayerShip")
var inventory := player.get_node("ShipInventory")
player.is_docked = true
var weapon: ItemInstance = inventory.weapon_slots[0]
var engine := ItemInstance.new()
engine.definition = preload("res://resources/items/engine_extension.tres")
inventory.module_slots[2] = engine
var progress: Resource = GameState.captain.expedition
progress.orbs = {"piercing":2,"booster":1}
check(not ORBS.apply(progress,inventory,engine,"piercing"),"Piercing category restriction")
check(not ORBS.apply(progress,inventory,weapon,"booster"),"Booster category restriction")
check(ORBS.apply(progress,inventory,weapon,"piercing"),"Apply piercing")
check(not ORBS.apply(progress,inventory,weapon,"piercing") and progress.orbs.piercing==1,"No stacking or extra cost")
check(ORBS.apply(progress,inventory,engine,"booster"),"Apply booster")
check(player.has_free_mini_boost(),"Equipped engine")
Input.action_press("ship_mini_boost")
player.energy = 0
player._update_mini_boost(1.0)
check(player._mini_boost_active and player.energy==0,"Free TAB with empty generator")
Input.action_release("ship_mini_boost")
inventory.module_slots[2] = null
check(not player.has_free_mini_boost(),"Unequipped engine no effect")
inventory.module_slots[2] = engine
check(game.travel_to_distant("integration"),"Flight requested")
await frames(3)
check(game.has_node("FreighterWarpDeparture") and get_tree().paused,"Departure cinematic freezes combat")
check(not game.travel_to_distant("integration"),"Cannot pay again during warp")
var warp_deadline := Time.get_ticks_msec()+7000
while get_tree().current_scene == game and Time.get_ticks_msec()<warp_deadline: await get_tree().process_frame
check(get_tree().current_scene != game and not get_tree().paused,"Warp completes and resumes gameplay")
await frames(8)
game = get_tree().current_scene
freeze_game(game)
check(GameState.captain.expedition.deuter==0,"Exactly one fuel payment")
check(not GameState.captain.expedition.distant_maps.has("integration"),"Map consumed on departure")
check(GameState.system_level==125 and GameState.highest_system==7,"Isolated progression")
check(game.get_node_or_null("CelestialBodies")==null,"No celestial bodies")
check(game.has_node("PlanetExpeditions") and game.has_node("Freighter"),"Freighter available in expedition")
check(game.get_node("HUD/HUDRoot").find_child("DistantStatusPanel",true,false)==null,"No redundant expedition status window")
check(game.get_node("HUD/HUDRoot").find_child("ReturnFromDistant",true,false)==null,"No return button in flight HUD")
check(not game.get_node("DistantSector")._return_home(),"Return requires docking")
check(game.get_node("AncientStructures").get_child_count()==14,"Decorative structures")
check(game.get_remaining_enemies()==46 and not game._boss_spawned,"Doubled encounter before boss")
player = game.get_node("PlayerShip")
inventory = player.get_node("ShipInventory")
player.is_docked = true
check("piercing" in inventory.weapon_slots[0].special_orbs,"Orb persisted across travel")
check(player.has_free_mini_boost(),"Engine orb persisted")
var enemy: Node = get_tree().get_first_node_in_group("enemies")
enemy.absorption = Vector3.ZERO
enemy.shields = 100
enemy.hull = 1000
enemy.take_piercing_weapon_hit(100,false,0,0.5)
check(is_equal_approx(enemy.shields,50) and is_equal_approx(enemy.hull,950),"50 percent shield bypass")
enemy.shields = 100
enemy.hull = 1000
enemy.take_weapon_hit(100,false,0)
check(is_equal_approx(enemy.shields,0) and is_equal_approx(enemy.hull,1000),"Other weapons unchanged")
enemy.shields = 10
enemy.hull = 1000
enemy.take_piercing_weapon_hit(100,false,0,0.5)
check(is_equal_approx(enemy.shields,0) and is_equal_approx(enemy.hull,910),"Shield overflow")
enemy.shields = 100
enemy.hull = 1000
player.get_node("WeaponController")._apply_damage(enemy,100,false,0.5)
check(is_equal_approx(enemy.hull,950),"Laser piercing")
enemy.shields = 100
enemy.hull = 1000
var projectile := preload("res://scenes/game/weapon_projectile.tscn").instantiate()
projectile.damage = 100
projectile.shield_piercing = 0.5
projectile.weapon_kind = ItemDefinition.WeaponKind.PLASMA
projectile._apply_damage_once(enemy)
check(is_equal_approx(enemy.hull,950),"Projectile piercing")
projectile.free()
var payload := Node2D.new()
payload.set_script(preload("res://scripts/items/expedition_pickup.gd"))
payload.payload = {"orb":"booster"}
payload.position = player.position
game.add_child(payload)
var count: int = preload("res://scripts/items/special_orbs.gd").count(progress,inventory,"booster")
payload._physics_process(0)
check(preload("res://scripts/items/special_orbs.gd").count(progress,inventory,"booster")==count+1,"Collect orb into cargo")
await frames()
for victim in get_tree().get_nodes_in_group("enemies"): victim.queue_free()
await frames()
game.get_node("DistantSector")._physics_process(0.3)
await frames()
check(game._boss_spawned and game.get_remaining_enemies()==1,"Exactly one boss after wave")
var boss: Node = get_tree().get_first_node_in_group("enemies")
boss.shields = 0
boss.take_damage(1000000000.0,false,0,true)
await frames()
var maps := 0
var orbs := 0
for pickup in get_tree().get_nodes_in_group("loot"):
if pickup.get_script() == preload("res://scripts/items/loot_pickup.gd") and not pickup.item.definition.orb_id.is_empty(): orbs += 1
if pickup.get_script() == preload("res://scripts/items/expedition_pickup.gd"):
if pickup.payload.has("map"): maps += 1
if pickup.payload.has("orb"): orbs += 1
check(maps==1 and orbs>=1 and orbs<=2,"Actual boss death drops map, guaranteed orb and optional replication")
check(get_tree().get_nodes_in_group("time_crystal_pickups").size()==1,"Boss also drops time crystals")
game.get_node("DistantSector")._physics_process(0.3)
check(game.system_cleared,"Sector cleared")
player.landing_combat_lock = 0
var controller := game.get_node("PlanetExpeditions")
player.is_docked = false
player.position = controller.freighter.position
controller.land_nearby()
check(is_instance_valid(controller.screen) and player.is_docked,"Normal docking during expedition")
check(controller.screen.find_child("ReturnFromDistant",true,false)!=null,"Return button inside freighter")
controller.screen._return_from_distant()
await frames(8)
game = get_tree().current_scene
freeze_game(game)
check(GameState.active_distant_map.is_empty() and GameState.system_level==7 and GameState.system_seed==777,"Return home")
check(GameState.captain.expedition.deuter==0,"Return free")
check(game.has_node("PlanetExpeditions") and game.has_node("CelestialBodies"),"Normal scene restored")
check(is_instance_valid(game.get_node("PlanetExpeditions").screen) and game.get_node("PlayerShip").is_docked,"Return docks at freighter")
var combat_checks := preload("res://tests/items/combat_orbs_checks.gd").new()
for failure in combat_checks.run(game): check(false,str(failure))
print("DISTANT_INTEGRATION_RESULT: "+JSON.stringify({"failures":failures,"passed":failures.is_empty()}))