VanguardReMaster/.opencode/skills/vanguard-remaster/card-implementation.md

194 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Реализация карт и эффектов
Источники: `content/sets/*.yaml` (`textEn`), `docs/ability-implementation.md`,
`server/src/Vanguard.Rules/Engine/Abilities/*`.
## 1. Данные карты
- `CardDefinition` — логическая карта по `nameId`: имя, тип, grade, power,
shield, critical, trigger, clan, `IsSentinel`, `IsRestraint`,
`BoostOverride`, `InterceptOverride`, `CanSnipeBackRow`, тексты.
- `CardPrinting` — конкретный принт: `printingId`, `nameId`, редкость,
`imageKey`, параллельный арт.
- В YAML добавляй/правь карту в `content/sets/{SET}.yaml`; текст — `textEn`.
Русский — `content/translations/ru.yaml`, не дублируй в set-файле.
- Статы/текст — не в ScriptableObject. SO в клиенте — визуал/конфиг, данные
карт приходят из каталога/сервера.
- Лимит 4 копий считается по `nameId`; `printingId` — единица коллекции/
принта. Параллельный SP — отдельный принт, но та же логическая карта.
## 2. Один текст — одна реализация
Каждая текстовая способность карты должна иметь ровно одну реализацию:
1. `CardScript` — `Abilities` (AUTO), `Activated` (ACT), `Continuous` (CONT);
2. общий движок: Sentinel, Heal/Trigger, Tejas `CanSnipeBackRow`,
Boost/Intercept, Restraint;
3. флаг данных, если поведение полностью описывается данными.
Если текст есть, а реализации нет — это баг. Если реализация дублирует уже
существующий общий механизм — это тоже баг. Реестр:
`CardScripts.Default` в `CardScripts.cs` + `CardScriptsBT01.cs` +
`CardScriptsTD.cs`.
## 3. AUTO
```csharp
public override IEnumerable<AbilityDefinition> Abilities
{
get
{
yield return new AbilityDefinition
{
Trigger = AbilityTrigger.OnPlace,
Optional = true,
Condition = ctx => ctx.Engine.IsRearGuardOf(ctx.Source, ctx.Owner)
&& ctx.Catalog.CardOf(ctx.Source.PrintingId).Clan == Clan.Kagero,
CanPay = ctx => ctx.Engine.CanCounterBlast(ctx.Owner, 2),
Pay = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2),
Resolve = ctx => { /* менять состояние только через Engine */ },
};
}
}
```
Правила:
- `Trigger` — только реальное окно события из `AbilityTrigger`; не используй
«примерно подходящий» триггер.
- Проверяй зону источника: `IsVanguardOf`, `IsRearGuardOf`, `IsInHand`;
`OnPlace` не должен срабатывать от VC, если текст требует RC.
- `Condition` — проверка события/состояния; `CanPay` — можно ли оплатить;
`Pay` — оплата; `Resolve` — эффект. Не смешивай оплату и проверку.
- `Optional = true` — движок спросит «активировать/пропустить»; без оплаты
эффект не выполняется.
- `OnRideOver` — событие старой карты, `Other` — новая карта. `OnBoost` —
бустер, `Other` — атакуемый; `OnBoosted` — атакуемый, `Other` — бустер.
- `OnAttackHit` разрешается после урона/помещения карты в Damage и после
damage-триггеров; не переноси это окно в момент объявления атаки.
- `OnOpponentRetire` ищет способности на поле и в руке; не добавляй отдельный
обход, если общий движок уже это делает.
- AUTO не вызывает UI напрямую; он меняет состояние и добавляет события.
Примеры: `ConroeScript` (`OnRideOver` + `AskEmptyRearGuardChoice`),
`GancelotScript` (ACT из VC, CB2, soul condition), `BarcgalScript`
(optional call + ACT search).
## 4. ACT
```csharp
public override IEnumerable<ActivatedAbility> Activated
{
get
{
yield return new ActivatedAbility
{
CanActivate = ctx => ctx.Engine.IsVanguardOf(ctx.Source, ctx.Owner)
&& ctx.Engine.CanCounterBlast(ctx.Owner, 2),
Resolve = ctx => ctx.Engine.CounterBlast(ctx.Owner, 2),
};
}
}
```
- `CanActivate` — только условия; оплата и эффект — в `Resolve` (или
`Pay` для AUTO).
- Флаги: `RestsSelf`, `RetiresSelf`, `RequiresDiscard`, `FromHand`,
`FromDropZone`.
- `RestsSelf`/`RetiresSelf` из руки/дропа не активируются, если текст этого
не разрешает; движок проверяет это в `AddActivatedCommands`/`Activate`.
- `RequiresDiscard` — клиент выбирает карту в руке; сервер получает
`DiscardPrintingId` и проверяет наличие.
- ACT из руки/дропа попадает в `LegalCommands` только в разрешённой фазе;
клиент показывает действие у карты, не отдельным глобальным списком.
- Несколько валидных ACT на карте: клиент показывает меню/оверлей выбора,
а не отправляет случайную.
## 5. CONT
- Сила/крит: `ContinuousAbility` с `Applies`, `PowerBonus`,
`CriticalBonus`.
- Исключения, не выражаемые бонусом: `RestrictionType`
(`CannotAttack`, `CannotBoost`, `CannotStand`, `CannotIntercept`,
`CannotBeChosen`) или `CardScript.CanBeBoosted`.
- CONT пересчитывается из состояния каждый раз; не храни вычисленную силу как
истину.
- Источник/срок модификатора обязательны: `AddPower(..., effectId,
ModifierDuration...)`.
- Для «нельзя поддерживать этот юнит» используй `CanBeBoosted`, а не
`CannotBoost` (это разные стороны).
## 6. Выборы и цели
Разрешённые helper'ы:
- `AskUnitChoice(player, opponent, prompt, slots, onChosen)`;
- `AskUnitMultiChoice(...)`;
- `AskHandCardChoice(...)`;
- `AskEmptyRearGuardChoice(...)`;
- `AskDamageChoice(...)` — внутренний для урона;
- `SearchDeckToHand/ToCall/ToRide` — поиск с автоматическим исходом:
игрок не просматривает колоду вручную; нет карт → shuffle + уведомление;
одна → сразу в руку/на поле; несколько → выбор; разные арты одной карты
(`printingId`) считаются разными результатами.
Не выбирай карту/цель кнопками в UI вместо объекта. Клиент подсвечивает
валидные объекты и отправляет индекс/`damageIndex`; сервер проверяет по
своему состоянию.
## 7. Стоимости
- **Counter Blast**: `CanCounterBlast`, `CounterBlast`; при выборе карт
`DamageIndexes` (multi-target), старый `DamageIndex` — совместимость.
- **Soul Blast/Charge, Counter Charge**: общие методы `GameEngine`; не
дублируй их в скрипте карты.
- **Discard**: `RequiresDiscard` + `DiscardPrintingId`; карта того же клана —
только если текст/Perfect Guard это требует.
- **Rest/Retire self**: флаги `RestsSelf`/`RetiresSelf`.
- **Field units**: выбирай через `AskUnitChoice`/`AskUnitMultiChoice`, не
списком кнопок.
- **[O]** Multi-target Soul Blast и отдельный `CostPaymentOverlay` пока не
реализованы; не описывай их как готовые.
## 8. Порядок, отмена, жизненный цикл
- Движок запрещает посторонние команды во время `Step.Trigger/Ability/Choice`;
каждая цепочка должна завершиться или отменить выбор.
- `_resolvingDepth` не даёт новым эффектам исполняться рекурсивно; они
попадают в `PendingQueue` и разрешаются после текущего.
- Новые срабатывания внутри эффекта не запускай вручную; используй
`TriggerFromCard`/`TriggerAll`.
- `ModifierDuration` задаёт срок; не удаляй модификаторы вручную, если
движок уже чистит их по фазе.
- `Optional` без оплаты не должен оставлять состояние pending.
- Если эффект просит выбор, `Resolve` должен дойти до `Step.Choice` и
вернуться через `ChooseCommand`; не продолжай цепочку до выбора.
- Ошибки/некорректные индексы — `CommandResult.Reject`; не бросай исключения
наружу из игрового действия.
- Не подписывай карты на Unity-события и не храни ссылки на UI в Rules.
## 9. Частые ошибки (из аудита)
- `OnPlace` срабатывал от VC у карт, которым нужен RC (Starlight Unicorn,
Hungry Dumpty, Clay Doll Mechanic) — проверяй зону.
- Joka/Rakshasa получали бонус, находясь в руке — проверяй `IsRearGuardOf`.
- Conroe не имел AUTO `OnRideOver` — добавляй оба окна, если текст требует.
- Maiden of Libra не проверяла клан авангарда — проверяй условие текста.
- Alfred: CONT-запрет поддержки авангарда — через `CanBeBoosted`, не через
силу.
- Perfect Guard: без оплаты 0 щита и нет nullify; не давай щит «на всякий
случай».
- Heal: не проси цель, если урон меньше соперника или зона пуста.
- Draw: не открывай вторую цель; сразу тяни карту.
## 10. Чеклист новой карты
1. Добавь/исправь YAML, `textEn`; RU — в translations.
2. Разбери текст на AUTO/ACT/CONT/общие механики.
3. Реализуй ровно одну реализацию; проверь зону/условие/optional/стоимость.
4. Добавь тест хотя бы на один триггер/активацию/условие.
5. Проверь весь `CardScripts.Default` на текст без скрипта и дубли.
6. Запусти серверные тесты (на home), `validate`, `translations check`.
7. Для клиентских изменений — EditMode, smoke, при необходимости standalone.
8. Не тестируй геймплей вручную за пользователя; он проверяет сам.