using System; using UnityEngine; namespace IdleSpectator; /// /// Kind-agnostic sticky scoreboard: lock sides, enroll members, refresh alive counts, /// hold elevated scale, promote follow on subject death. /// Combat, WarFront, PlotCell, FamilyPack, and StatusOutbreak are consumers. /// public static class StickyScoreboard { public const float ScaleHoldSeconds = 2.5f; /// Thin camp must reach this alive count to leave mop-up pack framing. public const int WipeRecoverMin = 2; public const float WarTheaterRadius = 48f; public const float PlotTheaterRadius = 36f; public const float FamilyTheaterRadius = 28f; public const float StatusOutbreakRadius = 32f; public static bool HasOpposingSides(InterestCandidate scene) => scene?.Sticky != null && scene.Sticky.HasOpposingSides; public static bool IsOnRoster(InterestCandidate scene, Actor actor) { if (scene?.Sticky == null || actor == null || !actor.isAlive()) { return false; } long id = EventFeedUtil.SafeId(actor); if (id == 0) { return false; } LiveSceneStickyState sticky = scene.Sticky; for (int i = 0; i < sticky.SideAIds.Count; i++) { if (sticky.SideAIds[i] == id) { return true; } } for (int i = 0; i < sticky.SideBIds.Count; i++) { if (sticky.SideBIds[i] == id) { return true; } } return false; } /// /// Promote another living roster member onto the candidate when follow is lost. /// public static bool TryPromoteFollow(InterestCandidate scene) { if (scene?.Sticky == null || !scene.Sticky.HasOpposingSides) { return false; } LiveSceneStickyState sticky = scene.Sticky; // Combat + war fronts: prefer units still in the scrap over chore bystanders. bool preferCombat = sticky.Kind == EnsembleKind.Combat || sticky.Kind == EnsembleKind.WarFront; bool plotLike = sticky.Kind == EnsembleKind.PlotCell; bool familyLike = sticky.Kind == EnsembleKind.FamilyPack; bool outbreakLike = sticky.Kind == EnsembleKind.StatusOutbreak; bool singleSide = familyLike || outbreakLike; Actor pickA = LiveEnsemble.FindBestAliveTracked(sticky.SideAIds, preferCombat: preferCombat); Actor pickB = singleSide ? null : LiveEnsemble.FindBestAliveTracked(sticky.SideBIds, preferCombat: preferCombat); if (pickA == null && sticky.Frame == EnsembleFrame.KingdomVsKingdom && !LiveEnsemble.IsPlotterSideKey(sticky.SideAKey) && !LiveEnsemble.IsFamilyPackSideKey(sticky.SideAKey) && !LiveEnsemble.IsStatusOutbreakSideKey(sticky.SideAKey)) { pickA = LiveEnsemble.PreferKingdomLeader(LiveEnsemble.FindKingdomByKey(sticky.SideAKey)); } if (pickB == null && !singleSide && sticky.Frame == EnsembleFrame.KingdomVsKingdom) { pickB = LiveEnsemble.PreferKingdomLeader(LiveEnsemble.FindKingdomByKey(sticky.SideBKey)); } // Family / plot / outbreak prefer Side A. Actor pick = (plotLike || singleSide) ? (pickA ?? pickB) : (pickA ?? pickB); if (pick == null || !pick.isAlive()) { return false; } if (!plotLike && !singleSide && pickA != null && pickB != null) { if (sticky.SideBCount > sticky.SideACount) { pick = pickB; } else if (sticky.SideACount > sticky.SideBCount) { pick = pickA; } } Actor other = singleSide ? null : (pick == pickA ? pickB : pickA); if (singleSide) { for (int i = 0; i < sticky.SideAIds.Count; i++) { Actor peer = LiveEnsemble.FindTrackedActor(sticky.SideAIds[i]); if (peer != null && peer.isAlive() && peer != pick) { other = peer; break; } } } scene.FollowUnit = pick; scene.SubjectId = EventFeedUtil.SafeId(pick); scene.RelatedUnit = other; scene.RelatedId = other != null ? EventFeedUtil.SafeId(other) : 0; try { scene.Position = pick.current_position; } catch { // keep } return true; } /// /// Hold presented tip tier across brief Skirmish↔Battle↔Mass flaps (up and down). /// Peak still tracks max fighters for headcount / Mass band recovery. /// public static void StabilizeScale(LiveSceneStickyState sticky, LiveEnsemble ensemble, float now) { if (sticky == null || ensemble == null) { return; } int n = Mathf.Max(1, ensemble.ParticipantCount); if (n > sticky.PeakParticipants) { sticky.PeakParticipants = n; } UpdateMopUp(sticky, now); EnsembleScale live = LiveEnsemble.ScaleForCount(n); // Opposing camp wiped / mop-up: drop Peak Mass hold so tip tier matches living pack. bool wipedOpposing = sticky.HasOpposingSides && sticky.TotalCount > 0 && (sticky.MopUpActive || sticky.SideACount <= 0 || sticky.SideBCount <= 0); if (wipedOpposing) { sticky.HasPresentedScale = true; sticky.PresentedScale = live; sticky.ScaleChangeSince = -999f; sticky.ScaleDropSince = -999f; ensemble.Scale = live; if (ensemble.Frame == EnsembleFrame.NamedPair && live > EnsembleScale.Pair) { ensemble.Frame = EnsembleFrame.CountOnly; } return; } if (!sticky.HasPresentedScale) { sticky.HasPresentedScale = true; sticky.PresentedScale = live; sticky.ScaleChangeSince = -999f; sticky.ScaleDropSince = -999f; ensemble.Scale = live; if (ensemble.Frame == EnsembleFrame.NamedPair && live > EnsembleScale.Pair) { ensemble.Frame = EnsembleFrame.CountOnly; } return; } if (live == sticky.PresentedScale) { sticky.ScaleChangeSince = -999f; sticky.ScaleDropSince = -999f; ensemble.Scale = sticky.PresentedScale; return; } if (sticky.ScaleChangeSince < 0f) { sticky.ScaleChangeSince = now; } if (now - sticky.ScaleChangeSince < ScaleHoldSeconds) { ensemble.Scale = sticky.PresentedScale; if (ensemble.Frame == EnsembleFrame.NamedPair && sticky.PresentedScale > EnsembleScale.Pair) { ensemble.Frame = EnsembleFrame.CountOnly; } return; } sticky.PresentedScale = live; sticky.ScaleChangeSince = -999f; sticky.ScaleDropSince = -999f; ensemble.Scale = live; if (ensemble.Frame == EnsembleFrame.NamedPair && live > EnsembleScale.Pair) { ensemble.Frame = EnsembleFrame.CountOnly; } } /// /// Lock opposing camps once (when multi), enroll members, rewrite ensemble sides/counts. /// public static void Refresh( InterestCandidate scene, LiveEnsemble ensemble, Vector3 pos, float radius) { if (scene?.Sticky == null || ensemble == null) { return; } LiveSceneStickyState sticky = scene.Sticky; TryLockFromEnsemble(sticky, ensemble); if (!sticky.HasOpposingSides) { return; } SeedFromEnsemble(sticky, ensemble); bool warLike = sticky.Kind == EnsembleKind.WarFront || ensemble.Kind == EnsembleKind.WarFront; bool plotLike = sticky.Kind == EnsembleKind.PlotCell || ensemble.Kind == EnsembleKind.PlotCell; bool familyLike = sticky.Kind == EnsembleKind.FamilyPack || ensemble.Kind == EnsembleKind.FamilyPack; bool outbreakLike = sticky.Kind == EnsembleKind.StatusOutbreak || ensemble.Kind == EnsembleKind.StatusOutbreak; Actor bestA = null; Actor bestB = null; if (outbreakLike) { SeedOutbreakRoster(sticky, ensemble); string statusId = scene.StatusId ?? ""; if (string.IsNullOrEmpty(statusId) && LiveEnsemble.IsStatusOutbreakSideKey(sticky.SideAKey)) { statusId = sticky.SideAKey.Substring("status:".Length); } // Re-scan nearby carriers into the roster, then recount. Vector2 scanPos = new Vector2(pos.x, pos.y); var carriers = new System.Collections.Generic.List(8); LiveEnsemble.CollectStatusCarriers(scanPos, Math.Max(radius, StatusOutbreakRadius), statusId, carriers); for (int i = 0; i < carriers.Count; i++) { long id = EventFeedUtil.SafeId(carriers[i]); if (id != 0) { AddUnique(sticky.SideAIds, id); } } LiveEnsemble.TryApplyStatusOutbreakStickyCounts(sticky, statusId); bestA = LiveEnsemble.FindBestAliveTracked(sticky.SideAIds, preferCombat: false) ?? ensemble.Focus; bestB = null; for (int i = 0; i < sticky.SideAIds.Count; i++) { Actor peer = LiveEnsemble.FindTrackedActor(sticky.SideAIds[i]); if (peer != null && peer.isAlive() && peer != bestA) { bestB = peer; break; } } } else if (familyLike) { SeedFamilyRoster(sticky, ensemble); LiveEnsemble.TryApplyFamilyStickyCounts(sticky); bestA = LiveEnsemble.FindBestAliveTracked(sticky.SideAIds, preferCombat: false) ?? ensemble.Focus; bestB = null; for (int i = 0; i < sticky.SideAIds.Count; i++) { Actor peer = LiveEnsemble.FindTrackedActor(sticky.SideAIds[i]); if (peer != null && peer.isAlive() && peer != bestA) { bestB = peer; break; } } } else if (plotLike) { SeedPlotRoster(sticky, ensemble); LiveEnsemble.TryApplyPlotStickyCounts(sticky); bestA = LiveEnsemble.FindBestAliveTracked(sticky.SideAIds, preferCombat: false) ?? ensemble.Focus; bestB = LiveEnsemble.FindBestAliveTracked(sticky.SideBIds, preferCombat: false) ?? LiveEnsemble.PreferKingdomLeader(LiveEnsemble.FindKingdomByKey(sticky.SideBKey)) ?? ensemble.Related; } else { float useRadius = warLike ? Math.Max(radius, WarTheaterRadius) : radius; LiveEnsemble.RefreshStickyMemberRoster( pos, useRadius, sticky.Frame, sticky.SideAKey, sticky.SideBKey, sticky.SideAIds, sticky.SideBIds, out int countA, out int countB, out bestA, out bestB, requireCombat: !warLike); sticky.SideACount = Math.Max(0, countA); sticky.SideBCount = Math.Max(0, countB); if (warLike) { // Counts stay kingdom-wide; focus picks prefer frontline fighters. bestA = LiveEnsemble.FindBestAliveTracked(sticky.SideAIds, preferCombat: true) ?? bestA; bestB = LiveEnsemble.FindBestAliveTracked(sticky.SideBIds, preferCombat: true) ?? bestB; LiveEnsemble.TryApplyKingdomPopulationCounts(sticky); } } int live = sticky.TotalCount; scene.ParticipantCount = live; if (live > sticky.PeakParticipants) { sticky.PeakParticipants = live; } ApplyToEnsemble(sticky, ensemble, bestA, bestB); if (ensemble.Scale == EnsembleScale.Pair && sticky.Kind == EnsembleKind.Combat) { ensemble.Scale = LiveEnsemble.ScaleForCount(Math.Max(3, sticky.PeakParticipants)); } if (warLike) { ensemble.Kind = EnsembleKind.WarFront; ensemble.Scale = LiveEnsemble.ScaleForCount(Math.Max(3, live)); } if (plotLike) { ensemble.Kind = EnsembleKind.PlotCell; ensemble.Scale = LiveEnsemble.ScaleForCount(Math.Max(3, live)); } if (familyLike) { ensemble.Kind = EnsembleKind.FamilyPack; ensemble.Scale = LiveEnsemble.ScaleForCount(Math.Max(2, sticky.SideACount)); ensemble.SideB = null; } if (outbreakLike) { ensemble.Kind = EnsembleKind.StatusOutbreak; ensemble.Scale = LiveEnsemble.ScaleForCount(Math.Max(2, sticky.SideACount)); ensemble.SideB = null; } if (bestA != null && (ensemble.Focus == null || ((sticky.Kind == EnsembleKind.Combat || sticky.Kind == EnsembleKind.WarFront) && !LiveEnsemble.IsCombatParticipant(ensemble.Focus)))) { ensemble.Focus = bestA; } if (bestB != null) { ensemble.Related = bestB; } // War / combat: if Focus is still a chore bystander, swap to a live fighter on either side. if ((sticky.Kind == EnsembleKind.Combat || sticky.Kind == EnsembleKind.WarFront) && ensemble.Focus != null && !LiveEnsemble.IsCombatParticipant(ensemble.Focus)) { Actor fighterA = LiveEnsemble.IsCombatParticipant(bestA) ? bestA : null; Actor fighterB = LiveEnsemble.IsCombatParticipant(bestB) ? bestB : null; Actor fighter = fighterA ?? fighterB; if (fighter != null) { ensemble.Related = fighter == bestA ? bestB : bestA; ensemble.Focus = fighter; } } } /// Lock sticky keys from a live opposing collective ensemble (once). public static bool TryLockFromEnsemble(LiveSceneStickyState sticky, LiveEnsemble ensemble) { if (sticky == null || ensemble == null || sticky.HasOpposingSides) { return false; } if (!LiveEnsemble.HasOpposingCollectiveSides(ensemble)) { return false; } int liveN = Math.Max( ensemble.ParticipantCount, (ensemble.SideA?.Count ?? 0) + (ensemble.SideB?.Count ?? 0)); // War / plot / family / outbreak lock even at low local fighter counts. if (ensemble.Kind != EnsembleKind.WarFront && ensemble.Kind != EnsembleKind.PlotCell && ensemble.Kind != EnsembleKind.FamilyPack && ensemble.Kind != EnsembleKind.StatusOutbreak && liveN <= 2 && ensemble.Scale <= EnsembleScale.Pair) { return false; } sticky.Kind = ensemble.Kind; sticky.Frame = ensemble.Frame; sticky.SideAKey = ensemble.SideA.Key ?? ""; sticky.SideADisplay = ensemble.SideA.Display ?? ""; sticky.SideAKingdom = ensemble.SideA.KingdomDisplay ?? ""; sticky.SideACount = Math.Max(0, ensemble.SideA.Count); if (ensemble.Kind == EnsembleKind.FamilyPack || ensemble.Kind == EnsembleKind.StatusOutbreak || ensemble.SideB == null) { sticky.SideBKey = ""; sticky.SideBDisplay = ""; sticky.SideBKingdom = ""; sticky.SideBCount = 0; } else { sticky.SideBKey = ensemble.SideB.Key ?? ""; sticky.SideBDisplay = ensemble.SideB.Display ?? ""; sticky.SideBKingdom = ensemble.SideB.KingdomDisplay ?? ""; sticky.SideBCount = Math.Max(0, ensemble.SideB.Count); } // Lock-time scale is a peak floor - live refresh may dip without erasing the theater. sticky.PeakParticipants = Math.Max(sticky.PeakParticipants, liveN); return true; } /// Build orange reason from sticky state; never thin Fight prose. public static string FormatReason(InterestCandidate scene, Actor focus, Actor related) { if (scene?.Sticky == null || !scene.Sticky.HasOpposingSides) { return ""; } LiveSceneStickyState sticky = scene.Sticky; UpdateMopUp(sticky, Time.unscaledTime); GetPresentationCounts(sticky, out int countA, out int countB); bool wipedOpposing = sticky.HasOpposingSides && sticky.TotalCount > 0 && (countA <= 0 || countB <= 0); int liveN = Math.Max(0, countA) + Math.Max(0, countB); int scaleN = wipedOpposing ? Math.Max(1, liveN) : Math.Max(3, sticky.PeakParticipants); var ensemble = new LiveEnsemble { Kind = sticky.Kind, Scale = LiveEnsemble.ScaleForCount(scaleN), Focus = focus, Related = related, Frame = sticky.Frame, SideA = new EnsembleSide { Key = sticky.SideAKey, Display = sticky.SideADisplay, KingdomDisplay = sticky.SideAKingdom, Count = countA, Best = focus }, SideB = new EnsembleSide { Key = sticky.SideBKey, Display = sticky.SideBDisplay, KingdomDisplay = sticky.SideBKingdom, Count = countB, Best = related }, ParticipantCount = liveN }; return EventReason.Ensemble(ensemble); } /// /// Presentation counts for combat tips: mop-up forces a thin recovering camp to 0 /// until it reaches . /// public static void GetPresentationCounts(LiveSceneStickyState sticky, out int countA, out int countB) { countA = 0; countB = 0; if (sticky == null) { return; } countA = Math.Max(0, sticky.SideACount); countB = Math.Max(0, sticky.SideBCount); if (!sticky.MopUpActive) { return; } if (countA > 0 && countA < WipeRecoverMin) { countA = 0; } if (countB > 0 && countB < WipeRecoverMin) { countB = 0; } } private static void UpdateMopUp(LiveSceneStickyState sticky, float now) { if (sticky == null || !sticky.HasOpposingSides) { return; } int a = Math.Max(0, sticky.SideACount); int b = Math.Max(0, sticky.SideBCount); if (a <= 0 || b <= 0) { if (!sticky.MopUpActive) { sticky.MopUpActive = true; sticky.MopUpSince = now; } return; } if (!sticky.MopUpActive) { return; } // Leave mop-up only when both camps are meaningfully back. if (a >= WipeRecoverMin && b >= WipeRecoverMin) { sticky.MopUpActive = false; sticky.MopUpSince = -999f; } } /// /// True when two sticky scenes share the same unordered kingdom theater /// (WarFront ↔ local Mass on Igguorn vs The Godo). /// Matches asset ids, display names, and KingdomDisplay aliases. /// public static bool SharesKingdomTheater(InterestCandidate a, InterestCandidate b) { if (a?.Sticky == null || b?.Sticky == null) { return false; } if (!TryKingdomAliasPair(a.Sticky, out string[] a1, out string[] a2) || !TryKingdomAliasPair(b.Sticky, out string[] b1, out string[] b2)) { return false; } return (AliasesOverlap(a1, b1) && AliasesOverlap(a2, b2)) || (AliasesOverlap(a1, b2) && AliasesOverlap(a2, b1)); } /// True when sticky kingdom pair matches a stored crisis theater. public static bool SharesKingdomTheater(InterestCandidate c, string theaterA, string theaterB) { if (c?.Sticky == null || string.IsNullOrEmpty(theaterA) || string.IsNullOrEmpty(theaterB) || !TryKingdomAliasPair(c.Sticky, out string[] a1, out string[] a2)) { return false; } string[] tA = KingdomAliases(theaterA); string[] tB = KingdomAliases(theaterB); return (AliasesOverlap(a1, tA) && AliasesOverlap(a2, tB)) || (AliasesOverlap(a1, tB) && AliasesOverlap(a2, tA)); } public static bool TryKingdomPair(LiveSceneStickyState sticky, out string a, out string b) { a = ""; b = ""; if (!TryKingdomAliasPair(sticky, out string[] aAliases, out string[] bAliases)) { return false; } a = aAliases.Length > 0 ? aAliases[0] : ""; b = bAliases.Length > 0 ? bAliases[0] : ""; return !string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b) && !KingdomEq(a, b); } private static bool TryKingdomAliasPair( LiveSceneStickyState sticky, out string[] sideA, out string[] sideB) { sideA = null; sideB = null; if (sticky == null) { return false; } if (sticky.Frame == EnsembleFrame.KingdomVsKingdom) { sideA = KingdomAliases(sticky.SideAKey, sticky.SideAKingdom, sticky.SideADisplay); sideB = KingdomAliases(sticky.SideBKey, sticky.SideBKingdom, sticky.SideBDisplay); } else { sideA = KingdomAliases(sticky.SideAKingdom, sticky.SideAKey, sticky.SideADisplay); sideB = KingdomAliases(sticky.SideBKingdom, sticky.SideBKey, sticky.SideBDisplay); } return sideA.Length > 0 && sideB.Length > 0 && !AliasesOverlap(sideA, sideB); } private static string[] KingdomAliases(params string[] raw) { var list = new System.Collections.Generic.List(6); for (int i = 0; i < raw.Length; i++) { AddKingdomAlias(list, raw[i]); if (!string.IsNullOrEmpty(raw[i])) { AddKingdomAlias(list, LiveEnsemble.KingdomDisplay(raw[i])); } } return list.ToArray(); } private static void AddKingdomAlias(System.Collections.Generic.List list, string key) { string n = NormKingdom(key); if (string.IsNullOrEmpty(n)) { return; } for (int i = 0; i < list.Count; i++) { if (string.Equals(list[i], n, StringComparison.Ordinal)) { return; } } list.Add(n); } private static bool AliasesOverlap(string[] a, string[] b) { if (a == null || b == null || a.Length == 0 || b.Length == 0) { return false; } for (int i = 0; i < a.Length; i++) { for (int j = 0; j < b.Length; j++) { if (KingdomEq(a[i], b[j])) { return true; } } } return false; } private static string NormKingdom(string key) { return string.IsNullOrEmpty(key) ? "" : key.Trim().ToLowerInvariant(); } private static bool KingdomEq(string a, string b) { return !string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b) && string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } private static void ApplyToEnsemble( LiveSceneStickyState sticky, LiveEnsemble ensemble, Actor bestA, Actor bestB) { ensemble.Frame = sticky.Frame; ensemble.SideA = new EnsembleSide { Key = sticky.SideAKey, Display = string.IsNullOrEmpty(sticky.SideADisplay) ? sticky.SideAKey : sticky.SideADisplay, KingdomDisplay = sticky.SideAKingdom, Count = sticky.SideACount, Best = bestA ?? ensemble.Focus }; ensemble.SideB = new EnsembleSide { Key = sticky.SideBKey, Display = string.IsNullOrEmpty(sticky.SideBDisplay) ? sticky.SideBKey : sticky.SideBDisplay, KingdomDisplay = sticky.SideBKingdom, Count = sticky.SideBCount, Best = bestB ?? ensemble.Related }; ensemble.ParticipantCount = sticky.TotalCount; } private static void SeedFromEnsemble(LiveSceneStickyState sticky, LiveEnsemble ensemble) { if (sticky == null || ensemble == null || !sticky.HasOpposingSides) { return; } bool plotLike = sticky.Kind == EnsembleKind.PlotCell || ensemble.Kind == EnsembleKind.PlotCell; bool familyLike = sticky.Kind == EnsembleKind.FamilyPack || ensemble.Kind == EnsembleKind.FamilyPack; bool outbreakLike = sticky.Kind == EnsembleKind.StatusOutbreak || ensemble.Kind == EnsembleKind.StatusOutbreak; void Enroll(Actor actor, bool forcePlotter = false) { if (actor == null || !actor.isAlive()) { return; } long id = EventFeedUtil.SafeId(actor); if (id == 0) { return; } if (familyLike || outbreakLike) { AddUnique(sticky.SideAIds, id); return; } if (plotLike || forcePlotter) { string kingdom = LiveEnsemble.KingdomKeyOf(actor); if (!string.IsNullOrEmpty(kingdom) && kingdom.Equals(sticky.SideBKey, StringComparison.OrdinalIgnoreCase) && !forcePlotter) { AddUnique(sticky.SideBIds, id); return; } // Default: living plot participants enroll on Side A. AddUnique(sticky.SideAIds, id); return; } string key = sticky.Frame == EnsembleFrame.KingdomVsKingdom ? LiveEnsemble.KingdomKeyOf(actor) : LiveEnsemble.SpeciesKeyOf(actor); if (string.IsNullOrEmpty(key)) { return; } if (key.Equals(sticky.SideAKey, StringComparison.OrdinalIgnoreCase)) { AddUnique(sticky.SideAIds, id); } else if (key.Equals(sticky.SideBKey, StringComparison.OrdinalIgnoreCase)) { AddUnique(sticky.SideBIds, id); } } Enroll(ensemble.Focus, forcePlotter: plotLike || familyLike || outbreakLike); Enroll(ensemble.Related); Enroll(ensemble.SideA?.Best, forcePlotter: plotLike || familyLike || outbreakLike); Enroll(ensemble.SideB?.Best); if (ensemble.ParticipantIds == null) { return; } for (int i = 0; i < ensemble.ParticipantIds.Count; i++) { long id = ensemble.ParticipantIds[i]; if (id == 0) { continue; } Actor actor = LiveEnsemble.FindTrackedActor(id); if (actor != null) { // Participant ids from TryBuildPlot are plotters first; related may also appear. bool forceA = plotLike && (actor == ensemble.Focus || actor == ensemble.SideA?.Best || (ensemble.Related == null || actor != ensemble.Related)); Enroll(actor, forcePlotter: forceA && actor != ensemble.Related); } } } private static void SeedPlotRoster(LiveSceneStickyState sticky, LiveEnsemble ensemble) { if (sticky == null || ensemble == null) { return; } // Ensure Side A display stays collective. if (string.IsNullOrEmpty(sticky.SideADisplay) || LiveEnsemble.IsPlotterSideKey(sticky.SideADisplay)) { sticky.SideADisplay = "Plotters"; } if (ensemble.SideA != null && ensemble.SideA.Count > sticky.SideACount) { sticky.SideACount = ensemble.SideA.Count; } if (ensemble.SideB != null && ensemble.SideB.Count > 0) { sticky.SideBCount = ensemble.SideB.Count; } } private static void SeedFamilyRoster(LiveSceneStickyState sticky, LiveEnsemble ensemble) { if (sticky == null || ensemble == null) { return; } if (ensemble.SideA != null) { if (!string.IsNullOrEmpty(ensemble.SideA.Display) && !LiveEnsemble.IsFamilyPackSideKey(ensemble.SideA.Display)) { sticky.SideADisplay = ensemble.SideA.Display; } if (ensemble.SideA.Count > sticky.SideACount) { sticky.SideACount = ensemble.SideA.Count; } } sticky.SideBKey = ""; sticky.SideBDisplay = ""; sticky.SideBCount = 0; } private static void SeedOutbreakRoster(LiveSceneStickyState sticky, LiveEnsemble ensemble) { if (sticky == null || ensemble == null) { return; } if (ensemble.SideA != null) { if (!string.IsNullOrEmpty(ensemble.SideA.Display) && !LiveEnsemble.IsStatusOutbreakSideKey(ensemble.SideA.Display)) { sticky.SideADisplay = ensemble.SideA.Display; } if (ensemble.SideA.Count > sticky.SideACount) { sticky.SideACount = ensemble.SideA.Count; } } sticky.SideBKey = ""; sticky.SideBDisplay = ""; sticky.SideBCount = 0; } private static void AddUnique(System.Collections.Generic.List ids, long id) { for (int i = 0; i < ids.Count; i++) { if (ids[i] == id) { return; } } ids.Add(id); } }