diff --git a/Assets/_Project/Definitions.meta b/Assets/_Project/Definitions.meta index a2de22b..d3c452a 100644 --- a/Assets/_Project/Definitions.meta +++ b/Assets/_Project/Definitions.meta @@ -1,9 +1,5 @@ fileFormatVersion: 2 -<<<<<<< HEAD guid: 01de85ee5d8a2014594d9910b1a6ff55 -======= -guid: cf294cbfb17f5a5d7846479a116fc7b3 ->>>>>>> 31b0b5f (adding uncommited files (oops)) folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/_Project/Definitions/Buffs.meta b/Assets/_Project/Definitions/Buffs.meta deleted file mode 100644 index 98de849..0000000 --- a/Assets/_Project/Definitions/Buffs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: eef6329e4294d86c6978ff9453b56956 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset b/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset deleted file mode 100644 index c707eae..0000000 --- a/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset +++ /dev/null @@ -1,17 +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: b01f9aea62ee8a1c1991bb2a097de224, type: 3} - m_Name: ExtraDamage25 - m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.BuffDefinition - DisplayName: 25% Increased Damage - Stat: 0 - Multiplier: 1.25 diff --git a/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset.meta b/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset.meta deleted file mode 100644 index b240b6c..0000000 --- a/Assets/_Project/Definitions/Buffs/ExtraDamage25.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9ec170e540b1f91cca762dcebe81a856 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset b/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset deleted file mode 100644 index 737c5ab..0000000 --- a/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset +++ /dev/null @@ -1,18 +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: 516e8faec948a4b3d977ec019376c438, type: 3} - m_Name: OffensiveBuffs - m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.BuffCategory - DisplayName: Offensive Buffs - Cost: 5 - Pool: - - {fileID: 11400000, guid: 9ec170e540b1f91cca762dcebe81a856, type: 2} diff --git a/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset.meta b/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset.meta deleted file mode 100644 index ced41c2..0000000 --- a/Assets/_Project/Definitions/Buffs/OffensiveBuffs.asset.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d8ed3b9535538f7fc82be878cc307d26 -NativeFormatImporter: - externalObjects: {} - mainObjectFileID: 11400000 - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/_Project/Prefabs/Player/Player.prefab b/Assets/_Project/Prefabs/Player/Player.prefab index 4d7c382..8a1fe80 100644 --- a/Assets/_Project/Prefabs/Player/Player.prefab +++ b/Assets/_Project/Prefabs/Player/Player.prefab @@ -13,7 +13,6 @@ GameObject: - component: {fileID: 2918837822014987993} - component: {fileID: 7845089877743661692} - component: {fileID: 4336209376377567030} - - component: {fileID: 2806524246861401760} m_Layer: 0 m_Name: Player m_TagString: Untagged @@ -48,7 +47,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 1552073510 + GlobalObjectIdHash: 121878297 InScenePlacedSourceGlobalObjectIdHash: 0 DeferredDespawnTick: 0 Ownership: 1 @@ -102,18 +101,3 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerMatchState ShowTopMostFoldoutHeaderGroup: 1 ---- !u!114 &2806524246861401760 -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: 7775ee3a2f441b52480aabf54be6c1b6, type: 3} - m_Name: - m_EditorClassIdentifier: Assembly-CSharp::TD.Gameplay.PlayerBuffManager - ShowTopMostFoldoutHeaderGroup: 1 - categories: - - {fileID: 11400000, guid: d8ed3b9535538f7fc82be878cc307d26, type: 2} diff --git a/Assets/_Project/Scripts/Combat/TowerCombat.cs b/Assets/_Project/Scripts/Combat/TowerCombat.cs index 01b1441..d270b96 100644 --- a/Assets/_Project/Scripts/Combat/TowerCombat.cs +++ b/Assets/_Project/Scripts/Combat/TowerCombat.cs @@ -58,11 +58,6 @@ namespace TD.Combat // Cached on OnNetworkSpawn — avoids GetComponent every Update. private TowerInstance towerInstance; - // Lazily resolved on first attack — the owner's slot isn't guaranteed to be - // set at spawn time, so we defer the lookup until combat actually begins. - private PlayerBuffManager ownerBuffManager; - private bool buffManagerResolved; - // Shared OverlapSphere result buffer. 32 covers any realistic enemy // density; size up if profiling reveals overflow. private static readonly Collider[] s_overlapBuffer = new Collider[32]; @@ -216,25 +211,6 @@ namespace TD.Combat ClearTarget(); } - // ----- Buff multiplier lookup ------------------------------------- - - // Resolved lazily on the first attack because the owner slot NetworkVariable - // may not yet have replicated by the time OnNetworkSpawn runs. - private PlayerBuffManager GetOwnerBuffManager() - { - if (buffManagerResolved) return ownerBuffManager; - buffManagerResolved = true; - - var slot = towerInstance.Owner; - if (slot == PlayerSlot.None) return null; - - var matchState = PlayerMatchState.GetForSlot(slot); - if (matchState == null) return null; - - ownerBuffManager = matchState.GetComponent(); - return ownerBuffManager; - } - // ----- Attack tick ------------------------------------------------- private void TickAttack(TowerDefinition def) @@ -242,9 +218,7 @@ namespace TD.Combat attackCooldown -= Time.deltaTime; if (attackCooldown > 0f) return; - float effectiveFireRate = def.FireRate - * (GetOwnerBuffManager()?.GetMultiplier(BuffStat.AttackSpeed) ?? 1f); - attackCooldown = 1f / effectiveFireRate; + attackCooldown = 1f / def.FireRate; Fire(def); } @@ -342,9 +316,7 @@ namespace TD.Combat private void HitEnemy(TowerDefinition def, EnemyHealth target, PlayerSlot owner) { - float effectiveDamage = def.Damage - * (GetOwnerBuffManager()?.GetMultiplier(BuffStat.Damage) ?? 1f); - target.TakeDamage(effectiveDamage, def.DamageType, owner); + target.TakeDamage(def.Damage, def.DamageType, owner); ApplyStatusEffect(def, target, owner); } @@ -368,8 +340,6 @@ namespace TD.Combat // ----- Projectile spawning ----------------------------------------- - // TODO this seems wacky. Why do we calculate effective damage twice? Are they both used? - // Spawn projectile is void. Should it return a projectile? private void SpawnProjectile(TowerDefinition def, EnemyHealth target) { var go = Instantiate(def.ProjectilePrefab, transform.position, Quaternion.identity); @@ -384,11 +354,9 @@ namespace TD.Combat return; } - float effectiveDamage = def.Damage - * (GetOwnerBuffManager()?.GetMultiplier(BuffStat.Damage) ?? 1f); proj.InitializeServer( target, - effectiveDamage, + def.Damage, def.DamageType, def.TargetType, def.SplashRadius, diff --git a/Assets/_Project/Scripts/Core/BuffStat.cs b/Assets/_Project/Scripts/Core/BuffStat.cs deleted file mode 100644 index 3e2132e..0000000 --- a/Assets/_Project/Scripts/Core/BuffStat.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Assets/_Project/Scripts/Core/BuffStat.cs -namespace TD.Core -{ - /// - /// Identifies which tower stat a modifies. - /// - public enum BuffStat : byte - { - Damage = 0, - AttackSpeed = 1, - } -} diff --git a/Assets/_Project/Scripts/Core/BuffStat.cs.meta b/Assets/_Project/Scripts/Core/BuffStat.cs.meta deleted file mode 100644 index b91fc16..0000000 --- a/Assets/_Project/Scripts/Core/BuffStat.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: fc84fec503f24bc7693ee29558f45d27 \ No newline at end of file diff --git a/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs b/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs deleted file mode 100644 index 3a54012..0000000 --- a/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/ActiveBuff.cs -using System; -using Unity.Collections; -using Unity.Netcode; -using TD.Core; - -namespace TD.Gameplay -{ - /// - /// One buff currently held by a player. Stored in - /// 's NetworkList so all peers see the same set. - /// - /// - /// IEquatable. NGO's NetworkList indexer setter short-circuits when - /// Equals returns true, silently dropping the write. Every mutable field - /// (only ) is included in the comparison so toggling a - /// buff correctly propagates to clients. - /// - public struct ActiveBuff : INetworkSerializable, IEquatable - { - /// Which tower stat this buff multiplies. - public BuffStat Stat; - - /// Multiplicative factor, e.g. 1.25 for +25%. - public float Multiplier; - - /// - /// False when disabled (e.g. by an enemy ability). Excluded from - /// while false. - /// - public bool IsActive; - - /// Human-readable name for the HUD buff list. - public FixedString64Bytes DisplayName; - - // ----- INetworkSerializable --------------------------------------- - - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter - { - byte statByte = (byte)Stat; - serializer.SerializeValue(ref statByte); - Stat = (BuffStat)statByte; - - serializer.SerializeValue(ref Multiplier); - serializer.SerializeValue(ref IsActive); - serializer.SerializeValue(ref DisplayName); - } - - // ----- IEquatable ------------------------------------------------- - - public bool Equals(ActiveBuff other) - => Stat == other.Stat - && Multiplier == other.Multiplier - && IsActive == other.IsActive - && DisplayName == other.DisplayName; - - public override bool Equals(object obj) => obj is ActiveBuff other && Equals(other); - - public override int GetHashCode() => HashCode.Combine((int)Stat, Multiplier, IsActive); - } -} diff --git a/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs.meta b/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs.meta deleted file mode 100644 index d6868bd..0000000 --- a/Assets/_Project/Scripts/Gameplay/ActiveBuff.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b09203f8acc450bf3b3c4bd8ef3fe84e \ No newline at end of file diff --git a/Assets/_Project/Scripts/Gameplay/BuffCategory.cs b/Assets/_Project/Scripts/Gameplay/BuffCategory.cs deleted file mode 100644 index bad6ee3..0000000 --- a/Assets/_Project/Scripts/Gameplay/BuffCategory.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/BuffCategory.cs -using UnityEngine; - -namespace TD.Gameplay -{ - /// - /// A purchasable category of buffs. The player pays gold - /// and receives a randomly chosen from . - /// - [CreateAssetMenu(fileName = "BuffCategory", menuName = "TD/Buffs/Buff Category")] - public class BuffCategory : ScriptableObject - { - [Tooltip("Name shown on the buff menu purchase button.")] - public string DisplayName; - - [Tooltip("Gold cost to purchase one random buff from this category.")] - [Min(0)] - public int Cost; - - [Tooltip("Set of buffs the player can receive. One is chosen uniformly at random.")] - public BuffDefinition[] Pool; - } -} diff --git a/Assets/_Project/Scripts/Gameplay/BuffCategory.cs.meta b/Assets/_Project/Scripts/Gameplay/BuffCategory.cs.meta deleted file mode 100644 index c2d766d..0000000 --- a/Assets/_Project/Scripts/Gameplay/BuffCategory.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 516e8faec948a4b3d977ec019376c438 \ No newline at end of file diff --git a/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs b/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs deleted file mode 100644 index 951972e..0000000 --- a/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/BuffDefinition.cs -using UnityEngine; -using TD.Core; - -namespace TD.Gameplay -{ - /// - /// One possible buff that can be awarded to a player. Lives in a - /// 's pool and is drawn randomly on purchase. - /// - [CreateAssetMenu(fileName = "BuffDefinition", menuName = "TD/Buffs/Buff Definition")] - public class BuffDefinition : ScriptableObject - { - [Tooltip("Name shown in the buff menu and tooltip.")] - public string DisplayName; - - [Tooltip("Which tower stat this buff multiplies.")] - public BuffStat Stat; - - [Tooltip("Multiplicative factor applied to the stat. 1.25 = +25%.")] - [Min(1f)] - public float Multiplier = 1.25f; - } -} diff --git a/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs.meta b/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs.meta deleted file mode 100644 index 12b38db..0000000 --- a/Assets/_Project/Scripts/Gameplay/BuffDefinition.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: b01f9aea62ee8a1c1991bb2a097de224 \ No newline at end of file diff --git a/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs b/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs index ce1e258..5de1f4a 100644 --- a/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs +++ b/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs @@ -149,14 +149,6 @@ namespace TD.Gameplay // Right-click. Suppressed entirely during a modal mode (placement/paint // controllers handle right-click as cancel there) and when over HUD. if (isModal) return; - - // B: toggle buff menu. - if (keyboard != null && keyboard.bKey.wasPressedThisFrame - && !HUDController.IsTextInputActive) - { - HUDController.Instance?.ToggleBuffMenu(); - } - if (pointerOverHud) return; if (!mouse.rightButton.wasPressedThisFrame) return; diff --git a/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs b/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs deleted file mode 100644 index 506791e..0000000 --- a/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs +++ /dev/null @@ -1,195 +0,0 @@ -// Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs -using System.Collections.Generic; -using Unity.Collections; -using Unity.Netcode; -using UnityEngine; -using TD.Core; - -namespace TD.Gameplay -{ - /// - /// Holds all buffs currently owned by one player. Lives on the Player prefab - /// alongside and . - /// - /// - /// Purchase flow. The owning client calls - /// with a category index. The server - /// validates gold, deducts the cost, picks a random - /// from the category's pool, and appends an - /// to the replicated list. - /// - /// Multiplier query. is called by - /// on the server each time a tower fires. - /// It multiplies all active buffs for the requested stat together. - /// - /// Enable / disable. Enemies can call - /// and by index to temporarily suppress buffs - /// without removing them from the list. - /// - public class PlayerBuffManager : NetworkBehaviour - { - // ----- Static registry (mirrors PlayerGoldManager pattern) ----------- - - private static readonly Dictionary s_byClientId - = new Dictionary(); - - /// Returns the PlayerBuffManager owned by the given client, or null. - public static PlayerBuffManager GetForClient(ulong clientId) - { - s_byClientId.TryGetValue(clientId, out var mgr); - return mgr; - } - - /// Convenience: the local client's own buff manager. - public static PlayerBuffManager Local - { - get - { - var nm = NetworkManager.Singleton; - if (nm == null) return null; - return GetForClient(nm.LocalClientId); - } - } - - // ----- Inspector ------------------------------------------------------ - - [Tooltip("Purchasable buff categories shown in the buff menu. Index in this " + - "array is the categoryIndex passed to RequestPurchaseBuffRpc.")] - [SerializeField] private BuffCategory[] categories; - - private NetworkList buffs; - - private void Awake() - { - buffs = new NetworkList(); - } - - // ----- NGO lifecycle ---------------------------------------------- - - public override void OnNetworkSpawn() - { - s_byClientId[OwnerClientId] = this; - } - - public override void OnNetworkDespawn() - { - if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this) - s_byClientId.Remove(OwnerClientId); - } - - // ----- Public API ------------------------------------------------- - - /// - /// Returns the combined multiplier for across all - /// active buffs owned by this player. Returns 1.0 if no matching buffs - /// are active. Safe to call from any peer. - /// - public float GetMultiplier(BuffStat stat) - { - float result = 1f; - for (int i = 0; i < buffs.Count; i++) - { - var buff = buffs[i]; - if (buff.Stat == stat && buff.IsActive) - result *= buff.Multiplier; - } - return result; - } - - /// - /// Returns the number of buff categories available, for building the UI. - /// - public int CategoryCount => categories?.Length ?? 0; - - /// Returns the category at the given index, or null. - public BuffCategory GetCategory(int index) - => (categories != null && index >= 0 && index < categories.Length) - ? categories[index] - : null; - - /// Read-only access to the buff list for UI rendering. - public NetworkList Buffs => buffs; - - // ----- Server helpers --------------------------------------------- - - /// - /// Server-only: disables the buff at . - /// The buff remains in the list and can be re-enabled. - /// - public void ServerDisableBuff(int index) - { - if (!IsServer) return; - if (index < 0 || index >= buffs.Count) return; - - var b = buffs[index]; - if (!b.IsActive) return; - b.IsActive = false; - buffs[index] = b; - } - - /// Server-only: re-enables the buff at . - public void ServerEnableBuff(int index) - { - if (!IsServer) return; - if (index < 0 || index >= buffs.Count) return; - - var b = buffs[index]; - if (b.IsActive) return; - b.IsActive = true; - buffs[index] = b; - } - - // ----- Purchase RPC ----------------------------------------------- - - /// - /// Owning-client entry point. Sends a request to the server to purchase - /// a random buff from the category at . - /// The server validates gold and adds the buff if the purchase succeeds. - /// - [Rpc(SendTo.Server, RequireOwnership = true)] - public void RequestPurchaseBuffRpc(int categoryIndex) - { - if (categories == null || categoryIndex < 0 || categoryIndex >= categories.Length) - { - Debug.LogWarning("[PlayerBuffManager] RequestPurchaseBuff: invalid category index."); - return; - } - - var category = categories[categoryIndex]; - if (category == null || category.Pool == null || category.Pool.Length == 0) - { - Debug.LogWarning("[PlayerBuffManager] RequestPurchaseBuff: category has no buffs."); - return; - } - - var goldManager = PlayerGoldManager.GetForClient(OwnerClientId); - if (goldManager == null) - { - Debug.LogError("[PlayerBuffManager] RequestPurchaseBuff: no PlayerGoldManager found."); - return; - } - - if (goldManager.CurrentGold < category.Cost) - { - Debug.Log($"[PlayerBuffManager] Client {OwnerClientId} cannot afford " + - $"'{category.DisplayName}' (cost {category.Cost}, " + - $"gold {goldManager.CurrentGold})."); - return; - } - - goldManager.DeductGold(category.Cost); - - var def = category.Pool[Random.Range(0, category.Pool.Length)]; - buffs.Add(new ActiveBuff - { - Stat = def.Stat, - Multiplier = def.Multiplier, - IsActive = true, - DisplayName = new FixedString64Bytes(def.DisplayName ?? string.Empty), - }); - - Debug.Log($"[PlayerBuffManager] Client {OwnerClientId} purchased buff " + - $"'{def.DisplayName}' ({def.Stat} ×{def.Multiplier})."); - } - } -} diff --git a/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs.meta b/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs.meta deleted file mode 100644 index b1951a5..0000000 --- a/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs.meta +++ /dev/null @@ -1,2 +0,0 @@ -fileFormatVersion: 2 -guid: 7775ee3a2f441b52480aabf54be6c1b6 \ No newline at end of file diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs index 45930de..6c3963f 100644 --- a/Assets/_Project/Scripts/UI/HUDController.cs +++ b/Assets/_Project/Scripts/UI/HUDController.cs @@ -21,60 +21,55 @@ namespace TD.UI [RequireComponent(typeof(UIDocument))] public class HUDController : MonoBehaviour { - public static HUDController Instance { get; private set; } - // ----- Inspector -------------------------------------------------- - [Header("Scene References")] [Tooltip("The local client's TowerPlacementController.")] [SerializeField] - private TowerPlacementController placementController; + [Header("Scene References")] + [Tooltip("The local client's TowerPlacementController.")] + [SerializeField] private TowerPlacementController placementController; [Tooltip("The local client's TowerPaintController (drives the Paint tab + paint cursor).")] [SerializeField] private TowerPaintController paintController; - [Tooltip("The TowerPlacementManager NetworkObject in the scene.")] [SerializeField] - private TowerPlacementManager placementManager; + [Tooltip("The TowerPlacementManager NetworkObject in the scene.")] + [SerializeField] private TowerPlacementManager placementManager; [Tooltip("The local client's CameraController. Used by the minimap for click-to-jump " + "and drag-to-pan.")] - [SerializeField] - private CameraController cameraController; + [SerializeField] private CameraController cameraController; - [Header("Settings")] [SerializeField] private float rejectionMessageDuration = 2.5f; + [Header("Settings")] + [SerializeField] private float rejectionMessageDuration = 2.5f; [Tooltip("Maximum visible height of the chat feed in pixels. Content past this " + "height is clipped — older messages scroll off the top of the visible area " + "but stay in history (scroll up while chat is open to view).")] - [SerializeField] - private float chatMaxHeight = 280f; + [SerializeField] private float chatMaxHeight = 280f; [Tooltip("Maximum messages kept in chat history. Defaults to effectively unlimited " + "(int.MaxValue) — every message sent during a match stays scrollable. " + "Lower the value if a long match ever shows DOM perf issues; this field " + "is the safety valve, not a normal-play limit.")] - [SerializeField] - private int chatMaxMessages = int.MaxValue; + [SerializeField] private int chatMaxMessages = int.MaxValue; - [Tooltip("Color used for SYSTEM chat messages (e.g. 'Life Lost', income changes).")] [SerializeField] - private Color chatSystemColor = new Color(1f, 0.7f, 0.2f); + [Tooltip("Color used for SYSTEM chat messages (e.g. 'Life Lost', income changes).")] + [SerializeField] private Color chatSystemColor = new Color(1f, 0.7f, 0.2f); [Tooltip("Color used for PLAYER chat message bodies. Sender prefix uses the player's slot color.")] - [SerializeField] - private Color chatPlayerColor = new Color(0.92f, 0.92f, 0.92f); + [SerializeField] private Color chatPlayerColor = new Color(0.92f, 0.92f, 0.92f); // ----- Cached UI element references ------------------------------- private Label goldLabel; private Label waveLabel; private Label livesLabel; - private Label nextWaveLabel; // prep countdown ("next: 0:12") - private Label leakedLabel; // local player's origin-leak count ("leaked: 3") - private Label incomeLabel; // top-bar per-wave gold-earned counter ("+150 g/wave") + private Label nextWaveLabel; // prep countdown ("next: 0:12") + private Label leakedLabel; // local player's origin-leak count ("leaked: 3") + private Label incomeLabel; // top-bar per-wave gold-earned counter ("+150 g/wave") private VisualElement playerListContainer; // right-panel scoreboard rows private Label portraitName; private Label levelLabel; private VisualElement statLines; private VisualElement commandGrid; - private VisualElement actionFrame; // hidden via display:none when no actions are available private VisualElement commandTabs; // Build/Paint tab row — shown only for a Builder selection private Button tabBuild; @@ -82,7 +77,6 @@ namespace TD.UI private VisualElement buildProgressContainer; // info-panel sub-view, shown for BuildSiteVisual selections private VisualElement buildProgressFill; // width driven each frame from progress private Label buildProgressPercent; - private Label ttTitle; private Label ttDesc; private Label ttStats; @@ -95,23 +89,19 @@ namespace TD.UI // without rebuilding the elements. private VisualElement enemyHealthBar; private VisualElement enemyHealthFill; - private Label enemyHealthText; + private Label enemyHealthText; // Match-end overlay — built once on Start and toggled on Phase changes. private VisualElement matchEndOverlay; - - // Buff menu overlay — toggled by the B key via ToggleBuffMenu(). - private VisualElement buffMenuOverlay; - private VisualElement buffMenuContent; - private Label matchEndTitle; + private Label matchEndTitle; // Chat panel (bottom-left, above portrait) — programmatic. The container // holds both the scrollable feed and the input. Highlight + scroll // interactivity are toggled on the container when typing. private VisualElement chatContainer; - private ScrollView chatFeed; - private TextField chatInput; - private bool chatInputOpen; + private ScrollView chatFeed; + private TextField chatInput; + private bool chatInputOpen; // Frame on which the chat input was opened or closed. Enter on that frame // and the next one is ignored to prevent the open/close-triggering keypress @@ -133,12 +123,12 @@ namespace TD.UI private CommandTab activeTab = CommandTab.Build; private Coroutine rejectionFadeCoroutine; - private bool placementManagerReady; // true once TowerPlacementManager.Instance is non-null + private bool placementManagerReady; // true once TowerPlacementManager.Instance is non-null private bool uiInitialized; - private bool selectionSubscribed; // true once we've successfully hooked SelectionState.OnSelectionChanged - private bool matchStateSubscribed; // true once OnPhaseChanged is hooked + private bool selectionSubscribed; // true once we've successfully hooked SelectionState.OnSelectionChanged + private bool matchStateSubscribed; // true once OnPhaseChanged is hooked private MinimapView minimapView; - private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us + private IPanel myPanel; // tracked separately so OnDestroy only clears the static if it still points at us // ----- Hotkeys ---------------------------------------------------- // @@ -160,15 +150,10 @@ namespace TD.UI private readonly struct HotkeyBinding { public readonly Key Key; - public readonly VisualElement Button; // for enabledSelf gating + public readonly VisualElement Button; // for enabledSelf gating public readonly System.Action Action; - public HotkeyBinding(Key k, VisualElement b, System.Action a) - { - Key = k; - Button = b; - Action = a; - } + { Key = k; Button = b; Action = a; } } // ----- Static hit-test probe -------------------------------------- @@ -264,7 +249,6 @@ namespace TD.UI // Cache element references — log a warning for any that are missing // so UXML/USS mismatches surface immediately. - goldLabel = Require