168 lines
4.2 KiB
C#
168 lines
4.2 KiB
C#
using UnityEngine;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Evaluates whether an interest scene is still "live" for protection.</summary>
|
|
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;
|
|
}
|
|
}
|
|
}
|