310 lines
8.8 KiB
C#
310 lines
8.8 KiB
C#
using System;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Harmony hooks for Phase 2 deferred libraries (items, spells, decisions, subspecies).</summary>
|
|
[HarmonyPatch]
|
|
public static class DeferredEventPatches
|
|
{
|
|
[HarmonyPatch(typeof(ItemManager), nameof(ItemManager.generateItem))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixGenerateItem(object __result, object[] __args)
|
|
{
|
|
if (AgentHarness.Busy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor craftsman = null;
|
|
if (__args != null)
|
|
{
|
|
for (int i = 0; i < __args.Length; i++)
|
|
{
|
|
if (__args[i] is Actor a && a.isAlive())
|
|
{
|
|
craftsman = a;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
string itemId = ReadAssetId(__result) ?? "item";
|
|
if (craftsman == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitItem(itemId, craftsman);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(ItemManager), "newItem")]
|
|
[HarmonyPostfix]
|
|
public static void PostfixNewItem(object __result)
|
|
{
|
|
// newItem may lack an actor; skip without a subject rather than invent one.
|
|
if (AgentHarness.Busy || __result == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor owner = ReadActorMember(__result, "owner", "actor", "creator", "getOwner");
|
|
if (owner == null || !owner.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitItem(ReadAssetId(__result) ?? "item", owner);
|
|
}
|
|
|
|
// Spells: live catalog + domain_feed; cast site varies by game build (no stable Actor.tryToCastSpell).
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.setDecisionCooldown))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixSetDecisionCooldown(Actor __instance, object[] __args)
|
|
{
|
|
if (__instance == null || !__instance.isAlive() || AgentHarness.Busy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
object pDecision = __args != null && __args.Length > 0 ? __args[0] : null;
|
|
if (pDecision == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = ReadAssetId(pDecision) ?? pDecision.ToString();
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
DiscreteEventEntry entry = DecisionInterestCatalog.GetOrFallback(id);
|
|
// Only emit Signal decisions from this high-frequency hook; Ambient would flood.
|
|
if (entry.OwnsCamera != InterestOwnership.Signal)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitDecision(id, __instance);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.generatePhenotypeAndShade))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixPhenotype(Actor __instance)
|
|
{
|
|
if (__instance == null || !__instance.isAlive() || AgentHarness.Busy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = "phenotype";
|
|
try
|
|
{
|
|
object data = __instance.data;
|
|
object ph = data?.GetType().GetField("phenotype")?.GetValue(data)
|
|
?? data?.GetType().GetProperty("phenotype")?.GetValue(data, null);
|
|
if (ph != null)
|
|
{
|
|
id = ph.ToString();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
DeferredInterestFeed.EmitPhenotype(id, __instance);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(SubspeciesManager), "addRandomTraitFromBiomeToSubspecies")]
|
|
[HarmonyPostfix]
|
|
public static void PostfixSubspeciesTraitRandom(object[] __args)
|
|
{
|
|
object pSubspecies = __args != null && __args.Length > 0 ? __args[0] : null;
|
|
EmitSubspecies(pSubspecies, "biome_trait");
|
|
}
|
|
|
|
[HarmonyPatch(typeof(SubspeciesManager), "addTraitsFromBiomeToSubspecies")]
|
|
[HarmonyPostfix]
|
|
public static void PostfixSubspeciesTraits(object[] __args)
|
|
{
|
|
object pSubspecies = __args != null && __args.Length > 0 ? __args[0] : null;
|
|
EmitSubspecies(pSubspecies, "biome_traits");
|
|
}
|
|
|
|
private static void EmitSubspecies(object subspecies, string fallbackId)
|
|
{
|
|
if (AgentHarness.Busy || subspecies == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor anchor = ReadActorMember(subspecies, "founder", "creator", "leader", "getFounder");
|
|
if (anchor == null || !anchor.isAlive())
|
|
{
|
|
// Prefer a unit of this subspecies if possible.
|
|
anchor = FindUnitForSubspecies(subspecies);
|
|
}
|
|
|
|
if (anchor == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string traitId = fallbackId;
|
|
try
|
|
{
|
|
object last = subspecies.GetType().GetField("last_trait")?.GetValue(subspecies)
|
|
?? subspecies.GetType().GetProperty("last_trait")?.GetValue(subspecies, null);
|
|
if (last != null)
|
|
{
|
|
traitId = ReadAssetId(last) ?? last.ToString() ?? fallbackId;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
DeferredInterestFeed.EmitSubspeciesTrait(traitId, anchor);
|
|
}
|
|
|
|
private static Actor FindUnitForSubspecies(object subspecies)
|
|
{
|
|
if (subspecies == null || World.world?.units == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
string sid = null;
|
|
try
|
|
{
|
|
object id = subspecies.GetType().GetMethod("getID")?.Invoke(subspecies, null)
|
|
?? subspecies.GetType().GetProperty("id")?.GetValue(subspecies, null);
|
|
sid = id?.ToString();
|
|
}
|
|
catch
|
|
{
|
|
sid = null;
|
|
}
|
|
|
|
try
|
|
{
|
|
foreach (Actor unit in World.world.units)
|
|
{
|
|
if (unit == null || !unit.isAlive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
try
|
|
{
|
|
object sub = unit.GetType().GetProperty("subspecies")?.GetValue(unit, null)
|
|
?? unit.GetType().GetField("subspecies")?.GetValue(unit);
|
|
if (sub == subspecies)
|
|
{
|
|
return unit;
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(sid))
|
|
{
|
|
object uid = sub?.GetType().GetMethod("getID")?.Invoke(sub, null);
|
|
if (uid != null && uid.ToString() == sid)
|
|
{
|
|
return unit;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// skip
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ReadAssetId(object obj)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (obj is string s)
|
|
{
|
|
return s;
|
|
}
|
|
|
|
object asset = obj.GetType().GetField("asset")?.GetValue(obj)
|
|
?? obj.GetType().GetProperty("asset")?.GetValue(obj, null)
|
|
?? obj.GetType().GetMethod("getAsset")?.Invoke(obj, null);
|
|
if (asset != null)
|
|
{
|
|
object id = asset.GetType().GetField("id")?.GetValue(asset)
|
|
?? asset.GetType().GetProperty("id")?.GetValue(asset, null);
|
|
if (id != null)
|
|
{
|
|
return id.ToString();
|
|
}
|
|
}
|
|
|
|
object direct = obj.GetType().GetField("id")?.GetValue(obj)
|
|
?? obj.GetType().GetProperty("id")?.GetValue(obj, null);
|
|
return direct?.ToString();
|
|
}
|
|
catch
|
|
{
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private static Actor ReadActorMember(object target, params string[] names)
|
|
{
|
|
if (target == null || names == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
Type type = target.GetType();
|
|
foreach (string name in names)
|
|
{
|
|
try
|
|
{
|
|
var prop = type.GetProperty(name);
|
|
if (prop != null && typeof(Actor).IsAssignableFrom(prop.PropertyType))
|
|
{
|
|
return prop.GetValue(target, null) as Actor;
|
|
}
|
|
|
|
var field = type.GetField(name);
|
|
if (field != null && typeof(Actor).IsAssignableFrom(field.FieldType))
|
|
{
|
|
return field.GetValue(target) as Actor;
|
|
}
|
|
|
|
var method = type.GetMethod(name, Type.EmptyTypes);
|
|
if (method != null && typeof(Actor).IsAssignableFrom(method.ReturnType))
|
|
{
|
|
return method.Invoke(target, null) as Actor;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// try next
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|