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

447 lines
12 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
namespace IdleSpectator;
/// <summary>
/// Polls live WarManager state and registers ongoing war interest candidates.
/// Active wars use <see cref="LiveEnsemble.TryBuildWar"/> + sticky WarFront tips.
/// </summary>
public static class WarInterestFeed
{
public static void EmitFromWarObject(object war, string phase = "active")
{
if (war == null || !AgentHarness.LiveFeedsAllowed)
{
return;
}
TryRegisterWar(war, phase);
}
public static void Tick()
{
if (!AgentHarness.LiveFeedsAllowed)
{
return;
}
object warsManager = ResolveWarsManager();
if (warsManager == null)
{
return;
}
IEnumerable active = InvokeEnumerable(warsManager, "getActiveWars")
?? InvokeEnumerable(warsManager, "getWars");
if (active == null)
{
return;
}
int registered = 0;
foreach (object war in active)
{
if (war == null || registered >= 6)
{
break;
}
if (TryRegisterWar(war, "active"))
{
registered++;
}
}
}
private static bool TryRegisterWar(object war, string phase)
{
string warTypeId = ReadWarTypeId(war);
WarTypeInterestEntry entry = EventCatalog.WarType.GetOrFallback(warTypeId);
if (entry == null || !EventCatalog.IsCameraWorthy(entry.CreatesInterest))
{
InterestDropLog.Record("createsInterest=false", "war:" + (warTypeId ?? ""));
return false;
}
string phaseKey = string.IsNullOrEmpty(phase) ? "active" : phase;
LiveEnsemble ensemble = null;
if (war is War typedWar && phaseKey == "active")
{
LiveEnsemble.TryBuildWar(typedWar, out ensemble);
}
object attacker = ReadPropOrField(war, "main_attacker") ?? Invoke(war, "get_main_attacker");
object defender = ReadPropOrField(war, "main_defender") ?? Invoke(war, "get_main_defender");
string attackerName = ensemble?.SideA != null
? (ensemble.SideA.Display ?? "")
: ReadMetaName(attacker);
string defenderName = ensemble?.SideB != null
? (ensemble.SideB.Display ?? "")
: ReadMetaName(defender);
string labelPair = string.IsNullOrEmpty(defenderName)
? attackerName
: attackerName + " vs " + defenderName;
if (string.IsNullOrEmpty(labelPair))
{
labelPair = ReadString(war, "name") ?? warTypeId ?? "war";
}
Vector3 position = Vector3.zero;
Actor follow = ensemble?.Focus;
Actor related = ensemble?.Related;
if (follow == null || !follow.isAlive())
{
if (!TryLocateWar(attacker, defender, out position, out follow))
{
return false;
}
}
else
{
try
{
position = follow.current_position;
}
catch
{
position = Vector3.zero;
}
}
string warId = ReadString(war, "id");
if (string.IsNullOrEmpty(warId) && war is War wId)
{
try
{
warId = wId.getID().ToString();
}
catch
{
warId = "";
}
}
string key = "war:" + phaseKey + ":" + (warId ?? labelPair) + ":" + (warTypeId ?? "normal");
string label;
InterestCompletionKind completion = InterestCompletionKind.FixedDwell;
float minWatch = 6f;
float maxWatch = 30f;
string assetId = warTypeId ?? "war";
if (ensemble != null && phaseKey == "active")
{
label = EventReason.WarFront(ensemble);
if (string.IsNullOrEmpty(label))
{
label = EventReason.Ensemble(ensemble);
}
completion = InterestCompletionKind.WarFront;
assetId = "live_war";
minWatch = 8f;
maxWatch = 45f;
}
else if (follow != null)
{
if (phaseKey == "new")
{
label = EventFeedUtil.SafeName(follow) + " begins a war";
if (!string.IsNullOrEmpty(defenderName))
{
label = EventFeedUtil.SafeName(follow) + " begins a war against " + defenderName;
}
}
else if (phaseKey == "end")
{
label = EventFeedUtil.SafeName(follow) + " ends a war";
}
else
{
label = string.IsNullOrEmpty(defenderName)
? EventFeedUtil.SafeName(follow) + " is at war"
: EventFeedUtil.SafeName(follow) + " is at war with " + defenderName;
}
}
else
{
label = EventCatalog.WarType.MakeLabel(entry, labelPair);
if (phaseKey == "new")
{
label = "War begins: " + labelPair;
}
else if (phaseKey == "end")
{
label = "War ends: " + labelPair;
}
else if (!string.IsNullOrEmpty(labelPair) && !label.Contains(labelPair))
{
label = label + ": " + labelPair;
}
}
float strength = entry.EventStrength;
if (phaseKey == "end")
{
strength = Mathf.Max(60f, strength - 10f);
}
if (ensemble != null)
{
strength = Mathf.Max(strength, 88f);
}
InterestCandidate registered = EventFeedUtil.Register(
key,
"war",
entry.Category,
label,
assetId,
strength,
position,
follow,
related: related,
minWatch: minWatch,
maxWatch: maxWatch,
completion: completion);
if (registered == null)
{
return false;
}
if (ensemble != null)
{
registered.CorrelationKey = key;
StickyScoreboard.TryLockFromEnsemble(registered.Sticky, ensemble);
StickyScoreboard.Refresh(
registered,
ensemble,
position,
StickyScoreboard.WarTheaterRadius);
registered.Label = EventReason.WarFront(ensemble);
if (string.IsNullOrEmpty(registered.Label))
{
registered.Label = label;
}
registered.ParticipantCount = ensemble.ParticipantCount;
registered.AssetId = "live_war";
InterestScoring.ScoreCheap(registered);
}
return true;
}
private static bool TryLocateWar(object attacker, object defender, out Vector3 position, out Actor follow)
{
position = Vector3.zero;
follow = null;
if (TryKingdomAnchor(attacker, out position, out follow))
{
return true;
}
if (TryKingdomAnchor(defender, out position, out follow))
{
return true;
}
return false;
}
private static bool TryKingdomAnchor(object kingdom, out Vector3 position, out Actor follow)
{
position = Vector3.zero;
follow = null;
if (kingdom == null)
{
return false;
}
if (kingdom is Kingdom typed)
{
follow = LiveEnsemble.PreferKingdomLeader(typed);
if (follow != null)
{
try
{
position = follow.current_position;
}
catch
{
// keep
}
return true;
}
}
try
{
object capital = ReadPropOrField(kingdom, "capital") ?? Invoke(kingdom, "getCapital");
if (capital != null)
{
object posObj = ReadPropOrField(capital, "current_position")
?? ReadPropOrField(capital, "city_center");
if (posObj is Vector3 v && v != Vector3.zero)
{
position = v;
}
else if (posObj is Vector2 v2)
{
position = new Vector3(v2.x, v2.y, 0f);
}
}
object king = ReadPropOrField(kingdom, "king");
if (king is Actor actorKing && actorKing.isAlive())
{
follow = actorKing;
position = actorKing.current_position;
return true;
}
if (position != Vector3.zero)
{
return true;
}
}
catch
{
// ignore
}
return false;
}
private static object ResolveWarsManager()
{
try
{
object world = World.world;
if (world == null)
{
return null;
}
object wars = ReadPropOrField(world, "wars");
if (wars != null)
{
return wars;
}
return typeof(AssetManager).Assembly.GetType("WarManager");
}
catch
{
return null;
}
}
private static string ReadWarTypeId(object war)
{
try
{
object warType = ReadPropOrField(war, "war_type") ?? Invoke(war, "get_war_type");
if (warType == null)
{
return "normal";
}
if (warType is string s)
{
return s;
}
string id = ReadString(warType, "id");
return string.IsNullOrEmpty(id) ? "normal" : id;
}
catch
{
return "normal";
}
}
private static string ReadMetaName(object meta)
{
if (meta == null)
{
return "";
}
try
{
object name = Invoke(meta, "getName") ?? ReadPropOrField(meta, "name") ?? ReadString(meta, "name");
return name as string ?? "";
}
catch
{
return "";
}
}
private static IEnumerable InvokeEnumerable(object target, string methodName)
{
try
{
MethodInfo method = target.GetType().GetMethod(methodName, Type.EmptyTypes);
object result = method?.Invoke(target, null);
return result as IEnumerable;
}
catch
{
return null;
}
}
private static object Invoke(object target, string methodName)
{
if (target == null || string.IsNullOrEmpty(methodName))
{
return null;
}
try
{
MethodInfo method = target.GetType().GetMethod(methodName, Type.EmptyTypes);
return method?.Invoke(target, null);
}
catch
{
return null;
}
}
private static object ReadPropOrField(object target, string name)
{
if (target == null || string.IsNullOrEmpty(name))
{
return null;
}
try
{
Type type = target.GetType();
PropertyInfo prop = type.GetProperty(name);
if (prop != null)
{
return prop.GetValue(target, null);
}
FieldInfo field = type.GetField(name);
return field?.GetValue(target);
}
catch
{
return null;
}
}
private static string ReadString(object target, string name)
{
return ReadPropOrField(target, name) as string;
}
}