extends Node const PROGRESS := preload("res://scripts/captain/captain_progress.gd") const COMBAT := preload("res://scripts/game/combat_rolls.gd") const GAME_SCENE := preload("res://scenes/game/game.tscn") const GENERATOR := preload("res://scripts/items/item_generator.gd") const PROJECTILE := preload("res://scenes/game/weapon_projectile.tscn") const BALANCE := preload("res://scripts/captain/captain_balance.gd") class DamageReceiver: extends Node2D var hits: Array[Dictionary] = [] func take_weapon_hit(amount: float, critical: bool, _damage_type: int = 0) -> void: hits.append({"damage": amount, "critical": critical}) func _ready() -> void: _test_skill_balance_and_reset() var profile := PROGRESS.new() assert(not profile.train_skill("gunner")) profile.add_rewards(99, 60) assert(profile.level == 1 and profile.experience == 99) profile.add_rewards(1, 15) assert(profile.level == 2 and profile.experience == 0 and profile.learning_points == 1 and profile.credits == 75) assert(profile.train_skill("gunner") and profile.get_skill_rank("gunner") == 1) assert(not profile.train_skill("piloting") and not profile.train_skill("unknown")) profile.add_rewards(450, 0) assert(profile.level == 4 and profile.experience == 100 and profile.learning_points == 2) profile.skills["piloting"] = 100 assert(not profile.train_skill("piloting")) assert(profile.reward_enemy(10, 2, 1.25, 1.5) == Vector2i(272, 261)) var hit := COMBAT.weapon_hit(10, 1.1, 0, 2, 0) assert(not hit["critical"] and is_equal_approx(hit["damage"], 11)) hit = COMBAT.weapon_hit(10, 1.1, 1, 2.4, 1) assert(hit["critical"] and is_equal_approx(hit["damage"], 26.4)) assert(not COMBAT.weapon_hit(10, 1, 0.01, 2, 0.01)["critical"]) assert(COMBAT.incoming_hit(100, 1, 0, 1)["evaded"]) assert(is_equal_approx(COMBAT.incoming_hit(100, 0.01, 0.5, 0.01)["damage"], 50)) assert(COMBAT.incoming_hit(100, 0, 1, 0)["damage"] == 0) assert(COMBAT.incoming_hit(-100, 0, 0, 0)["damage"] == 0) GameState.start_new_run() var game := GAME_SCENE.instantiate() as Node2D get_tree().root.call_deferred("add_child", game) await get_tree().process_frame await get_tree().process_frame get_tree().current_scene = game _freeze(game) profile = GameState.captain var enemies := get_tree().get_nodes_in_group("enemies") for enemy in enemies: enemy.call("take_weapon_hit", 1000000.0, true) enemy.call("take_damage", 1000000.0) assert(profile.level == 2 and profile.experience == 130 and profile.learning_points == 1) assert(profile.credits == 0) await _collect_credits(game.get_node("PlayerShip") as CharacterBody2D) assert(profile.credits == 180) var panel := game.get_node("HUD/HUDRoot/CaptainPanel") panel.get_node("Contents/Skills/gunner/Train_gunner").emit_signal("pressed") assert(profile.get_skill_rank("gunner") == 1 and profile.learning_points == 0) panel.get_node("Contents/Skills/piloting/Train_piloting").emit_signal("pressed") assert(profile.get_skill_rank("piloting") == 0) assert(panel.get_node("Contents/Header/Level").get("text") == "POZ. 2") var player := game.get_node("PlayerShip") as CharacterBody2D profile.skills["piloting"] = 100 assert(is_equal_approx(float(player.call("get_evasion_chance")), 0.25)) assert(COMBAT.incoming_hit(100, player.call("get_evasion_chance"), 0, 0.0)["evaded"]) assert(COMBAT.incoming_hit(100, player.call("get_evasion_chance"), 0, 0.25)["damage"] == 100) profile.skills["piloting"] = 0 profile.skills["mechanic"] = 15 player.set("shields", 100.0) # Fixed fixture, independent of selected chassis capacity. player.call("take_damage", 100.0) assert(is_equal_approx(float(player.get("shields")), 30) and float(player.get("hull")) == 100) player.set("shields", 0.0) player.call("take_damage", 40.0) assert(is_equal_approx(float(player.get("hull")), 72)) profile.skills["mechanic"] = 100 assert(is_equal_approx(float(player.call("get_damage_reduction")), 1.0 / 3.0)) player.call("take_damage", 30.0) assert(is_equal_approx(float(player.get("hull")), 52)) profile.skills["mechanic"] = 0 profile.skills["sharp_eye"] = 1 var inventory := player.get_node("ShipInventory") as ShipInventory var weapon := GENERATOR.generate_for_definition(preload("res://resources/items/plasma_cannon.tres"), 1) weapon.additional_modifiers.clear() weapon.damage_min = 10 weapon.damage_max = 10 for data in [["critical_chance_percent", 25.0], ["critical_damage_percent", 20.0], ["damage_percent", 10.0], ["experience_percent", 25.0], ["credits_percent", 50.0]]: var modifier := ItemStatModifier.new() modifier.stat_key = data[0] modifier.value = data[1] weapon.additional_modifiers.append(modifier) var free_slot := inventory.cargo.find(null) assert(inventory.add_item(weapon) and inventory.move_item("cargo", free_slot, "weapon", 0)) assert(is_equal_approx(float(player.call("get_critical_chance")), 0.26)) assert(is_equal_approx(float(player.call("get_critical_multiplier")), 2.4)) assert(is_equal_approx(float(player.call("get_weapon_damage_multiplier")), 1.122)) profile.skills["sharp_eye"] = 20 assert(is_equal_approx(float(player.call("get_critical_chance")), 0.45)) player.set("critical_chance_bonus", 0.8) # Force this metadata check independently of RNG. hit = player.call("roll_weapon_hit", weapon) assert(hit["critical"] and is_equal_approx(hit["damage"], 26.928)) player.set("critical_chance_bonus", 0.25) var receiver := DamageReceiver.new() game.add_child(receiver) var projectile := PROJECTILE.instantiate() projectile.call("configure", Vector2.ZERO, Vector2.RIGHT, ItemDefinition.WeaponKind.MISSILE, 400.0, 26.928, 1000.0, null, true) game.add_child(projectile) projectile.set_physics_process(false) projectile.call("_apply_damage_once", receiver) projectile.call("_apply_damage_once", receiver) assert(receiver.hits.size() == 1 and receiver.hits[0]["critical"]) assert(is_equal_approx(receiver.hits[0]["damage"], 26.928)) player.call("take_damage", 1000000.0) await get_tree().create_timer(1.3).timeout game = get_tree().current_scene as Node2D _freeze(game) assert(GameState.captain == profile and profile.level == 2 and profile.credits == 180) player = game.get_node("PlayerShip") as CharacterBody2D assert(player.get_node("ShipInventory").call("get_item", "weapon", 0) == weapon) for enemy in get_tree().get_nodes_in_group("enemies"): enemy.call("take_damage", 1000000.0) await _collect_credits(player) assert(profile.level == 4 and profile.experience == 68 and profile.credits == 452) var planet: Node2D = game.get("protected_planet") player.position = planet.position game.call("_physics_process", 0.0) assert(bool(game.call("open_wormhole"))) var portal: Node2D = game.get("wormhole") player.position = portal.position assert(bool(game.call("advance_to_next_level"))) await get_tree().process_frame await get_tree().process_frame await get_tree().process_frame game = get_tree().current_scene as Node2D _freeze(game) assert(GameState.captain == profile and GameState.system_level == 2) player = game.get_node("PlayerShip") as CharacterBody2D assert(profile.get_skill_rank("gunner") == 1 and profile.get_skill_rank("sharp_eye") == 20) var enemy := get_tree().get_nodes_in_group("enemies")[0] as CharacterBody2D enemy.call("take_damage", 1000000.0) await _collect_credits(player) assert(profile.level == 5 and profile.experience == 15 and profile.credits == 641) print("CAPTAIN_SYSTEM_TEST: PASS (XP, multiple levels, learning UI, rewards once, dodge, reduction, critical addons, projectile metadata, death and next system)") func _collect_credits(player: CharacterBody2D) -> void: await get_tree().process_frame for pickup in get_tree().get_nodes_in_group("credit_pickups"): player.global_position = pickup.global_position pickup.call("_physics_process", 0.0) func _test_skill_balance_and_reset() -> void: assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 1), 0.5 / 11.0)) assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 5), 1.0 / 6.0)) assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 10), 0.25)) assert(is_equal_approx(BALANCE.skill_bonus("mechanic", 20), 1.0 / 3.0)) assert(is_equal_approx(BALANCE.skill_bonus("piloting", 20), 0.25)) assert(is_equal_approx(BALANCE.skill_bonus("gunner", 20), 0.4)) assert(is_equal_approx(BALANCE.skill_bonus("sharp_eye", 20), 0.2)) for skill in ["mechanic", "piloting"]: var previous_gain := 1.0 for rank in range(1, 21): var gain := BALANCE.skill_bonus(skill, rank) - BALANCE.skill_bonus(skill, rank - 1) assert(gain > 0.0 and gain < previous_gain) previous_gain = gain var captain := PROGRESS.new() captain.learning_points = 100 for rank in 20: assert(captain.train_skill("mechanic")) assert(not captain.train_skill("mechanic") and captain.learning_points == 80) captain.level = 3 captain.credits = 299 assert(not captain.reset_skills()) assert(captain.skills["mechanic"] == 20 and captain.credits == 299) captain.credits = 300 assert(captain.reset_skills()) assert(captain.learning_points == 100 and captain.credits == 0 and captain.skills["mechanic"] == 0) captain.credits = 300 assert(not captain.reset_skills() and captain.credits == 300) for skill in BALANCE.SKILL_NAMES: captain.skills[skill] = 100 captain.learning_points = 7 captain.normalize_skill_ranks() assert(captain.learning_points == 327 and captain.get_spent_learning_points() == 80) captain.normalize_skill_ranks() assert(captain.learning_points == 327) assert(captain.reset_skills()) assert(captain.learning_points == 407) func _freeze(game: Node2D) -> void: game.set_physics_process(false) game.get_node("PlayerShip").set_physics_process(false) game.get_node("PlayerShip/WeaponController").set_physics_process(false) for enemy in get_tree().get_nodes_in_group("enemies"): if game.is_ancestor_of(enemy): enemy.set_physics_process(false)