using AcDream.App.Combat; using AcDream.Core.Combat; using AcDream.UI.Abstractions.Input; namespace AcDream.App.Tests.Combat; public sealed class CombatAttackControllerTests { [Fact] public void NormalStance_ChargesLinearlyOverOneSecond_AndReleaseSendsCurrentPower() { double now = 10d; var sent = new List<(AttackHeight Height, float Power)>(); var combat = new CombatState(); using var controller = Create(combat, () => now, sent); combat.SetCombatMode(CombatMode.Melee); controller.PressAttack(AttackHeight.High); now = 10.5d; Assert.Equal(0.5f, controller.PowerBarLevel, 3); controller.ReleaseAttack(); var attack = Assert.Single(sent); Assert.Equal(AttackHeight.High, attack.Height); Assert.Equal(0.5f, attack.Power, 3); } [Fact] public void DualWieldStance_UsesRetailPointEightSecondPowerUpTime() { double now = 3d; var combat = new CombatState(); using var controller = Create( combat, () => now, [], isDualWield: () => true); combat.SetCombatMode(CombatMode.Melee); controller.PressAttack(AttackHeight.Medium); now = 3.4d; Assert.Equal(0.5f, controller.PowerBarLevel, 3); } [Fact] public void EarlyRelease_CommitsOnUseTimeTickAtReleasedPower() { double now = 0d; var sent = new List<(AttackHeight Height, float Power)>(); var combat = new CombatState(); using var controller = Create(combat, () => now, sent); combat.SetCombatMode(CombatMode.Missile); controller.SetDesiredPower(1f); controller.PressAttack(AttackHeight.Low); now = 0.25d; controller.ReleaseAttack(); Assert.Empty(sent); now = 0.26d; controller.Tick(); var attack = Assert.Single(sent); Assert.Equal(AttackHeight.Low, attack.Height); Assert.Equal(0.25f, attack.Power, 3); } [Fact] public void HoldBinding_UsesPressAndReleaseTransitions() { double now = 1d; var sent = new List<(AttackHeight Height, float Power)>(); var combat = new CombatState(); using var controller = Create(combat, () => now, sent); combat.SetCombatMode(CombatMode.Melee); Assert.True(controller.HandleInputAction( InputAction.CombatLowAttack, ActivationType.Press)); now = 1.5d; Assert.True(controller.HandleInputAction( InputAction.CombatLowAttack, ActivationType.Release)); Assert.Equal(AttackHeight.Low, Assert.Single(sent).Height); } [Fact] public void LeavingTargetedCombat_CancelsAnActiveBuild() { double now = 0d; var combat = new CombatState(); using var controller = Create(combat, () => now, []); combat.SetCombatMode(CombatMode.Melee); controller.PressAttack(AttackHeight.Medium); now = 0.4d; combat.SetCombatMode(CombatMode.NonCombat); Assert.False(controller.BuildInProgress); Assert.False(controller.AttackRequestInProgress); Assert.Equal(0f, controller.PowerBarLevel); } [Fact] public void RequestWaitsUntilPlayerReachesReadyStanceBeforeBuilding() { double now = 0d; bool ready = false; var combat = new CombatState(); using var controller = new CombatAttackController( combat, () => true, (_, _) => true, playerReadyForAttack: () => ready, now: () => now); combat.SetCombatMode(CombatMode.Missile); controller.PressAttack(AttackHeight.High); Assert.True(controller.AttackRequestInProgress); Assert.False(controller.BuildInProgress); ready = true; now = 5d; controller.Tick(); Assert.True(controller.BuildInProgress); Assert.Equal(0f, controller.PowerBarLevel); } private static CombatAttackController Create( CombatState combat, Func now, List<(AttackHeight Height, float Power)> sent, Func? isDualWield = null) => new( combat, canStartAttack: () => true, sendAttack: (height, power) => { sent.Add((height, power)); return true; }, isDualWield: isDualWield, autoRepeatAttack: () => false, now: now); }