diff --git a/Assets/_Project/Definitions/Races/BloodAngels_Placeholder.asset b/Assets/_Project/Definitions/Races/BloodAngels_Placeholder.asset index 58d9e9b..7c72d09 100644 --- a/Assets/_Project/Definitions/Races/BloodAngels_Placeholder.asset +++ b/Assets/_Project/Definitions/Races/BloodAngels_Placeholder.asset @@ -20,7 +20,4 @@ MonoBehaviour: LoreText: Crucified and brought back by God because her job wasn't finished. She's here to bring death to to Xenos scum. BuilderPrefab: {fileID: 116861493430507844, guid: 3398cc5831880954487717577f61b6d7, type: 3} - Towers: - - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - - {fileID: 11400000, guid: c4a1f7e92b6d8a4e3f0c9b1d6e5a2f80, type: 2} - - {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2} + Towers: [] diff --git a/Assets/_Project/Definitions/Races/Ultramarines_Placeholder.asset b/Assets/_Project/Definitions/Races/Ultramarines_Placeholder.asset index 086a765..e83b9ea 100644 --- a/Assets/_Project/Definitions/Races/Ultramarines_Placeholder.asset +++ b/Assets/_Project/Definitions/Races/Ultramarines_Placeholder.asset @@ -20,7 +20,4 @@ MonoBehaviour: LoreText: King of the protoss, he's here to kick bubblegum and chew ass, and he's all out of ass. BuilderPrefab: {fileID: 116861493430507844, guid: 3398cc5831880954487717577f61b6d7, type: 3} - Towers: - - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - - {fileID: 11400000, guid: c4a1f7e92b6d8a4e3f0c9b1d6e5a2f80, type: 2} - - {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2} + Towers: [] diff --git a/Assets/_Project/Definitions/Towers/BasicTower.asset b/Assets/_Project/Definitions/Towers/BasicTower.asset index e32563a..c04cc5c 100644 --- a/Assets/_Project/Definitions/Towers/BasicTower.asset +++ b/Assets/_Project/Definitions/Towers/BasicTower.asset @@ -12,9 +12,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7b353a757b6e6774d97e6fb8ba138fcc, type: 3} m_Name: BasicTower m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.TowerDefinition - DisplayName: Basic Arrow - Description: Targets ground and air. Fires single-target arrows. The bread and butter - of any maze. + DisplayName: A basic tower for testing. + Description: FootprintSize: {x: 2, y: 2} GoldCost: 25 BuildTime: 0.5 diff --git a/Assets/_Project/Definitions/Towers/SiegeCannon.asset b/Assets/_Project/Definitions/Towers/SiegeCannon.asset deleted file mode 100644 index e9a41bc..0000000 --- a/Assets/_Project/Definitions/Towers/SiegeCannon.asset +++ /dev/null @@ -1,37 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 7b353a757b6e6774d97e6fb8ba138fcc, type: 3} - m_Name: SiegeCannon - m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.TowerDefinition - DisplayName: Siege Cannon - Description: Ground-only. Lobs a ballistic shell that deals splash damage around the - point of impact. - FootprintSize: {x: 2, y: 2} - GoldCost: 60 - BuildTime: 0.5 - TowerPrefab: {fileID: 6482414459531823157, guid: 1511641f145758b469e64376d2a0d434, type: 3} - DamageType: 0 - TargetPriority: 0 - TargetType: 1 - GroundedOnly: 1 - Damage: 25 - Range: 16 - FireRate: 1 - SplashRadius: 4 - ChainCount: 0 - ChainRange: 0 - SlowFactor: 0 - DotDamagePerSecond: 0 - EffectDuration: 0 - ProjectilePrefab: {fileID: 2664719039363295382, guid: dc2e4a4108e03874a8b2dab88dcc8fba, type: 3} - ProjectileSpeed: 12 - UpgradePaths: [] diff --git a/Assets/_Project/Definitions/Towers/SiegeCannon.asset.meta b/Assets/_Project/Definitions/Towers/SiegeCannon.asset.meta deleted file mode 100644 index 27097fd..0000000 --- a/Assets/_Project/Definitions/Towers/SiegeCannon.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: c4a1f7e92b6d8a4e3f0c9b1d6e5a2f80 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Definitions/Towers/Wall.asset b/Assets/_Project/Definitions/Towers/Wall.asset deleted file mode 100644 index 8ee63b7..0000000 --- a/Assets/_Project/Definitions/Towers/Wall.asset +++ /dev/null @@ -1,36 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!114 &11400000 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 0} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 7b353a757b6e6774d97e6fb8ba138fcc, type: 3} - m_Name: Wall - m_EditorClassIdentifier: Assembly-CSharp::TD.Towers.TowerDefinition - DisplayName: Wall - Description: Deals no damage and costs next to nothing. Use it to shape your maze. - FootprintSize: {x: 2, y: 2} - GoldCost: 1 - BuildTime: 0.5 - TowerPrefab: {fileID: 6482414459531823157, guid: 1511641f145758b469e64376d2a0d434, type: 3} - DamageType: 0 - TargetPriority: 0 - TargetType: 0 - GroundedOnly: 0 - Damage: 0 - Range: 0 - FireRate: 0 - SplashRadius: 0 - ChainCount: 0 - ChainRange: 0 - SlowFactor: 0 - DotDamagePerSecond: 0 - EffectDuration: 0 - ProjectilePrefab: {fileID: 0} - ProjectileSpeed: 0 - UpgradePaths: [] diff --git a/Assets/_Project/Definitions/Towers/Wall.asset.meta b/Assets/_Project/Definitions/Towers/Wall.asset.meta deleted file mode 100644 index e8abc02..0000000 --- a/Assets/_Project/Definitions/Towers/Wall.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Prefabs/Player/Player.prefab b/Assets/_Project/Prefabs/Player/Player.prefab index b6e83b9..4d7c382 100644 --- a/Assets/_Project/Prefabs/Player/Player.prefab +++ b/Assets/_Project/Prefabs/Player/Player.prefab @@ -14,7 +14,6 @@ GameObject: - component: {fileID: 7845089877743661692} - component: {fileID: 4336209376377567030} - component: {fileID: 2806524246861401760} - - component: {fileID: 2806524246861401799} m_Layer: 0 m_Name: Player m_TagString: Untagged @@ -118,16 +117,3 @@ MonoBehaviour: ShowTopMostFoldoutHeaderGroup: 1 categories: - {fileID: 11400000, guid: d8ed3b9535538f7fc82be878cc307d26, type: 2} ---- !u!114 &2806524246861401799 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 3493329038866903420} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: b7e4c91a08f5d2461a3f8e6c25b09d74, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerTowerDeck - ShowTopMostFoldoutHeaderGroup: 1 diff --git a/Assets/_Project/Scenes/Levels/9Player.unity b/Assets/_Project/Scenes/Levels/9Player.unity index 9e21cf2..1869cae 100644 --- a/Assets/_Project/Scenes/Levels/9Player.unity +++ b/Assets/_Project/Scenes/Levels/9Player.unity @@ -19772,12 +19772,6 @@ MonoBehaviour: towerDefinitions: - {fileID: 0} - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - - {fileID: 11400000, guid: c4a1f7e92b6d8a4e3f0c9b1d6e5a2f80, type: 2} - - {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2} - startingDeck: - - {fileID: 11400000, guid: d5b2e8fa3c7e9b5f4a1d0c2e7f6b3a91, type: 2} - - {fileID: 11400000, guid: 0f693e29ca953e1439e10cb8f12e4b30, type: 2} - - {fileID: 11400000, guid: c4a1f7e92b6d8a4e3f0c9b1d6e5a2f80, type: 2} --- !u!4 &1507514109 Transform: m_ObjectHideFlags: 0 @@ -22526,7 +22520,6 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::TD.Dev.DevWaveControls hotkey: 94 - grantTowerHotkey: 101 --- !u!4 &1731269687 Transform: m_ObjectHideFlags: 0 diff --git a/Assets/_Project/Scripts/Dev/DevWaveControls.cs b/Assets/_Project/Scripts/Dev/DevWaveControls.cs index 2dfd6ff..26e1295 100644 --- a/Assets/_Project/Scripts/Dev/DevWaveControls.cs +++ b/Assets/_Project/Scripts/Dev/DevWaveControls.cs @@ -28,21 +28,15 @@ namespace TD.Dev "Set to Key.None to use the OnGUI button only.")] [SerializeField] private Key hotkey = Key.F9; - [Tooltip("Keyboard shortcut to grant the local player the next catalog tower not " + - "yet in their deck. Stand-in for the draft system so deck growth can be " + - "verified now. Set to Key.None to use the OnGUI button only.")] - [SerializeField] private Key grantTowerHotkey = Key.F8; - private void Update() { + if (hotkey == Key.None) return; + var kb = Keyboard.current; if (kb == null) return; // no keyboard connected (e.g. headless server) + if (!kb[hotkey].wasPressedThisFrame) return; - if (hotkey != Key.None && kb[hotkey].wasPressedThisFrame) - TryForceNextWave(); - - if (grantTowerHotkey != Key.None && kb[grantTowerHotkey].wasPressedThisFrame) - TryGrantNextTower(); + TryForceNextWave(); } private void OnGUI() @@ -54,44 +48,9 @@ namespace TD.Dev // Anchored below the top HUD bar so it doesn't overlap gold/wave/lives. const float topOffset = 90f; - GUI.Box(new Rect(10, topOffset, 180, 95), "Dev: Wave Controls"); + GUI.Box(new Rect(10, topOffset, 180, 60), "Dev: Wave Controls"); if (GUI.Button(new Rect(20, topOffset + 25, 160, 28), "Force Next Wave")) TryForceNextWave(); - if (GUI.Button(new Rect(20, topOffset + 58, 160, 28), "Grant Next Tower")) - TryGrantNextTower(); - } - - // Dev stand-in for the draft system: grants the local player the first catalog - // tower they haven't unlocked yet, so deck growth + the HUD's live rebuild can be - // verified before the real draft exists. Server-only (host); ServerGrantTower - // no-ops elsewhere. - private void TryGrantNextTower() - { - if (NetworkManager.Singleton == null || !NetworkManager.Singleton.IsServer) - { - Debug.LogWarning("[DevWaveControls] Grant-tower requested off the server. Ignored."); - return; - } - - var placement = TowerPlacementManager.Instance; - var deck = PlayerTowerDeck.Local; - if (placement == null || deck == null) - { - Debug.LogWarning("[DevWaveControls] Cannot grant tower — TowerPlacementManager " + - "or local PlayerTowerDeck is not ready."); - return; - } - - foreach (var (def, typeId) in placement.GetAvailableDefinitions()) - { - if (deck.Contains(typeId)) continue; - if (deck.ServerGrantTower(typeId)) - Debug.Log($"[DevWaveControls] Granted '{def.DisplayName}' (typeId {typeId}) " + - $"to the local deck."); - return; - } - - Debug.Log("[DevWaveControls] Local deck already contains every catalog tower."); } private void TryForceNextWave() diff --git a/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs b/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs deleted file mode 100644 index eb5687c..0000000 --- a/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs -using System.Collections.Generic; -using Unity.Netcode; -using UnityEngine; - -namespace TD.Gameplay -{ - /// - /// Per-player set of unlocked tower types — the player's "deck". Lives on the - /// Player prefab alongside , - /// , and . - /// - /// - /// Roguelike keystone. Players no longer see every tower in the match - /// catalog. Each starts with a small base set (the starter towers) and grows the - /// deck through draft choices. This component is the data structure that growth - /// targets. - /// - /// Identity. The deck stores TowerTypeIds — indices into - /// 's catalog (towerDefinitions[]), the - /// same identifier the placement RPC, , and - /// already use. No new network identifier is introduced. - /// - /// Authority. Server-only mutation ( / - /// ). The owning client reads its own deck to build - /// the HUD command grid; the server reads it to gate placement - /// ( rejects a requested tower that isn't in the - /// requesting player's deck — the anti-cheat seam). - /// - /// Persistence caveat. TowerTypeId is a catalog index — stable - /// within a single match but NOT across sessions. Cross-match deck persistence (a - /// later keystone) will need a stable per-tower id (asset GUID or a serialized - /// StableId on TowerDefinition). - /// - public class PlayerTowerDeck : NetworkBehaviour - { - // ----- Static registry (mirrors PlayerGoldManager) ---------------- - - private static readonly Dictionary s_byClientId - = new Dictionary(); - - /// Returns the deck owned by the given client, or null. - public static PlayerTowerDeck GetForClient(ulong clientId) - { - s_byClientId.TryGetValue(clientId, out var deck); - return deck; - } - - /// Convenience: the local client's own deck. Null on a dedicated - /// server or before the local player has spawned. - public static PlayerTowerDeck Local - { - get - { - var nm = NetworkManager.Singleton; - if (nm == null || !nm.IsClient) return null; - return GetForClient(nm.LocalClientId); - } - } - - // ----- Networked state -------------------------------------------- - - // Unlocked tower type ids (indices into TowerPlacementManager's catalog). - // readPerm defaults to Everyone (consistent with the other per-player - // managers); only the owner consumes it for the HUD. writePerm Server. - private NetworkList unlockedTypeIds; - - /// - /// Fired on every peer when the deck contents change (server init, tower - /// granted). The HUD subscribes to rebuild the command grid live when a draft - /// grant lands while the builder is already selected. - /// - public event System.Action OnDeckChanged; - - private void Awake() - { - unlockedTypeIds = new NetworkList(); - } - - // ----- NGO lifecycle ---------------------------------------------- - - public override void OnNetworkSpawn() - { - s_byClientId[OwnerClientId] = this; - unlockedTypeIds.OnListChanged += HandleListChanged; - } - - public override void OnNetworkDespawn() - { - unlockedTypeIds.OnListChanged -= HandleListChanged; - - if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this) - s_byClientId.Remove(OwnerClientId); - } - - private void HandleListChanged(NetworkListEvent _) => OnDeckChanged?.Invoke(); - - // ----- Read API --------------------------------------------------- - - /// Number of towers unlocked in this deck. - public int Count => unlockedTypeIds.Count; - - /// The TowerTypeId at the given deck slot (0..Count-1). - public int GetTypeIdAt(int index) => unlockedTypeIds[index]; - - /// True if the given TowerTypeId is unlocked in this deck. - public bool Contains(int towerTypeId) - { - for (int i = 0; i < unlockedTypeIds.Count; i++) - if (unlockedTypeIds[i] == towerTypeId) return true; - return false; - } - - // ----- Server mutation -------------------------------------------- - - /// - /// Server-only: replace the deck with the given starting set. Called at match - /// start () so Retry / return-to-lobby cycles reset - /// the deck cleanly. Ignores ids <= 0 (the reserved catalog index) and - /// silently de-dupes. - /// - public void ServerInitialize(IReadOnlyList startingTypeIds) - { - if (!IsServer) return; - - unlockedTypeIds.Clear(); - if (startingTypeIds == null) return; - - for (int i = 0; i < startingTypeIds.Count; i++) - { - int id = startingTypeIds[i]; - if (id > 0 && !Contains(id)) unlockedTypeIds.Add(id); - } - } - - /// - /// Server-only: add a tower type to this deck (a draft grant). No-op if the id - /// is invalid or already present. Returns true if it was actually added. - /// - public bool ServerGrantTower(int towerTypeId) - { - if (!IsServer) return false; - if (towerTypeId <= 0) return false; - if (Contains(towerTypeId)) return false; - - unlockedTypeIds.Add(towerTypeId); - return true; - } - } -} diff --git a/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs.meta b/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs.meta deleted file mode 100644 index aedfc4c..0000000 --- a/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b7e4c91a08f5d2461a3f8e6c25b09d74 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs b/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs index 29cd90e..d7daf7d 100644 --- a/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs +++ b/Assets/_Project/Scripts/Gameplay/TowerPlacementManager.cs @@ -71,17 +71,12 @@ namespace TD.Gameplay "worst-case 90/second (9 players × 10 placements/second).")] [SerializeField] private int requestsPerFrame = 3; - [Tooltip("Tower CATALOG for this match, indexed by TowerTypeId. This is the full " + - "pool of towers that can exist (be drafted) this match — NOT what any one " + - "player can build. Per-player buildable sets live in PlayerTowerDeck. " + - "Index 0 is reserved; valid IDs start at 1.")] + [Tooltip("Tower definitions available in this match, indexed by TowerTypeId. " + + "Populate this with every TowerDefinition asset the current race roster " + + "contains. Index 0 is reserved; valid IDs start at 1. " + + "(Temporary: will be driven by RaceDefinition once Path E is complete.)")] [SerializeField] private TowerDefinition[] towerDefinitions = new TowerDefinition[0]; - [Tooltip("The starter towers every player begins the match with. Each must also " + - "appear in the catalog above (that's where its TowerTypeId comes from). " + - "Resolved to TypeIds and granted to each PlayerTowerDeck at match start.")] - [SerializeField] private TowerDefinition[] startingDeck = new TowerDefinition[0]; - // ----- Internal request queue ------------------------------------- private struct PlacementRequest @@ -193,16 +188,6 @@ namespace TD.Gameplay return; } - // Deck membership — the requesting player must have unlocked this tower. - // Authoritative gate: a modded client cannot place a tower it hasn't drafted, - // even though every tower exists in the shared catalog. - var deck = PlayerTowerDeck.GetForClient(req.SenderClientId); - if (deck == null || !deck.Contains(req.TowerTypeId)) - { - Reject(req, PlacementRejectionReason.TowerNotInDeck); - return; - } - var loader = LevelLoader.Instance; if (loader == null || !loader.IsLoaded) { @@ -667,46 +652,6 @@ namespace TD.Gameplay } } - /// - /// Reverse lookup: returns the TowerTypeId (catalog index) for a given - /// definition asset, or false if it isn't in the catalog. Used to resolve the - /// assets into the TypeIds a - /// stores. - /// - public bool TryGetTypeId(TowerDefinition def, out int typeId) - { - typeId = 0; - if (def == null) return false; - for (int i = 1; i < towerDefinitions.Length; i++) - { - if (towerDefinitions[i] == def) { typeId = i; return true; } - } - return false; - } - - /// - /// Resolves the configured assets to their catalog - /// TypeIds. Logs an error for any starter tower missing from the catalog (it - /// would have no TypeId and couldn't be placed). Called once at match start. - /// - public List GetStartingDeckTypeIds() - { - var ids = new List(); - if (startingDeck == null) return ids; - - foreach (var def in startingDeck) - { - if (def == null) continue; - if (TryGetTypeId(def, out int id)) - ids.Add(id); - else - Debug.LogError($"[TowerPlacementManager] Starting-deck tower '{def.name}' " + - $"is not in the towerDefinitions catalog. Add it to the catalog " + - $"so it has a TowerTypeId, or it can't be granted/placed."); - } - return ids; - } - private static PlayerSlot ClientIdToPlayerSlot(ulong clientId) => PlayerMatchState.SlotForClient(clientId); @@ -762,10 +707,6 @@ namespace TD.Gameplay /// The requested tower type ID is not in the server's definition list. InvalidTowerType, - /// The requested tower exists in the catalog but the placing player has - /// not unlocked it in their deck. Draft it first. - TowerNotInDeck, - /// An unexpected server-side error occurred (e.g., LevelLoader not loaded, /// client not mapped to a PlayerSlot). Check server logs. ServerError, diff --git a/Assets/_Project/Scripts/Gameplay/TowerPlacementSettings.cs b/Assets/_Project/Scripts/Gameplay/TowerPlacementSettings.cs index 6d3f79c..74278c1 100644 --- a/Assets/_Project/Scripts/Gameplay/TowerPlacementSettings.cs +++ b/Assets/_Project/Scripts/Gameplay/TowerPlacementSettings.cs @@ -72,10 +72,6 @@ namespace TD.Gameplay "Player must cancel pending jobs or wait for one to complete.")] public string MessageJobLimitReached = "Builder queue is full."; - [Tooltip("Shown when the player tries to place a tower they haven't unlocked in " + - "their deck yet. Draft it first.")] - public string MessageTowerNotInDeck = "You haven't unlocked that tower yet."; - [Tooltip("Shown for unexpected server-side errors (invalid tower type, etc.). " + "Should rarely appear in normal play.")] public string MessageServerError = "Placement failed. Please try again."; @@ -96,7 +92,6 @@ namespace TD.Gameplay case PlacementRejectionReason.OutOfRange: return MessageOutOfRange; case PlacementRejectionReason.BlocksPath: return MessageBlocksPath; case PlacementRejectionReason.JobLimitReached: return MessageJobLimitReached; - case PlacementRejectionReason.TowerNotInDeck: return MessageTowerNotInDeck; case PlacementRejectionReason.InvalidTowerType: case PlacementRejectionReason.ServerError: default: return MessageServerError; diff --git a/Assets/_Project/Scripts/Gameplay/WaveManager.cs b/Assets/_Project/Scripts/Gameplay/WaveManager.cs index d2d5c8b..96e507b 100644 --- a/Assets/_Project/Scripts/Gameplay/WaveManager.cs +++ b/Assets/_Project/Scripts/Gameplay/WaveManager.cs @@ -160,26 +160,6 @@ namespace TD.Gameplay } } - // Seed each player's tower deck with the starter set. Runs here (after the - // one-frame yield) so the TowerPlacementManager catalog is guaranteed spawned - // and its starting-deck assets resolve to valid TypeIds. Clearing-and-setting - // means Retry / return-to-lobby cycles reset the deck cleanly. - var placement = TowerPlacementManager.Instance; - if (placement != null) - { - var startingTypeIds = placement.GetStartingDeckTypeIds(); - foreach (var pms in PlayerMatchState.AllPlayers) - { - var deck = PlayerTowerDeck.GetForClient(pms.OwnerClientId); - if (deck != null) deck.ServerInitialize(startingTypeIds); - } - } - else - { - Debug.LogWarning("[WaveManager] TowerPlacementManager not found at match start. " + - "Player decks were not seeded."); - } - if (ms.Phase == MatchPhase.Playing) StartNextWave(); } diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs index ccef8f0..8dfe824 100644 --- a/Assets/_Project/Scripts/UI/HUDController.cs +++ b/Assets/_Project/Scripts/UI/HUDController.cs @@ -137,8 +137,6 @@ namespace TD.UI private bool uiInitialized; private bool selectionSubscribed; // true once we've successfully hooked SelectionState.OnSelectionChanged private bool matchStateSubscribed; // true once OnPhaseChanged is hooked - private bool deckSubscribed; // true once we've hooked the local PlayerTowerDeck.OnDeckChanged - private PlayerTowerDeck subscribedDeck; // the deck we hooked, so we can unsubscribe the same instance private MinimapView minimapView; private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us @@ -247,26 +245,6 @@ namespace TD.UI matchStateSubscribed = true; } - // Hook the local player's deck so the command grid rebuilds live when a draft - // grant lands while the builder is already selected. The local deck may not - // exist for the first few frames (spawn race) — retried each Update until it does. - private void TrySubscribeDeck() - { - var deck = PlayerTowerDeck.Local; - if (deck == null) return; - deck.OnDeckChanged += HandleDeckChanged; - subscribedDeck = deck; - deckSubscribed = true; - // The deck may have populated before we subscribed — rebuild now so the - // grid reflects the current contents. - PopulateGridForSelection(SelectionState.Instance?.SelectedObject); - } - - private void HandleDeckChanged() - { - PopulateGridForSelection(SelectionState.Instance?.SelectedObject); - } - private void InitializeUI() { var doc = GetComponent(); @@ -396,13 +374,6 @@ namespace TD.UI MatchState.Instance.OnPhaseChanged -= HandlePhaseChanged; matchStateSubscribed = false; } - - if (deckSubscribed && subscribedDeck != null) - { - subscribedDeck.OnDeckChanged -= HandleDeckChanged; - } - deckSubscribed = false; - subscribedDeck = null; } private void TrySubscribeSelection() @@ -423,9 +394,6 @@ namespace TD.UI if (!matchStateSubscribed) TrySubscribeMatchState(); - if (!deckSubscribed) - TrySubscribeDeck(); - RefreshGoldDisplay(); RefreshMatchStateDisplays(); UpdateBuildProgressIfShown(); @@ -795,23 +763,14 @@ namespace TD.UI } else { - // Build buttons come from the LOCAL player's deck — the per-player - // unlocked set — not the global catalog. Each deck entry is a - // TowerTypeId resolved back to its definition via the catalog. The - // last grid slot is reserved for the Buffs button, so towers fill - // slots 0..GRID_MAX-2. - var deck = PlayerTowerDeck.Local; - if (deck != null) + int i = 0; + foreach (var (def, typeId) in placementManager.GetAvailableDefinitions()) { - int count = Mathf.Min(deck.Count, GRID_MAX - 1); - for (int i = 0; i < count; i++) - { - int typeId = deck.GetTypeIdAt(i); - var def = TowerPlacementManager.GetDefinition(typeId); - if (def != null) - cells[i] = CreateTowerButton(def, typeId, HotkeyLayout[i]); - } + if (i >= GRID_MAX) break; + cells[i] = CreateTowerButton(def, typeId, HotkeyLayout[i]); + i++; } + } cells[GRID_MAX - 1] = CreateBuffMenuButton(HotkeyLayout[GRID_MAX - 1]); }