356 lines
9.1 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|