Add explicit ride controls, dynamic mulligan and hideable action panel
CI / test (push) Successful in 1m55s
Details
CI / test (push) Successful in 1m55s
Details
This commit is contained in:
parent
a3d2157925
commit
489d4eb394
|
|
@ -83,6 +83,9 @@ namespace Vanguard.UI
|
||||||
private ZoneBlockStripView _selfZones;
|
private ZoneBlockStripView _selfZones;
|
||||||
private ZoneBlockStripView _opponentZones;
|
private ZoneBlockStripView _opponentZones;
|
||||||
private CardActionMenuView _cardMenu;
|
private CardActionMenuView _cardMenu;
|
||||||
|
private RectTransform _intentPanel;
|
||||||
|
private Text _intentPanelToggleLabel;
|
||||||
|
private bool _intentPanelVisible = true;
|
||||||
private RectTransform _phasePanel;
|
private RectTransform _phasePanel;
|
||||||
private RectTransform _combatStats;
|
private RectTransform _combatStats;
|
||||||
private Text _combatStatsText;
|
private Text _combatStatsText;
|
||||||
|
|
@ -128,11 +131,23 @@ namespace Vanguard.UI
|
||||||
top, "Clock", "—", 22, Color.white, TextAnchor.MiddleRight);
|
top, "Clock", "—", 22, Color.white, TextAnchor.MiddleRight);
|
||||||
UiKit.SetPreferredHeight(_clock, 44f, 0f);
|
UiKit.SetPreferredHeight(_clock, 44f, 0f);
|
||||||
|
|
||||||
|
var toggle = UiKit.CreateButton(
|
||||||
|
top,
|
||||||
|
"ToggleIntentPanel",
|
||||||
|
"Скрыть панель",
|
||||||
|
ToggleIntentPanel,
|
||||||
|
16);
|
||||||
|
var toggleElement = UiKit.SetPreferredHeight(toggle, 44f, 0f);
|
||||||
|
toggleElement.preferredWidth = 168f;
|
||||||
|
_intentPanelToggleLabel = toggle.GetComponentInChildren<Text>();
|
||||||
|
UiKit.MakeLiteral(_intentPanelToggleLabel);
|
||||||
|
|
||||||
_concede = UiKit.CreateButton(top, "Concede", "Сдаться", OnConcedeClicked, 18);
|
_concede = UiKit.CreateButton(top, "Concede", "Сдаться", OnConcedeClicked, 18);
|
||||||
_concedeLabel = _concede.GetComponentInChildren<Text>();
|
_concedeLabel = _concede.GetComponentInChildren<Text>();
|
||||||
UiKit.SetPreferredHeight(_concede, 44f, 0f);
|
UiKit.SetPreferredHeight(_concede, 44f, 0f);
|
||||||
|
|
||||||
var right = UiKit.CreatePanel(_root, "IntentPanel", Panel);
|
var right = UiKit.CreatePanel(_root, "IntentPanel", Panel);
|
||||||
|
_intentPanel = right;
|
||||||
right.anchorMin = new Vector2(1f, 0f);
|
right.anchorMin = new Vector2(1f, 0f);
|
||||||
right.anchorMax = new Vector2(1f, 1f);
|
right.anchorMax = new Vector2(1f, 1f);
|
||||||
right.pivot = new Vector2(1f, 0.5f);
|
right.pivot = new Vector2(1f, 0.5f);
|
||||||
|
|
@ -236,6 +251,14 @@ namespace Vanguard.UI
|
||||||
_autoFinishMulliganAt = 0f;
|
_autoFinishMulliganAt = 0f;
|
||||||
_autoEndBattleArmed = false;
|
_autoEndBattleArmed = false;
|
||||||
_autoEndBattleAt = 0f;
|
_autoEndBattleAt = 0f;
|
||||||
|
_intentPanelVisible = true;
|
||||||
|
if (_intentPanel != null)
|
||||||
|
_intentPanel.gameObject.SetActive(true);
|
||||||
|
if (_intentPanelToggleLabel != null)
|
||||||
|
{
|
||||||
|
_intentPanelToggleLabel.text = "Скрыть панель";
|
||||||
|
UiKit.MakeLiteral(_intentPanelToggleLabel);
|
||||||
|
}
|
||||||
_inputLock.ReleaseAll();
|
_inputLock.ReleaseAll();
|
||||||
_feedbackLockHeld = false;
|
_feedbackLockHeld = false;
|
||||||
_logLines.Clear();
|
_logLines.Clear();
|
||||||
|
|
@ -847,6 +870,20 @@ namespace Vanguard.UI
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToggleIntentPanel()
|
||||||
|
{
|
||||||
|
_intentPanelVisible = !_intentPanelVisible;
|
||||||
|
if (_intentPanel != null)
|
||||||
|
_intentPanel.gameObject.SetActive(_intentPanelVisible);
|
||||||
|
if (_intentPanelToggleLabel != null)
|
||||||
|
{
|
||||||
|
_intentPanelToggleLabel.text = _intentPanelVisible
|
||||||
|
? "Скрыть панель"
|
||||||
|
: "Показать панель";
|
||||||
|
UiKit.MakeLiteral(_intentPanelToggleLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void OnConcedeClicked()
|
private void OnConcedeClicked()
|
||||||
{
|
{
|
||||||
if (_view == null || _finished)
|
if (_view == null || _finished)
|
||||||
|
|
@ -1247,9 +1284,26 @@ namespace Vanguard.UI
|
||||||
UiKit.SetPreferredHeight(button, 46f);
|
UiKit.SetPreferredHeight(button, 46f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddGuardButton(IReadOnlyList<GuardIntent> guards)
|
private void AddRideButtons(IReadOnlyList<RideIntent> rides)
|
||||||
{
|
{
|
||||||
if (guards == null || guards.Count == 0)
|
foreach (var ride in rides
|
||||||
|
.GroupBy(item => item.PrintingId)
|
||||||
|
.Select(group => group.First()))
|
||||||
|
{
|
||||||
|
var captured = ride;
|
||||||
|
var button = UiKit.CreateButton(
|
||||||
|
_intents,
|
||||||
|
"Ride_" + captured.PrintingId,
|
||||||
|
"Сменить: " + CardName(captured.PrintingId),
|
||||||
|
() => OnIntentClicked(captured),
|
||||||
|
16);
|
||||||
|
button.interactable = !_inputLock.IsLocked;
|
||||||
|
UiKit.SetPreferredHeight(button, 46f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddGuardButton(IReadOnlyList<GuardIntent> guards)
|
||||||
|
{ if (guards == null || guards.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var button = UiKit.CreateButton(
|
var button = UiKit.CreateButton(
|
||||||
|
|
@ -1327,19 +1381,44 @@ namespace Vanguard.UI
|
||||||
if (distinct.Count == 0)
|
if (distinct.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (distinct.Count == 1)
|
var free = distinct.FirstOrDefault(
|
||||||
|
variant => string.IsNullOrEmpty(variant.DiscardPrintingId));
|
||||||
|
var discardVariants = distinct
|
||||||
|
.Where(variant => !string.IsNullOrEmpty(variant.DiscardPrintingId))
|
||||||
|
.ToList();
|
||||||
|
if (discardVariants.Count == 0)
|
||||||
{
|
{
|
||||||
OnIntentClicked(distinct[0]);
|
OnIntentClicked(free ?? distinct[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = distinct
|
var items = new List<CardActionMenuItem>();
|
||||||
.Select(variant => new CardActionMenuItem(
|
if (free != null)
|
||||||
string.IsNullOrEmpty(variant.DiscardPrintingId)
|
items.Add(new CardActionMenuItem("Без сброса", () => OnIntentClicked(free)));
|
||||||
? "Без сброса"
|
|
||||||
: "Сбросить: " + CardName(variant.DiscardPrintingId),
|
var hand = PlayerHand();
|
||||||
() => OnIntentClicked(variant)))
|
foreach (var card in hand
|
||||||
.ToList();
|
.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)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (items.Count == 0)
|
||||||
|
{
|
||||||
|
items.AddRange(discardVariants.Select(variant =>
|
||||||
|
new CardActionMenuItem(
|
||||||
|
"Сбросить: " + CardName(variant.DiscardPrintingId),
|
||||||
|
() => OnIntentClicked(variant))));
|
||||||
|
}
|
||||||
|
|
||||||
ShowChoiceMenu("Сброс для способности", items);
|
ShowChoiceMenu("Сброс для способности", items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1354,29 +1433,48 @@ namespace Vanguard.UI
|
||||||
if (distinct.Count == 0)
|
if (distinct.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (distinct.Count > 1)
|
var discardVariants = distinct
|
||||||
{
|
.Where(guard => !string.IsNullOrEmpty(guard.DiscardPrintingId))
|
||||||
distinct = distinct
|
.ToList();
|
||||||
.Where(guard => !string.IsNullOrEmpty(guard.DiscardPrintingId))
|
if (discardVariants.Count == 0)
|
||||||
.ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (distinct.Count == 1)
|
|
||||||
{
|
{
|
||||||
OnIntentClicked(distinct[0]);
|
OnIntentClicked(distinct[0]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var items = distinct
|
var items = new List<CardActionMenuItem>();
|
||||||
.Select(guard => new CardActionMenuItem(
|
var hand = PlayerHand();
|
||||||
string.IsNullOrEmpty(guard.DiscardPrintingId)
|
foreach (var card in hand
|
||||||
? "Guard без сброса"
|
.GroupBy(item => item.PrintingId)
|
||||||
: "Perfect Guard: сбросить " + CardName(guard.DiscardPrintingId),
|
.Select(group => group.First()))
|
||||||
() => OnIntentClicked(guard)))
|
{
|
||||||
.ToList();
|
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);
|
ShowChoiceMenu("Guard", items, onClosed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<CardView> PlayerHand()
|
||||||
|
{
|
||||||
|
var player = _view?.Players.FirstOrDefault(item => item.Index == _view.You);
|
||||||
|
return player?.Hand ?? new List<CardView>();
|
||||||
|
}
|
||||||
|
|
||||||
private void ShowChoiceMenu(
|
private void ShowChoiceMenu(
|
||||||
string title,
|
string title,
|
||||||
IReadOnlyList<CardActionMenuItem> items,
|
IReadOnlyList<CardActionMenuItem> items,
|
||||||
|
|
@ -2204,10 +2302,12 @@ namespace Vanguard.UI
|
||||||
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();
|
||||||
var guards = playable.OfType<GuardIntent>().ToList();
|
var guards = playable.OfType<GuardIntent>().ToList();
|
||||||
|
var rides = playable.OfType<RideIntent>().ToList();
|
||||||
var board = playable
|
var board = playable
|
||||||
.Where(intent => IsBoardIntent(intent)
|
.Where(intent => IsBoardIntent(intent)
|
||||||
&& intent is not ActivateAbilityIntent
|
&& intent is not ActivateAbilityIntent
|
||||||
&& intent is not GuardIntent)
|
&& intent is not GuardIntent
|
||||||
|
&& intent is not RideIntent)
|
||||||
.ToList();
|
.ToList();
|
||||||
var global = playable
|
var global = playable
|
||||||
.Where(intent => !IsBoardIntent(intent)
|
.Where(intent => !IsBoardIntent(intent)
|
||||||
|
|
@ -2274,11 +2374,27 @@ namespace Vanguard.UI
|
||||||
var exchange = UiKit.CreateButton(
|
var exchange = UiKit.CreateButton(
|
||||||
_intents,
|
_intents,
|
||||||
"MulliganExchange",
|
"MulliganExchange",
|
||||||
"Маллиган: " + _mulliganSelected.Count + " карт",
|
"Заменить " + _mulliganSelected.Count + " карт",
|
||||||
SendMulligan,
|
SendMulligan,
|
||||||
16);
|
16);
|
||||||
UiKit.SetPreferredHeight(exchange, 44f);
|
UiKit.SetPreferredHeight(exchange, 44f);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var finishMulligan = (view.LegalIntents ?? new List<IntentPayload>())
|
||||||
|
.OfType<FinishMulliganIntent>()
|
||||||
|
.FirstOrDefault();
|
||||||
|
if (finishMulligan != null)
|
||||||
|
{
|
||||||
|
var finishButton = UiKit.CreateButton(
|
||||||
|
_intents,
|
||||||
|
"MulliganFinish",
|
||||||
|
"Закончить муллиган",
|
||||||
|
() => OnIntentClicked(finishMulligan),
|
||||||
|
16);
|
||||||
|
UiKit.SetPreferredHeight(finishButton, 44f);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsAbilityDamageTargeting())
|
if (IsAbilityDamageTargeting())
|
||||||
|
|
@ -2325,6 +2441,9 @@ namespace Vanguard.UI
|
||||||
if (guards.Count > 0)
|
if (guards.Count > 0)
|
||||||
AddGuardButton(guards);
|
AddGuardButton(guards);
|
||||||
|
|
||||||
|
if (rides.Count > 0)
|
||||||
|
AddRideButtons(rides);
|
||||||
|
|
||||||
foreach (var intent in global)
|
foreach (var intent in global)
|
||||||
AddIntentButton(intent);
|
AddIntentButton(intent);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,11 @@ namespace Vanguard.UI
|
||||||
["Защититься: "] = "Guard: ",
|
["Защититься: "] = "Guard: ",
|
||||||
["Защита"] = "Guard",
|
["Защита"] = "Guard",
|
||||||
["Не защищаться"] = "Do not guard",
|
["Не защищаться"] = "Do not guard",
|
||||||
|
["Скрыть панель"] = "Hide panel",
|
||||||
|
["Показать панель"] = "Show panel",
|
||||||
|
["Сменить: "] = "Ride: ",
|
||||||
|
["Заменить "] = "Replace ",
|
||||||
|
["Закончить муллиган"] = "Finish mulligan",
|
||||||
["Действия"] = "Actions",
|
["Действия"] = "Actions",
|
||||||
["Переместить"] = "Move",
|
["Переместить"] = "Move",
|
||||||
["Подробнее"] = "Details",
|
["Подробнее"] = "Details",
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ MVP: бустер **BT01** + триал-деки **TD01/TD02** — 94 карты
|
||||||
|
|
||||||
| Проверка | Результат |
|
| Проверка | Результат |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Тесты сервера | **186/186** (141 правил/контрактов + 45 сервер) |
|
| Тесты сервера | **187/187** (142 правил/контрактов + 45 сервер) |
|
||||||
| `validate` | 0 ошибок, 0 предупреждений |
|
| `validate` | 0 ошибок, 0 предупреждений |
|
||||||
| `translations check` | 94/94 |
|
| `translations check` | 94/94 |
|
||||||
| `stress 1000` | 1000/1000 партий, 0 ошибок |
|
| `stress 1000` | 1000/1000 партий, 0 ошибок |
|
||||||
|
|
@ -987,17 +987,22 @@ Battle; Boot и общие сервисы остаются персистент
|
||||||
Атака по-прежнему запрещена.
|
Атака по-прежнему запрещена.
|
||||||
- Смена: в фазе Ride предлагается и карта того же ранга, и следующего
|
- Смена: в фазе Ride предлагается и карта того же ранга, и следующего
|
||||||
(раньше legal-команды содержали только `+1`, хотя движок разрешал same
|
(раньше legal-команды содержали только `+1`, хотя движок разрешал same
|
||||||
grade).
|
grade). В панели действий есть явная кнопка «Сменить: карта».
|
||||||
|
- Маллиган: кнопка динамическая — без выбора «Закончить муллиган», с
|
||||||
|
выбором «Заменить X карт».
|
||||||
- Способности: `ActivateAbilityIntent` группируются по карте; в UI одна
|
- Способности: `ActivateAbilityIntent` группируются по карте; в UI одна
|
||||||
кнопка «Активировать способность». Если целей/вариантов несколько —
|
кнопка «Активировать способность». Если целей/вариантов несколько —
|
||||||
отдельный overlay, затем выбор карты для сброса (без duplicate-кнопок).
|
отдельный overlay, затем выбор карты для сброса (без duplicate-кнопок).
|
||||||
- Guard: варианты Perfect Guard/сброса показываются отдельным overlay;
|
- Guard: варианты Perfect Guard/сброса показываются отдельным overlay;
|
||||||
случайный первый интент больше не выбирается. В фазе защиты есть
|
случайный первый интент больше не выбирается. В фазе защиты есть
|
||||||
«Защититься» (при наличии допустимых карт) и «Не защищаться».
|
«Защититься» (при наличии допустимых карт) и «Не защищаться». Сброс
|
||||||
|
выбирается из карт в руке, а не по первому интенту.
|
||||||
|
- Правую панель действий можно временно скрыть/показать кнопкой в верхней
|
||||||
|
строке, чтобы видеть поле.
|
||||||
- Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X;
|
- Камера боя: `(0, 4.2, -8)` → цель `(0, 0, 0)`, только наклон по X;
|
||||||
поле по центру, без бокового смещения.
|
поле по центру, без бокового смещения.
|
||||||
- UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight
|
- UI: reference-разрешение **1920×1080**, CanvasScaler `matchWidthOrHeight
|
||||||
= 0.5`; standalone принудительно `1920×1080 FullScreenWindow`,
|
= 0.5`; standalone принудительно `1920×1080 FullScreenWindow`,
|
||||||
`defaultIsNativeResolution = 0`.
|
`defaultIsNativeResolution = 0`.
|
||||||
- Проверки: сервер **141** правил + **45** серверных, `validate` и
|
- Проверки: сервер **142** правил + **45** серверных, `validate` и
|
||||||
`translations check` OK; клиент EditMode **30/30**.
|
`translations check` OK; клиент EditMode **30/30**.
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
матчмейкинг, WebSocket-матч, история, реплеи.
|
матчмейкинг, WebSocket-матч, история, реплеи.
|
||||||
- Orleans включается `Orleans:Enabled`; в compose включён. Один silo,
|
- Orleans включается `Orleans:Enabled`; в compose включён. Один silo,
|
||||||
in-process реестр соединений.
|
in-process реестр соединений.
|
||||||
- Тесты: **141** правил + **45** серверных, CI зелёный.
|
- Тесты: **142** правил + **45** серверных, CI зелёный.
|
||||||
- Стенд: `http://192.168.31.158:18080`, `/health` → ok.
|
- Стенд: `http://192.168.31.158:18080`, `/health` → ok.
|
||||||
- Документация: `docs/server-api.md`, `docs/protocol.md`,
|
- Документация: `docs/server-api.md`, `docs/protocol.md`,
|
||||||
`docs/game-rules.md`, `docs/HANDOFF.md`.
|
`docs/game-rules.md`, `docs/HANDOFF.md`.
|
||||||
|
|
@ -84,12 +84,15 @@
|
||||||
`P/C`.
|
`P/C`.
|
||||||
- Смена: в фазе Ride доступны карты того же ранга и на 1 выше. На первом
|
- Смена: в фазе Ride доступны карты того же ранга и на 1 выше. На первом
|
||||||
ходу переход в Battle не предлагается; атака по-прежнему запрещена.
|
ходу переход в Battle не предлагается; атака по-прежнему запрещена.
|
||||||
|
В панели действий отображается явная кнопка «Сменить: карта».
|
||||||
- Способности: кнопка «Активировать способность» одна на карту; при
|
- Способности: кнопка «Активировать способность» одна на карту; при
|
||||||
нескольких вариантах открывается overlay выбора, затем — выбор карты
|
нескольких вариантах открывается overlay выбора, затем — выбор карты
|
||||||
для сброса. Perfect Guard также спрашивает карту для сброса отдельным
|
для сброса. Perfect Guard также спрашивает карту для сброса отдельным
|
||||||
overlay, а не выбирает случайную.
|
overlay, а не выбирает случайную.
|
||||||
- В фазе защиты доступны «Защититься» (если есть допустимые карты) и
|
- В фазе защиты доступны «Защититься» (если есть допустимые карты) и
|
||||||
«Не защищаться»; выбор карты защиты и сброса идёт через overlay.
|
«Не защищаться»; выбор карты защиты и сброса идёт через overlay.
|
||||||
|
- Правую панель действий можно временно скрыть кнопкой «Скрыть панель»
|
||||||
|
в верхней строке; поле остаётся видимым, вернуть — «Показать панель».
|
||||||
- Статистика карт — отдельное UI-наложение: своя панель снизу слева,
|
- Статистика карт — отдельное UI-наложение: своя панель снизу слева,
|
||||||
противника — сверху справа. Блоки `Рука/Колода/Дроп/Душа/Урон/Защита/Триггер`
|
противника — сверху справа. Блоки `Рука/Колода/Дроп/Душа/Урон/Защита/Триггер`
|
||||||
кликабельны и открывают общий горизонтальный `CardListView`; из списка
|
кликабельны и открывают общий горизонтальный `CardListView`; из списка
|
||||||
|
|
@ -105,7 +108,9 @@
|
||||||
выбранная карта переворачивается рубашкой. Heal выбирает карту урона;
|
выбранная карта переворачивается рубашкой. Heal выбирает карту урона;
|
||||||
Counter Blast — открытую карту до активации способности.
|
Counter Blast — открытую карту до активации способности.
|
||||||
- Маллиган: один раз на игрока, оба игрока; выбранные карты уходят в низ
|
- Маллиган: один раз на игрока, оба игрока; выбранные карты уходят в низ
|
||||||
колоды, добираются новые, затем shuffle. Клик по руке выбирает карты.
|
колоды, добираются новые, затем shuffle. Клик по руке выбирает карты;
|
||||||
|
кнопка динамическая: без выбора — «Закончить муллиган», с выбором —
|
||||||
|
«Заменить X карт».
|
||||||
- Анимации: event-фидбек (атака, guard, drive, урон, retire, heal) поверх
|
- Анимации: event-фидбек (атака, guard, drive, урон, retire, heal) поверх
|
||||||
стола; ввод заблокирован до завершения через `InputLockService`, очередь
|
стола; ввод заблокирован до завершения через `InputLockService`, очередь
|
||||||
событий + кнопка «Пропустить анимации». Длительности и скорости карт —
|
событий + кнопка «Пропустить анимации». Длительности и скорости карт —
|
||||||
|
|
|
||||||
|
|
@ -131,6 +131,28 @@ public class GameEngineTests
|
||||||
Assert.True(engine.Execute(new RideCommand(Conroe)).Accepted);
|
Assert.True(engine.Execute(new RideCommand(Conroe)).Accepted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Ride_Can_Reach_Second_Grade_On_Next_Turn()
|
||||||
|
{
|
||||||
|
var engine = StartedEngine();
|
||||||
|
Give(engine, 0, Marron);
|
||||||
|
Assert.True(engine.Execute(new RideCommand(Marron)).Accepted);
|
||||||
|
|
||||||
|
engine.Execute(new EndTurnCommand());
|
||||||
|
engine.Execute(new SkipRideCommand());
|
||||||
|
engine.Execute(new EndTurnCommand());
|
||||||
|
|
||||||
|
Give(engine, 0, Gallatin);
|
||||||
|
Assert.Contains(
|
||||||
|
engine.LegalCommands(),
|
||||||
|
command => command is RideCommand ride && ride.PrintingId == Gallatin);
|
||||||
|
|
||||||
|
Assert.True(engine.Execute(new RideCommand(Gallatin)).Accepted);
|
||||||
|
Assert.Equal(
|
||||||
|
2,
|
||||||
|
engine.State.Catalog.CardOf(engine.State.Players[0].Vanguard!.PrintingId).Grade);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Both_Players_Mulligan_Once_Then_First_Player_Starts()
|
public void Both_Players_Mulligan_Once_Then_First_Player_Starts()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue