diff --git a/server/src/Vanguard.Rules/Engine/GameEngine.cs b/server/src/Vanguard.Rules/Engine/GameEngine.cs index 69c87d1..a5e1917 100644 --- a/server/src/Vanguard.Rules/Engine/GameEngine.cs +++ b/server/src/Vanguard.Rules/Engine/GameEngine.cs @@ -9,10 +9,9 @@ namespace Vanguard.Rules.Engine; /// Упрощения первого среза (карты со способностями подключаются следующим шагом): /// * Sentinel добавляет 0 щита и не оплачивает стоимость — Perfect Guard пока не работает; /// * Boost не реализован; -/// * триггер на drive check требует выбора целей через ResolveTriggerCommand: +/// * триггер требует выбора целей через ResolveTriggerCommand: /// +5000 любой карте в кругах Авангарда или Арьергарда, затем второй эффект; -/// * триггер на damage check пока применяет только второй эффект, -/// Stand поднимает первый повёрнутый Арьергард. +/// правило одинаково для drive check и damage check. /// public sealed class GameEngine { @@ -485,8 +484,12 @@ public sealed class GameEngine { State.Step = Step.Drive; ContinueDriveChecks(battle); + return CommandResult.Ok(); } + if (pending.Context == TriggerContext.DamageCheck) + FinishDamage(pending); + return CommandResult.Ok(); } @@ -532,6 +535,9 @@ public sealed class GameEngine } } + if (State.PendingTrigger is not null) + return; + ClearBattle(battle); } @@ -567,17 +573,28 @@ public sealed class GameEngine if (card is null) return; + card.FaceUp = false; + State.Players[player].Damage.Add(card); + _events.Add(new DamageTakenEvent(player, 1, State.Players[player].Damage.Count)); + var def = State.Catalog.CardOf(card.PrintingId); if (def.Trigger != TriggerType.None) { _events.Add(new TriggerResolvedEvent(player, card.PrintingId, def.Trigger)); - ApplyDamageTrigger(player, def.Trigger); + State.PendingTrigger = new PendingTrigger + { + Player = player, + PrintingId = card.PrintingId, + Trigger = def.Trigger, + Context = TriggerContext.DamageCheck, + GrantsPower = true, + RemainingDamage = amount - i - 1, + }; + State.Step = Step.Trigger; + State.ActivePlayer = player; + return; } - card.FaceUp = false; - State.Players[player].Damage.Add(card); - _events.Add(new DamageTakenEvent(player, 1, State.Players[player].Damage.Count)); - if (State.Players[player].Damage.Count >= MaxDamage) { Defeat(player); @@ -586,33 +603,30 @@ public sealed class GameEngine } } - private void ApplyDamageTrigger(int player, TriggerType trigger) + /// Продолжает урон после разрешения триггера damage check. + private void FinishDamage(PendingTrigger pending) { - switch (trigger) - { - case TriggerType.Draw: - Draw(player, 1); - break; - case TriggerType.Stand: - StandFirstRested(player); - break; - case TriggerType.Heal: - Heal(player); - break; - } - } + if (State.IsFinished) + return; - private void StandFirstRested(int player) - { - var p = State.Players[player]; - for (var i = 0; i < p.RearGuards.Length; i++) + var player = pending.Player; + + if (State.Players[player].Damage.Count >= MaxDamage) { - if (p.RearGuards[i] is { Rested: true } unit) - { - unit.Rested = false; - return; - } + Defeat(player); + return; } + + if (pending.RemainingDamage > 0) + DealDamage(player, pending.RemainingDamage); + + if (State.PendingTrigger is not null) + return; + + State.Step = Step.None; + + if (State.Battle is { } battle) + ClearBattle(battle); } private void Heal(int player) diff --git a/server/src/Vanguard.Rules/Engine/GameModels.cs b/server/src/Vanguard.Rules/Engine/GameModels.cs index dcc851a..ec5dde7 100644 --- a/server/src/Vanguard.Rules/Engine/GameModels.cs +++ b/server/src/Vanguard.Rules/Engine/GameModels.cs @@ -37,6 +37,9 @@ public sealed class PendingTrigger /// На drive check триггер сначала даёт +5000 силы, на damage check — нет. public required bool GrantsPower { get; init; } + + /// Сколько урона осталось раздать после этого триггера (контекст damage check). + public int RemainingDamage { get; init; } } /// Экземпляр карты в партии. Один принт может встречаться много раз. diff --git a/server/tests/Vanguard.Rules.Tests/GameEngineTests.cs b/server/tests/Vanguard.Rules.Tests/GameEngineTests.cs index f7d6f03..fe19eb7 100644 --- a/server/tests/Vanguard.Rules.Tests/GameEngineTests.cs +++ b/server/tests/Vanguard.Rules.Tests/GameEngineTests.cs @@ -153,6 +153,30 @@ public class GameEngineTests Assert.False(rearGuard.Rested); } + [Fact] + public void Damage_Trigger_Also_Asks_For_Targets_And_Applies_Effect() + { + var engine = ReadyToAttack(); + StackDeck(engine.State.Players[0], Gallatin, Gallatin); + StackDeck(engine.State.Players[1], Nike); + + Assert.True(engine.Execute(new AttackCommand(-1, -1)).Accepted); + Assert.True(engine.Execute(new PassGuardCommand()).Accepted); + + var pending = engine.State.PendingTrigger; + Assert.NotNull(pending); + Assert.Equal(TriggerContext.DamageCheck, pending!.Context); + Assert.Single(engine.State.Players[1].Damage); + + Assert.True(engine.Execute(new ResolveTriggerCommand(-1, -1)).Accepted); + + Assert.Null(engine.State.PendingTrigger); + Assert.Null(engine.State.Battle); + Assert.Single(engine.State.Players[1].Damage); + Assert.Contains(engine.State.Players[1].Damage, c => c.PrintingId == Nike); + Assert.Contains(engine.Events, e => e is TriggerAppliedEvent { Player: 1 }); + } + [Fact] public void ResolveTrigger_Rejects_When_Nothing_Is_Pending() {