894 lines
26 KiB
C#
894 lines
26 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Harmony hooks for Phase 2 deferred libraries (items, spells, decisions, subspecies).</summary>
|
|
[HarmonyPatch]
|
|
public static class DeferredEventPatches
|
|
{
|
|
private static bool _spellCastInFlight;
|
|
private static string _spellCastId;
|
|
|
|
private static string _lastPowerId = "";
|
|
private static float _lastPowerAt = -999f;
|
|
|
|
[HarmonyPatch(typeof(ItemManager), nameof(ItemManager.generateItem))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixGenerateItem(object __result, object[] __args)
|
|
{
|
|
if (!AgentHarness.LiveFeedsAllowed)
|
|
{
|
|
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.LiveFeedsAllowed || __result == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor owner = ReadActorMember(__result, "owner", "actor", "creator", "getOwner");
|
|
if (owner == null || !owner.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitItem(ReadAssetId(__result) ?? "item", owner);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(CombatActionLibrary), nameof(CombatActionLibrary.tryToCastSpell))]
|
|
[HarmonyPrefix]
|
|
public static void PrefixTryToCastSpell()
|
|
{
|
|
_spellCastInFlight = true;
|
|
_spellCastId = null;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.getRandomSpell))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixGetRandomSpell(object __result)
|
|
{
|
|
if (!_spellCastInFlight || __result == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = ReadAssetId(__result);
|
|
if (!string.IsNullOrEmpty(id))
|
|
{
|
|
_spellCastId = id;
|
|
}
|
|
}
|
|
|
|
[HarmonyPatch(typeof(CombatActionLibrary), nameof(CombatActionLibrary.tryToCastSpell))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixTryToCastSpell(AttackData pData, bool __result)
|
|
{
|
|
_spellCastInFlight = false;
|
|
if (!__result || !AgentHarness.LiveFeedsAllowed)
|
|
{
|
|
_spellCastId = null;
|
|
return;
|
|
}
|
|
|
|
Actor actor = null;
|
|
try
|
|
{
|
|
BaseSimObject initiator = pData.initiator;
|
|
if (initiator != null)
|
|
{
|
|
actor = initiator.a;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
actor = null;
|
|
}
|
|
|
|
if (actor == null || !actor.isAlive())
|
|
{
|
|
try
|
|
{
|
|
object initiator = typeof(AttackData).GetField("initiator")?.GetValue(pData);
|
|
actor = ReadActorMember(initiator, "a", "actor")
|
|
?? (initiator as Actor);
|
|
}
|
|
catch
|
|
{
|
|
actor = null;
|
|
}
|
|
}
|
|
|
|
if (actor == null || !actor.isAlive())
|
|
{
|
|
_spellCastId = null;
|
|
return;
|
|
}
|
|
|
|
string spellId = _spellCastId;
|
|
_spellCastId = null;
|
|
if (string.IsNullOrEmpty(spellId))
|
|
{
|
|
spellId = ReadSpellFromAttackData(pData) ?? "spell";
|
|
}
|
|
|
|
DeferredInterestFeed.EmitSpell(spellId, actor);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.clickedFinal))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixClickedFinal(Vector2Int pPos, GodPower pPower)
|
|
{
|
|
if (!AgentHarness.LiveFeedsAllowed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string powerId = null;
|
|
if (pPower != null)
|
|
{
|
|
powerId = ReadAssetId(pPower);
|
|
if (string.IsNullOrEmpty(powerId))
|
|
{
|
|
try
|
|
{
|
|
powerId = pPower.name;
|
|
}
|
|
catch
|
|
{
|
|
powerId = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(powerId))
|
|
{
|
|
try
|
|
{
|
|
object selected = World.world?.selected_power;
|
|
powerId = ReadAssetId(selected);
|
|
if (string.IsNullOrEmpty(powerId))
|
|
{
|
|
powerId = World.world?.getSelectedPowerID();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
powerId = null;
|
|
}
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(powerId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
float now = Time.unscaledTime;
|
|
if (string.Equals(powerId, _lastPowerId, StringComparison.OrdinalIgnoreCase)
|
|
&& now - _lastPowerAt < 1.5f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_lastPowerId = powerId;
|
|
_lastPowerAt = now;
|
|
|
|
Vector3 pos = TilePos(pPos);
|
|
Actor near = pos != Vector3.zero ? EventFeedUtil.NearestUnit(pos, 48f) : null;
|
|
DeferredInterestFeed.EmitPower(powerId, pos, near);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(WorldLaws), nameof(WorldLaws.enable))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixWorldLawEnable(string pID)
|
|
{
|
|
if (!AgentHarness.LiveFeedsAllowed || string.IsNullOrEmpty(pID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitWorldLaw(pID, CameraOrMapPos());
|
|
}
|
|
|
|
[HarmonyPatch(typeof(WorldLawAsset), nameof(WorldLawAsset.toggle))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixWorldLawToggle(WorldLawAsset __instance, bool pState)
|
|
{
|
|
if (!pState || !AgentHarness.LiveFeedsAllowed || __instance == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = ReadAssetId(__instance) ?? __instance.id;
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitWorldLaw(id, CameraOrMapPos());
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Subspecies), nameof(Subspecies.mutateFrom))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixMutateFrom(Subspecies __instance)
|
|
{
|
|
if (!AgentHarness.LiveFeedsAllowed || __instance == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor anchor = FindUnitForSubspecies(__instance);
|
|
if (anchor == null || !anchor.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Prefer the mutation event id so ambient gene catalog entries do not suppress the emit.
|
|
string geneId = "mutation";
|
|
string nucleusGene = ReadNucleusGeneId(__instance);
|
|
if (!string.IsNullOrEmpty(nucleusGene)
|
|
&& (nucleusGene.IndexOf("warfare", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| nucleusGene.IndexOf("magic", StringComparison.OrdinalIgnoreCase) >= 0
|
|
|| nucleusGene.IndexOf("mutation", StringComparison.OrdinalIgnoreCase) >= 0))
|
|
{
|
|
geneId = nucleusGene;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitGene(geneId, anchor);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Chromosome), nameof(Chromosome.addGene))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixAddGene(Chromosome __instance, GeneAsset pGeneAsset)
|
|
{
|
|
if (!AgentHarness.LiveFeedsAllowed || pGeneAsset == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string geneId = ReadAssetId(pGeneAsset);
|
|
if (string.IsNullOrEmpty(geneId)
|
|
|| geneId.Equals("empty", StringComparison.OrdinalIgnoreCase)
|
|
|| geneId.IndexOf("void", StringComparison.OrdinalIgnoreCase) >= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor anchor = FindUnitNearChromosome(__instance);
|
|
if (anchor == null || !anchor.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitGene(geneId, anchor);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Subspecies), nameof(Subspecies.addTrait), new Type[] { typeof(SubspeciesTrait), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixSubspeciesAddTrait(Subspecies __instance, SubspeciesTrait pTrait, bool __result)
|
|
{
|
|
if (!__result || !AgentHarness.LiveFeedsAllowed || __instance == null || pTrait == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor anchor = FindUnitForSubspecies(__instance);
|
|
if (anchor == null || !anchor.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
string traitId = ReadAssetId(pTrait) ?? pTrait.id;
|
|
if (string.IsNullOrEmpty(traitId))
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitSubspeciesTrait(traitId, anchor);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MetaObjectWithTraits<CultureData, CultureTrait>), "addTrait", new Type[] { typeof(string), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixCultureAddTrait(Culture __instance, string __0, bool __result)
|
|
{
|
|
EmitMetaTraitGain(__result, __instance, __0, "culture_trait", DeferredInterestFeed.EmitCultureTrait);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MetaObjectWithTraits<ReligionData, ReligionTrait>), "addTrait", new Type[] { typeof(string), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixReligionAddTrait(Religion __instance, string __0, bool __result)
|
|
{
|
|
EmitMetaTraitGain(__result, __instance, __0, "religion_trait", DeferredInterestFeed.EmitReligionTrait);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MetaObjectWithTraits<ClanData, ClanTrait>), "addTrait", new Type[] { typeof(string), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixClanAddTrait(Clan __instance, string __0, bool __result)
|
|
{
|
|
EmitMetaTraitGain(__result, __instance, __0, "clan_trait", DeferredInterestFeed.EmitClanTrait);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(MetaObjectWithTraits<LanguageData, LanguageTrait>), "addTrait", new Type[] { typeof(string), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixLanguageAddTrait(Language __instance, string __0, bool __result)
|
|
{
|
|
EmitMetaTraitGain(__result, __instance, __0, "language_trait", DeferredInterestFeed.EmitLanguageTrait);
|
|
}
|
|
|
|
private static void EmitMetaTraitGain(
|
|
bool added,
|
|
object meta,
|
|
string traitId,
|
|
string domain,
|
|
Action<string, Actor> emit)
|
|
{
|
|
if (!added || !AgentHarness.LiveFeedsAllowed || string.IsNullOrEmpty(traitId) || emit == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Actor anchor = FindUnitForMeta(meta);
|
|
if (anchor == null || !anchor.isAlive())
|
|
{
|
|
return;
|
|
}
|
|
|
|
emit(traitId, anchor);
|
|
_ = domain;
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.setDecisionCooldown))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixSetDecisionCooldown(Actor __instance, object[] __args)
|
|
{
|
|
if (__instance == null || !__instance.isAlive() || !AgentHarness.LiveFeedsAllowed)
|
|
{
|
|
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;
|
|
}
|
|
|
|
// High-frequency hook: only kingdom-affecting decisions, never idle AI ticks.
|
|
if (!EventCatalog.Decision.IsCameraWorthy(id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
DeferredInterestFeed.EmitDecision(id, __instance);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.generatePhenotypeAndShade))]
|
|
[HarmonyPostfix]
|
|
public static void PostfixPhenotype(Actor __instance)
|
|
{
|
|
if (__instance == null || !__instance.isAlive() || !AgentHarness.LiveFeedsAllowed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = ResolvePhenotypeId(__instance);
|
|
if (string.IsNullOrEmpty(id)
|
|
|| string.Equals(id, "phenotype", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return;
|
|
}
|
|
|
|
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.LiveFeedsAllowed || 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 ReadSpellFromAttackData(AttackData pData)
|
|
{
|
|
try
|
|
{
|
|
Type type = typeof(AttackData);
|
|
foreach (string name in new[] { "spell", "spell_asset", "pSpell", "current_spell", "last_spell" })
|
|
{
|
|
object value = type.GetField(name)?.GetValue(pData)
|
|
?? type.GetProperty(name)?.GetValue(pData, null);
|
|
string id = ReadAssetId(value);
|
|
if (!string.IsNullOrEmpty(id))
|
|
{
|
|
return id;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static string ReadNucleusGeneId(Subspecies subspecies)
|
|
{
|
|
try
|
|
{
|
|
object nucleus = subspecies.nucleus;
|
|
if (nucleus == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object chromosomes = nucleus.GetType().GetField("chromosomes")?.GetValue(nucleus)
|
|
?? nucleus.GetType().GetProperty("chromosomes")?.GetValue(nucleus, null);
|
|
if (chromosomes is not System.Collections.IEnumerable list)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (object chrome in list)
|
|
{
|
|
if (chrome == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
object genes = chrome.GetType().GetField("genes")?.GetValue(chrome)
|
|
?? chrome.GetType().GetProperty("genes")?.GetValue(chrome, null);
|
|
if (genes is not System.Collections.IEnumerable geneList)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (object gene in geneList)
|
|
{
|
|
string id = ReadAssetId(gene);
|
|
if (!string.IsNullOrEmpty(id)
|
|
&& !id.Equals("empty", StringComparison.OrdinalIgnoreCase)
|
|
&& !id.Contains("void"))
|
|
{
|
|
return id;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Vector3 TilePos(Vector2Int pPos)
|
|
{
|
|
try
|
|
{
|
|
WorldTile tile = World.world?.GetTile(pPos.x, pPos.y)
|
|
?? World.world?.GetTileSimple(pPos.x, pPos.y);
|
|
if (tile != null)
|
|
{
|
|
object posObj = tile.GetType().GetField("posV3")?.GetValue(tile)
|
|
?? tile.GetType().GetProperty("posV3")?.GetValue(tile, null)
|
|
?? tile.GetType().GetField("pos")?.GetValue(tile)
|
|
?? tile.GetType().GetProperty("pos")?.GetValue(tile, null)
|
|
?? tile.GetType().GetField("position")?.GetValue(tile)
|
|
?? tile.GetType().GetProperty("position")?.GetValue(tile, null);
|
|
if (posObj is Vector3 v)
|
|
{
|
|
return v;
|
|
}
|
|
|
|
if (posObj is Vector2 v2)
|
|
{
|
|
return new Vector3(v2.x, v2.y, 0f);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// fall through
|
|
}
|
|
|
|
return new Vector3(pPos.x, pPos.y, 0f);
|
|
}
|
|
|
|
private static Vector3 CameraOrMapPos()
|
|
{
|
|
try
|
|
{
|
|
if (MoveCamera.instance != null)
|
|
{
|
|
Vector3 p = MoveCamera.instance.transform.position;
|
|
if (p != Vector3.zero)
|
|
{
|
|
return new Vector3(p.x, p.y, 0f);
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
try
|
|
{
|
|
if (Camera.main != null)
|
|
{
|
|
Vector3 p = Camera.main.transform.position;
|
|
return new Vector3(p.x, p.y, 0f);
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return Vector3.zero;
|
|
}
|
|
|
|
private static string ResolvePhenotypeId(Actor actor)
|
|
{
|
|
try
|
|
{
|
|
object data = actor.data;
|
|
if (data == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
object ph = data.GetType().GetField("phenotype")?.GetValue(data)
|
|
?? data.GetType().GetProperty("phenotype")?.GetValue(data, null);
|
|
if (ph != null)
|
|
{
|
|
string raw = ph.ToString();
|
|
if (!string.IsNullOrEmpty(raw)
|
|
&& !int.TryParse(raw, out _)
|
|
&& !string.Equals(raw, "0", StringComparison.Ordinal))
|
|
{
|
|
return raw;
|
|
}
|
|
}
|
|
|
|
int index = 0;
|
|
object idxObj = data.GetType().GetField("phenotype_index")?.GetValue(data)
|
|
?? data.GetType().GetProperty("phenotype_index")?.GetValue(data, null);
|
|
if (idxObj is int i)
|
|
{
|
|
index = i;
|
|
}
|
|
else if (idxObj != null)
|
|
{
|
|
int.TryParse(idxObj.ToString(), out index);
|
|
}
|
|
|
|
if (index <= 0 || AssetManager.phenotype_library?.list == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
foreach (PhenotypeAsset asset in AssetManager.phenotype_library.list)
|
|
{
|
|
if (asset != null && asset.phenotype_index == index)
|
|
{
|
|
return asset.id;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static Actor FindUnitNearChromosome(Chromosome chromosome)
|
|
{
|
|
if (chromosome == null || World.world?.units == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
foreach (Actor unit in World.world.units)
|
|
{
|
|
if (unit == null || !unit.isAlive() || unit.subspecies?.nucleus?.chromosomes == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
List<Chromosome> list = unit.subspecies.nucleus.chromosomes;
|
|
for (int i = 0; i < list.Count; i++)
|
|
{
|
|
if (ReferenceEquals(list[i], chromosome))
|
|
{
|
|
return unit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : null;
|
|
}
|
|
|
|
private static Actor FindUnitForMeta(object meta)
|
|
{
|
|
if (meta == null || World.world?.units == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
try
|
|
{
|
|
foreach (Actor unit in World.world.units)
|
|
{
|
|
if (unit == null || !unit.isAlive())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (ReferenceEquals(unit.culture, meta)
|
|
|| ReferenceEquals(unit.religion, meta)
|
|
|| ReferenceEquals(unit.clan, meta)
|
|
|| ReferenceEquals(unit.language, meta))
|
|
{
|
|
return unit;
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
return MoveCamera.hasFocusUnit() ? MoveCamera._focus_unit : 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 data = obj.GetType().GetField("data")?.GetValue(obj)
|
|
?? obj.GetType().GetProperty("data")?.GetValue(obj, null);
|
|
if (data != null)
|
|
{
|
|
object assetId = data.GetType().GetField("asset_id")?.GetValue(data)
|
|
?? data.GetType().GetProperty("asset_id")?.GetValue(data, null);
|
|
if (assetId != null && !string.IsNullOrEmpty(assetId.ToString()))
|
|
{
|
|
return assetId.ToString();
|
|
}
|
|
}
|
|
|
|
object direct = obj.GetType().GetField("id")?.GetValue(obj)
|
|
?? obj.GetType().GetProperty("id")?.GetValue(obj, null);
|
|
// Item/instance numeric ids are not catalog asset ids.
|
|
if (direct is string ds && !string.IsNullOrEmpty(ds))
|
|
{
|
|
return ds;
|
|
}
|
|
|
|
if (direct != null && !(direct is long) && !(direct is int) && !(direct is ulong))
|
|
{
|
|
return direct.ToString();
|
|
}
|
|
|
|
return null;
|
|
}
|
|
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;
|
|
}
|
|
}
|