using UnityEngine; namespace IdleSpectator; /// Whether an interest candidate may steal the idle camera. public enum InterestOwnership { /// May cut in / own EventLed camera focus. Signal, /// Tracked for fill/enrichment; fill-capped so it does not preempt Signal drama. Ambient } /// Shared registration helpers for domain event feeds. public static class EventFeedUtil { public static InterestCandidate Register( string key, string source, string category, string label, string assetId, float eventStrength, InterestOwnership ownership, Vector3 position, Actor follow, float minWatch = 4f, float maxWatch = 22f, InterestCompletionKind completion = InterestCompletionKind.FixedDwell) { if (string.IsNullOrEmpty(key) || position == Vector3.zero && follow == null) { return null; } if (follow == null && position == Vector3.zero) { return null; } Vector3 pos = follow != null ? follow.current_position : position; if (pos == Vector3.zero) { return null; } bool ambient = ownership == InterestOwnership.Ambient; float strength = eventStrength; if (ambient) { strength = Mathf.Min(strength, InterestScoringConfig.W.fillScoreMax - 1f); if (strength < 8f) { strength = 8f; } } var candidate = new InterestCandidate { Key = key, LeadKind = ambient ? InterestLeadKind.CharacterLed : InterestLeadKind.EventLed, Category = string.IsNullOrEmpty(category) ? "Event" : category, Source = source ?? "event", EventStrength = strength, CharacterSignificance = ambient ? strength * 0.5f : 0f, VisualConfidence = follow != null ? 0.7f : 0.35f, Position = pos, FollowUnit = follow, SubjectId = follow != null ? SafeId(follow) : 0, Label = label ?? assetId ?? key, AssetId = assetId ?? "", SpeciesId = follow?.asset != null ? follow.asset.id : "", CreatedAt = Time.unscaledTime, LastSeenAt = Time.unscaledTime, ExpiresAt = Time.unscaledTime + (ambient ? 18f : 35f), MinWatch = minWatch, MaxWatch = maxWatch, Completion = completion }; InterestScoring.ScoreCheap(candidate); return InterestRegistry.Upsert(candidate); } public static long SafeId(Actor actor) { if (actor == null) { return 0; } try { return actor.getID(); } catch { return 0; } } public static string SafeName(Actor actor) { if (actor == null) { return ""; } try { return actor.getName() ?? ""; } catch { return ""; } } public static Actor NearestUnit(Vector3 near, float radius = 200f) { try { return WorldActivityScanner.FindNearestAliveUnit(near, radius); } catch { return null; } } public static Actor AnyAliveUnit() { try { if (World.world?.units == null) { return null; } foreach (Actor actor in World.world.units) { if (actor != null && actor.isAlive()) { return actor; } } } catch { // ignore } return null; } public static bool TryResolveAnchor(Actor preferred, Vector3 position, out Actor follow, out Vector3 pos) { follow = preferred; pos = position; if (follow != null && follow.isAlive()) { pos = follow.current_position; return pos != Vector3.zero || true; } if (position != Vector3.zero) { follow = NearestUnit(position) ?? AnyAliveUnit(); pos = position; return true; } follow = AnyAliveUnit(); if (follow == null) { pos = Vector3.zero; return false; } pos = follow.current_position; return true; } }