Add card-tile overlay for deck search choices
CI / test (push) Has been cancelled
Details
CI / test (push) Has been cancelled
Details
This commit is contained in:
parent
f1ad0a75d3
commit
bf41fbe6d0
|
|
@ -71,6 +71,10 @@ namespace Vanguard.UI
|
||||||
private Text _abilityOverlayName;
|
private Text _abilityOverlayName;
|
||||||
private RectTransform _abilityOverlayButtons;
|
private RectTransform _abilityOverlayButtons;
|
||||||
private int _abilityPromptInstanceId;
|
private int _abilityPromptInstanceId;
|
||||||
|
private RectTransform _cardChoiceOverlay;
|
||||||
|
private Text _cardChoiceTitle;
|
||||||
|
private RectTransform _cardChoiceContent;
|
||||||
|
private string _cardChoiceKey;
|
||||||
private MatchConnectionInfo _info;
|
private MatchConnectionInfo _info;
|
||||||
private MatchView _view;
|
private MatchView _view;
|
||||||
private bool _connecting;
|
private bool _connecting;
|
||||||
|
|
@ -238,6 +242,7 @@ namespace Vanguard.UI
|
||||||
BuildPhasePanel();
|
BuildPhasePanel();
|
||||||
BuildCombatStats();
|
BuildCombatStats();
|
||||||
BuildAbilityOverlay();
|
BuildAbilityOverlay();
|
||||||
|
BuildCardChoiceOverlay();
|
||||||
BuildReconnectOverlay();
|
BuildReconnectOverlay();
|
||||||
_cardMenu = new CardActionMenuView();
|
_cardMenu = new CardActionMenuView();
|
||||||
_cardMenu.Build(_root);
|
_cardMenu.Build(_root);
|
||||||
|
|
@ -310,6 +315,7 @@ namespace Vanguard.UI
|
||||||
_board3D?.SetVisible(true);
|
_board3D?.SetVisible(true);
|
||||||
_overlays?.CloseAll();
|
_overlays?.CloseAll();
|
||||||
HideAbilityOverlay();
|
HideAbilityOverlay();
|
||||||
|
HideCardChoiceOverlay();
|
||||||
_selfZones?.Update(null, self: true);
|
_selfZones?.Update(null, self: true);
|
||||||
_opponentZones?.Update(null, self: false);
|
_opponentZones?.Update(null, self: false);
|
||||||
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
_topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId;
|
||||||
|
|
@ -343,6 +349,7 @@ namespace Vanguard.UI
|
||||||
_skipFeedback.gameObject.SetActive(false);
|
_skipFeedback.gameObject.SetActive(false);
|
||||||
_overlays?.CloseAll();
|
_overlays?.CloseAll();
|
||||||
HideAbilityOverlay();
|
HideAbilityOverlay();
|
||||||
|
HideCardChoiceOverlay();
|
||||||
_cardMenu?.Hide();
|
_cardMenu?.Hide();
|
||||||
_combatStats?.gameObject.SetActive(false);
|
_combatStats?.gameObject.SetActive(false);
|
||||||
_board3D?.SetVisible(false);
|
_board3D?.SetVisible(false);
|
||||||
|
|
@ -363,6 +370,7 @@ namespace Vanguard.UI
|
||||||
RefreshActionPrompt();
|
RefreshActionPrompt();
|
||||||
SanitizeInputLocks();
|
SanitizeInputLocks();
|
||||||
SyncAbilityPrompt();
|
SyncAbilityPrompt();
|
||||||
|
SyncCardChoicePrompt();
|
||||||
UpdateAutoPhases();
|
UpdateAutoPhases();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -385,6 +393,8 @@ namespace Vanguard.UI
|
||||||
_inputLock.Release("card-menu");
|
_inputLock.Release("card-menu");
|
||||||
if (_abilityOverlay == null || !_abilityOverlay.gameObject.activeSelf)
|
if (_abilityOverlay == null || !_abilityOverlay.gameObject.activeSelf)
|
||||||
_inputLock.Release("ability");
|
_inputLock.Release("ability");
|
||||||
|
if (_cardChoiceOverlay == null || !_cardChoiceOverlay.gameObject.activeSelf)
|
||||||
|
_inputLock.Release("card-choice");
|
||||||
if (!_feedbackPlaying && _feedbackQueue.Count == 0)
|
if (!_feedbackPlaying && _feedbackQueue.Count == 0)
|
||||||
{
|
{
|
||||||
_feedbackLockHeld = false;
|
_feedbackLockHeld = false;
|
||||||
|
|
@ -747,6 +757,7 @@ namespace Vanguard.UI
|
||||||
|
|
||||||
Render();
|
Render();
|
||||||
SyncAbilityPrompt();
|
SyncAbilityPrompt();
|
||||||
|
SyncCardChoicePrompt();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEvents(ServerEvents events)
|
private void OnEvents(ServerEvents events)
|
||||||
|
|
@ -1307,6 +1318,175 @@ namespace Vanguard.UI
|
||||||
|| prompt.IndexOf("rear", StringComparison.OrdinalIgnoreCase) >= 0);
|
|| prompt.IndexOf("rear", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildCardChoiceOverlay()
|
||||||
|
{
|
||||||
|
_cardChoiceOverlay = UiKit.CreatePanel(
|
||||||
|
_root,
|
||||||
|
"CardChoiceOverlay",
|
||||||
|
new Color(0f, 0f, 0f, 0.78f));
|
||||||
|
var card = UiKit.CreateCenteredCard(
|
||||||
|
_cardChoiceOverlay,
|
||||||
|
"CardChoiceCard",
|
||||||
|
new Vector2(1120f, 620f),
|
||||||
|
Panel);
|
||||||
|
UiKit.AddVerticalLayout(card, 20, 10);
|
||||||
|
|
||||||
|
var title = UiKit.CreateText(
|
||||||
|
card,
|
||||||
|
"CardChoiceTitle",
|
||||||
|
"ВЫБОР КАРТЫ",
|
||||||
|
22,
|
||||||
|
new Color(1f, 0.80f, 0.42f),
|
||||||
|
TextAnchor.MiddleCenter);
|
||||||
|
title.fontStyle = FontStyle.Bold;
|
||||||
|
UiKit.SetPreferredHeight(title, 34f);
|
||||||
|
|
||||||
|
_cardChoiceTitle = UiKit.CreateText(
|
||||||
|
card,
|
||||||
|
"CardChoicePrompt",
|
||||||
|
string.Empty,
|
||||||
|
17,
|
||||||
|
new Color(0.82f, 0.88f, 0.98f),
|
||||||
|
TextAnchor.MiddleCenter);
|
||||||
|
UiKit.SetPreferredHeight(_cardChoiceTitle, 42f);
|
||||||
|
|
||||||
|
var scroll = UiKit.CreateGridScrollView(
|
||||||
|
card,
|
||||||
|
"CardChoiceScroll",
|
||||||
|
new Vector2(160f, 230f),
|
||||||
|
new Vector2(12f, 12f),
|
||||||
|
out _cardChoiceContent);
|
||||||
|
var element = scroll.gameObject.AddComponent<LayoutElement>();
|
||||||
|
element.minHeight = 400f;
|
||||||
|
element.flexibleHeight = 1f;
|
||||||
|
|
||||||
|
_cardChoiceOverlay.gameObject.SetActive(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetChoicePrintingId(
|
||||||
|
ChoiceOptionView option,
|
||||||
|
out string printingId)
|
||||||
|
{
|
||||||
|
printingId = null;
|
||||||
|
var label = option?.Label ?? string.Empty;
|
||||||
|
var open = label.LastIndexOf('(');
|
||||||
|
var close = label.LastIndexOf(')');
|
||||||
|
if (open < 0 || close <= open + 1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
printingId = label.Substring(open + 1, close - open - 1).Trim();
|
||||||
|
return printingId.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsCardChoice()
|
||||||
|
{
|
||||||
|
var options = _view?.Choice?.Options;
|
||||||
|
return options is { Count: > 0 }
|
||||||
|
&& options.All(option => TryGetChoicePrintingId(option, out _));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SyncCardChoicePrompt()
|
||||||
|
{
|
||||||
|
if (_cardChoiceOverlay == null || _sending)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var choice = _view?.Choice;
|
||||||
|
if (choice == null
|
||||||
|
|| _view == null
|
||||||
|
|| _view.ActivePlayer != _view.You
|
||||||
|
|| !IsCardChoice())
|
||||||
|
{
|
||||||
|
HideCardChoiceOverlay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = (choice.Prompt ?? string.Empty) + "|" +
|
||||||
|
string.Join(",", choice.Options.Select(option => option.Label));
|
||||||
|
if (_cardChoiceOverlay.gameObject.activeSelf
|
||||||
|
&& string.Equals(_cardChoiceKey, key, StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cardChoiceKey = key;
|
||||||
|
_cardChoiceTitle.text = choice.Prompt ?? "Выберите карту";
|
||||||
|
UiKit.MakeLiteral(_cardChoiceTitle);
|
||||||
|
ClearChildren(_cardChoiceContent);
|
||||||
|
|
||||||
|
for (var i = 0; i < choice.Options.Count; i++)
|
||||||
|
{
|
||||||
|
var index = i;
|
||||||
|
var option = choice.Options[i];
|
||||||
|
if (!TryGetChoicePrintingId(option, out var printingId))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var tile = UiKit.CreatePanel(
|
||||||
|
_cardChoiceContent,
|
||||||
|
"CardChoice_" + index,
|
||||||
|
new Color(0.05f, 0.075f, 0.11f, 0.98f));
|
||||||
|
var tileElement = tile.gameObject.AddComponent<LayoutElement>();
|
||||||
|
tileElement.preferredWidth = 160f;
|
||||||
|
tileElement.preferredHeight = 230f;
|
||||||
|
tileElement.flexibleWidth = 0f;
|
||||||
|
tileElement.flexibleHeight = 0f;
|
||||||
|
|
||||||
|
var button = tile.gameObject.AddComponent<Button>();
|
||||||
|
button.targetGraphic = tile.GetComponent<Image>();
|
||||||
|
button.onClick.AddListener(() =>
|
||||||
|
{
|
||||||
|
HideCardChoiceOverlay();
|
||||||
|
OnIntentClicked(
|
||||||
|
new ChooseIntent { Index = index },
|
||||||
|
ignoreInputLock: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
var art = new GameObject(
|
||||||
|
"Art", typeof(RectTransform), typeof(Image));
|
||||||
|
var artRect = (RectTransform)art.transform;
|
||||||
|
artRect.SetParent(tile, false);
|
||||||
|
artRect.anchorMin = Vector2.zero;
|
||||||
|
artRect.anchorMax = Vector2.one;
|
||||||
|
artRect.offsetMin = new Vector2(4f, 34f);
|
||||||
|
artRect.offsetMax = new Vector2(-4f, -4f);
|
||||||
|
var artImage = art.GetComponent<Image>();
|
||||||
|
artImage.raycastTarget = false;
|
||||||
|
artImage.preserveAspect = true;
|
||||||
|
var meta = ResolveCatalogItem(printingId);
|
||||||
|
var imageKey = meta?.ImageKey
|
||||||
|
?? CardImageService.ImageKeyForPrinting(printingId);
|
||||||
|
CardImageService.Apply(artImage, imageKey, null);
|
||||||
|
if (artImage.sprite == null)
|
||||||
|
artImage.color = CardArt.ClanColor(meta?.Clan);
|
||||||
|
|
||||||
|
var label = UiKit.CreateText(
|
||||||
|
tile,
|
||||||
|
"Name",
|
||||||
|
CardName(printingId),
|
||||||
|
13,
|
||||||
|
new Color(0.92f, 0.95f, 1f),
|
||||||
|
TextAnchor.MiddleCenter);
|
||||||
|
var labelRect = label.rectTransform;
|
||||||
|
labelRect.anchorMin = new Vector2(0f, 0f);
|
||||||
|
labelRect.anchorMax = new Vector2(1f, 0f);
|
||||||
|
labelRect.pivot = new Vector2(0.5f, 0f);
|
||||||
|
labelRect.offsetMin = new Vector2(4f, 2f);
|
||||||
|
labelRect.offsetMax = new Vector2(-4f, 32f);
|
||||||
|
label.raycastTarget = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_cardChoiceOverlay.SetAsLastSibling();
|
||||||
|
_cardChoiceOverlay.gameObject.SetActive(true);
|
||||||
|
_inputLock.Acquire("card-choice");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HideCardChoiceOverlay()
|
||||||
|
{
|
||||||
|
if (_cardChoiceOverlay == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_cardChoiceKey = null;
|
||||||
|
_cardChoiceOverlay.gameObject.SetActive(false);
|
||||||
|
_inputLock.Release("card-choice");
|
||||||
|
}
|
||||||
|
|
||||||
private void BuildReconnectOverlay()
|
private void BuildReconnectOverlay()
|
||||||
{
|
{
|
||||||
_reconnectOverlay = UiKit.CreatePanel(
|
_reconnectOverlay = UiKit.CreatePanel(
|
||||||
|
|
@ -2346,7 +2526,9 @@ namespace Vanguard.UI
|
||||||
}
|
}
|
||||||
else if (_view.Choice != null && _view.ActivePlayer == _view.You)
|
else if (_view.Choice != null && _view.ActivePlayer == _view.You)
|
||||||
{
|
{
|
||||||
title = IsRearGuardChoice() ? "ВЫБОР КРУГА" : "ВЫБОР";
|
title = IsRearGuardChoice()
|
||||||
|
? "ВЫБОР КРУГА"
|
||||||
|
: IsCardChoice() ? "ВЫБОР КАРТЫ" : "ВЫБОР";
|
||||||
hint = _view.Choice.Prompt;
|
hint = _view.Choice.Prompt;
|
||||||
}
|
}
|
||||||
else if (_view.ActivePlayer != _view.You)
|
else if (_view.ActivePlayer != _view.You)
|
||||||
|
|
@ -3116,7 +3298,8 @@ namespace Vanguard.UI
|
||||||
&& !IsPhaseIntent(intent)
|
&& !IsPhaseIntent(intent)
|
||||||
&& intent is not ActivateAbilityIntent
|
&& intent is not ActivateAbilityIntent
|
||||||
&& intent is not ResolveAbilityIntent
|
&& intent is not ResolveAbilityIntent
|
||||||
&& !(intent is ChooseIntent && IsRearGuardChoice()))
|
&& !(intent is ChooseIntent
|
||||||
|
&& (IsRearGuardChoice() || IsCardChoice())))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (_pendingIntents.Count > 0)
|
if (_pendingIntents.Count > 0)
|
||||||
|
|
|
||||||
|
|
@ -385,6 +385,58 @@ namespace Vanguard.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Card_Choice_Shows_Overlay_With_Card_Tiles()
|
||||||
|
{
|
||||||
|
var config = new AppConfig();
|
||||||
|
config.Load();
|
||||||
|
var tokens = new TokenStore();
|
||||||
|
var api = new ApiClient(config, tokens);
|
||||||
|
var player = new PlayerState();
|
||||||
|
var auth = new AuthService(api, tokens, player);
|
||||||
|
var meta = new MetaService(api, player);
|
||||||
|
var canvas = UiKit.CreateCanvas("Card Choice Test Canvas");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var battle = new BattleView(canvas.transform, meta, () => { });
|
||||||
|
var match = new MatchView
|
||||||
|
{
|
||||||
|
You = 0,
|
||||||
|
ActivePlayer = 0,
|
||||||
|
Choice = new ChoiceView
|
||||||
|
{
|
||||||
|
Player = 0,
|
||||||
|
Prompt = "Найдите Future Knight, Llew или Flogal",
|
||||||
|
Options = new List<ChoiceOptionView>
|
||||||
|
{
|
||||||
|
new() { Slot = 0, Label = "Flogal (BT01/046EN)" },
|
||||||
|
new() { Slot = 1, Label = "Future Knight, Llew (BT01/045EN)" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
LegalIntents = new List<IntentPayload>(),
|
||||||
|
};
|
||||||
|
|
||||||
|
typeof(BattleView)
|
||||||
|
.GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.SetValue(battle, match);
|
||||||
|
typeof(BattleView)
|
||||||
|
.GetMethod("SyncCardChoicePrompt", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||||
|
.Invoke(battle, null);
|
||||||
|
|
||||||
|
var overlay = Resources.FindObjectsOfTypeAll<GameObject>()
|
||||||
|
.First(go => go.name == "CardChoiceOverlay");
|
||||||
|
Assert.IsTrue(overlay.activeSelf);
|
||||||
|
Assert.IsTrue(Resources.FindObjectsOfTypeAll<GameObject>()
|
||||||
|
.Any(go => go.name == "CardChoice_1"));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Object.DestroyImmediate(canvas.gameObject);
|
||||||
|
Cleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void Action_Prompt_Shows_Current_Required_Action()
|
public void Action_Prompt_Shows_Current_Required_Action()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1052,5 +1052,7 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
передний RC и Авангард (центральный задний круг).
|
передний RC и Авангард (центральный задний круг).
|
||||||
- Barcgal: добавлена ACT-способность `[Rest]` — поиск Llew/Flogal в колоде,
|
- Barcgal: добавлена ACT-способность `[Rest]` — поиск Llew/Flogal в колоде,
|
||||||
выбор круга и призыв; AUTO-призыв Barcgal из души после рейда сохранён.
|
выбор круга и призыв; AUTO-призыв Barcgal из души после рейда сохранён.
|
||||||
|
Найденные карты показываются модальным оверлеем с карточными плитками
|
||||||
|
(арт + имя), выбор — кликом по карте, без списка кнопок.
|
||||||
- Проверки: сервер **144** правил + **45** серверных, `validate` и
|
- Проверки: сервер **144** правил + **45** серверных, `validate` и
|
||||||
`translations check` OK; клиент EditMode **34/34**.
|
`translations check` OK; клиент EditMode **35/35**.
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,8 @@
|
||||||
после чего атаковать можно только поддерживаемой картой; передний круг →
|
после чего атаковать можно только поддерживаемой картой; передний круг →
|
||||||
карта противника объявляет атаку. Авангард поддерживается центральным
|
карта противника объявляет атаку. Авангард поддерживается центральным
|
||||||
задним кругом. Barcgal имеет ACT `[Rest]` — поиск Llew/Flogal в колоде с
|
задним кругом. Barcgal имеет ACT `[Rest]` — поиск Llew/Flogal в колоде с
|
||||||
выбором круга призыва.
|
выбором круга призыва; найденные карты показываются оверлеем с плитками
|
||||||
|
карт и выбираются кликом по карте.
|
||||||
- Реальные арты: `imageKey` из `/api/cards` + `CardImageService` ищет
|
- Реальные арты: `imageKey` из `/api/cards` + `CardImageService` ищет
|
||||||
`Resources/CardArt/{imageKey}`. Скачаны все 124 арта. Импорт:
|
`Resources/CardArt/{imageKey}`. Скачаны все 124 арта. Импорт:
|
||||||
`tools/import_card_art.py`.
|
`tools/import_card_art.py`.
|
||||||
|
|
@ -226,7 +227,7 @@ Standalone Windows:
|
||||||
-executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat
|
-executeMethod Vanguard.Editor.SceneSmoke.RunPlaymat
|
||||||
```
|
```
|
||||||
|
|
||||||
Клиентские проверки: **34/34** EditMode (сборка всех экранов, импорт арта,
|
Клиентские проверки: **35/35** EditMode (сборка всех экранов, импорт арта,
|
||||||
перевод статических/динамических строк, live-переключение языка, рендер
|
перевод статических/динамических строк, live-переключение языка, рендер
|
||||||
3D-доски с уроном и отдыхающей картой, геометрия поля, правила
|
3D-доски с уроном и отдыхающей картой, геометрия поля, правила
|
||||||
перемещения, рука в поле зрения камеры, обмен колоды/дропа, триггер-зона,
|
перемещения, рука в поле зрения камеры, обмен колоды/дропа, триггер-зона,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue