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 _clockReceivedMs;
|
||||
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? _triggerPowerSlot;
|
||||
private readonly HashSet<int> _mulliganSelected = new HashSet<int>();
|
||||
|
|
@ -249,6 +255,11 @@ namespace Vanguard.UI
|
|||
_reconnect.gameObject.SetActive(false);
|
||||
_result.gameObject.SetActive(false);
|
||||
_pendingIntents.Clear();
|
||||
_discardChoiceActive = false;
|
||||
_discardAbilityChoices.Clear();
|
||||
_discardGuardChoices.Clear();
|
||||
_discardCancelAction = null;
|
||||
_discardPrompt = null;
|
||||
_highlightSlots.Clear();
|
||||
_mulliganSelected.Clear();
|
||||
_mulliganUsed = false;
|
||||
|
|
@ -984,6 +995,9 @@ namespace Vanguard.UI
|
|||
if (_inputLock.IsLocked && intent is not ConcedeIntent)
|
||||
return;
|
||||
|
||||
if (_discardChoiceActive)
|
||||
return;
|
||||
|
||||
if (_abilityDamageIndex >= 0)
|
||||
{
|
||||
if (intent is ActivateAbilityIntent activate)
|
||||
|
|
@ -1276,6 +1290,12 @@ namespace Vanguard.UI
|
|||
if (_view == null || _sending || _inputLock.IsLocked)
|
||||
return;
|
||||
|
||||
if (_discardChoiceActive)
|
||||
{
|
||||
HandleDiscardCardClicked(card);
|
||||
return;
|
||||
}
|
||||
|
||||
var targetSelection = _pendingIntents.Count > 0
|
||||
|| IsMulliganSelecting()
|
||||
|| IsDamageChoice()
|
||||
|
|
@ -1472,7 +1492,7 @@ namespace Vanguard.UI
|
|||
|
||||
private void OnBoard3DSlotClicked(BoardCardRef slot)
|
||||
{
|
||||
if (_view == null || _sending || _inputLock.IsLocked)
|
||||
if (_view == null || _sending || _inputLock.IsLocked || _discardChoiceActive)
|
||||
return;
|
||||
|
||||
if (slot.Zone == BoardZone.Guardian)
|
||||
|
|
@ -1604,34 +1624,29 @@ namespace Vanguard.UI
|
|||
return;
|
||||
}
|
||||
|
||||
var items = new List<CardActionMenuItem>();
|
||||
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(
|
||||
item => item.DiscardPrintingId == card.PrintingId);
|
||||
if (variant == null)
|
||||
continue;
|
||||
|
||||
items.Add(new CardActionMenuItem(
|
||||
"Сбросить: " + CardName(card.PrintingId),
|
||||
() => OnIntentClicked(variant)));
|
||||
ShowChoiceMenu(
|
||||
"Способность",
|
||||
new List<CardActionMenuItem>
|
||||
{
|
||||
new CardActionMenuItem(
|
||||
"Без сброса",
|
||||
() => OnIntentClicked(free)),
|
||||
new CardActionMenuItem(
|
||||
"Сбросить карту в руке",
|
||||
() => StartDiscardSelection(
|
||||
"Выберите карту в руке для сброса.",
|
||||
discardVariants,
|
||||
null)),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (items.Count == 0)
|
||||
{
|
||||
items.AddRange(discardVariants.Select(variant =>
|
||||
new CardActionMenuItem(
|
||||
"Сбросить: " + CardName(variant.DiscardPrintingId),
|
||||
() => OnIntentClicked(variant))));
|
||||
}
|
||||
|
||||
ShowChoiceMenu("Сброс для способности", items);
|
||||
StartDiscardSelection(
|
||||
"Выберите карту в руке для сброса.",
|
||||
discardVariants,
|
||||
null);
|
||||
}
|
||||
|
||||
private void ShowGuardDiscardChoice(
|
||||
|
|
@ -1654,37 +1669,108 @@ namespace Vanguard.UI
|
|||
return;
|
||||
}
|
||||
|
||||
var items = new List<CardActionMenuItem>();
|
||||
var hand = PlayerHand();
|
||||
foreach (var card in hand
|
||||
.GroupBy(item => item.PrintingId)
|
||||
.Select(group => group.First()))
|
||||
{
|
||||
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)
|
||||
{
|
||||
items.AddRange(discardVariants.Select(guard =>
|
||||
new CardActionMenuItem(
|
||||
"Perfect Guard: сбросить " + CardName(guard.DiscardPrintingId),
|
||||
() => OnIntentClicked(guard))));
|
||||
}
|
||||
|
||||
ShowChoiceMenu("Guard", items, onClosed);
|
||||
StartDiscardSelection(
|
||||
"Perfect Guard: выберите карту в руке для сброса.",
|
||||
null,
|
||||
discardVariants,
|
||||
onClosed);
|
||||
}
|
||||
|
||||
private List<CardView> PlayerHand()
|
||||
private void HandleDiscardCardClicked(BoardCardRef card)
|
||||
{
|
||||
var player = _view?.Players.FirstOrDefault(item => item.Index == _view.You);
|
||||
return player?.Hand ?? new List<CardView>();
|
||||
if (_view == null || card.Zone != BoardZone.Hand || card.Player != _view.You)
|
||||
{
|
||||
SetStatus("Выберите карту в руке для сброса.", new Color(0.98f, 0.82f, 0.42f));
|
||||
return;
|
||||
}
|
||||
|
||||
var printing = card.PrintingId;
|
||||
var ability = _discardAbilityChoices.FirstOrDefault(
|
||||
item => item.DiscardPrintingId == printing);
|
||||
if (ability != null)
|
||||
{
|
||||
FinishDiscardSelection();
|
||||
OnIntentClicked(ability);
|
||||
return;
|
||||
}
|
||||
|
||||
var guard = _discardGuardChoices.FirstOrDefault(
|
||||
item => item.DiscardPrintingId == printing);
|
||||
if (guard != null)
|
||||
{
|
||||
FinishDiscardSelection();
|
||||
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(
|
||||
|
|
@ -1898,6 +1984,22 @@ namespace Vanguard.UI
|
|||
title.fontStyle = FontStyle.Bold;
|
||||
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)
|
||||
{
|
||||
var captured = intent;
|
||||
|
|
@ -2523,6 +2625,12 @@ namespace Vanguard.UI
|
|||
{
|
||||
ClearChildren(_intents);
|
||||
|
||||
if (_discardChoiceActive)
|
||||
{
|
||||
BuildDiscardPrompt();
|
||||
return;
|
||||
}
|
||||
|
||||
var legal = view.LegalIntents ?? new List<IntentPayload>();
|
||||
var playable = legal.Where(intent => intent is not ConcedeIntent).ToList();
|
||||
var abilities = playable.OfType<ActivateAbilityIntent>().ToList();
|
||||
|
|
|
|||
|
|
@ -68,6 +68,10 @@ namespace Vanguard.UI
|
|||
["Сброс для способности"] = "Discard for ability",
|
||||
["Сбросить: "] = "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",
|
||||
["Perfect Guard: сбросить "] = "Perfect Guard: discard ",
|
||||
["Защититься"] = "Guard",
|
||||
|
|
|
|||
|
|
@ -987,16 +987,16 @@ Battle; Boot и общие сервисы остаются персистент
|
|||
Атака по-прежнему запрещена.
|
||||
- Смена: в фазе Ride предлагается и карта того же ранга, и следующего
|
||||
(раньше legal-команды содержали только `+1`, хотя движок разрешал same
|
||||
grade). В панели действий есть явная кнопка «Сменить: карта».
|
||||
grade). Кнопки «Сменить: карта» есть в панели действий и в «Фазах».
|
||||
- Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с
|
||||
выбором «Заменить X карт».
|
||||
- Способности: `ActivateAbilityIntent` группируются по карте; в UI одна
|
||||
кнопка «Активировать способность». Если целей/вариантов несколько —
|
||||
отдельный overlay, затем выбор карты для сброса (без duplicate-кнопок).
|
||||
кнопка «Активировать способность». Если вариантов несколько — overlay
|
||||
выбора способности; карта для сброса выбирается кликом по руке.
|
||||
- Guard: варианты Perfect Guard/сброса показываются отдельным overlay;
|
||||
случайный первый интент больше не выбирается. В фазе защиты есть
|
||||
«Защититься» (при наличии допустимых карт) и «Не защищаться». Сброс
|
||||
выбирается из карт в руке, а не по первому интенту.
|
||||
«Защититься» (при наличии допустимых карт) и «Не защищаться». Карта
|
||||
сброса выбирается кликом по карте в руке.
|
||||
- Правую панель действий можно временно скрыть/показать кнопкой в верхней
|
||||
строке, чтобы видеть поле. При скрытии панель фаз и UI противника
|
||||
сдвигаются вправо.
|
||||
|
|
|
|||
|
|
@ -84,13 +84,13 @@
|
|||
`P/C`.
|
||||
- Смена: в фазе Ride доступны карты того же ранга и на 1 выше. На первом
|
||||
ходу переход в Battle не предлагается; атака по-прежнему запрещена.
|
||||
В панели действий отображается явная кнопка «Сменить: карта».
|
||||
Кнопки «Сменить: карта» есть и в панели действий, и в панели «Фазы».
|
||||
- Способности: кнопка «Активировать способность» одна на карту; при
|
||||
нескольких вариантах открывается overlay выбора, затем — выбор карты
|
||||
для сброса. Perfect Guard также спрашивает карту для сброса отдельным
|
||||
overlay, а не выбирает случайную.
|
||||
нескольких вариантах открывается overlay выбора способности. Карта для
|
||||
сброса выбирается **кликом по карте в руке**, а не пунктом меню.
|
||||
Perfect Guard также просит выбрать карту сброса в руке.
|
||||
- В фазе защиты доступны «Защититься» (если есть допустимые карты) и
|
||||
«Не защищаться»; выбор карты защиты и сброса идёт через overlay.
|
||||
«Не защищаться»; карта защиты и карта сброса выбираются в руке.
|
||||
- Локальный standalone работает в фоне (`runInBackground`), чтобы heartbeat
|
||||
WebSocket не останавливался при потере фокуса.
|
||||
- При потере соединения весь бой блокируется полноэкранным overlay
|
||||
|
|
|
|||
Loading…
Reference in New Issue