Audit abilities and add Conroe auto call
CI / test (push) Successful in 1m46s Details

This commit is contained in:
MpakobecEx 2026-10-24 00:40:22 +05:00
parent d917448a5f
commit 228bc4ce8c
10 changed files with 247 additions and 10 deletions

View File

@ -31,6 +31,38 @@
- Информационные оверлеи (`ActionPrompt`, подсказки) не должны перехватывать - Информационные оверлеи (`ActionPrompt`, подсказки) не должны перехватывать
клики и не дублируют выбор кнопками. клики и не дублируют выбор кнопками.
## Способности
- Каждая текстовая способность карты должна иметь ровно одну реализацию:
`CardScript` (`Abilities`/`Continuous`/`Activated`), общий движок
(Sentinel, Heal/Trigger, Tejas) или флаг данных. Текст без реализации —
баг.
- **AUTO**: `AbilityDefinition` с `Trigger`, `Condition`, `Optional`,
`CanPay`/`Pay` и `Resolve`. Проверяй зону/источник через
`IsVanguardOf`/`IsRearGuardOf`/`IsInHand`; `OnPlace` не должен
срабатывать от VC, если текст требует RC.
- **ACT**: `ActivatedAbility`; флаги `RestsSelf`, `RetiresSelf`,
`RequiresDiscard`, `FromHand`, `FromDropZone`. `CanActivate` — только
проверка условий; оплата и эффект — в `Resolve` (или `Pay` для AUTO).
- **CONT**: `ContinuousAbility` для силы/критики; исключения вроде
«нельзя поддерживать» — через `CardScript.CanBeBoosted`, ограничения
передвижения/атаки — через `RestrictionType`.
- **Sentinel**: `IsSentinel` в данных, общий механизм Guard + сброс карты
того же клана; отдельный скрипт не нужен.
- **Trigger/Heal**: данные `trigger`; общий движок проверок и лечения.
- **Ключевые механики**: Tejas — `CanSnipeBackRow`; Restraint — флаг;
Boost/Intercept — `HasBoost`/`HasIntercept` и общий движок.
- **Выбор и цели**: только `AskUnitChoice`, `AskUnitMultiChoice`,
`AskHandCardChoice`, `AskEmptyRearGuardChoice`; клиент не выбирает
карты кнопками вместо объектов.
- **Клиент**: AUTO-подтверждение — оверлей; ACT — меню/оверлей карты;
стоимость способности (сброс/урон/поле) выбирается по объектам и
подтверждается кнопкой панели «Действия».
- После правок способностей проверять весь реестр `CardScripts.Default`,
тесты правил, `validate` и `translations check`.
- Полный аудит 66 карт и правила для новых карт —
`docs/ability-implementation.md`.
## Технические ограничения ## Технические ограничения
- Не запускать `dotnet` и Docker на рабочем ПК; сервер проверять на - Не запускать `dotnet` и Docker на рабочем ПК; сервер проверять на

View File

