205 lines
5.3 KiB
C#
205 lines
5.3 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using HarmonyLib;
|
|
using UnityEngine;
|
|
|
|
namespace IdleSpectator;
|
|
|
|
/// <summary>Actor trait add/remove interest patches from mutation discovery.</summary>
|
|
[HarmonyPatch]
|
|
public static class TraitEventPatches
|
|
{
|
|
/// <summary>
|
|
/// Birth/spawn endowment traits that are still worth a camera beat.
|
|
/// Routine species defaults (poisonous frog, bloodlust beetle, …) are not.
|
|
/// </summary>
|
|
private static readonly HashSet<string> LegendaryBirthTraits = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"immortal",
|
|
"chosen_one",
|
|
"miracle_born",
|
|
"miracle_bearer",
|
|
"scar_of_divinity",
|
|
"giant",
|
|
"whirlwind",
|
|
"death_nuke",
|
|
"death_bomb",
|
|
"bomberman"
|
|
};
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.addTrait), new Type[] { typeof(string), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixAddTraitString(Actor __instance, string pTraitID, bool __result)
|
|
{
|
|
if (!__result || __instance == null || !__instance.isAlive() || string.IsNullOrEmpty(pTraitID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Emit(pTraitID, __instance, gained: true);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.addTrait), new Type[] { typeof(ActorTrait), typeof(bool) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixAddTraitAsset(Actor __instance, ActorTrait pTrait, bool __result)
|
|
{
|
|
if (!__result || __instance == null || !__instance.isAlive() || pTrait == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = "";
|
|
try
|
|
{
|
|
id = pTrait.id ?? "";
|
|
}
|
|
catch
|
|
{
|
|
id = "";
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Emit(id, __instance, gained: true);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.removeTrait), new Type[] { typeof(string) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixRemoveTraitString(Actor __instance, string pTraitID)
|
|
{
|
|
if (__instance == null || !__instance.isAlive() || string.IsNullOrEmpty(pTraitID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Emit(pTraitID, __instance, gained: false);
|
|
}
|
|
|
|
[HarmonyPatch(typeof(Actor), nameof(Actor.removeTrait), new Type[] { typeof(ActorTrait) })]
|
|
[HarmonyPostfix]
|
|
public static void PostfixRemoveTraitAsset(Actor __instance, ActorTrait pTrait, bool __result)
|
|
{
|
|
if (!__result || __instance == null || !__instance.isAlive() || pTrait == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string id = "";
|
|
try
|
|
{
|
|
id = pTrait.id ?? "";
|
|
}
|
|
catch
|
|
{
|
|
id = "";
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
Emit(id, __instance, gained: false);
|
|
}
|
|
|
|
private static void Emit(string traitId, Actor actor, bool gained)
|
|
{
|
|
if (AgentHarness.Busy)
|
|
{
|
|
return;
|
|
}
|
|
|
|
DiscreteEventEntry entry = TraitInterestCatalog.GetOrFallback(traitId);
|
|
if (!entry.CreatesInterest)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Quiet-world Ambient traits must not steal the camera.
|
|
if (entry.EventStrength < InterestScoringConfig.W.noticeScoreMin)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Spawn/birth endowment: only legendary rarities.
|
|
if (gained && IsSpawnTraitWindow(actor) && !LegendaryBirthTraits.Contains(entry.Id))
|
|
{
|
|
return;
|
|
}
|
|
|
|
float strength = entry.EventStrength;
|
|
if (!gained)
|
|
{
|
|
strength = Mathf.Min(strength, 45f);
|
|
// Trait loss is quieter unless it was dramatic.
|
|
if (entry.EventStrength < InterestScoringConfig.W.hotScoreMin)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
string label = EventReason.Trait(actor, entry.Id, gained);
|
|
string phase = gained ? "gains" : "loses";
|
|
string key = "trait:" + entry.Id + ":" + phase + ":" + EventFeedUtil.SafeId(actor);
|
|
EventFeedUtil.Register(
|
|
key,
|
|
"trait",
|
|
entry.Category,
|
|
label,
|
|
entry.Id,
|
|
strength,
|
|
InterestOwnership.Signal,
|
|
actor.current_position,
|
|
actor);
|
|
}
|
|
|
|
/// <summary>True while traits are still being stamped onto a brand-new unit.</summary>
|
|
private static bool IsSpawnTraitWindow(Actor actor)
|
|
{
|
|
if (actor == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
try
|
|
{
|
|
if (actor.isBaby())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore
|
|
}
|
|
|
|
try
|
|
{
|
|
double created = actor.data != null ? actor.data.created_time : 0;
|
|
if (created <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
double now = 0;
|
|
if (World.world != null)
|
|
{
|
|
now = World.world.getCurWorldTime();
|
|
}
|
|
else if (MapBox.instance != null)
|
|
{
|
|
now = MapBox.instance.getCurWorldTime();
|
|
}
|
|
|
|
// Within ~2 world-years of creation = spawn endowment, not a mid-life gain.
|
|
return now > 0 && (now - created) < 2.0;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|