worldbox-observer-mod/IdleSpectator/ActorRelation.cs
2026-07-15 22:16:24 -05:00

368 lines
8.3 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
namespace IdleSpectator;
/// <summary>
/// Live-game relation resolver. Labels that claim a second party must resolve through here.
/// </summary>
public static class ActorRelation
{
public static Actor GetLover(Actor actor)
{
if (actor == null)
{
return null;
}
try
{
if (!actor.hasLover())
{
return null;
}
}
catch
{
// fall through to field/property
}
try
{
Actor lover = null;
try
{
lover = actor.lover;
}
catch
{
lover = null;
}
if (lover == null)
{
lover = ReadActorMember(actor, "lover", "get_lover", "getLover");
}
if (lover != null && lover.isAlive() && lover != actor)
{
return lover;
}
}
catch
{
// ignore
}
return null;
}
public static bool TryGetLover(Actor actor, out Actor lover)
{
lover = GetLover(actor);
return lover != null;
}
public static Actor GetBestFriend(Actor actor)
{
if (actor == null)
{
return null;
}
try
{
long id = 0;
object data = actor.data;
if (data != null)
{
object raw = data.GetType().GetProperty("best_friend_id")?.GetValue(data, null)
?? data.GetType().GetField("best_friend_id")?.GetValue(data);
if (raw is long l)
{
id = l;
}
else if (raw is int i)
{
id = i;
}
}
if (id == 0)
{
return null;
}
return FindUnitById(id);
}
catch
{
return null;
}
}
/// <summary>First living peer in the same family (not self).</summary>
public static Actor FirstFamilyPeer(Actor actor)
{
foreach (Actor peer in EnumerateFamilyMembers(actor, max: 8))
{
if (peer != null && peer != actor && peer.isAlive())
{
return peer;
}
}
return null;
}
public static IEnumerable<Actor> EnumerateFamilyMembers(Actor actor, int max = 12)
{
if (actor == null || max <= 0)
{
yield break;
}
object family = ReadMember(actor, "family", "get_family", "getFamily");
if (family == null)
{
yield break;
}
int yielded = 0;
foreach (Actor member in EnumerateActorsFromMeta(family))
{
if (member == null || !member.isAlive())
{
continue;
}
yield return member;
yielded++;
if (yielded >= max)
{
yield break;
}
}
}
public static IEnumerable<Actor> EnumerateChildren(Actor actor, int max = 12)
{
if (actor == null || max <= 0)
{
yield break;
}
IEnumerable children = null;
try
{
MethodInfo getChildren = typeof(Actor).GetMethod("getChildren", Type.EmptyTypes);
if (getChildren != null)
{
children = getChildren.Invoke(actor, null) as IEnumerable;
}
}
catch
{
children = null;
}
if (children == null)
{
yield break;
}
int yielded = 0;
foreach (object raw in children)
{
Actor child = raw as Actor ?? ResolveActor(raw);
if (child == null || !child.isAlive())
{
continue;
}
yield return child;
yielded++;
if (yielded >= max)
{
yield break;
}
}
}
/// <summary>
/// Resolve a related unit for pack/family labels. Prefers lover, then family peer, then child.
/// </summary>
public static Actor ResolvePackRelated(Actor subject)
{
if (subject == null)
{
return null;
}
Actor lover = GetLover(subject);
if (lover != null)
{
return lover;
}
Actor peer = FirstFamilyPeer(subject);
if (peer != null)
{
return peer;
}
foreach (Actor child in EnumerateChildren(subject, max: 1))
{
return child;
}
return null;
}
public static Actor ResolveActor(object raw)
{
if (raw == null)
{
return null;
}
if (raw is Actor actor)
{
return actor;
}
try
{
if (raw is BaseSimObject sim && sim.isActor())
{
return sim.a;
}
}
catch
{
// fall through
}
return ReadActorMember(raw, "actor", "Actor", "a", "getActor", "GetActor");
}
private static IEnumerable<Actor> EnumerateActorsFromMeta(object meta)
{
if (meta == null)
{
yield break;
}
// Common: units / members / getUnits / getSimpleList
object list = ReadMember(meta, "units", "members", "getUnits", "getSimpleList", "getList");
if (list is IEnumerable enumerable)
{
foreach (object raw in enumerable)
{
Actor a = ResolveActor(raw);
if (a != null)
{
yield return a;
}
}
yield break;
}
Actor founder = ReadActorMember(meta, "founder", "creator", "leader", "getFounder");
if (founder != null)
{
yield return founder;
}
}
private static Actor FindUnitById(long id)
{
if (id == 0 || World.world?.units == null)
{
return null;
}
try
{
foreach (Actor unit in World.world.units)
{
if (unit == null)
{
continue;
}
try
{
if (unit.getID() == id && unit.isAlive())
{
return unit;
}
}
catch
{
// skip
}
}
}
catch
{
// ignore
}
return null;
}
private static Actor ReadActorMember(object target, params string[] names)
{
object raw = ReadMember(target, names);
return raw as Actor;
}
private static object ReadMember(object target, params string[] names)
{
if (target == null || names == null)
{
return null;
}
Type type = target as Type ?? target.GetType();
object instance = target is Type ? null : target;
foreach (string name in names)
{
if (string.IsNullOrEmpty(name))
{
continue;
}
try
{
PropertyInfo prop = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (prop != null)
{
return prop.GetValue(instance, null);
}
FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
if (field != null)
{
return field.GetValue(instance);
}
MethodInfo method = type.GetMethod(name, Type.EmptyTypes);
if (method != null)
{
return method.Invoke(instance, null);
}
}
catch
{
// try next
}
}
return null;
}
}