Add boost selection, battle feedback, and card art pipeline
CI / test (push) Successful in 9m2s Details

This commit is contained in:
MpakobecEx 2026-10-21 23:57:46 +05:00
parent 17c589ab1e
commit 84e2706d8d
15 changed files with 287 additions and 23 deletions

View File

@ -268,7 +268,8 @@
**Визуальный каркас есть:** доска, рука, карточные рамки/арт-заглушки **Визуальный каркас есть:** доска, рука, карточные рамки/арт-заглушки
по клану, сила/крит, часы, все легальные интенты кнопками, сдача и по клану, сила/крит, часы, все легальные интенты кнопками, сдача и
результат. Реальные арты и анимации — следующие. Финальный визуал — результат. Реальные арты подключаются через `imageKey`, event-фидбек уже
есть; полноценная очередь анимаций — следующая. Финальный визуал —
отдельная 3D-сцена `Battle`, а не текущий Canvas отдельная 3D-сцена `Battle`, а не текущий Canvas
(`docs/client-visual-direction.md`). (`docs/client-visual-direction.md`).
@ -279,6 +280,8 @@
- [x] Старт: маллиган (сервер пока даёт только «закончить маллиган»). - [x] Старт: маллиган (сервер пока даёт только «закончить маллиган»).
- [x] Ввод: кнопки легальных интентов; клик по картам/зонам, - [x] Ввод: кнопки легальных интентов; клик по картам/зонам,
подсветка целей, группировка действий и журнал боя. подсветка целей, группировка действий и журнал боя.
- [x] Boost: выбор карты заднего ряда как поддержки перед атакой.
- [x] Event-фидбек: атака, guard, drive, урон, heal, retire, perfect guard.
- [x] Отображение силы/крита из `MatchView`; модификаторы уже посчитаны сервером. - [x] Отображение силы/крита из `MatchView`; модификаторы уже посчитаны сервером.
- [ ] Очередь анимаций строго по событиям сервера: ride, call, attack, drive, damage, retire, boost, guard. - [ ] Очередь анимаций строго по событиям сервера: ride, call, attack, drive, damage, retire, boost, guard.
- [ ] Скип/ускорение анимаций. - [ ] Скип/ускорение анимаций.

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 1e9c4a7b2d5f4c8e9a3b6d0f7c2e8a51
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7a3d1f8c5e2b4a6d9c0f3b7e1a8d2c64
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,10 @@
# Card art drop-in
Положите сюда изображения карт с именем `{imageKey}.png` (или `.jpg`).
`imageKey` приходит из `GET /api/cards` (например, `bt01_034`).
Unity импортирует файл как Sprite; `CardImageService` подхватит его через
`Resources.Load("CardArt/{imageKey}")`. Если файла нет — используется
процедурный fallback из `CardArt`.
Рекомендуемый размер: 512×720, Sprite (2D and UI), без анимации.

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 0b7e4c1f8a2d4f6e9c3a5b7d1e8f2a46

View File

@ -312,6 +312,7 @@ namespace Vanguard.Api.Contracts
[JsonProperty("hasIntercept")] public bool HasIntercept { get; set; } [JsonProperty("hasIntercept")] public bool HasIntercept { get; set; }
[JsonProperty("isParallel")] public bool IsParallel { get; set; } [JsonProperty("isParallel")] public bool IsParallel { get; set; }
[JsonProperty("basePrintingId")] public string BasePrintingId { get; set; } [JsonProperty("basePrintingId")] public string BasePrintingId { get; set; }
[JsonProperty("imageKey")] public string ImageKey { get; set; }
[JsonProperty("textEn")] public string TextEn { get; set; } [JsonProperty("textEn")] public string TextEn { get; set; }
[JsonProperty("textRu")] public string TextRu { get; set; } [JsonProperty("textRu")] public string TextRu { get; set; }
} }

View File

