worldbox-observer-mod/IdleSpectator/InterestCandidate.cs
2026-07-16 23:10:54 -05:00

203 lines
7.1 KiB
C#

using System.Collections.Generic;
using UnityEngine;
namespace IdleSpectator;
public enum InterestLeadKind
{
EventLed = 0,
CharacterLed = 1
}
public enum InterestCompletionKind
{
/// <summary>One-shot narrative dwell (WorldLog, harness tips).</summary>
FixedDwell = 0,
/// <summary>Combat / hunt / fire while participants stay live.</summary>
CombatActive = 1,
/// <summary>Owning AI task / beat still live.</summary>
ActivityActive = 2,
/// <summary>Grief / family emotion with survivor + optional crying.</summary>
HappinessGrief = 3,
/// <summary>Status phase still present on subject.</summary>
StatusPhase = 4,
/// <summary>Character vignette until max watch or cold activity.</summary>
CharacterVignette = 5,
/// <summary>Harness-forced session; active until released or max cap.</summary>
Manual = 6
}
/// <summary>
/// Durable interest scene candidate. <see cref="TotalScore"/> is the sole ranking signal;
/// typed fields (lead, completion, combat) gate interrupt policy.
/// </summary>
public sealed class InterestCandidate
{
public string Key = "";
public InterestLeadKind LeadKind = InterestLeadKind.EventLed;
public string Category = "";
public string Source = "";
public float EventStrength;
public float CharacterSignificance;
public float Novelty = 1f;
public float VisualConfidence = 0.5f;
public float TotalScore;
/// <summary>Live fighters / participants in a cluster (battles, multi-unit events).</summary>
public int ParticipantCount;
/// <summary>Peak fighters seen this combat scene (scale demotion hysteresis).</summary>
public int CombatPeakParticipants;
/// <summary>Sticky opposing camps for the orange tip (species/kingdom keys).</summary>
public EnsembleFrame CombatSideFrame = EnsembleFrame.CountOnly;
public string CombatSideAKey = "";
public string CombatSideADisplay = "";
public string CombatSideAKingdom = "";
public int CombatSideACount;
public string CombatSideBKey = "";
public string CombatSideBDisplay = "";
public string CombatSideBKingdom = "";
public int CombatSideBCount;
/// <summary>Tracked camp members for sticky scoreboard (kept until combat grace ends).</summary>
public readonly List<long> CombatSideAIds = new List<long>(8);
public readonly List<long> CombatSideBIds = new List<long>(8);
/// <summary>Notable (king/favorite/leader/high renown) participants in the cluster.</summary>
public int NotableParticipantCount;
public Vector3 Position;
public Actor FollowUnit;
public Actor RelatedUnit;
public long SubjectId;
public long RelatedId;
public string Label = "";
public string AssetId = "";
public string SpeciesId = "";
public string Verb = "";
public string CityKey = "";
public string KingdomKey = "";
public string RegionKey = "";
public string HappinessEffectId = "";
public string StatusId = "";
public string CorrelationKey = "";
public float CreatedAt;
public float LastSeenAt;
public float ExpiresAt;
public float MinWatch = 1.5f;
public float MaxWatch = 45f;
public InterestCompletionKind Completion = InterestCompletionKind.FixedDwell;
public bool Resumable = true;
public bool ForceActive;
public bool Selected;
public string ScoreDetail = "";
public readonly List<long> ParticipantIds = new List<long>(4);
public bool HasFollowUnit => FollowUnit != null && FollowUnit.isAlive();
public bool HasValidPosition =>
HasFollowUnit || (Position != Vector3.zero && !float.IsNaN(Position.x));
public bool HasStickyCombatSides =>
!string.IsNullOrEmpty(CombatSideAKey)
&& !string.IsNullOrEmpty(CombatSideBKey)
&& (CombatSideFrame == EnsembleFrame.SpeciesVsSpecies
|| CombatSideFrame == EnsembleFrame.KingdomVsKingdom);
public void ClearCombatSticky()
{
CombatPeakParticipants = 0;
CombatSideFrame = EnsembleFrame.CountOnly;
CombatSideAKey = "";
CombatSideADisplay = "";
CombatSideAKingdom = "";
CombatSideACount = 0;
CombatSideBKey = "";
CombatSideBDisplay = "";
CombatSideBKingdom = "";
CombatSideBCount = 0;
CombatSideAIds.Clear();
CombatSideBIds.Clear();
}
public InterestEvent ToInterestEvent()
{
return new InterestEvent
{
Score = TotalScore,
Position = Position,
FollowUnit = FollowUnit,
RelatedUnit = RelatedUnit,
Label = Label,
CreatedAt = CreatedAt,
AssetId = AssetId,
ParticipantCount = ParticipantCount,
NotableParticipantCount = NotableParticipantCount,
CharacterSignificance = CharacterSignificance
};
}
public InterestCandidate CloneShallow()
{
var copy = new InterestCandidate
{
Key = Key,
LeadKind = LeadKind,
Category = Category,
Source = Source,
EventStrength = EventStrength,
CharacterSignificance = CharacterSignificance,
Novelty = Novelty,
VisualConfidence = VisualConfidence,
TotalScore = TotalScore,
ParticipantCount = ParticipantCount,
CombatPeakParticipants = CombatPeakParticipants,
CombatSideFrame = CombatSideFrame,
CombatSideAKey = CombatSideAKey,
CombatSideADisplay = CombatSideADisplay,
CombatSideAKingdom = CombatSideAKingdom,
CombatSideACount = CombatSideACount,
CombatSideBKey = CombatSideBKey,
CombatSideBDisplay = CombatSideBDisplay,
CombatSideBKingdom = CombatSideBKingdom,
CombatSideBCount = CombatSideBCount,
NotableParticipantCount = NotableParticipantCount,
Position = Position,
FollowUnit = FollowUnit,
RelatedUnit = RelatedUnit,
SubjectId = SubjectId,
RelatedId = RelatedId,
Label = Label,
AssetId = AssetId,
SpeciesId = SpeciesId,
Verb = Verb,
CityKey = CityKey,
KingdomKey = KingdomKey,
RegionKey = RegionKey,
HappinessEffectId = HappinessEffectId,
StatusId = StatusId,
CorrelationKey = CorrelationKey,
CreatedAt = CreatedAt,
LastSeenAt = LastSeenAt,
ExpiresAt = ExpiresAt,
MinWatch = MinWatch,
MaxWatch = MaxWatch,
Completion = Completion,
Resumable = Resumable,
ForceActive = ForceActive,
Selected = Selected,
ScoreDetail = ScoreDetail
};
for (int i = 0; i < ParticipantIds.Count; i++)
{
copy.ParticipantIds.Add(ParticipantIds[i]);
}
for (int i = 0; i < CombatSideAIds.Count; i++)
{
copy.CombatSideAIds.Add(CombatSideAIds[i]);
}
for (int i = 0; i < CombatSideBIds.Count; i++)
{
copy.CombatSideBIds.Add(CombatSideBIds[i]);
}
return copy;
}
}