extends Node2D const PLAYER := preload("res://scenes/game/player_ship.tscn") const ENEMY := preload("res://scenes/game/enemy_ship.tscn") class AimedGun: extends "res://scripts/game/weapon_controller.gd" func _aim_direction_from(_origin: Vector2) -> Vector2: return Vector2.RIGHT func _ready() -> void: get_tree().create_timer(20.0).timeout.connect(func() -> void: push_error("MACHINE_GUN_SHIELD_RECOVERY_TEST: timed out") Input.action_release("ship_fire_primary") get_tree().quit(1) ) seed(12345) GameState.start_new_run() var ship := PLAYER.instantiate() as CharacterBody2D ship.name = "PlayerShip" add_child(ship) ship.set_physics_process(false) ship.get_node("WeaponController").set_physics_process(false) ship.set("shields", 0.0) ship.set("energy", 0.0) var inventory := ship.get_node("ShipInventory") as ShipInventory var gun := ItemInstance.new() gun.definition = preload("res://resources/items/machine_gun.tres") assert(inventory.add_item(gun)) assert(inventory.move_item("cargo", 0, "weapon", 0)) var module := ItemInstance.new() module.definition = preload("res://resources/items/engine_extension.tres") var addon := ItemStatModifier.new() addon.affix = preload("res://resources/items/affixes/shield_on_damage.tres") addon.stat_key = addon.affix.stat_key addon.value = addon.affix.roll_value(1) module.additional_modifiers.append(addon) assert(inventory.add_item(module)) assert(inventory.move_item("cargo", inventory.cargo.find(module), "module", 0)) assert(ship.get("shield_on_damage_percent") == 1.0) var indicator := Node2D.new() indicator.set_script(preload("res://scripts/game/ship_energy_indicator.gd")) indicator.set("player", ship) ship.add_child(indicator) var enemy := ENEMY.instantiate() as CharacterBody2D enemy.position = Vector2(240, -28) add_child(enemy) enemy.set_physics_process(false) enemy.set("_player", ship) enemy.set("max_hull", 10000.0) enemy.set("hull", 10000.0) var controller := AimedGun.new() ship.add_child(controller) await get_tree().physics_frame await get_tree().physics_frame # Fire a real projectile with the smallest level-1 damage: 2 DMG. gun.damage_min = 2.0 gun.damage_max = 2.0 Input.action_press("ship_fire_primary") for frame in 60: await get_tree().physics_frame if inventory.ammunition < 50: break Input.action_release("ship_fire_primary") assert(inventory.ammunition == 49) for frame in 60: await get_tree().physics_frame if enemy.get("hull") < 10000.0: break assert(is_equal_approx(enemy.get("hull"), 9998.0)) assert(is_equal_approx(ship.get("shields"), 0.02)) print("MACHINE_GUN_RECOVERY: single physical hit: 2 DMG -> +%.4f shields" % ship.get("shields")) # Restore the normal starter roll (3–4 DMG), then empty the remaining magazine. gun.damage_min = -1.0 gun.damage_max = -1.0 Input.action_press("ship_fire_primary") for frame in 600: await get_tree().physics_frame if inventory.ammunition == 0: break Input.action_release("ship_fire_primary") assert(inventory.ammunition == 0) for frame in 45: await get_tree().physics_frame var actual_damage := 10000.0 - float(enemy.get("hull")) var recovered := float(ship.get("shields")) assert(is_equal_approx(recovered, actual_damage * 0.01)) assert(recovered > 1.0 and ship.get("energy") == 0.0) indicator.call("_process", 1.0) assert(is_equal_approx(indicator.get("_display_shield_ratio"), recovered / float(ship.get("max_shields")))) print("MACHINE_GUN_RECOVERY: 50 physical hits, %.0f total DMG -> +%.4f shields; white bar: %.4f%% (%.3f px)" % [actual_damage, recovered, recovered / float(ship.get("max_shields")) * 100.0, recovered / float(ship.get("max_shields")) * 88.0]) # Empty ammo cannot keep firing or generate any further shields. Input.action_press("ship_fire_primary") for frame in 30: await get_tree().physics_frame Input.action_release("ship_fire_primary") assert(is_equal_approx(ship.get("shields"), recovered)) print("MACHINE_GUN_SHIELD_RECOVERY_TEST: PASS (real firing input, projectile flight, collisions, 2-DMG fractions, accumulated recovery, ammo consumption, zero energy, shield bar, empty ammo)") get_tree().quit()