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:
Matt F 2026-06-24 00:15:17 -07:00
parent 1d03689dad
commit 3ceebed8f6
22 changed files with 1060 additions and 431 deletions

View file

@ -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