@ -43,6 +43,11 @@ namespace Vanguard.UI
private readonly RectTransform _result; private readonly RectTransform _result;
private RectTransform _logContent; private RectTransform _logContent;
private Text _resultText; private Text _resultText;
private Text _feedback;
private Image _flash;
private float _feedbackTime;
private float _feedbackDuration;
private Color _feedbackColor;
private readonly List<string> _logLines = new List<string>(); private readonly List<string> _logLines = new List<string>();
private GameConnection _connection; private GameConnection _connection;
@ -60,6 +65,7 @@ namespace Vanguard.UI
private long _clockReceivedMs; private long _clockReceivedMs;
private readonly List<IntentPayload> _pendingIntents = new List<IntentPayload>(); private readonly List<IntentPayload> _pendingIntents = new List<IntentPayload>();
private readonly HashSet<string> _highlightKeys = new HashSet<string>(StringComparer.Ordinal); private readonly HashSet<string> _highlightKeys = new HashSet<string>(StringComparer.Ordinal);
private int _pendingBoosterSlot = -1;
public BattleView(Transform parent, MetaService meta, Action onExit) public BattleView(Transform parent, MetaService meta, Action onExit)
{ {
@ -140,6 +146,23 @@ namespace Vanguard.UI
_result = BuildResultPanel(_root); _result = BuildResultPanel(_root);
_result.gameObject.SetActive(false); _result.gameObject.SetActive(false);
_flash = UiKit.CreatePanel(_root, "BattleFlash", Color.clear).GetComponent<Image>();
_flash.raycastTarget = false;
_flash.gameObject.SetActive(false);
_feedback = UiKit.CreateText(
_root, "BattleFeedback", string.Empty, 44,
Color.white, TextAnchor.MiddleCenter);
_feedback.fontStyle = FontStyle.Bold;
_feedback.raycastTarget = false;
var feedbackRect = _feedback.rectTransform;
feedbackRect.anchorMin = new Vector2(0.5f, 0.5f);
feedbackRect.anchorMax = new Vector2(0.5f, 0.5f);
feedbackRect.pivot = new Vector2(0.5f, 0.5f);
feedbackRect.sizeDelta = new Vector2(900f, 120f);
feedbackRect.anchoredPosition = new Vector2(0f, 130f);
_feedback.gameObject.SetActive(false);
_root.gameObject.SetActive(false); _root.gameObject.SetActive(false);
} }
@ -155,9 +178,13 @@ namespace Vanguard.UI
_result.gameObject.SetActive(false); _result.gameObject.SetActive(false);
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightKeys.Clear(); _highlightKeys.Clear();
_pendingBoosterSlot = -1;
_showAllIntents = false; _showAllIntents = false;
_logLines.Clear(); _logLines.Clear();
RebuildLog(); RebuildLog();
_feedbackTime = 0f;
_feedback.gameObject.SetActive(false);
_flash.gameObject.SetActive(false);
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId; _topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
SetStatus("Подключаемся к матчу…", Muted); SetStatus("Подключаемся к матчу…", Muted);
_root.gameObject.SetActive(true); _root.gameObject.SetActive(true);
@ -180,6 +207,8 @@ namespace Vanguard.UI
_root.gameObject.SetActive(false); _root.gameObject.SetActive(false);
_result.gameObject.SetActive(false); _result.gameObject.SetActive(false);
_feedback.gameObject.SetActive(false);
_flash.gameObject.SetActive(false);
_view = null; _view = null;
} }
@ -192,6 +221,45 @@ namespace Vanguard.UI
} }
UpdateClock(); UpdateClock();
UpdateFeedback();
}
private void ShowFeedback(string text, Color color, float duration = 1.25f, bool flash = false)
{
_feedback.text = text;
_feedbackColor = color;
_feedbackDuration = duration;
_feedbackTime = duration;
_feedback.gameObject.SetActive(true);
_feedback.rectTransform.localScale = Vector3.one * 1.12f;
if (flash)
{
_flash.gameObject.SetActive(true);
_flash.color = new Color(color.r, color.g, color.b, 0.22f);
}
}
private void UpdateFeedback()
{
if (_feedbackTime <= 0f)
return;
_feedbackTime -= Time.unscaledDeltaTime;
var t = Mathf.Clamp01(_feedbackTime / Mathf.Max(0.01f, _feedbackDuration));
var alpha = Mathf.Clamp01(t * 1.8f);
_feedback.color = new Color(_feedbackColor.r, _feedbackColor.g, _feedbackColor.b, alpha);
_feedback.rectTransform.localScale = Vector3.one * Mathf.Lerp(1f, 1.12f, t);
if (_flash != null && _flash.gameObject.activeSelf)
_flash.color = new Color(_feedbackColor.r, _feedbackColor.g, _feedbackColor.b, alpha * 0.22f);
if (_feedbackTime > 0f)
return;
_feedback.gameObject.SetActive(false);
if (_flash != null)
_flash.gameObject.SetActive(false);
} }
private RectTransform BuildResultPanel(Transform parent) private RectTransform BuildResultPanel(Transform parent)
@ -308,6 +376,7 @@ namespace Vanguard.UI
_finished = _view.IsFinished; _finished = _view.IsFinished;
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightKeys.Clear(); _highlightKeys.Clear();
_pendingBoosterSlot = -1;
if (_view.Clock != null) if (_view.Clock != null)
{ {
_clockServerMs = _view.Clock.ServerTimeMs; _clockServerMs = _view.Clock.ServerTimeMs;
@ -326,6 +395,8 @@ namespace Vanguard.UI
var line = Describe(envelope.Event); var line = Describe(envelope.Event);
if (!string.IsNullOrWhiteSpace(line)) if (!string.IsNullOrWhiteSpace(line))
_logLines.Add(line); _logLines.Add(line);
ShowFeedbackFor(envelope.Event);
} }
while (_logLines.Count > 40) while (_logLines.Count > 40)
@ -357,6 +428,51 @@ namespace Vanguard.UI
} }
} }
private void ShowFeedbackFor(EventView view)
{
switch (view)
{
case AttackDeclaredView attack:
ShowFeedback("АТАКА " + SlotName(attack.AttackerSlot)
+ " → " + SlotName(attack.TargetSlot),
new Color(1f, 0.82f, 0.35f), 1.0f);
break;
case GuardAddedView guard:
ShowFeedback("GUARD +" + guard.Shield,
new Color(0.45f, 0.82f, 1f), 1.0f);
break;
case DriveCheckedView drive:
ShowFeedback("DRIVE " + TriggerShort(drive.Trigger.ToString()),
new Color(0.72f, 0.88f, 1f), 1.0f);
break;
case TriggerResolvedView trigger:
ShowFeedback("TRIGGER " + trigger.Trigger,
new Color(0.72f, 0.88f, 1f), 1.1f);
break;
case BattleResolvedView battle:
ShowFeedback(battle.Hit ? "HIT" : "BLOCK",
battle.Hit ? new Color(1f, 0.45f, 0.35f) : new Color(0.45f, 0.82f, 1f),
0.9f);
break;
case DamageTakenView damage:
ShowFeedback("+" + damage.Amount + " DAMAGE",
new Color(1f, 0.30f, 0.26f), 1.2f, flash: true);
break;
case HealedView:
ShowFeedback("HEAL",
new Color(0.45f, 0.95f, 0.62f), 1.1f, flash: true);
break;
case UnitRetiredView:
ShowFeedback("RETIRE",
new Color(1f, 0.55f, 0.30f), 1.0f);
break;
case PerfectGuardView:
ShowFeedback("PERFECT GUARD",
new Color(0.60f, 0.82f, 1f), 1.1f);
break;
}
}
private string Describe(EventView view) private string Describe(EventView view)
{ {
if (view == null) if (view == null)
@ -451,6 +567,7 @@ namespace Vanguard.UI
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightKeys.Clear(); _highlightKeys.Clear();
_pendingBoosterSlot = -1;
_sending = true; _sending = true;
SetStatus("Отправляем: " + Label(intent), Muted); SetStatus("Отправляем: " + Label(intent), Muted);
@ -840,13 +957,21 @@ namespace Vanguard.UI
var art = UiKit.CreatePanel(panel, "Art", Color.white); var art = UiKit.CreatePanel(panel, "Art", Color.white);
UiKit.SetPreferredHeight(art, compact ? 46f : 58f); UiKit.SetPreferredHeight(art, compact ? 46f : 58f);
var artImage = art.GetComponent<Image>(); var artImage = art.GetComponent<Image>();
artImage.sprite = card.FaceUp if (card.FaceUp)
? CardArt.ForCard( {
meta != null ? meta.NameId : card.PrintingId, CardImageService.Apply(
meta != null ? meta.Clan : "None") artImage,
: CardArt.Back; meta != null ? meta.ImageKey : null,
artImage.color = Color.white; CardArt.ForCard(
artImage.preserveAspect = false; meta != null ? meta.NameId : card.PrintingId,
meta != null ? meta.Clan : "None"));
}
else
{
artImage.sprite = CardArt.Back;
artImage.color = Color.white;
artImage.preserveAspect = false;
}
if (card.FaceUp) if (card.FaceUp)
{ {
@ -936,9 +1061,38 @@ namespace Vanguard.UI
if (_pendingIntents.Count > 0) if (_pendingIntents.Count > 0)
{ {
// Клик по своему заднему ряду выбирает карту поддержки.
if (target.Player == _view.You && target.Slot is 0 or 1 or 2)
{
var boosted = _pendingIntents
.OfType<AttackIntent>()
.Where(attack => attack.BoosterSlot == target.Slot)
.ToList();
if (boosted.Count > 0)
{
_pendingBoosterSlot = target.Slot;
RebuildHighlights();
RebuildIntents(_view);
SetStatus(
"Поддержка: " + CardName(target.PrintingId) + ". Выберите цель атаки.",
new Color(0.98f, 0.82f, 0.42f));
return;
}
}
var matches = _pendingIntents var matches = _pendingIntents
.Where(intent => TryGetTarget(intent, out var candidate) && candidate.Matches(target)) .Where(intent => TryGetTarget(intent, out var candidate) && candidate.Matches(target))
.OrderBy(intent => intent is AttackIntent attack && attack.BoosterSlot >= 0 ? 1 : 0) .OrderBy(intent =>
{
if (intent is not AttackIntent attack)
return 2;
if (_pendingBoosterSlot >= 0)
return attack.BoosterSlot == _pendingBoosterSlot ? 0 : 3;
return attack.BoosterSlot < 0 ? 0 : 1;
})
.ToList(); .ToList();
if (matches.Count > 0) if (matches.Count > 0)
@ -988,6 +1142,7 @@ namespace Vanguard.UI
{ {
_pendingIntents.Clear(); _pendingIntents.Clear();
_highlightKeys.Clear(); _highlightKeys.Clear();
_pendingBoosterSlot = -1;
if (rebuild && _view != null) if (rebuild && _view != null)
RebuildIntents(_view); RebuildIntents(_view);
} }
@ -999,6 +1154,15 @@ namespace Vanguard.UI
{ {
if (TryGetTarget(intent, out var target)) if (TryGetTarget(intent, out var target))
_highlightKeys.Add(TargetKey(target.Player, target.Slot, target.PrintingId)); _highlightKeys.Add(TargetKey(target.Player, target.Slot, target.PrintingId));
if (intent is AttackIntent attack && attack.BoosterSlot >= 0 && _view != null)
{
var booster = CardAt(_view.You, attack.BoosterSlot);
_highlightKeys.Add(TargetKey(
_view.You,
attack.BoosterSlot,
booster != null ? booster.PrintingId : null));
}
} }
} }
@ -1138,7 +1302,11 @@ namespace Vanguard.UI
var prompt = UiKit.CreateText( var prompt = UiKit.CreateText(
_intents, _intents,
"TargetPrompt", "TargetPrompt",
"Выберите цель на поле.\nНажмите «Отмена», чтобы снять выбор.", _pendingBoosterSlot >= 0
? "Поддержка: " + CardName(
CardAt(_view.You, _pendingBoosterSlot)?.PrintingId ?? string.Empty)
+ ".\nВыберите цель атаки или «Отмена»."
: "Выберите цель на поле.\nНажмите «Отмена», чтобы снять выбор.",
16, 16,
new Color(0.98f, 0.82f, 0.42f), new Color(0.98f, 0.82f, 0.42f),
TextAnchor.UpperLeft); TextAnchor.UpperLeft);

View File

@ -0,0 +1,44 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
namespace Vanguard.UI
{
/// <summary>
/// Загрузка настоящих артов по imageKey. Соглашение: спрайт лежит в
/// Assets/Vanguard/Resources/CardArt/{imageKey}.png. Пока файла нет —
/// используется процедурный CardArt. Позже этот же вход можно
/// перевести на Addressables без изменения экранов.
/// </summary>
public static class CardImageService
{
private const string ResourceFolder = "CardArt/";
private static readonly Dictionary<string, Sprite> Cache =
new Dictionary<string, Sprite>();
public static void Apply(Image target, string imageKey, Sprite fallback)
{
if (target == null)
return;
target.sprite = fallback;
target.color = Color.white;
target.preserveAspect = false;
if (string.IsNullOrWhiteSpace(imageKey))
return;
if (Cache.TryGetValue(imageKey, out var cached))
{
if (cached != null)
target.sprite = cached;
return;
}
var sprite = Resources.Load<Sprite>(ResourceFolder + imageKey);
Cache[imageKey] = sprite;
if (sprite != null)
target.sprite = sprite;
}
}
}

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4d2c7a9e6b1f4e8da3c5b7f0e9a21d63

View File

@ -504,9 +504,10 @@ namespace Vanguard.UI
return; return;
var meta = _selectedMeta; var meta = _selectedMeta;
_cardArt.sprite = CardArt.ForCard(meta.NameId, meta.Clan); CardImageService.Apply(
_cardArt.color = Color.white; _cardArt,
_cardArt.preserveAspect = false; meta.ImageKey,
CardArt.ForCard(meta.NameId, meta.Clan));
_cardArtName.text = meta.NameEn; _cardArtName.text = meta.NameEn;
_cardTitle.text = meta.NameEn; _cardTitle.text = meta.NameEn;
_cardSubtitle.text = (string.IsNullOrEmpty(meta.NameRu) ? string.Empty : meta.NameRu + " · ") _cardSubtitle.text = (string.IsNullOrEmpty(meta.NameRu) ? string.Empty : meta.NameRu + " · ")
@ -879,9 +880,10 @@ namespace Vanguard.UI
var art = UiKit.CreatePanel(cell, "Art", Color.white); var art = UiKit.CreatePanel(cell, "Art", Color.white);
UiKit.SetPreferredHeight(art, 112f); UiKit.SetPreferredHeight(art, 112f);
var artImage = art.GetComponent<Image>(); var artImage = art.GetComponent<Image>();
artImage.sprite = CardArt.ForCard(meta.NameId, meta.Clan); CardImageService.Apply(
artImage.color = Color.white; artImage,
artImage.preserveAspect = false; meta.ImageKey,
CardArt.ForCard(meta.NameId, meta.Clan));
var artName = UiKit.CreateText( var artName = UiKit.CreateText(
art, "ArtName", meta.NameEn, 12, art, "ArtName", meta.NameEn, 12,
@ -964,11 +966,12 @@ namespace Vanguard.UI
row, "Art", Color.white); row, "Art", Color.white);
SetFixedSize(art, 36f, 50f); SetFixedSize(art, 36f, 50f);
var artImage = art.GetComponent<Image>(); var artImage = art.GetComponent<Image>();
artImage.sprite = CardArt.ForCard( CardImageService.Apply(
meta != null ? meta.NameId : printingId, artImage,
meta != null ? meta.Clan : "None"); meta != null ? meta.ImageKey : null,
artImage.color = Color.white; CardArt.ForCard(
artImage.preserveAspect = false; meta != null ? meta.NameId : printingId,
meta != null ? meta.Clan : "None"));
var artText = UiKit.CreateText( var artText = UiKit.CreateText(
art, "ArtText", meta != null ? "G" + meta.Grade : "?", 13, art, "ArtText", meta != null ? "G" + meta.Grade : "?", 13,

View File

@ -68,6 +68,9 @@ http://localhost:5080
действия, журнал событий и подсказки. действия, журнал событий и подсказки.
- Карты в бою и коллекции: процедурные арты по клану, рамка, имя, ранг, - Карты в бою и коллекции: процедурные арты по клану, рамка, имя, ранг,
триггер, сила/крит, REST и рубашка для скрытых карт. триггер, сила/крит, REST и рубашка для скрытых карт.
- Реальные арты: `CardImageService` ищет спрайт `Resources/CardArt/{imageKey}`;
`imageKey` приходит из `/api/cards`. Пока файлов нет — используется
процедурный fallback.
- Экран входа и главный экран с балансами, MMR и переходами в разделы. - Экран входа и главный экран с балансами, MMR и переходами в разделы.
## Что дальше ## Что дальше

View File

@ -749,6 +749,11 @@ Battle; Boot и общие сервисы остаются персистент
рубашку; `BattleView` показывает рамки, имена, ранги, триггеры, рубашку; `BattleView` показывает рамки, имена, ранги, триггеры,
силу/крит, REST и скрытые карты; поля обоих игроков, рука и панель силу/крит, REST и скрытые карты; поля обоих игроков, рука и панель
действий. Коллекция использует те же арты. действий. Коллекция использует те же арты.
- Реальные арты: `imageKey` в `/api/cards`, `CardImageService` читает
`Resources/CardArt/{imageKey}`; без файлов работает процедурный fallback.
- Boost UI: клик по своему `SUP`-слоту выбирает поддержку, затем цель.
- Event-фидбек: всплывающие атака/guard/drive/урон/heal/retire, не
блокирует ввод.
- Боевой UX: клик по карте/зоне, подсветка целей, отдельная группировка - Боевой UX: клик по карте/зоне, подсветка целей, отдельная группировка
действий, журнал последних событий и подсказки выбора. действий, журнал последних событий и подсказки выбора.
- Поле: `R V R` / `R R R` у каждого игрока, зона урона слева, колода и - Поле: `R V R` / `R R R` у каждого игрока, зона урона слева, колода и

View File

@ -60,13 +60,17 @@
- Поле: у каждого игрока два ряда `R V R` / `R R R`, слева зона урона - Поле: у каждого игрока два ряда `R V R` / `R R R`, слева зона урона
(+soul), справа колода и ниже дроп; у противника зеркально; между (+soul), справа колода и ниже дроп; у противника зеркально; между
полями — зона защиты со щитом и проверками. полями — зона защиты со щитом и проверками.
- Анимации: event-фидбек (атака, guard, drive, урон, retire, heal) поверх
стола, не блокирует ввод.
- Реальные арты: `imageKey` из `/api/cards` + `CardImageService` ищет
`Resources/CardArt/{imageKey}`; без файлов — процедурный fallback.
- Взаимодействие в бою: клик по картам/зонам, подсветка целей, - Взаимодействие в бою: клик по картам/зонам, подсветка целей,
группировка действий (карты → цели, глобальные кнопки отдельно), группировка действий (карты → цели, глобальные кнопки отдельно),
журнал последних событий. журнал последних событий.
- Отдельная сцена `Collection` и `CollectionView` удалены; коллекция - Отдельная сцена `Collection` и `CollectionView` удалены; коллекция
открывается из Lobby кнопкой «Коллекция» (сцена `DeckBuilder`). открывается из Lobby кнопкой «Коллекция» (сцена `DeckBuilder`).
- Не готово: реальные арты/модели/анимации, локализация RU/EN, настройки, - Не готово: файлы реальных артов (пайплайн готов), 3D-модели,
DOTween. полноценная очередь анимаций, локализация RU/EN, настройки, DOTween.
## Запуск ## Запуск

View File

@ -203,6 +203,7 @@ GET /api/cards [Bearer]
"hasIntercept": true, "hasIntercept": true,
"isParallel": false, "isParallel": false,
"basePrintingId": null, "basePrintingId": null,
"imageKey": "bt01_034",
"textEn": "[AUTO](VC/RC):…", "textEn": "[AUTO](VC/RC):…",
"textRu": null "textRu": null
} }

View File

@ -45,6 +45,7 @@ public sealed record CardCatalogItemDto(
bool HasIntercept, bool HasIntercept,
bool IsParallel, bool IsParallel,
string? BasePrintingId, string? BasePrintingId,
string? ImageKey,
string? TextEn, string? TextEn,
string? TextRu); string? TextRu);
@ -111,6 +112,7 @@ public static class MetaEndpoints
card.HasIntercept, card.HasIntercept,
printing.IsParallel, printing.IsParallel,
printing.BasePrintingId, printing.BasePrintingId,
printing.ImageKey,
card.TextEn, card.TextEn,
card.TextRu); card.TextRu);
}) })