Select discard cards from hand and expose ride in phase panel
CI / test (push) Successful in 2m43s
Details
CI / test (push) Successful in 2m43s
Details
This commit is contained in:
parent
e81b966867
commit
6d8882ba2f
|
|
@ -78,6 +78,12 @@ namespace Vanguard.UI
|
||||||
private long _clockServerMs;
|
private long _clockServerMs;
|
||||||
private long _clockReceivedMs;
|
private long _clockReceivedMs;
|
||||||
private readonly List<IntentPayload> _pendingIntents = new List<IntentPayload>();
|
private readonly List<IntentPayload> _pendingIntents = new List<IntentPayload>();
|
||||||
|
private readonly List<ActivateAbilityIntent> _discardAbilityChoices =
|
||||||
|
new List<ActivateAbilityIntent>();
|
||||||
|
private readonly List<GuardIntent> _discardGuardChoices = new List<GuardIntent>();
|
||||||
|
private bool _discardChoiceActive;
|
||||||
|
private string _discardPrompt;
|
||||||
|
private Action _discardCancelAction;
|
||||||
private int _pendingBoosterSlot = -1;
|
private int _pendingBoosterSlot = -1;
|
||||||
private int? _triggerPowerSlot;
|
private int? _triggerPowerSlot;
|
||||||
private readonly HashSet<int> _mulliganSelected = new HashSet<int>();
|
private readonly HashSet<int> _mulliganSelected = new HashSet<int>();
|
||||||
|
|
@ -249,6 +255,11 @@ namespace Vanguard.UI
|
||||||
_reconnect.gameObject.SetActive(false);
|
_reconnect.gameObject.SetActive(false);
|
||||||
_result.gameObject.SetActive(false);
|
_result.gameObject.SetActive(false);
|
||||||
_pendingIntents.Clear();
|
_pendingIntents.Clear();
|
||||||
|
_discardChoiceActive = false;
|
||||||
|
_discardAbilityChoices.Clear();
|
||||||
|
_discardGuardChoices.Clear();
|
||||||
|
_discardCancelAction = null;
|
||||||
|
_discardPrompt = null;
|
||||||
_highlightSlots.Clear();
|
_highlightSlots.Clear();
|
||||||
_mulliganSelected.Clear();
|
_mulliganSelected.Clear();
|
||||||
_mulliganUsed = false;
|
_mulliganUsed = false;
|
||||||
|
|
@ -984,6 +995,9 @@ namespace Vanguard.UI
|
||||||
if (_inputLock.IsLocked && intent is not ConcedeIntent)
|
if (_inputLock.IsLocked && intent is not ConcedeIntent)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (_discardChoiceActive)
|
||||||
|
return;
|
||||||
|
|
||||||
if (_abilityDamageIndex >= 0)
|
if (_abilityDamageIndex >= 0)
|
||||||
{
|
{
|
||||||
if (intent is ActivateAbilityIntent activate)
|
if (intent is ActivateAbilityIntent activate)
|
||||||
|
|
@ -1276,6 +1290,12 @@ namespace Vanguard.UI
|
||||||
if (_view == null || _sending || _inputLock.IsLocked)
|
if (_view == null || _sending || _inputLock.IsLocked)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (_discardChoiceActive)
|
||||||
|
{
|
||||||
|
HandleDiscardCardClicked(card);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var targetSelection = _pendingIntents.Count > 0
|
var targetSelection = _pendingIntents.Count > 0
|
||||||
|| IsMulliganSelecting()
|
|| IsMulliganSelecting()
|
||||||
|| IsDamageChoice()
|
|| IsDamageChoice()
|
||||||
|
|
@ -1472,7 +1492,7 @@ namespace Vanguard.UI
|
||||||
|
|
||||||
private void OnBoard3DSlotClicked(BoardCardRef slot)
|
private void OnBoard3DSlotClicked(BoardCardRef slot)
|
||||||
{
|
{
|
||||||
if (_view == null || _sending || _inputLock.IsLocked)
|
if (_view == null || _sending || _inputLock.IsLocked || _discardChoiceActive)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (slot.Zone == BoardZone.Guardian)
|
if (slot.Zone == BoardZone.Guardian)
|
||||||
|
|
@ -1604,34 +1624,29 @@ namespace Vanguard.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = new List<CardActionMenuItem>();
|
|
||||||
if (free != null)
|
if (free != null)
|
||||||
items.Add(new CardActionMenuItem("Без сброса", () => OnIntentClicked(free)));
|
|
||||||
|
|
||||||
var hand = PlayerHand();
|
|
||||||
foreach (var card in hand
|
|
||||||
.GroupBy(item => item.PrintingId)
|
|
||||||
.Select(group => group.First()))
|
|
||||||
{
|
{
|
||||||
var variant = discardVariants.FirstOrDefault(
|
ShowChoiceMenu(
|
||||||
item => item.DiscardPrintingId == card.PrintingId);
|
"Способность",
|
||||||
if (variant == null)
|
new List<CardActionMenuItem>
|
||||||
continue;
|
|
||||||
|
|
||||||
items.Add(new CardActionMenuItem(
|
|
||||||
"Сбросить: " + CardName(card.PrintingId),
|
|
||||||
() => OnIntentClicked(variant)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (items.Count == 0)
|
|
||||||
{
|
{
|
||||||
items.AddRange(discardVariants.Select(variant =>
|
|
||||||
new CardActionMenuItem(
|
new CardActionMenuItem(
|
||||||
"Сбросить: " + CardName(variant.DiscardPrintingId),
|
"Без сброса",
|
||||||
() => OnIntentClicked(variant))));
|
() => OnIntentClicked(free)),
|
||||||
|
new CardActionMenuItem(
|
||||||
|
"Сбросить карту в руке",
|
||||||
|
() => StartDiscardSelection(
|
||||||
|
"Выберите карту в руке для сброса.",
|
||||||
|
discardVariants,
|
||||||
|
null)),
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowChoiceMenu("Сброс для способности", items);
|
StartDiscardSelection(
|
||||||
|
"Выберите карту в руке для сброса.",
|
||||||
|
discardVariants,
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowGuardDiscardChoice(
|
private void ShowGuardDiscardChoice(
|
||||||
|
|
@ -1654,37 +1669,108 @@ namespace Vanguard.UI
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = new List<CardActionMenuItem>();
|
StartDiscardSelection(
|
||||||
var hand = PlayerHand();
|
"Perfect Guard: выберите карту в руке для сброса.",
|
||||||
foreach (var card in hand
|
null,
|
||||||
.GroupBy(item => item.PrintingId)
|
discardVariants,
|
||||||
.Select(group => group.First()))
|
onClosed);
|
||||||
{
|
|
||||||
var variant = discardVariants.FirstOrDefault(
|
|
||||||
guard => guard.DiscardPrintingId == card.PrintingId);
|
|
||||||
if (variant == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
items.Add(new CardActionMenuItem(
|
|
||||||
"Perfect Guard: сбросить " + CardName(card.PrintingId),
|
|
||||||
() => OnIntentClicked(variant)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (items.Count == 0)
|
private void HandleDiscardCardClicked(BoardCardRef card)
|
||||||
{
|
{
|
||||||
items.AddRange(discardVariants.Select(guard =>
|
if (_view == null || card.Zone != BoardZone.Hand || card.Player != _view.You)
|
||||||
new CardActionMenuItem(
|
{
|
||||||
"Perfect Guard: сбросить " + CardName(guard.DiscardPrintingId),
|
SetStatus("Выберите карту в руке для сброса.", new Color(0.98f, 0.82f, 0.42f));
|
||||||
() => OnIntentClicked(guard))));
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowChoiceMenu("Guard", items, onClosed);
|
var printing = card.PrintingId;
|
||||||
|
var ability = _discardAbilityChoices.FirstOrDefault(
|
||||||
|
item => item.DiscardPrintingId == printing);
|
||||||
|
if (ability != null)
|
||||||
|
{
|
||||||
|
FinishDiscardSelection();
|
||||||
|
OnIntentClicked(ability);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CardView> PlayerHand()
|
var guard = _discardGuardChoices.FirstOrDefault(
|
||||||
|
item => item.DiscardPrintingId == printing);
|
||||||
|
if (guard != null)
|
||||||
{
|
{
|
||||||
var player = _view?.Players.FirstOrDefault(item => item.Index == _view.You);
|
FinishDiscardSelection();
|
||||||
return player?.Hand ?? new List<CardView>();
|
OnIntentClicked(guard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetStatus("Эта карта не подходит для сброса.", new Color(1f, 0.55f, 0.45f));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartDiscardSelection(
|
||||||
|
string prompt,
|
||||||
|
IReadOnlyList<ActivateAbilityIntent> abilities,
|
||||||
|
IReadOnlyList<GuardIntent> guards,
|
||||||
|
Action onCancel = null)
|
||||||
|
{
|
||||||
|
_discardChoiceActive = true;
|
||||||
|
_discardPrompt = prompt;
|
||||||
|
_discardCancelAction = onCancel;
|
||||||
|
_discardAbilityChoices.Clear();
|
||||||
|
if (abilities != null)
|
||||||
|
_discardAbilityChoices.AddRange(abilities);
|
||||||
|
_discardGuardChoices.Clear();
|
||||||
|
if (guards != null)
|
||||||
|
_discardGuardChoices.AddRange(guards);
|
||||||
|
|
||||||
|
SetStatus(prompt, new Color(0.98f, 0.82f, 0.42f));
|
||||||
|
RebuildIntents(_view);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FinishDiscardSelection()
|
||||||
|
{
|
||||||
|
_discardChoiceActive = false;
|
||||||
|
_discardAbilityChoices.Clear();
|
||||||
|
_discardGuardChoices.Clear();
|
||||||
|
_discardCancelAction = null;
|
||||||
|
_discardPrompt = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CancelDiscardSelection()
|
||||||
|
{
|
||||||
|
if (!_discardChoiceActive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cancel = _discardCancelAction;
|
||||||
|
FinishDiscardSelection();
|
||||||
|
if (_view != null)
|
||||||
|
{
|
||||||
|
RebuildIntents(_view);
|
||||||
|
SetStatus("Выбор сброса отменён.", Muted);
|
||||||
|
}
|
||||||
|
cancel?.Invoke();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildDiscardPrompt()
|
||||||
|
{
|
||||||
|
var prompt = UiKit.CreateText(
|
||||||
|
_intents,
|
||||||
|
"DiscardPrompt",
|
||||||
|
string.IsNullOrEmpty(_discardPrompt)
|
||||||
|
? "Выберите карту в руке для сброса."
|
||||||
|
: _discardPrompt,
|
||||||
|
16,
|
||||||
|
new Color(0.98f, 0.82f, 0.42f),
|
||||||
|
TextAnchor.UpperLeft);
|
||||||
|
prompt.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||||
|
UiKit.SetPreferredHeight(prompt, 72f);
|
||||||
|
|
||||||
|
var cancel = UiKit.CreateButton(
|
||||||
|
_intents,
|
||||||
|
"DiscardCancel",
|
||||||
|
"Отмена",
|
||||||
|
CancelDiscardSelection,
|
||||||
|
16);
|
||||||
|
UiKit.SetPreferredHeight(cancel, 44f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowChoiceMenu(
|
private void ShowChoiceMenu(
|
||||||
|
|
@ -1898,6 +1984,22 @@ namespace Vanguard.UI
|
||||||
title.fontStyle = FontStyle.Bold;
|
title.fontStyle = FontStyle.Bold;
|
||||||
UiKit.SetPreferredHeight(title, 22f);
|
UiKit.SetPreferredHeight(title, 22f);
|
||||||
|
|
||||||
|
foreach (var ride in (view.LegalIntents ?? new List<IntentPayload>())
|
||||||
|
.OfType<RideIntent>()
|
||||||
|
.GroupBy(item => item.PrintingId)
|
||||||
|
.Select(group => group.First()))
|
||||||
|
{
|
||||||
|
var captured = ride;
|
||||||
|
var rideButton = UiKit.CreateButton(
|
||||||
|
_phasePanel,
|
||||||
|
"Phase_Ride_" + captured.PrintingId,
|
||||||
|
"Сменить: " + CardName(captured.PrintingId),
|
||||||
|
() => OnIntentClicked(captured),
|
||||||
|
14);
|
||||||
|
rideButton.interactable = !_inputLock.IsLocked;
|
||||||
|
UiKit.SetPreferredHeight(rideButton, 40f);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var intent in intents)
|
foreach (var intent in intents)
|
||||||
{
|
{
|
||||||
var captured = intent;
|
var captured = intent;
|
||||||
|
|
@ -2523,6 +2625,12 @@ namespace Vanguard.UI
|
||||||
{
|
{
|
||||||
ClearChildren(_intents);
|
ClearChildren(_intents);
|
||||||
|
|
||||||
|
if (_discardChoiceActive)
|
||||||
|
{
|
||||||
|
BuildDiscardPrompt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var legal = view.LegalIntents ?? new List<IntentPayload>();
|
var legal = view.LegalIntents ?? new List<IntentPayload>();
|
||||||
var playable = legal.Where(intent => intent is not ConcedeIntent).ToList();
|
var playable = legal.Where(intent => intent is not ConcedeIntent).ToList();
|
||||||
var abilities = playable.OfType<ActivateAbilityIntent>().ToList();
|
var abilities = playable.OfType<ActivateAbilityIntent>().ToList();
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,10 @@ namespace Vanguard.UI
|
||||||
["Сброс для способности"] = "Discard for ability",
|
["Сброс для способности"] = "Discard for ability",
|
||||||
["Сбросить: "] = "Discard: ",
|
["Сбросить: "] = "Discard: ",
|
||||||
["Без сброса"] = "No discard",
|
["Без сброса"] = "No discard",
|
||||||
|
["Сбросить карту в руке"] = "Discard a card from hand",
|
||||||
|
["Выберите карту в руке для сброса."] = "Choose a card in hand to discard.",
|
||||||
|
["Perfect Guard: выберите карту в руке для сброса."] = "Perfect Guard: choose a card in hand to discard.",
|
||||||
|
["Способность"] = "Ability",
|
||||||
["Guard без сброса"] = "Guard without discard",
|
["Guard без сброса"] = "Guard without discard",
|
||||||
["Perfect Guard: сбросить "] = "Perfect Guard: discard ",
|
["Perfect Guard: сбросить "] = "Perfect Guard: discard ",
|
||||||
["Защититься"] = "Guard",
|
["Защититься"] = "Guard",
|
||||||
|
|
|
||||||
|
|
@ -987,16 +987,16 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
Атака по-прежнему запрещена.
|
Атака по-прежнему запрещена.
|
||||||
- Смена: в фазе Ride предлагается и карта того же ранга, и следующего
|
- Смена: в фазе Ride предлагается и карта того же ранга, и следующего
|
||||||
(раньше legal-команды содержали только `+1`, хотя движок разрешал same
|
(раньше legal-команды содержали только `+1`, хотя движок разрешал same
|
||||||
grade). В панели действий есть явная кнопка «Сменить: карта».
|
grade). Кнопки «Сменить: карта» есть в панели действий и в «Фазах».
|
||||||
- Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с
|
- Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с
|
||||||
выбором «Заменить X карт».
|
выбором «Заменить X карт».
|
||||||
- Способности: `ActivateAbilityIntent` группируются по карте; в UI одна
|
- Способности: `ActivateAbilityIntent` группируются по карте; в UI одна
|
||||||
кнопка «Активировать способность». Если целей/вариантов несколько —
|
кнопка «Активировать способность». Если вариантов несколько — overlay
|
||||||
отдельный overlay, затем выбор карты для сброса (без duplicate-кнопок).
|
выбора способности; карта для сброса выбирается кликом по руке.
|
||||||
- Guard: варианты Perfect Guard/сброса показываются отдельным overlay;
|
- Guard: варианты Perfect Guard/сброса показываются отдельным overlay;
|
||||||
случайный первый интент больше не выбирается. В фазе защиты есть
|
случайный первый интент больше не выбирается. В фазе защиты есть
|
||||||
«Защититься» (при наличии допустимых карт) и «Не защищаться». Сброс
|
«Защититься» (при наличии допустимых карт) и «Не защищаться». Карта
|
||||||
выбирается из карт в руке, а не по первому интенту.
|
сброса выбирается кликом по карте в руке.
|
||||||
- Правую панель действий можно временно скрыть/показать кнопкой в верхней
|
- Правую панель действий можно временно скрыть/показать кнопкой в верхней
|
||||||
строке, чтобы видеть поле. При скрытии панель фаз и UI противника
|
строке, чтобы видеть поле. При скрытии панель фаз и UI противника
|
||||||
сдвигаются вправо.
|
сдвигаются вправо.
|
||||||
|
|
|
||||||
|
|
@ -84,13 +84,13 @@
|
||||||
`P/C`.
|
`P/C`.
|
||||||
- Смена: в фазе Ride доступны карты того же ранга и на 1 выше. На первом
|
- Смена: в фазе Ride доступны карты того же ранга и на 1 выше. На первом
|
||||||
ходу переход в Battle не предлагается; атака по-прежнему запрещена.
|
ходу переход в Battle не предлагается; атака по-прежнему запрещена.
|
||||||
В панели действий отображается явная кнопка «Сменить: карта».
|
Кнопки «Сменить: карта» есть и в панели действий, и в панели «Фазы».
|
||||||
- Способности: кнопка «Активировать способность» одна на карту; при
|
- Способности: кнопка «Активировать способность» одна на карту; при
|
||||||
нескольких вариантах открывается overlay выбора, затем — выбор карты
|
нескольких вариантах открывается overlay выбора способности. Карта для
|
||||||
для сброса. Perfect Guard также спрашивает карту для сброса отдельным
|
сброса выбирается **кликом по карте в руке**, а не пунктом меню.
|
||||||
overlay, а не выбирает случайную.
|
Perfect Guard также просит выбрать карту сброса в руке.
|
||||||
- В фазе защиты доступны «Защититься» (если есть допустимые карты) и
|
- В фазе защиты доступны «Защититься» (если есть допустимые карты) и
|
||||||
«Не защищаться»; выбор карты защиты и сброса идёт через overlay.
|
«Не защищаться»; карта защиты и карта сброса выбираются в руке.
|
||||||
- Локальный standalone работает в фоне (`runInBackground`), чтобы heartbeat
|
- Локальный standalone работает в фоне (`runInBackground`), чтобы heartbeat
|
||||||
WebSocket не останавливался при потере фокуса.
|
WebSocket не останавливался при потере фокуса.
|
||||||
- При потере соединения весь бой блокируется полноэкранным overlay
|
- При потере соединения весь бой блокируется полноэкранным overlay
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue