UnityTowerDefense/Assets/_Project/Scripts/Gameplay/PlayerBuffManager.cs

195 lines
7.3 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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, 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}).");
}
}
}