// Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs
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
{
[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();
}
// ----- 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}).");
}
}
}