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/Definitions/BuilderSpells/SlowAreaSpell.asset b/Assets/_Project/Definitions/BuilderSpells/SlowAreaSpell.asset index e2550d2..3439119 100644 --- a/Assets/_Project/Definitions/BuilderSpells/SlowAreaSpell.asset +++ b/Assets/_Project/Definitions/BuilderSpells/SlowAreaSpell.asset @@ -20,7 +20,7 @@ MonoBehaviour: Radius: 5 enemyLayerMask: serializedVersion: 2 - m_Bits: 1024 + m_Bits: 1152 areaVfxPrefab: {fileID: 1462673130280185047, guid: bf2f1f2a9a54e9162b80673e3f7eeaf6, type: 3} areaSound: clip: {fileID: 8300000, guid: 038ff3eab9aa0034bac26ccb648ef2a1, type: 3} diff --git a/Assets/_Project/Prefabs/Player/Player.prefab b/Assets/_Project/Prefabs/Player/Player.prefab index 6e06dfd..ce17790 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} - component: {fileID: 2806524246861401799} - component: {fileID: 2806524246861401801} - component: {fileID: 5683786710272852339} @@ -53,7 +52,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 121878297 + GlobalObjectIdHash: 1552073510 InScenePlacedSourceGlobalObjectIdHash: 0 DeferredDespawnTick: 0 Ownership: 1 @@ -107,21 +106,6 @@ 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} --- !u!114 &2806524246861401799 MonoBehaviour: m_ObjectHideFlags: 0 diff --git a/Assets/_Project/Scripts/Combat/TowerCombat.cs b/Assets/_Project/Scripts/Combat/TowerCombat.cs index 6c1a53f..3001be9 100644 --- a/Assets/_Project/Scripts/Combat/TowerCombat.cs +++ b/Assets/_Project/Scripts/Combat/TowerCombat.cs @@ -67,11 +67,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]; @@ -239,25 +234,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; - - var slot = towerInstance.Owner; - if (slot == PlayerSlot.None) return null; - - var matchState = PlayerMatchState.GetForSlot(slot); - if (matchState == null) return null; - - ownerBuffManager = matchState.GetComponent(); - buffManagerResolved = true; - return ownerBuffManager; - } - // ----- Attack tick ------------------------------------------------- private void TickAttack(TowerDefinition def) @@ -265,9 +241,7 @@ namespace TD.Combat attackCooldown -= Time.deltaTime; if (attackCooldown > 0f) return; - float effectiveFireRate = def.AttacksPerSecond - * (GetOwnerBuffManager()?.GetMultiplier(BuffStat.AttackSpeed) ?? 1f); - attackCooldown = 1f / effectiveFireRate; + attackCooldown = 1f / def.AttacksPerSecond; Fire(def); } @@ -484,9 +458,7 @@ namespace TD.Combat private void HitEnemy(in CombatProfile p, EnemyHealth target, PlayerSlot owner) { - var multiplier = GetOwnerBuffManager()?.GetMultiplier(BuffStat.Damage) ?? 1f; - float effectiveDamage = p.Damage * multiplier; - target.TakeDamage(effectiveDamage, p.DamageType, owner); + target.TakeDamage(p.Damage, p.DamageType, owner); ApplyStatusEffect(p, target, owner); } @@ -525,11 +497,9 @@ namespace TD.Combat return; } - float effectiveDamage = def.Damage - * (GetOwnerBuffManager()?.GetMultiplier(BuffStat.Damage) ?? 1f); proj.InitializeServer( target, - effectiveDamage, + def.Damage, p.DamageType, p.TargetType, p.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 23a1c05..41eeee6 100644 --- a/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs +++ b/Assets/_Project/Scripts/Gameplay/BuilderInputController.cs @@ -151,13 +151,6 @@ namespace TD.Gameplay // 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(); - } - // Tab: select the builder, or (if already selected) recenter the camera on it. if (keyboard != null && keyboard.tabKey.wasPressedThisFrame && !HUDController.IsTextInputActive) diff --git a/Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs b/Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs index 5f07d31..f11f9ec 100644 --- a/Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs +++ b/Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs @@ -9,8 +9,7 @@ namespace TD.Gameplay { /// /// Per-player set of granted builder effects. Lives on the Player prefab alongside - /// , , and - /// . + /// and . /// /// /// List of granted kinds. A builder effect carries no per-grant state (no diff --git a/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs b/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs deleted file mode 100644 index 52efbdc..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, InvokePermission = RpcInvokePermission.Owner)] - 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/Gameplay/PlayerTowerDeck.cs b/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs index eb5687c..356d459 100644 --- a/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs +++ b/Assets/_Project/Scripts/Gameplay/PlayerTowerDeck.cs @@ -7,8 +7,8 @@ namespace TD.Gameplay { /// /// Per-player set of unlocked tower types — the player's "deck". Lives on the - /// Player prefab alongside , - /// , and . + /// Player prefab alongside and + /// . /// /// /// Roguelike keystone. Players no longer see every tower in the match diff --git a/Assets/_Project/Scripts/UI/HUDController.cs b/Assets/_Project/Scripts/UI/HUDController.cs index badd2b8..450e0f9 100644 --- a/Assets/_Project/Scripts/UI/HUDController.cs +++ b/Assets/_Project/Scripts/UI/HUDController.cs @@ -101,10 +101,6 @@ namespace TD.UI // 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; // Draft overlay (roguelike between-waves draft). Non-modal: the card row floats at @@ -698,9 +694,6 @@ namespace TD.UI // MatchState.OnPhaseChanged fires Victory or Defeat. BuildMatchEndOverlay(root); - // Build the buff menu overlay. Hidden until the player presses B. - BuildBuffMenuOverlay(root); - // Build the draft overlay (roguelike between-waves draft). Hidden until the // local player has an active draft (or to show the paid "Buy Roll" button). BuildDraftOverlay(root); @@ -1173,13 +1166,11 @@ namespace TD.UI { // 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. + // TowerTypeId resolved back to its definition via the catalog. var deck = PlayerTowerDeck.Local; if (deck != null) { - int count = Mathf.Min(deck.Count, GRID_MAX - 1); + int count = Mathf.Min(deck.Count, GRID_MAX); for (int i = 0; i < count; i++) { int typeId = deck.GetTypeIdAt(i); @@ -1189,7 +1180,6 @@ namespace TD.UI } } } - cells[GRID_MAX - 1] = CreateBuffMenuButton(HotkeyLayout[GRID_MAX - 1]); } else if (selection is TowerInstance tower) { @@ -1374,9 +1364,7 @@ namespace TD.UI // Sell action for a completed tower. Refund is computed on the TowerInstance (single // source of truth shared with the server) so the badge shows exactly what the player - // gets back. Hotkey is Key.None: the bottom-right slot's letter (B) is already the - // global buff-menu toggle — see CreateBuffMenuButton — so binding it here would fire - // both. The button stays click-only. + // gets back. The button stays click-only (Key.None). private VisualElement CreateSellButton(TowerInstance tower, Key hotkey) { // Only the tower's owner can sell it (the server enforces this too). A non-owner @@ -1399,17 +1387,6 @@ namespace TD.UI return btn; } - private VisualElement CreateBuffMenuButton(Key hotkey) - { - // Key.None so HandleHotkeys doesn't register B here — BuilderInputController - // already fires ToggleBuffMenu on B globally. Registering it in both places - // causes a double-toggle on the same frame when the builder is selected. - return CreateActionButton( - costText: "Buffs [B]", - hotkey: Key.None, - onClick: () => ToggleBuffMenu()); - } - // Cancel action for an in-progress build. Fires the owner-only RPC; the // server cancels the matching job (or, for shelved sites, refunds + despawns // directly), full gold is refunded, the BuildSiteVisual is despawned, and @@ -1712,149 +1689,6 @@ namespace TD.UI // ----- Chat feed + input ----------------------------------------- - // ----- Buff menu overlay ------------------------------------------ - - private void BuildBuffMenuOverlay(VisualElement root) - { - buffMenuOverlay = new VisualElement(); - buffMenuOverlay.style.position = Position.Absolute; - buffMenuOverlay.style.left = 0; - buffMenuOverlay.style.right = 0; - buffMenuOverlay.style.top = 0; - buffMenuOverlay.style.bottom = 0; - buffMenuOverlay.style.alignItems = Align.Center; - buffMenuOverlay.style.justifyContent = Justify.Center; - buffMenuOverlay.style.backgroundColor = new Color(0f, 0f, 0f, 0.5f); - buffMenuOverlay.style.display = DisplayStyle.None; - buffMenuOverlay.pickingMode = PickingMode.Position; - - var panel = new VisualElement(); - panel.style.minWidth = 340; - panel.style.paddingTop = 20; - panel.style.paddingBottom = 20; - panel.style.paddingLeft = 28; - panel.style.paddingRight = 28; - panel.style.backgroundColor = new Color(0.08f, 0.08f, 0.10f, 0.95f); - panel.style.borderTopWidth = panel.style.borderBottomWidth = - panel.style.borderLeftWidth = panel.style.borderRightWidth = 2; - var border = new Color(0.4f, 0.4f, 0.45f); - panel.style.borderTopColor = panel.style.borderBottomColor = - panel.style.borderLeftColor = panel.style.borderRightColor = border; - - var title = new Label("Buffs"); - title.style.fontSize = 24; - title.style.color = Color.white; - title.style.unityFontStyleAndWeight = FontStyle.Bold; - title.style.marginBottom = 12; - panel.Add(title); - - // Scrollable content area: current buffs + purchase buttons. - // Rebuilt each time the menu is shown via RefreshBuffMenuContent(). - buffMenuContent = new VisualElement(); - panel.Add(buffMenuContent); - - var closeBtn = new Button(() => SetBuffMenuVisible(false)) { text = "Close [B]" }; - closeBtn.style.marginTop = 16; - closeBtn.style.height = 32; - panel.Add(closeBtn); - - buffMenuOverlay.Add(panel); - root.Add(buffMenuOverlay); - } - - /// - /// Toggles the buff menu overlay. Called by - /// when the player presses B. - /// - public void ToggleBuffMenu() - { - bool nowVisible = buffMenuOverlay?.style.display == DisplayStyle.None; - SetBuffMenuVisible(nowVisible); - } - - private void SetBuffMenuVisible(bool visible) - { - if (buffMenuOverlay == null) return; - buffMenuOverlay.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None; - if (visible) RefreshBuffMenuContent(); - } - - private void RefreshBuffMenuContent() - { - if (buffMenuContent == null) return; - buffMenuContent.Clear(); - - var buffManager = TD.Gameplay.PlayerBuffManager.Local; - - // Current buffs section. - var buffsHeader = new Label("Active Buffs"); - buffsHeader.style.color = new Color(0.7f, 0.9f, 0.7f); - buffsHeader.style.fontSize = 14; - buffsHeader.style.marginBottom = 4; - buffMenuContent.Add(buffsHeader); - - if (buffManager == null || buffManager.Buffs.Count == 0) - { - var none = new Label("None"); - none.style.color = new Color(0.55f, 0.55f, 0.55f); - none.style.marginBottom = 8; - buffMenuContent.Add(none); - } - else - { - for (int i = 0; i < buffManager.Buffs.Count; i++) - { - var buff = buffManager.Buffs[i]; - string statName = buff.Stat == TD.Core.BuffStat.Damage ? "Damage" : "Attack Speed"; - string activeTag = buff.IsActive ? "" : " [DISABLED]"; - var row = new Label($"• {buff.DisplayName} ({statName} ×{buff.Multiplier:F2}){activeTag}"); - row.style.color = buff.IsActive ? Color.white : new Color(0.5f, 0.5f, 0.5f); - row.style.fontSize = 13; - buffMenuContent.Add(row); - } - buffMenuContent.style.marginBottom = 12; - } - - // Purchase buttons section. - var buyHeader = new Label("Purchase"); - buyHeader.style.color = new Color(0.9f, 0.8f, 0.5f); - buyHeader.style.fontSize = 14; - buyHeader.style.marginTop = 8; - buyHeader.style.marginBottom = 4; - buffMenuContent.Add(buyHeader); - - int categoryCount = buffManager?.CategoryCount ?? 0; - if (categoryCount == 0) - { - var none = new Label("No categories available."); - none.style.color = new Color(0.55f, 0.55f, 0.55f); - buffMenuContent.Add(none); - return; - } - - int localGold = TD.Gameplay.PlayerGoldManager.Local?.CurrentGold ?? 0; - for (int i = 0; i < categoryCount; i++) - { - var category = buffManager.GetCategory(i); - if (category == null) continue; - - int idx = i; // capture for lambda - var btn = new Button(() => - { - TD.Gameplay.PlayerBuffManager.Local?.RequestPurchaseBuffRpc(idx); - RefreshBuffMenuContent(); - }) - { - text = $"{category.DisplayName} — {category.Cost}g" - }; - btn.style.height = 34; - btn.style.fontSize = 13; - btn.style.marginBottom = 4; - btn.SetEnabled(localGold >= category.Cost); - buffMenuContent.Add(btn); - } - } - // Bottom-left chat panel. Anchored 12px from the left edge, with the // bottom edge sitting above the 220px bottom-ui. Layout uses a flex // column: scrollable feed on top, input below. The feed clips at