@ -1,4 +1,4 @@
# Передача сессии # Передача сессии
Состояние проекта на **23 октября 2026**. Документ нужен, чтобы новая сессия Состояние проекта на **23 октября 2026**. Документ нужен, чтобы новая сессия
продолжила работу без повторного разбора истории. Краткая оперативная продолжила работу без повторного разбора истории. Краткая оперативная
@ -26,7 +26,10 @@
в руке (Gancelot) доступна как `FromHand`-активация. Списки всех смен в руке (Gancelot) доступна как `FromHand`-активация. Списки всех смен
убраны из панелей, смена/призыв — драгом. убраны из панелей, смена/призыв — драгом.
- Защита из руки ограничена рангом авангарда. - Защита из руки ограничена рангом авангарда.
- Тесты: сервер **146/146** правил + **45/45** серверных, клиент - Проведён аудит всех 66 карт с текстом: `docs/ability-implementation.md`.
Исправлены Conroe AUTO, зоны у Maiden of Libra/Starlight/Hungry/Clay/
Joka/Rakshasa, запрет поддержки Alfred.
- Тесты: сервер **148/148** правил + **45/45** серверных, клиент
**38/38** EditMode; `SCENE_SMOKE` и `PLAYMAT_SMOKE` успешны. **38/38** EditMode; `SCENE_SMOKE` и `PLAYMAT_SMOKE` успешны.
--- ---
@ -64,7 +67,7 @@ MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты
| Проверка | Результат | | Проверка | Результат |
|---|---| |---|---|
| Тесты сервера | **191/191** (146 правил/контрактов + 45 сервер) | | Тесты сервера | **193/193** (148 правил/контрактов + 45 сервер) |
| `validate` | 0 ошибок, 0 предупреждений | | `validate` | 0 ошибок, 0 предупреждений |
| `translations check` | 94/94 | | `translations check` | 94/94 |
| `stress 1000` | 1000/1000 партий, 0 ошибок | | `stress 1000` | 1000/1000 партий, 0 ошибок |
@ -97,7 +100,7 @@ dotnet run --project server\src\Vanguard.Cli -- stress 1000
Ожидаемо: Ожидаемо:
``` ```
Пройден! : не пройдено 0, пройдено 146 (Vanguard.Rules.Tests) Пройден! : не пройдено 0, пройдено 148 (Vanguard.Rules.Tests)
Пройден! : не пройдено 0, пройдено 45 (Vanguard.Server.Tests) Пройден! : не пройдено 0, пройдено 45 (Vanguard.Server.Tests)
OK: ошибок 0, предупреждений 0 OK: ошибок 0, предупреждений 0
Переведено: 94/94 Переведено: 94/94

View File

@ -1,4 +1,4 @@
# Vanguard ReMaster — состояние сессии # Vanguard ReMaster — состояние сессии
**Дата:** 2026-10-23 **Дата:** 2026-10-23
@ -19,7 +19,7 @@
матчмейкинг, WebSocket-матч, история, реплеи. матчмейкинг, WebSocket-матч, история, реплеи.
- Orleans включается `Orleans:Enabled`; в compose включён. Один silo, - Orleans включается `Orleans:Enabled`; в compose включён. Один silo,
in-process реестр соединений. in-process реестр соединений.
- Тесты: **146** правил + **45** серверных, CI зелёный. - Тесты: **148** правил + **45** серверных, CI зелёный.
- Стенд: `http://192.168.31.158:18080`, `/health` → ok. - Стенд: `http://192.168.31.158:18080`, `/health` → ok.
- Диагностика Ride в `MatchGrain`: входящие интенты, результат, события, - Диагностика Ride в `MatchGrain`: входящие интенты, результат, события,
снапшоты и `rideReady` по руке/рангам; логи через `docker compose logs`. снапшоты и `rideReady` по руке/рангам; логи через `docker compose logs`.
@ -94,6 +94,8 @@
Призыв разрешён и в занятый RC; прежний юнит уходит в Дроп. Правило Призыв разрешён и в занятый RC; прежний юнит уходит в Дроп. Правило
действует для обычного призыва, призыва из души/колоды и выбора круга действует для обычного призыва, призыва из души/колоды и выбора круга
способностями. способностями.
- Аудит всех 66 карт со способностями и правила реализации:
`docs/ability-implementation.md`. Conroe получил AUTO-call при ride-over.
- Способности: кнопка «Активировать способность» одна на карту; при - Способности: кнопка «Активировать способность» одна на карту; при
нескольких вариантах открывается overlay выбора способности. Карта для нескольких вариантах открывается overlay выбора способности. Карта для
сброса выбирается **кликом по карте в руке**, а не пунктом меню. сброса выбирается **кликом по карте в руке**, а не пунктом меню.

View File

