worldbox-observer-mod/IdleSpectator/Events/Feeds/FamilyInterestFeed.cs
2026-07-17 01:54:59 -05:00

172 lines
4.8 KiB
C#

using UnityEngine;
namespace IdleSpectator;
/// <summary>
/// Soft FamilyPack sticky tips. Cold-starts only from real relationship mutations
/// (<see cref="EmitFromActor"/>); <see cref="Tick"/> only refreshes the current pack scene.
/// Join/leave/form one-shot prose stays on <see cref="RelationshipInterestFeed"/>.
/// </summary>
public static class FamilyInterestFeed
{
/// <summary>Warm-social band - below notice milestones so unit events can cut in.</summary>
private const float PackEventStrength = 44f;
public static void Tick()
{
if (!AgentHarness.LiveFeedsAllowed)
{
return;
}
// Do not poll every living family - that hogged the camera.
// Only keep the active pack scene fresh while FamilyPack owns the shot.
InterestCandidate current = InterestDirector.CurrentCandidate;
if (current == null || current.Completion != InterestCompletionKind.FamilyPack)
{
return;
}
Actor follow = current.FollowUnit;
if (follow == null || !follow.isAlive())
{
return;
}
try
{
if (follow.hasFamily() && follow.family != null)
{
TryRegisterFamily(follow.family, softRefresh: true);
}
}
catch
{
// ignore
}
}
/// <summary>
/// After a join/form beat, optionally open a soft sticky pack scene for the living family.
/// </summary>
public static void EmitFromActor(Actor subject)
{
if (!AgentHarness.LiveFeedsAllowed || subject == null || !subject.isAlive())
{
return;
}
try
{
if (!subject.hasFamily() || subject.family == null)
{
return;
}
TryRegisterFamily(subject.family, softRefresh: false);
}
catch
{
// ignore
}
}
private static bool TryRegisterFamily(Family family, bool softRefresh)
{
if (!LiveEnsemble.TryBuildFamily(family, out LiveEnsemble ensemble) || ensemble == null)
{
return false;
}
// Solo families are not a pack scene - leave to FixedDwell phase tips.
if (ensemble.SideA == null || ensemble.SideA.Count < 2)
{
return false;
}
Actor follow = ensemble.Focus;
if (follow == null || !follow.isAlive())
{
return false;
}
DiscreteEventEntry entry = EventCatalog.Relationship.GetOrFallback("family_group_join");
if (!EventCatalog.IsCameraWorthy(entry))
{
InterestDropLog.Record("createsInterest=false", "family_pack");
return false;
}
string familyId = "";
try
{
familyId = family.getID().ToString();
}
catch
{
familyId = "";
}
string key = "family:pack:" + (familyId ?? EventFeedUtil.SafeId(follow).ToString());
string label = EventReason.FamilyPack(ensemble);
if (string.IsNullOrEmpty(label))
{
return false;
}
Vector3 position = Vector3.zero;
try
{
position = follow.current_position;
}
catch
{
position = Vector3.zero;
}
// Soft cluster: short hold, warm-social strength (never combat-class 82).
float strength = PackEventStrength;
if (entry.EventStrength > 0f && entry.EventStrength < PackEventStrength)
{
strength = Mathf.Clamp(entry.EventStrength, 28f, PackEventStrength);
}
InterestCandidate registered = EventFeedUtil.Register(
key,
"relationship",
entry.Category,
label,
"live_family",
strength,
position,
follow,
related: ensemble.Related,
minWatch: 4f,
maxWatch: 12f,
completion: InterestCompletionKind.FamilyPack);
if (registered == null)
{
return false;
}
registered.CorrelationKey = key;
StickyScoreboard.TryLockFromEnsemble(registered.Sticky, ensemble);
StickyScoreboard.Refresh(
registered,
ensemble,
position,
StickyScoreboard.FamilyTheaterRadius);
registered.Label = EventReason.FamilyPack(ensemble);
registered.ParticipantCount = ensemble.ParticipantCount;
registered.AssetId = "live_family";
// Pack size must not inflate TotalScore via combat mass bonuses.
registered.NotableParticipantCount = 0;
InterestScoring.ScoreCheap(registered);
if (!softRefresh)
{
InterestDropLog.Record("family_pack_open", key);
}
return true;
}
}