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;
}
}