using UnityEngine; namespace IdleSpectator; /// Evaluates whether an interest scene is still "live" for protection. public static class InterestCompletion { public static bool IsActive(InterestCandidate candidate, float sessionStartedAt, float now) { if (candidate == null) { return false; } if (candidate.ForceActive) { return true; } if (!candidate.HasValidPosition) { return false; } float age = now - sessionStartedAt; if (age >= candidate.MaxWatch) { return false; } switch (candidate.Completion) { case InterestCompletionKind.Manual: return age < candidate.MaxWatch; case InterestCompletionKind.FixedDwell: return age < Mathf.Max(candidate.MinWatch, candidate.MaxWatch * 0.35f); case InterestCompletionKind.CombatActive: return CombatStillActive(candidate); case InterestCompletionKind.ActivityActive: return ActivityStillActive(candidate); case InterestCompletionKind.HappinessGrief: return GriefStillActive(candidate); case InterestCompletionKind.StatusPhase: return StatusStillActive(candidate); case InterestCompletionKind.CharacterVignette: return VignetteStillActive(candidate, age); default: return age < candidate.MinWatch; } } private static bool CombatStillActive(InterestCandidate c) { Actor unit = c.FollowUnit; if (unit == null || !unit.isAlive()) { return false; } try { if (unit.has_attack_target) { return true; } if (unit.hasTask() && unit.ai?.task != null && (unit.ai.task.in_combat || unit.ai.task.is_fireman)) { return true; } } catch { // ignore } // Battle asset without a live fighter: keep briefly via position only if battle still hot. if (c.AssetId == "live_battle") { InterestEvent battle = WorldActivityScanner.FindHottestBattle(); return battle != null; } return false; } private static bool ActivityStillActive(InterestCandidate c) { Actor unit = c.FollowUnit; if (unit == null || !unit.isAlive()) { return false; } ActivityBand band = ActivityInterestTable.Classify(unit); return band >= ActivityBand.Warm || unit.has_attack_target; } private static bool GriefStillActive(InterestCandidate c) { Actor survivor = c.FollowUnit; if (survivor == null || !survivor.isAlive()) { return false; } if (!string.IsNullOrEmpty(c.StatusId) && HasStatus(survivor, c.StatusId)) { return true; } // Short grief window after the happiness signal. return Time.unscaledTime - c.LastSeenAt < 8f; } private static bool StatusStillActive(InterestCandidate c) { Actor unit = c.FollowUnit; if (unit == null || !unit.isAlive() || string.IsNullOrEmpty(c.StatusId)) { return false; } return HasStatus(unit, c.StatusId); } private static bool VignetteStillActive(InterestCandidate c, float age) { if (age < c.MinWatch) { return true; } Actor unit = c.FollowUnit; if (unit == null || !unit.isAlive()) { return false; } // Stay while still warm; cold ends after min watch. return !WorldActivityScanner.IsFocusActivityCold(unit); } private static bool HasStatus(Actor actor, string statusId) { if (actor == null || string.IsNullOrEmpty(statusId)) { return false; } try { return actor.hasStatus(statusId); } catch { return false; } } }