// Assets/_Project/Scripts/Gameplay/SpellSlot.cs using System; using Unity.Netcode; using TD.Core; namespace TD.Gameplay { /// /// One granted builder spell in a . Replicated as part of /// a ; the entry's index in that list IS the hotkey slot /// (grant order == key order), so no separate slot index field is needed. /// /// /// One struct, not parallel lists. is set once at grant time; /// is rewritten on every cast. NetworkList{T}'s /// indexer setter (spells[i] = newValue) short-circuits the write when /// Equals on the old value returns true (see for the /// documented case that first surfaced this). Comparing only would mean /// a cooldown update — same Kind, new CooldownEndServerTime — never actually replicates. /// therefore compares every field. /// [Serializable] public struct SpellSlot : INetworkSerializable, IEquatable { /// Which spell occupies this slot. Fixed for the slot's lifetime. public BuilderSpellKind Kind; /// /// NetworkManager.ServerTime.Time at which this slot is next ready to cast. /// 0 (or any value <= current server time) means ready now. /// public double CooldownEndServerTime; public static SpellSlot CreateReady(BuilderSpellKind kind) { return new SpellSlot { Kind = kind, CooldownEndServerTime = 0d }; } // ----- INetworkSerializable --------------------------------------- public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { byte kindByte = (byte)Kind; serializer.SerializeValue(ref kindByte); Kind = (BuilderSpellKind)kindByte; serializer.SerializeValue(ref CooldownEndServerTime); } // ----- IEquatable ------------------------------------------------- // // Full-field comparison — see remarks above. Without this, cooldown writes // (Kind unchanged, only CooldownEndServerTime updated) would be silently dropped // by NetworkList's indexer setter. public bool Equals(SpellSlot other) => Kind == other.Kind && CooldownEndServerTime == other.CooldownEndServerTime; public override bool Equals(object obj) => obj is SpellSlot other && Equals(other); public override int GetHashCode() => (int)Kind; } }