Adding the bones of the drafting system by removing the wall and siege towers from the base set and adding them temporarily as choices for the draft. Also adding Project_Context and Project_Roadmap documents to use with Claude
This commit is contained in:
parent
1d03689dad
commit
3ceebed8f6
22 changed files with 1060 additions and 431 deletions
|
|
@ -8,6 +8,7 @@ using UnityEngine.SceneManagement;
|
|||
using UnityEngine.UIElements;
|
||||
using TD.Core;
|
||||
using TD.Gameplay;
|
||||
using TD.Gameplay.Draft;
|
||||
using TD.Towers;
|
||||
using TD.UI.Minimap;
|
||||
|
||||
|
|
@ -105,6 +106,15 @@ namespace TD.UI
|
|||
private VisualElement buffMenuContent;
|
||||
private Label matchEndTitle;
|
||||
|
||||
// Draft overlay (roguelike between-waves draft). Non-modal: the card row floats at
|
||||
// top-center so the player can keep building during prep. The "Buy Roll" button is
|
||||
// shown when no draft is pending so a paid roll can be bought any time.
|
||||
private VisualElement draftPanel;
|
||||
private VisualElement draftCardRow;
|
||||
private Button draftBuyButton;
|
||||
private bool draftSubscribed;
|
||||
private PlayerDraft subscribedDraft;
|
||||
|
||||
// Chat panel (bottom-left, above portrait) — programmatic. The container
|
||||
// holds both the scrollable feed and the input. Highlight + scroll
|
||||
// interactivity are toggled on the container when typing.
|
||||
|
|
@ -267,6 +277,163 @@ namespace TD.UI
|
|||
PopulateGridForSelection(SelectionState.Instance?.SelectedObject);
|
||||
}
|
||||
|
||||
// Hook the local player's draft so the overlay rebuilds when options are offered,
|
||||
// picked, or rerolled. Retried each Update until the local PlayerDraft exists.
|
||||
private void TrySubscribeDraft()
|
||||
{
|
||||
var draft = PlayerDraft.Local;
|
||||
if (draft == null) return;
|
||||
draft.OnDraftChanged += HandleDraftChanged;
|
||||
subscribedDraft = draft;
|
||||
draftSubscribed = true;
|
||||
RebuildDraftCards();
|
||||
}
|
||||
|
||||
private void HandleDraftChanged()
|
||||
{
|
||||
RebuildDraftCards();
|
||||
}
|
||||
|
||||
// ----- Draft overlay ----------------------------------------------
|
||||
|
||||
// Non-modal draft UI: a card row floats at top-center while a draft is active, and
|
||||
// a "Buy Roll" button shows when no draft is pending. The host panel ignores picks
|
||||
// so empty space clicks through to the world — the player keeps building during prep.
|
||||
private void BuildDraftOverlay(VisualElement root)
|
||||
{
|
||||
draftPanel = new VisualElement();
|
||||
draftPanel.style.position = Position.Absolute;
|
||||
draftPanel.style.left = 0;
|
||||
draftPanel.style.right = 0;
|
||||
draftPanel.style.top = 70; // below the top bar
|
||||
draftPanel.style.alignItems = Align.Center;
|
||||
draftPanel.pickingMode = PickingMode.Ignore;
|
||||
draftPanel.style.display = DisplayStyle.None;
|
||||
|
||||
var box = new VisualElement();
|
||||
box.style.flexDirection = FlexDirection.Column;
|
||||
box.style.alignItems = Align.Center;
|
||||
box.pickingMode = PickingMode.Ignore;
|
||||
|
||||
draftCardRow = new VisualElement();
|
||||
draftCardRow.style.flexDirection = FlexDirection.Row;
|
||||
draftCardRow.pickingMode = PickingMode.Ignore;
|
||||
box.Add(draftCardRow);
|
||||
|
||||
draftBuyButton = new Button(() => PlayerDraft.Local?.RequestBuyRerollRpc())
|
||||
{
|
||||
text = "Buy Roll"
|
||||
};
|
||||
draftBuyButton.style.marginTop = 6;
|
||||
box.Add(draftBuyButton);
|
||||
|
||||
draftPanel.Add(box);
|
||||
root.Add(draftPanel);
|
||||
|
||||
UpdateDraftVisibility();
|
||||
}
|
||||
|
||||
// Rebuilds the option cards from the local player's current offer. Called on
|
||||
// OnDraftChanged (offered / picked / rerolled).
|
||||
private void RebuildDraftCards()
|
||||
{
|
||||
if (draftCardRow == null) return;
|
||||
draftCardRow.Clear();
|
||||
|
||||
var draft = PlayerDraft.Local;
|
||||
var pool = DraftPool.Instance;
|
||||
if (draft != null && pool != null)
|
||||
{
|
||||
for (int i = 0; i < draft.OptionCount; i++)
|
||||
{
|
||||
int id = draft.GetOptionId(i);
|
||||
var option = pool.Get(id);
|
||||
if (option == null) continue;
|
||||
draftCardRow.Add(CreateDraftCard(option, id, draft));
|
||||
}
|
||||
}
|
||||
|
||||
UpdateDraftVisibility();
|
||||
}
|
||||
|
||||
private VisualElement CreateDraftCard(DraftOption option, int optionId, PlayerDraft draft)
|
||||
{
|
||||
var card = new VisualElement();
|
||||
card.style.width = 150;
|
||||
card.style.marginLeft = card.style.marginRight = 4;
|
||||
card.style.paddingTop = card.style.paddingBottom = 10;
|
||||
card.style.paddingLeft = card.style.paddingRight = 10;
|
||||
card.style.backgroundColor = new Color(0.10f, 0.10f, 0.13f, 0.96f);
|
||||
card.style.borderTopWidth = card.style.borderBottomWidth =
|
||||
card.style.borderLeftWidth = card.style.borderRightWidth = 2;
|
||||
var border = new Color(0.4f, 0.4f, 0.45f);
|
||||
card.style.borderTopColor = card.style.borderBottomColor =
|
||||
card.style.borderLeftColor = card.style.borderRightColor = border;
|
||||
card.style.alignItems = Align.Center;
|
||||
|
||||
var title = new Label(option.DisplayName);
|
||||
title.style.fontSize = 14;
|
||||
title.style.color = Color.white;
|
||||
title.style.unityFontStyleAndWeight = FontStyle.Bold;
|
||||
title.style.whiteSpace = WhiteSpace.Normal;
|
||||
title.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
title.style.marginBottom = 6;
|
||||
card.Add(title);
|
||||
|
||||
if (option.Icon != null)
|
||||
{
|
||||
var img = new Image { sprite = option.Icon };
|
||||
img.style.width = 48;
|
||||
img.style.height = 48;
|
||||
img.style.marginBottom = 6;
|
||||
card.Add(img);
|
||||
}
|
||||
|
||||
var desc = new Label(option.Description ?? "");
|
||||
desc.style.fontSize = 11;
|
||||
desc.style.color = new Color(0.82f, 0.82f, 0.82f);
|
||||
desc.style.whiteSpace = WhiteSpace.Normal;
|
||||
desc.style.unityTextAlign = TextAnchor.MiddleCenter;
|
||||
desc.style.marginBottom = 8;
|
||||
card.Add(desc);
|
||||
|
||||
var pick = new Button(() => draft.RequestPickRpc(optionId)) { text = "Pick" };
|
||||
card.Add(pick);
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
// Per-frame: toggles the card row vs. the buy button and keeps the buy button's
|
||||
// label/enabled state in sync with gold. Cheap (mirrors the gold label's per-frame
|
||||
// refresh); only allocates a string while the buy button is shown.
|
||||
private void UpdateDraftVisibility()
|
||||
{
|
||||
if (draftPanel == null) return;
|
||||
|
||||
var draft = PlayerDraft.Local;
|
||||
var service = DraftService.Instance;
|
||||
var gold = PlayerGoldManager.Local;
|
||||
|
||||
bool hasDraft = draft != null && draft.HasActiveDraft;
|
||||
bool canShowBuy = draft != null && service != null && !hasDraft;
|
||||
|
||||
if (draftCardRow != null)
|
||||
draftCardRow.style.display = hasDraft ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
|
||||
if (draftBuyButton != null)
|
||||
{
|
||||
draftBuyButton.style.display = canShowBuy ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
if (canShowBuy)
|
||||
{
|
||||
int cost = service.RerollCost;
|
||||
draftBuyButton.text = $"Buy Roll ({cost}g)";
|
||||
draftBuyButton.SetEnabled(gold != null && gold.CurrentGold >= cost);
|
||||
}
|
||||
}
|
||||
|
||||
draftPanel.style.display = (hasDraft || canShowBuy) ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
private void InitializeUI()
|
||||
{
|
||||
var doc = GetComponent<UIDocument>();
|
||||
|
|
@ -351,6 +518,10 @@ namespace TD.UI
|
|||
// Build the buff menu overlay. Hidden until the player presses B.
|
||||
BuildBuffMenuOverlay(root);
|
||||
|
||||
// Build the draft overlay (roguelike between-waves draft). Hidden until the
|
||||
// local player has an active draft (or to show the paid "Buy Roll" button).
|
||||
BuildDraftOverlay(root);
|
||||
|
||||
// Chat feed + input. Anchored bottom-left, just above the portrait/bottom-ui bar.
|
||||
// Player typing toggled with Enter; system messages (e.g. life lost) post via
|
||||
// ChatService.PostLocalSystem on every peer.
|
||||
|
|
@ -403,6 +574,13 @@ namespace TD.UI
|
|||
}
|
||||
deckSubscribed = false;
|
||||
subscribedDeck = null;
|
||||
|
||||
if (draftSubscribed && subscribedDraft != null)
|
||||
{
|
||||
subscribedDraft.OnDraftChanged -= HandleDraftChanged;
|
||||
}
|
||||
draftSubscribed = false;
|
||||
subscribedDraft = null;
|
||||
}
|
||||
|
||||
private void TrySubscribeSelection()
|
||||
|
|
@ -426,10 +604,14 @@ namespace TD.UI
|
|||
if (!deckSubscribed)
|
||||
TrySubscribeDeck();
|
||||
|
||||
if (!draftSubscribed)
|
||||
TrySubscribeDraft();
|
||||
|
||||
RefreshGoldDisplay();
|
||||
RefreshMatchStateDisplays();
|
||||
UpdateBuildProgressIfShown();
|
||||
UpdateEnemyInfoIfShown();
|
||||
UpdateDraftVisibility();
|
||||
HandleChatInput();
|
||||
|
||||
// Skip gameplay hotkeys while the chat input is focused — letters
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue