141 lines
6.2 KiB
C#
141 lines
6.2 KiB
C#
// Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs
|
|
using System.Collections.Generic;
|
|
using Unity.Netcode;
|
|
using UnityEngine;
|
|
using TD.Core;
|
|
using TD.Gameplay.BuilderEffects;
|
|
|
|
namespace TD.Gameplay
|
|
{
|
|
/// <summary>
|
|
/// Per-player set of granted builder effects. Lives on the Player prefab alongside
|
|
/// <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
|
|
/// level, no enable/disable toggle) — it's either granted or it isn't — so the granted set
|
|
/// is a <see cref="NetworkList{T}"/> of <see cref="BuilderEffectKind"/> values. Stored as
|
|
/// <c>byte</c> because <c>NetworkList<T></c> requires <c>T : IEquatable<T></c>
|
|
/// and plain enums don't implement that (see <see cref="TD.Gameplay.BuildJob"/> for the
|
|
/// same constraint on a struct); <c>byte</c> already does, for free.</para>
|
|
///
|
|
/// <para><b>Granted via draft.</b> <see cref="TD.Gameplay.Draft.BuilderEffectDraftOption"/>
|
|
/// calls <see cref="ServerGrantEffect"/> when a player picks it. Effects are otherwise
|
|
/// permanent for the match — the only way one leaves the granted set is
|
|
/// <see cref="ServerUpgradeEffect"/> swapping it for its upgraded replacement.</para>
|
|
///
|
|
/// <para><b>Query, don't snapshot.</b> Consumers (e.g. <see cref="WaveManager"/> awarding
|
|
/// kill gold) query this manager live at the point of use rather than towers caching their
|
|
/// own copy — every tower a player owns benefits immediately, including ones built before
|
|
/// the effect was granted.</para>
|
|
///
|
|
/// <para><b>Generic accessor, not one method per effect.</b>
|
|
/// <see cref="GetEffectDefinition{T}"/> is the only way to read an effect's tunable data.
|
|
/// This keeps the API surface fixed as the effect roster grows — a new effect kind adds a
|
|
/// call site where it's consumed (e.g. a new line in <c>WaveManager</c>), not a new method
|
|
/// here.</para>
|
|
/// </remarks>
|
|
public class BuilderUpgradeManager : NetworkBehaviour
|
|
{
|
|
// ----- Static registry (mirrors PlayerGoldManager / PlayerTowerDeck) -----
|
|
|
|
private static readonly Dictionary<ulong, BuilderUpgradeManager> s_byClientId
|
|
= new Dictionary<ulong, BuilderUpgradeManager>();
|
|
|
|
/// <summary>Returns the manager owned by the given client, or null.</summary>
|
|
public static BuilderUpgradeManager GetForClient(ulong clientId)
|
|
{
|
|
s_byClientId.TryGetValue(clientId, out var mgr);
|
|
return mgr;
|
|
}
|
|
|
|
/// <summary>Convenience: the local client's own manager.</summary>
|
|
public static BuilderUpgradeManager Local
|
|
{
|
|
get
|
|
{
|
|
var nm = NetworkManager.Singleton;
|
|
if (nm == null || !nm.IsClient) return null;
|
|
return GetForClient(nm.LocalClientId);
|
|
}
|
|
}
|
|
|
|
// ----- Networked state --------------------------------------------
|
|
|
|
// Granted effect kinds, stored as byte (see class remarks for why not the enum
|
|
// directly). readPerm Everyone (consistent with the other per-player managers);
|
|
// writePerm Server — both are NetworkList's defaults.
|
|
private readonly NetworkList<byte> grantedKinds = new NetworkList<byte>();
|
|
|
|
/// <summary>Fired on every peer when a new effect is granted.</summary>
|
|
public event System.Action OnUpgradesChanged;
|
|
|
|
// ----- NGO lifecycle ------------------------------------------------
|
|
|
|
public override void OnNetworkSpawn()
|
|
{
|
|
s_byClientId[OwnerClientId] = this;
|
|
grantedKinds.OnListChanged += HandleGrantedKindsChanged;
|
|
}
|
|
|
|
public override void OnNetworkDespawn()
|
|
{
|
|
grantedKinds.OnListChanged -= HandleGrantedKindsChanged;
|
|
|
|
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
|
|
s_byClientId.Remove(OwnerClientId);
|
|
}
|
|
|
|
private void HandleGrantedKindsChanged(NetworkListEvent<byte> change) => OnUpgradesChanged?.Invoke();
|
|
|
|
// ----- Read API -----------------------------------------------------
|
|
|
|
/// <summary>True if this player has been granted the given effect.</summary>
|
|
public bool PlayerHasEffect(BuilderEffectKind kind) => grantedKinds.Contains((byte)kind);
|
|
|
|
/// <summary>
|
|
/// Returns this player's <see cref="BuilderEffectDefinition"/> for <paramref name="kind"/>
|
|
/// as <typeparamref name="T"/>, or null if the player doesn't have it (or the pool has no
|
|
/// asset for it). Callers extract whatever field they need — see the "generic accessor"
|
|
/// note above for why this isn't a per-effect method.
|
|
/// </summary>
|
|
public T GetEffectDefinition<T>(BuilderEffectKind kind) where T : BuilderEffectDefinition
|
|
=> PlayerHasEffect(kind) ? BuilderEffectPool.Instance?.Get(kind) as T : null;
|
|
|
|
// ----- Server mutation -----------------------------------------------
|
|
|
|
/// <summary>
|
|
/// Server-only: grants the given effect (a draft pick). No-op if already granted.
|
|
/// Returns true if it was actually granted.
|
|
/// </summary>
|
|
public bool ServerGrantEffect(BuilderEffectKind kind)
|
|
{
|
|
if (!IsServer) return false;
|
|
|
|
byte value = (byte)kind;
|
|
if (grantedKinds.Contains(value)) return false;
|
|
|
|
grantedKinds.Add(value);
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Server-only: replaces a previously granted effect with a different one (an upgrade
|
|
/// pick). No-op if the old effect isn't currently granted or the new one already is.
|
|
/// Returns true if the swap happened.
|
|
/// </summary>
|
|
public bool ServerUpgradeEffect(BuilderEffectKind oldKind, BuilderEffectKind newKind)
|
|
{
|
|
if (!IsServer) return false;
|
|
|
|
byte newValue = (byte)newKind;
|
|
if (grantedKinds.Contains(newValue)) return false;
|
|
|
|
byte oldValue = (byte)oldKind;
|
|
if (!grantedKinds.Remove(oldValue)) return false;
|
|
|
|
grantedKinds.Add(newValue);
|
|
return true;
|
|
}
|
|
}
|
|
}
|