336 lines
8.5 KiB
C#
336 lines
8.5 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 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))
|
|
{
|
|
registered++;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static bool TryRegisterWar(object war)
|
|
{
|
|
string warTypeId = ReadWarTypeId(war);
|
|
WarTypeInterestEntry entry = WarTypeInterestCatalog.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 key = "war:" + (ReadString(war, "id") ?? labelPair) + ":" + (warTypeId ?? "normal");
|
|
string label = WarTypeInterestCatalog.MakeLabel(entry, labelPair);
|
|
var candidate = new InterestCandidate
|
|
{
|
|
Key = key,
|
|
LeadKind = InterestLeadKind.EventLed,
|
|
Category = entry.Category,
|
|
Source = "war",
|
|
EventStrength = entry.EventStrength,
|
|
VisualConfidence = follow != null ? 0.65f : 0.35f,
|
|
Position = position,
|
|
FollowUnit = follow,
|
|
SubjectId = follow != null ? SafeActorId(follow) : 0,
|
|
Label = label,
|
|
AssetId = warTypeId ?? "war",
|
|
KingdomKey = attackerName,
|
|
CreatedAt = Time.unscaledTime,
|
|
LastSeenAt = Time.unscaledTime,
|
|
ExpiresAt = Time.unscaledTime + 25f,
|
|
MinWatch = 6f,
|
|
MaxWatch = 30f,
|
|
Completion = InterestCompletionKind.FixedDwell
|
|
};
|
|
InterestScoring.ScoreCheap(candidate);
|
|
InterestRegistry.Upsert(candidate);
|
|
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;
|
|
}
|
|
|
|
// Fall back to any living unit.
|
|
try
|
|
{
|
|
if (World.world?.units != null)
|
|
{
|
|
foreach (Actor actor in World.world.units)
|
|
{
|
|
if (actor == null || !actor.isAlive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
follow = actor;
|
|
position = actor.current_position;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|