// Assets/_Project/Scripts/Gameplay/PlayerTowerUpgrades.cs using System.Collections.Generic; using Unity.Netcode; using UnityEngine; namespace TD.Gameplay { /// /// Per-player set of unlocked tower upgrade nodes — the tower types this player is /// allowed to convert an existing tower into. Lives on the Player prefab alongside /// . /// /// /// Distinct from the deck, deliberately. is what you /// can build from scratch; this is what you can upgrade into. They have to be /// separate sets because the 2.0 model is two-step: a draft pick unlocks a node, and gold spent /// on an already-placed tower converts it. Unlocking an upgrade must not make it directly /// buildable, or the gold step — the entire cost of the upgrade — is bypassed. /// /// The tree lives in the data. Edges are TowerDefinition.UpgradePaths; this /// component only records which nodes a player has earned the right to use. Whether a /// particular tower can take a particular upgrade is a question for the tree (is it a child of /// what's placed?) crossed with this set (has the player unlocked it?). /// /// Append-only within a match, mirroring the deck. Reset happens by re-initializing at /// match start, so Retry / return-to-lobby cycles start clean. /// public class PlayerTowerUpgrades : NetworkBehaviour { // ----- Static registry (mirrors PlayerTowerDeck / PlayerGoldManager) ----- private static readonly Dictionary s_byClientId = new Dictionary(); public static PlayerTowerUpgrades GetForClient(ulong clientId) { s_byClientId.TryGetValue(clientId, out var upgrades); return upgrades; } public static PlayerTowerUpgrades Local { get { var nm = NetworkManager.Singleton; if (nm == null || !nm.IsClient) return null; return GetForClient(nm.LocalClientId); } } // ----- Networked state -------------------------------------------- // TowerTypeIds (catalog indices) this player may upgrade into. private NetworkList unlockedNodes; /// Fired on every peer when the unlocked set changes. The HUD subscribes so a /// selected tower's upgrade buttons appear the moment a draft grants them. public event System.Action OnUpgradesChanged; private void Awake() { unlockedNodes = new NetworkList(); } public override void OnNetworkSpawn() { s_byClientId[OwnerClientId] = this; unlockedNodes.OnListChanged += HandleListChanged; } public override void OnNetworkDespawn() { unlockedNodes.OnListChanged -= HandleListChanged; if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this) s_byClientId.Remove(OwnerClientId); } private void HandleListChanged(NetworkListEvent _) => OnUpgradesChanged?.Invoke(); // ----- Read API --------------------------------------------------- public int Count => unlockedNodes.Count; public int GetTypeIdAt(int index) => unlockedNodes[index]; /// True if this player has unlocked the given upgrade node. public bool Contains(int towerTypeId) { for (int i = 0; i < unlockedNodes.Count; i++) if (unlockedNodes[i] == towerTypeId) return true; return false; } // ----- Server API ------------------------------------------------- /// Server-only: clear the unlocked set. Called at match start. public void ServerInitialize() { if (!IsServer) return; unlockedNodes.Clear(); } /// /// Server-only: unlock an upgrade node. Returns false if already unlocked (so a draft /// option can report a wasted pick rather than silently succeeding). /// public bool ServerUnlock(int towerTypeId) { if (!IsServer) return false; if (towerTypeId < 0 || Contains(towerTypeId)) return false; unlockedNodes.Add(towerTypeId); return true; } } }