remove deprecated buff system

This commit is contained in:
Ian Woods 2026-07-21 21:20:30 -07:00
parent 35ea90f71b
commit 330a929985
22 changed files with 11 additions and 615 deletions

View file

@ -1,61 +0,0 @@
// Assets/_Project/Scripts/Gameplay/ActiveBuff.cs
using System;
using Unity.Collections;
using Unity.Netcode;
using TD.Core;
namespace TD.Gameplay
{
/// <summary>
/// One buff currently held by a player. Stored in
/// <see cref="PlayerBuffManager"/>'s NetworkList so all peers see the same set.
/// </summary>
/// <remarks>
/// <para><b>IEquatable.</b> NGO's NetworkList indexer setter short-circuits when
/// Equals returns true, silently dropping the write. Every mutable field
/// (only <see cref="IsActive"/>) is included in the comparison so toggling a
/// buff correctly propagates to clients.</para>
/// </remarks>
public struct ActiveBuff : INetworkSerializable, IEquatable<ActiveBuff>
{
/// <summary>Which tower stat this buff multiplies.</summary>
public BuffStat Stat;
/// <summary>Multiplicative factor, e.g. 1.25 for +25%.</summary>
public float Multiplier;
/// <summary>
/// False when disabled (e.g. by an enemy ability). Excluded from
/// <see cref="PlayerBuffManager.GetMultiplier"/> while false.
/// </summary>
public bool IsActive;
/// <summary>Human-readable name for the HUD buff list.</summary>
public FixedString64Bytes DisplayName;
// ----- INetworkSerializable ---------------------------------------
public void NetworkSerialize<T>(BufferSerializer<T> 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);
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: b09203f8acc450bf3b3c4bd8ef3fe84e

View file

@ -1,23 +0,0 @@
// Assets/_Project/Scripts/Gameplay/BuffCategory.cs
using UnityEngine;
namespace TD.Gameplay
{
/// <summary>
/// A purchasable category of buffs. The player pays <see cref="Cost"/> gold
/// and receives a randomly chosen <see cref="BuffDefinition"/> from <see cref="Pool"/>.
/// </summary>
[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;
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 516e8faec948a4b3d977ec019376c438

View file

@ -1,24 +0,0 @@
// Assets/_Project/Scripts/Gameplay/BuffDefinition.cs
using UnityEngine;
using TD.Core;
namespace TD.Gameplay
{
/// <summary>
/// One possible buff that can be awarded to a player. Lives in a
/// <see cref="BuffCategory"/>'s pool and is drawn randomly on purchase.
/// </summary>
[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;
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: b01f9aea62ee8a1c1991bb2a097de224

View file

@ -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)

View file

@ -9,8 +9,7 @@ namespace TD.Gameplay
{
/// <summary>
/// Per-player set of granted builder effects. Lives on the Player prefab alongside
/// <see cref="PlayerGoldManager"/>, <see cref="PlayerTowerDeck"/>, and
/// <see cref="PlayerBuffManager"/>.
/// <see cref="PlayerGoldManager"/> and <see cref="PlayerTowerDeck"/>.
/// </summary>
/// <remarks>
/// <para><b>List of granted kinds.</b> A builder effect carries no per-grant state (no

View file

@ -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
{
/// <summary>
/// Holds all buffs currently owned by one player. Lives on the Player prefab
/// alongside <see cref="PlayerGoldManager"/> and <see cref="PlayerMatchState"/>.
/// </summary>
/// <remarks>
/// <para><b>Purchase flow.</b> The owning client calls
/// <see cref="RequestPurchaseBuffRpc"/> with a category index. The server
/// validates gold, deducts the cost, picks a random
/// <see cref="BuffDefinition"/> from the category's pool, and appends an
/// <see cref="ActiveBuff"/> to the replicated <see cref="buffs"/> list.</para>
///
/// <para><b>Multiplier query.</b> <see cref="GetMultiplier"/> is called by
/// <see cref="TD.Combat.TowerCombat"/> on the server each time a tower fires.
/// It multiplies all active buffs for the requested stat together.</para>
///
/// <para><b>Enable / disable.</b> Enemies can call <see cref="ServerDisableBuff"/>
/// and <see cref="ServerEnableBuff"/> by index to temporarily suppress buffs
/// without removing them from the list.</para>
/// </remarks>
public class PlayerBuffManager : NetworkBehaviour
{
// ----- Static registry (mirrors PlayerGoldManager pattern) -----------
private static readonly Dictionary<ulong, PlayerBuffManager> s_byClientId
= new Dictionary<ulong, PlayerBuffManager>();
/// <summary>Returns the PlayerBuffManager owned by the given client, or null.</summary>
public static PlayerBuffManager GetForClient(ulong clientId)
{
s_byClientId.TryGetValue(clientId, out var mgr);
return mgr;
}
/// <summary>Convenience: the local client's own buff manager.</summary>
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<ActiveBuff> buffs;
private void Awake()
{
buffs = new NetworkList<ActiveBuff>();
}
// ----- 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 -------------------------------------------------
/// <summary>
/// Returns the combined multiplier for <paramref name="stat"/> across all
/// active buffs owned by this player. Returns 1.0 if no matching buffs
/// are active. Safe to call from any peer.
/// </summary>
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;
}
/// <summary>
/// Returns the number of buff categories available, for building the UI.
/// </summary>
public int CategoryCount => categories?.Length ?? 0;
/// <summary>Returns the category at the given index, or null.</summary>
public BuffCategory GetCategory(int index)
=> (categories != null && index >= 0 && index < categories.Length)
? categories[index]
: null;
/// <summary>Read-only access to the buff list for UI rendering.</summary>
public NetworkList<ActiveBuff> Buffs => buffs;
// ----- Server helpers ---------------------------------------------
/// <summary>
/// Server-only: disables the buff at <paramref name="index"/>.
/// The buff remains in the list and can be re-enabled.
/// </summary>
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;
}
/// <summary>Server-only: re-enables the buff at <paramref name="index"/>.</summary>
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 -----------------------------------------------
/// <summary>
/// Owning-client entry point. Sends a request to the server to purchase
/// a random buff from the category at <paramref name="categoryIndex"/>.
/// The server validates gold and adds the buff if the purchase succeeds.
/// </summary>
[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}).");
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 7775ee3a2f441b52480aabf54be6c1b6

View file

@ -7,8 +7,8 @@ namespace TD.Gameplay
{
/// <summary>
/// Per-player set of unlocked tower types — the player's "deck". Lives on the
/// Player prefab alongside <see cref="PlayerGoldManager"/>,
/// <see cref="PlayerMatchState"/>, and <see cref="PlayerBuffManager"/>.
/// Player prefab alongside <see cref="PlayerGoldManager"/> and
/// <see cref="PlayerMatchState"/>.
/// </summary>
/// <remarks>
/// <para><b>Roguelike keystone.</b> Players no longer see every tower in the match