diff --git a/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs b/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs index 83aec06..b809684 100644 --- a/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs +++ b/Assets/_Project/Scripts/Gameplay/RaceDefinition.cs @@ -68,8 +68,8 @@ namespace TD.Gameplay "the prefab based on the player's RaceSelection.")] public GameObject BuilderPrefab; - [Tooltip("STUB (Phase 1.8): tower roster available to this race. TowerRegistry will " + - "filter to this list when the active player belongs to this race.")] + [Tooltip("STUB: tower roster available to this race/builder. The draft pool or " + + "starting deck will eventually filter to this list for this race.")] public TowerDefinition[] Towers; } } diff --git a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs index b99c0c2..724776e 100644 --- a/Assets/_Project/Scripts/Gameplay/TowerInstance.cs +++ b/Assets/_Project/Scripts/Gameplay/TowerInstance.cs @@ -1,5 +1,4 @@ // Assets/_Project/Scripts/Gameplay/TowerInstance.cs -using Unity.Collections; using Unity.Netcode; using UnityEngine; using TD.Core; @@ -32,11 +31,11 @@ namespace TD.Gameplay /// are idempotent writes, so double-stamping is safe. /// /// Definition reference replication. TowerDefinition assets live in the - /// project on all clients. We replicate the asset by name via a - /// holding a FixedString64Bytes, then look - /// up the asset locally. This avoids serializing the full ScriptableObject over the - /// network. The lookup uses a singleton that must be - /// present in the scene. (Temporary: will be driven by RaceDefinition in Path E.) + /// project on all clients. We replicate the tower's TowerTypeId (its index in + /// 's catalog) via a , + /// then resolve the full asset locally with . + /// This avoids serializing the ScriptableObject over the network and reuses the same + /// identifier placement and the deck already use — a single source of truth. /// /// Combat. No combat logic here yet. Combat fields live stubbed on /// ; they will be consumed by a future @@ -58,12 +57,13 @@ namespace TD.Gameplay // ----- Networked state ------------------------------------------------ - // The name of the TowerDefinition asset for this tower. Replicated so all - // clients can look up the full definition locally without sending the whole - // ScriptableObject over the wire. - private readonly NetworkVariable definitionName = - new NetworkVariable( - default, + // The TowerTypeId (index into TowerPlacementManager's catalog) for this tower. + // Replicated so every client resolves the full definition locally via the catalog — + // the same identifier placement and the deck already use. 0 = unset/invalid (the + // reserved catalog index). Replaces the old replicate-by-name + TowerRegistry path. + private readonly NetworkVariable definitionTypeId = + new NetworkVariable( + 0, readPerm: NetworkVariableReadPermission.Everyone, writePerm: NetworkVariableWritePermission.Server); @@ -95,8 +95,8 @@ namespace TD.Gameplay // ----- Local resolved state ------------------------------------------- - // Resolved on every client in OnNetworkSpawn from definitionName. - // Null if the lookup fails (missing TowerRegistry or unknown name). + // Resolved on every client in OnNetworkSpawn from definitionTypeId via the catalog. + // Null if the lookup fails (TypeId not in the catalog on this peer). private TowerDefinition resolvedDefinition; // ----- Pre-spawn initialization data ---------------------------------- @@ -192,7 +192,7 @@ namespace TD.Gameplay hasPendingInit = true; // Cache the resolved definition on the server immediately — clients - // will resolve via the registry lookup once definitionName arrives. + // resolve from the replicated TowerTypeId via the catalog once it arrives. resolvedDefinition = def; } @@ -206,7 +206,17 @@ namespace TD.Gameplay // every client sees correct values on its very first OnNetworkSpawn. if (IsServer && hasPendingInit) { - definitionName.Value = new FixedString64Bytes(pendingDefinition.name); + // Resolve the catalog index for this definition and replicate that. + int typeId = 0; + var pm = TowerPlacementManager.Instance; + if (pm == null || !pm.TryGetTypeId(pendingDefinition, out typeId)) + { + Debug.LogError($"[TowerInstance] Could not resolve a TowerTypeId for " + + $"'{pendingDefinition?.name}'. Ensure it is in the " + + $"TowerPlacementManager catalog (towerDefinitions)."); + } + + definitionTypeId.Value = typeId; anchorTile.Value = pendingAnchor; ownerSlot.Value = pendingOwner; @@ -331,27 +341,13 @@ namespace TD.Gameplay // Already resolved (server path via InitializeServer). if (resolvedDefinition != null) return; - string defName = definitionName.Value.ToString(); - if (string.IsNullOrEmpty(defName)) - { - Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " + - $"definitionName is empty. Cannot resolve TowerDefinition."); - return; - } - - var registry = TowerRegistry.Instance; - if (registry == null) - { - Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " + - $"No TowerRegistry found in scene. Cannot resolve '{defName}'."); - return; - } - - resolvedDefinition = registry.Get(defName); + int typeId = definitionTypeId.Value; + resolvedDefinition = TowerPlacementManager.GetDefinition(typeId); if (resolvedDefinition == null) { Debug.LogError($"[TowerInstance] NetworkObject {NetworkObjectId}: " + - $"TowerRegistry does not contain a definition named '{defName}'."); + $"no TowerDefinition for TypeId {typeId}. Is the " + + $"TowerPlacementManager catalog populated on this peer?"); } } diff --git a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs b/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs deleted file mode 100644 index 389df11..0000000 --- a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs +++ /dev/null @@ -1,120 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/TowerRegistry.cs -using System.Collections.Generic; -using UnityEngine; -using TD.Towers; - -namespace TD.Gameplay -{ - /// - /// Scene singleton that holds every available in the - /// current match and lets any code look one up by asset name. - /// - /// - /// Why this exists. replicates a tower's - /// definition by name (a FixedString64Bytes over the network), then resolves - /// the full ScriptableObject locally on every client. TowerRegistry is the lookup - /// table that makes that resolution possible without hard-coding asset paths. - /// - /// Registration. Drag every asset into the - /// Definitions list on this component in the inspector. Assets can live anywhere - /// in the project — no special folder required. - /// - /// Path E upgrade path. In Path E the registry will filter to only the - /// definitions belonging to the active match's RaceDefinition rosters. For now - /// all assigned assets are registered. - /// - /// Plain MonoBehaviour. Not a NetworkBehaviour — the registry is - /// identical on every peer (same assets, same names), so there is nothing to sync. - /// - public class TowerRegistry : MonoBehaviour - { - // ----- Singleton -------------------------------------------------- - - /// - /// The active TowerRegistry. Null before Awake or after the scene unloads. - /// Always null-check before use. - /// - public static TowerRegistry Instance { get; private set; } - - // ----- Inspector -------------------------------------------------- - - [Tooltip("All TowerDefinition assets available in this match. " + - "Drag assets here from Assets/_Project/Data/TowerDefinitions/ " + - "(or wherever they live). Asset name is used as the registry key.")] - [SerializeField] private TowerDefinition[] definitions; - - // ----- Internal lookup table -------------------------------------- - - // Keyed by TowerDefinition.name (the asset name, not DisplayName). - private readonly Dictionary byName - = new Dictionary(); - - // ----- Lifecycle -------------------------------------------------- - - private void Awake() - { - if (Instance != null && Instance != this) - { - Debug.LogError("[TowerRegistry] Multiple instances detected. " + - "Only one TowerRegistry should exist per scene."); - return; - } - Instance = this; - - BuildLookupTable(); - } - - private void OnDestroy() - { - if (Instance == this) Instance = null; - } - - // ----- Public API ------------------------------------------------- - - /// - /// Returns the whose asset name equals - /// , or null if no match is found. - /// - public TowerDefinition Get(string assetName) - { - byName.TryGetValue(assetName, out var def); - return def; - } - - /// - /// Returns all registered tower definitions. Enumerates the internal - /// dictionary values — do not modify the returned collection. - /// - public IEnumerable All => byName.Values; - - // ----- Private ---------------------------------------------------- - - private void BuildLookupTable() - { - byName.Clear(); - - if (definitions == null || definitions.Length == 0) - { - Debug.LogWarning("[TowerRegistry] No TowerDefinition assets assigned. " + - "Drag assets into the Definitions list on the TowerRegistry component."); - return; - } - - foreach (var def in definitions) - { - if (def == null) continue; - - if (byName.ContainsKey(def.name)) - { - Debug.LogWarning($"[TowerRegistry] Duplicate asset name '{def.name}'. " + - $"Only the first entry will be used. Rename one of the assets."); - continue; - } - - byName[def.name] = def; - } - - Debug.Log($"[TowerRegistry] Registered {byName.Count} tower definition(s)."); - } - } -} diff --git a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta b/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta deleted file mode 100644 index 6a7a87c..0000000 --- a/Assets/_Project/Scripts/Gameplay/TowerRegistry.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: a9dc0fbbe4422bc479ab8db7658c082b \ No newline at end of file diff --git a/Project_Context.md b/Project_Context.md index 7242383..b9552bb 100644 --- a/Project_Context.md +++ b/Project_Context.md @@ -90,7 +90,7 @@ Unity **6.4 (6000.4.4f1)**, URP, IL2CPP, .NET Standard 2.1, Linear color space, - **Visuals are placeholder** throughout (primitive/cone towers, sourced enemy models). See roadmap art TODOs. - **Enemies lack idle animation support** — needs adding across all enemy prefabs. - **Tower footprint visuals don't fill their 2×2 space** — the cone/mesh sits centered and reads smaller than the tile area it occupies; should visually reflect the full footprint. -- **Two redundant tower lists:** `TowerPlacementManager.towerDefinitions[]` (by-index catalog) and `TowerRegistry.definitions[]` (by-name, for instance resolution). Candidate for consolidation. +- **Tower catalog is now a single source of truth:** `TowerPlacementManager.towerDefinitions[]` (by-index, = `TowerTypeId`). `TowerInstance` replicates that index and resolves via `TowerPlacementManager.GetDefinition`. *(The old by-name `TowerRegistry` was removed 2026-06-24 — add new towers only to `towerDefinitions` + `DraftPool`.)* - **Catalog index 0 is a reserved sentinel** (valid `TowerTypeId`s start at 1) — easy to forget when wiring the catalog in the inspector. - **Catalog-index identifiers aren't session-stable** — blocks cross-match persistence until a stable ID is added. - **Paint system frozen**; **Race vs Builder** naming unresolved; the gold **"Buy Roll available any time"** rule is provisional and may change.