new builder option per draft - first one is gold per kill
This commit is contained in:
parent
4a37d3747c
commit
157b77ebfa
21 changed files with 456 additions and 7 deletions
125
Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs
Normal file
125
Assets/_Project/Scripts/Gameplay/BuilderUpgradeManager.cs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
// 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"/>, <see cref="PlayerTowerDeck"/>, and
|
||||
/// <see cref="PlayerBuffManager"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para><b>Bitmask, not a list.</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 one
|
||||
/// <see cref="NetworkVariable{T}"/> bitmask keyed by <see cref="BuilderEffectKind"/> rather
|
||||
/// than a <c>NetworkList</c>. "Has effect" and "grant effect" are both O(1) bit ops.</para>
|
||||
///
|
||||
/// <para><b>Granted via draft.</b> <see cref="TD.Gameplay.Draft.BuilderEffectDraftOption"/>
|
||||
/// calls <see cref="ServerGrantEffect"/> when a player picks it. Effects are permanent for
|
||||
/// the match — there is no revoke.</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 --------------------------------------------
|
||||
|
||||
// One bit per BuilderEffectKind. readPerm Everyone (consistent with the other
|
||||
// per-player managers); writePerm Server.
|
||||
private readonly NetworkVariable<uint> grantedMask = new NetworkVariable<uint>(
|
||||
value: 0,
|
||||
readPerm: NetworkVariableReadPermission.Everyone,
|
||||
writePerm: NetworkVariableWritePermission.Server
|
||||
);
|
||||
|
||||
/// <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;
|
||||
grantedMask.OnValueChanged += HandleMaskChanged;
|
||||
}
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
grantedMask.OnValueChanged -= HandleMaskChanged;
|
||||
|
||||
if (s_byClientId.TryGetValue(OwnerClientId, out var registered) && registered == this)
|
||||
s_byClientId.Remove(OwnerClientId);
|
||||
}
|
||||
|
||||
private void HandleMaskChanged(uint previous, uint current) => OnUpgradesChanged?.Invoke();
|
||||
|
||||
// ----- Read API -----------------------------------------------------
|
||||
|
||||
/// <summary>True if this player has been granted the given effect.</summary>
|
||||
public bool PlayerHasEffect(BuilderEffectKind kind) => (grantedMask.Value & BitFor(kind)) != 0;
|
||||
|
||||
/// <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;
|
||||
|
||||
uint bit = BitFor(kind);
|
||||
if ((grantedMask.Value & bit) != 0) return false;
|
||||
|
||||
grantedMask.Value |= bit;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static uint BitFor(BuilderEffectKind kind) => 1u << (int)kind;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue