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]);
}