diff --git a/AGENTS.md b/AGENTS.md index bb261fb..3818eed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,6 +19,18 @@ подробную историю — в `docs/HANDOFF.md`. - После блока: тесты, `validate`, `translations check`, коммит и push. +## UI и взаимодействие + +- **Выбор делаем интерактивом по объектам:** клик/драг по карте, кругу, + зоне или полю. Не заменяем выбор карты, круга или цели списком кнопок в + интерфейсе. +- Кнопки допустимы только для глобальных действий и подтверждений: + завершить ход, сдаться, отмена, принять/пропустить опциональную + способность. Если выбирается конкретная карта/круг/зона — это делается + кликом по объекту. +- Информационные оверлеи (`ActionPrompt`, подсказки) не должны перехватывать + клики и не дублируют выбор кнопками. + ## Технические ограничения - Не запускать `dotnet` и Docker на рабочем ПК; сервер проверять на diff --git a/client/Assets/Vanguard/Runtime/UI/BattleView.cs b/client/Assets/Vanguard/Runtime/UI/BattleView.cs index c49744c..f3499c3 100644 --- a/client/Assets/Vanguard/Runtime/UI/BattleView.cs +++ b/client/Assets/Vanguard/Runtime/UI/BattleView.cs @@ -71,10 +71,6 @@ namespace Vanguard.UI private Text _abilityOverlayName; private RectTransform _abilityOverlayButtons; private int _abilityPromptInstanceId; - private RectTransform _choiceOverlay; - private Text _choiceOverlayTitle; - private RectTransform _choiceOverlayButtons; - private string _choicePromptKey; private MatchConnectionInfo _info; private MatchView _view; private bool _connecting; @@ -241,7 +237,6 @@ namespace Vanguard.UI BuildPhasePanel(); BuildCombatStats(); BuildAbilityOverlay(); - BuildChoiceOverlay(); BuildReconnectOverlay(); _cardMenu = new CardActionMenuView(); _cardMenu.Build(_root); @@ -313,7 +308,6 @@ namespace Vanguard.UI _board3D?.SetVisible(true); _overlays?.CloseAll(); HideAbilityOverlay(); - HideChoiceOverlay(); _selfZones?.Update(null, self: true); _opponentZones?.Update(null, self: false); _topInfo.text = (info.DeckName ?? string.Empty) + " · " + info.MatchId; @@ -347,7 +341,6 @@ namespace Vanguard.UI _skipFeedback.gameObject.SetActive(false); _overlays?.CloseAll(); HideAbilityOverlay(); - HideChoiceOverlay(); _cardMenu?.Hide(); _combatStats?.gameObject.SetActive(false); _board3D?.SetVisible(false); @@ -368,7 +361,6 @@ namespace Vanguard.UI RefreshActionPrompt(); SanitizeInputLocks(); SyncAbilityPrompt(); - SyncChoicePrompt(); UpdateAutoPhases(); } @@ -391,8 +383,6 @@ namespace Vanguard.UI _inputLock.Release("card-menu"); if (_abilityOverlay == null || !_abilityOverlay.gameObject.activeSelf) _inputLock.Release("ability"); - if (_choiceOverlay == null || !_choiceOverlay.gameObject.activeSelf) - _inputLock.Release("choice"); if (!_feedbackPlaying && _feedbackQueue.Count == 0) { _feedbackLockHeld = false; @@ -755,7 +745,6 @@ namespace Vanguard.UI Render(); SyncAbilityPrompt(); - SyncChoicePrompt(); } private void OnEvents(ServerEvents events) @@ -1307,45 +1296,6 @@ namespace Vanguard.UI _inputLock.Release("ability"); } - private void BuildChoiceOverlay() - { - _choiceOverlay = UiKit.CreatePanel( - _root, - "ChoiceOverlay", - new Color(0f, 0f, 0f, 0.72f)); - var card = UiKit.CreateCenteredCard( - _choiceOverlay, - "ChoiceCard", - new Vector2(760f, 300f), - Panel); - UiKit.AddVerticalLayout(card, 22, 10); - - var title = UiKit.CreateText( - card, - "ChoiceTitle", - "Выбор", - 22, - new Color(1f, 0.78f, 0.42f), - TextAnchor.MiddleCenter); - title.fontStyle = FontStyle.Bold; - UiKit.SetPreferredHeight(title, 34f); - - _choiceOverlayTitle = UiKit.CreateText( - card, - "ChoicePrompt", - string.Empty, - 18, - Color.white, - TextAnchor.MiddleCenter); - UiKit.SetPreferredHeight(_choiceOverlayTitle, 52f); - - _choiceOverlayButtons = UiKit.CreatePanel( - card, "ChoiceButtons", Color.clear); - UiKit.AddHorizontalLayout(_choiceOverlayButtons, 0, 8); - UiKit.SetPreferredHeight(_choiceOverlayButtons, 52f); - _choiceOverlay.gameObject.SetActive(false); - } - private bool IsRearGuardChoice() { var prompt = _view?.Choice?.Prompt; @@ -1354,68 +1304,6 @@ namespace Vanguard.UI || prompt.IndexOf("rear", StringComparison.OrdinalIgnoreCase) >= 0); } - private void SyncChoicePrompt() - { - if (_choiceOverlay == null || _sending) - return; - - var choice = _view?.Choice; - if (choice == null - || _view == null - || _view.ActivePlayer != _view.You - || !IsRearGuardChoice()) - { - HideChoiceOverlay(); - return; - } - - var key = (choice.Prompt ?? string.Empty) + "|" + - string.Join(",", choice.Options.Select(option => option.Slot)); - if (_choiceOverlay.gameObject.activeSelf - && string.Equals(_choicePromptKey, key, StringComparison.Ordinal)) - return; - - _choicePromptKey = key; - _choiceOverlayTitle.text = choice.Prompt ?? "Выберите круг"; - UiKit.MakeLiteral(_choiceOverlayTitle); - ClearChildren(_choiceOverlayButtons); - - for (var i = 0; i < choice.Options.Count; i++) - { - var index = i; - var option = choice.Options[i]; - var button = UiKit.CreateButton( - _choiceOverlayButtons, - "Choice_" + option.Slot, - string.IsNullOrWhiteSpace(option.Label) - ? "Круг " + (option.Slot + 1) - : option.Label, - () => - { - HideChoiceOverlay(); - OnIntentClicked( - new ChooseIntent { Index = index }, - ignoreInputLock: true); - }, - 16); - UiKit.SetPreferredHeight(button, 46f); - } - - _choiceOverlay.SetAsLastSibling(); - _choiceOverlay.gameObject.SetActive(true); - _inputLock.Acquire("choice"); - } - - private void HideChoiceOverlay() - { - if (_choiceOverlay == null) - return; - - _choicePromptKey = null; - _choiceOverlay.gameObject.SetActive(false); - _inputLock.Release("choice"); - } - private void BuildReconnectOverlay() { _reconnectOverlay = UiKit.CreatePanel( @@ -1865,7 +1753,6 @@ namespace Vanguard.UI option => option.Slot == slot.Slot); if (index >= 0) { - HideChoiceOverlay(); OnIntentClicked( new ChooseIntent { Index = index }, ignoreInputLock: true); diff --git a/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs b/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs index d10dc42..6b9ecb5 100644 --- a/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs +++ b/client/Assets/Vanguard/Tests/Editor/ClientSmokeTests.cs @@ -326,7 +326,7 @@ namespace Vanguard.Tests } [Test] - public void Rear_Guard_Choice_Shows_Overlay_With_Circles() + public void Rear_Guard_Choice_Is_Shown_Without_Buttons() { var config = new AppConfig(); config.Load(); @@ -364,13 +364,18 @@ namespace Vanguard.Tests .GetField("_view", BindingFlags.Instance | BindingFlags.NonPublic)! .SetValue(battle, match); typeof(BattleView) - .GetMethod("SyncChoicePrompt", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetMethod("RefreshActionPrompt", BindingFlags.Instance | BindingFlags.NonPublic)! .Invoke(battle, null); - var overlay = Resources.FindObjectsOfTypeAll() - .First(go => go.name == "ChoiceOverlay"); - Assert.IsTrue(overlay.activeSelf); - Assert.IsTrue(Resources.FindObjectsOfTypeAll() + var prompt = Resources.FindObjectsOfTypeAll() + .First(go => go.name == "ActionPrompt"); + Assert.IsTrue(prompt.activeSelf); + var title = Resources.FindObjectsOfTypeAll() + .First(text => text.name == "ActionPromptTitle"); + Assert.AreEqual("ВЫБОР КРУГА", title.text); + Assert.IsFalse(Resources.FindObjectsOfTypeAll() + .Any(go => go.name == "ChoiceOverlay")); + Assert.IsFalse(Resources.FindObjectsOfTypeAll() .Any(go => go.name == "Choice_3")); } finally diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 1fc3cf6..8d9316a 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -1015,8 +1015,11 @@ Battle; Boot и общие сервисы остаются персистент кнопками «Активировать» / «Пропустить»; из боковой панели их кнопки убраны. После подтверждения призыва карта из души не ставится в первый свободный круг автоматически: сервер предлагает `AskEmptyRearGuardChoice`, - клиент показывает оверлей с кругами и подсвечивает свободные RC. - Боковая панель пока оставлена для остальных действий. + клиент подсвечивает свободные RC и принимает выбор кликом по кругу, без + списка кнопок. Боковая панель пока оставлена для остальных действий. +- Зафиксировано правило взаимодействия: выбор карты/круга/зоны/цели — клик + или drag по объекту на поле; кнопки — только глобальные действия и + подтверждения (`AGENTS.md`, `docs/client-visual-direction.md`). - Добавлен общий верхний `ActionPrompt` с текущим обязательным действием: муллиган, смена, защита, бой, выбор карты/круга/цели, триггер, порядок триггеров и опциональная способность. Промпт информационный, не diff --git a/docs/SESSION-STATE.md b/docs/SESSION-STATE.md index 7c77d2f..78b4168 100644 --- a/docs/SESSION-STATE.md +++ b/docs/SESSION-STATE.md @@ -138,13 +138,16 @@ - Опциональные AUTO-способности (Barcgal после рейда Marron) открывают модальный оверлей «Способность» с «Активировать»/«Пропустить»; в боковой панели этих кнопок нет. После подтверждения призыва сервер предлагает - `AskEmptyRearGuardChoice`, клиент показывает оверлей с кругами и - подсвечивает свободные RC — первый круг больше не выбирается - автоматически. Сама панель пока оставлена. + `AskEmptyRearGuardChoice`, клиент подсвечивает свободные RC и принимает + выбор кликом по кругу — первого круга по умолчанию нет и отдельных + кнопок выбора тоже. Сама панель пока оставлена. - Общий верхний `ActionPrompt` показывает, что сейчас требуется: муллиган, смена, защита, бой, выбор карты/круга/цели, триггер, порядок триггеров, опциональная способность. Он не перехватывает клики и скрыт в ход противника. +- Правило: выбор карты/круга/зоны/цели делается кликом/драгом по объекту + на поле, не кнопками; кнопки — только глобальные действия/подтверждения. + Зафиксировано в `AGENTS.md` и `docs/client-visual-direction.md`. - Бой: динамическая плашка `POWER X vs SHIELD Y` (Power атакующего против Power защищающегося + GuardShield), пересчитывается на каждом снимке, в том числе после Guard. От атакующего к цели идёт затухающая 3D-стрелка, diff --git a/docs/client-visual-direction.md b/docs/client-visual-direction.md index ef4148c..abb1638 100644 --- a/docs/client-visual-direction.md +++ b/docs/client-visual-direction.md @@ -54,6 +54,17 @@ перенесены без изменения контрактов. `Boot` остаётся стартовой сценой, `VanguardApp` — персистентным composition root. +## Взаимодействие + +- Выбор объектов — только интерактивом по сцене: карта, круг, зона, поле. + Не показываем список кнопок «выбрать карту/круг» вместо клика по объекту. +- Кнопки остаются для глобальных действий и подтверждений (завершить ход, + сдаться, отмена, принять/пропустить опциональную способность). +- Информационные оверлеи только объясняют, что требуется; они не + перехватывают клики и не дублируют выбор кнопками. +- `ActionPrompt` в бою — информационный слой; подсветка на 3D-поле + показывает доступные карты/круги/зоны. + ## Ограничения и следующие шаги - Пока нет 3D-ассетов: карты показываются процедурными градиентными