worldbox-observer-mod/IdleSpectator/Events/Feeds/WarInterestFeed.cs
2026-07-16 00:40:53 -05:00

356 lines
9.1 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.
/// </summary>
public static class WarInterestFeed
{
public static void EmitFromWarObject(object war, string phase = "active")
{
if (war == null || AgentHarness.Busy)
{
return;
}
TryRegisterWar(war, phase);
}
public static void Tick()
{
if (AgentHarness.Busy)
{
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.CreatesInterest)
{
return false;
}
object attacker = ReadPropOrField(war, "main_attacker") ?? Invoke(war, "get_main_attacker");
object defender = ReadPropOrField(war, "main_defender") ?? Invoke(war, "get_main_defender");
string attackerName = ReadMetaName(attacker);
string defenderName = 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 = null;
if (!TryLocateWar(attacker, defender, out position, out follow))
{
return false;
}
string phaseKey = string.IsNullOrEmpty(phase) ? "active" : phase;
string key = "war:" + phaseKey + ":" + (ReadString(war, "id") ?? labelPair) + ":" + (warTypeId ?? "normal");
string 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;
}
// Prefer sentence form when we have an owned follow unit.
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;
}
}
float strength = entry.EventStrength;
if (phaseKey == "end")
{
strength = Mathf.Max(60f, strength - 10f);
}
EventFeedUtil.Register(
key,
"war",
entry.Category,
label,
warTypeId ?? "war",
strength,
position,
follow,
minWatch: 6f,
maxWatch: 30f);
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;
}
// No kingdom unit found - location-only if we have a capital position is handled above.
// Never invent a global random unit as war subject.
return false;
}
private static bool TryKingdomAnchor(object kingdom, out Vector3 position, out Actor follow)
{
position = Vector3.zero;
follow = null;
if (kingdom == null)
{
return false;
}
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;
}
private static long SafeActorId(Actor actor)
{
if (actor == null)
{
return 0;
}
try
{
return actor.getID();
}
catch
{
return 0;
}
}
}