// 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.
///
/// Auto-discovery. On Awake, all assets
/// under Resources/TowerDefinitions/ are loaded automatically. No inspector
/// drag-and-drop required — add a new asset to that folder and it is registered at
/// runtime with no other changes needed. This scales cleanly to 100+ tower types.
///
/// 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 assets in the Resources folder 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; }
// ----- Constants --------------------------------------------------
///
/// Resources-relative folder path that TowerDefinition assets must live under
/// to be auto-discovered. Create this folder if it doesn't exist.
/// Full path: Assets/Resources/TowerDefinitions/
///
private const string ResourcesFolder = "TowerDefinitions";
// ----- 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();
// Resources.LoadAll finds every TowerDefinition asset anywhere under
// Assets/Resources/TowerDefinitions/ (including sub-folders).
// No manual registration needed — drop an asset in the folder and it
// is available on the next play session.
var loaded = Resources.LoadAll(ResourcesFolder);
if (loaded.Length == 0)
{
Debug.LogWarning($"[TowerRegistry] No TowerDefinition assets found under " +
$"Resources/{ResourcesFolder}/. " +
$"Create the folder and add TowerDefinition assets to it.");
return;
}
foreach (var def in loaded)
{
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] Auto-discovered and registered " +
$"{byName.Count} tower definition(s) from Resources/{ResourcesFolder}/.");
}
}
}