74 lines
4.3 KiB
C#
74 lines
4.3 KiB
C#
using Unity.Entities;
|
|
|
|
namespace ProjectM.Simulation
|
|
{
|
|
/// <summary>
|
|
/// SERVER-ONLY expiry tracker paired with a <see cref="StatModifier"/> by <see cref="SourceId"/>. It is NOT a
|
|
/// [GhostField] and lives in a SEPARATE buffer so the replicated <see cref="StatModifier"/> layout stays
|
|
/// byte-identical — adding ANY member (even non-ghost) to a [GhostField] buffer element regenerates its
|
|
/// serializer/stride/hash = an effective ghost re-bake. To grant a TIMED buff, append both a StatModifier and a
|
|
/// TimedModifier sharing one unique SourceId; <c>TimedModifierExpirySystem</c> removes the matching StatModifier
|
|
/// when <see cref="UntilTick"/> elapses, and that removal replicates for free via the StatModifier [GhostField]
|
|
/// buffer (OwnerSendType.All), so StatRecomputeSystem reverts the effective stat on both worlds with no change.
|
|
/// </summary>
|
|
public struct TimedModifier : IBufferElementData
|
|
{
|
|
/// <summary>Matches the <see cref="StatModifier.SourceId"/> this row governs.</summary>
|
|
public uint SourceId;
|
|
|
|
/// <summary>Server tick at which the paired StatModifier expires (0 = no expiry / inert; schedule via TickUtil.NonZero).</summary>
|
|
public uint UntilTick;
|
|
}
|
|
|
|
/// <summary>Pure helpers for removing modifiers by provenance (clear-by-type / timed expiry). Deterministic, no RNG/wall-clock.</summary>
|
|
public static class TimedModifierUtil
|
|
{
|
|
/// <summary>Remove every <see cref="StatModifier"/> row whose SourceId matches (RemoveAtSwapBack). Returns the count removed.</summary>
|
|
public static int RemoveBySourceId(DynamicBuffer<StatModifier> mods, uint sourceId)
|
|
{
|
|
int removed = 0;
|
|
for (int j = mods.Length - 1; j >= 0; j--)
|
|
if (mods[j].SourceId == sourceId) { mods.RemoveAtSwapBack(j); removed++; }
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>Remove every <see cref="StatModifier"/> row whose SourceId lies in [lo, hiExclusive) — the
|
|
/// run-scoped BOON strip (per-pick distinct ids share the band; one call clears the whole run's boons while
|
|
/// class/meta/equip bands survive untouched). Idempotent; RemoveAtSwapBack. Returns the count removed.</summary>
|
|
public static int RemoveBySourceIdRange(DynamicBuffer<StatModifier> mods, uint lo, uint hiExclusive)
|
|
{
|
|
int removed = 0;
|
|
for (int j = mods.Length - 1; j >= 0; j--)
|
|
if (mods[j].SourceId >= lo && mods[j].SourceId < hiExclusive) { mods.RemoveAtSwapBack(j); removed++; }
|
|
return removed;
|
|
}
|
|
|
|
/// <summary>Phase 1.7: guarantee EXACTLY ONE row per <paramref name="sourceId"/> in BOTH the replicated
|
|
/// <see cref="StatModifier"/> buffer and this server-only <see cref="TimedModifier"/> buffer (remove-then-add) so a
|
|
/// timed buff REFRESHES (re-stamps <paramref name="untilTick"/>) rather than stacking on a repeat grant. Used by
|
|
/// <c>KillRewardSystem</c> for Frenzy so successive kills extend the surge instead of compounding the modifier.</summary>
|
|
public static void Upsert(DynamicBuffer<StatModifier> mods, DynamicBuffer<TimedModifier> timed,
|
|
uint sourceId, byte target, byte op, float value, uint untilTick)
|
|
{
|
|
RemoveBySourceId(mods, sourceId);
|
|
for (int j = timed.Length - 1; j >= 0; j--)
|
|
if (timed[j].SourceId == sourceId) timed.RemoveAtSwapBack(j);
|
|
mods.Add(new StatModifier { Target = target, Op = op, Value = value, SourceId = sourceId });
|
|
timed.Add(new TimedModifier { SourceId = sourceId, UntilTick = untilTick });
|
|
}
|
|
|
|
/// <summary>Phase 1.7: remove every server-only <see cref="TimedModifier"/> row matching <paramref name="sourceId"/>
|
|
/// (the paired <see cref="StatModifier"/> is cleared separately — e.g. the Returning boon-band range-strip). This
|
|
/// closes the cross-run gap where a stale Frenzy timed row could outlive its StatModifier. Returns the count removed.</summary>
|
|
public static int RemoveBySourceId(DynamicBuffer<TimedModifier> timed, uint sourceId)
|
|
{
|
|
int removed = 0;
|
|
for (int j = timed.Length - 1; j >= 0; j--)
|
|
if (timed[j].SourceId == sourceId) { timed.RemoveAtSwapBack(j); removed++; }
|
|
return removed;
|
|
}
|
|
|
|
|
|
}
|
|
}
|