@ -0,0 +1,113 @@
# Реализация способностей карт
Актуально на **2026-10-24**. Источник правил — `content/sets/*.yaml`,
`textEn`; источник исполнения — `Vanguard.Rules`.
## Проверка реестра
Проверены все **66** карт с текстом способностей в BT01/TD01/TD02.
Для каждой есть одна реализация:
- `CardScript` — 59 карт в `CardScripts.Default`;
- `Generic: Sentinel` — 3 карты (`flash-shield-iseult`,
`wyvern-guard-barri`, `battle-sister-chocolat`): данные `sentinel` +
общий Guard/сброс;
- `Generic: Heal trigger` — 3 карты (`yggdrasil-maiden-elaine`,
`dragon-monk-genjo`, `ring-girl-clara`): данные `trigger: Heal`;
- `Data: CanSnipeBackRow` — `wyvern-strike-tejas`: флаг данных + общий
движок атаки заднего ряда.
## Полный список
| Карта | Клан | Ранг | Реализация |
|---|---|---:|---|
| `king-of-knights-alfred` | RoyalPaladin | 3 | CardScript |
| `blaster-blade` | RoyalPaladin | 2 | CardScript |
| `barcgal` | RoyalPaladin | 0 | CardScript |
| `dragonic-overlord` | Kagero | 3 | CardScript |
| `embodiment-of-victory-aleph` | Kagero | 3 | CardScript |
| `ceo-amaterasu` | OracleThinkTank | 3 | CardScript |
| `battle-sister-cocoa` | OracleThinkTank | 1 | CardScript |
| `asura-kaiser` | NovaGrappler | 3 | CardScript |
| `demon-slaying-knight-lohengrin` | RoyalPaladin | 3 | CardScript |
| `solitary-knight-gancelot` | RoyalPaladin | 3 | CardScript |
| `flash-shield-iseult` | RoyalPaladin | 1 | Generic: Sentinel |
| `future-knight-llew` | RoyalPaladin | 0 | CardScript |
| `vortex-dragon` | Kagero | 3 | CardScript |
| `dragon-knight-aleph` | Kagero | 2 | CardScript |
| `wyvern-guard-barri` | Kagero | 1 | Generic: Sentinel |
| `lizard-soldier-conroe` | Kagero | 0 | CardScript |
| `maiden-of-libra` | OracleThinkTank | 2 | CardScript |
| `battle-sister-mocha` | OracleThinkTank | 2 | CardScript |
| `battle-sister-chocolat` | OracleThinkTank | 1 | Generic: Sentinel |
| `juggernaut-maximum` | SpikeBrothers | 3 | CardScript |
| `wyvern-strike-tejas` | Kagero | 2 | Data: CanSnipeBackRow |
| `oracle-guardian-apollon` | OracleThinkTank | 3 | CardScript |
| `lozenge-magus` | OracleThinkTank | 0 | CardScript |
| `mr-invincible` | NovaGrappler | 3 | CardScript |
| `brutal-jack` | NovaGrappler | 2 | CardScript |
| `queen-of-heart` | NovaGrappler | 1 | CardScript |
| `battleraizer` | NovaGrappler | 0 | CardScript |
| `tyrant-deathrex` | Tachikaze | 3 | CardScript |
| `assault-dragon-blightops` | Tachikaze | 2 | CardScript |
| `stealth-dragon-voidmaster` | Nubatama | 3 | CardScript |
| `demon-eater` | DarkIrregulars | 3 | CardScript |
| `monster-frank` | Granblue | 3 | CardScript |
| `hell-spider` | Megacolony | 3 | CardScript |
| `covenant-knight-randolf` | RoyalPaladin | 2 | CardScript |
| `lake-maiden-lien` | RoyalPaladin | 1 | CardScript |
| `wingal` | RoyalPaladin | 1 | CardScript |
| `yggdrasil-maiden-elaine` | RoyalPaladin | 0 | Generic: Heal trigger |
| `dragon-monk-gojo` | Kagero | 1 | CardScript |
| `wyvern-strike-jarran` | Kagero | 1 | CardScript |
| `dragon-monk-genjo` | Kagero | 0 | Generic: Heal trigger |
| `weather-girl-milk` | OracleThinkTank | 1 | CardScript |
| `hungry-dumpty` | NovaGrappler | 2 | CardScript |
| `screamin-and-dancin-announcer-shout` | NovaGrappler | 1 | CardScript |
| `clay-doll-mechanic` | NovaGrappler | 1 | CardScript |
| `ring-girl-clara` | NovaGrappler | 0 | Generic: Heal trigger |
| `stealth-beast-chigasumi` | Nubatama | 2 | CardScript |
| `stealth-dragon-dreadmaster` | Nubatama | 1 | CardScript |
| `stealth-beast-hagakure` | Nubatama | 0 | CardScript |
| `blue-dust` | DarkIrregulars | 2 | CardScript |
| `nightmare-baby` | DarkIrregulars | 1 | CardScript |
| `rock-the-wall` | DarkIrregulars | 0 | CardScript |
| `highspeed-brakki` | SpikeBrothers | 2 | CardScript |
| `redshoe-milly` | SpikeBrothers | 0 | CardScript |
| `guiding-zombie` | Granblue | 0 | CardScript |
| `karma-queen` | Megacolony | 1 | CardScript |
| `crimson-butterfly-brigitte` | RoyalPaladin | 3 | CardScript |
| `knight-of-conviction-bors` | RoyalPaladin | 3 | CardScript |
| `knight-of-the-harp-tristan` | RoyalPaladin | 2 | CardScript |
| `starlight-unicorn` | RoyalPaladin | 1 | CardScript |
| `knight-of-rose-morgana` | RoyalPaladin | 1 | CardScript |
| `dragon-monk-goku` | Kagero | 3 | CardScript |
| `demonic-dragon-berserker-yaksha` | Kagero | 3 | CardScript |
| `berserk-dragon` | Kagero | 2 | CardScript |
| `flame-of-hope-aermo` | Kagero | 1 | CardScript |
| `demonic-dragon-madonna-joka` | Kagero | 1 | CardScript |
| `demonic-dragon-mage-rakshasa` | Kagero | 0 | CardScript |
## Исправлено при аудите 2026-10-24
- **Conroe**: добавлена недостающая `AUTO`: когда другой Kagero ride-over
эту карту, можно призвать Conroe из души в RC (`OnRideOver`).
- **Maiden of Libra**: добавлена проверка, что авангард — Oracle Think
Tank, как в тексте.
- **Starlight Unicorn**, **Hungry Dumpty**, **Clay Doll Mechanic**:
`OnPlace` теперь требует именно RC, а не срабатывает от VC.
- **Joka**, **Rakshasa**: бонус за retire соперника теперь требует
нахождения на RC, а не просто «не Vanguard» (включая руку).
- **Alfred**: добавлен CONT-запрет поддержки авангарда Alfred.
## Новые карты
Для новой карты с текстом:
1. Найти все `AUTO`/`ACT`/`CONT`/ключевые механики в `textEn`.
2. Реализовать `CardScript` или общий движок; не оставлять текст без
`AbilityDefinition`.
3. Проверить зону источника (`IsVanguardOf`, `IsRearGuardOf`,
`IsInHand`) и опциональность (`Optional`/`CanPay`/`Pay`).
4. Для выбора использовать только `Ask*Choice`, не кнопочные списки.
5. Добавить тест на каждый триггер/активацию и прогнать весь реестр.

View File

@ -87,6 +87,12 @@ public abstract class CardScript
public virtual IEnumerable<AbilityDefinition> Abilities => Array.Empty<AbilityDefinition>(); public virtual IEnumerable<AbilityDefinition> Abilities => Array.Empty<AbilityDefinition>();
public virtual IEnumerable<ContinuousAbility> Continuous => Array.Empty<ContinuousAbility>(); public virtual IEnumerable<ContinuousAbility> Continuous => Array.Empty<ContinuousAbility>();
public virtual IEnumerable<ActivatedAbility> Activated => Array.Empty<ActivatedAbility>(); public virtual IEnumerable<ActivatedAbility> Activated => Array.Empty<ActivatedAbility>();
/// <summary>
/// CONT-исключения, которые нельзя выразить только бонусом силы,
/// например «ваши юниты не могут поддерживать этот юнит».
/// </summary>
public virtual bool CanBeBoosted(AbilityContext context) => true;
} }
/// <summary>Сработавшая способность, ожидающая выбора порядка или решения игрока.</summary> /// <summary>Сработавшая способность, ожидающая выбора порядка или решения игрока.</summary>

View File

@ -186,6 +186,7 @@ public sealed class MaidenOfLibraScript : CardScript
{ {
Trigger = AbilityTrigger.OnAttackHit, Trigger = AbilityTrigger.OnAttackHit,
Optional = true, Optional = true,
Condition = ctx => ctx.Engine.VanguardClan(ctx.Owner) == Clan.OracleThinkTank,
CanPay = ctx => ctx.Engine.CanCounterBlast(ctx.Owner, 2), CanPay = ctx => ctx.Engine.CanCounterBlast(ctx.Owner, 2),
Pay = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2), Pay = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2),
Resolve = ctx => ctx.Engine.DrawCards(ctx.Owner, 1), Resolve = ctx => ctx.Engine.DrawCards(ctx.Owner, 1),
@ -224,6 +225,25 @@ public sealed class ConroeScript : CardScript
{ {
public override string NameId => "lizard-soldier-conroe"; public override string NameId => "lizard-soldier-conroe";
public override IEnumerable<AbilityDefinition> Abilities
{
get
{
yield return new AbilityDefinition
{
Trigger = AbilityTrigger.OnRideOver,
Optional = true,
Condition = ctx =>
ctx.Other is not null &&
ctx.Catalog.CardOf(ctx.Other.PrintingId).Clan == Clan.Kagero,
Resolve = ctx => ctx.Engine.AskEmptyRearGuardChoice(
ctx.Owner,
"Выберите круг для призыва Conroe",
slot => ctx.Engine.CallFromSoul(ctx.Source, ctx.Owner, slot)),
};
}
}
public override IEnumerable<ActivatedAbility> Activated public override IEnumerable<ActivatedAbility> Activated
{ {
get get
@ -410,6 +430,9 @@ public sealed class AlfredScript : CardScript
{ {
public override string NameId => "king-of-knights-alfred"; public override string NameId => "king-of-knights-alfred";
public override bool CanBeBoosted(AbilityContext context) =>
!context.Engine.IsVanguardOf(context.Source, context.Owner);
public override IEnumerable<ContinuousAbility> Continuous public override IEnumerable<ContinuousAbility> Continuous
{ {
get get

View File

@ -195,7 +195,7 @@ public sealed class FlipDamageOnPlaceScript : CardScript
{ {
Trigger = AbilityTrigger.OnPlace, Trigger = AbilityTrigger.OnPlace,
Condition = ctx => Condition = ctx =>
!ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner) && ctx.Engine.IsRearGuardOf(ctx.Source, ctx.Owner) &&
ctx.Engine.VanguardClan(ctx.Owner) == _clan, ctx.Engine.VanguardClan(ctx.Owner) == _clan,
Resolve = ctx => ctx.Engine.TurnOneDamageFaceUp(ctx.Owner), Resolve = ctx => ctx.Engine.TurnOneDamageFaceUp(ctx.Owner),
}; };

View File

@ -98,7 +98,7 @@ public sealed class StarlightUnicornScript : CardScript
yield return new AbilityDefinition yield return new AbilityDefinition
{ {
Trigger = AbilityTrigger.OnPlace, Trigger = AbilityTrigger.OnPlace,
Condition = ctx => !ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner), Condition = ctx => ctx.Engine.IsRearGuardOf(ctx.Source, ctx.Owner),
Resolve = ctx => Resolve = ctx =>
{ {
var slots = new List<int>(); var slots = new List<int>();
@ -261,7 +261,7 @@ public sealed class OpponentRetireBonusScript : CardScript
{ {
Trigger = AbilityTrigger.OnOpponentRetire, Trigger = AbilityTrigger.OnOpponentRetire,
Condition = ctx => Condition = ctx =>
!ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner) && ctx.Engine.IsRearGuardOf(ctx.Source, ctx.Owner) &&
ctx.Phase == Phase.Main, ctx.Phase == Phase.Main,
Resolve = ctx => ctx.Engine.AddPower( Resolve = ctx => ctx.Engine.AddPower(
ctx.Source, ctx.Source,

View File

@ -563,6 +563,25 @@ public sealed class GameEngine
.Sum(c => c.PowerBonus?.Invoke(context) ?? 0); .Sum(c => c.PowerBonus?.Invoke(context) ?? 0);
} }
private bool CanBeBoosted(CardInstance unit)
{
var owner = OwnerOf(unit);
if (owner < 0)
return true;
var nameId = State.Catalog.CardOf(unit.PrintingId).NameId;
if (!_scripts.TryGetValue(nameId, out var script))
return true;
return script.CanBeBoosted(new AbilityContext
{
Engine = this,
Source = unit,
Owner = owner,
Phase = State.Phase,
});
}
public int OwnerOf(CardInstance unit) public int OwnerOf(CardInstance unit)
{ {
for (var i = 0; i < 2; i++) for (var i = 0; i < 2; i++)
@ -580,6 +599,9 @@ public sealed class GameEngine
public bool IsVanguardOf(CardInstance unit, int player) => public bool IsVanguardOf(CardInstance unit, int player) =>
ReferenceEquals(State.Players[player].Vanguard, unit); ReferenceEquals(State.Players[player].Vanguard, unit);
public bool IsRearGuardOf(CardInstance unit, int player) =>
Array.IndexOf(State.Players[player].RearGuards, unit) >= 0;
public int CountRearGuards(int player, Clan clan) => public int CountRearGuards(int player, Clan clan) =>
State.Players[player].RearGuards.Count(u => State.Players[player].RearGuards.Count(u =>
u is not null && State.Catalog.CardOf(u.PrintingId).Clan == clan); u is not null && State.Catalog.CardOf(u.PrintingId).Clan == clan);
@ -1519,7 +1541,7 @@ public sealed class GameEngine
4 => 2, 4 => 2,
_ => -1, _ => -1,
}; };
if (boosterSlot >= 0) if (boosterSlot >= 0 && CanBeBoosted(attacker))
{ {
var booster = State.GetUnit(player, boosterSlot); var booster = State.GetUnit(player, boosterSlot);
if (booster is not null && !booster.Rested && State.CanBoost(booster)) if (booster is not null && !booster.Rested && State.CanBoost(booster))
@ -2019,6 +2041,9 @@ public sealed class GameEngine
if (!State.CanBoost(booster)) if (!State.CanBoost(booster))
return CommandResult.Reject("Эта карта не может поддерживать."); return CommandResult.Reject("Эта карта не может поддерживать.");
if (!CanBeBoosted(attacker))
return CommandResult.Reject("Этого юнита нельзя поддерживать.");
} }
var attackerPlayer = State.ActivePlayer; var attackerPlayer = State.ActivePlayer;

View File

@ -844,6 +844,22 @@ public class GameEngineTests
Assert.DoesNotContain(engine.State.Players[0].Soul, c => c.PrintingId == Barcgal); Assert.DoesNotContain(engine.State.Players[0].Soul, c => c.PrintingId == Barcgal);
} }
[Fact]
public void Conroe_Offers_Optional_Call_When_Ridden_Over()
{
var engine = StartedEngine();
SetVanguard(engine, 0, Conroe);
Give(engine, 0, Conroe);
Assert.True(engine.Execute(new RideCommand(Conroe)).Accepted);
Assert.NotNull(engine.State.PendingAbility);
Assert.True(engine.Execute(new ResolveAbilityCommand(true)).Accepted);
Assert.NotNull(engine.State.PendingChoice);
Assert.True(engine.Execute(new ChooseCommand(0)).Accepted);
Assert.Equal("BT01/016EN", engine.State.Players[0].RearGuards[0]?.PrintingId);
}
[Fact] [Fact]
public void Barcgal_Decline_Keeps_Card_In_Soul() public void Barcgal_Decline_Keeps_Card_In_Soul()
{ {
@ -936,6 +952,23 @@ public class GameEngineTests
Assert.Equal(0, engine.State.Players[0].RearGuards[3]!.Modifiers.Where(m => m.EffectId == "ability").Sum(m => m.Power)); Assert.Equal(0, engine.State.Players[0].RearGuards[3]!.Modifiers.Where(m => m.EffectId == "ability").Sum(m => m.Power));
} }
[Fact]
public void Alfred_On_Vanguard_Cannot_Be_Boosted()
{
var engine = StartedEngine();
engine.Execute(new SkipRideCommand());
SetVanguard(engine, 0, Alfred);
Give(engine, 0, Marron);
Assert.True(engine.Execute(new CallCommand(Marron, 1)).Accepted);
engine.State.Players[0].RearGuards[1]!.Rested = false;
SetVanguard(engine, 1, Gallatin);
Assert.True(engine.Execute(new EnterBattleCommand()).Accepted);
Assert.False(engine.Execute(new AttackCommand(-1, -1, 1)).Accepted);
}
[Fact] [Fact]
public void Alfred_Gets_2000_Per_RoyalPaladin_RearGuard() public void Alfred_Gets_2000_Per_RoyalPaladin_RearGuard()
{ {