using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using AggroKit; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using CompanionKit.Core; using DonorKit; using ForgeKit; using HarmonyLib; using NetKit; using NetKit.Core; using Photon; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("CompanionKit")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.4.20.0")] [assembly: AssemblyInformationalVersion("0.4.20+091b206910305beb491301afe1db01b8cd7b8e72")] [assembly: AssemblyProduct("CompanionKit")] [assembly: AssemblyTitle("CompanionKit")] [assembly: AssemblyMetadata("BuildStamp", "091b2069 2026-08-28")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] namespace CompanionKit; public static class AnchorAnimSpy { private const float IntervalSeconds = 2f; private static float _nextAt; private static readonly string[] StateProbes = new string[15] { "Attack", "Attack1", "Attack2", "AttackA", "AttackB", "Idle", "Move", "Movement", "Locomotion", "Run", "Walk", "Block", "Death", "Unsheathe", "Sheathe" }; private static ModLog Log => CompanionRuntime.Log; internal static void Tick() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) ConfigEntry anchorAnimSpy = CkConfig.Effigy.AnchorAnimSpy; if (anchorAnimSpy == null || !anchorAnimSpy.Value || Time.unscaledTime < _nextAt) { return; } _nextAt = Time.unscaledTime + 2f; CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return; } int num = 0; try { foreach (Character value in instance.Characters.Values) { if (!((Object)(object)value == (Object)null) && value.Alive && AnchorSentinel.IsAnchorUid(UID.op_Implicit(value.UID)) && !CompanionAnchor.IsAnchor(value)) { DumpAnchor(value); num++; } } } catch (Exception ex) { Log.LogWarning((object)("[ANIMSPY] enumeration threw: " + ex.Message)); return; } if (num == 0) { Log.LogMessage((object)"[ANIMSPY] no foreign anchor replicas on this machine right now (run this on the NON-owner during a pet fight — the owner's own anchor is skipped)."); } } private static void DumpAnchor(Character c) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) AnchorSentinel.TryParseOwner(UID.op_Implicit(c.UID), out var ownerUid); string text = CompanionAnchor.ViewIdOf(c); Animator componentInChildren = ((Component)c).GetComponentInChildren(true); if ((Object)(object)componentInChildren == (Object)null) { Log.LogMessage((object)("[ANIMSPY] anchor viewID=" + text + " owner='" + ownerUid + "': NO Animator found under the replica.")); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[ANIMSPY] anchor viewID=" + text + " owner='" + ownerUid + "' controller=" + (((Object)(object)componentInChildren.runtimeAnimatorController != (Object)null) ? ((Object)componentInChildren.runtimeAnimatorController).name : "none") + " " + $"layers={componentInChildren.layerCount}"); stringBuilder.Append(" | params: "); AnimatorControllerParameter[] parameters = componentInChildren.parameters; if (parameters == null || parameters.Length == 0) { stringBuilder.Append("none"); } else { for (int i = 0; i < parameters.Length; i++) { AnimatorControllerParameter val = parameters[i]; if (i > 0) { stringBuilder.Append(", "); } stringBuilder.Append(val.name).Append('=').Append(ParamValue(componentInChildren, val)); } } Log.LogMessage((object)stringBuilder.ToString()); for (int j = 0; j < componentInChildren.layerCount; j++) { AnimatorStateInfo currentAnimatorStateInfo = componentInChildren.GetCurrentAnimatorStateInfo(j); StringBuilder stringBuilder2 = new StringBuilder(); stringBuilder2.Append($"[ANIMSPY] layer {j} '{componentInChildren.GetLayerName(j)}' state fullPathHash={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).fullPathHash} " + $"shortNameHash={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).shortNameHash} normTime={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime:F2} " + $"inTransition={componentInChildren.IsInTransition(j)} loop={((AnimatorStateInfo)(ref currentAnimatorStateInfo)).loop}"); string text2 = ProbeNames(componentInChildren, j); if (text2.Length > 0) { stringBuilder2.Append(" matches: ").Append(text2); } Log.LogMessage((object)stringBuilder2.ToString()); } } private static string ParamValue(Animator anim, AnimatorControllerParameter p) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected I4, but got Unknown //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 AnimatorControllerParameterType type = p.type; switch (type - 1) { default: if ((int)type != 9) { break; } return anim.GetBool(p.nameHash) + "(trig)"; case 0: return anim.GetFloat(p.nameHash).ToString("F2") + "(f)"; case 2: return anim.GetInteger(p.nameHash) + "(i)"; case 3: return anim.GetBool(p.nameHash) + "(b)"; case 1: break; } return "?"; } private static string ProbeNames(Animator anim, int layer) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); AnimatorStateInfo currentAnimatorStateInfo = anim.GetCurrentAnimatorStateInfo(layer); string[] stateProbes = StateProbes; foreach (string text in stateProbes) { if (((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsName(text)) { if (stringBuilder.Length > 0) { stringBuilder.Append('/'); } stringBuilder.Append(text); } } return stringBuilder.ToString(); } } [HarmonyPatch(typeof(Character), "SendPerformAttackTrivial", new Type[] { typeof(int), typeof(int), typeof(bool) })] internal static class AnchorAttackMirror { internal static int RpcSwings; [HarmonyPostfix] private static void Postfix(Character __instance, int _type) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)__instance == (Object)null) && AnchorSentinel.IsAnchorUid(UID.op_Implicit(__instance.UID)) && CompanionEffigy.TryGetBodyForAnchor(__instance, out var body)) { EffigySwingMirror component = ((Component)body).GetComponent(); if (!((Object)(object)component == (Object)null)) { RpcSwings++; component.Mirror(_type); } } } catch (Exception ex) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[PIN] attack mirror threw (swallowed): " + ex.Message)); } } } } internal static class WeaponNeuter { private static readonly FieldInfo BaseDamageField = typeof(Weapon).GetField("m_baseDamage", BindingFlags.Instance | BindingFlags.NonPublic); internal static void Apply(Weapon w) { if ((Object)(object)w == (Object)null) { return; } DamageList damage = w.Damage; if (damage != null) { damage.Clear(); } if (!(BaseDamageField == null)) { object? value = BaseDamageField.GetValue(w); DamageList val = (DamageList)((value is DamageList) ? value : null); if (val != null) { val.Clear(); } } } } public sealed class AnchorDressing { private readonly Func _current; private readonly Func _cfg; private GameObject _voiceSource; private CharacterSoundManager _bodyCsm; private static readonly WaitForSeconds _hideBurst0 = new WaitForSeconds(0.2f); private static readonly WaitForSeconds _hideBurst1 = new WaitForSeconds(0.4f); private static readonly WaitForSeconds _hideBurst2 = new WaitForSeconds(0.9f); private static readonly WaitForSeconds _hideBurst3 = new WaitForSeconds(1.5f); private static readonly WaitForSeconds _hideBurst4 = new WaitForSeconds(2f); private static readonly WaitForSeconds _hideSlowWait = new WaitForSeconds(0.5f); private static readonly WaitForSeconds _muteWait = new WaitForSeconds(1f); private readonly List _rendererBuf = new List(); private readonly List _audioBuf = new List(); private readonly List _particleBuf = new List(); private float _hideLogAt; private int _hideLogCount; private int _stopLogCount; private Character Current => _current(); private ICompanionSettings Cfg => _cfg(); private bool HasLiveAnchor { get { Character val = _current(); if ((Object)(object)val != (Object)null) { return val.Alive; } return false; } } private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg); public AnchorDressing(Func current, Func cfg) { _current = current; _cfg = cfg; } public void ResetVoiceSource() { _voiceSource = null; } public void ResetBodySound() { _bodyCsm = null; } public IEnumerator NeuterWeaponWhenReady(Character anchor) { for (int i = 0; i < 20; i++) { yield return (object)new WaitForSeconds(0.5f); if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor) { yield break; } Weapon currentWeapon = anchor.CurrentWeapon; if ((Object)(object)currentWeapon == (Object)null) { continue; } if (Cfg.AnchorInvisible) { Renderer[] componentsInChildren = ((Component)currentWeapon).GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { val.enabled = false; } } try { WeaponNeuter.Apply(currentWeapon); CompanionRuntime.Log.LogMessage((object)(TagAnchor + " weapon damage zeroed (defense-only anchor; CompanionCombat owns damage).")); yield break; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " weapon neuter failed: " + ex.Message)); yield break; } } CompanionRuntime.Log.LogWarning((object)(TagAnchor + " no weapon appeared to neuter (anchor may deal its own damage).")); } public IEnumerator HideSweep(Character anchor) { WaitForSeconds[] array = (WaitForSeconds[])(object)new WaitForSeconds[5] { _hideBurst0, _hideBurst1, _hideBurst2, _hideBurst3, _hideBurst4 }; WaitForSeconds[] array2 = array; for (int i = 0; i < array2.Length; i++) { yield return array2[i]; if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor) { yield break; } HidePass(anchor); } while ((Object)(object)anchor != (Object)null && (Object)(object)Current == (Object)(object)anchor) { yield return _hideSlowWait; if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor) { break; } HidePass(anchor); } } private void HidePass(Character anchor) { int num = 0; ((Component)anchor).GetComponentsInChildren(true, _rendererBuf); foreach (Renderer item in _rendererBuf) { if (item.enabled) { item.enabled = false; num++; } } int num2 = 0; ((Component)anchor).GetComponentsInChildren(true, _particleBuf); foreach (ParticleSystem item2 in _particleBuf) { if (item2.isPlaying || item2.particleCount > 0) { item2.Stop(true, (ParticleSystemStopBehavior)0); num2++; } } if (num != 0 || num2 != 0) { _hideLogCount += num; _stopLogCount += num2; if (Time.time - _hideLogAt > 10f) { CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} hid {_hideLogCount} renderer(s), stopped {_stopLogCount} particle system(s)."); _hideLogAt = Time.time; _hideLogCount = 0; _stopLogCount = 0; } } } public void ApplyVoice(CompanionBody body) { if (Cfg.SpeciesVoice && HasLiveAnchor && !((Object)(object)body == (Object)null) && !((Object)(object)((Component)body).gameObject == (Object)(object)_voiceSource)) { _voiceSource = ((Component)body).gameObject; CharacterSoundManager val = ((Component)body).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)body).GetComponentInChildren(true); } _bodyCsm = val; CharacterSoundsPresets val2 = (((Object)(object)val != (Object)null) ? val.m_characterSoundsPresets : null); CharacterSoundManager component = ((Component)Current).GetComponent(); int num = ((Component)Current).GetComponentsInChildren(true).Length; if ((Object)(object)component != (Object)null && (Object)(object)val2 != (Object)null) { component.m_characterSoundsPresets = val2; } AnchorVoice anchorVoice = ((Component)Current).GetComponent(); if ((Object)(object)anchorVoice == (Object)null) { anchorVoice = ((Component)Current).gameObject.AddComponent(); } anchorVoice.BodySound = val; CompanionRuntime.Log.LogMessage((object)(TagAnchor + " voice wired for '" + body.SpeciesId + "': bodyPreset=" + (((Object)(object)val2 != (Object)null) ? ((Object)val2).name : "NONE") + " " + $"anchorRootCSM={(Object)(object)component != (Object)null} anchorChildCSMs={num} — hurt via CharHurt receiver, death via HandleDeath" + (((Object)(object)val2 == (Object)null) ? " (NO body preset — hurt/death vocals unavailable for this species)" : "") + ".")); } } public void PlayDeathVocal(Character corpse) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) if (!Cfg.SpeciesVoice || (Object)(object)_bodyCsm == (Object)null || (Object)(object)corpse == (Object)null) { return; } try { Global.AudioManager.PlaySoundAtPosition(_bodyCsm.GetDeathSound(), ((Component)corpse).transform, 0f, 1f, 1f, 1f, 1f); CompanionRuntime.Log.LogMessage((object)(TagAnchor + " species death vocal played.")); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " death vocal failed: " + ex.Message)); } } public IEnumerator MuteSweep(Character anchor) { int muted = 0; while ((Object)(object)anchor != (Object)null && (Object)(object)Current == (Object)(object)anchor) { ((Component)anchor).GetComponentsInChildren(true, _audioBuf); foreach (AudioSource item in _audioBuf) { if (!item.mute) { item.mute = true; muted++; } } if (muted > 0) { CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} muted {muted} local audio source(s) (weapon whoosh / movement)."); muted = 0; } yield return _muteWait; } } public void ApplyHealthBarConfig(Character anchor) { if (!Cfg.AnchorShowHealthBar) { CharacterBarManager component = ((Component)anchor).GetComponent(); if (!((Object)(object)component == (Object)null)) { component.BarDisplayPrefab = null; ((Behaviour)component).enabled = false; } } } } public class AnchorVoice : MonoBehaviour { public CharacterSoundManager BodySound; private Character _anchor; private float _lastCryAt; private float _lastHealth = float.NaN; private const float MinHurtDamage = 9f; private void CharHurt(Character _dealer) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)BodySound == (Object)null) { return; } if ((Object)(object)_anchor == (Object)null) { _anchor = ((Component)this).GetComponent(); } if ((Object)(object)_anchor != (Object)null) { float health = _anchor.Health; float num = (float.IsNaN(_lastHealth) ? 9f : (_lastHealth - health)); _lastHealth = health; if (num < 9f) { return; } } if (Time.time - _lastCryAt < 0.7f) { return; } _lastCryAt = Time.time; try { Global.AudioManager.PlaySoundAtPosition(BodySound.GetHurtSound(), ((Component)this).transform, 0f, 1f, 1f, 1f, 1f); } catch { } } } public sealed class AnchorPhysics { private readonly struct PairKey : IEquatable { private readonly int _a; private readonly int _b; public PairKey(Collider a, Collider b) { int instanceID = ((Object)a).GetInstanceID(); int instanceID2 = ((Object)b).GetInstanceID(); if (instanceID <= instanceID2) { _a = instanceID; _b = instanceID2; } else { _a = instanceID2; _b = instanceID; } } public bool Equals(PairKey o) { if (_a == o._a) { return _b == o._b; } return false; } public override bool Equals(object o) { if (o is PairKey o2) { return Equals(o2); } return false; } public override int GetHashCode() { return (_a * 397) ^ _b; } } private readonly Func _current; private readonly Func _cfg; private static readonly WaitForSeconds _burst0 = new WaitForSeconds(0.2f); private static readonly WaitForSeconds _burst1 = new WaitForSeconds(0.4f); private static readonly WaitForSeconds _burst2 = new WaitForSeconds(0.9f); private static readonly WaitForSeconds _burst3 = new WaitForSeconds(1.5f); private Character _stampHost; private readonly HashSet _stamped = new HashSet(); private int _stamps; private int _restamps; private float _lastRestampAt = -1f; private bool _phantomApplied; private float _restampLogAt; private AnchorCollisionMode _lastMode; private bool _modeKnown; private readonly List _players = new List(); private float _playersAt = -999f; private const float PlayerCacheSeconds = 1f; private Character Current => _current(); private ICompanionSettings Cfg => _cfg(); private bool HasLiveAnchor { get { Character val = _current(); if ((Object)(object)val != (Object)null) { return val.Alive; } return false; } } private string TagPhys => CompanionRuntime.Tag("ANCHORPHYS", Cfg); public AnchorPhysics(Func current, Func cfg) { _current = current; _cfg = cfg; } public void Forget() { _stampHost = null; _stamped.Clear(); _stamps = 0; _restamps = 0; _lastRestampAt = -1f; _phantomApplied = false; _players.Clear(); _playersAt = -999f; } private List Players() { bool flag = _players.Count == 0 || Time.unscaledTime - _playersAt > 1f; if (!flag) { for (int i = 0; i < _players.Count; i++) { if ((Object)(object)_players[i] == (Object)null) { flag = true; break; } } } if (!flag) { return _players; } _playersAt = Time.unscaledTime; _players.Clear(); CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return _players; } for (int j = 0; j < instance.PlayerCharacters.Count; j++) { Character character = instance.GetCharacter(instance.PlayerCharacters.Values[j]); if ((Object)(object)character != (Object)null) { _players.Add(character); } } return _players; } public void Sync() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Invalid comparison between Unknown and I4 //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) if (!HasLiveAnchor) { return; } Character current = Current; if (_stampHost != current) { _stampHost = current; _stamped.Clear(); _stamps = 0; _restamps = 0; _lastRestampAt = -1f; _phantomApplied = false; } AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision; if (!_modeKnown || anchorPlayerCollision != _lastMode) { if (_modeKnown) { CompanionRuntime.Log.LogMessage((object)$"{TagPhys} mode {_lastMode} -> {anchorPlayerCollision} (live)."); } _lastMode = anchorPlayerCollision; _modeKnown = true; } if ((int)anchorPlayerCollision == 2) { ApplyPhantom(current); return; } RestorePhantom(current); Collider characterController = (Collider)(object)current.CharacterController; Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider; List list = Players(); for (int i = 0; i < list.Count; i++) { Character val = list[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current)) { Collider characterController2 = (Collider)(object)val.CharacterController; Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider; Converge(anchorPlayerCollision, characterController, charMoveBlockCollider2, "anchorCC<->playerBox"); Converge(anchorPlayerCollision, characterController2, charMoveBlockCollider, "playerCC<->anchorBox"); Converge(anchorPlayerCollision, characterController, characterController2, "anchorCC<->playerCC"); Converge(anchorPlayerCollision, charMoveBlockCollider, charMoveBlockCollider2, "anchorBox<->playerBox"); } } } private void Converge(AnchorCollisionMode mode, Collider a, Collider b, string label) { //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected I4, but got Unknown bool flag = Ready(a) && Ready(b); PairKey item = (flag ? new PairKey(a, b) : default(PairKey)); bool flag2 = flag && _stamped.Contains(item); bool flag3 = flag && Physics.GetIgnoreCollision(a, b); AnchorPhysAction val = AnchorPhysicsPolicy.Decide(mode, flag2, flag3, flag); switch (val - 1) { case 0: Physics.IgnoreCollision(a, b, true); _stamped.Add(item); _stamps++; CompanionRuntime.Log.LogMessage((object)(TagPhys + " exempt " + label + " — the anchor can no longer push the player.")); break; case 2: Physics.IgnoreCollision(a, b, true); _restamps++; _lastRestampAt = Time.time; if (Time.time - _restampLogAt > 5f) { _restampLogAt = Time.time; CompanionRuntime.Log.LogMessage((object)$"{TagPhys} re-stamp: {label} was reset by a collider toggle (restamps={_restamps})."); } break; case 1: Physics.IgnoreCollision(a, b, false); _stamped.Remove(item); CompanionRuntime.Log.LogMessage((object)(TagPhys + " revoked " + label + " (mode=Block) — the anchor blocks the player again.")); break; } } private static bool Ready(Collider c) { if ((Object)(object)c != (Object)null && c.enabled) { return ((Component)c).gameObject.activeInHierarchy; } return false; } private void ApplyPhantom(Character anchor) { CharacterController characterController = anchor.CharacterController; if ((Object)(object)characterController != (Object)null && characterController.detectCollisions) { characterController.detectCollisions = false; _phantomApplied = true; CompanionRuntime.Log.LogMessage((object)(TagPhys + " PHANTOM: controller detectCollisions=false.")); } BoxCollider charMoveBlockCollider = anchor.CharMoveBlockCollider; if ((Object)(object)charMoveBlockCollider != (Object)null && ((Collider)charMoveBlockCollider).enabled) { ((Collider)charMoveBlockCollider).enabled = false; _phantomApplied = true; CompanionRuntime.Log.LogMessage((object)(TagPhys + " PHANTOM: move-block box disabled — nothing can be blocked by the anchor (enemies may now overlap the pet's model). Flip back to PassPlayer + reloadcfg to restore it.")); } } private void RestorePhantom(Character anchor) { if (_phantomApplied) { _phantomApplied = false; CharacterController characterController = anchor.CharacterController; if ((Object)(object)characterController != (Object)null) { characterController.detectCollisions = true; } BoxCollider charMoveBlockCollider = anchor.CharMoveBlockCollider; if ((Object)(object)charMoveBlockCollider != (Object)null) { ((Collider)charMoveBlockCollider).enabled = true; } CompanionRuntime.Log.LogMessage((object)(TagPhys + " left PHANTOM — the anchor's blocking volumes are back (it blocks enemies again).")); } } public string Dump() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Invalid comparison between Unknown and I4 //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Invalid comparison between Unknown and I4 StringBuilder stringBuilder = new StringBuilder(); AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision; stringBuilder.AppendLine($"{TagPhys} mode={anchorPlayerCollision} glue={Cfg.GlueMode} offsetBehind={Cfg.GlueOffsetBehind:F2}m"); if (!HasLiveAnchor) { stringBuilder.Append(TagPhys + " no live anchor."); return stringBuilder.ToString(); } Character current = Current; stringBuilder.AppendLine(TagPhys + " anchor '" + current.Name + "': " + Describe(current)); stringBuilder.AppendLine($"{TagPhys} stamps={_stamps} restamps={_restamps} " + string.Format("lastRestamp={0} phantomApplied={1}", (_lastRestampAt < 0f) ? "never" : $"{Time.time - _lastRestampAt:F0}s ago", _phantomApplied)); Collider characterController = (Collider)(object)current.CharacterController; Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider; List list = Players(); for (int i = 0; i < list.Count; i++) { Character val = list[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current)) { float num = Vector3.Distance(((Component)current).transform.position, ((Component)val).transform.position); stringBuilder.AppendLine($"{TagPhys} player '{val.Name}' (local={val.IsLocalPlayer}, {num:F1}m from the anchor): {Describe(val)}"); Collider characterController2 = (Collider)(object)val.CharacterController; Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider; int ok = 0; int total = 0; stringBuilder.AppendLine(TagPhys + " " + Pair(characterController, charMoveBlockCollider2, "anchorCC<->playerBox", ref ok, ref total)); stringBuilder.AppendLine(TagPhys + " " + Pair(characterController2, charMoveBlockCollider, "playerCC<->anchorBox", ref ok, ref total)); stringBuilder.AppendLine(TagPhys + " " + Pair(characterController, characterController2, "anchorCC<->playerCC", ref ok, ref total)); stringBuilder.AppendLine(TagPhys + " " + Pair(charMoveBlockCollider, charMoveBlockCollider2, "anchorBox<->playerBox", ref ok, ref total)); stringBuilder.AppendLine($"{TagPhys} ignored {ok}/{total} pair(s)" + (((int)anchorPlayerCollision == 1 && ok < total) ? " — NOT fully exempt: the anchor can still shove this player." : (((int)anchorPlayerCollision == 1) ? " — the anchor cannot move this player." : ""))); } } return stringBuilder.ToString().TrimEnd(Array.Empty()); } public string Fragment() { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 //IL_00db: Unknown result type (might be due to invalid IL or missing references) if (!HasLiveAnchor) { return "phys=no-anchor"; } AnchorCollisionMode anchorPlayerCollision = Cfg.AnchorPlayerCollision; if ((int)anchorPlayerCollision == 2) { return $"phys=Phantom(applied={_phantomApplied})"; } Character current = Current; Collider characterController = (Collider)(object)current.CharacterController; Collider charMoveBlockCollider = (Collider)(object)current.CharMoveBlockCollider; int ok = 0; int total = 0; List list = Players(); for (int i = 0; i < list.Count; i++) { Character val = list[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val == (Object)(object)current)) { Collider characterController2 = (Collider)(object)val.CharacterController; Collider charMoveBlockCollider2 = (Collider)(object)val.CharMoveBlockCollider; Count(characterController, charMoveBlockCollider2, ref ok, ref total); Count(characterController2, charMoveBlockCollider, ref ok, ref total); Count(characterController, characterController2, ref ok, ref total); Count(charMoveBlockCollider, charMoveBlockCollider2, ref ok, ref total); } } return $"phys={anchorPlayerCollision} ignored={ok}/{total} restamps={_restamps}"; } private static void Count(Collider a, Collider b, ref int ok, ref int total) { total++; if (Ready(a) && Ready(b) && Physics.GetIgnoreCollision(a, b)) { ok++; } } private string Pair(Collider a, Collider b, string label, ref int ok, ref int total) { total++; if (!Ready(a) || !Ready(b)) { return label + " = NOT READY (" + (Ready(a) ? "" : ("a:" + State(a) + " ")) + (Ready(b) ? "" : ("b:" + State(b))) + ") — skipped, will retry"; } bool ignoreCollision = Physics.GetIgnoreCollision(a, b); if (ignoreCollision) { ok++; } return $"{label} = ignored:{ignoreCollision}"; } private static string State(Collider c) { if (!((Object)(object)c == (Object)null)) { if (((Component)c).gameObject.activeInHierarchy) { if (c.enabled) { return "ready"; } return "disabled"; } return "GO-inactive"; } return "absent"; } private static string Describe(Character c) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) CharacterController characterController = c.CharacterController; BoxCollider charMoveBlockCollider = c.CharMoveBlockCollider; string text = (((Object)(object)characterController == (Object)null) ? "CC=absent" : $"CC={State((Collider)(object)characterController)} r={characterController.radius:F2} h={characterController.height:F2} layer={LayerMask.LayerToName(((Component)characterController).gameObject.layer)} detect={characterController.detectCollisions}"); string text2 = (((Object)(object)charMoveBlockCollider == (Object)null) ? "CharMoveBlock=absent" : $"CharMoveBlock={State((Collider)(object)charMoveBlockCollider)} size={charMoveBlockCollider.size} layer={LayerMask.LayerToName(((Component)charMoveBlockCollider).gameObject.layer)}"); return text + " · " + text2; } public IEnumerator StampWhenReady(Character anchor) { Sync(); WaitForSeconds[] array = (WaitForSeconds[])(object)new WaitForSeconds[4] { _burst0, _burst1, _burst2, _burst3 }; WaitForSeconds[] array2 = array; for (int i = 0; i < array2.Length; i++) { yield return array2[i]; if ((Object)(object)anchor == (Object)null || (Object)(object)Current != (Object)(object)anchor) { break; } Sync(); } } } public static class AnchorSentinel { public const string Prefix = "CKA1:"; private const int SuffixLen = 8; public static string MakeUid(string ownerUid) { return "CKA1:" + Guid.NewGuid().ToString("N").Substring(0, 8) + ":" + ownerUid; } public static bool IsAnchorUid(string uid) { if (!string.IsNullOrEmpty(uid)) { return uid.StartsWith("CKA1:", StringComparison.Ordinal); } return false; } public static bool TryParseOwner(string uid, out string ownerUid) { ownerUid = null; if (!IsAnchorUid(uid) || uid.Length < "CKA1:".Length + 8 + 1) { return false; } ownerUid = uid.Substring("CKA1:".Length + 8 + 1); return true; } } [HarmonyPatch(typeof(CharacterManager), "InstantiateNetworkCharacter")] internal static class AnchorRecognizer { [HarmonyPostfix] private static void Postfix(GameObject __result, string _uid) { if ((Object)(object)__result == (Object)null || !AnchorSentinel.IsAnchorUid(_uid)) { return; } Character component = __result.GetComponent(); if ((Object)(object)component == (Object)null) { return; } if ((Object)(object)Plugin.Instance == (Object)null) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[ANCHOR] sentinel anchor recognized with no plugin instance (uid '" + _uid + "') — dressing/defuse SKIPPED; this was believed structurally impossible, escalate.")); } } else { ((MonoBehaviour)Plugin.Instance).StartCoroutine(AnchorReplicaDress.DressReplica(component, _uid)); CompanionEffigy.PokeReconcile(); } } } internal static class AnchorReplicaDress { private static readonly WaitForSeconds _sweepWait = new WaitForSeconds(0.5f); private const int ScopeCommitSweeps = 4; private static readonly List _rendererBuf = new List(); private static readonly List _audioBuf = new List(); private static readonly List _particleBuf = new List(); private static readonly ICompanionSettings _fallback = new CompanionSettingsDefaults(); private static ICompanionSettings Cfg => CompanionRuntime.Fallback ?? _fallback; internal static float NetLerpSpeed => Mathf.Clamp(CkConfig.Effigy.AnchorNetLerpSpeed?.Value ?? 4f, 0f, 20f); internal static float NetMoveSpeed => Mathf.Clamp(CkConfig.Effigy.AnchorNetMoveSpeed?.Value ?? 1f, 0f, 10f); internal static IEnumerator DressReplica(Character anchor, string uid) { yield return null; yield return null; if ((Object)(object)anchor == (Object)null || CompanionAnchor.IsAnchor(anchor)) { yield break; } AnchorSentinel.TryParseOwner(uid, out var ownerUid); AnchorReplicaPlan plan = AnchorReplicaPolicy.For(false); string censusHeld = StatusDisplayCensus(anchor); bool weaponNeutered = false; bool barKilled = false; bool lifetimeCancelled = false; bool nccRetuned = false; bool scopeCommitted = false; bool correctionLogged = false; bool weaponNeuterPending = false; int sweeps = 0; string lastViewId = "n/a"; float bakedFuse = 0f; string neuterError = null; int hidden = 0; int muted = 0; int stopped = 0; float logAt = 0f; while ((Object)(object)anchor != (Object)null) { ICompanionSettings cfg = Cfg; bool flag = CompanionEffigy.IsLocalOwner(ownerUid); lastViewId = CompanionAnchor.ViewIdOf(anchor); if (!scopeCommitted) { sweeps++; if (flag || OwnerResolvable(ownerUid) || sweeps >= 4) { plan = AnchorReplicaPolicy.For(flag); scopeCommitted = true; if (flag) { CompanionRuntime.Log.LogMessage((object)("[ANCHOR] own-pet companion anchor replica recognized (owner UID '" + ownerUid + "', viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") — this machine OWNS that pet and its anchor is proxied on the master; running the same convergent dressing (hide/mute/neuter/health-bar/collision) on the replica.")); } else { CompanionRuntime.Log.LogMessage((object)("[ANCHOR] foreign companion anchor recognized (owner UID '" + ownerUid + "', viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") — running convergent dressing (hide/mute/neuter/health-bar/collision) on this machine.")); } CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + plan.LogScope + " status-display census: " + censusHeld)); } } else if (flag != plan.LocalOwner) { plan = AnchorReplicaPolicy.For(flag); if (!correctionLogged) { correctionLogged = true; CompanionRuntime.Log.LogMessage((object)("[ANCHOR] anchor replica ownership re-resolved for owner UID '" + ownerUid + "' (viewID=" + CompanionAnchor.ViewIdOf(anchor) + ") → " + (flag ? "OWN pet (proxied on the master)" : "foreign") + " — the recognition line above said otherwise (the owner Character resolved late); the dressing itself is identical either way. Logged once per replica.")); } } string logScope = plan.LogScope; if (anchor.Lifetime > 0f) { bakedFuse = anchor.Lifetime; anchor.Lifetime = -1f; } if (bakedFuse > 0f && !lifetimeCancelled && scopeCommitted) { lifetimeCancelled = true; CompanionRuntime.Log.LogMessage((object)($"[ANCHOR] {logScope} lifetime neutralized (baked {bakedFuse:F0}s " + "summon self-despawn cancelled on this replica — the countdown runs per-machine and the owner's neutralization does not replicate).")); } float netLerpSpeed = NetLerpSpeed; float netMoveSpeed = NetMoveSpeed; if (netLerpSpeed > 0f || netMoveSpeed > 0f) { CharacterControl characterControl = anchor.CharacterControl; NetworkCharacterControl val = (NetworkCharacterControl)(object)((characterControl is NetworkCharacterControl) ? characterControl : null); if ((Object)(object)val != (Object)null) { if (netLerpSpeed > 0f) { val.LerpSpeed = netLerpSpeed; } if (netMoveSpeed > 0f) { val.MoveSpeed = netMoveSpeed; } if (!nccRetuned && scopeCommitted) { nccRetuned = true; CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + logScope + " net convergence retuned " + $"(LerpSpeed={netLerpSpeed:F2}, MoveSpeed={netMoveSpeed:F2}; vanilla 1/0.2) — " + "the replica now tracks its streamed position closely enough for a pinned body.")); } } } if (cfg.AnchorInvisible) { ((Component)anchor).GetComponentsInChildren(true, _rendererBuf); foreach (Renderer item in _rendererBuf) { if (item.enabled) { item.enabled = false; hidden++; } } ((Component)anchor).GetComponentsInChildren(true, _particleBuf); foreach (ParticleSystem item2 in _particleBuf) { if (item2.isPlaying || item2.particleCount > 0) { item2.Stop(true, (ParticleSystemStopBehavior)0); stopped++; } } } if (cfg.SpeciesVoice) { ((Component)anchor).GetComponentsInChildren(true, _audioBuf); foreach (AudioSource item3 in _audioBuf) { if (!item3.mute) { item3.mute = true; muted++; } } } Weapon currentWeapon = anchor.CurrentWeapon; if ((Object)(object)currentWeapon != (Object)null) { if (cfg.AnchorInvisible) { Renderer[] componentsInChildren = ((Component)currentWeapon).GetComponentsInChildren(true); foreach (Renderer val2 in componentsInChildren) { if (val2.enabled) { val2.enabled = false; hidden++; } } } if (currentWeapon.Damage != null && currentWeapon.Damage.Count > 0) { try { WeaponNeuter.Apply(currentWeapon); weaponNeutered = true; weaponNeuterPending = true; } catch (Exception ex) { neuterError = ex.Message; } } } if (neuterError != null && scopeCommitted) { CompanionRuntime.Log.LogWarning((object)("[ANCHOR] " + logScope + " weapon neuter failed: " + neuterError)); neuterError = null; } if (weaponNeuterPending && scopeCommitted) { weaponNeuterPending = false; CompanionRuntime.Log.LogMessage((object)("[ANCHOR] " + logScope + " weapon damage zeroed — its mirrored swings on this machine can no longer RPC phantom damage to the master.")); } if (!barKilled && !cfg.AnchorShowHealthBar) { CharacterBarManager component = ((Component)anchor).GetComponent(); if ((Object)(object)component != (Object)null) { component.BarDisplayPrefab = null; ((Behaviour)component).enabled = false; barKilled = true; } } if ((int)cfg.AnchorPlayerCollision == 1) { StampPlayerPairs(anchor); } if (scopeCommitted && (hidden > 0 || muted > 0 || stopped > 0) && Time.time - logAt > 10f) { CompanionRuntime.Log.LogMessage((object)($"[ANCHOR] {logScope} dressing: hid {hidden} renderer(s), " + $"muted {muted} audio source(s), stopped {stopped} particle system(s)" + (weaponNeutered ? ", weapon zeroed" : "") + (barKilled ? ", health bar off" : "") + ".")); logAt = Time.time; hidden = 0; muted = 0; stopped = 0; } yield return _sweepWait; } if (!scopeCommitted) { CompanionRuntime.Log.LogMessage((object)("[ANCHOR] anchor replica destroyed before its scope resolved " + $"(owner UID '{ownerUid}', viewID={lastViewId}, {sweeps} sweep(s)) — dressing ran, ownership never " + "resolved, so it is unknown whether this was an own-pet or a foreign anchor.")); } } private static bool OwnerResolvable(string ownerUid) { try { if (string.IsNullOrEmpty(ownerUid) || (Object)(object)CharacterManager.Instance == (Object)null) { return false; } Character character = CharacterManager.Instance.GetCharacter(ownerUid); return (Object)(object)character != (Object)null && (Object)(object)character.OwnerPlayerSys != (Object)null; } catch { return false; } } private static string StatusDisplayCensus(Character anchor) { StringBuilder stringBuilder = new StringBuilder(); MonoBehaviour[] componentsInChildren = ((Component)anchor).GetComponentsInChildren(true); foreach (MonoBehaviour val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } string name = ((object)val).GetType().Name; if (name.IndexOf("Status", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Bond", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Bar", StringComparison.OrdinalIgnoreCase) >= 0 || name.IndexOf("Display", StringComparison.OrdinalIgnoreCase) >= 0) { if (stringBuilder.Length > 0) { stringBuilder.Append(", "); } stringBuilder.Append(name).Append('(').Append(((Behaviour)val).enabled ? "on" : "off") .Append(')'); } } if (stringBuilder.Length != 0) { return stringBuilder.ToString(); } return "none"; } private static void StampPlayerPairs(Character anchor) { CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return; } Collider characterController = (Collider)(object)anchor.CharacterController; Collider charMoveBlockCollider = (Collider)(object)anchor.CharMoveBlockCollider; for (int i = 0; i < instance.PlayerCharacters.Count; i++) { Character character = instance.GetCharacter(instance.PlayerCharacters.Values[i]); if (!((Object)(object)character == (Object)null) && !((Object)(object)character == (Object)(object)anchor)) { Stamp(characterController, (Collider)(object)character.CharMoveBlockCollider); Stamp((Collider)(object)character.CharacterController, charMoveBlockCollider); Stamp(characterController, (Collider)(object)character.CharacterController); Stamp(charMoveBlockCollider, (Collider)(object)character.CharMoveBlockCollider); } } } private static void Stamp(Collider a, Collider b) { if (!((Object)(object)a == (Object)null) && !((Object)(object)b == (Object)null) && a.enabled && b.enabled && ((Component)a).gameObject.activeInHierarchy && ((Component)b).gameObject.activeInHierarchy && !Physics.GetIgnoreCollision(a, b)) { Physics.IgnoreCollision(a, b, true); } } } public sealed class AnchorStats { private readonly Func _current; private readonly Func _cfg; private const string StatSourceId = "CK_SpeciesStats"; private Character _statsHost; private float[] _statBaseline; private float[] _appliedWant; private Character _zeroHost; private int _zeroReads; private bool _zeroWarned; private float _zeroFirstReadAt; private bool _acceptedZeroBaseline; private StatApplyGate _notedGate; private Character _vitalsHost; private float _healthFraction = 1f; private Func _healthEnabled; private Character Current => _current(); private ICompanionSettings Cfg => _cfg(); private bool HasLiveAnchor { get { Character val = _current(); if ((Object)(object)val != (Object)null) { return val.Alive; } return false; } } private string TagStats => CompanionRuntime.Tag("STATS", Cfg); private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg); public StatApplyGate LastGate { get; private set; } public bool VitalsPending { get; private set; } private bool HealthPersistenceOn { get { if (_healthEnabled != null) { return _healthEnabled(); } return false; } } public float HealthFraction => _healthFraction; public AnchorStats(Func current, Func cfg) { _current = current; _cfg = cfg; } public void ForgetAnchor() { //IL_0045: Unknown result type (might be due to invalid IL or missing references) _statsHost = null; _statBaseline = null; _appliedWant = null; _vitalsHost = null; _zeroHost = null; _zeroReads = 0; _zeroWarned = false; _zeroFirstReadAt = 0f; _acceptedZeroBaseline = false; _notedGate = (StatApplyGate)0; LastGate = (StatApplyGate)0; VitalsPending = false; } private void Gate(StatApplyGate gate) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) LastGate = gate; if (gate != _notedGate) { _notedGate = gate; if (PendingApplyPolicy.IsPending(gate)) { CompanionRuntime.Log.LogMessage((object)(TagStats + " species-stat apply WAITING: " + PendingApplyPolicy.Describe(gate) + " — retrying per frame (V88 breadcrumb).")); } } } public void ApplyCreatureStats(CreatureAttributes eff) { //IL_0105: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_034a: Expected O, but got Unknown if (!HasLiveAnchor) { Gate((StatApplyGate)4); return; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { Gate((StatApplyGate)5); return; } Stat[] array = Targets(stats); if (_statsHost != Current) { float[] array2 = new float[array.Length]; bool flag = false; bool flag2 = false; for (int i = 0; i < array.Length; i++) { if (array[i] != null) { flag2 = true; array2[i] = array[i].BaseValue; if (Mathf.Abs(array2[i]) > 0.001f) { flag = true; } } } if (flag2 && !flag) { if (_zeroHost != Current) { _zeroHost = Current; _zeroReads = 0; _zeroWarned = false; _zeroFirstReadAt = Time.unscaledTime; } _zeroReads++; if ((int)ZeroBaselinePolicy.Decide(_zeroReads, (double)(Time.unscaledTime - _zeroFirstReadAt), 8, 1.0) == 0) { if (!_zeroWarned) { _zeroWarned = true; CompanionRuntime.Log.LogWarning((object)(TagStats + " the anchor's stat baseline read ALL ZERO — its CharacterStats are not initialised yet. Skipping the species-stat apply and re-snapshotting next tick (a zero baseline would double every applied stat for this anchor's life). " + $"(warned once per ANCHOR; accepting the zero baseline after {8} reads " + $"AND {1.0:F0}s, then self-correcting if it ever populates)")); } Gate((StatApplyGate)6); return; } _acceptedZeroBaseline = true; CompanionRuntime.Log.LogWarning((object)(TagStats + $" the anchor's stat baseline has read ALL ZERO {_zeroReads} times " + $"in a row over {Time.unscaledTime - _zeroFirstReadAt:F1}s — treating it as a GENUINE zero baseline and " + "applying species stats against it (V88: the old guard skipped forever here, leaving the pet on ghost defenses for good). Still watching: if these BaseValues ever populate, the baseline is re-snapshotted and the stacks re-applied against the truth.")); } else { _acceptedZeroBaseline = false; } _statsHost = Current; _appliedWant = null; _statBaseline = array2; } else if (_acceptedZeroBaseline) { float[] array3 = new float[array.Length]; bool flag3 = false; for (int j = 0; j < array.Length; j++) { if (array[j] != null) { array3[j] = array[j].BaseValue; if (Mathf.Abs(array3[j]) > 0.001f) { flag3 = true; } } } if (ZeroBaselinePolicy.ShouldResnapshotAfterAccept(_acceptedZeroBaseline, flag3)) { _acceptedZeroBaseline = false; _statBaseline = array3; _appliedWant = null; CompanionRuntime.Log.LogWarning((object)(TagStats + " the anchor's stat baseline POPULATED after we accepted it as genuinely zero — re-snapshotting the baseline and re-applying the species stats against the real BaseValues (review F1: without this, every stat would have stayed doubled for this anchor's life).")); } } if (eff == null) { Gate((StatApplyGate)7); if (_appliedWant == null) { return; } foreach (Stat obj in array) { if (obj != null) { obj.RemoveStack("CK_SpeciesStats", false); } } _appliedWant = null; Gate((StatApplyGate)3); CompanionRuntime.Log.LogMessage((object)(TagStats + " species stats cleared from the anchor (ghost defaults restored).")); return; } float[] array4 = Wants(eff); if (_appliedWant != null && ApproxSame(array4, _appliedWant)) { Gate((StatApplyGate)2); return; } int num = 0; for (int l = 0; l < array.Length; l++) { if (array[l] != null) { float num2 = array4[l] - _statBaseline[l]; array[l].RemoveStack("CK_SpeciesStats", false); if (Mathf.Abs(num2) > 0.001f) { array[l].AddStack(new StatStack("CK_SpeciesStats", num2, (Tag[])null), false); num++; } } } _appliedWant = array4; Gate((StatApplyGate)1); string text = ((eff.StatusResistance > 95.001f) ? $" [statusRes CAPPED {eff.StatusResistance:F0} -> {95f:F0}: 100 is the engine's status-refusal sentinel]" : ""); CompanionRuntime.Log.LogMessage((object)$"{TagStats} species defense applied to the anchor ({num} stat stack(s)): {AttributeCapture.Describe(eff)}{text}"); } private static Stat[] Targets(CharacterStats st) { int num = 9; Stat[] array = (Stat[])(object)new Stat[2 * num + 4]; for (int i = 0; i < num; i++) { array[i] = ((st.m_damageResistance != null && i < st.m_damageResistance.Length) ? st.m_damageResistance[i] : null); array[num + i] = ((st.m_damageProtection != null && i < st.m_damageProtection.Length) ? st.m_damageProtection[i] : null); } array[2 * num] = st.m_allDamageProtection; array[2 * num + 1] = st.m_impactResistance; array[2 * num + 2] = st.m_barrierStat; array[2 * num + 3] = st.m_allStatusEffectBuildUpResistance; return array; } private static float[] Wants(CreatureAttributes eff) { int num = 9; float[] array = new float[2 * num + 4]; for (int i = 0; i < num; i++) { array[i] = eff.Resist[i]; array[num + i] = eff.Protection[i]; } array[2 * num] = eff.ProtectionAll; array[2 * num + 1] = eff.ImpactResistance; array[2 * num + 2] = eff.Barrier; array[2 * num + 3] = StatusResistPolicy.Cap(eff.StatusResistance); return array; } private static bool ApproxSame(float[] a, float[] b) { if (a.Length != b.Length) { return false; } for (int i = 0; i < a.Length; i++) { if (Mathf.Abs(a[i] - b[i]) > 0.001f) { return false; } } return true; } public void DumpCreatureStats() { //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Invalid comparison between Unknown and I4 if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null) { CompanionRuntime.Log.LogMessage((object)(TagStats + " no live anchor to dump.")); return; } if (_appliedWant == null || _statsHost != Current) { bool flag = !PhotonNetwork.isNonMasterClientInRoom; CompanionRuntime.Log.LogMessage((object)(TagStats + " no species stats applied to this anchor (ghost defaults). gate=" + PendingApplyPolicy.Describe(LastGate) + ((!VitalsPending) ? "" : (flag ? "; vitals also PENDING (max health not established on this anchor)" : "; vitals proxied to the master (guest box — not locally establishable, not pending)")) + (((int)LastGate == 6) ? $"; zero-baseline reads={_zeroReads}" : ""))); return; } string[] array = new string[2] { "resist", "protection" }; Stat[] array2 = Targets(Current.Stats); int num = 9; for (int i = 0; i < array2.Length; i++) { if (array2[i] != null && (_appliedWant[i] != 0f || _statBaseline[i] != 0f)) { string text = ((i < num) ? $"{array[0]}[{i}]" : ((i < 2 * num) ? $"{array[1]}[{i - num}]" : ((i == 2 * num) ? "protAll" : ((i == 2 * num + 1) ? "impactRes" : ((i == 2 * num + 2) ? "barrier" : "statusRes"))))); CompanionRuntime.Log.LogMessage((object)$"{TagStats} {text}: baseline={_statBaseline[i]:F1} + stack={_appliedWant[i] - _statBaseline[i]:F1} -> live={array2[i].CurrentValue:F1}"); } } } public void EnableHealthPersistence(Func enabled) { _healthEnabled = enabled; } public void SeedHealthFraction(float f) { _healthFraction = Mathf.Clamp01(f); } public void ResetHealthFraction() { _healthFraction = 1f; } public void CaptureHealthFraction() { if (!HealthPersistenceOn || !HasLiveAnchor || (Object)(object)_vitalsHost != (Object)(object)Current) { return; } CharacterStats stats = Current.Stats; if (!((Object)(object)stats == (Object)null)) { float maxHealth = stats.MaxHealth; if (!(maxHealth <= 0f)) { _healthFraction = Mathf.Clamp01(stats.CurrentHealth / maxHealth); } } } public void ApplyVitals(float maxHealth) { if (maxHealth <= 0f) { return; } if (!HasLiveAnchor) { VitalsPending = true; return; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { VitalsPending = true; return; } VitalsPending = false; bool flag = (Object)(object)_vitalsHost != (Object)(object)Current; if (!flag && Mathf.Approximately(stats.BaseMaxHealth, maxHealth)) { return; } stats.BaseMaxHealth = maxHealth; if (flag) { _vitalsHost = Current; float currentHealth = stats.CurrentHealth; float num = (HealthPersistenceOn ? _healthFraction : 1f); float num2 = AnchorVitals.RestoreHealth((double)num, (double)maxHealth); stats.SetHealth(num2); if (!Mathf.Approximately(currentHealth, num2)) { CompanionRuntime.Log.LogMessage((object)($"{TagStats} fresh anchor vitals: {currentHealth:F0} -> {num2:F0} " + $"(restore {num * 100f:F0}% of max {maxHealth:F0}; bug-23 first-apply).")); } } else if (stats.CurrentHealth > maxHealth) { stats.SetHealth(maxHealth); } } public bool RestoreLiveHealth(float frac) { if (!HealthPersistenceOn || !HasLiveAnchor) { return false; } if ((Object)(object)_vitalsHost != (Object)(object)Current) { return false; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return false; } float maxHealth = stats.MaxHealth; if (maxHealth <= 0f) { return false; } float num = AnchorVitals.RestoreHealth((double)frac, (double)maxHealth); float currentHealth = stats.CurrentHealth; _healthFraction = Mathf.Clamp01(frac); if (currentHealth <= num + 0.5f) { return false; } stats.SetHealth(num); CompanionRuntime.Log.LogMessage((object)($"{TagAnchor} restored live HP {currentHealth:F0} -> {num:F0} " + $"(carry {frac * 100f:F0}% of max {maxHealth:F0}; Bug 48 in-session persisting-anchor over-heal).")); return true; } public bool ApplyTemperatureDrain(float amount) { if (!HasLiveAnchor || amount <= 0f) { return false; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return false; } float currentHealth = stats.CurrentHealth; float num = Mathf.Max(currentHealth - amount, 1f); if (num < currentHealth) { stats.SetHealth(num); } return currentHealth - amount < 1f; } public string HealthSummary(bool critFired) { if (!HasLiveAnchor) { return "no live anchor."; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return "no live anchor stats."; } float num = ((stats.MaxHealth > 0f) ? (stats.CurrentHealth / stats.MaxHealth * 100f) : 0f); return $"hp={stats.CurrentHealth:F0}/{stats.MaxHealth:F0} ({num:F0}%) critFired={critFired}"; } public bool Heal() { if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null) { return false; } Current.Stats.SetHealth(Current.Stats.ActiveMaxHealth); return true; } public bool HealAmount(float amount, out bool reArmCrit, bool quiet = false) { reArmCrit = false; if (!HasLiveAnchor || amount <= 0f || (Object)(object)Current.Stats == (Object)null) { return false; } CharacterStats stats = Current.Stats; float currentHealth = stats.CurrentHealth; stats.SetHealth(Mathf.Min(currentHealth + amount, stats.ActiveMaxHealth)); if (stats.CurrentHealth > stats.ActiveMaxHealth * 0.5f) { reArmCrit = true; } if (!quiet && stats.CurrentHealth - currentHealth > 0.005f) { CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} fed-heal +{stats.CurrentHealth - currentHealth:F0} ({currentHealth:F0} -> {stats.CurrentHealth:F0}/{stats.ActiveMaxHealth:F0})."); } return true; } public bool SetHealth(float value, out bool reArmCrit) { reArmCrit = false; if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null) { return false; } CharacterStats stats = Current.Stats; float currentHealth = stats.CurrentHealth; stats.SetHealth(Mathf.Clamp(value, 1f, stats.ActiveMaxHealth)); if (stats.CurrentHealth > stats.ActiveMaxHealth * 0.5f) { reArmCrit = true; } CompanionRuntime.Log.LogMessage((object)$"{TagAnchor} dev set-health {currentHealth:F0} -> {stats.CurrentHealth:F0}/{stats.ActiveMaxHealth:F0}."); return true; } public bool TryGetHealth(out float current, out float max) { if (!HasLiveAnchor) { current = 0f; max = 0f; return false; } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { current = 0f; max = 0f; return false; } current = stats.CurrentHealth; max = stats.MaxHealth; return true; } } internal sealed class AnchorTargeting { private readonly Func _current; private readonly Func _ai; private readonly Func _cfg; private Character _assertedLock; private float _unifyLogAt; private Character _unifySkipLogged; private Character Current => _current(); private CharacterAI AI => _ai(); private ICompanionSettings Cfg => _cfg(); private bool HasLiveAnchor { get { if ((Object)(object)Current != (Object)null) { return Current.Alive; } return false; } } private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg); private string TagGlue => CompanionRuntime.Tag("GLUE", Cfg); internal Character AssertedLock { get { return _assertedLock; } set { _assertedLock = value; } } internal AnchorTargeting(Func current, Func ai, Func cfg) { _current = current; _ai = ai; _cfg = cfg; } internal void ForgetSkipMarker() { _unifySkipLogged = null; } internal static bool IsProtectedFrom(Character attacker, Character target) { return TargetableOverrides.IsBlocked(attacker, target); } internal static Character LockedEnemy(CharacterAI ai) { Character val = (((Object)(object)ai != (Object)null && (Object)(object)ai.TargetingSystem != (Object)null) ? ai.TargetingSystem.LockedCharacter : null); if (!((Object)(object)val != (Object)null) || !val.Alive) { return null; } return val; } internal void Calm() { _assertedLock = null; _unifySkipLogged = null; if (HasLiveAnchor) { CalmAnchor(AI); } } internal void CalmAnchor(CharacterAI ai) { try { AggroTools.Calm(ai); DictionaryExt val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.Characters : null); if (val == null) { return; } for (int i = 0; i < val.Count; i++) { Character val2 = val.Values[i]; if (!((Object)(object)val2 == (Object)null) && val2.IsAI && val2.Alive && !((Object)(object)val2 == (Object)(object)Current)) { CharacterAI component = ((Component)val2).GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.TargetingSystem == (Object)null) && !((Object)(object)component.TargetingSystem.LockedCharacter != (Object)(object)Current)) { component.TargetingSystem.SetLockingPoint((LockingPoint)null); CompanionRuntime.Log.LogMessage((object)(TagAnchor + " released '" + val2.Name + "'s reciprocal lock on the anchor.")); } } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagAnchor + " calm failed: " + ex.Message)); } } internal void PinTo(Vector3 puppetPos) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (HasLiveAnchor && !(Vector3.Distance(((Component)Current).transform.position, puppetPos) <= 4f)) { Vector3 pos; Vector3 val = (NavProbe.SampleAtFeet(puppetPos, 1.5f, out pos) ? pos : puppetPos); Current.Teleport(val, Quaternion.identity); CompanionRuntime.Log.LogMessage((object)(TagAnchor + " combat pin: anchor -> puppet at " + ((Vector3)(ref val)).ToString("F1") + " (bug-3 fix).")); } } internal void UnifyLock(Character target) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) CharacterAI aI = AI; bool flag = (Object)(object)aI != (Object)null && (Object)(object)aI.TargetingSystem != (Object)null && (Object)(object)aI.TargetingSystem.LockedCharacter == (Object)(object)target; if (!AnchorGlue.ShouldAssertLock(Cfg.UnifyTargets, (Object)(object)target != (Object)null && target.Alive, HasLiveAnchor && (Object)(object)aI != (Object)null, flag)) { if (flag && (Object)(object)target != (Object)null && (Object)(object)_unifySkipLogged != (Object)(object)target) { _unifySkipLogged = target; CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor already locked onto '" + target.Name + "' by itself — no unify write needed.")); } return; } if (Vector3.Distance(((Component)Current).transform.position, ((Component)target).transform.position) > Cfg.CombatLeashDistance) { if ((Object)(object)_unifySkipLogged != (Object)(object)target) { _unifySkipLogged = target; CompanionRuntime.Log.LogMessage((object)(TagGlue + " unify refused: '" + target.Name + "' is beyond the combat leash — stale, not a live fight (session-27 fix).")); } return; } if (IsProtectedFrom(Current, target)) { if ((Object)(object)_unifySkipLogged != (Object)(object)target) { _unifySkipLogged = target; CompanionRuntime.Log.LogMessage((object)(TagGlue + " unify refused: '" + target.Name + "' is PROTECTED (AggroKit override) — the anchor may not target it.")); } return; } try { if ((Object)(object)target.LockingPoint == (Object)null) { if ((Object)(object)_unifySkipLogged != (Object)(object)target) { _unifySkipLogged = target; CompanionRuntime.Log.LogWarning((object)(TagGlue + " unify skipped: '" + target.Name + "' has no LockingPoint — the anchor cannot lock it.")); } } else if (!((Object)(object)aI.TargetingSystem == (Object)null)) { AggroTools.ForceTarget(aI, target); _assertedLock = target; if (Time.time - _unifyLogAt > 2f) { _unifyLogAt = Time.time; CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor lock unified onto '" + target.Name + "' (pet's combat target).")); } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagGlue + " lock unify failed: " + ex.Message)); } } } internal sealed class AnchorWeld { private readonly Func _current; private readonly Func _ai; private readonly Func _cfg; private readonly AnchorPhysics _physics; private bool _glueWasEngaged; private bool _agentWarpNoted; private float _glueJumpLog; private Character Current => _current(); private ICompanionSettings Cfg => _cfg(); private bool HasLiveAnchor { get { if ((Object)(object)Current != (Object)null) { return Current.Alive; } return false; } } private string TagGlue => CompanionRuntime.Tag("GLUE", Cfg); internal AnchorWeld(Func current, Func ai, Func cfg, AnchorPhysics physics) { _current = current; _ai = ai; _cfg = cfg; _physics = physics; } internal void ResetWarpNote() { _agentWarpNoted = false; } internal void GlueTick(CompanionBody body) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_0123: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a8: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)body == (Object)null)) { _physics.Sync(); bool hasLiveAnchor = HasLiveAnchor; CharacterAI val = (hasLiveAnchor ? _ai() : null); bool flag = (Object)(object)body.CombatTarget != (Object)null || (hasLiveAnchor && (Object)(object)AnchorTargeting.LockedEnemy(val) != (Object)null); bool flag2 = AnchorGlue.Engaged(Cfg.GlueMode, true, flag); if (flag2 != _glueWasEngaged) { _glueWasEngaged = flag2; CompanionRuntime.Log.LogMessage((object)string.Format("{0} {1} (mode={2}, combat={3}).", TagGlue, flag2 ? "engaged" : "released", Cfg.GlueMode, flag)); } Vector3 position = ((Component)body).transform.position; float num = (hasLiveAnchor ? Vector3.Distance(((Component)Current).transform.position, position) : 0f); AnchorGlueAction val2 = AnchorGlue.Decide(Cfg.GlueMode, true, flag, hasLiveAnchor, !PhotonNetwork.isNonMasterClientInRoom, CompanionRuntime.IsSanePosition(position) && (!hasLiveAnchor || CompanionRuntime.IsSanePosition(((Component)Current).transform.position)), (Object)(object)val != (Object)null && (Object)(object)val.NavMeshAgent != (Object)null && val.NavMeshAgent.updatePosition, num); if ((int)val2 != 0 && hasLiveAnchor && !((Object)(object)Current == (Object)null)) { Vector3 facingDir = body.FacingDir; facingDir.y = 0f; float num2 = default(float); float num3 = default(float); float num4 = default(float); AnchorGlue.WeldPosition(position.x, position.y, position.z, facingDir.x, facingDir.z, Cfg.GlueOffsetBehind, ref num2, ref num3, ref num4); Apply(new Vector3(num2, num3, num4), facingDir, val2, num); } } } internal void Apply(Vector3 pos, Vector3 facingFlat, AnchorGlueAction act, float sep, bool logJump = true) { //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected I4, but got Unknown //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Current == (Object)null) { return; } CharacterAI val = _ai(); Quaternion val2 = ((((Vector3)(ref facingFlat)).sqrMagnitude > 1E-06f) ? Quaternion.LookRotation(((Vector3)(ref facingFlat)).normalized, Vector3.up) : ((Component)Current).transform.rotation); switch (act - 1) { case 0: ((Component)Current).transform.SetPositionAndRotation(pos, val2); _agentWarpNoted = false; break; case 1: try { Current.Internal_SendTeleport(pos, val2); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(TagGlue + " Internal_SendTeleport failed (" + ex.Message + ") — plain write instead.")); ((Component)Current).transform.SetPositionAndRotation(pos, val2); } _agentWarpNoted = false; _physics.Sync(); if (logJump && Time.time - _glueJumpLog > 2f) { _glueJumpLog = Time.time; CompanionRuntime.Log.LogMessage((object)$"{TagGlue} closed a {sep:F1}m gap (collider-safe local move, no RPC)."); } break; case 2: if ((Object)(object)val != (Object)null && (Object)(object)val.NavMeshAgent != (Object)null) { val.NavMeshAgent.Warp(pos); if (!_agentWarpNoted) { _agentWarpNoted = true; CompanionRuntime.Log.LogMessage((object)(TagGlue + " anchor AI is in far/inactive mode (agent drives the transform) — warping the agent instead of the transform.")); } } else if (!_agentWarpNoted) { _agentWarpNoted = true; CompanionRuntime.Log.LogWarning((object)(TagGlue + " AgentWarp wanted but the anchor has no CharacterAI/NavMeshAgent — nothing warped (weld skipped this frame).")); } break; } } } public static class AttributeCapture { private static readonly string[] TypeNames = new string[9] { "Phys", "Ethereal", "Decay", "Electric", "Frost", "Fire", "Dark", "Light", "Raw" }; public static CreatureAttributes From(Character src) { return From(src, null); } public static CreatureAttributes From(Character src, CompanionHost host) { if ((Object)(object)src == (Object)null) { return null; } ModLog val = host?.Log ?? CompanionRuntime.Log; string text = CompanionRuntime.Tag("STATS", host?.Settings); try { CreatureAttributes val2 = Read(src); if (val2 != null) { float num = CoopHealthDivisor(src); if (num != 1f) { val2.MaxHealth = CoopCaptureNormalizer.Normalize(val2.MaxHealth, num); } val.LogMessage((object)(text + " captured '" + src.Name + "': " + Describe(val2) + ((num != 1f) ? $" coopDiv={num:F2} (guest capture normalized to the solo baseline — MP-CAPNORM)" : ""))); } return val2; } catch (Exception ex) { val.LogWarning((object)(text + " capture from '" + src.Name + "' failed (" + ex.Message + ") — config-stat fallback applies.")); return null; } } private static CreatureAttributes Read(Character src) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Expected O, but got Unknown CharacterStats stats = src.Stats; if ((Object)(object)stats == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[STATS] '" + src.Name + "' has no CharacterStats — nothing to capture.")); return null; } float num = stats.MovementSpeed; if (num < 0.1f) { num = 1f; } CreatureAttributes val = new CreatureAttributes { MaxHealth = stats.BaseMaxHealth, MoveSpeed = src.Speed * num, ImpactResistance = BaseOf(stats.m_impactResistance), Barrier = BaseOf(stats.m_barrierStat), ProtectionAll = BaseOf(stats.m_allDamageProtection), StatusResistance = BaseOf(stats.m_allStatusEffectBuildUpResistance) }; int num2 = 9; for (int i = 0; i < num2; i++) { if (stats.m_damageResistance != null && i < stats.m_damageResistance.Length) { val.Resist[i] = BaseOf(stats.m_damageResistance[i]); } if (stats.m_damageProtection != null && i < stats.m_damageProtection.Length) { val.Protection[i] = BaseOf(stats.m_damageProtection[i]); } } if (!CaptureWeaponDamage(src, stats, val) && !CaptureHitboxDamage(src, val)) { CompanionRuntime.Log.LogMessage((object)("[STATS] '" + src.Name + "' has no readable weapon or hitbox damage — the pet keeps [Combat] AttackDamage.")); } return val; } private static bool CaptureWeaponDamage(Character src, CharacterStats st, CreatureAttributes a) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Expected I4, but got Unknown Weapon currentWeapon = src.CurrentWeapon; if ((Object)(object)currentWeapon == (Object)null || currentWeapon.Damage == null || currentWeapon.Damage.Count == 0) { return false; } foreach (DamageType item in currentWeapon.Damage.List) { int num = (int)item.Type; if (num >= 0 && num < 9 && !(item.Damage <= 0f)) { a.Damage[num] += item.Damage * DealerBonus(st, num); } } a.Impact = currentWeapon.Impact; return a.HasDamage; } private static bool CaptureHitboxDamage(Character src, CreatureAttributes a) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Expected I4, but got Unknown PunctualDamage val = null; float num = 0f; PunctualDamage[] componentsInChildren = ((Component)src).GetComponentsInChildren(true); foreach (PunctualDamage val2 in componentsInChildren) { float num2 = TotalOf(val2.Damages); if (num2 <= 0f) { num2 = TotalOf(val2.DamagesAI); } if (num2 > num) { num = num2; val = val2; } } if ((Object)(object)val == (Object)null) { return false; } DamageType[] array = ((TotalOf(val.Damages) > 0f) ? val.Damages : val.DamagesAI); CharacterStats stats = src.Stats; DamageType[] array2 = array; foreach (DamageType val3 in array2) { int num3 = (int)val3.Type; if (num3 >= 0 && num3 < 9 && !(val3.Damage <= 0f)) { a.Damage[num3] += val3.Damage * DealerBonus(stats, num3); } } a.Impact = val.Knockback; return a.HasDamage; } private static float DealerBonus(CharacterStats st, int typeIndex) { if ((Object)(object)st == (Object)null || st.m_damageTypesModifier == null || typeIndex >= st.m_damageTypesModifier.Length) { return 1f; } Stat val = st.m_damageTypesModifier[typeIndex]; if (val == null) { return 1f; } float num = val.CurrentValue; if (num < 0.01f) { num = val.BaseValue; } if (!(num < 0.01f)) { return num; } return 1f; } private static float CoopHealthDivisor(Character src) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) try { if (!PhotonNetwork.isNonMasterClientInRoom) { return 1f; } CharacterStats val = (((Object)(object)src != (Object)null) ? src.Stats : null); CoopStats val2 = (((Object)(object)val != (Object)null) ? val.CoopStats : null); if ((Object)(object)val2 == (Object)null || val2.StatData == null) { return 1f; } int num = (((Object)(object)Global.Lobby != (Object)null && Global.Lobby.PlayersInLobby != null) ? Global.Lobby.PlayersInLobby.Count : 0); CoopStatData[] statData = val2.StatData; foreach (CoopStatData val3 in statData) { object obj; if (val3 == null) { obj = null; } else { TagSourceSelector stat = val3.Stat; obj = ((stat != null) ? stat.Tag.TagName : null); } string text = (string)obj; if (text != null && text.Contains("MaxHealth")) { return CoopCaptureNormalizer.Divisor(true, num, val3.Value); } } return 1f; } catch { return 1f; } } private static float BaseOf(Stat s) { return s?.BaseValue ?? 0f; } private static float TotalOf(DamageType[] damages) { if (damages == null) { return 0f; } float num = 0f; foreach (DamageType val in damages) { num += val.Damage; } return num; } public static string Describe(CreatureAttributes a) { if (a == null) { return "none"; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"hp={a.MaxHealth:F0} spd={a.MoveSpeed:F2} dmg=[{PerType(a.Damage)}] impact={a.Impact:F1}"); stringBuilder.Append(" res=[" + PerType(a.Resist) + "] prot=[" + PerType(a.Protection) + "]"); if (a.ProtectionAll != 0f) { stringBuilder.Append($" protAll={a.ProtectionAll:F0}"); } if (a.ImpactResistance != 0f) { stringBuilder.Append($" impactRes={a.ImpactResistance:F0}"); } if (a.Barrier != 0f) { stringBuilder.Append($" barrier={a.Barrier:F0}"); } if (a.StatusResistance != 0f) { stringBuilder.Append($" statusRes={a.StatusResistance:F0}"); } return stringBuilder.ToString(); } private static string PerType(float[] values) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < values.Length && i < TypeNames.Length; i++) { if (values[i] != 0f) { if (stringBuilder.Length > 0) { stringBuilder.Append(' '); } stringBuilder.Append(TypeNames[i]).Append(':').Append(values[i].ToString("F1")); } } if (stringBuilder.Length <= 0) { return "none"; } return stringBuilder.ToString(); } } public sealed class BodyAcquisitionSpec { public CompanionHost Host; public MonoBehaviour Runner; public string Noun = "companion"; public Func Live; public Func Player; public Func SpeciesId; public Func IsGhost; public Action OnBodyBuilt; public Func TryBuildNearby; public bool UseTemplateCache; public bool UseDonorHarvest; public bool UseGhostStandIn; public Func RangedCaptureFilter; public Func RangedCaptureSkillId; public float HarvestRetryCooldownSeconds = 60f; } public sealed class BodyAcquisition { private readonly BodyAcquisitionSpec _spec; public bool Active { get; private set; } private ModLog Log => _spec.Host.Log; public BodyAcquisition(BodyAcquisitionSpec spec) { if (spec == null) { throw new ArgumentNullException("spec"); } if (spec.Host == null || (Object)(object)spec.Runner == (Object)null || spec.Live == null || spec.Player == null || spec.SpeciesId == null || spec.OnBodyBuilt == null) { throw new ArgumentException("BodyAcquisitionSpec: Host/Runner/Live/Player/SpeciesId/OnBodyBuilt are required."); } _spec = spec; } public void Kick() { if (!Active && _spec.Live() != null) { _spec.Runner.StartCoroutine(Run()); } } private bool GhostNow() { if (_spec.IsGhost != null) { return _spec.IsGhost(); } return false; } private string Filter(string species) { return _spec.RangedCaptureFilter?.Invoke(species); } private int SkillId(string species) { return _spec.RangedCaptureSkillId?.Invoke(species) ?? 0; } public IEnumerator Run() { Companion c = _spec.Live(); if (c == null) { yield break; } Active = true; bool triedStandIn = false; float voidHoldLogAt = -999f; float expeditionHoldLogAt = -999f; float expeditionHoldSince = -1f; HarvestPacing pacing = new HarvestPacing(_spec.HarvestRetryCooldownSeconds, 9, 3); try { while (_spec.Live() == c && ReformFlow.WantsBodyUpgrade((Object)(object)c.Body != (Object)null, GhostNow())) { if (ExpeditionHarvest.InProgress) { if (expeditionHoldSince < 0f) { expeditionHoldSince = Time.unscaledTime; } if (Time.unscaledTime - expeditionHoldLogAt > 15f) { expeditionHoldLogAt = Time.unscaledTime; float num = Time.unscaledTime - expeditionHoldSince; Log.LogMessage((object)("[PERSIST] re-form ladder holding: an expedition owns the scene " + $"pipeline (elapsed={num:F0}s) — no body built this pass." + ((num > 120f) ? " This is far longer than a harvest takes; if it does not clear, the expedition flag is WEDGED — run the 'expeditionreset' verb." : ""))); } yield return (object)new WaitForSeconds(1.5f); continue; } expeditionHoldSince = -1f; Character player = _spec.Player(); if ((Object)(object)player != (Object)null && !CompanionRuntime.IsSanePosition(((Component)player).transform.position)) { if (Time.unscaledTime - voidHoldLogAt > 10f) { voidHoldLogAt = Time.unscaledTime; Log.LogMessage((object)("[PERSIST] re-form ladder holding: owner reads as void/staging " + $"(floor y={-3000f:F0}) — no body built this pass.")); } yield return (object)new WaitForSeconds(1.5f); continue; } if ((Object)(object)player != (Object)null) { if (_spec.TryBuildNearby != null) { CompanionBody companionBody = _spec.TryBuildNearby(player); if ((Object)(object)companionBody != (Object)null) { _spec.OnBodyBuilt.Invoke(companionBody, false); break; } } string species = _spec.SpeciesId(); if (_spec.UseTemplateCache && BodyTemplateCache.TryResolve(species, out var template)) { CompanionBody companionBody2 = BodyTemplateCache.PuppetFrom(template, player, Filter(species), SkillId(species)); if ((Object)(object)companionBody2 != (Object)null) { Log.LogMessage((object)("[PERSIST] re-forming '" + species + "' from the session body-template cache.")); _spec.OnBodyBuilt.Invoke(companionBody2, false); break; } } if (_spec.UseDonorHarvest) { Scene activeScene = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene)).name; string lastScene = pacing.LastScene; if (pacing.TryEarlyRearm(name, Time.time)) { Log.LogMessage((object)("[PERSIST] harvest rung re-armed early: scene changed '" + lastScene + "' → '" + name + "' (fresh region-aware donor order).")); } bool flag = pacing.Allowed(DonorHarvest.CyclesThisSession, name); bool flag2 = (Object)(object)c.Body == (Object)null || GhostNow(); if (pacing.TryPark(flag, flag2, Time.time)) { Log.LogMessage((object)("[PERSIST] harvest rung PARKED for '" + species + "' — " + ((DonorHarvest.CyclesThisSession >= pacing.MaxCyclesPerSession) ? $"session additive-cycle budget spent ({DonorHarvest.CyclesThisSession} cycles; the LightProbes crash ceiling is ~11-17). A relaunch resets it." : "3 failed retries this scene; a scene change re-arms it."))); } if (pacing.ReadyToAttempt(flag, flag2, Time.time) && DonorHarvest.TryGetDonorScenes(species, out var sceneNames, out var searchTerm)) { if (pacing.HasAttempted) { Log.LogMessage((object)("[PERSIST] harvest rung re-armed (" + ((name != pacing.LastScene) ? "scene change" : $"{_spec.HarvestRetryCooldownSeconds:F0}s cooldown") + ") — retrying the donor chain for '" + species + "'" + (GhostNow() ? " (upgrading the ghost stand-in)" : "") + ".")); } pacing.NoteAttempt(name, Time.time); CompanionBody harvested = null; Log.LogMessage((object)("[PERSIST] no wild '" + species + "' nearby — harvesting a body (" + (PhotonNetwork.isNonMasterClientInRoom ? "guest-local" : "master") + ", " + $"{sceneNames.Count} donor candidate(s), region-aware order).")); yield return DonorHarvest.HarvestChain(sceneNames, searchTerm, player, delegate(CompanionBody b) { harvested = b; }, Filter(species), SkillId(species)); if (_spec.Live() != c) { if ((Object)(object)harvested != (Object)null) { Object.Destroy((Object)(object)((Component)harvested).gameObject); } Log.LogMessage((object)("[LIFECYCLE] " + _spec.Noun + " despawned during re-form: destroying in-flight body." + (((Object)(object)harvested != (Object)null) ? $" (body#{harvested.BodyId})" : ""))); break; } if ((Object)(object)harvested != (Object)null) { _spec.OnBodyBuilt.Invoke(harvested, false); break; } Log.LogMessage((object)("[PERSIST] harvest failed — " + (GhostNow() ? "keeping the ghost stand-in" : "falling back to the ghost stand-in") + "; " + $"the rung retries in {_spec.HarvestRetryCooldownSeconds:F0}s or on a scene change (F2).")); } } if (_spec.UseGhostStandIn && ReformFlow.StandInGate((Object)(object)c.Body != (Object)null, triedStandIn)) { triedStandIn = true; Character ghost = BodyFactory.SpawnGhostActive(player); if ((Object)(object)ghost != (Object)null) { float t0 = Time.time; while ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost) && Time.time - t0 < 4f) { BodyFactory.NudgeGhostActive(ghost); yield return null; } if ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost)) { BodyFactory.ForceGhostVisuals(ghost); } bool flag3 = BodyFactory.GhostVisualReady(ghost); Log.LogMessage((object)$"[PERSIST] ghost visuals ready={flag3} after {Time.time - t0:F1}s — forming spectral stand-in."); CompanionBody companionBody3 = BodyFactory.FinishGhostPuppet(ghost, player); if (_spec.Live() != c) { if ((Object)(object)companionBody3 != (Object)null) { Object.Destroy((Object)(object)((Component)companionBody3).gameObject); } Log.LogMessage((object)("[LIFECYCLE] " + _spec.Noun + " despawned during re-form: destroying in-flight body." + (((Object)(object)companionBody3 != (Object)null) ? $" (body#{companionBody3.BodyId})" : ""))); break; } if ((Object)(object)companionBody3 != (Object)null) { _spec.OnBodyBuilt.Invoke(companionBody3, true); } } else { Log.LogMessage((object)("[PERSIST] no spawnable stand-in body — " + _spec.Noun + " stays bodiless (systems tick) until you pass a wild '" + species + "'.")); } } } yield return (object)new WaitForSeconds(1.5f); } } finally { BodyAcquisition bodyAcquisition = this; bodyAcquisition.Active = false; try { Companion companion = bodyAcquisition._spec.Live(); if (companion != null && companion == c && (Object)(object)c.Body == (Object)null) { bodyAcquisition.Log.LogMessage((object)"[PERSIST] re-form ladder ended with no body (exception or abort) — the next Kick()/recall restarts it."); } } catch { } } } } public static class BodyCensus { internal sealed class Entry { public CompanionBody Body; public float BornAt; public bool EverClaimed; public float UnclaimedSince; public bool WarnedOrphan; } private static readonly List s_entries = new List(); private static readonly List>> s_claims = new List>>(); private static int s_consumerSources; internal static int ConsumerSourceCount => s_consumerSources; internal static int SourceCount => s_claims.Count; public static int LiveCount => s_entries.Count; internal static void Register(CompanionBody b) { if ((Object)(object)b == (Object)null) { return; } for (int i = 0; i < s_entries.Count; i++) { if (s_entries[i].Body == b) { return; } } s_entries.Add(new Entry { Body = b, BornAt = Time.unscaledTime, UnclaimedSince = Time.unscaledTime }); } internal static void Unregister(CompanionBody b) { for (int num = s_entries.Count - 1; num >= 0; num--) { if (s_entries[num].Body == b) { s_entries.RemoveAt(num); } } } internal static void PurgeDead() { for (int num = s_entries.Count - 1; num >= 0; num--) { if ((Object)(object)s_entries[num].Body == (Object)null) { s_entries.RemoveAt(num); } } } public static void RegisterClaimSource(Func> source) { RegisterClaimSource(source, isConsumer: true); } public static void RegisterClaimSource(Func> source, bool isConsumer) { if (source != null && !s_claims.Contains(source)) { s_claims.Add(source); if (isConsumer) { s_consumerSources++; } } } internal static void RegisterInternalClaimSource(Func> source) { RegisterClaimSource(source, isConsumer: false); } internal static List Snapshot() { return new List(s_entries); } internal static HashSet ClaimedNow() { HashSet hashSet = new HashSet(); for (int i = 0; i < s_claims.Count; i++) { try { foreach (CompanionBody item in s_claims[i]() ?? Enumerable.Empty()) { if ((Object)(object)item != (Object)null) { hashSet.Add(item); } } } catch (Exception ex) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[CENSUS] claim source threw (skipped this sweep): " + ex.Message)); } } } return hashSet; } public static string Dump() { //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_01f6: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append($"[CENSUS] {s_entries.Count} live CompanionBody instance(s), {s_claims.Count} claim source(s) " + $"({s_consumerSources} consumer, {s_claims.Count - s_consumerSources} internal)."); if (s_consumerSources == 0) { stringBuilder.Append("\n[CENSUS] NO CONSUMER claim source — the reaper is disabled. A consumer plugin (Beastwhispering/Hireling) older than this CompanionKit is the usual cause."); } HashSet hashSet = ClaimedNow(); foreach (Entry item in Snapshot()) { CompanionBody body = item.Body; if (!((Object)(object)body == (Object)null)) { NavMeshAgent agent = body._agent; string text; Vector3 val; if ((Object)(object)agent == (Object)null) { text = "agent=none"; } else if (!((Behaviour)agent).enabled) { text = "agent=disabled"; } else if (!agent.isOnNavMesh) { text = "agent=off-mesh"; } else { val = agent.nextPosition; text = string.Format("agent=on next={0} updPos={1}", ((Vector3)(ref val)).ToString("F1"), agent.updatePosition); } string text2 = (body.StuckDetection ? string.Format(" stuck={0:F0}s{1}", body.StuckSeconds, body.StuckTripped ? " TRIPPED" : "") : ""); string text3 = ((body.Contamination > 0) ? $" contaminated={body.Contamination}" : ""); string text4 = $"\n[CENSUS] body#{body.BodyId} '{body.SpeciesId}' origin={body.Origin} age={Time.unscaledTime - item.BornAt:F0}s "; object[] obj = new object[4] { hashSet.Contains(body), item.EverClaimed, null, null }; val = ((Component)body).transform.position; obj[2] = ((Vector3)(ref val)).ToString("F1"); obj[3] = text; stringBuilder.Append(text4 + string.Format("claimed={0} everClaimed={1} pos={2} {3}", obj) + text2 + text3); } } return stringBuilder.ToString(); } } internal static class BodyDiagnostics { internal static IEnumerator DriftScan(CompanionBody body) { Transform root = ((Component)body).transform; Transform[] all = ((Component)body).GetComponentsInChildren(true); Vector3[] start = all.Select((Transform t) => root.InverseTransformPoint(t.position)).ToArray(); float[] maxd = new float[all.Length]; for (int f = 0; f < 60; f++) { for (int num = 0; num < all.Length; num++) { float num2 = Vector3.Distance(root.InverseTransformPoint(all[num].position), start[num]); if (num2 > maxd[num]) { maxd[num] = num2; } } yield return null; } Dictionary dictionary = new Dictionary(); for (int num3 = 0; num3 < all.Length; num3++) { dictionary[all[num3]] = num3; } int num4 = (from i in Enumerable.Range(0, all.Length) orderby maxd[i] descending select i).First(); CompanionRuntime.Log.LogMessage((object)"[DRIFT] ancestry of top drifter (the root-motion bone = where drift jumps up):"); Transform val = all[num4]; while ((Object)(object)val != (Object)null) { int value; float num5 = (dictionary.TryGetValue(val, out value) ? maxd[value] : (-1f)); CompanionRuntime.Log.LogMessage((object)$"[DRIFT] '{((Object)val).name}' drift={num5:F2}m"); if (!((Object)(object)val == (Object)(object)root)) { val = val.parent; continue; } break; } } internal static IEnumerator PosDumpAll() { List list = BodyCensus.Snapshot(); CompanionRuntime.Log.LogMessage((object)$"[POS] census-wide posdump: {list.Count} live body(ies)."); List runs = new List(); foreach (BodyCensus.Entry item in list) { if ((Object)(object)item.Body != (Object)null) { runs.Add(PosDump(item.Body)); } } for (int f = 0; f < 90; f++) { for (int num = runs.Count - 1; num >= 0; num--) { if (!runs[num].MoveNext()) { runs.RemoveAt(num); } } if (runs.Count == 0) { break; } yield return null; } } internal static IEnumerator PosDump(CompanionBody body) { for (int i = 0; i < 90; i++) { if ((Object)(object)body == (Object)null) { break; } Vector3 position = ((Component)body).transform.position; bool flag = (Object)(object)body._agent != (Object)null && ((Behaviour)body._agent).enabled && body._agent.isOnNavMesh; float value; string on; string text = ((body._loco != null && body._loco.ReadForward(out value, out on)) ? $"{value:F1}@{on}" : "n/a"); string text2 = ((body._slope != null) ? body._slope.Describe() : "none"); if (flag) { Vector3 nextPosition = body._agent.nextPosition; ModLog log = CompanionRuntime.Log; object[] obj = new object[10] { body.BodyId, i, position.x, position.z, Vector3.Distance(position, nextPosition), ((Component)body).transform.eulerAngles.y, null, null, null, null }; Vector3 velocity = body._agent.velocity; obj[6] = ((Vector3)(ref velocity)).magnitude; obj[7] = body._agent.isStopped; obj[8] = text; obj[9] = text2; log.LogMessage((object)string.Format("[POS]#{0} f{1} tf=({2:F2},{3:F2}) gap={4:F2} rotY={5:F0} vel={6:F2} stop={7} mF={8} slope={9}", obj)); } else { string text3 = (((Object)(object)body._agent == (Object)null) ? "none" : ((!((Behaviour)body._agent).enabled) ? "disabled (latched/direct-drive)" : "off-mesh")); CompanionRuntime.Log.LogMessage((object)$"[POS]#{body.BodyId} f{i} tf=({position.x:F2},{position.z:F2}) agent={text3} rotY={((Component)body).transform.eulerAngles.y:F0} mF={text} slope={text2}"); } yield return null; } } } public static class BodyFactory { public enum EquipmentStripMode { Components, GameObjects } public static bool IsWildTamable(Character c) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Invalid comparison between Unknown and I4 //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Invalid comparison between Unknown and I4 //IL_004a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c != (Object)null && c.IsAI && c.Alive && (int)c.Faction != 1 && (int)c.Faction != 0 && (int)c.Faction != 7 && (Object)(object)c.OwnerPlayerSys == (Object)null && !CompanionAnchor.IsAnchor(c)) { return !AnchorSentinel.IsAnchorUid(UID.op_Implicit(c.UID)); } return false; } public static string WhyNotWildTamable(Character c) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 //IL_009d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)c == (Object)null) { return "null"; } List list = new List(); if (!c.IsAI) { list.Add("not-AI"); } if (!c.Alive) { list.Add("dead"); } if ((int)c.Faction == 1 || (int)c.Faction == 0 || (int)c.Faction == 7) { list.Add($"faction={c.Faction}"); } if ((Object)(object)c.OwnerPlayerSys != (Object)null) { list.Add("player-owned"); } if (CompanionAnchor.IsAnchor(c)) { list.Add("anchor(live)"); } if (AnchorSentinel.IsAnchorUid(UID.op_Implicit(c.UID))) { list.Add("anchor(uid)"); } if (list.Count != 0) { return string.Join(",", list); } return "tamable"; } public static Character FindNearest(Character player, float range, string speciesFilter) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Invalid comparison between Unknown and I4 //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) List list = new List(); CharacterManager.Instance.FindCharactersInRange(((Component)player).transform.position, range, ref list); Character result = null; float num = float.MaxValue; foreach (Character item in list) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)player) && item.IsAI && item.Alive && (int)item.Faction != 1 && !((Object)(object)item.OwnerPlayerSys != (Object)null) && !CompanionAnchor.IsAnchor(item) && !AnchorSentinel.IsAnchorUid(UID.op_Implicit(item.UID)) && (string.IsNullOrEmpty(speciesFilter) || Species.NameEquals(item.Name, speciesFilter))) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position); if (num2 < num) { num = num2; result = item; } } } return result; } public static Character FindNearestCharacter(Character player, float range, Func accept) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) List list = new List(); CharacterManager.Instance.FindCharactersInRange(((Component)player).transform.position, range, ref list); Character result = null; float num = float.MaxValue; foreach (Character item in list) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)player) && item.Alive && (accept == null || accept(item))) { float num2 = Vector3.Distance(((Component)player).transform.position, ((Component)item).transform.position); if (num2 < num) { num = num2; result = item; } } } return result; } public static Character FindGhostPrefab() { return GhostStandIn.FindGhostPrefab(); } public static Character SpawnGhostActive(Character player) { return GhostStandIn.SpawnGhostActive(player); } public static bool GhostVisualReady(Character ghost) { return GhostStandIn.GhostVisualReady(ghost); } public static void NudgeGhostActive(Character ghost) { GhostStandIn.NudgeGhostActive(ghost); } public static bool ForceGhostVisuals(Character ghost) { return GhostStandIn.ForceGhostVisuals(ghost); } public static CompanionBody FinishGhostPuppet(Character ghost, Character player) { return GhostStandIn.FinishGhostPuppet(ghost, player); } public static int CountRenderReady(GameObject go) { return PuppetVisibility.CountRenderReady(go); } public static void VisDump(GameObject go, string tag) { PuppetVisibility.VisDump(go, tag); } public static bool VisRepair(GameObject go) { return PuppetVisibility.VisRepair(go); } public static CompanionBody BuildHumanoidPuppet(Character src, Character player, EquipmentStripMode equipStrip = EquipmentStripMode.Components, CreatureAttributes precaptured = null, CompanionHost host = null) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) if (RefuseBuild(player)) { return null; } CloneHolder.SweepHolderOrphans(); string name = src.Name; CreatureAttributes capturedStats = precaptured ?? AttributeCapture.From(src, host); Vector3 val = ((Component)player).transform.position + ((Component)player).transform.forward * 2f; if (NavProbe.SampleAtFeet(val, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos)) { val = pos; } GameObject val2 = Object.Instantiate(((Component)src).gameObject, InactiveHolder("humanoid clone")); try { val2.transform.SetPositionAndRotation(val, ((Component)src).transform.rotation); Views.Neutralize(val2, "humanoid clone of '" + name + "'"); BodyFactory.DestroyImmediateAll(val2); CharacterAI[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (CharacterAI val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null) { try { val3.m_aiStatesRoot = null; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] m_aiStatesRoot clear threw (humanoid strip): " + ex.Message)); } Object.DestroyImmediate((Object)(object)val3); } } BodyFactory.DestroyImmediateAll(val2); BodyFactory.DestroyImmediateAll(val2); BodyFactory.DestroyImmediateAll(val2); CharacterBarManager[] componentsInChildren2 = val2.GetComponentsInChildren(true); foreach (CharacterBarManager val4 in componentsInChildren2) { Object.DestroyImmediate((Object)(object)val4); } if (equipStrip == EquipmentStripMode.Components) { BrainStrip.StripEquipmentGameplay(val2); } else { BrainStrip.StripEquippedItems(val2); } BrainStrip.StripHumanoidMachinery(val2); BodyFactory.DestroyImmediateAll(val2); val2.transform.SetParent((Transform)null, true); VerifySurvivors(val2, name, "humanoid clone activation"); CompanionBody companionBody = FinishPuppet(val2, name, player, equipStrip, consumedClone: false, "humanoid"); if ((Object)(object)companionBody != (Object)null) { if ((Object)(object)val2.GetComponent() == (Object)null) { val2.AddComponent(); } companionBody.YawOffset = 0f; companionBody.CapturedStats = capturedStats; companionBody.HumanoidAgent = true; companionBody.AgentBaseOffset = ComputeAgentBaseOffset(val2); RigStabilizer component = val2.GetComponent(); if ((Object)(object)component != (Object)null) { component.HumanoidMode = true; } } return companionBody; } catch { CloneHolder.DestroyStranded(val2, "humanoid puppet body"); throw; } } public static CompanionBody BuildPuppet(Character src, Character player, bool consume, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0, CreatureAttributes precaptured = null, CompanionHost host = null) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) if (RefuseBuild(player)) { return null; } CloneHolder.SweepHolderOrphans(); string name = src.Name; CreatureAttributes capturedStats = precaptured ?? AttributeCapture.From(src, host); ProjectileCapture.RangedAttackRig rangedAttackRig = ((rangedProjectileFilter != null) ? ProjectileCapture.From(src, rangedProjectileFilter, InactiveHolder("ranged-rig park"), rangedSkillPrefabId) : null); Vector3 val = ((Component)player).transform.position + ((Component)player).transform.forward * 2f; if (NavProbe.SampleAtFeet(val, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos)) { val = pos; } GameObject val2 = null; try { val2 = (consume ? CloneConsuming(src, val) : CloneNonConsuming(src, val)); GameObject go = val2; bool consumedClone = consume; CompanionBody companionBody = FinishPuppet(go, name, player, null, consumedClone, "wild-clone"); if ((Object)(object)companionBody != (Object)null) { companionBody.CapturedStats = capturedStats; ProjectileCapture.Attach(rangedAttackRig, companionBody); } return companionBody; } catch { CloneHolder.DestroyStranded(val2, "no-consume/consume puppet body"); if (rangedAttackRig != null && (Object)(object)rangedAttackRig.Root != (Object)null) { CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded ranged rig after failed build: '" + ((Object)rangedAttackRig.Root).name + "'")); Object.Destroy((Object)(object)rangedAttackRig.Root); } throw; } } internal static bool RefuseBuild(Character player) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) Vector3 val = (((Object)(object)player != (Object)null) ? ((Component)player).transform.position : Vector3.zero); Vector3 val2 = (((Object)(object)player != (Object)null) ? (val + ((Component)player).transform.forward * 2f) : Vector3.zero); PlaceRefusal val3 = PlacementGate.Check((Object)(object)player != (Object)null, val.x, val.y, val.z, val2.x, val2.y, val2.z); if (PlacementGate.Allows(val3)) { return false; } CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] build REFUSED: " + PlacementGate.Describe(val3) + " (owner=" + ((Vector3)(ref val)).ToString("F1") + ") — no body is built at a staging position; the caller retries.")); return true; } internal static CompanionBody FinishPuppet(GameObject go, string speciesId, Character player, EquipmentStripMode? humanoidEquipStrip = null, bool consumedClone = false, string origin = "unknown") { //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_010d: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) BrainStrip.StripBrain(go, humanoidEquipStrip); CompanionBody component = go.GetComponent(); if ((Object)(object)component != (Object)null) { CompanionRuntime.Log.LogError((object)($"[CLONE] TRIPWIRE: '{speciesId}' already carries CompanionBody#{component.BodyId} " + "(a body is being re-finished, or a clone was taken FROM a live puppet). Destroying the prior component; if this line ever appears, the two-body leak path is here.")); Object.DestroyImmediate((Object)(object)component); RigStabilizer component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { Object.DestroyImmediate((Object)(object)component2); } } CompanionBody companionBody = go.AddComponent(); companionBody.Origin = origin; go.AddComponent(); companionBody.Target = ((Component)player).transform; companionBody.SpeciesId = speciesId; Object.DontDestroyOnLoad((Object)(object)go); if (consumedClone) { EngagementHygiene.FullClear(player, "consume-tame clone"); } int num = go.GetComponentsInChildren(true).Length; int num2 = PuppetVisibility.CountRenderReady(go); CompanionRuntime.Log.LogMessage((object)$"[CLONE]#{companionBody.BodyId} '{speciesId}' puppet ready (Animator={(Object)(object)go.GetComponent() != (Object)null}, renderers={num}, drawReady={num2}, origin={origin})."); SurvivorFacts val = Views.CountNetwork(go); int views = val.Views; int netControls = val.NetControls; int num3 = BodyFactory.CountAll(go); int num4 = BodyFactory.CountAll(go); int num5 = BodyFactory.CountAll(go); int num6 = BodyFactory.CountAll(go); CompanionRuntime.Log.LogMessage((object)($"[CLONE]#{companionBody.BodyId} strip census: pv={views} " + $"ncc={netControls} charAI={num3} " + $"caid={num4} character={num5} items={num6}.")); companionBody.Contamination = ContaminationPolicy.Total(views, netControls, num3, num4); if (ContaminationPolicy.ShouldRebuild(companionBody.Contamination)) { CompanionRuntime.Log.LogError((object)($"[CLONE]#{companionBody.BodyId} CONTAMINATED: {companionBody.Contamination} component(s) " + "survived the brain strip (" + ContaminationPolicy.Describe(views, netControls, num3, num4, num5) + "). DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it. This body is not trustworthy: a surviving network component can drive it while forging healthy kinematics. A consumer watchdog (if any) should rebuild it immediately, without waiting for a stuck window.")); } if (num > 0 && num2 == 0) { CompanionRuntime.Log.LogWarning((object)("[CLONE] '" + speciesId + "' has NO draw-ready renderer (bug-8 signature) — dumping state + attempting auto-repair.")); PuppetVisibility.VisDump(go, "bug-8 pre-repair"); PuppetVisibility.VisRepair(go); } return companionBody; } internal static float ComputeAgentBaseOffset(GameObject go) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) float num = float.PositiveInfinity; SkinnedMeshRenderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val in componentsInChildren) { Bounds bounds = ((Renderer)val).bounds; if (((Bounds)(ref bounds)).min.y < num) { bounds = ((Renderer)val).bounds; num = ((Bounds)(ref bounds)).min.y; } } if (float.IsPositiveInfinity(num)) { return 0f; } return AgentFit.BaseOffset(go.transform.position.y, num); } private static GameObject CloneConsuming(Character src, Vector3 pos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) return Object.Instantiate(((Component)src).gameObject, pos, ((Component)src).transform.rotation); } private static GameObject CloneNonConsuming(Character src, Vector3 pos) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate(((Component)src).gameObject, InactiveHolder("no-consume clone")); try { if (val.activeInHierarchy) { CompanionRuntime.Log.LogError((object)("[CLONE] INVARIANT VIOLATED: clone of '" + src.Name + "' was born ACTIVE under the holder — its Awakes (PhotonView registration included) already ran; stripping anyway.")); } val.transform.SetPositionAndRotation(pos, ((Component)src).transform.rotation); Views.Neutralize(val, "no-consume clone of '" + src.Name + "'"); BodyFactory.DestroyImmediateAll(val); CharacterAI[] componentsInChildren = val.GetComponentsInChildren(true); foreach (CharacterAI val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { try { val2.m_aiStatesRoot = null; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] m_aiStatesRoot clear threw (no-consume strip): " + ex.Message)); } Object.DestroyImmediate((Object)(object)val2); } } BodyFactory.DestroyImmediateAll(val); BodyFactory.DestroyImmediateAll(val); BodyFactory.DestroyImmediateAll(val); CharacterBarManager[] componentsInChildren2 = val.GetComponentsInChildren(true); foreach (CharacterBarManager val3 in componentsInChildren2) { Object.DestroyImmediate((Object)(object)val3); } BrainStrip.StripEquippedItems(val); BodyFactory.DestroyImmediateAll(val); val.transform.SetParent((Transform)null, true); VerifySurvivors(val, src.Name, "no-consume clone activation"); return val; } catch { CloneHolder.DestroyStranded(val, "no-consume clone"); throw; } } internal static int DestroyImmediateAll(GameObject go) where T : Component { int num = 0; T[] componentsInChildren = go.GetComponentsInChildren(true); foreach (T val in componentsInChildren) { if ((Object)(object)val != (Object)null) { Object.DestroyImmediate((Object)(object)val); num++; } } return num; } internal static int CountAll(GameObject go) where T : Component { int num = 0; T[] componentsInChildren = go.GetComponentsInChildren(true); foreach (T val in componentsInChildren) { if ((Object)(object)val != (Object)null) { num++; } } return num; } private static Transform InactiveHolder(string what) { GameObject val = CloneHolder.Holder(); if (val.activeInHierarchy) { CompanionRuntime.Log.LogError((object)("[CLONE] INVARIANT VIOLATED: CK_CloneHolder is ACTIVE at " + what + " time — the Awake-deferral guarantee is void for anything instantiated under it. Re-deactivating and proceeding.")); val.SetActive(false); } return val.transform; } private static void VerifySurvivors(GameObject go, string speciesId, string context) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) SurvivorFacts val = Views.CountNetwork(go); int views = val.Views; int netControls = val.NetControls; int num = BodyFactory.CountAll(go); int num2 = BodyFactory.CountAll(go); int num3 = BodyFactory.CountAll(go); int num4 = BodyFactory.CountAll(go); if (views == 0 && netControls == 0 && num == 0 && num2 == 0 && num3 == 0) { return; } CompanionRuntime.Log.LogError((object)($"[CLONE] TRIPWIRE: '{speciesId}' activated with survivors (pv={views} ncc={netControls} " + $"charAI={num} caid={num2} character={num3} items={num4}) — " + "DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it. The chow-tame path runs inside an animation event. Disarming what can be disarmed; the strip census line is the record.")); Views.DisarmSurvivors(go, context, "[CLONE]"); try { Character[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Character val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { ((Behaviour)val2).enabled = false; Object.Destroy((Object)(object)val2); } } CharacterAI[] componentsInChildren2 = go.GetComponentsInChildren(true); foreach (CharacterAI val3 in componentsInChildren2) { if ((Object)(object)val3 != (Object)null) { try { val3.m_aiStatesRoot = null; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] m_aiStatesRoot clear threw (survivor disarm): " + ex.Message)); } ((Behaviour)val3).enabled = false; Object.Destroy((Object)(object)val3); } } CharAIDisable[] componentsInChildren3 = go.GetComponentsInChildren(true); foreach (CharAIDisable val4 in componentsInChildren3) { if ((Object)(object)val4 != (Object)null) { ((Behaviour)val4).enabled = false; Object.Destroy((Object)(object)val4); } } AISquadMember[] componentsInChildren4 = go.GetComponentsInChildren(true); foreach (AISquadMember val5 in componentsInChildren4) { if ((Object)(object)val5 != (Object)null) { ((Behaviour)val5).enabled = false; Object.Destroy((Object)(object)val5); } } CharacterBarManager[] componentsInChildren5 = go.GetComponentsInChildren(true); foreach (CharacterBarManager val6 in componentsInChildren5) { if ((Object)(object)val6 != (Object)null) { ((Behaviour)val6).enabled = false; Object.Destroy((Object)(object)val6); } } Item[] componentsInChildren6 = go.GetComponentsInChildren(true); foreach (Item val7 in componentsInChildren6) { if ((Object)(object)val7 != (Object)null) { Object.Destroy((Object)(object)((Component)val7).gameObject); } } } catch (Exception ex2) { CompanionRuntime.Log.LogError((object)("[CLONE] gameplay survivor disarm threw (partial disarm may have landed; the body is kept — the census line is the record): " + ex2)); } } } public enum FxSource { StatusFxLive, StatusFxPrefab, StatusSpecialFx, SkillStartVfx, CreatureFxSubtree } public enum FxAttach { AboveHead, Encase, Ground } public sealed class FxRecipe { public FxSource Source; public string StatusName; public int SkillItemId; public FxAttach Attach; public Vector3 Offset; public float Scale; public float Seconds; public int SoundId; public string SpeciesKey; public string SubtreeFilter; public string Signature => BodyFxMath.Signature((int)Source, StatusName, SkillItemId, (int)Attach, Offset.x, Offset.y, Offset.z, Scale, Seconds, SoundId, SpeciesKey, SubtreeFilter); } public static class BodyFx { internal const string CloneNamePrefix = "CK_BodyFx_"; private static readonly HashSet s_noSmrWarned = new HashSet(); private static bool s_noBinderWarned; private static bool s_playThrewWarned; private static bool s_attachThrewWarned; private static int s_resolveFails; private static int s_snapStripped; private static int s_snapSurvived; private const string Tag = "[BODYFX]"; private static string s_lastPlayBindings = ""; public const string DeferredBindingsNote = "bindings pending (play deferred one frame)"; public static string LastPlayBindings => s_lastPlayBindings; public static bool TryResolveSource(FxRecipe r, Character statusHost, out Transform source, out bool isLiveInstance) { source = null; isLiveInstance = false; if (r == null) { return false; } switch (r.Source) { case FxSource.StatusFxLive: { if ((Object)(object)statusHost == (Object)null || (Object)(object)statusHost.StatusEffectMngr == (Object)null) { s_resolveFails++; return false; } StatusEffect statusEffectOfName = statusHost.StatusEffectMngr.GetStatusEffectOfName(r.StatusName); if ((Object)(object)statusEffectOfName == (Object)null) { s_resolveFails++; return false; } if ((Object)(object)statusEffectOfName.FxTransform != (Object)null) { source = statusEffectOfName.FxTransform; isLiveInstance = true; return true; } if ((Object)(object)statusEffectOfName.FXPrefab != (Object)null) { source = statusEffectOfName.FXPrefab; return true; } s_resolveFails++; return false; } case FxSource.StatusFxPrefab: case FxSource.StatusSpecialFx: { if (ResourcesPrefabManager.Instance == null) { s_resolveFails++; return false; } StatusEffect statusEffectPrefab = ResourcesPrefabManager.Instance.GetStatusEffectPrefab(r.StatusName); if ((Object)(object)statusEffectPrefab == (Object)null) { s_resolveFails++; return false; } Transform val = ((r.Source == FxSource.StatusFxPrefab) ? statusEffectPrefab.FXPrefab : statusEffectPrefab.SpecialFXPrefab); if ((Object)(object)val == (Object)null) { s_resolveFails++; return false; } source = val; return true; } case FxSource.SkillStartVfx: { if (ResourcesPrefabManager.Instance == null) { s_resolveFails++; return false; } Item itemPrefab = ResourcesPrefabManager.Instance.GetItemPrefab(r.SkillItemId); Skill val2 = (Skill)(object)((itemPrefab is Skill) ? itemPrefab : null); if ((Object)(object)val2 == (Object)null || (Object)(object)val2.StartVFX == (Object)null) { s_resolveFails++; return false; } source = ((Component)val2.StartVFX).transform; return true; } case FxSource.CreatureFxSubtree: { if (CompanionAura.TryGetCapturedSubtree(r.SpeciesKey, r.SubtreeFilter, out var subtree)) { source = subtree; return true; } s_resolveFails++; return false; } default: s_resolveFails++; return false; } } public static GameObject PlayOneShot(FxRecipe r, Transform body, Character binder) { string sourceName; return PlayOneShot(r, body, binder, out sourceName); } public static GameObject PlayOneShot(FxRecipe r, Transform body, Character binder, out string sourceName) { //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Expected O, but got Unknown //IL_029d: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_027e: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) sourceName = ""; s_lastPlayBindings = ""; if (r == null || (Object)(object)body == (Object)null) { return null; } if ((Object)(object)binder == (Object)null) { if (!s_noBinderWarned) { s_noBinderWarned = true; CompanionRuntime.Log.LogWarning((object)"[BODYFX] one-shot needs a real Character binder (VFXSystem.Play's null gate) and got none — pass the local player. (Warned once.)"); } return null; } if (!TryResolveSource(r, binder, out var source, out var _)) { return null; } sourceName = (((Object)(object)source == (Object)null) ? "" : ((Object)source).name); GameObject val = null; Transform val2 = null; try { bool flag = (Object)(object)body == (Object)(object)((Component)binder).transform; val = new GameObject("CK_BodyFxShotHolder"); val.SetActive(false); GameObject val3 = Object.Instantiate(((Component)source).gameObject, val.transform); ((Object)val3).name = "CK_BodyFxShot"; val2 = val3.transform; BodyFxPin bodyFxPin = null; bool flag2 = false; if (!flag) { List list = SnapFamily(val2); foreach (Component item in list) { StripOne(item); } List list2 = new List(); foreach (Component item2 in SnapFamily(val2)) { list2.Add(((object)item2).GetType().Name + "@'" + ((Object)item2).name + "'"); Behaviour val4 = (Behaviour)(object)((item2 is Behaviour) ? item2 : null); if (val4 != null) { val4.enabled = false; } Object.Destroy((Object)(object)item2); } s_snapStripped += list.Count - list2.Count; s_snapSurvived += list2.Count; flag2 = list2.Count > 0; if (flag2) { CompanionRuntime.Log.LogWarning((object)("[BODYFX]" + $" {list2.Count} snap component(s) survived the one-shot strip " + "[" + string.Join(", ", list2.ToArray()) + "] — DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it. Play is DEFERRED one frame (the queued Destroys flush at end of THIS frame, so next frame's VFXPlayed broadcast reaches nothing) and a BodyFxPin holds the flourish on the body regardless.")); } if (flag2) { bodyFxPin = val3.AddComponent(); bodyFxPin.Body = body; bodyFxPin.LocalPose = true; bodyFxPin.Offset = r.Offset; bodyFxPin.Scale = r.Scale; bodyFxPin.Init(null); } } if ((Object)(object)bodyFxPin == (Object)null && r.Scale > 0f && !Mathf.Approximately(r.Scale, 1f)) { val2.localScale *= r.Scale; } val2.SetParent(body, false); val2.localPosition = r.Offset; val2.localRotation = Quaternion.identity; ((Component)val2).gameObject.SetActive(true); Object.Destroy((Object)(object)val); val = null; if (flag2) { BodyFxDeferredPlay bodyFxDeferredPlay = val3.AddComponent(); bodyFxDeferredPlay.Body = body; bodyFxDeferredPlay.Binder = binder; bodyFxDeferredPlay.Pin = bodyFxPin; bodyFxDeferredPlay.Arm(); s_lastPlayBindings = "bindings pending (play deferred one frame)"; } else { FinishOneShot(val2, body, binder, null, flag); s_lastPlayBindings = DescribeBindings(((Component)val2).gameObject); } if (r.SoundId > 0 && (Object)(object)Global.AudioManager != (Object)null) { Global.AudioManager.PlaySoundAtPosition((Sounds)r.SoundId, body, 0f, 1f, 1f, 1f, 1f); } Object.Destroy((Object)(object)((Component)val2).gameObject, BodyFxMath.ClampSeconds(r.Seconds)); return ((Component)val2).gameObject; } catch (Exception arg) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)((Component)val2).gameObject); } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if (!s_playThrewWarned) { s_playThrewWarned = true; CompanionRuntime.Log.LogWarning((object)("[BODYFX]" + $" one-shot threw (won't retry-log): {arg}")); } return null; } } public static GameObject AttachPersistent(FxRecipe r, Transform body, int slot, Character statusHost = null) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0284: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) if (r == null || (Object)(object)body == (Object)null) { return null; } if (!TryResolveSource(r, statusHost, out var source, out var _)) { return null; } Detach(body, slot); GameObject val = null; GameObject val2 = null; try { val = new GameObject("CK_BodyFxHolder"); val.SetActive(false); val2 = Object.Instantiate(((Component)source).gameObject, val.transform); ((Object)val2).name = "CK_BodyFx_" + slot.ToString(CultureInfo.InvariantCulture); MonoBehaviour[] componentsInChildren = val2.GetComponentsInChildren(true); foreach (MonoBehaviour val3 in componentsInChildren) { if ((Object)(object)val3 != (Object)null) { StripOne((Component)(object)val3); } } Collider[] componentsInChildren2 = val2.GetComponentsInChildren(true); foreach (Collider val4 in componentsInChildren2) { if ((Object)(object)val4 != (Object)null) { StripOne((Component)(object)val4); } } Joint[] componentsInChildren3 = val2.GetComponentsInChildren(true); foreach (Joint val5 in componentsInChildren3) { if ((Object)(object)val5 != (Object)null) { StripOne((Component)(object)val5); } } Rigidbody[] componentsInChildren4 = val2.GetComponentsInChildren(true); foreach (Rigidbody val6 in componentsInChildren4) { if ((Object)(object)val6 != (Object)null) { StripOne((Component)(object)val6); } } List list = new List(); MonoBehaviour[] componentsInChildren5 = val2.GetComponentsInChildren(true); foreach (MonoBehaviour val7 in componentsInChildren5) { if ((Object)(object)val7 != (Object)null) { list.Add(((object)val7).GetType().Name); } } Collider[] componentsInChildren6 = val2.GetComponentsInChildren(true); foreach (Collider val8 in componentsInChildren6) { if ((Object)(object)val8 != (Object)null) { list.Add(((object)val8).GetType().Name); } } Rigidbody[] componentsInChildren7 = val2.GetComponentsInChildren(true); foreach (Rigidbody val9 in componentsInChildren7) { if ((Object)(object)val9 != (Object)null) { list.Add(((object)val9).GetType().Name); } } if (list.Count > 0) { CompanionRuntime.Log.LogWarning((object)("[BODYFX]" + $" {list.Count} component(s) survived the strip " + "[" + string.Join(", ", list.ToArray()) + "] — a live script on the clone can move or destroy it; that is the 'FX vanishes/teleports' shape.")); } BodyFxPin bodyFxPin = val2.AddComponent(); bodyFxPin.Body = body; bodyFxPin.Attach = r.Attach; bodyFxPin.Offset = r.Offset; bodyFxPin.Scale = r.Scale; bodyFxPin.Init(((Component)body).GetComponentInChildren()); val2.transform.SetParent(body, false); val2.SetActive(true); Object.Destroy((Object)(object)val); int num2 = 0; int num3 = 0; ParticleSystem[] componentsInChildren8 = val2.GetComponentsInChildren(true); foreach (ParticleSystem val10 in componentsInChildren8) { if (!((Object)(object)val10 == (Object)null)) { num2++; if (!val10.isPlaying) { val10.Play(false); num3++; } } } CompanionRuntime.Log.LogMessage((object)("[BODYFX] persistent attach '" + ((Object)val2).name + "' on '" + ((Object)body).name + "': " + $"{num2} particle system(s) ({num3} started explicitly), " + $"{val2.GetComponentsInChildren(true).Length} renderer(s), " + $"{val2.GetComponentsInChildren(true).Length} light(s).")); return val2; } catch (Exception arg) { if ((Object)(object)val2 != (Object)null) { Object.Destroy((Object)(object)val2); } if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } if (!s_attachThrewWarned) { s_attachThrewWarned = true; CompanionRuntime.Log.LogWarning((object)("[BODYFX]" + $" persistent attach threw (won't retry-log): {arg}")); } return null; } } public static bool Detach(Transform body, int slot) { if ((Object)(object)body == (Object)null) { return false; } string text = "CK_BodyFx_" + slot.ToString(CultureInfo.InvariantCulture); bool result = false; for (int num = body.childCount - 1; num >= 0; num--) { Transform child = body.GetChild(num); if ((Object)(object)child != (Object)null && ((Object)child).name == text) { Object.Destroy((Object)(object)((Component)child).gameObject); result = true; } } return result; } public static bool Has(Transform body, int slot) { if ((Object)(object)body == (Object)null) { return false; } string text = "CK_BodyFx_" + slot.ToString(CultureInfo.InvariantCulture); for (int num = body.childCount - 1; num >= 0; num--) { Transform child = body.GetChild(num); if ((Object)(object)child != (Object)null && ((Object)child).name == text) { return true; } } return false; } public static int DetachAll(Transform body) { if ((Object)(object)body == (Object)null) { return 0; } int num = 0; for (int num2 = body.childCount - 1; num2 >= 0; num2--) { Transform child = body.GetChild(num2); if ((Object)(object)child != (Object)null && ((Object)child).name.StartsWith("CK_BodyFx_", StringComparison.Ordinal)) { Object.Destroy((Object)(object)((Component)child).gameObject); num++; } } return num; } public static string Audit(FxRecipe r, Character statusHost = null) { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (r == null) { return "[BODYFX] audit: null recipe"; } string text = ((r.Source == FxSource.SkillStartVfx) ? r.SkillItemId.ToString(CultureInfo.InvariantCulture) : ((r.Source == FxSource.CreatureFxSubtree) ? ("'" + r.SpeciesKey + "'/'" + r.SubtreeFilter + "'") : ("'" + r.StatusName + "'"))); if (!TryResolveSource(r, statusHost, out var source, out var isLiveInstance)) { return "[BODYFX]" + $" {r.Source}({text}): UNRESOLVED (resolveFails={s_resolveFails}) sig='{r.Signature}'"; } int num = ((Component)source).GetComponentsInChildren(true).Length; int num2 = ((Component)source).GetComponentsInChildren(true).Length; int count = SnapFamily(source).Count; int num3 = 0; int num4 = 0; ParticleSystem[] componentsInChildren = ((Component)source).GetComponentsInChildren(true); foreach (ParticleSystem val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { num3++; MainModule main = val.main; if (((MainModule)(ref main)).playOnAwake) { num4++; } } } int num5 = ((Component)source).GetComponentsInChildren(true).Length; int num6 = ((Component)source).GetComponentsInChildren(true).Length; return "[BODYFX]" + string.Format(" {0}({1}): fx '{2}' ({3}, ", r.Source, text, ((Object)source).name, isLiveInstance ? "live" : "prefab") + $"{num} VFXSystem(s), {num2} on-renderer emitter(s), " + $"{num3} raw ParticleSystem(s) [{num4} playOnAwake], {num5} Light(s), " + $"{num6} Renderer(s), {count} snap-family " + $"(stripped {s_snapStripped}, REFUSED {s_snapSurvived} to date)) attach={r.Attach} " + $"sec={BodyFxMath.ClampSeconds(r.Seconds):F1} snd={r.SoundId} " + $"resolveFails={s_resolveFails} sig='{r.Signature}'"; } private static List SnapFamily(Transform fx) { List list = new List(); VFXPositionOnChar[] componentsInChildren = ((Component)fx).GetComponentsInChildren(true); foreach (VFXPositionOnChar val in componentsInChildren) { if ((Object)(object)val != (Object)null) { list.Add((Component)(object)val); } } VFXFollowCharBone[] componentsInChildren2 = ((Component)fx).GetComponentsInChildren(true); foreach (VFXFollowCharBone val2 in componentsInChildren2) { if ((Object)(object)val2 != (Object)null) { list.Add((Component)(object)val2); } } VFXParticlesOnVisuals[] componentsInChildren3 = ((Component)fx).GetComponentsInChildren(true); foreach (VFXParticlesOnVisuals val3 in componentsInChildren3) { if ((Object)(object)val3 != (Object)null) { list.Add((Component)(object)val3); } } VFXLookAtSource[] componentsInChildren4 = ((Component)fx).GetComponentsInChildren(true); foreach (VFXLookAtSource val4 in componentsInChildren4) { if ((Object)(object)val4 != (Object)null) { list.Add((Component)(object)val4); } } return list; } internal static void FinishOneShot(Transform fx, Transform body, Character binder, BodyFxPin pin, bool selfPlay, bool deferredPlay = false) { if ((Object)(object)fx == (Object)null || (Object)(object)body == (Object)null || (Object)(object)binder == (Object)null) { return; } VFXSystem[] componentsInChildren = ((Component)fx).GetComponentsInChildren(true); foreach (VFXSystem val in componentsInChildren) { if ((Object)(object)val != (Object)null) { val.Play(binder, (Character)null); } } if (!selfPlay) { SkinnedMeshRenderer componentInChildren = ((Component)body).GetComponentInChildren(); if ((Object)(object)componentInChildren != (Object)null) { Rebind(fx, componentInChildren); } else if (s_noSmrWarned.Add(((Object)body).name)) { CompanionRuntime.Log.LogWarning((object)("[BODYFX] body '" + ((Object)body).name + "' has no SkinnedMeshRenderer (ghost stand-in?) — on-renderer emitters skipped, the rest of the FX still plays. (Warned once per body name.)")); } } else { SkinnedMeshRenderer componentInChildren2 = ((Component)body).GetComponentInChildren(); if ((Object)(object)componentInChildren2 != (Object)null) { RebindNullTargets(fx, componentInChildren2); } } if (deferredPlay) { string text = DescribeBindings(((Component)fx).gameObject); CompanionRuntime.Log.LogMessage((object)("[BODYFX] deferred one-shot on '" + ((Object)body).name + "' bound: " + ((text.Length > 0) ? text : "(no on-renderer emitters)"))); } if ((Object)(object)pin != (Object)null) { pin.Apply(); } } public static string DescribeBindings(GameObject fxRoot) { if ((Object)(object)fxRoot == (Object)null) { return ""; } try { List list = new List(); VFXParticlesOnRenderer[] componentsInChildren = fxRoot.GetComponentsInChildren(true); foreach (VFXParticlesOnRenderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { Renderer targetRenderer = val.m_targetRenderer; list.Add(BodyFxDescribe.Binding(((object)val).GetType().Name, ((Object)val).name, ((Object)(object)targetRenderer == (Object)null) ? null : ((Object)targetRenderer).name)); } } return BodyFxDescribe.Bindings((IEnumerable)list); } catch (Exception) { return ""; } } private static void RebindNullTargets(Transform fx, SkinnedMeshRenderer smr) { //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) VFXParticlesOnRenderer[] componentsInChildren = ((Component)fx).GetComponentsInChildren(true); foreach (VFXParticlesOnRenderer val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && BodyFxMath.SelfPlayNeedsRebind((Object)(object)val.m_targetRenderer != (Object)null, (Object)(object)smr != (Object)null)) { ParticleSystem component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null)) { ShapeModule shape = component.shape; ((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)14; ((ShapeModule)(ref shape)).skinnedMeshRenderer = smr; val.m_targetRenderer = (Renderer)(object)smr; } } } } private static void Rebind(Transform fx, SkinnedMeshRenderer smr) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) VFXParticlesOnRenderer[] componentsInChildren = ((Component)fx).GetComponentsInChildren(true); foreach (VFXParticlesOnRenderer val in componentsInChildren) { ParticleSystem component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null)) { ShapeModule shape = component.shape; ((ShapeModule)(ref shape)).shapeType = (ParticleSystemShapeType)14; ((ShapeModule)(ref shape)).skinnedMeshRenderer = smr; val.m_targetRenderer = (Renderer)(object)smr; } } } private static void StripOne(Component c) { try { Object.DestroyImmediate((Object)(object)c); } catch (Exception) { } } } internal sealed class BodyFxDeferredPlay : MonoBehaviour { internal Transform Body; internal Character Binder; internal BodyFxPin Pin; private int _queuedFrame; internal void Arm() { _queuedFrame = Time.frameCount; } private void Update() { if (Time.frameCount > _queuedFrame) { BodyFx.FinishOneShot(((Component)this).transform, Body, Binder, Pin, selfPlay: false, deferredPlay: true); Object.Destroy((Object)(object)this); } } } public sealed class BodyFxPin : MonoBehaviour { public Transform Body; public FxAttach Attach; public bool LocalPose; public Vector3 Offset; public float Scale; public SkinnedMeshRenderer Smr; private Vector3 _baseScale; private bool _hadSmr; private bool _staticScaleApplied; internal void Init(SkinnedMeshRenderer smr) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Smr = smr; _hadSmr = (Object)(object)smr != (Object)null; _baseScale = ((Component)this).transform.localScale; } private void LateUpdate() { Apply(); } internal void Apply() { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_01eb: Unknown result type (might be due to invalid IL or missing references) //IL_01fb: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0211: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: Unknown result type (might be due to invalid IL or missing references) //IL_01b7: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0274: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02aa: Unknown result type (might be due to invalid IL or missing references) //IL_02bd: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Unknown result type (might be due to invalid IL or missing references) //IL_02dd: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02ef: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)Body == (Object)null) { return; } if (_hadSmr && (Object)(object)Smr == (Object)null) { Object.Destroy((Object)(object)((Component)this).gameObject); return; } if (LocalPose) { ((Component)this).transform.position = Body.TransformPoint(Offset); ((Component)this).transform.rotation = Body.rotation; ApplyStaticScale(); return; } Vector3 forward = Body.forward; float num = default(float); float num2 = default(float); BodyFxMath.FlattenForward(forward.x, forward.z, ref num, ref num2); Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(num, 0f, num2); Vector3 val2 = Vector3.Cross(Vector3.up, val); switch (Attach) { case FxAttach.Encase: { Vector3 val3; float num5; if ((Object)(object)Smr != (Object)null) { Bounds bounds2 = ((Renderer)Smr).bounds; val3 = ((Bounds)(ref bounds2)).center; num5 = BodyFxMath.EncaseScale(((Bounds)(ref bounds2)).size.x, ((Bounds)(ref bounds2)).size.y, ((Bounds)(ref bounds2)).size.z, Scale); } else { val3 = Body.position + Vector3.up * 0.75f; num5 = BodyFxMath.EncaseScale(2f, 2f, 2f, Scale); } ((Component)this).transform.position = val3 + Vector3.up * Offset.y + val * Offset.z + val2 * Offset.x; ((Component)this).transform.rotation = Quaternion.identity; ((Component)this).transform.localScale = _baseScale * num5; break; } case FxAttach.Ground: ((Component)this).transform.position = Body.position + Vector3.up * Offset.y + val * Offset.z + val2 * Offset.x; ((Component)this).transform.rotation = Quaternion.LookRotation(val, Vector3.up); ApplyStaticScale(); break; default: { float num3; if (!((Object)(object)Smr != (Object)null)) { num3 = 1.5f; } else { Bounds bounds = ((Renderer)Smr).bounds; num3 = BodyFxMath.TopY(((Bounds)(ref bounds)).max.y, Body.position.y); } float num4 = num3; ((Component)this).transform.position = Body.position + Vector3.up * (num4 + Offset.y) + val * Offset.z + val2 * Offset.x; ((Component)this).transform.rotation = Quaternion.identity; ApplyStaticScale(); break; } } } private void ApplyStaticScale() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) if (!_staticScaleApplied) { _staticScaleApplied = true; if (Scale > 0f && !Mathf.Approximately(Scale, 1f)) { ((Component)this).transform.localScale = _baseScale * Scale; } } } } public enum OrphanBodyPolicy { Off, LogOnly, Reap } internal static class BodyReaper { private static readonly WaitForSeconds s_wait = new WaitForSeconds(2f); private static bool s_warnedNoConsumer; private static OrphanBodyPolicy Policy => CkConfig.Diag.OrphanBodyPolicy?.Value ?? OrphanBodyPolicy.Reap; private static float Grace => Mathf.Max(10f, CkConfig.Diag.OrphanBodyGraceSeconds?.Value ?? 90f); private static float Condemn => Mathf.Max(2f, CkConfig.Diag.OrphanBodyCondemnSeconds?.Value ?? 20f); internal static IEnumerator Sweep() { yield return s_wait; ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)ConsumerContract.Describe()); } while (true) { try { SweepOnce(); } catch (Exception ex) { ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[REAPER] sweep threw (recovered; next sweep in 2s): " + ex.Message)); } } yield return s_wait; } } private static void SweepOnce() { //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Invalid comparison between Unknown and I4 BodyCensus.PurgeDead(); OrphanBodyPolicy policy = Policy; if (policy == OrphanBodyPolicy.Off || BodyCensus.LiveCount == 0) { return; } if (BodyCensus.ConsumerSourceCount == 0) { if (!s_warnedNoConsumer) { s_warnedNoConsumer = true; ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[REAPER] no consumer registered a claim source — orphan-body reaping DISABLED for this session. Every companion body would read as an orphan and be destroyed ~" + $"{Grace:F0}s after it is built. The usual cause is a consumer plugin " + "(Beastwhispering/Hireling) OLDER than this CompanionKit — check that their versions came from the same release train.")); } } return; } s_warnedNoConsumer = false; HashSet hashSet = BodyCensus.ClaimedNow(); float unscaledTime = Time.unscaledTime; foreach (BodyCensus.Entry item in BodyCensus.Snapshot()) { CompanionBody body = item.Body; if ((Object)(object)body == (Object)null) { continue; } if (hashSet.Contains(body)) { item.EverClaimed = true; item.UnclaimedSince = unscaledTime; item.WarnedOrphan = false; continue; } ReapVerdict val = BodyReaperPolicy.Decide(unscaledTime - item.BornAt, false, unscaledTime - item.UnclaimedSince, policy == OrphanBodyPolicy.Reap, Grace, Condemn, item.EverClaimed, item.WarnedOrphan); if ((int)val == 0) { continue; } string text = Describe(body, item, unscaledTime); if ((int)val == 1) { if (!item.WarnedOrphan) { item.WarnedOrphan = true; if (!item.EverClaimed) { item.UnclaimedSince = unscaledTime; } CompanionRuntime.Log.LogWarning((object)("[REAPER] orphan " + text + " — WATCHING " + $"(no claim source owns it; destroyed in ~{Condemn:F0}s unless claimed" + ((policy == OrphanBodyPolicy.LogOnly) ? "; LogOnly — would-be destroy is logged, not executed" : "") + ").")); } } else { CompanionRuntime.Log.LogWarning((object)("[REAPER] orphan " + text + " — DESTROYED (no active companion, no effigy row, no in-flight ladder owns it).")); Object.Destroy((Object)(object)((Component)body).gameObject); } } } private static string Describe(CompanionBody b, BodyCensus.Entry e, float now) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) NavMeshAgent agent = b._agent; string text; Vector3 val; if ((Object)(object)agent == (Object)null) { text = "agent=none"; } else if (!((Behaviour)agent).enabled) { text = "agent=disabled"; } else { object arg = agent.isOnNavMesh; val = agent.nextPosition; text = string.Format("agentEnabled=True onNavMesh={0} next={1}", arg, ((Vector3)(ref val)).ToString("F1")); } string[] obj = new string[8] { $"CompanionBody#{b.BodyId} ('{b.SpeciesId}', origin={b.Origin}, age={now - e.BornAt:F0}s, ", $"everClaimed={e.EverClaimed}, unclaimedFor={now - e.UnclaimedSince:F0}s, ", $"claims={BodyCensus.ConsumerSourceCount}c/{BodyCensus.SourceCount}, ", "pos=", null, null, null, null }; val = ((Component)b).transform.position; obj[4] = ((Vector3)(ref val)).ToString("F1"); obj[5] = ", "; obj[6] = text; obj[7] = ")"; return string.Concat(obj); } } public sealed class BodyTemplate { internal readonly BodyTemplate Inner; public GameObject Dormant => Inner.Dormant; public string Key => Inner.Key; public string SpeciesId => Inner.SpeciesId; public CreatureAttributes Captured => Inner.Captured; public bool Substituted => Inner.Substituted; public bool NoAiGraph => Inner.NoAiGraph; public string Origin => Inner.Origin; internal BodyTemplate(BodyTemplate inner) { Inner = inner; } } public static class BodyTemplateCache { public static int Count => BodyTemplateStore.Count; private static BodyTemplate Wrap(BodyTemplate t) { if (t != null) { return new BodyTemplate(t); } return null; } public static bool TryResolve(string speciesId, out BodyTemplate template) { BodyTemplate t = default(BodyTemplate); bool flag = BodyTemplateStore.TryResolve(speciesId, ref t); template = (flag ? Wrap(t) : null); return flag; } public static bool TryResolveExact(string speciesId, out BodyTemplate template) { BodyTemplate t = default(BodyTemplate); bool flag = BodyTemplateStore.TryResolveExact(speciesId, ref t); template = (flag ? Wrap(t) : null); return flag; } public static BodyTemplate GetResident(string key) { return Wrap(BodyTemplateStore.GetResident(key)); } public static BodyTemplate Capture(Character src, string key, CompanionHost host = null) { return Wrap(BodyTemplateStore.Capture(src, key, (object)host)); } public static CompanionBody PuppetFrom(BodyTemplate template, Character player, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { if (template == null || (Object)(object)template.Dormant == (Object)null || (Object)(object)player == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] cannot build from this template — " + ((template == null) ? "no template supplied" : (((Object)(object)template.Dormant == (Object)null) ? ("'" + template.Key + "' has a destroyed/absent dormant object") : ("'" + template.Key + "': no player"))) + "; the body ladder falls through to its next source.")); return null; } try { Character component = template.Dormant.GetComponent(); if ((Object)(object)component == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] '" + template.Key + "' has no Character component — dropping it.")); BodyTemplateStore.Drop(template.Key); return null; } CompanionBody companionBody = BodyFactory.BuildPuppet(component, player, consume: false, rangedProjectileFilter, rangedSkillPrefabId, template.Captured); if ((Object)(object)companionBody != (Object)null) { if (!string.IsNullOrEmpty(template.SpeciesId)) { companionBody.SpeciesId = template.SpeciesId; } if (companionBody.CapturedStats == null) { companionBody.CapturedStats = template.Captured; } companionBody.Origin = ((template.Origin == "prebuilt") ? "bundle" : "cache"); CompanionRuntime.Log.LogMessage((object)$"[TEMPLATE]#{companionBody.BodyId} built puppet from cached '{template.Key}' — no scene load needed."); } return companionBody; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[TEMPLATE] puppet build from '" + template.Key + "' failed: " + ex.Message)); return null; } } public static int ClearAllAndForgetMisses() { int result = Clear(); ExpeditionOrchestrator.ForgetMisses(); BodyTemplateStore.ResetSubstitutionWarnings(); CompanionEffigy.ResetHarvestGate(); return result; } public static int Clear() { return BodyTemplateStore.Clear(); } public static string ClearVerb(string[] parts) { if (TemplateClearVerb.WantsAll(parts)) { return TemplateClearVerb.Summary(ClearAllAndForgetMisses(), 0, true); } int num2 = default(int); int num = BodyTemplateStore.ClearHarvested(ref num2); ExpeditionOrchestrator.ForgetMisses(); BodyTemplateStore.ResetSubstitutionWarnings(); CompanionEffigy.ResetHarvestGate(); return TemplateClearVerb.Summary(num, num2, false); } public static string Dump() { return BodyTemplateStore.Dump(); } public static string Probe() { return BodyTemplateStore.Probe(); } } internal static class BrainStrip { private static readonly HashSet HumanoidStrip = new HashSet { "SNPC", "SNPCMoving", "SNPCContainer", "NPCInteraction", "DialogueActor", "DialogueActorLocalize", "DialogueStarter", "DialogueTreeExt", "DialogueTreeController", "DialogueAudio", "BasicDialogueSetup", "DialogueSetup", "Merchant", "MerchantPouch", "MerchantRotatingInventory", "MerchantFastTravel", "InteractionDialogue", "InteractionMerchantDialogue", "InteractionTrainerDialogue", "AutoFacing", "NPCLookFollow" }; private static readonly HashSet GameplayStrip = new HashSet { "MeleeSkill", "Hitbox", "PunctualDamage", "WeaponDamage", "CharacterSkillKnowledge", "ItemContainer", "InteractionActivator", "InteractionOpenContainer", "InteractionTriggerBase", "DropTable", "Dropable", "GuaranteedDrop", "LootableOnDeath", "StartingEquipment", "QuestEventOnDeath", "CharAIDisable", "CharacterBarManager", "NetworkCharacterControl" }; internal static void StripEquippedItems(GameObject go) { Item[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Item val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { try { Object.DestroyImmediate((Object)(object)((Component)val).gameObject); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] equipped-item strip threw: " + ex.Message)); } } } } internal static void StripEquipmentGameplay(GameObject go) { int num = 0; Item[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Item val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { try { Object.DestroyImmediate((Object)(object)val); num++; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] equipment-gameplay strip threw: " + ex.Message)); } } } if (num > 0) { CompanionRuntime.Log.LogMessage((object)$"[CLONE] humanoid equipment strip: {num} Item component(s) removed (GameObjects kept)."); } } internal static void StripHumanoidMachinery(GameObject go) { List list = new List(); Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && HumanoidStrip.Contains(((object)val).GetType().Name)) { list.Add(((object)val).GetType().Name); try { Object.DestroyImmediate((Object)(object)val); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] humanoid machinery strip threw on " + ((object)val).GetType().Name + ": " + ex.Message)); } } } CompanionRuntime.Log.LogMessage((object)((list.Count > 0) ? ("[CLONE] humanoid machinery strip: " + string.Join(", ", list.ToArray()) + ".") : "[CLONE] humanoid machinery strip: nothing matched (clean NPC).")); DisarmSurvivorsByName(go, HumanoidStrip, "humanoid machinery"); } private static void DisarmSurvivorsByName(GameObject go, HashSet nameSet, string pass) { List list = null; Component[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && nameSet.Contains(((object)val).GetType().Name)) { (list ?? (list = new List())).Add(((object)val).GetType().Name); Behaviour val2 = (Behaviour)(object)((val is Behaviour) ? val : null); if ((Object)(object)val2 != (Object)null) { val2.enabled = false; } Object.Destroy((Object)(object)val); } } if (list != null) { CompanionRuntime.Log.LogError((object)($"[CLONE] {list.Count} component(s) survived the {pass} strip " + "[" + string.Join(", ", list.ToArray()) + "] — DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it. Disabled and queued for a deferred Destroy.")); } } internal static void StripBrain(GameObject go, BodyFactory.EquipmentStripMode? humanoidEquipStrip = null) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) Views.Neutralize(go, "brain strip"); BodyFactory.DestroyImmediateAll(go); CharacterAI[] componentsInChildren = go.GetComponentsInChildren(true); foreach (CharacterAI val in componentsInChildren) { if ((Object)(object)val != (Object)null) { try { val.m_aiStatesRoot = null; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CLONE] m_aiStatesRoot clear threw (brain strip): " + ex.Message)); } Object.Destroy((Object)(object)val); } } BodyFactory.DestroyImmediateAll(go); Rigidbody component = go.GetComponent(); if ((Object)(object)component != (Object)null) { Object.Destroy((Object)(object)component); } if (humanoidEquipStrip == BodyFactory.EquipmentStripMode.Components) { StripEquipmentGameplay(go); } else { StripEquippedItems(go); } if (humanoidEquipStrip.HasValue) { StripHumanoidMachinery(go); } Character component2 = go.GetComponent(); if ((Object)(object)component2 != (Object)null) { ((Behaviour)component2).enabled = false; } CharacterController component3 = go.GetComponent(); if ((Object)(object)component3 != (Object)null) { ((Collider)component3).enabled = false; } AdvancedMover component4 = go.GetComponent(); if ((Object)(object)component4 != (Object)null) { ((Behaviour)component4).enabled = false; } RigidbodySuspender component5 = go.GetComponent(); if ((Object)(object)component5 != (Object)null) { ((Behaviour)component5).enabled = false; } Collider[] componentsInChildren2 = go.GetComponentsInChildren(true); foreach (Collider val2 in componentsInChildren2) { val2.enabled = false; } Component[] componentsInChildren3 = go.GetComponentsInChildren(true); foreach (Component val3 in componentsInChildren3) { if (!((Object)(object)val3 == (Object)null) && GameplayStrip.Contains(((object)val3).GetType().Name)) { try { Object.DestroyImmediate((Object)(object)val3); } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[CLONE] deep strip threw on " + ((object)val3).GetType().Name + ": " + ex2.Message)); } } } DisarmSurvivorsByName(go, GameplayStrip, "deep gameplay"); } } [HarmonyPatch(typeof(CharacterManager), "CharacterHasBeenDestroyed")] internal static class CharacterRegistryGuard { private static readonly HashSet _warnedUids = new HashSet(); private static int _guarded; [HarmonyPrefix] private static bool Prefix(CharacterManager __instance, Character _character) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)__instance == (Object)null || _character == null) { return true; } string text = UID.op_Implicit(_character.UID); if (string.IsNullOrEmpty(text)) { return true; } if (!__instance.m_characters.ContainsKey(text)) { return true; } Character val = __instance.m_characters[text]; if (val == _character) { return true; } if ((Object)(object)val == (Object)null) { return true; } __instance.m_characterToInitialized.Remove(_character); _guarded++; if (_warnedUids.Add(text)) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)$"[CHAR-GUARD] guarded m_characters eviction: destroyed copy of '{text}' (go='{SafeGoName(_character)}') is not the registered instance ('{SafeGoName(val)}') — the live NPC stays registered (donor-unload asymmetry, DonorPhotonGuard's registry sibling; {_guarded} guarded total, further hits for this UID silent)."); } } return false; } catch (Exception ex) { ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[CHAR-GUARD] prefix failed (" + ex.Message + ") — falling through to vanilla.")); } return true; } } internal static string SafeGoName(Character c) { try { return ((Object)(object)c != (Object)null && (Object)(object)((Component)c).gameObject != (Object)null) ? ((Object)((Component)c).gameObject).name : "?"; } catch { return "?"; } } } [HarmonyPatch(typeof(CharacterManager), "AddCharacter")] internal static class AddCharacterDupGuard { private static int _guarded; [HarmonyPrefix] private static bool Prefix(CharacterManager __instance, Character _char) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Invalid comparison between Unknown and I4 try { if ((Object)(object)__instance == (Object)null || _char == null) { return true; } string text = UID.op_Implicit(_char.UID); if (string.IsNullOrEmpty(text)) { return true; } Character val = default(Character); if (!__instance.m_characters.TryGetValue(text, ref val)) { return true; } if ((Object)(object)val == (Object)null) { return true; } if (val == _char) { return true; } bool flag = false; try { flag = (int)_char.InstantiationType == 1 || CharacterRegistryGuard.SafeGoName(_char).StartsWith("PlayerChar ", StringComparison.Ordinal); } catch { } if (flag) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[CHAR-GUARD] duplicate PLAYER UID '" + text + "' (newcomer go='" + CharacterRegistryGuard.SafeGoName(_char) + "', registered go='" + CharacterRegistryGuard.SafeGoName(val) + "') — NOT refusing: a rejoin race must never deactivate a joining player's character; vanilla behavior stands.")); } return true; } Debug.LogError((object)$"{_char} has the same UID as {val}"); ((Component)_char).gameObject.SetActive(false); _guarded++; ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[CHAR-GUARD] AddCharacter duplicate-UID refusal ran CLEANLY for '" + text + "' (newcomer go='" + CharacterRegistryGuard.SafeGoName(_char) + "', registered go='" + CharacterRegistryGuard.SafeGoName(val) + "'): newcomer deactivated, registered instance kept. Vanilla's own branch throws FormatException (broken " + $"format string, CharacterManager.cs:1178) and leaves the duplicate active + half-initialized ({_guarded} guarded total).")); } return false; } catch (Exception ex) { ModLog log3 = CompanionRuntime.Log; if (log3 != null) { log3.LogWarning((object)("[CHAR-GUARD] AddCharacter prefix failed (" + ex.Message + ") — falling through to vanilla.")); } return true; } } } public static class CkConfig { public static class Equipment { public static ConfigEntry EnableCompanionEquipment; internal static void Bind(ConfigFile cfg) { EnableCompanionEquipment = cfg.Bind("Equipment", "EnableCompanionEquipment", true, "The companion-equipment fabric (docs/pet-armor-plan.md): consumers can give a companion a virtual equipment slot (equip captures + destroys the item, unequip re-mints it, damage wears its durability, a reagent repairs it). Off = every equip/unequip/repair refuses and wear stops, but an already-equipped item still rides the consumer's save untouched, so flipping back loses nothing. Live flip works after `ckreload`. Consumers layer their own kill-switch on top (BW: [PetArmor] EnablePetArmor); forensics: `ckequipdump` on ck_cmd.txt."); } } public static class Effigy { public static ConfigEntry EnableCompanionEffigies; public static ConfigEntry MaxBodies; public static ConfigEntry EnableEffigyHarvest; public static ConfigEntry HarvestRetryMinutes; public static ConfigEntry PinToAnchor; public static ConfigEntry PinLerp; public static ConfigEntry PinCatchUpSpeed; public static ConfigEntry PinSnapDistance; public static ConfigEntry AnchorNetLerpSpeed; public static ConfigEntry AnchorNetMoveSpeed; public static ConfigEntry AnchorAnimSpy; internal static void Bind(ConfigFile cfg) { EnableCompanionEffigies = cfg.Bind("Effigy", "EnableCompanionEffigies", true, "Show other players' pets on THIS machine as local cosmetic bodies (\"effigies\") that follow their pet's networked anchor — host sees guest pets, guests see the host's pet and each other's. Purely visual: no combat, no HUD, no sim. OFF actively despawns every effigy body on the next tick (identity rows are kept, so flipping back ON resumes with no rejoin needed). Live after 'ckreload'."); MaxBodies = cfg.Bind("Effigy", "MaxBodies", 4, "Upper bound on simultaneous effigy BODIES on this machine (each is a full brain-stripped creature clone — meshes, textures, an animator). Rows over the cap simply wait for a slot. 4 covers a full vanilla co-op party with headroom."); EnableEffigyHarvest = cfg.Bind("Effigy", "EnableEffigyHarvest", true, "SOURCELESS effigy rung (MP session 3, 2026-07-20: a guest-tamed Hyena the host had never cached ghosted forever): when an effigy's species has no template-cache entry and no nearby wild, THIS machine runs the same additive donor-scene harvest chain the local pet uses (~1s per candidate, no loading screen) and writes the result into the body-template cache — paid once, every later effigy/pet re-form of the species is then instant. Since the GUEST BODY SOURCE wave, guests harvest guest-locally on the same terms as the master (the old master-only gate ghosted a remote pet forever on a cold guest — Bug 41/E2); region/oversized-donor-only species stay a ghost honestly (expedition trips never fire for a cosmetic body). OFF = wild → cache → ghost."); HarvestRetryMinutes = cfg.Bind("Effigy", "HarvestRetryMinutes", 10f, "When an effigy donor harvest comes up dry (all candidates exhausted), how long before THAT species may try again (per-species clock — the ~2s reconcile must not re-run a ~1s-per-scene additive load chain in a loop). Live after 'ckreload'; floor 10 seconds."); PinToAnchor = cfg.Bind("Effigy", "PinToAnchor", false, "Dress-the-anchor drive mode (docs/mp-architecture-review-2026-07-21.md §4 candidate B — Cobalt's 2026-07-31 ruling: the pursued architecture direction): stop navigating effigy bodies independently and instead PIN each one onto its pet's foreign anchor replica (the anchor already streams smoothly on every machine via the game's own NetworkCharacterControl), driving the walk/idle blend from the pinned transform's displacement. Kills the independent-nav bug class (stuck ghost / walk-in-place / warp thrash). Attack-swing mirroring is MODE-INDEPENDENT since MP-ANIMASYM (2026-08-05): both modes play foreign pets' swings ([PIN]/[SWING] swing lines) — this flag chooses only the movement drive. DEFAULT OFF; live via 'ckreload' — a flip tears down and rebuilds every effigy body in the new mode."); PinLerp = cfg.Bind("Effigy", "PinLerp", 1f, "PinToAnchor position follow factor per frame: 1 = weld (direct copy of the anchor transform — the anchor is already engine-smoothed, so this is the expected setting); lower it toward 0.5 ONLY if a live session shows visible stepping. Clamped to [0.05, 1]. Live via 'ckreload'."); PinCatchUpSpeed = cfg.Bind("Effigy", "PinCatchUpSpeed", 12f, "How fast (m/s) a pinned effigy body may CLOSE a gap larger than ~1.5m to its anchor — after an anchor-missing window (replica death, downed window, late join) the body now RUNS back instead of teleporting in one frame (the shipped behavior; a live tick caught corr=0.82m at speed=46.31). Default 12 = about 1.5x pet sprint, so a catch-up reads as a sprint and the displacement-driven animator shows a run for free. Inside the dead-band the weld/PinLerp path is unchanged. Clamped to [1, 50]. Live via 'ckreload'."); PinSnapDistance = cfg.Bind("Effigy", "PinSnapDistance", 12f, "Gap (metres) at or beyond which a pinned effigy body TELEPORTS onto its anchor instead of gliding. Zone warps, scene changes and fast-travel must not produce a body visibly running across the map and through geometry; the game's own NetworkCharacterControl hard-snaps a replica for the same reason. 12 because that snap fires at 10m: the anchor can rarely BE further than ~10m from its streamed truth, so a bigger gap than that is a teleport-class event (warp, respawn, scene change) rather than a catch-up — while the ~9m 'respawned at the owner's heel' case still glides back on foot. Clamped to [5, 100]. Live via 'ckreload'."); AnchorNetLerpSpeed = cfg.Bind("Effigy", "AnchorNetLerpSpeed", 4f, "Re-tune of NetworkCharacterControl.LerpSpeed (vanilla 1) on FOREIGN companion-anchor replicas on this machine. Vanilla convergence leaves a replica ~4m behind its streamed target at pet sprint (it only hard-snaps past 10m) — invisible for a normal creature, but a pinned effigy body wears that lag on screen. The anchor is invisible, so snappier replica motion costs nothing visually. 0 = leave vanilla untouched. Clamped to [0, 20]. Live via 'ckreload' (re-stamped by the ~0.5s foreign-anchor dressing sweep). Applies to the foreign anchor replica, not the effigy body."); AnchorNetMoveSpeed = cfg.Bind("Effigy", "AnchorNetMoveSpeed", 1f, "Re-tune of NetworkCharacterControl.MoveSpeed (vanilla 0.2) on FOREIGN companion-anchor replicas on this machine — the companion of AnchorNetLerpSpeed above, same reasoning and same sweep. 0 = leave vanilla untouched. Clamped to [0, 10]. Live via 'ckreload'. Applies to the foreign anchor replica, not the effigy body."); AnchorAnimSpy = cfg.Bind("Effigy", "AnchorAnimSpy", false, "DE-RISK SPIKE 2 (docs/mp-architecture-review-2026-07-21.md §6 spike 2): every ~2s, dump every recognized FOREIGN anchor replica's live Animator on THIS machine under [ANIMSPY] — all parameter names/types/values, per-layer AnimatorStateInfo (fullPathHash/normalizedTime), state-name probes and IsInTransition. One host-pet fight while a guest runs this answers whether attack states are OBSERVABLE on the replica (the prerequisite for the Bug-40 visual fix). Pure diagnostics, no gameplay effect. DEFAULT OFF; live via 'ckreload'."); } } public static class PetFx { public static ConfigEntry EnablePetFx; internal static void Bind(ConfigFile cfg) { EnablePetFx = cfg.Bind("PetFx", "EnablePetFx", true, "Replicate pets' spell visuals across machines (the `petfx` ck store + ck.petfx.cast): persistent buff FX (ward glow, lantern halo) and one-shot cast flourishes, applied to the pet's real body on its owner's machine and to its cosmetic effigy everywhere else. Purely visual — no stats, no combat. OFF gates everything on THIS machine (sends, receives, local applies) and actively strips every worn FX subtree on the next tick; wire records are kept where already held, so flipping back ON re-applies with no rejoin. NB when the MASTER flips OFF it also stops relaying guests' FX (session-wide effect), and remote machines keep an already-worn visual until the ~90s watchdog expires it — the flip is not instant elsewhere. Live after 'ckreload'."); } } public static class Proxy { public static ConfigEntry PosStaleSeconds; internal static void Bind(ConfigFile cfg) { PosStaleSeconds = cfg.Bind("Proxy", "PosStaleSeconds", 10f, "MP-PETAIMDRIFT fix (2026-08-06): a guest streams its pet's position to the host (ck.proxy.pos) and the host's proxy anchor is placed on that stream instead of independently walking after the owner. This is the staleness window: no position for this many seconds reverts that pet's anchor to the legacy owner-follow (the guest quit/wedged, or runs an old build that never streams — those get exactly the pre-fix behavior, logged once per transition). The stream re-sends at rest " + $"every {4f:F0}s; a window that a single lost resend could span " + "would fake stale blips on a stationary pet, so reads are floored to 2x that + 2s. Live via 'ckreload'."); } } public static class Diag { public static ConfigEntry OrphanBodyPolicy; public static ConfigEntry OrphanBodyGraceSeconds; public static ConfigEntry OrphanBodyCondemnSeconds; internal static void Bind(ConfigFile cfg) { OrphanBodyPolicy = cfg.Bind("Diag", "OrphanBodyPolicy", CompanionKit.OrphanBodyPolicy.Reap, "E8d-A (2026-08-01, log-confirmed): a CompanionBody can leak past its bond's despawn and keep driving — the 2026-07-31 session ran TWO bodies for one pet, one healthy and one pinned at world origin, which the player sees as the pet flickering at their feet. The reaper destroys any live body no claim source owns (active companion, effigy binding) after the grace+condemn windows, logging its origin= — which names the leak path. Reap = warn then destroy (default); LogOnly = warn only (the live escape hatch if a legitimate holder is suspected); Off = no sweep. Live via 'ckreload'."); OrphanBodyGraceSeconds = cfg.Bind("Diag", "OrphanBodyGraceSeconds", 90f, "Never judge a body younger than this (seconds, unscaled). In-flight ladder/ghost builds hold an unclaimed body for ~4s worst-case; 90 is >20x that. Floor 10. Live via 'ckreload'."); OrphanBodyCondemnSeconds = cfg.Bind("Diag", "OrphanBodyCondemnSeconds", 20f, "How long an orphan is WARNED about ([REAPER] ... WATCHING) after the grace before it is destroyed. Floor 2. Live via 'ckreload'."); } } public static class Combat { public static ConfigEntry EnableEngagementSweep; public static ConfigEntry EngagementSweepSeconds; internal static void Bind(ConfigFile cfg) { EnableEngagementSweep = cfg.Bind("Combat", "EnableEngagementSweep", true, "Safety net for the vanilla stuck-in-combat bug (docs/stuck-in-combat-notes.md). While the local player is InCombat, periodically drop engaged-list entries whose target is DESTROYED or DEAD — vanilla removes an engagement from exactly one coroutine (Character.WaitForHostilityEnd), and if that coroutine ever throws it never restarts, so the list only grows and combat music / no-rest persist for the session. Only null/dead entries are dropped: a live enemy is never swept, at any distance, so real fights are untouched. Guests stand down (their pet-proxy sweep is a superset). Live via 'ckreload'."); EngagementSweepSeconds = cfg.Bind("Combat", "EngagementSweepSeconds", 5f, "How often (seconds, unscaled) the EnableEngagementSweep safety net runs while in combat. It is a list walk over a handful of entries, so the cost is nil; 5 keeps a stale entry's dwell short without logging noise. Floor 1. Live via 'ckreload'."); } } public static class Slope { public static ConfigEntry EnableSlopeTilt; public static ConfigEntry MaxPitchDegrees; public static ConfigEntry MaxRollDegrees; public static ConfigEntry SmoothingTau; public static ConfigEntry MaxTurnRateDegPerSec; public static ConfigEntry DeadbandDegrees; public static ConfigEntry ProbeHz; internal static void Bind(ConfigFile cfg) { EnableSlopeTilt = cfg.Bind("Slope", "EnableSlopeTilt", true, "Master KILL-SWITCH for slope alignment: pet bodies (and remote pets' effigies) tilting to match the ground instead of standing perfectly horizontal on hills. Which species actually tilt is a PER-SPECIES opt-in (quadrupeds tilt, bipeds walk upright — BW's manifest 'slopeTilt' axis / SpeciesSlopeTilt.txt; unlisted species always stand upright), so ON here changes nothing for a species that hasn't opted in. Ground is probed with two short downward raycasts under the body, smoothed and clamped so faceted terrain can't jitter the model. Purely visual: the combat anchor, navigation and MP wire stay flat/yaw-only. OFF reproduces pre-feature behavior exactly for every species. Live via 'ckreload'."); MaxPitchDegrees = cfg.Bind("Slope", "MaxPitchDegrees", 30f, "Max nose-up/nose-down tilt (degrees). Walkable navmesh slope tops out ~45°, so 30 covers everything a pet actually stands on without ever reading as broken. Live via 'ckreload'."); MaxRollDegrees = cfg.Bind("Slope", "MaxRollDegrees", 15f, "Max side-lean (degrees). Roll is where flip artifacts read worst, so it gets half the pitch budget — most of pitch-only's stability without its stiffness. Live via 'ckreload'."); SmoothingTau = cfg.Bind("Slope", "SmoothingTau", 0.15f, "Ground-normal smoothing time constant (seconds, frame-rate independent: alpha=1-exp(-dt/tau)). Lower = snappier tracking, higher = heavier creature feel; 0.25 for a lumbering species, 0.08 if tracking feels laggy. Live via 'ckreload'."); MaxTurnRateDegPerSec = cfg.Bind("Slope", "MaxTurnRateDegPerSec", 120f, "Hard cap (deg/s) on how fast the tilt may change — the guarantee that the body can never whip whatever the probe returns. Applies to the TILT only; yaw keeps its own existing 540°/s. Live via 'ckreload'."); DeadbandDegrees = cfg.Bind("Slope", "DeadbandDegrees", 5f, "Slopes shallower than this (degrees) count as FLAT and the body stands upright — kills micro-twitch on nominally-flat triangulated ground, where most perceived jitter lives. Live via 'ckreload'."); ProbeHz = cfg.Bind("Slope", "ProbeHz", 20f, "Ground raycast cadence per body (Hz; 2 casts per probe). Smoothing runs every frame, so 20 Hz sampling still renders smooth at any framerate; raise only if a fast pet visibly lags terrain changes. Clamped [1, 60]. Live via 'ckreload'."); } } public static class Expedition { public static ConfigEntry CaptureOnSceneEntry => Expedition.CaptureOnSceneEntry; public static ConfigEntry AutoWarmAtBoot => Expedition.AutoWarmAtBoot; public static ConfigEntry AlwaysWarmSpecies => Expedition.AlwaysWarmSpecies; public static ConfigEntry AutoWarmRetrySeconds => Expedition.AutoWarmRetrySeconds; public static ConfigEntry ReturnRetrySeconds => Expedition.ReturnRetrySeconds; public static ConfigEntry AllowCoop => Expedition.AllowCoop; public static ConfigEntry GuestAutoContinue => Expedition.GuestAutoContinue; } } internal static class CloneHolder { private static GameObject _holder; internal static GameObject Holder() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_holder == (Object)null) { _holder = new GameObject("CK_CloneHolder"); _holder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_holder); } return _holder; } internal static void SweepHolderOrphans() { if (!((Object)(object)_holder == (Object)null)) { Transform transform = _holder.transform; for (int num = transform.childCount - 1; num >= 0; num--) { GameObject gameObject = ((Component)transform.GetChild(num)).gameObject; CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded clone after failed build: holder orphan '" + ((Object)gameObject).name + "'")); Object.Destroy((Object)(object)gameObject); } } } internal static void DestroyStranded(GameObject go, string what) { if (!((Object)(object)go == (Object)null)) { CompanionRuntime.Log.LogWarning((object)("[BODYFACTORY] destroyed stranded clone after failed build: " + what + " '" + ((Object)go).name + "'")); Object.Destroy((Object)(object)go); } } } public class Companion { public CompanionBody Body; public CompanionAnchor Anchor; public CommandStance Stance; private CompanionCombat _combat; private Func _capturedSource; private Action _capturedSink; private CreatureAttributes _captured; public Func, ICompanionNetMirror> NetMirrorFactory; public Func CombatMandate; private readonly CompanionHost _host; private readonly ICompanionSettings _settings; private readonly OwnerRef _owner; private bool _consequenceNoteLogged; private ICompanionNetMirror _mirror; private CompanionBody _mirrorBody; private CreatureAttributes _pendEff; private float? _pendMaxHealth; private float? _pendSpeed; private float? _pendFollowFloor; private float? _pendDamageMult; private Character _pendAnchor; private int _pendAttempts; private bool _pendWasNoted; private bool _pendGaveUp; public CompanionCombat Combat { get { if (!((Object)(object)Body != (Object)null) || !((Object)(object)_combat != (Object)null)) { return null; } return _combat; } } public CreatureAttributes CapturedStats { get { if (_capturedSource == null) { return _captured; } return _capturedSource(); } set { if (_capturedSink != null) { _capturedSink(value); } else { _captured = value; } } } private ICompanionSettings Cfg => _settings ?? _host?.Settings ?? CompanionRuntime.Fallback; private ModLog Log => _host?.Log ?? CompanionRuntime.Log; public void BindCapturedStats(Func source, Action sink) { _capturedSource = source; _capturedSink = sink; } public Companion(CompanionHost host, ICompanionSettings settings = null) { _host = host; _settings = settings; _owner = new OwnerRef(); Anchor = new CompanionAnchor(host, settings); Stance = new CommandStance(); } public Companion(ICompanionSettings settings = null) : this(null, settings) { } public CompanionCombat AdoptBody(CompanionBody body, bool enableCombat, bool weaponPosture = false) { if ((Object)(object)body == (Object)null) { return null; } if ((Object)(object)Body != (Object)null && Body != body) { Log.LogError((object)($"[COMPANION] AdoptBody is replacing a LIVE body#{Body.BodyId} with body#{body.BodyId} " + "without a Despawn in between — the old body is now an orphan (expect a flicker/duplicate until the reaper collects it).")); } Body = body; body.Owner = _owner.Get; if (body.Settings == null) { body.Settings = _settings; } if (body.Host == null) { body.Host = _host; } ICompanionNetMirror companionNetMirror; if (_mirrorBody == body && _mirror != null) { companionNetMirror = _mirror; } else { companionNetMirror = NetMirrorFactory?.Invoke(_owner.Get); if (companionNetMirror != null) { ICompanionNetMirror m = companionNetMirror; body.OnAfterMove += delegate(CompanionBody b) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) m.SyncPosition(((Component)b).transform.position, ProxyPosPacing.YawDegrees(b.FacingDir.x, b.FacingDir.z)); }; } _mirror = companionNetMirror; _mirrorBody = body; } CompanionCombat companionCombat = null; if (enableCombat) { companionCombat = ((Component)body).gameObject.AddComponent(); companionCombat.Wire(Anchor, Stance, _settings, _owner.Get, _host, weaponPosture, companionNetMirror); } _combat = companionCombat; Anchor.AttachBody(body); if (!_consequenceNoteLogged && Anchor.OnAnchorDeath == null && Anchor.OnAnchorCriticallyHurt == null && Anchor.OnCombatEnded == null) { _consequenceNoteLogged = true; Log.LogMessage((object)("[COMPANION] note: no anchor consequence events wired (OnAnchorDeath / OnAnchorCriticallyHurt / OnCombatEnded) — a downed companion will silently respawn after " + $"{Cfg.AnchorRespawnSeconds:F0}s with full HP and fight-ends go unreported. Wire them on " + "Companion.Anchor if the consumer should react.")); } return companionCombat; } public void ResetProxyPosMirror() { _mirror?.InvalidatePosition(); } public void ApplyAttributes(CreatureAttributes eff, float? maxHealth = null, float? speed = null, float? followSpeedFloor = null, float? damageMultiplier = null) { _pendEff = eff; _pendMaxHealth = maxHealth; _pendSpeed = speed; _pendFollowFloor = followSpeedFloor; _pendDamageMult = damageMultiplier; RebudgetIfAnchorChanged(); ApplyLatched(); } private void RebudgetIfAnchorChanged() { Character current = Anchor.Current; if (current != _pendAnchor) { _pendAnchor = current; _pendAttempts = 0; _pendWasNoted = false; _pendGaveUp = false; } } private void ApplyLatched() { ApplyAttributesNow(_pendEff, _pendMaxHealth, _pendSpeed, _pendFollowFloor, _pendDamageMult); } public void RetryPendingAttributes() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) RebudgetIfAnchorChanged(); if (_pendGaveUp) { return; } bool flag = !PhotonNetwork.isNonMasterClientInRoom; if (!PendingApplyPolicy.IsAnythingPending(Anchor.StatGate, Anchor.VitalsPending, flag)) { if (_pendWasNoted) { _pendWasNoted = false; Log.LogMessage((object)(string.Format("{0} late attribute apply LANDED after {1} frame(s) ", CompanionRuntime.Tag("STATS", Cfg), _pendAttempts) + "(V88: the anchor was not ready when the host first applied — this is the retry, not the sim tick).")); } } else if (!PendingApplyPolicy.ShouldRetry(Anchor.StatGate, Anchor.VitalsPending, _pendAttempts, flag, 240)) { _pendGaveUp = true; Log.LogWarning((object)(CompanionRuntime.Tag("STATS", Cfg) + " GAVE UP re-applying the companion's attributes after " + $"{_pendAttempts} frames — held by {PendingApplyPolicy.DescribeHeld(Anchor.StatGate, Anchor.VitalsPending, flag)}" + ". Said once per anchor life: the host's own per-tick apply still runs (pre-V88 behavior), and a fresh anchor gets a fresh retry budget." + (flag ? "" : " NB this box is a GUEST — anchor-shaped gates AND vitals are proxied to the master, so neither counts as pending here; a guest give-up now means NoStats/ZeroBaseline held (a real, role-independent problem)."))); } else { _pendAttempts++; _pendWasNoted = true; ApplyLatched(); } } private void ApplyAttributesNow(CreatureAttributes eff, float? maxHealth, float? speed, float? followSpeedFloor, float? damageMultiplier) { if (maxHealth.HasValue) { Anchor.ApplyVitals(maxHealth.Value); } Anchor.ApplyCreatureStats(eff); CompanionCombat combat = Combat; if ((Object)(object)combat != (Object)null) { if (damageMultiplier.HasValue) { combat.SetDamageMultiplier(damageMultiplier.Value); } combat.SetAttackProfile(eff); } if ((Object)(object)Body != (Object)null) { if (speed.HasValue) { Body.Speed = speed.Value; } if (followSpeedFloor.HasValue) { Body.FollowSpeedFloor = followSpeedFloor.Value; } } } public void Tick(Character player, MonoBehaviour host) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Invalid comparison between Unknown and I4 //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) if (!Cfg.AnchorEnabled) { return; } if ((Object)(object)Body == (Object)null) { Action val = BodilessAnchorRule.Decide(Cfg.BodilessAnchor, (Object)(object)Anchor.Current != (Object)null); if ((int)val != 1) { if ((int)val == 2) { Anchor.Upkeep(player, host, bodyFighting: false, Stance.Passive, CombatMandate); } } else { Anchor.DestroyCurrent(); } return; } bool bodyFighting = (Object)(object)Body.CombatTarget != (Object)null; Anchor.Upkeep(player, host, bodyFighting, Stance.Passive, CombatMandate); Anchor.ApplyVoice(Body); Transform val2 = (Anchor.HasLiveAnchor ? ((Component)Anchor.Current).transform : null); Transform val3 = (((int)Cfg.GlueMode == 2) ? null : val2); if ((Object)(object)Body.FollowOverride != (Object)(object)val3) { Body.FollowOverride = val3; } if ((int)Cfg.GlueMode == 0 && (Object)(object)Body.CombatTarget != (Object)null) { Anchor.PinTo(((Component)Body).transform.position); } } public void Despawn() { Log.LogMessage((object)("[COMPANION] despawn: unwelding + destroying the anchor and body, resetting stance. (body#" + (((Object)(object)Body != (Object)null) ? Body.BodyId.ToString() : "none") + ", anchor viewID=" + (((Object)(object)Anchor.Current != (Object)null) ? CompanionAnchor.ViewIdOf(Anchor.Current).ToString() : "none") + ")")); Anchor.DetachBody(); Anchor.DestroyCurrent(); Stance.Reset(); if ((Object)(object)Body != (Object)null) { Object.Destroy((Object)(object)((Component)Body).gameObject); } Body = null; _combat = null; _mirror = null; _mirrorBody = null; } public void DropBody() { Log.LogMessage((object)("[COMPANION] drop body: destroying body#" + (((Object)(object)Body != (Object)null) ? Body.BodyId.ToString() : "none") + " ONLY — the anchor, the command stance and the bond aggregate are kept (body rebuild).")); Anchor.DetachBody(); if ((Object)(object)Body != (Object)null) { Object.Destroy((Object)(object)((Component)Body).gameObject); } Body = null; _combat = null; _mirror = null; _mirrorBody = null; } } public sealed class CommandStance { private Character _commanded; public bool Passive { get; private set; } public bool Stay { get; private set; } public CommandMode Mode { get { if (!Stay) { if (Passive) { return (CommandMode)1; } return (CommandMode)0; } return (CommandMode)2; } } public Character CommandedTarget { get { if ((Object)(object)_commanded != (Object)null && _commanded.Alive) { return _commanded; } _commanded = null; return null; } } public event Action Changed; public void CommandEngage(Character target) { Set(passive: false, stay: false, target); } public void CommandDisengage() { Set(passive: true, stay: false, null); } public void CommandStay() { Set(passive: true, stay: true, null); } public void Reset() { Set(passive: false, stay: false, null); } private void Set(bool passive, bool stay, Character commanded) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) CommandMode mode = Mode; Passive = passive; Stay = stay; _commanded = commanded; if (mode != Mode) { this.Changed?.Invoke(Mode); } } public void DropCommanded() { _commanded = null; } } public sealed class OwnerRef { private readonly Func _resolve; private Character _cached; public OwnerRef(Func resolve = null) { _resolve = resolve ?? ((Func)(() => CompanionRuntime.LocalPlayer())); } public Character Get() { if ((Object)(object)_cached == (Object)null) { _cached = _resolve(); } return _cached; } } public sealed class CompanionAnchor { private const int InstantiationTypeNetwork = 3; internal static readonly ViewLease Lease = new ViewLease("[ANCHOR]"); private static float s_sweepAt; private readonly CompanionHost _host; private readonly ICompanionSettings _cfg; private readonly AnchorStats _stats; private readonly AnchorDressing _dressing; private readonly AnchorPhysics _physics; private readonly AnchorTargeting _targeting; private readonly AnchorWeld _weld; private static readonly Dictionary s_liveAnchors = new Dictionary(); private bool _linkedSummon; private string _linkedOwnerUid; public Action OnAnchorDeath; public Action OnAnchorCriticallyHurt; public Action OnCombatEnded; public Func ExternallyDriven; private float _deadUntil; private bool _critFired; private bool _wasInCombat; private float _leashReconAt; private float _recallAt; private float _leashSuppressLogAt; private float _recallLogAt; private float _passiveCalmLogAt; private float _voidLogAt = -999f; private CharacterAI _ai; private float _lastHp; private float _hpDriftLogAt; private CompanionBody _body; private const float RecallThrottleSeconds = 3f; private ICompanionSettings Cfg => _cfg ?? _host?.Settings ?? CompanionRuntime.Fallback; private ModLog Log => _host?.Log ?? CompanionRuntime.Log; private string TagAnchor => CompanionRuntime.Tag("ANCHOR", Cfg); public Character Current { get; private set; } public bool HasLiveAnchor { get { if ((Object)(object)Current != (Object)null) { return Current.Alive; } return false; } } public CharacterAI AI { get { if (!((Object)(object)_ai != (Object)null)) { return _ai = (((Object)(object)Current != (Object)null) ? ((Component)Current).GetComponent() : null); } return _ai; } } public Character LastAssertedTarget => _targeting.AssertedLock; public bool IsFighting { get { if (HasLiveAnchor) { if (!Current.InCombat) { return (Object)(object)AnchorTargeting.LockedEnemy(AI) != (Object)null; } return true; } return false; } } internal ICompanionSettings DriveCfg => Cfg; internal bool AgentDrivesTransform { get { CharacterAI aI = AI; if ((Object)(object)aI != (Object)null && (Object)(object)aI.NavMeshAgent != (Object)null) { return aI.NavMeshAgent.updatePosition; } return false; } } public StatApplyGate StatGate => _stats.LastGate; public bool VitalsPending => _stats.VitalsPending; public float HealthFraction => _stats.HealthFraction; internal static void SweepLease() { if (!(Time.unscaledTime - s_sweepAt < 2f)) { s_sweepAt = Time.unscaledTime; Lease.SweepPending((Action, LeaseVerdict, float>)AnchorAgedOutNotice, (Action)null); } } private static void AnchorAgedOutNotice(LeaseEntry e, LeaseVerdict verdict, float age) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)verdict == 3) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)($"[ANCHOR] viewID {e.ViewId} parked on live anchor '{((Object)e.Body).name}' " + $"for {age:0}s — CORRECT: the lease spans the anchor's whole life and releases when the ghost " + "is destroyed (any path, tracked or not).")); } } else { ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)($"[ANCHOR] viewID {e.ViewId} parked on '{((Object)e.Body).name}' for {age:0}s " + "WITHOUT the may-persist declaration — an anchor park should always declare it; investigate.")); } } } public CompanionAnchor(CompanionHost host, ICompanionSettings cfg = null) : this(cfg) { _host = host; } public CompanionAnchor(ICompanionSettings cfg = null) { _cfg = cfg; _stats = new AnchorStats(() => Current, () => Cfg); _dressing = new AnchorDressing(() => Current, () => Cfg); _physics = new AnchorPhysics(() => Current, () => Cfg); _targeting = new AnchorTargeting(() => Current, () => AI, () => Cfg); _weld = new AnchorWeld(() => Current, () => AI, () => Cfg, _physics); } public static bool IsAnchor(Character c) { if ((Object)(object)c != (Object)null) { return s_liveAnchors.ContainsKey(c); } return false; } private static bool SummonSlotHeldByAnother(CompanionAnchor self, string playerUid) { foreach (KeyValuePair s_liveAnchor in s_liveAnchors) { CompanionAnchor value = s_liveAnchor.Value; if (value != self && value._linkedSummon && !(value._linkedOwnerUid != playerUid) && (Object)(object)s_liveAnchor.Key != (Object)null && s_liveAnchor.Key.Alive) { return true; } } return false; } private static void PruneDeadAnchors() { List list = null; foreach (KeyValuePair s_liveAnchor in s_liveAnchors) { if ((Object)(object)s_liveAnchor.Key == (Object)null) { (list ?? (list = new List())).Add(s_liveAnchor.Key); } } if (list == null) { return; } foreach (Character item in list) { s_liveAnchors.Remove(item); } } public static bool ShouldHideSummonIcon(Character summon) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)summon == (Object)null) { return false; } if (s_liveAnchors.TryGetValue(summon, out var value)) { return value.Cfg.AnchorHideSummonIcon; } if (AnchorSentinel.IsAnchorUid(UID.op_Implicit(summon.UID))) { return CompanionHost.AnyWantsSummonIconHidden(); } return false; } public void AttachBody(CompanionBody body) { if (_body != body) { DetachBody(); _body = body; if ((Object)(object)body != (Object)null) { body.OnAfterMove += GlueTick; body.CompanionRef = AnchorTransformOrNull; } } } public void DetachBody() { if ((Object)(object)_body == (Object)null) { _body = null; return; } _body.OnAfterMove -= GlueTick; if (_body.CompanionRef == new Func(AnchorTransformOrNull)) { _body.CompanionRef = null; } _body = null; } private Transform AnchorTransformOrNull() { if (!HasLiveAnchor) { return null; } return ((Component)Current).transform; } public void Upkeep(Character player, MonoBehaviour host, bool bodyFighting, bool stancePassive = false, Func combatMandate = null) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } if (!CompanionRuntime.IsSanePosition(((Component)player).transform.position)) { if (Time.unscaledTime - _voidLogAt > 5f) { _voidLogAt = Time.unscaledTime; Vector3 position = ((Component)player).transform.position; Log.LogWarning((object)($"{TagAnchor} holding: player position ({position.x:F1}, {position.y:F1}, {position.z:F1}) reads as " + $"void/staging (floor y={-3000f:F0}), so no anchor will spawn, leash " + "or recall. If you are standing in a REAL place, this heuristic is misfiring (bug 33).")); } } else if (HasLiveAnchor) { _physics.Sync(); float num = Vector3.Distance(((Component)Current).transform.position, ((Component)player).transform.position); CharacterAI aI = AI; Character val = AnchorTargeting.LockedEnemy(aI); string arg = "none"; if ((Object)(object)val != (Object)null) { try { arg = val.Name; } catch { arg = "unreadable"; } } bool flag = false; if ((Object)(object)val != (Object)null && AnchorTargeting.IsProtectedFrom(Current, val)) { Log.LogMessage((object)(TagAnchor + " released lock on PROTECTED '" + val.Name + "' (AggroKit override: the anchor may not target it).")); if ((Object)(object)_targeting.AssertedLock == (Object)(object)val) { _targeting.AssertedLock = null; } _targeting.CalmAnchor(aI); val = null; flag = true; } float num2 = (((Object)(object)val != (Object)null) ? Vector3.Distance(((Component)Current).transform.position, ((Component)val).transform.position) : (-1f)); if ((Object)(object)val != (Object)null && num2 > Cfg.CombatLeashDistance) { Log.LogMessage((object)$"{TagAnchor} stale combat lock: '{val.Name}' is {num2:F0}m from the anchor — releasing both sides (bug-4 fix)."); if ((Object)(object)_targeting.AssertedLock == (Object)(object)val) { _targeting.AssertedLock = null; } _targeting.CalmAnchor(aI); val = null; flag = true; } bool flag2 = Current.InCombat || (Object)(object)val != (Object)null; bool flag3 = flag2; bool flag4; if (combatMandate == null) { flag4 = !stancePassive; } else { try { flag4 = combatMandate(); } catch (Exception ex) { flag4 = false; if (Time.unscaledTime - _passiveCalmLogAt > 10f) { Log.LogWarning((object)(TagAnchor + " combat-mandate seam threw (" + ex.Message + ") — treating as un-mandated this tick.")); } } } if (ProxyStance.ShouldCalm(stancePassive, flag2, flag4)) { if (Time.unscaledTime - _passiveCalmLogAt > 10f) { _passiveCalmLogAt = Time.unscaledTime; Log.LogMessage((object)(stancePassive ? (TagAnchor + " re-calming the anchor — the companion is PASSIVE and something re-engaged it (" + DescribeAI() + "). It absorbs hits but does not fight while disengaged.") : (TagAnchor + " re-calming the anchor — self-lock with no active combat mandate (" + DescribeAI() + "). It absorbs hits but does not brawl un-mandated."))); } if ((Object)(object)_targeting.AssertedLock == (Object)(object)val) { _targeting.AssertedLock = null; } _targeting.CalmAnchor(aI); val = null; flag2 = false; flag = true; } bool flag5 = false; if (ExternallyDriven != null) { try { flag5 = ExternallyDriven(); } catch { } } bool flag6 = !AnchorGlue.LeashApplies(Cfg.GlueMode, (Object)(object)_body != (Object)null, flag2 || bodyFighting, flag5); if ((Object)(object)val != (Object)null && !stancePassive && val.Alive && (num2 < 0f || num2 <= Cfg.CombatLeashDistance)) { player.AddCombatEngagement(val); } float num3 = (flag2 ? Cfg.CombatLeashDistance : Cfg.AnchorLeashDistance); if (flag2 != _wasInCombat) { _wasInCombat = flag2; Log.LogMessage((object)string.Format("{0} combat {1}: {2} playerDist={3:F1} glued={4}", TagAnchor, flag2 ? "ENTER" : "EXIT", DescribeAI(), num, flag6)); if (!flag2) { OnCombatEnded?.Invoke(); } } if (flag6 && num > num3 && Time.unscaledTime - _leashSuppressLogAt > 10f) { _leashSuppressLogAt = Time.unscaledTime; Log.LogMessage((object)($"{TagAnchor} leash suppressed: dist={num:F1}m > leash={num3:F0}m " + "(" + (flag2 ? "combat" : "follow") + " leash) but the anchor's position is not its own " + $"(GlueMode={Cfg.GlueMode} body={(Object)(object)_body != (Object)null} driven={flag5}) — the BODY's own leash " + "owns recovery and drags the welded anchor with it. Not a defect.")); } if (!flag6 && flag2 && num > Cfg.AnchorLeashDistance && Time.time - _leashReconAt > 3f) { _leashReconAt = Time.time; Log.LogMessage((object)string.Format("{0} {1:F0}m behind the player but fighting ({2}) — leash relaxed to {3:F0}m.", CompanionRuntime.Tag("ANCHOR-RECON", Cfg), num, DescribeAI(), num3)); } if ((!ProxyStance.ShouldRecall(stancePassive, flag6 || (Object)(object)_body != (Object)null, flag3) || !Recall(player, "passive stance", num)) && !flag6 && num > num3) { if (flag2) { Log.LogMessage((object)$"{TagAnchor} player left the fight ({num:F0}m away) — calming the anchor and recalling it (bug-4 fix)."); _targeting.CalmAnchor(aI); } if (TeleportToOwnerFeet(player)) { Log.LogMessage((object)($"{TagAnchor} leash warp: dist={num:F1}m > leash={num3:F0}m " + "(" + (flag2 ? "combat" : "follow") + " leash) reason=Leash — teleported the anchor to the owner's feet.")); } else if (Time.unscaledTime - _recallLogAt > 10f) { _recallLogAt = Time.unscaledTime; Log.LogMessage((object)($"{TagAnchor} leash recall could NOT place the anchor ({num:F0}m away):" + " no navmesh at the owner's feet — leaving it to walk back, retrying next tick.")); } } CharacterStats stats = Current.Stats; if ((Object)(object)stats == (Object)null) { return; } float currentHealth = stats.CurrentHealth; float maxHealth = stats.MaxHealth; if (_lastHp > 0f && currentHealth < _lastHp - 0.25f && !flag2 && Time.time - _hpDriftLogAt > 5f) { _hpDriftLogAt = Time.time; string text = "none"; try { StatusEffectManager statusEffectMngr = Current.StatusEffectMngr; if ((Object)(object)statusEffectMngr != (Object)null && statusEffectMngr.Statuses != null && statusEffectMngr.Statuses.Count > 0) { List list = new List(); foreach (StatusEffect status in statusEffectMngr.Statuses) { if ((Object)(object)status != (Object)null) { list.Add(status.IdentifierName); } } if (list.Count > 0) { text = string.Join(",", list.ToArray()); } } } catch (Exception ex2) { text = "probe-failed:" + ex2.Message; } Log.LogMessage((object)($"{TagAnchor} hp drift OUT OF COMBAT: {_lastHp:F1} -> {currentHealth:F1} ({currentHealth - _lastHp:F1}); statuses=[{text}] " + $"locked='{arg}' ai=({DescribeAI()}) calmedThisTick={flag}")); } _lastHp = currentHealth; _stats.CaptureHealthFraction(); if (!_critFired && maxHealth > 0f && currentHealth < Cfg.CritHealthFraction * maxHealth) { _critFired = true; OnAnchorCriticallyHurt?.Invoke(); } else if (_critFired && maxHealth > 0f && currentHealth > Cfg.CritRearmFraction * maxHealth) { _critFired = false; } } else if ((Object)(object)Current != (Object)null) { if (!Current.Alive) { HandleDeath(); } } else if (!(Time.time < _deadUntil)) { Spawn(player, host, Cfg.AnchorInvisible); } } private bool TeleportToOwnerFeet(Character player) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) Vector3 reference = ((Component)player).transform.position + ((Component)player).transform.forward * 0.5f; if (!NavProbe.SampleAtFeet(reference, 1.5f, out var pos)) { return false; } Current.Teleport(pos, Quaternion.identity); return true; } public bool Recall(Character player, string reason, float knownDist = -1f) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null || !HasLiveAnchor) { return false; } if (Time.time - _recallAt < 3f) { return false; } _recallAt = Time.time; float num = ((knownDist >= 0f) ? knownDist : Vector3.Distance(((Component)Current).transform.position, ((Component)player).transform.position)); bool isFighting = IsFighting; _targeting.AssertedLock = null; _targeting.CalmAnchor(AI); bool flag = TeleportToOwnerFeet(player); if (Time.unscaledTime - _recallLogAt > 10f) { _recallLogAt = Time.unscaledTime; Log.LogMessage((object)($"{TagAnchor} recall ({reason}): the anchor was {num:F0}m from its owner, fighting={isFighting}" + " — calmed and " + (flag ? "teleported to the owner's feet" : "left to walk back (no navmesh at the owner's feet)") + ";" + $" neither the {Cfg.AnchorLeashDistance:F0}m follow leash nor the {Cfg.CombatLeashDistance:F0}m combat leash was waited for.")); } return true; } public bool Spawn(Character player, MonoBehaviour host, bool hide) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0240: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Expected O, but got Unknown //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown //IL_0278: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.isNonMasterClientInRoom) { return false; } PruneDeadAnchors(); if ((Object)(object)Current != (Object)null) { Log.LogMessage((object)(TagAnchor + " one already exists.")); return false; } Vector3 val = ((Component)player).transform.position + ((Component)player).transform.forward * 2f; if (NavProbe.SampleAtFeet(val, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos)) { val = pos; } bool flag = Cfg.AnchorLinkSummonSlot; if (flag && SummonSlotHeldByAnother(this, UID.op_Implicit(player.UID))) { flag = false; Log.LogWarning((object)(TagAnchor + " summon-slot claim REFUSED — another companion's anchor already holds the vanilla one-summon slot for this player (first claim wins; D5a arbitration). This anchor spawns UNLINKED: combat body fully functional, just not player.CurrentSummon.")); } string text = (flag ? UID.op_Implicit(player.UID) : string.Empty); string text2 = AnchorSentinel.MakeUid(UID.op_Implicit(player.UID)); GameObject val2; try { val2 = CharacterManager.Instance.InstantiateNetworkCharacter(Cfg.GhostPrefabName, val, Quaternion.identity, 3, text, text2, -1); } catch (Exception ex) { Log.LogWarning((object)(TagAnchor + " spawn threw: " + ex)); return false; } if ((Object)(object)val2 == (Object)null) { Log.LogWarning((object)(TagAnchor + " spawn returned null.")); return false; } PhotonView component = val2.GetComponent(); if ((Object)(object)component != (Object)null && component.viewID > 0) { Lease.DeferRelease(component.viewID, val2, true); } Character component2 = val2.GetComponent(); if ((Object)(object)component2 == (Object)null) { Log.LogWarning((object)(TagAnchor + " spawned object has no Character.")); Object.Destroy((Object)(object)val2); return false; } component2.Lifetime = -1f; CharacterAI component3 = val2.GetComponent(); if ((Object)(object)component3 != (Object)null) { AIState currentAiState = component3.CurrentAiState; AISWander val3 = (AISWander)(object)((currentAiState is AISWander) ? currentAiState : null); if (val3 != null) { val3.FollowTransform = ((Component)player).transform; goto IL_0231; } } Log.LogWarning((object)(TagAnchor + " expected AISWander as the initial state — follow not wired.")); goto IL_0231; IL_0231: component2.OnDeath = (UnityAction)Delegate.Combine((Delegate?)(object)component2.OnDeath, (Delegate?)new UnityAction(HandleDeath)); Current = component2; s_liveAnchors[component2] = this; _linkedSummon = flag; _linkedOwnerUid = (flag ? UID.op_Implicit(player.UID) : null); _ai = component3; _lastHp = 0f; _critFired = false; _wasInCombat = false; _recallAt = 0f; _recallLogAt = 0f; _targeting.AssertedLock = null; _weld.ResetWarpNote(); _dressing.ResetVoiceSource(); if ((Object)(object)host != (Object)null) { host.StartCoroutine(_physics.StampWhenReady(component2)); } if (hide && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.HideSweep(component2)); } if (!Cfg.AnchorDealsDamage && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.NeuterWeaponWhenReady(component2)); } if (Cfg.SpeciesVoice && (Object)(object)host != (Object)null) { host.StartCoroutine(_dressing.MuteSweep(component2)); } _dressing.ApplyHealthBarConfig(component2); Log.LogMessage((object)($"{TagAnchor} spawned (hp={(((Object)(object)component2.Stats != (Object)null) ? component2.Stats.CurrentHealth : (-1f)):F0}, " + $"linked={flag}, invisible={hide}, viewID={ViewIdOf(component2)}, uid={text2}).")); return true; } internal static string ViewIdOf(Character c) { try { return ((Object)(object)c != (Object)null && (Object)(object)((MonoBehaviour)c).photonView != (Object)null) ? ((MonoBehaviour)c).photonView.viewID.ToString() : "-"; } catch { return "-"; } } private void HandleDeath() { Log.LogMessage((object)$"{TagAnchor} anchor died (viewID={ViewIdOf(Current)}) — auto-respawn in {Cfg.AnchorRespawnSeconds:F0}s."); _deadUntil = Time.time + Cfg.AnchorRespawnSeconds; Character current = Current; Current = null; if ((Object)(object)current != (Object)null) { s_liveAnchors.Remove(current); } _ai = null; _targeting.AssertedLock = null; _stats.ForgetAnchor(); _stats.ResetHealthFraction(); _physics.Forget(); _dressing.PlayDeathVocal(current); _dressing.ResetVoiceSource(); if ((Object)(object)current != (Object)null) { EngagementHygiene.ReleaseLocksOn(current); try { current.StartDestroy(); } catch (Exception ex) { Log.LogWarning((object)(TagAnchor + " corpse StartDestroy failed (viewID=" + ViewIdOf(current) + ") — an invisible stand-in may be left in the scene: " + ex.Message)); } } EngagementHygiene.SweepStaleAll("anchor death"); OnAnchorDeath?.Invoke(); } public void ClearAssertedLock() { _targeting.AssertedLock = null; } public void Calm() { _targeting.Calm(); } public void PinTo(Vector3 puppetPos) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) _targeting.PinTo(puppetPos); } public void UnifyLock(Character target) { _targeting.UnifyLock(target); } public void GlueTick(CompanionBody body) { _weld.GlueTick(body); } internal void DriveApply(Vector3 pos, Vector3 facingFlat, AnchorGlueAction act, float sep, bool logJump = true) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _weld.Apply(pos, facingFlat, act, sep, logJump); } public void SetFollowTarget(Transform target) { CharacterAI aI = AI; if ((Object)(object)aI == (Object)null || aI.AiStates == null) { return; } AIState[] aiStates = aI.AiStates; foreach (AIState val in aiStates) { AISWander val2 = (AISWander)(object)((val is AISWander) ? val : null); if (val2 != null) { val2.FollowTransform = target; break; } } } public Transform GetFollowTarget() { CharacterAI aI = AI; if ((Object)(object)aI == (Object)null || aI.AiStates == null) { return null; } AIState[] aiStates = aI.AiStates; foreach (AIState val in aiStates) { AISWander val2 = (AISWander)(object)((val is AISWander) ? val : null); if (val2 != null) { return val2.FollowTransform; } } return null; } private string DescribeAI() { try { if (!HasLiveAnchor) { return "no-anchor"; } CharacterAI component = ((Component)Current).GetComponent(); if ((Object)(object)component == (Object)null) { return "no-CharacterAI"; } string text = (((Object)(object)component.CurrentAiState != (Object)null) ? ((object)component.CurrentAiState).GetType().Name : "none"); Character val = (((Object)(object)component.TargetingSystem != (Object)null) ? component.TargetingSystem.LockedCharacter : null); return "state=" + text + " locked=" + (((Object)(object)val != (Object)null) ? val.Name : "none"); } catch (Exception ex) { return "ai-probe-failed:" + ex.Message; } } public void ApplyCreatureStats(CreatureAttributes eff) { _stats.ApplyCreatureStats(eff); } public void DumpCreatureStats() { _stats.DumpCreatureStats(); } public void ApplyVitals(float maxHealth) { _stats.ApplyVitals(maxHealth); } public void EnableHealthPersistence(Func enabled) { _stats.EnableHealthPersistence(enabled); } public void SeedHealthFraction(float f) { _stats.SeedHealthFraction(f); } public bool RestoreLiveHealth(float frac) { return _stats.RestoreLiveHealth(frac); } public bool ApplyTemperatureDrain(float amount) { return _stats.ApplyTemperatureDrain(amount); } public string HealthSummary() { return _stats.HealthSummary(_critFired); } public bool TryGetHealth(out float current, out float max) { return _stats.TryGetHealth(out current, out max); } public bool TryGetResistances(int count, out float[] resistancesPct) { resistancesPct = null; if (!HasLiveAnchor || (Object)(object)Current.Stats == (Object)null || count <= 0) { return false; } float[] array = new float[count]; for (int i = 0; i < count; i++) { array[i] = Current.Stats.GetDamageResistance((Types)i) * 100f; } resistancesPct = array; return true; } public bool Heal() { if (!_stats.Heal()) { return false; } _critFired = false; return true; } public bool HealAmount(float amount, bool quiet = false) { bool reArmCrit; bool result = _stats.HealAmount(amount, out reArmCrit, quiet); if (reArmCrit) { _critFired = false; } return result; } public bool SetHealth(float value) { bool reArmCrit; bool result = _stats.SetHealth(value, out reArmCrit); if (reArmCrit) { _critFired = false; } return result; } public void DestroyCurrent() { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown if (!((Object)(object)Current == (Object)null)) { Character current = Current; Current = null; s_liveAnchors.Remove(current); _ai = null; _targeting.AssertedLock = null; _targeting.ForgetSkipMarker(); _stats.ForgetAnchor(); _physics.Forget(); _dressing.ResetVoiceSource(); _dressing.ResetBodySound(); current.OnDeath = (UnityAction)Delegate.Remove((Delegate?)(object)current.OnDeath, (Delegate?)new UnityAction(HandleDeath)); string text = ViewIdOf(current); EngagementHygiene.ReleaseLocksOn(current); EngagementHygiene.RemoveFromAll(current, "anchor teardown"); try { current.StartDestroy(); } catch (Exception ex) { Log.LogWarning((object)(TagAnchor + " destroy failed: " + ex.Message)); } EngagementHygiene.SweepStaleAll("anchor teardown"); Log.LogMessage((object)(TagAnchor + " destroyed (viewID=" + text + ").")); } } public void SyncPhysics() { _physics.Sync(); } public string PhysicsSummary() { return _physics.Dump(); } public string PhysicsFragment() { return _physics.Fragment(); } public void ApplyVoice(CompanionBody body) { _dressing.ApplyVoice(body); } } public sealed class AuraRecipe { public string Key; public int Slot; public FxRecipe Recipe; } public static class CompanionAura { private sealed class Row { public AuraRecipe Recipe; public bool? Force; } private sealed class Capture { public AuraCaptureState State = (AuraCaptureState)1; public Transform Subtree; public string Note = "not attempted yet"; } private const string Tag = "[AURA]"; private const string CacheHolderName = "CK_AuraTemplates"; private static readonly Dictionary _rows = new Dictionary(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary _captures = new Dictionary(StringComparer.Ordinal); private static GameObject _cacheHolder; private static readonly HashSet _unknownWarned = new HashSet(StringComparer.OrdinalIgnoreCase); private static ModLog Log => CompanionRuntime.Log; public static bool Register(AuraRecipe recipe) { if (recipe == null || recipe.Recipe == null) { Log.LogWarning((object)"[AURA] Register refused: null recipe."); return false; } List list = new List(); foreach (Row value in _rows.Values) { list.Add(value.Recipe.Slot); } string text = AuraBook.ValidateRegistration(recipe.Key, recipe.Slot, (IEnumerable)_rows.Keys, (IEnumerable)list); if (text != null) { Log.LogWarning((object)("[AURA] Register refused: " + text)); return false; } _rows[recipe.Key] = new Row { Recipe = recipe }; Log.LogMessage((object)("[AURA]" + $" registered '{recipe.Key}' slot {recipe.Slot} " + $"(source {recipe.Recipe.Source}" + ((recipe.Recipe.Source == FxSource.CreatureFxSubtree) ? (": species '" + recipe.Recipe.SpeciesKey + "' filter '" + recipe.Recipe.SubtreeFilter + "'") : "") + ").")); return true; } public static bool IsAuraKey(string spellKey) { return AuraBook.IsAuraKey(spellKey); } internal static FxRecipe ResolveRecipe(PetFxRequest req) { if (req == null || !IsAuraKey(req.SpellKey)) { return null; } if (!req.Persist) { return null; } if (!_rows.TryGetValue(req.SpellKey, out var value)) { return null; } if (req.Slot != value.Recipe.Slot) { return null; } FxRecipe recipe = value.Recipe.Recipe; if (recipe.Source == FxSource.CreatureFxSubtree) { EnsureCapture(recipe.SpeciesKey, recipe.SubtreeFilter); } return recipe; } public static void SetActive(string ownerUid, string auraKey, bool active) { if (string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(auraKey)) { return; } if (!_rows.TryGetValue(auraKey, out var value)) { if (_unknownWarned.Add(auraKey)) { Log.LogWarning((object)("[AURA] SetActive for unregistered aura '" + auraKey + "' — no-op. (Warned once per key.)")); } } else { bool active2 = value.Force ?? active; CompanionPetFx.SyncOwnedFx(ownerUid, auraKey, value.Recipe.Slot, active2); } } public static bool SetForce(string auraKey, bool? state) { if (string.IsNullOrEmpty(auraKey) || !_rows.TryGetValue(auraKey, out var value)) { return false; } value.Force = state; return true; } internal static bool TryGetCapturedSubtree(string speciesKey, string filter, out Transform subtree) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Invalid comparison between Unknown and I4 subtree = null; if (!_captures.TryGetValue(AuraBook.CaptureKey(speciesKey, filter), out var value)) { return false; } if ((int)value.State != 3 || (Object)(object)value.Subtree == (Object)null) { return false; } subtree = value.Subtree; return true; } private static void EnsureCapture(string speciesKey, string filter) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Invalid comparison between Unknown and I4 //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) string key = AuraBook.CaptureKey(speciesKey, filter); if (_captures.TryGetValue(key, out var value)) { if ((int)value.State == 3 && (Object)(object)value.Subtree == (Object)null) { value.State = (AuraCaptureState)1; value.Note = "cached subtree died — re-capturing"; } if ((int)value.State != 1) { return; } } Capture capture = value ?? (_captures[key] = new Capture()); List list = AuraBook.FilterCandidates(filter); if (list.Count == 0) { capture.State = (AuraCaptureState)4; capture.Note = "empty subtree filter"; Log.LogWarning((object)("[AURA] species '" + speciesKey + "': empty subtree filter — an aura may not clone a donor's ENTIRE FX set; name the nodes. Capture failed (once).")); return; } if (BodyTemplateCache.TryResolve(speciesKey, out var template) && template != null && (Object)(object)template.Dormant != (Object)null && !template.Substituted) { FinishCapture(capture, speciesKey, filter, list, template.Dormant.transform, "resident template"); return; } if (PhotonNetwork.isNonMasterClientInRoom) { if (capture.Note == "not attempted yet") { capture.Note = "guest — waiting for a resident template (no guest harvest)"; Log.LogMessage((object)("[AURA] species '" + speciesKey + "': guest machine defers capture until a body template is resident (harvest is master/solo-only).")); } return; } if (!DonorHarvest.TryGetDonorScenes(speciesKey, out var sceneNames, out var searchTerm)) { capture.State = (AuraCaptureState)4; capture.Note = "species not in DonorScenes.txt"; Log.LogWarning((object)("[AURA] species '" + speciesKey + "' is not in the donor-scene table — aura capture failed (once). Check the species key against DonorScenes.txt.")); return; } capture.State = (AuraCaptureState)2; capture.Note = "harvest in flight"; Log.LogMessage((object)("[AURA] capturing '" + filter + "' off '" + speciesKey + "' via donor harvest (once per session — cached forever after).")); ((MonoBehaviour)Plugin.Instance).StartCoroutine(HarvestCapture(capture, speciesKey, filter, list, sceneNames, searchTerm)); } private static IEnumerator HarvestCapture(Capture c, string speciesKey, string filter, List candidates, List scenes, string searchTerm) { object result = null; yield return DonorHarvest.HarvestChain(scenes, searchTerm, (Character src) => ExtractClone(((Object)(object)src != (Object)null) ? ((Component)src).transform : null, speciesKey, candidates), delegate(object r) { result = r; }); object obj = result; GameObject val = (GameObject)((obj is GameObject) ? obj : null); if ((Object)(object)val == (Object)null) { c.State = (AuraCaptureState)4; c.Note = "harvest yielded no matching subtree"; Log.LogWarning((object)("[AURA] species '" + speciesKey + "': harvest completed but no subtree matched filter '" + filter + "' (the node census above lists what the donor carries). Capture failed (once).")); } else { AdoptClone(c, speciesKey, filter, val); } } private static void FinishCapture(Capture c, string speciesKey, string filter, List candidates, Transform sourceRoot, string via) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) GameObject val = ExtractClone(sourceRoot, speciesKey, candidates); if ((Object)(object)val == (Object)null) { c.State = (AuraCaptureState)4; c.Note = "no subtree matched via " + via; Log.LogWarning((object)("[AURA] species '" + speciesKey + "': no particle subtree matched filter '" + filter + "' on the " + via + " (node census above). Capture failed (once).")); } else { AdoptClone(c, speciesKey, filter, val); } } private static void AdoptClone(Capture c, string speciesKey, string filter, GameObject clone) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) clone.transform.SetParent(CacheHolder().transform, false); c.Subtree = clone.transform; c.State = (AuraCaptureState)3; c.Note = $"captured ({CountParticles(clone.transform)} particle system(s))"; Log.LogMessage((object)("[AURA] species '" + speciesKey + "' filter '" + filter + "': " + c.Note + " — cached; the aura attaches on the next reconcile.")); } private static GameObject ExtractClone(Transform sourceRoot, string speciesKey, List candidates) { //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018b: Expected O, but got Unknown //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)sourceRoot == (Object)null) { return null; } List matches = new List(); List list = new List(); ParticleSystem[] componentsInChildren = ((Component)sourceRoot).GetComponentsInChildren(true); foreach (ParticleSystem val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { if (list.Count < 30) { list.Add(((Object)val).name); } List list2 = new List(); List list3 = new List(); Transform val2 = ((Component)val).transform; while ((Object)(object)val2 != (Object)null && (Object)(object)val2 != (Object)(object)sourceRoot) { list2.Add(((Object)val2).name); list3.Add(val2); val2 = val2.parent; } int num = AuraBook.SelectCaptureDepth((IReadOnlyList)list2, (IReadOnlyList)candidates); if (num >= 0 && !matches.Contains(list3[num])) { matches.Add(list3[num]); } } } matches.RemoveAll(delegate(Transform m) { Transform parent = m.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)sourceRoot) { if (matches.Contains(parent)) { return true; } parent = parent.parent; } return false; }); if (matches.Count == 0) { Log.LogMessage((object)("[AURA] species '" + speciesKey + "' particle-node census " + string.Format("({0} shown): {1}", list.Count, string.Join(", ", list.ToArray())))); return null; } GameObject val3 = new GameObject("CK_AuraFx_" + speciesKey.Replace(' ', '_')); val3.SetActive(false); List list4 = new List(); foreach (Transform item in matches) { GameObject val4 = Object.Instantiate(((Component)item).gameObject, val3.transform); ((Object)val4).name = ((Object)item).name; val4.transform.localPosition = Vector3.zero; val4.transform.localRotation = Quaternion.identity; val4.SetActive(true); list4.Add($"'{((Object)item).name}' ({val4.GetComponentsInChildren(true).Length} PS)"); } int num2 = 0; int num3 = 0; int num4 = 0; int num5 = 0; int num6 = 0; SkinnedMeshRenderer[] componentsInChildren2 = val3.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val5 in componentsInChildren2) { num2++; Object.DestroyImmediate((Object)(object)val5); } Animator[] componentsInChildren3 = val3.GetComponentsInChildren(true); foreach (Animator val6 in componentsInChildren3) { num3++; Object.DestroyImmediate((Object)(object)val6); } Animation[] componentsInChildren4 = val3.GetComponentsInChildren(true); foreach (Animation val7 in componentsInChildren4) { num3++; Object.DestroyImmediate((Object)(object)val7); } Cloth[] componentsInChildren5 = val3.GetComponentsInChildren(true); foreach (Cloth val8 in componentsInChildren5) { num4++; Object.DestroyImmediate((Object)(object)val8); } Joint[] componentsInChildren6 = val3.GetComponentsInChildren(true); foreach (Joint val9 in componentsInChildren6) { num5++; Object.DestroyImmediate((Object)(object)val9); } Rigidbody[] componentsInChildren7 = val3.GetComponentsInChildren(true); foreach (Rigidbody val10 in componentsInChildren7) { num6++; Object.DestroyImmediate((Object)(object)val10); } if (num2 > 0 || num3 > 0) { Log.LogWarning((object)("[AURA] species '" + speciesKey + "': capture matched BODY content — stripped " + $"{num2} SkinnedMeshRenderer(s), {num3} Animator/Animation(s), {num4} Cloth, " + $"{num5} Joint(s), {num6} Rigidbody(ies). The filter over-reached; consider " + "naming the FX nodes more precisely.")); } int num13 = val3.GetComponentsInChildren(true).Length; int num14 = val3.GetComponentsInChildren(true).Length; Log.LogMessage((object)("[AURA]" + $" species '{speciesKey}' capture: {matches.Count} subtree(s) " + string.Format("[{0}] — {1} particle system(s), ", string.Join(", ", list4.ToArray()), num13) + $"{num14} renderer(s) after body-strip.")); if (num13 == 0) { Log.LogWarning((object)("[AURA] species '" + speciesKey + "': capture REFUSED — no particle systems survive the body-strip (the filter matched body nodes only).")); Object.DestroyImmediate((Object)(object)val3); return null; } return val3; } private static GameObject CacheHolder() { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown if ((Object)(object)_cacheHolder == (Object)null) { _cacheHolder = new GameObject("CK_AuraTemplates"); _cacheHolder.SetActive(false); Object.DontDestroyOnLoad((Object)(object)_cacheHolder); } return _cacheHolder; } private static int CountParticles(Transform t) { return ((Component)t).GetComponentsInChildren(true).Length; } public static string Dump() { //IL_00a1: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("[AURA]" + $" {_rows.Count} registered aura(s), {_captures.Count} capture pair(s)."); foreach (Row value2 in _rows.Values) { FxRecipe recipe = value2.Recipe.Recipe; string text = ""; if (recipe.Source == FxSource.CreatureFxSubtree) { _captures.TryGetValue(AuraBook.CaptureKey(recipe.SpeciesKey, recipe.SubtreeFilter), out var value); text = ((value == null) ? " capture=Pending (never resolved)" : $" capture={value.State} ({value.Note})"); } string text2 = (value2.Force.HasValue ? (value2.Force.Value ? " FORCED-ON" : " FORCED-OFF") : ""); stringBuilder.Append(string.Format("\n{0} '{1}' slot {2}: source {3}", "[AURA]", value2.Recipe.Key, value2.Recipe.Slot, recipe.Source) + ((recipe.Source == FxSource.CreatureFxSubtree) ? (" species '" + recipe.SpeciesKey + "' filter '" + recipe.SubtreeFilter + "'") : "") + $" attach {recipe.Attach} scale {recipe.Scale:0.##}{text}{text2}"); } return stringBuilder.ToString(); } public static List Keys() { List list = new List(_rows.Keys); list.Sort(StringComparer.OrdinalIgnoreCase); return list; } } public class CompanionBody : MonoBehaviour { public Transform Target; public string SpeciesId = ""; public float Speed = 4.5f; public float StopDistance = 3f; private const float LoafArriveDistance = 0.6f; public float LeashDistance = float.NaN; public Transform CombatTarget; public Transform SceneSpot; public Transform StaySpot; public Transform FollowOverride; public float YawOffset = float.NaN; public float FollowSpeedFloor; public bool HumanoidAgent; public float AgentBaseOffset; public CreatureAttributes CapturedStats; public ProjectileCapture.RangedAttackRig RangedRig; public ICompanionSettings Settings; public CompanionHost Host; private static int s_nextBodyId; private readonly int _bodyId = ++s_nextBodyId; public string Origin = "unknown"; public Func Owner; public Func CompanionRef; private Transform _ownerCtrlFor; private LocalCharacterControl _ownerCtrl; internal Animator _anim; internal LocoRig _loco; internal NavMeshAgent _agent; private bool _agentSuspended; private bool _directDriving; private float _repath; private float _diag; private float _warpCd; private float _lastVoidLogAt = -999f; private Vector3 _faceDir = Vector3.forward; private bool _planted; private bool _wasPlanted; private CombatStationState _station; private Transform _stationFor; private Vector3 _stationGround; private bool _stationOnMesh; private bool _stationActive; private bool _stationUnreachable; private float _stationPartialSince = -1f; private StationPlantEdge _plantEdge; private RigStabilizer _rig; internal SlopeTilt _slope; public float SlopeTiltOverride = float.NaN; private float _animF; private float _animS; private bool _strafeForcing; private bool _approachRefused; private bool _wasApproachActive; private LookTarget _lastLook = (LookTarget)1; private Quaternion _flatRot = Quaternion.identity; private float _warpBlockedSince = -1f; private float _warpRefusedLogAt = -999f; private float _leashGraceUntil = -1f; private Vector3 _lastDest = Vector3.positiveInfinity; private Vector3 _facePoint; private float _faceUntil = -1f; private bool _wasPointingHold; private float _velAnomalyLog; private float _desyncLog; private float _roofLog; private readonly LoafTracker _loaf = new LoafTracker(); private readonly MovementGate _moveGate = new MovementGate(); private Vector3 _lastAnimPos; private bool _animPosPrimed; private readonly ReplaceConvergence _replaceConv = new ReplaceConvergence(); private bool _agentAbandoned; private readonly StuckBodyPolicy _stuck = new StuckBodyPolicy(); private float _bornAt = -1f; public bool StuckDetection; public int Contamination; private static int s_sceneEpoch; private static bool s_sceneHookArmed; private int _stuckEpoch; internal const string TagRig = "[PETRIG]"; private bool _needsBind; private static readonly FollowTuning Tuning = new FollowTuning(); private const float MinLookSqr = 0.01f; private readonly CombatStationTuning _stationTuning = new CombatStationTuning(); private SpeedEstimate _enemySpeed; private static readonly float[] s_ladder = new float[8]; private static readonly float[] s_bearings = new float[5] { 0f, -30f, 30f, -60f, 60f }; private static readonly NavMeshPath s_path = new NavMeshPath(); private const float PathDetourFactor = 1.6f; private string _landing = ""; private bool _approaching; private string _warpReason = ""; private float _cantReachSince = -1f; private const int VanillaPetSkill_SummonPearlBird = 8400010; private static ParticleSystem s_warpFx; private static bool s_warpFxTried; public CombatStyle CombatStyle { get; set; } public CombatStationState Station => _station; public bool PlantedOnStation { get { if (_planted) { return _stationActive; } return false; } } public bool AgentSuspended => _agentSuspended; private ICompanionSettings Cfg => Settings ?? Host?.Settings ?? CompanionRuntime.Fallback; public int BodyId => _bodyId; private string TagPuppet { get { string text = CompanionRuntime.Tag("PUPPET", Cfg); int bodyId = _bodyId; return text + "#" + bodyId; } } private string TagDesync { get { string text = CompanionRuntime.Tag("DESYNC", Cfg); int bodyId = _bodyId; return text + "#" + bodyId; } } private string TagVel { get { string text = CompanionRuntime.Tag("VEL", Cfg); int bodyId = _bodyId; return text + "#" + bodyId; } } public Vector3 FacingDir => _faceDir; private float DriveSpeed => FollowPolicy.FollowSpeed(FollowRefDist(), Speed, FloorNow, CatchUpSpeedNow, (Object)(object)CombatTarget != (Object)null, Tuning); private float CatchUpSpeedNow { get { if (!((Object)(object)SceneSpot != (Object)null) && !((Object)(object)StaySpot != (Object)null)) { return FollowPolicy.OwnerScaledSpeed(Cfg.CatchUpSpeed, OwnerSpeedMultiplier()); } return 0f; } } private float FloorNow => FollowPolicy.OwnerScaledSpeed(FollowSpeedFloor, OwnerSpeedMultiplier()); private float FlatDriveSpeed => FollowPolicy.FollowSpeed(0f, Speed, FloorNow, 0f, (Object)(object)CombatTarget != (Object)null, Tuning); private float EffectiveLeash { get { if (!float.IsNaN(LeashDistance)) { return LeashDistance; } return Cfg.LeashDistance; } } public bool AllowsBackpedal { get; set; } internal bool SlopeTiltEligible { get { if (!float.IsNaN(SlopeTiltOverride)) { return SlopeTiltOverride > 0.5f; } return Cfg.SlopeTiltEnabled; } } public StuckBodyPolicy StuckPolicy => _stuck; public float StuckSeconds => _stuck.Elapsed; public float StuckGoalDist => _stuck.LastGoalDist; public bool StuckTripped => _stuck.Tripped; public bool AgentAbandoned => _agentAbandoned; public string AgentState { get { if (!((Object)(object)_agent == (Object)null)) { if (((Behaviour)_agent).enabled) { if (_agent.isOnNavMesh) { return "on"; } return "off-mesh"; } return "disabled"; } return "none"; } } private float TurnCapDegPerSec { get { float result = default(float); float num = default(float); FollowPolicy.AgentDynamics((Object)(object)CombatTarget != (Object)null, Tuning, ref result, ref num); return result; } } private string TagFace => CompanionRuntime.Tag("FACE", Cfg); private string TagStation => CompanionRuntime.Tag("STATION", Cfg); public event Action OnAfterMove; public event Action OnWarpedToOwner; public event Action OnStuckDetected; public void SuspendAgent(bool suspended) { if (_agentSuspended != suspended) { _agentSuspended = suspended; if (suspended && (Object)(object)_agent != (Object)null && ((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = false; } } } private void RaiseWarpedToOwner(string reason) { this.OnWarpedToOwner?.Invoke(this, reason); } private float OwnerSpeedMultiplier() { if ((Object)(object)Target == (Object)null) { return 1f; } if (_ownerCtrlFor != Target || ((Object)(object)_ownerCtrl == (Object)null && (Object)(object)_ownerCtrlFor != (Object)null)) { _ownerCtrlFor = Target; Character val = ((Component)Target).GetComponent(); if ((Object)(object)val == (Object)null) { val = ((Component)Target).GetComponentInParent(); } _ownerCtrl = (LocalCharacterControl)(((Object)(object)val != (Object)null) ? /*isinst with value type is only supported in some contexts*/: null); } if (!((Object)(object)_ownerCtrl != (Object)null)) { return 1f; } return _ownerCtrl.MovementMultiplier; } private float FollowRefDist() { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) Transform val = (((Object)(object)FollowOverride != (Object)null) ? FollowOverride : Target); if (!((Object)(object)val != (Object)null)) { return 0f; } return Vector3.Distance(((Component)this).transform.position, val.position); } private static void ArmSceneEpochHook() { if (s_sceneHookArmed) { return; } s_sceneHookArmed = true; SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 if ((int)mode != 1) { s_sceneEpoch++; } }; } public void ResetStuckEpisode() { _stuck.Reset(); } private void AccountStuck(Transform goal) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) if (!StuckDetection || (Object)(object)goal == (Object)null) { return; } ArmSceneEpochHook(); if (_stuckEpoch != s_sceneEpoch) { _stuckEpoch = s_sceneEpoch; _stuck.Reset(); } StuckObservation val = new StuckObservation { Dt = Time.unscaledDeltaTime, AgentUnusable = ((Object)(object)_agent == (Object)null || !((Behaviour)_agent).enabled || !_agent.isOnNavMesh || _agentAbandoned), GoalDist = Vector3.Distance(((Component)this).transform.position, goal.position), BodyAgeSeconds = ((_bornAt < 0f) ? 0f : (Time.unscaledTime - _bornAt)) }; bool flag = (Object)(object)NetworkLevelLoader.Instance != (Object)null && NetworkLevelLoader.Instance.IsGameplayPaused; val.WorldReady = !flag && (Object)(object)Target != (Object)null && CompanionRuntime.IsSanePosition(Target.position) && Time.timeScale > 0f; if (_stuck.Observe(ref val)) { Vector3 position = ((Component)this).transform.position; string text = string.Format("goalDist={0:F0} (best {1:F0}) pos={2} ", val.GoalDist, _stuck.BestGoalDist, ((Vector3)(ref position)).ToString("F1")); string obj = (StuckBodyPolicy.NearOrigin(position.x, position.y, position.z, 5f) ? "AT-WORLD-ORIGIN " : ""); string agentState = AgentState; object arg = _agentAbandoned; Vector3 position2 = Target.position; string arg2 = text + obj + string.Format("agent={0} abandoned={1} owner={2}", agentState, arg, ((Vector3)(ref position2)).ToString("F1")); ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)($"{TagPuppet} STUCK-WATCH: stuck {_stuck.Elapsed:F0}s {arg2} — " + "an external writer is holding this body; it cannot re-place its way out. A consumer watchdog (if any) decides what to do about it.")); } this.OnStuckDetected?.Invoke(this, arg2); } } internal static void InitAnimForPuppet(Animator anim, GameObject go) { if ((Object)(object)anim != (Object)null) { ((Behaviour)anim).enabled = true; anim.applyRootMotion = false; anim.cullingMode = (AnimatorCullingMode)0; AnimatorControllerParameter[] parameters = anim.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == "Sheathed") { anim.SetBool("Sheathed", true); } if (val.name == "Sheathe") { anim.SetTrigger("Sheathe"); } if (val.name == "Block") { anim.SetBool("Block", false); } } } NormalizeChildAnimators(anim, go); foreach (Animator item in CollectSkinAnimators(go)) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)anim)) { ((Behaviour)item).enabled = true; item.applyRootMotion = false; item.cullingMode = (AnimatorCullingMode)0; } } SkinnedMeshRenderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val2 in componentsInChildren) { val2.updateWhenOffscreen = true; ((Renderer)val2).enabled = true; } } internal static void NormalizeChildAnimators(Animator rootAnim, GameObject go) { if ((Object)(object)go == (Object)null) { return; } if ((Object)(object)rootAnim == (Object)null) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[PETRIG] no root animator on '" + ((Object)go).name + "' — running the skin census anyway (a disabled skin driver is still repaired; no child can be an override without a root).")); } } bool flag = (Object)(object)rootAnim != (Object)null && HasParam(rootAnim, "Attack1"); SkinnedMeshRenderer[] componentsInChildren = go.GetComponentsInChildren(true); Animator[] componentsInChildren2 = go.GetComponentsInChildren(true); foreach (Animator val in componentsInChildren2) { if ((Object)(object)val == (Object)null || (Object)(object)val == (Object)(object)rootAnim) { continue; } bool flag2 = HasParam(val, "Attack1"); if (DrivesSkin(val, componentsInChildren)) { if (!((Behaviour)val).enabled) { ((Behaviour)val).enabled = true; ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[PETRIG] RE-ENABLED skin-driving animator '" + ((Object)((Component)val).gameObject).name + "' ctrl='" + Ctrl(val) + "' on '" + ((Object)go).name + "' — it was disabled and it owns the rendered skin (disabling it freezes the body entirely; see BUG-PETANIMSTUCKIDLE round 3).")); } } else { ModLog log3 = CompanionRuntime.Log; if (log3 != null) { log3.LogMessage((object)("[PETRIG] kept child animator '" + ((Object)((Component)val).gameObject).name + "' " + $"ctrl='{Ctrl(val)}' on '{((Object)go).name}' — it drives a SkinnedMeshRenderer (hasAttack1={flag2}); " + "not an override, disabling it would freeze the body.")); } } } else if (flag && !flag2 && ((Behaviour)val).enabled) { ((Behaviour)val).enabled = false; ModLog log4 = CompanionRuntime.Log; if (log4 != null) { log4.LogMessage((object)("[PETRIG] disabled child override animator '" + ((Object)((Component)val).gameObject).name + "' ctrl='" + Ctrl(val) + "' on '" + ((Object)go).name + "' (no Attack1, drives no skin — root '" + Ctrl(rootAnim) + "' drives the rig).")); } } else if (!flag && flag2) { ModLog log5 = CompanionRuntime.Log; if (log5 != null) { log5.LogWarning((object)("[PETRIG] root animator on '" + ((Object)go).name + "' has no Attack1 but child '" + ((Object)((Component)val).gameObject).name + "' does — attack triggers will route to the child (see CompanionCombat).")); } } } if ((Object)(object)rootAnim != (Object)null && flag && componentsInChildren.Length != 0 && !DrivesSkin(rootAnim, componentsInChildren)) { ModLog log6 = CompanionRuntime.Log; if (log6 != null) { log6.LogWarning((object)("[PETRIG] root animator on '" + ((Object)go).name + "' ctrl='" + Ctrl(rootAnim) + "' has Attack1 but drives no skin on this body — its attack states cannot render (BUG-PETANIMSTUCKIDLE, optimized-rig class). Locomotion is unaffected; the skin's own animator owns it.")); } } } private static Animator SkinOwner(SkinnedMeshRenderer smr) { if ((Object)(object)smr == (Object)null) { return null; } Transform val = smr.rootBone; if ((Object)(object)val == (Object)null && smr.bones != null && smr.bones.Length != 0) { val = smr.bones[0]; } if ((Object)(object)val == (Object)null) { val = ((Component)smr).transform; } Transform val2 = val; while ((Object)(object)val2 != (Object)null) { Animator component = ((Component)val2).GetComponent(); if ((Object)(object)component != (Object)null) { return component; } val2 = val2.parent; } return null; } private static bool DrivesSkin(Animator anim, SkinnedMeshRenderer[] skins) { if ((Object)(object)anim == (Object)null || skins == null) { return false; } foreach (SkinnedMeshRenderer val in skins) { if ((Object)(object)val != (Object)null && (Object)(object)SkinOwner(val) == (Object)(object)anim) { return true; } } return false; } internal static List CollectSkinAnimators(GameObject go) { List list = new List(); if ((Object)(object)go == (Object)null) { return list; } SkinnedMeshRenderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer smr in componentsInChildren) { Animator val = SkinOwner(smr); if (!((Object)(object)val == (Object)null) && ((Component)val).transform.IsChildOf(go.transform) && !list.Contains(val)) { list.Add(val); } } return list; } private static string Ctrl(Animator a) { if (!((Object)(object)a != (Object)null) || !((Object)(object)a.runtimeAnimatorController != (Object)null)) { return "none"; } return ((Object)a.runtimeAnimatorController).name; } internal static bool HasParam(Animator anim, string name) { if ((Object)(object)anim == (Object)null) { return false; } AnimatorControllerParameter[] parameters = anim.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == name) { return true; } } return false; } private void OnEnable() { if (_bornAt < 0f) { _bornAt = Time.unscaledTime; } BodyCensus.Register(this); } private void OnDestroy() { //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) BodyCensus.Unregister(this); ModLog log = CompanionRuntime.Log; if (log != null) { string[] obj = new string[8] { TagPuppet, " destroyed (species='", SpeciesId, "', origin=", Origin, ", lastPos=", null, null }; Vector3 position = ((Component)this).transform.position; obj[6] = ((Vector3)(ref position)).ToString("F1"); obj[7] = ")."; log.LogMessage((object)string.Concat(obj)); } } private void Start() { //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0101: Unknown result type (might be due to invalid IL or missing references) _anim = ((Component)this).GetComponent(); InitAnimForPuppet(_anim, ((Component)this).gameObject); _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)("[PETRIG] locomotion drive on '" + ((Object)((Component)this).gameObject).name + "': " + _loco.Describe() + ".")); } _rig = ((Component)this).GetComponent(); if ((Object)(object)_rig != (Object)null && _rig.SpeedSource == null) { _rig.SpeedSource = () => _moveGate.SmoothedSpeed; } _agent = ((Component)this).GetComponent(); _slope = new SlopeTilt(((Component)this).transform); _slope.Calibrate(((Component)this).GetComponentInChildren(), _agent); _flatRot = ((Component)this).transform.rotation; if ((Object)(object)_agent != (Object)null) { CompanionRuntime.Log.LogMessage((object)($"{TagPuppet} creature natural agent speed={_agent.speed:F2} m/s; species/config speed {Speed:F2}, " + $"driving at {DriveSpeed:F2} now (follow ceiling {Cfg.CatchUpSpeed:F1} × owner speedmult {OwnerSpeedMultiplier():0.##}, floor {FollowSpeedFloor:F1}; agent radius={_agent.radius:F2} height={_agent.height:F2}).")); _agent.speed = DriveSpeed; float angularSpeed = default(float); float acceleration = default(float); FollowPolicy.AgentDynamics((Object)(object)CombatTarget != (Object)null, DriveSpeed, Tuning, ref angularSpeed, ref acceleration); _agent.angularSpeed = angularSpeed; _agent.acceleration = acceleration; _agent.stoppingDistance = StopDistance; _agent.autoTraverseOffMeshLink = true; _agent.updatePosition = true; _agent.updateRotation = false; _agent.obstacleAvoidanceType = (ObstacleAvoidanceType)0; if (HumanoidAgent) { _agent.radius = 0.4f; _agent.height = 1.8f; _agent.baseOffset = AgentBaseOffset; } TryBindAgent(); } CompanionRuntime.Log.LogMessage((object)$"{TagPuppet} agent={(Object)(object)_agent != (Object)null} onNavMesh={(Object)(object)_agent != (Object)null && _agent.isOnNavMesh} anim={(Object)(object)_anim != (Object)null}"); } private void TryBindAgent() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_01ef: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0218: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_agent == (Object)null) { return; } Vector3 val = (((Object)(object)Target != (Object)null) ? Target.position : ((Component)this).transform.position); PlaceRefusal val2 = PlacementGate.Check(true, val.x, val.y, val.z, ((Component)this).transform.position.x, ((Component)this).transform.position.y, ((Component)this).transform.position.z); if (!PlacementGate.Allows(val2)) { ((Behaviour)_agent).enabled = false; if (!_needsBind) { _needsBind = true; CompanionRuntime.Log.LogWarning((object)(TagPuppet + " agent bind DEFERRED: " + PlacementGate.Describe(val2) + " (ref=" + ((Vector3)(ref val)).ToString("F1") + ") — agent stays disabled; the bind retries each frame until the owner is placed.")); } return; } _needsBind = false; _stuck.Reset(); ((Behaviour)_agent).enabled = true; Vector3 pos; bool flag = ((!HumanoidAgent) ? (NavProbe.SampleAtFeet(((Component)this).transform.position, 1.5f, out pos) || NavProbe.SampleAtFeet(val, 2f, out pos)) : (NavProbe.SampleAtFeet(val, 1.5f, out pos) || NavProbe.SampleAtFeet(((Component)this).transform.position, 2f, out pos))); bool flag2 = flag && _agent.Warp(pos); if (flag) { _moveGate.Reset(); } _animPosPrimed = false; string[] obj = new string[5] { $"{TagPuppet} agent bound: humanoid={HumanoidAgent} warp={flag2} onNavMesh={_agent.isOnNavMesh} ", null, null, null, null }; Vector3 val3 = _agent.nextPosition; string arg = ((Vector3)(ref val3)).ToString("F1"); object arg2 = _agent.updatePosition; val3 = ((Component)this).transform.position; obj[1] = string.Format("next={0} updPos={1} pos={2} ", arg, arg2, ((Vector3)(ref val3)).ToString("F1")); obj[2] = "ref="; obj[3] = ((Vector3)(ref val)).ToString("F1"); obj[4] = (HumanoidAgent ? $" baseOffset={AgentBaseOffset:F2}" : ""); string text = string.Concat(obj); if (flag2) { CompanionRuntime.Log.LogMessage((object)text); } else { CompanionRuntime.Log.LogWarning((object)(text + " — Warp REFUSED or no polygon sampled; the zone re-place / leash warp is the backstop.")); } } private unsafe void Update() { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Invalid comparison between Unknown and I4 //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_02f8: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0301: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_030b: Unknown result type (might be due to invalid IL or missing references) //IL_030d: Unknown result type (might be due to invalid IL or missing references) //IL_0313: Invalid comparison between Unknown and I4 //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0418: Unknown result type (might be due to invalid IL or missing references) //IL_041d: Unknown result type (might be due to invalid IL or missing references) //IL_041f: Unknown result type (might be due to invalid IL or missing references) //IL_03e2: Unknown result type (might be due to invalid IL or missing references) //IL_0431: Unknown result type (might be due to invalid IL or missing references) //IL_0436: Unknown result type (might be due to invalid IL or missing references) //IL_0444: Unknown result type (might be due to invalid IL or missing references) //IL_0454: Unknown result type (might be due to invalid IL or missing references) //IL_045e: Unknown result type (might be due to invalid IL or missing references) //IL_0465: Unknown result type (might be due to invalid IL or missing references) //IL_0498: Unknown result type (might be due to invalid IL or missing references) //IL_04a8: Unknown result type (might be due to invalid IL or missing references) //IL_04b2: Unknown result type (might be due to invalid IL or missing references) //IL_04b7: Unknown result type (might be due to invalid IL or missing references) //IL_04b9: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_033f: Unknown result type (might be due to invalid IL or missing references) //IL_0344: Unknown result type (might be due to invalid IL or missing references) //IL_027f: Unknown result type (might be due to invalid IL or missing references) //IL_01cf: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_01f9: Unknown result type (might be due to invalid IL or missing references) //IL_04f7: Unknown result type (might be due to invalid IL or missing references) //IL_04f9: Unknown result type (might be due to invalid IL or missing references) //IL_04ff: Invalid comparison between Unknown and I4 //IL_04c4: Unknown result type (might be due to invalid IL or missing references) //IL_04d1: Unknown result type (might be due to invalid IL or missing references) //IL_04db: Unknown result type (might be due to invalid IL or missing references) //IL_0367: Unknown result type (might be due to invalid IL or missing references) //IL_0376: Unknown result type (might be due to invalid IL or missing references) //IL_0385: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0542: Unknown result type (might be due to invalid IL or missing references) //IL_0544: Unknown result type (might be due to invalid IL or missing references) //IL_054a: Invalid comparison between Unknown and I4 //IL_050f: Unknown result type (might be due to invalid IL or missing references) //IL_085e: Unknown result type (might be due to invalid IL or missing references) //IL_0876: Unknown result type (might be due to invalid IL or missing references) //IL_08ab: Unknown result type (might be due to invalid IL or missing references) //IL_08ad: Unknown result type (might be due to invalid IL or missing references) //IL_054f: Unknown result type (might be due to invalid IL or missing references) //IL_052b: Unknown result type (might be due to invalid IL or missing references) //IL_08b9: Unknown result type (might be due to invalid IL or missing references) //IL_0566: Unknown result type (might be due to invalid IL or missing references) //IL_0568: Unknown result type (might be due to invalid IL or missing references) //IL_056e: Invalid comparison between Unknown and I4 //IL_0572: Unknown result type (might be due to invalid IL or missing references) //IL_0574: Unknown result type (might be due to invalid IL or missing references) //IL_05cb: Unknown result type (might be due to invalid IL or missing references) //IL_08dc: Unknown result type (might be due to invalid IL or missing references) //IL_08ec: Unknown result type (might be due to invalid IL or missing references) //IL_093b: Unknown result type (might be due to invalid IL or missing references) //IL_094b: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Unknown result type (might be due to invalid IL or missing references) //IL_0a62: Unknown result type (might be due to invalid IL or missing references) //IL_097b: Unknown result type (might be due to invalid IL or missing references) //IL_0987: Unknown result type (might be due to invalid IL or missing references) //IL_05f4: Unknown result type (might be due to invalid IL or missing references) //IL_05b2: Unknown result type (might be due to invalid IL or missing references) //IL_05b7: Unknown result type (might be due to invalid IL or missing references) //IL_0e7e: Unknown result type (might be due to invalid IL or missing references) //IL_0e88: Unknown result type (might be due to invalid IL or missing references) //IL_0e8d: Unknown result type (might be due to invalid IL or missing references) //IL_0e96: Unknown result type (might be due to invalid IL or missing references) //IL_0e9b: Unknown result type (might be due to invalid IL or missing references) //IL_0e9e: Unknown result type (might be due to invalid IL or missing references) //IL_0ac4: Unknown result type (might be due to invalid IL or missing references) //IL_0aca: Invalid comparison between Unknown and I4 //IL_0a77: Unknown result type (might be due to invalid IL or missing references) //IL_0a7e: Unknown result type (might be due to invalid IL or missing references) //IL_099f: Unknown result type (might be due to invalid IL or missing references) //IL_09a6: Unknown result type (might be due to invalid IL or missing references) //IL_0f29: Unknown result type (might be due to invalid IL or missing references) //IL_0f3b: Unknown result type (might be due to invalid IL or missing references) //IL_0f47: Unknown result type (might be due to invalid IL or missing references) //IL_0ec3: Unknown result type (might be due to invalid IL or missing references) //IL_0ece: Unknown result type (might be due to invalid IL or missing references) //IL_0ed3: Unknown result type (might be due to invalid IL or missing references) //IL_0ed8: Unknown result type (might be due to invalid IL or missing references) //IL_0f03: Unknown result type (might be due to invalid IL or missing references) //IL_0f0e: Unknown result type (might be due to invalid IL or missing references) //IL_0f19: Unknown result type (might be due to invalid IL or missing references) //IL_0a97: Unknown result type (might be due to invalid IL or missing references) //IL_0a99: Unknown result type (might be due to invalid IL or missing references) //IL_09bf: Unknown result type (might be due to invalid IL or missing references) //IL_09c1: Unknown result type (might be due to invalid IL or missing references) //IL_0f71: Unknown result type (might be due to invalid IL or missing references) //IL_0f56: Unknown result type (might be due to invalid IL or missing references) //IL_0f5f: Unknown result type (might be due to invalid IL or missing references) //IL_0f61: Unknown result type (might be due to invalid IL or missing references) //IL_060d: Unknown result type (might be due to invalid IL or missing references) //IL_0f86: Unknown result type (might be due to invalid IL or missing references) //IL_0687: Unknown result type (might be due to invalid IL or missing references) //IL_068c: Unknown result type (might be due to invalid IL or missing references) //IL_06a3: Unknown result type (might be due to invalid IL or missing references) //IL_06a8: Unknown result type (might be due to invalid IL or missing references) //IL_06ec: Unknown result type (might be due to invalid IL or missing references) //IL_06f1: Unknown result type (might be due to invalid IL or missing references) //IL_0621: Unknown result type (might be due to invalid IL or missing references) //IL_0614: Unknown result type (might be due to invalid IL or missing references) //IL_0731: Unknown result type (might be due to invalid IL or missing references) //IL_0fb5: Unknown result type (might be due to invalid IL or missing references) //IL_0b12: Unknown result type (might be due to invalid IL or missing references) //IL_0b17: Unknown result type (might be due to invalid IL or missing references) //IL_0b1f: Unknown result type (might be due to invalid IL or missing references) //IL_0b24: Unknown result type (might be due to invalid IL or missing references) //IL_0b2c: Unknown result type (might be due to invalid IL or missing references) //IL_0b31: Unknown result type (might be due to invalid IL or missing references) //IL_0b39: Unknown result type (might be due to invalid IL or missing references) //IL_0b40: Unknown result type (might be due to invalid IL or missing references) //IL_0b59: Unknown result type (might be due to invalid IL or missing references) //IL_0b60: Unknown result type (might be due to invalid IL or missing references) //IL_0b67: Unknown result type (might be due to invalid IL or missing references) //IL_0b6e: Unknown result type (might be due to invalid IL or missing references) //IL_0b75: Unknown result type (might be due to invalid IL or missing references) //IL_0b7c: Unknown result type (might be due to invalid IL or missing references) //IL_0fe9: Unknown result type (might be due to invalid IL or missing references) //IL_0bb1: Unknown result type (might be due to invalid IL or missing references) //IL_0bb6: Unknown result type (might be due to invalid IL or missing references) //IL_0bd2: Unknown result type (might be due to invalid IL or missing references) //IL_0be4: Unknown result type (might be due to invalid IL or missing references) //IL_0799: Unknown result type (might be due to invalid IL or missing references) //IL_079e: Unknown result type (might be due to invalid IL or missing references) //IL_07d2: Unknown result type (might be due to invalid IL or missing references) //IL_07d7: Unknown result type (might be due to invalid IL or missing references) //IL_07f6: Unknown result type (might be due to invalid IL or missing references) //IL_07fb: Unknown result type (might be due to invalid IL or missing references) //IL_1003: Unknown result type (might be due to invalid IL or missing references) //IL_1008: Unknown result type (might be due to invalid IL or missing references) //IL_1021: Unknown result type (might be due to invalid IL or missing references) //IL_0d1f: Unknown result type (might be due to invalid IL or missing references) //IL_0d21: Unknown result type (might be due to invalid IL or missing references) //IL_0d23: Unknown result type (might be due to invalid IL or missing references) //IL_0c0b: Unknown result type (might be due to invalid IL or missing references) //IL_115a: Unknown result type (might be due to invalid IL or missing references) //IL_1167: Unknown result type (might be due to invalid IL or missing references) //IL_1174: Unknown result type (might be due to invalid IL or missing references) //IL_1179: Unknown result type (might be due to invalid IL or missing references) //IL_117b: Unknown result type (might be due to invalid IL or missing references) //IL_117e: Invalid comparison between Unknown and I4 //IL_11c5: Unknown result type (might be due to invalid IL or missing references) //IL_1180: Unknown result type (might be due to invalid IL or missing references) //IL_1183: Invalid comparison between Unknown and I4 //IL_11ca: Unknown result type (might be due to invalid IL or missing references) //IL_11cd: Unknown result type (might be due to invalid IL or missing references) //IL_11cf: Unknown result type (might be due to invalid IL or missing references) //IL_11d7: Unknown result type (might be due to invalid IL or missing references) //IL_11dc: Unknown result type (might be due to invalid IL or missing references) //IL_11ea: Unknown result type (might be due to invalid IL or missing references) //IL_11b8: Unknown result type (might be due to invalid IL or missing references) //IL_1185: Unknown result type (might be due to invalid IL or missing references) //IL_1228: Unknown result type (might be due to invalid IL or missing references) //IL_11fe: Unknown result type (might be due to invalid IL or missing references) //IL_11b3: Unknown result type (might be due to invalid IL or missing references) //IL_1189: Unknown result type (might be due to invalid IL or missing references) //IL_118c: Invalid comparison between Unknown and I4 //IL_0e45: Unknown result type (might be due to invalid IL or missing references) //IL_0e4a: Unknown result type (might be due to invalid IL or missing references) //IL_0e54: Unknown result type (might be due to invalid IL or missing references) //IL_0e59: Unknown result type (might be due to invalid IL or missing references) //IL_0e5b: Unknown result type (might be due to invalid IL or missing references) //IL_0e60: Unknown result type (might be due to invalid IL or missing references) //IL_11a1: Unknown result type (might be due to invalid IL or missing references) //IL_11a7: Unknown result type (might be due to invalid IL or missing references) //IL_11ac: Unknown result type (might be due to invalid IL or missing references) //IL_1194: Unknown result type (might be due to invalid IL or missing references) //IL_1085: Unknown result type (might be due to invalid IL or missing references) //IL_108c: Unknown result type (might be due to invalid IL or missing references) //IL_0cff: Unknown result type (might be due to invalid IL or missing references) //IL_0d01: Unknown result type (might be due to invalid IL or missing references) //IL_0d03: Unknown result type (might be due to invalid IL or missing references) //IL_10b4: Unknown result type (might be due to invalid IL or missing references) //IL_10d0: Unknown result type (might be due to invalid IL or missing references) //IL_10d5: Unknown result type (might be due to invalid IL or missing references) //IL_10e3: Unknown result type (might be due to invalid IL or missing references) //IL_10e6: Unknown result type (might be due to invalid IL or missing references) //IL_10eb: Unknown result type (might be due to invalid IL or missing references) //IL_10f0: Unknown result type (might be due to invalid IL or missing references) //IL_1125: Unknown result type (might be due to invalid IL or missing references) //IL_0d8f: Unknown result type (might be due to invalid IL or missing references) //IL_0d95: Invalid comparison between Unknown and I4 //IL_0df2: Unknown result type (might be due to invalid IL or missing references) //IL_0df7: Unknown result type (might be due to invalid IL or missing references) //IL_0e08: Unknown result type (might be due to invalid IL or missing references) //IL_0e17: Unknown result type (might be due to invalid IL or missing references) //IL_0e19: Unknown result type (might be due to invalid IL or missing references) //IL_0e1b: Unknown result type (might be due to invalid IL or missing references) //IL_12fb: Unknown result type (might be due to invalid IL or missing references) //IL_1300: Unknown result type (might be due to invalid IL or missing references) //IL_1335: Unknown result type (might be due to invalid IL or missing references) //IL_136e: Unknown result type (might be due to invalid IL or missing references) //IL_1387: Unknown result type (might be due to invalid IL or missing references) Character val = ((Owner != null) ? Owner() : CompanionRuntime.LocalPlayer()); if ((Object)(object)val != (Object)null) { Target = ((Component)val).transform; } if (_stationFor != CombatTarget) { CombatStation.Reset(ref _station); ((SpeedEstimate)(ref _enemySpeed)).Reset(); _stationFor = CombatTarget; _stationOnMesh = false; _stationUnreachable = false; _stationPartialSince = -1f; _plantEdge = default(StationPlantEdge); } _wasPlanted = _planted; if ((int)CombatStyle == 1) { ((StationPlantEdge)(ref _plantEdge)).CarryFromLastFrame(PlantedOnStation); _planted = false; } _stationActive = false; if (_needsBind) { TryBindAgent(); } Transform val2 = (((Object)(object)SceneSpot != (Object)null) ? SceneSpot : (((Object)(object)CombatTarget != (Object)null) ? CombatTarget : (((Object)(object)StaySpot != (Object)null) ? StaySpot : (((Object)(object)FollowOverride != (Object)null) ? FollowOverride : Target)))); FollowFacts val3 = GatherFollowFacts(val2); float distToOwner = val3.DistToOwner; Vector3 pos = Vector3.zero; Vector3 boundAt; if (FollowPolicy.NeedsGoalSample(ref val3)) { val3.GoalOnMesh = NavProbe.SampleAtFeet(val2.position, Tuning.GoalSampleRadius, out pos); if (val3.GoalOnMesh && pos.y - val2.position.y > Tuning.RoofSanityMeters && Time.time - _roofLog > 2f) { _roofLog = Time.time; ModLog log = CompanionRuntime.Log; object[] obj = new object[6] { TagDesync, pos.y - val2.position.y, ((Object)val2).name, null, null, null }; boundAt = val2.position; obj[3] = ((Vector3)(ref boundAt)).ToString("F1"); obj[4] = ((Vector3)(ref pos)).ToString("F1"); obj[5] = (Object)(object)CombatTarget != (Object)null; log.LogMessage((object)string.Format("{0} goalGround {1:+0.0}m ABOVE goal '{2}': goal={3} sampled={4} combat={5}", obj)); } ReconDesync(val2); val3.AgentEnabled = ((Behaviour)_agent).enabled; if (FollowPolicy.ShouldReArmAgent(ref val3)) { ((Behaviour)_agent).enabled = true; if (!_agent.isOnNavMesh && !_agent.Warp(((Component)this).transform.position)) { _agent.Warp(pos); } _moveGate.Reset(); _animPosPrimed = false; _stuck.Reset(); if (_directDriving) { CompanionRuntime.Log.LogMessage((object)(TagPuppet + " direct-drive ended — navmesh back under the goal; agent re-armed.")); } _directDriving = false; } val3.AgentOnMesh = _agent.isOnNavMesh; } FollowDecision val4 = FollowPolicy.Decide(ref val3, Tuning); if ((int)val4.Mode == 0) { if ((int)val4.Hold == 2) { if (Time.unscaledTime - _lastVoidLogAt > 5f) { _lastVoidLogAt = Time.unscaledTime; Vector3 position = Target.position; ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)($"{TagPuppet} holding: player position ({position.x:F1}, {position.y:F1}, {position.z:F1}) " + $"reads as void/staging (floor y={-3000f:F0}), so the body will not " + "follow. If you are standing in a REAL place, this heuristic is misfiring (bug 33).")); } } _moveGate.Reset(); _animPosPrimed = false; } Idle(); return; } if (val3.AgentAbandoned) { AccountStuck(val2); if (val4.PreReplaceOwner) { AgentFreeReplaceOntoOwner("latched", "Latched", distToOwner); } DirectDrive(val2.position, (Object)(object)CombatTarget != (Object)null); return; } bool flag = false; Vector3 zero = Vector3.zero; if (val4.LoafEligible) { Vector3 forward = Target.forward; LoafPoint val5 = _loaf.Resolve(Target.position.x, Target.position.z, forward.x, forward.z, Time.deltaTime, Cfg.LoafDistanceMin, Cfg.LoafDistanceMax, Cfg.LoafRepickDistance, ((Component)this).transform.position.x, ((Component)this).transform.position.z); if (val5.Active) { ((Vector3)(ref zero))..ctor(val5.X, Target.position.y, val5.Z); flag = true; } } else { _loaf.Clear(); } if ((int)val4.Mode == 1) { _directDriving = true; AccountStuck(val2); if (val4.PreReplaceOwner) { AgentFreeReplaceOntoOwner("coverage gap", "CoverageGap", distToOwner); } DirectDrive(val2.position, (Object)(object)CombatTarget != (Object)null); return; } if ((int)val4.Mode == 2) { if (val4.ReplaceNow) { _warpCd = Time.time; bool flag2 = (int)val4.ReplaceDest == 1; Vector3 spot = pos; if (flag2 && !WarpLandingBehind(Tuning.OwnerSampleRadius, out spot) && !NavProbe.SampleAtFeet(Target.position, Tuning.OwnerSampleRadius, out spot)) { spot = Target.position; } ((Behaviour)_agent).enabled = false; ((Component)this).transform.position = spot; if (!_agentSuspended) { ((Behaviour)_agent).enabled = true; } bool flag3 = !_agentSuspended && _agent.Warp(spot); if (!flag3 && !_agentSuspended) { ReboundAfterRefusedWarp(spot, flag2 ? Target.position : val2.position, Tuning.OwnerSampleRadius, out boundAt); } _moveGate.Reset(); _animPosPrimed = false; ModLog log3 = CompanionRuntime.Log; object[] obj2 = new object[5] { TagPuppet, flag2 ? "owner" : "goal", distToOwner, null, null }; boundAt = ((Component)this).transform.position; obj2[3] = ((Vector3)(ref boundAt)).ToString("F0"); boundAt = Target.position; obj2[4] = ((Vector3)(ref boundAt)).ToString("F0"); string text = string.Format("{0} zone re-place onto {1} polygon: dist={2:F0} me={3} target={4} ", obj2); object[] obj3 = new object[4] { flag3, _agent.isOnNavMesh, null, null }; boundAt = _agent.nextPosition; obj3[2] = ((Vector3)(ref boundAt)).ToString("F0"); obj3[3] = _agent.updatePosition; log3.LogMessage((object)(text + string.Format("warp={0} onNavMesh={1} next={2} updPos={3}", obj3))); if (flag2) { RaiseWarpedToOwner("ZoneReplace"); } if (val4.ReplaceMeasurable && _replaceConv.RecordReplace(distToOwner)) { ModLog log4 = CompanionRuntime.Log; string[] obj4 = new string[7] { $"{TagPuppet} zone re-place NOT CONVERGING after {_replaceConv.ConsecutiveStuck} consecutive attempts — ", null, null, null, null, null, null }; object arg = _agent.isOnNavMesh; boundAt = _agent.nextPosition; obj4[1] = string.Format("agent state: onNavMesh={0} next={1} updPos={2} ", arg, ((Vector3)(ref boundAt)).ToString("F1"), _agent.updatePosition); obj4[2] = "me="; boundAt = ((Component)this).transform.position; obj4[3] = ((Vector3)(ref boundAt)).ToString("F1"); obj4[4] = " target="; boundAt = Target.position; obj4[5] = ((Vector3)(ref boundAt)).ToString("F1"); obj4[6] = " — abandoning the agent for this body's lifetime; DirectDrive (transform + raycast grounding) takes over. `posdump` splits agent-dragback from an external writer."; log4.LogWarning((object)string.Concat(obj4)); _agentAbandoned = true; _agent.updatePosition = false; ((Behaviour)_agent).enabled = false; } } Idle(); return; } _replaceConv.RecordConverged(); _stuck.Reset(); _agent.speed = val4.DriveSpeed; float angularSpeed = default(float); float acceleration = default(float); FollowPolicy.AgentDynamics((Object)(object)CombatTarget != (Object)null, val4.DriveSpeed, Tuning, ref angularSpeed, ref acceleration); _agent.angularSpeed = angularSpeed; _agent.acceleration = acceleration; bool flag4 = false; bool flag5 = false; Vector3 val6 = pos; bool flag6 = false; float num = default(float); float num2 = default(float); if (!flag && val4.LoafEligible && (Object)(object)FollowOverride == (Object)null && FollowPolicy.ApproachDestination(Target.position.x, Target.position.z, _loaf.HasMotionDir, _loaf.MotionDirX, _loaf.MotionDirZ, distToOwner, (Cfg.LoafDistanceMin + Cfg.LoafDistanceMax) * 0.5f, ((Component)this).transform.position.x, ((Component)this).transform.position.z, _approaching, _approachRefused, Tuning, ref num, ref num2, ref flag6) && NavProbe.SampleAtFeet(new Vector3(num, Target.position.y, num2), Tuning.LoafSampleRadius, out var pos2) && Mathf.Abs(pos2.y - pos.y) <= Tuning.RoofSanityMeters) { val6 = pos2; flag5 = true; } if (flag6 && !_approachRefused) { CompanionRuntime.Log.LogMessage((object)$"{TagFace} approach spot refused: owner between pet and spot (dist={distToOwner:F1}) — plain owner follow until clear"); } _approachRefused = flag6; _approaching = flag5; if (flag5 != _wasApproachActive) { _wasApproachActive = flag5; CompanionRuntime.Log.LogMessage((object)(TagFace + " approach " + (flag5 ? "began" : "ended") + ": " + DescribeFacing())); } if (flag) { if (NavProbe.SampleAtFeet(zero, Tuning.LoafSampleRadius, out var pos3) && Mathf.Abs(pos3.y - pos.y) <= Tuning.RoofSanityMeters) { val6 = pos3; flag4 = true; } else { _loaf.Reject(); } } float stationDist = 0f; if ((Object)(object)CombatTarget != (Object)null && (int)CombatStyle == 1 && (Object)(object)SceneSpot == (Object)null && !_station.CapFallback && !_stationUnreachable && (Object)(object)Target != (Object)null) { Vector3 position2 = CombatTarget.position; Vector3 position3 = Target.position; Vector3 position4 = ((Component)this).transform.position; float num3 = ((SpeedEstimate)(ref _enemySpeed)).Update(position2.x, position2.z, Time.time); if (CombatStation.Decide(ref _station, position2.x, position2.z, position3.x, position3.z, position4.x, position4.z, Cfg.AttackRange, Time.time, StationTuning(), num3)) { _wasPlanted = false; _lastDest = Vector3.positiveInfinity; _stationPartialSince = -1f; _stationOnMesh = NavProbe.SampleAtFeet(new Vector3(_station.X, position2.y, _station.Z), Tuning.GoalSampleRadius, out _stationGround) && Mathf.Abs(_stationGround.y - position2.y) <= Tuning.RoofSanityMeters; if (_stationOnMesh) { _station.X = _stationGround.x; _station.Z = _stationGround.z; } string what = ((!(_station.LastWhy == "first")) ? (((_station.LastWhy == "reach") ? $"restation(reach) #{_station.Reaches}" : $"restation #{_station.Restations}") + (_stationOnMesh ? "" : " (OFF-MESH: chasing)")) : (_stationOnMesh ? "set:" : "set(OFF-MESH: chasing):")); LogStation(what, position2, position3, position4); } else if (_station.CapFallback) { LogStation("cap fallback — chasing for this engagement", position2, position3, position4); } if (_station.Has && !_station.CapFallback && _stationOnMesh) { bool flag7 = ((Behaviour)_agent).enabled && _agent.isOnNavMesh && !_agent.pathPending && _agent.hasPath && (int)_agent.pathStatus > 0; if (!flag7) { _stationPartialSince = -1f; } else if (_stationPartialSince < 0f) { _stationPartialSince = Time.time; } if (flag7 && Time.time - _stationPartialSince > 2f) { _stationUnreachable = true; _stationOnMesh = false; _wasPlanted = false; _lastDest = Vector3.positiveInfinity; LogStation($"set(UNREACHABLE: chasing): path={_agent.pathStatus}", position2, position3, position4); } } if (_station.Has && !_station.CapFallback && _stationOnMesh) { val6 = _stationGround; _stationActive = true; Vector3 val7 = _stationGround - position4; val7.y = 0f; stationDist = ((Vector3)(ref val7)).magnitude; } } AgentDriveFacts val8 = GatherDriveFacts(val2, distToOwner, flag4, flag5, val6, _stationActive, stationDist); AgentDrivePlan val9 = FollowPolicy.PlanAgentDrive(ref val8, Tuning); _planted = val9.Planted; if (((StationPlantEdge)(ref _plantEdge)).Fires(PlantedOnStation)) { Vector3 val10 = CombatTarget.position - ((Component)this).transform.position; val10.y = 0f; LogStation($"planted: distToEnemy={((Vector3)(ref val10)).magnitude:F1}", CombatTarget.position, Target.position, ((Component)this).transform.position); } _agent.stoppingDistance = val9.StoppingDistance; _agent.isStopped = val9.AgentStopped; if (val9.Repath) { _agent.SetDestination(val6); _lastDest = val6; _repath = Time.time; } if (val9.GraceEnded) { _leashGraceUntil = -1f; } _cantReachSince = ((!val9.CantReachNow) ? (-1f) : ((_cantReachSince < 0f) ? Time.time : _cantReachSince)); if (val9.LeashWarp) { _warpReason = ((object)(*(WarpReason*)(&val9.LeashWarpReason))/*cast due to .constrained prefix*/).ToString(); TryWarp(Tuning.LeashWarpSearchRadius, force: false); } else if (val9.ClearWarpBlocked) { _warpBlockedSince = -1f; } Vector3 velocity = _agent.velocity; bool flag8 = ((Vector3)(ref velocity)).magnitude > 0.25f; if (((Vector3)(ref velocity)).magnitude > val4.DriveSpeed * 2f + 1f && Time.time - _velAnomalyLog > 2f) { _velAnomalyLog = Time.time; Transform val11 = CompanionRef?.Invoke(); float num4 = (((Object)(object)val11 != (Object)null) ? Vector3.Distance(((Component)this).transform.position, val11.position) : (-1f)); ModLog log5 = CompanionRuntime.Log; string text2 = $"{TagVel} anomaly: vel={((Vector3)(ref velocity)).magnitude:F1} at drive speed {val4.DriveSpeed:F1} — "; boundAt = _agent.desiredVelocity; object arg2 = ((Vector3)(ref boundAt)).magnitude; boundAt = pos - _lastDest; log5.LogWarning((object)(text2 + $"desVel={arg2:F1} destΔ={((Vector3)(ref boundAt)).magnitude:F2} " + $"anchorSep={num4:F2} radius={_agent.radius:F2} avoid={_agent.obstacleAvoidanceType}")); } bool flag9 = !_moveGate.DisplacingBelief; LookTarget val12 = LookTargetPolicy.Choose((Object)(object)CombatTarget != (Object)null, val9.Planted, _stationActive, val9.PointingHold, flag4, flag5, flag9); Vector3 val13 = (((int)val12 == 2) ? CombatTarget.position : (((int)val12 == 3) ? _facePoint : (((int)val12 == 0) ? val6 : (((int)val12 == 4) ? (((Component)this).transform.position + OwnerHeadingFlat()) : Target.position)))); DriveFacingAndAnim(val12, val13 - ((Component)this).transform.position, flag8, ((Vector3)(ref velocity)).magnitude, val9.Planted); if (_wasPointingHold && !val9.PointingHold) { CompanionRuntime.Log.LogMessage((object)(TagFace + " point-hold ended: " + DescribeFacing())); } _wasPointingHold = val9.PointingHold; float num5 = ((!flag8 && ((Vector3)(ref velocity)).magnitude < 0.05f) ? 30f : 2f); if (Time.time - _diag > num5) { _diag = Time.time; string on = "none"; float value; float num6 = ((_loco != null && _loco.ReadForward(out value, out on)) ? value : (-99f)); float value2; string on2; float num7 = ((_loco != null && _loco.ReadSide(out value2, out on2)) ? value2 : (-99f)); ModLog log6 = CompanionRuntime.Log; object[] obj5 = new object[12] { TagPuppet, distToOwner, ((Vector3)(ref velocity)).magnitude, null, null, null, null, null, null, null, null, null }; boundAt = _agent.desiredVelocity; obj5[3] = ((Vector3)(ref boundAt)).magnitude; obj5[4] = flag8; obj5[5] = num6; obj5[6] = num7; obj5[7] = on; obj5[8] = _lastLook; obj5[9] = (Object)(object)_anim != (Object)null && _anim.applyRootMotion; obj5[10] = ((Component)this).transform.position.y; obj5[11] = _agent.pathStatus; log6.LogDebug((object)string.Format("{0} dist={1:F1} vel={2:F2} desVel={3:F2} moving={4} mF={5:F2} mS={6:F2}@{7} look={8} rootMotion={9} y={10:F2} path={11}", obj5)); } } private FollowFacts GatherFollowFacts(Transform goal) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Unknown result type (might be due to invalid IL or missing references) FollowFacts val = default(FollowFacts); val.HasTargetAndAgent = (Object)(object)Target != (Object)null && (Object)(object)_agent != (Object)null; val.TargetSane = val.HasTargetAndAgent && CompanionRuntime.IsSanePosition(Target.position); val.AgentAbandoned = _agentAbandoned; val.AgentEnabled = (Object)(object)_agent != (Object)null && ((Behaviour)_agent).enabled; val.AgentSuspendedExternally = _agentSuspended; val.DistToOwner = (val.HasTargetAndAgent ? Vector3.Distance(((Component)this).transform.position, Target.position) : 0f); val.HasCombatTarget = (Object)(object)CombatTarget != (Object)null; val.HasFollowOverride = (Object)(object)FollowOverride != (Object)null; val.HasSceneSpot = (Object)(object)SceneSpot != (Object)null; val.HasStaySpot = (Object)(object)StaySpot != (Object)null; val.GoalIsOwner = goal == Target; val.Now = Time.time; val.LastReplaceAt = _warpCd; val.LeashGraceUntil = _leashGraceUntil; val.FaceUntil = _faceUntil; val.BaseSpeed = Speed; val.FollowSpeedFloor = FloorNow; val.FollowRefDist = FollowRefDist(); val.CatchUpSpeed = CatchUpSpeedNow; val.LeashDistance = EffectiveLeash; val.CombatLeashDistance = Cfg.CombatLeashDistance; val.SuppressLeashWarp = Cfg.SuppressLeashWarp; return val; } private AgentDriveFacts GatherDriveFacts(Transform goal, float dist, bool loafActive, bool approachActive, Vector3 dest, bool stationActive, float stationDist) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Invalid comparison between Unknown and I4 //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) AgentDriveFacts result = new AgentDriveFacts { HasCombatTarget = ((Object)(object)CombatTarget != (Object)null), GoalDist = Vector3.Distance(((Component)this).transform.position, goal.position), AttackRange = Cfg.AttackRange, HasStation = stationActive, StationDist = stationDist, StationArriveMeters = Cfg.StationArriveMeters, WasPlanted = _wasPlanted, Now = Time.time, FaceUntil = _faceUntil, LoafActive = loafActive, ApproachActive = approachActive, SceneSpotActive = ((Object)(object)SceneSpot != (Object)null), StaySpotActive = ((Object)(object)StaySpot != (Object)null), HasPath = _agent.hasPath, LastRepathAt = _repath }; Vector3 val = dest - _lastDest; result.DestMovedSq = ((Vector3)(ref val)).sqrMagnitude; result.LeashDist = (((Object)(object)FollowOverride != (Object)null && (Object)(object)StaySpot == (Object)null) ? Vector3.Distance(((Component)this).transform.position, FollowOverride.position) : dist); result.LeashGraceUntil = _leashGraceUntil; result.PathPending = _agent.pathPending; result.PathComplete = (int)_agent.pathStatus == 0; result.LastWarpAt = _warpCd; result.CantReachSince = _cantReachSince; result.StopDistance = StopDistance; result.LeashDistance = EffectiveLeash; result.CombatLeashDistance = Cfg.CombatLeashDistance; result.SuppressLeashWarp = Cfg.SuppressLeashWarp; return result; } private void ReconDesync(Transform goal) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_015a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)FollowOverride == (Object)null) && !(Time.time - _desyncLog < 2f)) { float num = Vector3.Distance(((Component)this).transform.position, FollowOverride.position); if (!(num <= 5f)) { _desyncLog = Time.time; ModLog log = CompanionRuntime.Log; object[] obj = new object[4] { TagDesync, num, null, null }; Vector3 position = ((Component)this).transform.position; obj[2] = ((Vector3)(ref position)).ToString("F1"); position = FollowOverride.position; obj[3] = ((Vector3)(ref position)).ToString("F1"); log.LogMessage((object)(string.Format("{0} sep={1:F1}m puppet={2} anchor={3} ", obj) + string.Format("goal='{0}' combatTarget={1} planted={2} ", ((Object)(object)goal != (Object)null) ? ((Object)goal).name : "?", ((Object)(object)CombatTarget != (Object)null) ? ((Object)CombatTarget).name : "none", _planted) + string.Format("agentOn={0} path={1}", (Object)(object)_agent != (Object)null && ((Behaviour)_agent).enabled, ((Object)(object)_agent != (Object)null && ((Behaviour)_agent).enabled && _agent.isOnNavMesh) ? ((object)_agent.pathStatus/*cast due to .constrained prefix*/).ToString() : "n/a"))); } } } private void LateUpdate() { if ((Object)(object)_rig != (Object)null) { _rig.PinTick(); } this.OnAfterMove?.Invoke(this); } private Vector3 OwnerHeadingFlat() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) Vector3 val = (((Object)(object)Target != (Object)null) ? Target.forward : _faceDir); val.y = 0f; if (!(((Vector3)(ref val)).sqrMagnitude > 0.0001f)) { return _faceDir; } return ((Vector3)(ref val)).normalized; } private void DriveFacingAndAnim(LookTarget look, Vector3 lookDelta, bool isMoving, float animValue, bool planted) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) //IL_0318: Unknown result type (might be due to invalid IL or missing references) //IL_031d: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0349: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Unknown result type (might be due to invalid IL or missing references) //IL_021e: Unknown result type (might be due to invalid IL or missing references) //IL_0223: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0280: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_029f: Unknown result type (might be due to invalid IL or missing references) //IL_02a4: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) Vector3 position = ((Component)this).transform.position; float deltaTime = Time.deltaTime; Vector3 val = Vector3.zero; if (_animPosPrimed && deltaTime > 0f) { val = position - _lastAnimPos; isMoving = _moveGate.Evaluate(val.x, val.z, deltaTime, isMoving); } _lastAnimPos = position; _animPosPrimed = true; if (_loco == null) { _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); } Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(val.x, 0f, val.z); bool flag = StrafeCapability.Exempt(look, planted); float num = ((!flag && isMoving && ((Vector3)(ref val2)).sqrMagnitude > 1E-08f && ((Vector3)(ref _faceDir)).sqrMagnitude > 1E-06f) ? Vector3.Angle(val2, _faceDir) : 0f); Verdict val3 = StrafeCapability.Decide(_loco.HasSide, AllowsBackpedal, num, _strafeForcing); _strafeForcing = val3.ForceFaceToTravel; if (val3.ForceFaceToTravel) { look = (LookTarget)0; lookDelta = val2; } _lastLook = look; Vector3 val4 = lookDelta; val4.y = 0f; if (!LookTargetPolicy.HoldLastFacing(look, isMoving) && ((Vector3)(ref val4)).sqrMagnitude > 0.01f) { Vector3 normalized = ((Vector3)(ref lookDelta)).normalized; if (TurnLaw.Allowed(normalized.y)) { Vector3 normalized2 = ((Vector3)(ref val4)).normalized; if (((Vector3)(ref _faceDir)).sqrMagnitude < 1E-06f) { _faceDir = normalized2; } float num2 = Vector3.Angle(_faceDir, normalized2); float num3 = TurnLaw.StepDeg(num2, Tuning.TurnSpeed, TurnCapDegPerSec, deltaTime); if (num3 > 0f) { Vector3 val5 = Vector3.RotateTowards(_faceDir, normalized2, num3 * ((float)Math.PI / 180f), 0f); _faceDir = ((Vector3)(ref val5)).normalized; } } } if (((Vector3)(ref _faceDir)).sqrMagnitude > 0.01f) { float num4 = (float.IsNaN(YawOffset) ? Cfg.ModelYawOffset : YawOffset); Quaternion val6 = Quaternion.LookRotation(_faceDir, Vector3.up) * Quaternion.Inverse(Quaternion.Euler(0f, num4, 0f)); if (_slope == null || !CkConfig.Slope.EnableSlopeTilt.Value || !SlopeTiltEligible) { ((Component)this).transform.rotation = Quaternion.RotateTowards(((Component)this).transform.rotation, val6, TurnCapDegPerSec * Time.deltaTime); _flatRot = ((Component)this).transform.rotation; } else { _flatRot = Quaternion.RotateTowards(_flatRot, val6, TurnCapDegPerSec * Time.deltaTime); ((Component)this).transform.rotation = _slope.Apply(_flatRot, ((Component)this).transform.position, Time.deltaTime); } } if (val3.FeedSide) { Blend val7 = LocoFrame.ProjectDisplacement(val.x, val.z, deltaTime, _faceDir.x, _faceDir.z, isMoving); _animF = LocoFrame.SmoothToward(_animF, val7.Forward, deltaTime, 1.5f, 0.5f); _animS = LocoFrame.SmoothToward(_animS, val7.Side, deltaTime, 1.5f, 0.5f); _loco.Drive(isMoving, _animF, _animS); } else { _animF = EffigyPinMath.AnimForward(animValue, isMoving); _animS = 0f; _loco.Drive(isMoving, animValue); } } public void FindDrift() { ((MonoBehaviour)this).StartCoroutine(BodyDiagnostics.DriftScan(this)); } private void AgentFreeReplaceOntoOwner(string label, string reason, float dist) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Target == (Object)null)) { _warpCd = Time.time; Vector3 pos; Vector3 val = (NavProbe.SampleAtFeet(Target.position, Tuning.OwnerSampleRadius, out pos) ? pos : Target.position); val.y = GroundY(val); ((Component)this).transform.position = val; _moveGate.Reset(); _animPosPrimed = false; ModLog log = CompanionRuntime.Log; string[] obj = new string[5] { $"{TagPuppet} agent-free re-place ({label}): dist={dist:F0} ", "me=", null, null, null }; Vector3 position = ((Component)this).transform.position; obj[2] = ((Vector3)(ref position)).ToString("F0"); obj[3] = " target="; position = Target.position; obj[4] = ((Vector3)(ref position)).ToString("F0"); log.LogMessage((object)string.Concat(obj)); RaiseWarpedToOwner(reason); } } private void DirectDrive(Vector3 targetPos, bool combat) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Invalid comparison between Unknown and I4 //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0173: Invalid comparison between Unknown and I4 //IL_0183: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Invalid comparison between Unknown and I4 //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) if (((Behaviour)_agent).enabled) { ((Behaviour)_agent).enabled = false; CompanionRuntime.Log.LogMessage((object)(TagPuppet + " direct-drive: no baked navmesh at the goal — transform drive with raycast grounding " + (_agentAbandoned ? "(agent abandoned for this body — no re-arm)." : "(agent re-arms when mesh returns)."))); } Vector3 val = ((Component)this).transform.position; Vector3 val2 = targetPos - val; val2.y = 0f; float magnitude = ((Vector3)(ref val2)).magnitude; float num = (combat ? (Cfg.AttackRange * 0.9f) : StopDistance); float num2 = ((magnitude > num) ? FlatDriveSpeed : 0f); if (num2 > 0f) { val += ((Vector3)(ref val2)).normalized * Mathf.Min(num2 * Time.deltaTime, magnitude); } float num3 = GroundY(val); val.y = ((Mathf.Abs(num3 - val.y) > 1.5f) ? num3 : Mathf.Lerp(val.y, num3, Time.deltaTime * 10f)); ((Component)this).transform.position = val; bool flag = num2 <= 0f || !_moveGate.DisplacingBelief; LookTarget val3 = LookTargetPolicy.Choose(combat, combat && num2 <= 0f, false, false, false, (Object)(object)FollowOverride != (Object)null, flag); Vector3 val4 = (((int)val3 == 2 && (Object)(object)CombatTarget != (Object)null) ? CombatTarget.position : (((int)val3 == 1 && (Object)(object)Target != (Object)null) ? Target.position : (((int)val3 == 4 && (Object)(object)Target != (Object)null) ? (val + OwnerHeadingFlat()) : targetPos))); DriveFacingAndAnim(val3, val4 - val, num2 > 0f, num2, combat && num2 <= 0f); if (Time.time - _diag > 2f) { _diag = Time.time; ModLog log = CompanionRuntime.Log; object[] obj = new object[5] { TagPuppet, magnitude, num2, ((Component)this).transform.position.y, null }; object obj2; if (!((Object)(object)FollowOverride != (Object)null)) { obj2 = "none"; } else { Vector3 position = FollowOverride.position; obj2 = ((Vector3)(ref position)).ToString("F1"); } obj[4] = obj2; log.LogMessage((object)string.Format("{0} direct-drive: goalDist={1:F1} speed={2:F1} y={3:F2} anchor={4}", obj)); } } private float GroundY(Vector3 at) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) RaycastHit val = default(RaycastHit); if (!Physics.Raycast(at + Vector3.up * 2f, Vector3.down, ref val, 8f, Global.LargeEnvironmentMask)) { return at.y; } return ((RaycastHit)(ref val)).point.y; } public void PlaceAt(Vector3 spot) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) PlaceRefusal val = PlacementGate.CheckSpot(spot.x, spot.y, spot.z); if (!PlacementGate.Allows(val)) { CompanionRuntime.Log.LogWarning((object)(TagPuppet + " place REFUSED: " + PlacementGate.Describe(val) + " (spot=" + ((Vector3)(ref spot)).ToString("F1") + ") — body unchanged.")); return; } if ((Object)(object)_agent == (Object)null) { ((Component)this).transform.position = spot; _moveGate.Reset(); _animPosPrimed = false; _stuck.Reset(); return; } ((Behaviour)_agent).enabled = false; spot.y = GroundY(spot); ((Component)this).transform.position = spot; _moveGate.Reset(); _animPosPrimed = false; _stuck.Reset(); if (!_agentAbandoned && NavProbe.SampleAtFeet(spot, 1.5f, out var _)) { ((Behaviour)_agent).enabled = true; if (!_agent.isOnNavMesh) { _agent.Warp(spot); } } _warpCd = Time.time; CompanionRuntime.Log.LogMessage((object)string.Format("{0} placed at {1} (onMesh={2}).", TagPuppet, ((Vector3)(ref spot)).ToString("F1"), ((Behaviour)_agent).enabled)); } private void Idle() { if (_loco == null) { _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); } _loco.Idle(); } public void GraceLeash(float seconds) { if (seconds > 0f) { _leashGraceUntil = Time.time + seconds; } } public void FacePoint(Vector3 worldPos, float seconds) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) _facePoint = worldPos; _faceUntil = ((seconds > 0f) ? (Time.time + seconds) : (-1f)); } public string DescribeFacing() { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_0262: Unknown result type (might be due to invalid IL or missing references) //IL_02cf: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) float num = (float.IsNaN(YawOffset) ? Cfg.ModelYawOffset : YawOffset); float y = ((Component)this).transform.eulerAngles.y; Vector3 val = ((Component)this).transform.rotation * Quaternion.Euler(0f, num, 0f) * Vector3.forward; float num2 = Mathf.Atan2(val.x, val.z) * 57.29578f; float num3 = ((((Vector3)(ref _faceDir)).sqrMagnitude > 1E-06f) ? (Mathf.Atan2(_faceDir.x, _faceDir.z) * 57.29578f) : float.NaN); string text = "none"; if ((Object)(object)Target != (Object)null) { Vector3 val2 = Target.position - ((Component)this).transform.position; val2.y = 0f; float num4 = Mathf.Atan2(val2.x, val2.z) * 57.29578f; text = $"dist={((Vector3)(ref val2)).magnitude:F2} bearing={num4:F0} noseOffOwner={Mathf.DeltaAngle(num4, num2):F0}"; } float num5 = _faceUntil - Time.time; string[] obj = new string[9] { $"transformYaw={y:F0} yawOffset={num:F0} noseYaw={num2:F0} faceDirYaw={num3:F0} ", "owner[", text, "] hold=", (num5 > 0f) ? string.Format("{0:F1}s to {1}", num5, ((Vector3)(ref _facePoint)).ToString("F1")) : "none", " ", $"sceneSpot={(Object)(object)SceneSpot != (Object)null} staySpot={(Object)(object)StaySpot != (Object)null} followOverride={(Object)(object)FollowOverride != (Object)null} ", $"combat={(Object)(object)CombatTarget != (Object)null} loaf[engaged={_loaf.Engaged} point={_loaf.HasPoint}] ", null }; object[] obj2 = new object[6] { _lastLook, _strafeForcing, AllowsBackpedal, _animF, _animS, null }; float num6; if (!((Object)(object)_agent != (Object)null) || !((Behaviour)_agent).enabled) { num6 = 0f; } else { Vector3 velocity = _agent.velocity; num6 = ((Vector3)(ref velocity)).magnitude; } obj2[5] = num6; obj[8] = string.Format("look={0} strafeForcing={1} backpedal={2} mF={3:F2} mS={4:F2} vel={5:F2}", obj2); return string.Concat(obj); } private CombatStationTuning StationTuning() { _stationTuning.RingFraction = Cfg.StationRingFraction; _stationTuning.LineAngleDeg = Cfg.StationLineAngleDeg; _stationTuning.RestationMeters = Cfg.StationRestationMeters; _stationTuning.RestationSeconds = Cfg.StationRestationSeconds; _stationTuning.ArriveMeters = Cfg.StationArriveMeters; _stationTuning.MaxRestations = Cfg.StationMaxRestations; _stationTuning.FarMeters = Cfg.StationFarMeters; _stationTuning.ProgressMeters = Cfg.StationProgressMeters; _stationTuning.EnemyFastMetersPerSecond = Cfg.StationEnemyFastMetersPerSecond; return _stationTuning; } private void LogStation(string what, Vector3 enemy, Vector3 owner, Vector3 pet) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) float num = CombatStation.CorridorAngleDeg(enemy.x, enemy.z, owner.x, owner.z, pet.x, pet.z); float num2 = CombatStation.CorridorBlockAngleDeg(enemy.x, enemy.z, owner.x, owner.z, pet.x, pet.z); ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)($"{TagStation} {what} why={_station.LastWhy} corridor={num:F0} block={num2:F0} " + $"restations={_station.Restations} reaches={_station.Reaches} station=({_station.X:F1},{_station.Z:F1}) " + "enemy=" + ((Vector3)(ref enemy)).ToString("F1") + " owner=" + ((Vector3)(ref owner)).ToString("F1") + " pet=" + ((Vector3)(ref pet)).ToString("F1") + " " + string.Format("target='{0}' body=#{1}", ((Object)(object)CombatTarget != (Object)null) ? ((Object)CombatTarget).name : "none", _bodyId))); } } private void ResetStationAfterTeleport() { //IL_004b: Unknown result type (might be due to invalid IL or missing references) if (_station.Has || _stationUnreachable) { CombatStation.Reset(ref _station); ((SpeedEstimate)(ref _enemySpeed)).Reset(); _stationOnMesh = false; _stationUnreachable = false; _stationPartialSince = -1f; _plantEdge = default(StationPlantEdge); } } public void Resummon() { _warpReason = "Resummon"; TryWarp(20f, force: true); } private void TryWarp(float searchRadius, bool force) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_023b: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_agent == (Object)null || (Object)(object)Target == (Object)null) { return; } if (_agentAbandoned) { if (NavProbe.SampleAtFeet(Target.position, force ? searchRadius : 1.5f, out var pos)) { pos.y = GroundY(pos); ((Component)this).transform.position = pos; _moveGate.Reset(); _animPosPrimed = false; _stuck.Reset(); _warpCd = Time.time; CompanionRuntime.Log.LogMessage((object)string.Format("{0} warped to player (agent-free, latched, force={1}) spot={2}", TagPuppet, force, ((Vector3)(ref pos)).ToString("F1"))); RaiseWarpedToOwner(_warpReason); ResetStationAfterTeleport(); } else if (force) { CompanionRuntime.Log.LogWarning((object)(TagPuppet + " resummon: no navmesh near the player to warp onto.")); } return; } float radius = (force ? searchRadius : ((_warpBlockedSince >= 0f && Time.time - _warpBlockedSince > 6f) ? 3f : 1.5f)); Vector3 spot; bool flag = WarpLandingBehind(radius, out spot); if (flag || NavProbe.SampleAtFeet(Target.position, radius, out spot)) { if (_agent.Warp(spot)) { _moveGate.Reset(); _animPosPrimed = false; _stuck.Reset(); _warpCd = Time.time; _warpBlockedSince = -1f; FaceOwnerForward(); PlayWarpFx(spot); CompanionRuntime.Log.LogMessage((object)string.Format("{0} warped to player (force={1}) spot={2} Δy={3:+0.0;-0.0} behind={4} gap={5:F1}m reason={6} landing[{7}]", TagPuppet, force, ((Vector3)(ref spot)).ToString("F1"), spot.y - Target.position.y, flag, Vector3.Distance(spot, Target.position), _warpReason, _landing)); ResetStationAfterTeleport(); RaiseWarpedToOwner(_warpReason); } else { Vector3 boundAt; bool flag2 = ReboundAfterRefusedWarp(spot, Target.position, radius, out boundAt); _moveGate.Reset(); _animPosPrimed = false; _warpCd = Time.time; _warpBlockedSince = -1f; if (Time.time - _warpRefusedLogAt > 2f) { _warpRefusedLogAt = Time.time; CompanionRuntime.Log.LogWarning((object)(string.Format("{0} warp REFUSED by navmesh at spot={1} (force={2}) — ", TagPuppet, ((Vector3)(ref spot)).ToString("F1"), force) + (flag2 ? ("rebound at a wider sample (" + ((Vector3)(ref boundAt)).ToString("F1") + ")") : "no bindable polygon; teleported the transform + rebound the agent") + $" onNavMesh={_agent.isOnNavMesh}.")); } ResetStationAfterTeleport(); RaiseWarpedToOwner(_warpReason); } } else if (force) { CompanionRuntime.Log.LogWarning((object)(TagPuppet + " resummon: no navmesh near the player to warp onto.")); } else if (_warpBlockedSince < 0f) { _warpBlockedSince = Time.time; CompanionRuntime.Log.LogMessage((object)(TagPuppet + " leash-warp blocked: no navmesh at the player's feet (r=1.5m); retrying (widens to 3m after 6s).")); } } private bool WarpLandingBehind(float radius, out Vector3 spot) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0358: Unknown result type (might be due to invalid IL or missing references) //IL_035a: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Unknown result type (might be due to invalid IL or missing references) //IL_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0172: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_019e: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01c2: Unknown result type (might be due to invalid IL or missing references) //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01e0: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01f8: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Unknown result type (might be due to invalid IL or missing references) //IL_02c3: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d7: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e5: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_02a5: Unknown result type (might be due to invalid IL or missing references) //IL_02a7: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) //IL_0302: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) spot = default(Vector3); _landing = ""; if ((Object)(object)Target == (Object)null) { return false; } if (!NavProbe.SampleAtFeet(Target.position, Mathf.Min(radius, 1.5f), out var pos)) { _landing = "no-feet-polygon"; return false; } Camera main = Camera.main; Vector3 val = (((Object)(object)main != (Object)null) ? ((Component)main).transform.forward : Target.forward); Vector3 val2 = (((Object)(object)main != (Object)null) ? ((Component)main).transform.right : Target.right); val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { val = Target.forward; val.y = 0f; } if (((Vector3)(ref val)).sqrMagnitude < 0.0001f) { _landing = "no-heading"; return false; } int num = FollowPolicy.WarpBehindLadder(Tuning, s_ladder); bool flag = false; Vector3 val3 = default(Vector3); float num2 = -1f; string text = ""; float num3 = default(float); float num4 = default(float); NavMeshHit val7 = default(NavMeshHit); for (int i = 0; i < num; i++) { for (int j = 0; j < s_bearings.Length; j++) { Vector3 val4 = Quaternion.Euler(0f, s_bearings[j], 0f) * val; Vector3 val5 = Quaternion.Euler(0f, s_bearings[j], 0f) * val2; FollowPolicy.WarpOffset(val4.x, val4.z, val5.x, val5.z, s_ladder[i], Tuning, ref num3, ref num4); Vector3 val6 = pos + new Vector3(num3, 0f, num4); if (!NavMesh.SamplePosition(val6, ref val7, 2.5f, -1)) { continue; } Vector3 position = ((NavMeshHit)(ref val7)).position; float num5 = Vector3.Distance(pos, position); if (num5 < s_ladder[0] * 0.6f || !NavMesh.CalculatePath(pos, position, -1, s_path) || (int)s_path.status != 0) { continue; } float num6 = 0f; Vector3[] corners = s_path.corners; for (int k = 1; k < corners.Length; k++) { num6 += Vector3.Distance(corners[k - 1], corners[k]); } if (!(num6 > num5 * 1.6f)) { string text2 = $"rung={s_ladder[i]:F1} bearing={s_bearings[j]:+0;-0} straight={num5:F1} path={num6:F1}"; if ((Object)(object)main == (Object)null) { spot = position; _landing = text2 + " (no camera)"; return true; } Vector3 val8 = main.WorldToViewportPoint(position + Vector3.up * 1f); if (FollowPolicy.IsOffscreen(val8.x, val8.y, val8.z, Tuning)) { spot = position; _landing = text2 + " offscreen"; return true; } if (num5 > num2) { flag = true; val3 = position; num2 = num5; text = text2; } } } } if (flag) { spot = val3; _landing = text + " ONSCREEN-fallback"; return true; } _landing = "no-reachable-rung"; return false; } private void FaceOwnerForward() { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)Target == (Object)null)) { Vector3 forward = Target.forward; forward.y = 0f; if (((Vector3)(ref forward)).sqrMagnitude > 0.0001f) { _faceDir = ((Vector3)(ref forward)).normalized; } } } private void PlayWarpFx(Vector3 at) { //IL_00fb: Unknown result type (might be due to invalid IL or missing references) try { if (!s_warpFxTried) { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; if (instance == null) { return; } s_warpFxTried = true; Item itemPrefab = instance.GetItemPrefab(8400010); SummonPet val = (((Object)(object)itemPrefab != (Object)null) ? ((Component)itemPrefab).GetComponentInChildren(true) : null); ParticleSystem val2 = (((Object)(object)val != (Object)null && (Object)(object)((Summon)val).SummonedPrefab != (Object)null) ? ((Component)((Summon)val).SummonedPrefab).GetComponentInChildren(true) : null); if ((Object)(object)val2 != (Object)null && (Object)(object)((Component)val2).transform != (Object)(object)((Summon)val).SummonedPrefab) { s_warpFx = Object.Instantiate(val2); ((Object)s_warpFx).name = "CK_WarpFx"; Object.DontDestroyOnLoad((Object)(object)((Component)s_warpFx).gameObject); ((Component)s_warpFx).gameObject.SetActive(true); s_warpFx.Stop(true, (ParticleSystemStopBehavior)0); } else { CompanionRuntime.Log.LogMessage((object)(TagPuppet + " warp FX: no cosmetic-pet SpawnFX found (DLC data absent?) — warps play silently.")); } } if (!((Object)(object)s_warpFx == (Object)null)) { ((Component)s_warpFx).transform.position = at; s_warpFx.Play(true); } } catch (Exception ex) { s_warpFx = null; CompanionRuntime.Log.LogWarning((object)(TagPuppet + " warp FX disabled: " + ex.Message)); } } private bool ReboundAfterRefusedWarp(Vector3 spot, Vector3 reference, float radius, out Vector3 boundAt) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) boundAt = spot; if (!_agentAbandoned) { for (float num = radius * 2f; num <= radius * 8f; num *= 2f) { if (NavProbe.SampleAtFeet(reference, num, out var pos) && _agent.Warp(pos)) { boundAt = pos; return true; } } } ((Behaviour)_agent).enabled = false; ((Component)this).transform.position = spot; if (!_agentAbandoned) { ((Behaviour)_agent).enabled = true; } return false; } public void DumpPositions() { ((MonoBehaviour)this).StartCoroutine(BodyDiagnostics.PosDump(this)); } public static void DumpAllPositions() { if ((Object)(object)Plugin.Instance != (Object)null) { ((MonoBehaviour)Plugin.Instance).StartCoroutine(BodyDiagnostics.PosDumpAll()); return; } ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)"[POS] census-wide posdump unavailable — kit plugin not ready."); } } } public class CompanionCombat : MonoBehaviour { public Action OnEnemyDefeated; public float DecayRiderFraction; public float ElementRiderFraction; public Types ElementRiderType = (Types)5; public static bool TraceRiderHits; private const float RiderTraceMinInterval = 0.5f; private float _lastRiderTraceAt; private int _riderTraceSuppressed; private const float Knockback = 1.5f; private float[] _dmgProfile; private float _dmgProfileTotal; private float _impact = 1.5f; private CompanionBody _follower; private Animator _anim; private CharacterSoundManager _sound; private bool _vocalWarned; private bool _hasAttackAnim; private bool _attackProbed; private int _attackProbes; private const int UnansweredProbeWarnAt = 5; private Character _target; private float _lastScan; private float _lastAttack; private Character _prevTarget; private bool _prevHadTarget; private PunctualDamage _hit; private static readonly MethodInfo ActivateLocallyMethod = typeof(PunctualDamage).GetMethod("ActivateLocally", BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(Character), typeof(object[]) }, null); private static bool s_activateLocallyWarned; private bool _pipelineBroken; private int _pipelineFailures; private bool _loggedPipeline; private Character _protectedLogged; public const float RemoteDefendHoldSeconds = 10f; private Character _remoteDefend; private double _remoteDefendAt = -1.0; public const float OwnerFocusHoldSeconds = 8f; private const float SwingDedupeSeconds = 0.1f; private float _lastSwingSentAt; private int _lastSwingType = -1; private bool _swingWarned; private bool _weaponDrawn; private bool _postureReported; private GameObject _staySpotGo; private Transform _prevAnchorFollow; public float DamageMultiplier { get; private set; } = 1f; public CompanionAnchor Anchor { get; private set; } public CommandStance Stance { get; private set; } public ICompanionSettings Settings { get; private set; } public CompanionHost Host { get; private set; } public Func Owner { get; private set; } public bool WeaponPosture { get; private set; } public ICompanionNetMirror NetMirror { get; private set; } private ICompanionSettings Cfg => Settings ?? Host?.Settings ?? CompanionRuntime.Fallback; private ModLog Log => Host?.Log ?? CompanionRuntime.Log; private string TagCombat => CompanionRuntime.Tag(Host?.CombatTag ?? "PETCOMBAT", Cfg); private CommandStance St => Stance; public Character CurrentTarget => _target; public Character SpecialAttackTarget { get { if (!((Object)(object)_target != (Object)null)) { return St.CommandedTarget; } return _target; } } public float BaseDamage => ((_dmgProfileTotal > 0f) ? _dmgProfileTotal : Cfg.AttackDamage) * Mathf.Max(0f, DamageMultiplier); public float AttackImpact => _impact; public void SetDamageMultiplier(float value) { DamageMultiplier = value; } internal void Wire(CompanionAnchor anchor, CommandStance stance, ICompanionSettings settings, Func owner, CompanionHost host, bool weaponPosture, ICompanionNetMirror netMirror) { Anchor = anchor; Stance = stance; Settings = settings; Owner = owner; Host = host; WeaponPosture = weaponPosture; NetMirror = netMirror; } private Character ResolveOwner() { if (Owner == null) { return CompanionRuntime.LocalPlayer(); } return Owner(); } public void SetAttackProfile(CreatureAttributes eff) { if (eff != null && eff.HasDamage) { _dmgProfile = eff.Damage; _dmgProfileTotal = eff.DamageTotal; _impact = ((eff.Impact > 0f) ? eff.Impact : 1.5f); } else { _dmgProfile = null; _dmgProfileTotal = 0f; _impact = 1.5f; } } private DamageType[] BuildDamages(float total) { return BuildDamagesFrom(_dmgProfile, _dmgProfileTotal, total); } private static DamageType[] BuildDamagesFrom(float[] profile, float profileTotal, float total) { //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown if (profile == null || profileTotal <= 0f) { return (DamageType[])(object)new DamageType[1] { new DamageType(total) }; } float[] array = CreatureAttributes.DistributeTotal(profile, total, profile.Length); List list = new List(3); for (int i = 0; i < array.Length; i++) { if (array[i] > 0f) { list.Add(new DamageType { Type = (Types)i, Damage = array[i] }); } } return list.ToArray(); } private void Start() { _follower = ((Component)this).GetComponent(); _follower.OnWarpedToOwner += OnWarpedToOwner; if (St != null && St.Stay) { St.CommandDisengage(); Log.LogMessage((object)(TagCombat + " stance: STAY ended — new body (scene change / rebuild); following.")); } _anim = ((Component)this).GetComponent(); _sound = ((Component)this).GetComponent(); CompanionBody.NormalizeChildAnimators(_anim, ((Component)this).gameObject); ProbeAttackRig(); } private void ProbeAttackRig() { if (_attackProbed || (Object)(object)_anim == (Object)null) { return; } Animator[] componentsInChildren = ((Component)this).GetComponentsInChildren(true); bool flag = CompanionBody.HasParam(_anim, "Attack1"); Animator anim = (flag ? _anim : null); if (!flag) { Animator[] array = componentsInChildren; foreach (Animator val in array) { if ((Object)(object)val != (Object)null && (Object)(object)val != (Object)(object)_anim && CompanionBody.HasParam(val, "Attack1")) { anim = val; flag = true; break; } } } bool flag2 = AnimParamLatch.IsAnswer(_anim.parameterCount); Animator[] array2 = componentsInChildren; foreach (Animator val2 in array2) { if ((Object)(object)val2 != (Object)null && !AnimParamLatch.IsAnswer(val2.parameterCount)) { flag2 = false; break; } } if (!AnimParamLatch.ShouldLatchCensus(flag, flag2)) { if (++_attackProbes == 5) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)($"{TagCombat} attack rig still unanswered after {_attackProbes} swings on " + "'" + ((Object)((Component)this).gameObject).name + "' — some animator keeps reporting zero parameters; the pet is swinging with no animation. `animdump` names the rig.")); } } return; } _hasAttackAnim = flag; if (flag) { _anim = anim; } _attackProbed = true; ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogMessage((object)(TagCombat + " anim rig on '" + ((Object)((Component)this).gameObject).name + "': anim='" + (((Object)(object)_anim != (Object)null) ? ((Object)((Component)_anim).gameObject).name : "none") + "' ctrl='" + (((Object)(object)_anim != (Object)null && (Object)(object)_anim.runtimeAnimatorController != (Object)null) ? ((Object)_anim.runtimeAnimatorController).name : "none") + "' " + $"hasAttack1={_hasAttackAnim}.")); } } private void Update() { //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_047c: Unknown result type (might be due to invalid IL or missing references) //IL_048c: Unknown result type (might be due to invalid IL or missing references) //IL_02dc: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e0: Unknown result type (might be due to invalid IL or missing references) //IL_02e2: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02fd: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0306: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Expected I4, but got Unknown //IL_03c4: Unknown result type (might be due to invalid IL or missing references) //IL_03dc: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) Character val = ResolveOwner(); if ((Object)(object)val == (Object)null || (Object)(object)_follower == (Object)null) { Disengage(); } else { if (Stance == null) { return; } if ((Object)(object)_target != (Object)null && !_target.Alive) { OnEnemyDefeated?.Invoke(); _target = null; } SyncProxyStance(val, St.Passive); if (St.Passive) { if ((Object)(object)_target != (Object)null || (Object)(object)_follower.CombatTarget != (Object)null) { Disengage(); Anchor?.Calm(); OwnerFocusTracker.Forget(val); Log.LogMessage((object)(TagCombat + " stance: PASSIVE — " + (St.Stay ? "holding this spot" : "returning to the player") + " until re-engaged.")); } ApplyStaySpot(); return; } ApplyStaySpot(); if (Time.time - _lastScan > 0.3f) { _lastScan = Time.time; Character val2 = RefuseProtected(St.CommandedTarget, "commanded"); Character val3 = RefuseProtected(OwnerFocusCandidate(val), "owner-focus"); Character val4 = RefuseProtected(AnchorLockCandidate(), "anchor-defend"); List engagedCharacters = val.EngagedCharacters; Character val5 = RefuseProtected(NearestEngagedAI(val, engagedCharacters), "player-engaged"); CombatTargetFacts val6 = new CombatTargetFacts { StancePassive = St.Passive, AssistOnOwnerHit = Cfg.AssistOnOwnerHit, OwnerFocusExists = ((Object)(object)val3 != (Object)null), OwnerFocusDistance = (((Object)(object)val3 != (Object)null) ? DistTo(val3) : 0f), OwnerFocusRange = Cfg.OwnerFocusRange, CommandedExists = ((Object)(object)val2 != (Object)null), CommandedDistance = (((Object)(object)val2 != (Object)null) ? DistTo(val2) : 0f), AnchorDefendExists = ((Object)(object)val4 != (Object)null), AnchorDefendIsEcho = ((Object)(object)val4 != (Object)null && (Object)(object)val4 == (Object)(object)Anchor?.LastAssertedTarget), AnchorDefendDistance = (((Object)(object)val4 != (Object)null) ? DistTo(val4) : 0f), PlayerInCombat = (val.InCombat && engagedCharacters != null), PlayerEngagedExists = ((Object)(object)val5 != (Object)null), PlayerEngagedDistance = (((Object)(object)val5 != (Object)null) ? DistTo(val5) : 0f), AggroRange = Cfg.AggroRange, CombatLeashDistance = Cfg.CombatLeashDistance }; CombatTargetDecision val7 = CombatTargetPolicy.Decide(val6); if (val7.DropCommandedOrder) { St.DropCommanded(); } CombatTargetSource source = val7.Source; switch (source - 1) { case 0: _target = val2; break; case 1: _target = val3; break; case 2: _target = val4; break; case 3: _target = val5; break; default: _target = null; break; } if ((Object)(object)_target != (Object)(object)_prevTarget || (Object)(object)_target != (Object)null != _prevHadTarget) { ModLog log = Log; string[] obj = new string[10] { TagCombat, " target: ", NameOf(_prevTarget), " -> ", NameOf(_target), " (", val7.Reason, ", petPos=", null, null }; Vector3 position = ((Component)this).transform.position; obj[8] = ((Vector3)(ref position)).ToString("F0"); obj[9] = ")"; log.LogMessage((object)string.Concat(obj)); _prevTarget = _target; _prevHadTarget = (Object)(object)_target != (Object)null; } } if ((Object)(object)_target == (Object)null) { Disengage(); return; } _follower.CombatTarget = ((Component)_target).transform; SetWeaponDrawn(drawn: true); Anchor?.UnifyLock(_target); SyncProxyTarget(_target); float num = Vector3.Distance(((Component)this).transform.position, ((Component)_target).transform.position); if (num <= Cfg.AttackRange && Time.time - _lastAttack > Cfg.AttackInterval) { _lastAttack = Time.time; Attack(val, _target); } } } private float DistTo(Character c) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) return Vector3.Distance(((Component)this).transform.position, ((Component)c).transform.position); } private Character RefuseProtected(Character c, string slot) { if ((Object)(object)c == (Object)null) { return null; } Character attacker = ((Anchor != null && Anchor.HasLiveAnchor) ? Anchor.Current : ResolveOwner()); if (!AnchorTargeting.IsProtectedFrom(attacker, c)) { return c; } if ((Object)(object)_protectedLogged != (Object)(object)c) { _protectedLogged = c; Log.LogMessage((object)(TagCombat + " refusing PROTECTED '" + c.Name + "' as a " + slot + " target (AggroKit override).")); } return null; } private static string NameOf(Character c) { if (!((Object)(object)c != (Object)null)) { return "none"; } return c.Name; } private Character AnchorLockCandidate() { if (Anchor != null && Anchor.HasLiveAnchor) { CharacterAI aI = Anchor.AI; Character val = (((Object)(object)aI != (Object)null && (Object)(object)aI.TargetingSystem != (Object)null) ? aI.TargetingSystem.LockedCharacter : null); if ((Object)(object)val != (Object)null && val.Alive && val.IsAI) { return val; } return null; } return RemoteDefendCandidate(); } public bool ReportAnchorAttacked(Character attacker) { bool flag; try { flag = (Object)(object)attacker != (Object)null && attacker.Alive && attacker.IsAI; } catch { flag = false; } if (!flag) { return false; } _remoteDefend = attacker; _remoteDefendAt = Time.time; return true; } private Character RemoteDefendCandidate() { if ((Object)(object)_remoteDefend == (Object)null) { return null; } if (!RemoteDefend.Active(_remoteDefendAt, (double)Time.time, 10f)) { _remoteDefend = null; _remoteDefendAt = -1.0; return null; } Character remoteDefend = _remoteDefend; bool flag; try { flag = remoteDefend.Alive && remoteDefend.IsAI; } catch { flag = false; } if (!flag) { _remoteDefend = null; _remoteDefendAt = -1.0; return null; } return remoteDefend; } private Character OwnerFocusCandidate(Character owner) { if (!Cfg.AssistOnOwnerHit) { return null; } return OwnerFocusTracker.Current(owner, 8f); } private Character NearestEngagedAI(Character player, IList engaged) { if (engaged == null) { return null; } Character result = null; float num = float.MaxValue; for (int i = 0; i < engaged.Count; i++) { Character val = engaged[i]; if (!((Object)(object)val == (Object)null) && val.Alive && val.IsAI && !((Object)(object)val == (Object)(object)player)) { float num2 = DistTo(val); if (num2 <= num) { num = num2; result = val; } } } return result; } private void Attack(Character player, Character target) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) ProbeAttackRig(); if (_hasAttackAnim) { _anim.SetTrigger("Attack1"); MirrorSwing(0); } if (Cfg.AttackVocals) { PlayVocal(0); } Vector3 val = ((Component)target).transform.position - ((Component)this).transform.position; Vector3 normalized = ((Vector3)(ref val)).normalized; DealDamage(target, BaseDamage, normalized); } public void MirrorSwing(int type) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) if (NetMirror == null || type < 0 || (_lastSwingSentAt > 0f && type == _lastSwingType && Time.time - _lastSwingSentAt < 0.1f)) { return; } _lastSwingSentAt = Time.time; _lastSwingType = type; if (NetMirror.ReportSwing(type) || !RoomHasPeers()) { return; } Character val = ResolveOwner(); if ((Object)(object)val == (Object)null) { return; } try { CompanionEffigy.MasterOnSwing(UID.op_Implicit(val.UID), type); } catch (Exception ex) { if (!_swingWarned) { _swingWarned = true; Log.LogWarning((object)(TagCombat + " swing mirror threw (won't retry-log): " + ex.Message)); } } } private static bool RoomHasPeers() { try { return PhotonNetwork.inRoom && PhotonNetwork.otherPlayers != null && PhotonNetwork.otherPlayers.Length != 0; } catch { return false; } } public void PlayVocal(int attackType) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_sound == (Object)null) { return; } try { Global.AudioManager.PlaySoundAtPosition(_sound.GetAttackSound(attackType), ((Component)this).transform, 0f, 1f, 1f, 1f, 1f); } catch (Exception ex) { if (!_vocalWarned) { _vocalWarned = true; Log.LogWarning((object)(TagCombat + " species attack vocal failed (won't retry-log): " + ex.Message)); } } } public void DealDamage(Character target, float dmg, Vector3 dir, float knockbackOverride = -1f) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) DealDamageWith(target, dmg, dir, BuildDamages(dmg), knockbackOverride); } public void DealDamageTyped(Character target, float dmg, Vector3 dir, float[] profile) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) float num = 0f; if (profile != null) { for (int i = 0; i < profile.Length; i++) { num += profile[i]; } } DealDamageWith(target, dmg, dir, BuildDamagesFrom(profile, num, dmg)); } private void DealDamageWith(Character target, float dmg, Vector3 dir, DamageType[] damages, float knockbackOverride = -1f) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) float baseTotal = (TraceRiderHits ? TotalOf(damages) : 0f); damages = AppendRiders(damages, DecayRiderFraction, ElementRiderFraction, ElementRiderType); if (TraceRiderHits) { TraceRider(baseTotal); } float num = ((knockbackOverride >= 0f) ? knockbackOverride : _impact); Character val = ResolveOwner(); bool flag = Anchor != null && Anchor.HasLiveAnchor; Character val2 = (flag ? Anchor.Current : val); if (!_pipelineBroken && TryPipelineHit(target, dir, damages, num)) { if (flag) { ((Component)target).SendMessage("CharHurt", (object)Anchor.Current, (SendMessageOptions)1); } else { NetMirror?.ReportHit(target); } } else { target.ReceiveHit((Weapon)null, dmg, dir, target.CenterPosition, 45f, 1f, val2, num); } } private void TraceRider(float baseTotal) { //IL_0091: Unknown result type (might be due to invalid IL or missing references) float time = Time.time; if (_lastRiderTraceAt > 0f && time - _lastRiderTraceAt < 0.5f) { _riderTraceSuppressed++; return; } _lastRiderTraceAt = time; float num = DamageRider.Amount(baseTotal, ElementRiderFraction); float num2 = DamageRider.Amount(baseTotal, DecayRiderFraction); string arg = ((_riderTraceSuppressed > 0) ? $" (+{_riderTraceSuppressed} hits not traced)" : string.Empty); _riderTraceSuppressed = 0; Log.LogMessage((object)($"[INFUSE] +{num:0.0} {ElementRiderType} rider on hit (base {baseTotal:0.0}, " + $"fraction {ElementRiderFraction:0.###}; decay rider +{num2:0.0}){arg}.")); } private static float TotalOf(DamageType[] damages) { if (damages == null) { return 0f; } float num = 0f; for (int i = 0; i < damages.Length; i++) { num += damages[i].Damage; } return num; } private static DamageType[] AppendRiders(DamageType[] damages, float decayFraction, float elementFraction, Types elementType) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Expected O, but got Unknown if (damages == null || damages.Length == 0) { return damages; } if (decayFraction <= 0f && elementFraction <= 0f) { return damages; } float num = 0f; for (int i = 0; i < damages.Length; i++) { num += damages[i].Damage; } float num2 = DamageRider.Amount(num, decayFraction); float num3 = DamageRider.Amount(num, elementFraction); if (num2 <= 0f && num3 <= 0f) { return damages; } List list = new List(damages.Length + 2); list.AddRange(damages); if (num2 > 0f) { list.Add(new DamageType { Type = (Types)2, Damage = num2 }); } if (num3 > 0f) { list.Add(new DamageType { Type = elementType, Damage = num3 }); } return list.ToArray(); } private bool TryPipelineHit(Character target, Vector3 dir, DamageType[] damages, float knockback) { //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)_hit == (Object)null) { _hit = ((Component)this).gameObject.AddComponent(); _hit.NoDealer = true; _hit.DamageAmplifiedByOwner = false; _hit.HitInventory = false; _hit.DamagesAI = (DamageType[])(object)new DamageType[0]; } if (ActivateLocallyMethod == null) { if (!s_activateLocallyWarned) { s_activateLocallyWarned = true; Log.LogWarning((object)(TagCombat + " PunctualDamage.ActivateLocally not found (game/GameLibs signature change?) — every pet hit falls back to direct ReceiveHit: damage still lands, but CombatHUD will show no numbers for it.")); } return false; } _hit.Damages = damages; _hit.Knockback = knockback; ActivateLocallyMethod.Invoke(_hit, new object[2] { target, new object[2] { ((Component)this).transform.position, dir } }); _pipelineFailures = 0; if (!_loggedPipeline) { _loggedPipeline = true; Log.LogMessage((object)(TagCombat + " dealing damage via DealHit pipeline (CombatHUD-visible).")); } return true; } catch (Exception ex) { _pipelineFailures++; if (_pipelineFailures == 1) { Log.LogWarning((object)(TagCombat + " DealHit pipeline failed; using direct ReceiveHit for this hit: " + ex.Message)); } if (_pipelineFailures >= 3 && !_pipelineBroken) { _pipelineBroken = true; Log.LogWarning((object)(TagCombat + " DealHit pipeline failed 3× in a row — latching to the direct ReceiveHit path (no CombatHUD numbers) for this body's life.")); } return false; } } private void SetWeaponDrawn(bool drawn) { if (!WeaponPosture || (Object)(object)_anim == (Object)null || _weaponDrawn == drawn || !AnimParamLatch.IsAnswer(_anim.parameterCount)) { return; } _weaponDrawn = drawn; bool flag = false; bool flag2 = false; bool flag3 = false; AnimatorControllerParameter[] parameters = _anim.parameters; foreach (AnimatorControllerParameter val in parameters) { if (val.name == "Sheathed") { flag = true; _anim.SetBool("Sheathed", !drawn); } if (val.name == "Unsheathe") { flag2 = true; if (drawn) { _anim.SetTrigger("Unsheathe"); } } if (val.name == "Sheathe") { flag3 = true; if (!drawn) { _anim.SetTrigger("Sheathe"); } } } if (!_postureReported) { _postureReported = true; string text = ((flag || flag2 || flag3) ? ((flag ? "Sheathed " : "") + (flag2 ? "Unsheathe " : "") + (flag3 ? "Sheathe" : "")).Trim() : "NONE"); Log.LogMessage((object)(TagCombat + " weapon posture vocabulary on '" + ((Object)((Component)_anim).gameObject).name + "' ctrl='" + (((Object)(object)_anim.runtimeAnimatorController != (Object)null) ? ((Object)_anim.runtimeAnimatorController).name : "none") + "': " + text + ((text == "NONE") ? " — [Anchor] WeaponPosture is a no-op on this rig." : "."))); } Log.LogMessage((object)(TagCombat + " weapon posture: " + (drawn ? "drawn" : "sheathed") + ".")); } private void ApplyStaySpot() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Expected O, but got Unknown //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) bool flag = St != null && St.Stay; if (flag && (Object)(object)_follower.StaySpot == (Object)null) { Vector3 val = ((Component)this).transform.position; if (NavProbe.SampleAtFeet(val, 1.5f, out var pos)) { val = pos; } _staySpotGo = new GameObject("CK_StaySpot"); _staySpotGo.transform.position = val; _follower.StaySpot = _staySpotGo.transform; _prevAnchorFollow = Anchor?.GetFollowTarget(); Anchor?.SetFollowTarget(_staySpotGo.transform); Log.LogMessage((object)(TagCombat + " stance: STAY at " + ((Vector3)(ref val)).ToString("F1") + " — holding until ordered, or until a leash warp brings it home.")); } else if (!flag && (Object)(object)_follower.StaySpot != (Object)null) { ClearStaySpot(); } } private void ClearStaySpot() { if ((Object)(object)_follower != (Object)null && (Object)(object)_follower.StaySpot != (Object)null && (Object)(object)_staySpotGo != (Object)null && _follower.StaySpot == _staySpotGo.transform) { _follower.StaySpot = null; } if ((Object)(object)_staySpotGo != (Object)null) { Anchor?.SetFollowTarget(_prevAnchorFollow); Object.Destroy((Object)(object)_staySpotGo); _staySpotGo = null; } _prevAnchorFollow = null; } private void OnWarpedToOwner(CompanionBody body, string reason) { if (St != null && St.Stay) { St.CommandDisengage(); Log.LogMessage((object)(TagCombat + " stance: STAY ended — warped to the owner (" + reason + "); following.")); } } private void OnDestroy() { if ((Object)(object)_follower != (Object)null) { _follower.OnWarpedToOwner -= OnWarpedToOwner; } ClearStaySpot(); } private void Disengage() { SetWeaponDrawn(drawn: false); if ((Object)(object)_follower != (Object)null && (Object)(object)_follower.CombatTarget != (Object)null) { _follower.GraceLeash(Cfg.DisengageRunHomeSeconds); _follower.CombatTarget = null; } _target = null; _prevTarget = null; _prevHadTarget = false; Anchor?.ClearAssertedLock(); SyncProxyTarget(null); } private void SyncProxyTarget(Character target) { if (NetMirror != null && NetMirror.SyncTarget(target)) { Log.LogMessage((object)(TagCombat + " [G→M] proxy target → " + (((Object)(object)target != (Object)null) ? ("'" + target.Name + "'") : "none (Calm)") + ".")); } } public void ResetProxyTargetMirror() { NetMirror?.InvalidateTarget(); } private void SyncProxyStance(Character owner, bool passive) { if (!((Object)(object)owner == (Object)null) && NetMirror != null && NetMirror.SyncStance(passive)) { Log.LogMessage((object)(TagCombat + " [G→M] proxy stance → " + (passive ? "passive" : "engaged") + ".")); } } public void ResetProxyStanceMirror() { NetMirror?.InvalidateStance(); } } public class EffigyBodySettings : CompanionSettingsDefaults { private readonly string _suffix; public string Species { get; } public override string LogTagSuffix => _suffix; public override float ModelYawOffset => 180f; public EffigyBodySettings(string species) { Species = species ?? ""; _suffix = (string.IsNullOrEmpty(species) ? "EFFIGY" : ("EFFIGY:" + species)); } } public static class CompanionEffigy { private sealed class Binding { public Character Anchor; public CompanionBody Body; public bool GhostBuildInFlight; public float GhostBuildSince; public string LastRung = ""; public bool AnchorWasLive; public bool AnchorEverResolved; public float AnchorLostAt; public float AnchorDiagAt; public float AnchorDeadSince = -1f; public bool DeadReplicaNoted; public float StagingHoldLogAt; } public const string SetVerb = "ck.effigy.set"; public const string ClearVerb = "ck.effigy.clear"; public const string StanceVerb = "ck.effigy.stance"; public const string SwingVerb = "ck.effigy.swing"; private const float TickSeconds = 2f; private const float WildSearchRange = 40f; private const float OwnedResendSeconds = 30f; private static readonly EffigyLedger _ledger = new EffigyLedger(3f, 10f); private static readonly Dictionary _bind = new Dictionary(StringComparer.Ordinal); public static Func BodySettingsFactory; private static bool _factoryWarned; private static float _nextTickAt; private static bool _poke; private static bool _pinMode; private static bool _pinModeKnown; private static float _scanFailLogAt; private static readonly HashSet _suppressWarned = new HashSet(StringComparer.Ordinal); private static ReplicatedStore _store; private static StateMirror _ownedStance; private static string _reportUid = ""; private static bool _reportHasBody; private static bool _reportStancePassive; private static string _ownedUid; private static string _ownedSig; private static float _rowFailLogAt; private static readonly EffigyHarvestGate _harvestGate = new EffigyHarvestGate(600f); private static bool _harvestInFlight; private static float _harvestInFlightSince; private static string _harvestSpecies = ""; private static readonly HashSet _harvestNoted = new HashSet(StringComparer.OrdinalIgnoreCase); private const float HarvestStaleSeconds = 300f; private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; private static ModLog Log => CompanionRuntime.Log; private static bool Enabled => CkConfig.Effigy.EnableCompanionEffigies?.Value ?? true; private static int MaxBodies => Mathf.Clamp(CkConfig.Effigy.MaxBodies?.Value ?? 4, 0, 32); private static float HarvestRetrySeconds => Mathf.Max(10f, (CkConfig.Effigy.HarvestRetryMinutes?.Value ?? 10f) * 60f); private static ICompanionSettings SettingsFor(string species) { Func bodySettingsFactory = BodySettingsFactory; if (bodySettingsFactory != null) { try { EffigyBodySettings effigyBodySettings = bodySettingsFactory(species); if (effigyBodySettings != null) { return effigyBodySettings; } } catch (Exception ex) { if (!_factoryWarned) { _factoryWarned = true; Log.LogWarning((object)("[EFFIGY] BodySettingsFactory threw (falling back to the kit default; warned once): " + ex.Message)); } } } return new EffigyBodySettings(species); } internal static void Init() { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown //IL_006c: Expected O, but got Unknown //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Expected O, but got Unknown _pinMode = CkConfig.Effigy.PinToAnchor?.Value ?? false; _pinModeKnown = true; _store = NetBus.RegisterStore("effigy", new StoreOptions { Authority = (StoreAuthority)1, RefreshSeconds = 30f, FlushOnPeerReady = false, ClearOnRoomChange = true, Verbs = new StoreVerbs { Announce = "ck.effigy.set", Release = "ck.effigy.clear" } }); _store.OnSet += OnRecordSet; _store.OnCleared += OnRecordCleared; NetBus.Register("ck.effigy.stance", OnStance, (HandlerRole)12); NetBus.Register("ck.effigy.swing", OnSwing, (HandlerRole)12); Net.OnRoomChanged += OnRoomChanged; BodyCensus.RegisterInternalClaimSource(ClaimedBodies); NetBus.SubscribePeerSceneReady(FlushTo); _ownedStance = NetBus.Mirror("ck.effigy.stance", (MirrorTarget)1, () => (!_reportHasBody || _reportUid.Length <= 0) ? null : NetProtocol.BuildStance(_reportStancePassive), new MirrorOptions { Extra = () => _reportUid, ResendSeconds = 30f }); } private static int LocalActorId() { try { return (PhotonNetwork.player != null) ? PhotonNetwork.player.ID : 0; } catch { return 0; } } private static void OnRecordSet(string key, string payload, RecordMeta meta) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) string ownerUid = default(string); int num = default(int); RecordKey.TryParse(key, ref ownerUid, ref num); string species = default(string); int tier = default(int); string extension = default(string); NetProtocol.ParseEffigySet(payload, ref species, ref tier, ref extension); bool selfApply = meta.SenderActor != 0 && meta.SenderActor == LocalActorId(); ApplySet(ownerUid, species, tier, extension, selfApply); } private static void OnRecordCleared(string key, string reason, RecordMeta meta) { string ownerUid = default(string); int num = default(int); RecordKey.TryParse(key, ref ownerUid, ref num); RemoveRow(ownerUid, string.IsNullOrEmpty(reason) ? "clear message" : reason); } private static void OnStance(NetBus.NetMessage msg) { ApplyStance(msg.OwnerUid, NetProtocol.ParseStancePassive(msg.Payload)); } private static void OnSwing(NetBus.NetMessage msg) { int type = default(int); if (!NetProtocol.TryParseSwingType(msg.Payload, ref type)) { NetBus.CountDrop("ck.effigy.swing", "unparseable"); } else { ApplySwing(msg.OwnerUid, type); } } internal static void MasterOnProxyAnnounce(string ownerUid, string species, int tier, string displayName = null) { if (!PhotonNetwork.isNonMasterClientInRoom && _store != null) { EffigyRow val = default(EffigyRow); if (!string.IsNullOrEmpty(ownerUid) && !_ledger.TryGet(ownerUid, ref val)) { _store.Invalidate(ownerUid); } _store.Announce(ownerUid, NetProtocol.BuildEffigySet(species, tier, displayName), ""); } } internal static void MasterOnProxyTeardown(string ownerUid, string reason) { if (!PhotonNetwork.isNonMasterClientInRoom && _store != null) { _store.Release(ownerUid, NetProtocol.BuildEffigyClear(reason)); } } internal static void MasterOnStance(string ownerUid, bool passive) { if (!PhotonNetwork.isNonMasterClientInRoom) { ApplyStance(ownerUid, passive); NetBus.SendToOthers("ck.effigy.stance", ownerUid, NetProtocol.BuildStance(passive)); } } internal static void MasterOnSwing(string ownerUid, int type) { if (!PhotonNetwork.isNonMasterClientInRoom) { ApplySwing(ownerUid, type); NetBus.SendToOthers("ck.effigy.swing", ownerUid, NetProtocol.BuildSwing(type)); } } private static void ApplySwing(string ownerUid, int type) { if (string.IsNullOrEmpty(ownerUid)) { NetBus.CountDrop("ck.effigy.swing", "empty-identity"); } else { if (IsLocalOwner(ownerUid)) { return; } if (!_bind.TryGetValue(ownerUid, out var value) || (Object)(object)value.Body == (Object)null) { NetBus.CountDrop("ck.effigy.swing", "no-row"); return; } EffigySwingMirror component = ((Component)value.Body).GetComponent(); if ((Object)(object)component == (Object)null) { NetBus.CountDrop("ck.effigy.swing", "no-mirror"); } else { component.Mirror(type); } } } public static void SyncOwnedPet(string ownerUid, string species, int tier, bool hasBody, bool stancePassive = false, string displayName = null) { //IL_0166: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.inRoom || PhotonNetwork.isNonMasterClientInRoom) { _ownedUid = null; _ownedSig = null; StateMirror ownedStance = _ownedStance; if (ownedStance != null) { ownedStance.Reset(); } } else { if (_store == null || _ownedStance == null) { return; } _reportUid = ownerUid ?? ""; _reportHasBody = hasBody; _reportStancePassive = stancePassive; if (hasBody && !string.IsNullOrEmpty(ownerUid) && !string.IsNullOrEmpty(species)) { if (_ownedUid != null && !string.Equals(_ownedUid, ownerUid, StringComparison.Ordinal)) { ReleaseOwned("owner uid changed"); } string text = NetProtocol.BuildEffigySet(species, tier, displayName); if (_store.Announce(ownerUid, text, "")) { _ownedUid = ownerUid; string text2 = ownerUid + "\u001f" + text; if (!string.Equals(text2, _ownedSig, StringComparison.Ordinal)) { _ownedSig = text2; Log.LogMessage((object)($"[EFFIGY] owned pet set → Others (owner '{ownerUid}', species '{species}', tier {tier}, " + "stance=" + (stancePassive ? "passive" : "engaged") + (string.IsNullOrEmpty(displayName) ? "" : (", name '" + displayName + "'")) + ").")); } } } else if (_ownedUid != null) { ReleaseOwned("owner pet bodiless or gone"); } _ownedStance.Tick(0); } } private static void ReleaseOwned(string reason) { string ownedUid = _ownedUid; _ownedUid = null; _ownedSig = null; _store.Release(ownedUid, NetProtocol.BuildEffigyClear(reason)); Log.LogMessage((object)("[EFFIGY] owned pet clear → Others (owner '" + ownedUid + "').")); StateMirror ownedStance = _ownedStance; if (ownedStance != null) { ownedStance.Reset(); } } private static void FlushTo(int actor) { if (!PhotonNetwork.inRoom || PhotonNetwork.isNonMasterClientInRoom || _store == null) { return; } int num = _store.FlushTo(actor, (Action)null); foreach (EffigyRow item in _ledger.RowsSnapshot()) { if (item.StancePassive) { NetBus.SendToActor(actor, "ck.effigy.stance", item.OwnerUid, NetProtocol.BuildStance(true)); } } StateMirror ownedStance = _ownedStance; if (NetProtocol.ParseStancePassive((ownedStance != null) ? ownedStance.LastPayload : null)) { NetBus.SendToActor(actor, "ck.effigy.stance", _ownedStance.LastExtra ?? "", NetProtocol.BuildStance(true)); } if (num > 0) { Log.LogMessage((object)$"[EFFIGY] flushed {num} effigy-set state(s) to actor {actor} (peer-ready — set is idempotent)."); } } private static void ApplySet(string ownerUid, string species, int tier, string extension, bool selfApply) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Invalid comparison between Unknown and I4 if (string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(species)) { NetBus.CountDrop("ck.effigy.set", "empty-identity"); return; } if (IsLocalOwner(ownerUid)) { EffigyRow val = default(EffigyRow); if (_ledger.TryGet(ownerUid, ref val)) { RemoveRow(ownerUid, "owner suppression — this machine owns the pet"); } else if (!selfApply && _suppressWarned.Add(ownerUid)) { Log.LogWarning((object)("[EFFIGY] set for '" + ownerUid + "' suppressed — this machine owns that pet.")); } return; } EffigySetResult val2 = _ledger.Set(ownerUid, species, tier, extension); if ((int)val2 != 1) { if ((int)val2 == 3) { Log.LogMessage((object)$"[EFFIGY] set: owner '{ownerUid}' species changed to '{species}' (tier {tier}) — rebuilding the body."); if (_bind.TryGetValue(ownerUid, out var value)) { DestroyBody(ownerUid, value, "species changed"); } _poke = true; } } else { Log.LogMessage((object)($"[EFFIGY] set: owner '{ownerUid}' species '{species}' tier {tier}" + (string.IsNullOrEmpty(extension) ? "" : (" name '" + extension + "'")) + " — body follows once the anchor resolves.")); _poke = true; } } private static void ApplyStance(string ownerUid, bool passive) { if (!string.IsNullOrEmpty(ownerUid) && !IsLocalOwner(ownerUid) && _ledger.SetStance(ownerUid, passive)) { Log.LogMessage((object)("[EFFIGY] owner '" + ownerUid + "' stance=" + (passive ? "passive" : "engaged") + " — body follows " + (passive ? "the owner's replica" : "the anchor") + ".")); } } private static void RemoveRow(string ownerUid, string reason) { EffigyRow val = default(EffigyRow); if (!string.IsNullOrEmpty(ownerUid) && _ledger.TryGet(ownerUid, ref val)) { if (_bind.TryGetValue(ownerUid, out var value)) { DestroyBody(ownerUid, value, reason); _bind.Remove(ownerUid); } _ledger.Clear(ownerUid); Log.LogMessage((object)("[EFFIGY] clear: owner '" + ownerUid + "' (" + reason + ").")); } } private static Character OwnerReplica(string ownerUid) { try { Character val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(ownerUid) : null); return ((Object)(object)val != (Object)null && val.Alive) ? val : null; } catch { return null; } } internal static bool IsLocalOwner(string ownerUid) { try { Character val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(ownerUid) : null); return (Object)(object)val != (Object)null && (Object)(object)val.OwnerPlayerSys != (Object)null && val.OwnerPlayerSys.IsLocalPlayer; } catch { return false; } } internal static void PokeReconcile() { _poke = true; } private static void OnRoomChanged(RoomChange change) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if (_ledger.Count > 0) { TeardownAll("room changed ('" + (change.OldRoomName ?? "none") + "' → '" + (change.NewRoomName ?? "none") + "')"); } _ownedUid = null; _ownedSig = null; StateMirror ownedStance = _ownedStance; if (ownedStance != null) { ownedStance.Reset(); } } private static string CurrentRoomName() { if (!PhotonNetwork.inRoom || PhotonNetwork.room == null) { return null; } return PhotonNetwork.room.Name; } internal static void Tick(MonoBehaviour host) { AnchorAnimSpy.Tick(); bool flag = CkConfig.Effigy.PinToAnchor?.Value ?? false; if (!_pinModeKnown) { _pinMode = flag; _pinModeKnown = true; } else if (flag != _pinMode) { _pinMode = flag; int num = 0; foreach (KeyValuePair item in _bind) { if ((Object)(object)item.Value.Body != (Object)null) { DestroyBody(item.Key, item.Value, "PinToAnchor flip", quiet: true); num++; } } Log.LogMessage((object)(string.Format("[EFFIGY] PinToAnchor flipped → {0} — tore down {1} ", flag ? "PIN" : "agent-follow", num) + "effigy body(ies); the reconcile rebuilds them in the new mode.")); _poke = true; } if (_ledger.Count == 0 || (!_poke && Time.unscaledTime < _nextTickAt)) { return; } _poke = false; _nextTickAt = Time.unscaledTime + 2f; bool enabled = Enabled; int maxBodies = MaxBodies; foreach (EffigyRow item2 in _ledger.RowsSnapshot()) { if (IsLocalOwner(item2.OwnerUid)) { RemoveRow(item2.OwnerUid, "owner suppression — this machine owns the pet (reconcile re-check)"); continue; } Binding b = BindingOf(item2.OwnerUid); try { ReconcileRow(item2, b, host, enabled, maxBodies); } catch (Exception arg) { if (Time.unscaledTime - _rowFailLogAt > 30f) { _rowFailLogAt = Time.unscaledTime; Log.LogWarning((object)("[EFFIGY] reconcile for owner '" + item2.OwnerUid + "' threw (throttled; " + $"other rows unaffected): {arg}")); } } } } private static void ReconcileRow(EffigyRow row, Binding b, MonoBehaviour host, bool enabled, int maxBodies) { //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0356: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0359: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Expected I4, but got Unknown //IL_02ea: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)b.Anchor == (Object)null || !b.Anchor.Alive) { b.Anchor = FindAnchorFor(row.OwnerUid); } bool flag = (Object)(object)b.Anchor != (Object)null && b.Anchor.Alive; _ledger.MarkAnchor(row.OwnerUid, flag, Time.time); if (!flag && b.AnchorWasLive) { b.AnchorLostAt = Time.unscaledTime; b.AnchorDiagAt = Time.unscaledTime; Log.LogMessage((object)("[EFFIGY] owner '" + row.OwnerUid + "': anchor UNRESOLVED (was live) — body holds " + $"through the {8f:F0}s grace, then despawns; " + "the sentinel sweep keeps scanning.")); } else if (flag && !b.AnchorWasLive && b.AnchorEverResolved) { Log.LogMessage((object)("[EFFIGY] owner '" + row.OwnerUid + "': anchor re-resolved (viewID=" + CompanionAnchor.ViewIdOf(b.Anchor) + ") after " + $"{Time.unscaledTime - b.AnchorLostAt:F0}s unresolved.")); } if (flag) { b.AnchorEverResolved = true; } b.AnchorWasLive = flag; if (!flag) { Character val = FindDeadSentinelFor(row.OwnerUid); if ((Object)(object)val != (Object)null) { if (b.AnchorDeadSince < 0f) { b.AnchorDeadSince = Time.unscaledTime; } if (!b.DeadReplicaNoted) { b.DeadReplicaNoted = true; Log.LogWarning((object)("[EFFIGY] owner '" + row.OwnerUid + "': anchor replica PRESENT but DEAD (viewID=" + CompanionAnchor.ViewIdOf(val) + ") — this row cannot rebind until the master respawns/re-instantiates it; 'effigyrebind' will NOT help (FindAnchorFor re-rejects a dead replica).")); } } else { b.AnchorDeadSince = -1f; b.DeadReplicaNoted = false; } } else { b.AnchorDeadSince = -1f; b.DeadReplicaNoted = false; } if (!flag && Time.unscaledTime - b.AnchorDiagAt > 60f) { b.AnchorDiagAt = Time.unscaledTime; string arg = ((b.AnchorDeadSince >= 0f) ? $"deadFor={Time.unscaledTime - b.AnchorDeadSince:F0}s " : ""); Log.LogWarning((object)("[EFFIGY] owner '" + row.OwnerUid + "': anchor still unresolved after " + $"{Time.unscaledTime - b.AnchorLostAt:F0}s — {arg}{AnchorScanDiag(row.OwnerUid)} " + $"ghostBuildInFlight={b.GhostBuildInFlight}. ('effigyrebind {row.OwnerUid}' " + "resets this row's binding by hand.)")); } if ((Object)(object)b.Body == (Object)null && !b.GhostBuildInFlight && (int)row.Body != 0) { _ledger.MarkBody(row.OwnerUid, (EffigyBodyRung)0); } if (b.GhostBuildInFlight && Time.unscaledTime - b.GhostBuildSince > 30f) { b.GhostBuildInFlight = false; Log.LogWarning((object)("[EFFIGY] owner '" + row.OwnerUid + "': the ghost-build-in-flight flag has been set for over 30s — the build coroutine likely died without disposal. Cleared, so the acquisition ladder isn't gated for the rest of the session.")); } EffigyStep val2 = _ledger.Reconcile(row, enabled, maxBodies, Time.time); switch (val2 - 1) { case 2: DestroyBody(row.OwnerUid, b, (!enabled) ? "kill-switch off" : "anchor lost (downed window / despawn)"); break; case 0: if (!b.GhostBuildInFlight) { TryAcquire(row, b, host); } break; case 1: TryUpgrade(row, b, host); break; } } private static string AnchorScanDiag(string ownerUid) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) try { CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return "CharacterManager=null."; } int num = 0; StringBuilder stringBuilder = new StringBuilder("sentinel census: "); foreach (Character value in instance.Characters.Values) { if (!((Object)(object)value == (Object)null) && AnchorSentinel.TryParseOwner(UID.op_Implicit(value.UID), out var ownerUid2)) { num++; stringBuilder.Append($"[viewID={CompanionAnchor.ViewIdOf(value)} alive={value.Alive} " + $"ownerMatch={string.Equals(ownerUid2, ownerUid, StringComparison.Ordinal)}] "); } } if (num == 0) { stringBuilder.Append("NO sentinel-uid characters in the registry."); } return stringBuilder.ToString(); } catch (Exception ex) { return "scan diag threw: " + ex.Message + "."; } } private static IEnumerable ClaimedBodies() { foreach (KeyValuePair item in _bind) { if ((Object)(object)item.Value.Body != (Object)null) { yield return item.Value.Body; } } } private static Binding BindingOf(string ownerUid) { if (!_bind.TryGetValue(ownerUid, out var value)) { value = (_bind[ownerUid] = new Binding()); value.AnchorLostAt = Time.unscaledTime; value.AnchorDiagAt = Time.unscaledTime; } return value; } internal static bool TryGetBody(string ownerUid, out CompanionBody body) { body = null; if (string.IsNullOrEmpty(ownerUid) || !_bind.TryGetValue(ownerUid, out var value) || (Object)(object)value.Body == (Object)null) { return false; } body = value.Body; return true; } internal static List> BoundBodiesSnapshot() { List> list = new List>(); foreach (KeyValuePair item in _bind) { if ((Object)(object)item.Value.Body != (Object)null) { list.Add(new KeyValuePair(item.Key, item.Value.Body)); } } return list; } internal static bool TryGetBodyForAnchor(Character anchor, out CompanionBody body) { body = null; if ((Object)(object)anchor == (Object)null) { return false; } foreach (KeyValuePair item in _bind) { if (item.Value.Anchor == anchor && !((Object)(object)item.Value.Body == (Object)null)) { body = item.Value.Body; return true; } } return false; } internal static string RebindOwner(string arg) { if (string.IsNullOrEmpty(arg)) { return "[EFFIGY] usage: effigyrebind (owner uids: 'effigydump')."; } int num = 0; foreach (EffigyRow item in _ledger.RowsSnapshot()) { if (string.Equals(arg, "all", StringComparison.OrdinalIgnoreCase) || string.Equals(item.OwnerUid, arg, StringComparison.Ordinal)) { if (_bind.TryGetValue(item.OwnerUid, out var value)) { DestroyBody(item.OwnerUid, value, "effigyrebind (dev)", quiet: true); _bind.Remove(item.OwnerUid); } _ledger.MarkBody(item.OwnerUid, (EffigyBodyRung)0); num++; } } PokeReconcile(); if (num <= 0) { return "[EFFIGY] rebind: no row matches '" + arg + "' ('effigydump' lists owners)."; } return $"[EFFIGY] rebind: reset {num} row binding(s) — the next reconcile re-resolves the anchor and re-acquires a body."; } private static bool AnchorReadsAsStaging(string ownerUid, Binding b, Character anchor) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) if (CompanionRuntime.IsSanePosition(((Component)anchor).transform.position)) { return false; } if (Time.unscaledTime - b.StagingHoldLogAt > 10f) { b.StagingHoldLogAt = Time.unscaledTime; ModLog log = Log; string[] obj = new string[5] { "[EFFIGY] owner '", ownerUid, "': acquisition holding — anchor reads as void/staging (", null, null }; Vector3 position = ((Component)anchor).transform.position; obj[3] = ((Vector3)(ref position)).ToString("F1"); obj[4] = "); retrying on the reconcile cadence."; log.LogMessage((object)string.Concat(obj)); } return true; } private static Character FindAnchorFor(string ownerUid) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return null; } try { foreach (Character value in instance.Characters.Values) { if (!((Object)(object)value == (Object)null) && value.Alive && AnchorSentinel.TryParseOwner(UID.op_Implicit(value.UID), out var ownerUid2) && string.Equals(ownerUid2, ownerUid, StringComparison.Ordinal)) { return value; } } } catch (Exception ex) { if (Time.unscaledTime - _scanFailLogAt > 30f) { _scanFailLogAt = Time.unscaledTime; Log.LogWarning((object)("[EFFIGY] anchor sentinel scan threw (throttled): " + ex.Message)); } } return null; } private static Character FindDeadSentinelFor(string ownerUid) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) CharacterManager instance = CharacterManager.Instance; if ((Object)(object)instance == (Object)null) { return null; } try { foreach (Character value in instance.Characters.Values) { if (!((Object)(object)value == (Object)null) && !value.Alive && AnchorSentinel.TryParseOwner(UID.op_Implicit(value.UID), out var ownerUid2) && string.Equals(ownerUid2, ownerUid, StringComparison.Ordinal)) { return value; } } } catch { } return null; } private static void TryAcquire(EffigyRow row, Binding b, MonoBehaviour host) { Character anchor = b.Anchor; if ((Object)(object)anchor == (Object)null || AnchorReadsAsStaging(row.OwnerUid, b, anchor)) { return; } Character src = null; try { src = BodyFactory.FindNearest(anchor, 40f, row.Species); } catch (Exception ex) { Log.LogWarning((object)("[EFFIGY] wild search threw: " + ex.Message)); } if ((Object)(object)src != (Object)null) { CompanionBody companionBody = BuildSafe(() => BodyFactory.BuildPuppet(src, anchor, consume: false), "wild"); if ((Object)(object)companionBody != (Object)null) { AdoptBody(row, b, companionBody, ghost: false, "nearby wild"); return; } } if (BodyTemplateCache.TryResolve(row.Species, out var cached)) { CompanionBody companionBody2 = BuildSafe(() => BodyTemplateCache.PuppetFrom(cached, anchor), "cache"); if ((Object)(object)companionBody2 != (Object)null) { AdoptBody(row, b, companionBody2, ghost: false, "template cache"); return; } } MaybeStartHarvest(row, host); b.GhostBuildInFlight = true; b.GhostBuildSince = Time.unscaledTime; host.StartCoroutine(GhostBuild(row.OwnerUid, b)); } private static void TryUpgrade(EffigyRow row, Binding b, MonoBehaviour host) { Character anchor = b.Anchor; if ((Object)(object)anchor == (Object)null || (Object)(object)b.Body == (Object)null || AnchorReadsAsStaging(row.OwnerUid, b, anchor)) { return; } CompanionBody companionBody = null; string text = null; Character src = null; try { src = BodyFactory.FindNearest(anchor, 40f, row.Species); } catch (Exception ex) { Log.LogWarning((object)("[EFFIGY] wild search threw: " + ex.Message)); } if ((Object)(object)src != (Object)null) { companionBody = BuildSafe(() => BodyFactory.BuildPuppet(src, anchor, consume: false), "wild"); text = "nearby wild"; } if ((Object)(object)companionBody == (Object)null && BodyTemplateCache.TryResolve(row.Species, out var cached)) { companionBody = BuildSafe(() => BodyTemplateCache.PuppetFrom(cached, anchor), "cache"); text = "template cache"; } if ((Object)(object)companionBody == (Object)null) { MaybeStartHarvest(row, host); return; } Log.LogMessage((object)("[EFFIGY] owner '" + row.OwnerUid + "': ghost stand-in upgraded to a real '" + row.Species + "' body (" + text + ").")); DestroyBody(row.OwnerUid, b, "upgraded", quiet: true); AdoptBody(row, b, companionBody, ghost: false, text, quiet: true); } internal static void ResetHarvestGate() { _harvestGate.Reset(); _harvestNoted.Clear(); } private static void MaybeStartHarvest(EffigyRow row, MonoBehaviour host) { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017c: Unknown result type (might be due to invalid IL or missing references) //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Expected I4, but got Unknown if (_harvestInFlight && Time.realtimeSinceStartup - _harvestInFlightSince > 300f) { Log.LogWarning((object)("[EFFIGY] the harvest-in-flight flag ('" + _harvestSpecies + "') has been set for over " + $"{5f:F0} min — the chain likely aborted without disposal. Clearing it " + "so the harvest rung isn't dead for the session (the backoff clock still paces retries).")); _harvestInFlight = false; _harvestSpecies = ""; } List scenes = null; string term = null; bool flag = false; bool hasExpeditionDonor = false; try { List list = default(List); if (SpeciesTable.TryResolveKey>(DonorHarvest.DonorScenes, row.Species, ref term, ref list, (string)null) && list != null && list.Count > 0) { List list3 = default(List); List list2 = DonorTable.FilterViable((IEnumerable)list, ref list3); flag = list2.Count > 0; hasExpeditionDonor = list3.Count > 0; if (flag) { Scene activeScene = SceneManager.GetActiveScene(); scenes = DonorTable.OrderCandidates((IEnumerable)list2, ((Scene)(ref activeScene)).name); } } } catch (Exception ex) { Log.LogWarning((object)("[EFFIGY] donor-table lookup threw: " + ex.Message)); return; } _harvestGate.RetrySeconds = HarvestRetrySeconds; EffigyHarvestFacts val = new EffigyHarvestFacts { Enabled = (CkConfig.Effigy.EnableEffigyHarvest?.Value ?? true), ExpeditionBusy = ExpeditionHarvest.InProgress, HarvestBusy = (_harvestInFlight || DonorPhotonGuard.WindowActive), HasAdditiveDonor = flag, HasExpeditionDonor = hasExpeditionDonor }; EffigyHarvestStep val2 = _harvestGate.Decide(row.Species, ref val, Time.time); switch ((int)val2) { case 0: host.StartCoroutine(HarvestFor(row.Species, term, scenes)); break; case 3: NoteOnce(row.Species, "expedition-only", "[EFFIGY] '" + row.Species + "' has only region/oversized donors — an expedition round trip never fires for a cosmetic body (v1 scope), so the ghost stand-in stays. Host 'expedition " + row.Species + "' warms the cache if it matters."); break; case 2: NoteOnce(row.Species, "no-donor", "[EFFIGY] '" + row.Species + "' has no donor-table entry — the ghost stand-in stays until a wild/cache source appears."); break; case 1: break; } } private static void NoteOnce(string species, string reason, string line) { if (_harvestNoted.Add(species + "|" + reason)) { Log.LogMessage((object)line); } } private static IEnumerator HarvestFor(string species, string term, List scenes) { _harvestInFlight = true; _harvestInFlightSince = Time.realtimeSinceStartup; _harvestSpecies = species; bool guest = PhotonNetwork.isNonMasterClientInRoom; Log.LogMessage((object)("[EFFIGY] no local source for '" + species + "' — harvesting a body template (" + (guest ? "guest-local" : "master") + ", " + $"{scenes.Count} donor candidate(s), region-aware order).")); object result = null; try { yield return DonorHarvest.HarvestChain(scenes, term, (Character src) => BodyTemplateCache.Capture(src, species), delegate(object r) { result = r; }); } finally { _harvestInFlight = false; _harvestSpecies = ""; } bool flag = result is BodyTemplate bodyTemplate && (Object)(object)bodyTemplate.Dormant != (Object)null; _harvestGate.ReportResult(species, flag, Time.time); if (flag) { Log.LogMessage((object)("[EFFIGY] harvested '" + species + "' into the body-template cache (" + (guest ? "guest-local" : "master") + ", " + $"session donor cycles={DonorHarvest.CyclesThisSession}) — the trip is paid once; " + "the ghost upgrades from the cache on the next reconcile (≤10s).")); PokeReconcile(); } else { Log.LogWarning((object)("[EFFIGY] donor harvest for '" + species + "' came up dry — the ghost stand-in stays; " + $"next attempt in {_harvestGate.RetrySeconds / 60f:F0} min ('effigydump' shows the clock).")); } } private static CompanionBody BuildSafe(Func build, string what) { try { return build(); } catch (Exception ex) { Log.LogWarning((object)("[EFFIGY] " + what + " body build threw: " + ex.Message)); return null; } } private static IEnumerator GhostBuild(string ownerUid, Binding b) { try { Character anchor = b.Anchor; Character ghost = (((Object)(object)anchor != (Object)null) ? BodyFactory.SpawnGhostActive(anchor) : null); if ((Object)(object)ghost == (Object)null) { yield break; } float t0 = Time.time; while ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost) && Time.time - t0 < 4f) { BodyFactory.NudgeGhostActive(ghost); yield return null; } if ((Object)(object)ghost != (Object)null && !BodyFactory.GhostVisualReady(ghost)) { BodyFactory.ForceGhostVisuals(ghost); } CompanionBody companionBody = (((Object)(object)ghost != (Object)null) ? BodyFactory.FinishGhostPuppet(ghost, anchor) : null); b.GhostBuildInFlight = false; EffigyRow row = default(EffigyRow); bool flag = _ledger.TryGet(ownerUid, ref row); Binding value; bool flag2 = _bind.TryGetValue(ownerUid, out value) && value == b; if (!flag || !flag2 || (Object)(object)b.Body != (Object)null || (Object)(object)b.Anchor == (Object)null || !b.Anchor.Alive) { if ((Object)(object)companionBody != (Object)null) { int bodyId = companionBody.BodyId; Object.Destroy((Object)(object)((Component)companionBody).gameObject); Log.LogMessage((object)$"[EFFIGY] owner '{ownerUid}': ghost build landed after the row/anchor moved on — destroyed in-flight body. (body#{bodyId})"); } } else if ((Object)(object)companionBody != (Object)null) { AdoptBody(row, b, companionBody, ghost: true, "ghost stand-in"); } } finally { b.GhostBuildInFlight = false; } } private static void AdoptBody(EffigyRow row, Binding b, CompanionBody body, bool ghost, string rung, bool quiet = false) { string ownerUid = row.OwnerUid; ((Object)((Component)body).gameObject).name = "CK_Effigy_" + row.Species; body.SpeciesId = row.Species; body.Origin = "effigy:" + rung; ICompanionSettings companionSettings = (body.Settings = SettingsFor(row.Species)); if (body.CapturedStats != null && body.CapturedStats.MoveSpeed > 0f) { body.Speed = body.CapturedStats.MoveSpeed; } if (_pinMode) { float num = (float.IsNaN(body.YawOffset) ? companionSettings.ModelYawOffset : body.YawOffset); EffigySwingMirror effigySwingMirror = ((Component)body).gameObject.AddComponent(); effigySwingMirror.Setup(companionSettings, "PIN"); EffigyPin effigyPin = ((Component)body).gameObject.AddComponent(); effigyPin.Setup(body, delegate { Binding value; Character val = (_bind.TryGetValue(ownerUid, out value) ? value : null)?.Anchor; return (!((Object)(object)val != (Object)null) || !val.Alive) ? null : val; }, num, companionSettings); b.Body = body; b.LastRung = rung + " (pinned)"; _ledger.MarkBody(ownerUid, (EffigyBodyRung)((!ghost) ? 1 : 2)); if (!quiet) { Log.LogMessage((object)("[EFFIGY] owner '" + ownerUid + "': body up PINNED (" + rung + ", species '" + row.Species + "', " + $"anchor viewID={CompanionAnchor.ViewIdOf(b.Anchor)}, yaw={num:F0}, body#{body.BodyId}).")); } return; } b.Body = body; b.LastRung = rung; EffigySwingMirror effigySwingMirror2 = ((Component)body).gameObject.AddComponent(); effigySwingMirror2.Setup(companionSettings, "SWING"); body.Owner = delegate { //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Invalid comparison between Unknown and I4 Binding value; Character val = (_bind.TryGetValue(ownerUid, out value) ? value : null)?.Anchor; EffigyRow val2 = default(EffigyRow); bool flag = _ledger.TryGet(ownerUid, ref val2) && val2.StancePassive; Character val3 = (flag ? OwnerReplica(ownerUid) : null); if ((int)EffigyFollow.Choose(flag, (Object)(object)val3 != (Object)null) == 1) { return val3; } return (!((Object)(object)val != (Object)null) || !val.Alive) ? null : val; }; _ledger.MarkBody(ownerUid, (EffigyBodyRung)((!ghost) ? 1 : 2)); if (!quiet) { Log.LogMessage((object)("[EFFIGY] owner '" + ownerUid + "': body up (" + rung + ", species '" + row.Species + "', anchor viewID=" + CompanionAnchor.ViewIdOf(b.Anchor) + ", " + $"yaw={(float.IsNaN(body.YawOffset) ? companionSettings.ModelYawOffset : body.YawOffset):F0}, " + $"speed={body.Speed:F1}, body#{body.BodyId}).")); } } private static void DestroyBody(string ownerUid, Binding b, string reason, bool quiet = false) { if ((Object)(object)b.Body != (Object)null) { int bodyId = b.Body.BodyId; CompanionPetFx.OnEffigyBodyDown(ownerUid, b.Body); Object.Destroy((Object)(object)((Component)b.Body).gameObject); if (!quiet) { Log.LogMessage((object)$"[EFFIGY] owner '{ownerUid}': body down ({reason}, body#{bodyId})."); } } b.Body = null; _ledger.MarkBody(ownerUid, (EffigyBodyRung)0); } private static void TeardownAll(string reason) { foreach (KeyValuePair item in _bind) { if ((Object)(object)item.Value.Body != (Object)null) { Object.Destroy((Object)(object)((Component)item.Value.Body).gameObject); } } _bind.Clear(); _suppressWarned.Clear(); int num = _ledger.ClearAll(); if (num > 0) { Log.LogMessage((object)$"[EFFIGY] cleared all {num} effigy row(s) ({reason})."); } } private static string Fmt(Vector3 v) { return "(" + v.x.ToString("F1", Inv) + "," + v.y.ToString("F1", Inv) + "," + v.z.ToString("F1", Inv) + ")"; } private static string PositionFragment(EffigyRow row, Binding b, bool pinned) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (b == null || (Object)(object)b.Body == (Object)null) { return "body=none"; } Vector3 position = ((Component)b.Body).transform.position; Character val = ((!pinned && row.StancePassive) ? OwnerReplica(row.OwnerUid) : null); bool flag = !pinned && (int)EffigyFollow.Choose(row.StancePassive, (Object)(object)val != (Object)null) == 1; Transform val2 = ((!flag) ? (((Object)(object)b.Anchor != (Object)null && b.Anchor.Alive) ? ((Component)b.Anchor).transform : null) : (((Object)(object)val != (Object)null) ? ((Component)val).transform : null)); string text = (pinned ? "anchor(pinned)" : (flag ? "owner" : "anchor")); string text2 = (((Object)(object)val2 != (Object)null) ? Fmt(val2.position) : "none"); string text3 = (((Object)(object)val2 != (Object)null) ? Vector3.Distance(position, val2.position).ToString("F1", Inv) : "n/a"); return "body=" + Fmt(position) + " follow=" + text + "=" + text2 + " dist=" + text3; } public static string Dump() { //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"[EFFIGY] enabled={Enabled} maxBodies={MaxBodies} rows={_ledger.Count} " + string.Format("liveBodies={0} room='{1}' ", _ledger.LiveBodies, CurrentRoomName() ?? "none") + $"master={PhotonNetwork.inRoom && !PhotonNetwork.isNonMasterClientInRoom} " + $"pinMode={CkConfig.Effigy.PinToAnchor?.Value ?? false} " + $"animSpy={CkConfig.Effigy.AnchorAnimSpy?.Value ?? false}"); stringBuilder.AppendLine($"[EFFIGY] harvest rung: enabled={CkConfig.Effigy.EnableEffigyHarvest?.Value ?? true} " + $"retry={HarvestRetrySeconds / 60f:F1}min " + "inFlight=" + (_harvestInFlight ? ("'" + _harvestSpecies + "'") : "no") + " " + $"guardWindow={DonorPhotonGuard.WindowActive} expedition={ExpeditionHarvest.InProgress} " + $"sessionDonorCycles={DonorHarvest.CyclesThisSession} (light-probe crash ceiling ~11-17)"); foreach (EffigyRow item in _ledger.RowsSnapshot()) { Binding value; Binding binding = (_bind.TryGetValue(item.OwnerUid, out value) ? value : null); string text = (((Object)(object)binding?.Anchor != (Object)null && binding.Anchor.Alive) ? ("resolved (viewID=" + CompanionAnchor.ViewIdOf(binding.Anchor) + ")") : "unresolved"); string text2 = (((int)item.Body != 0) ? string.Format("{0} ({1}, alive={2})", item.Body, binding?.LastRung ?? "?", (Object)(object)binding?.Body != (Object)null) : ((binding != null && binding.GhostBuildInFlight) ? "ghost build in flight" : "none")); float num = _harvestGate.SecondsUntilRetry(item.Species, Time.time); int num2 = _harvestGate.Failures(item.Species); string text3 = ((!_harvestInFlight || !string.Equals(item.Species, _harvestSpecies, StringComparison.OrdinalIgnoreCase)) ? ((num > 0f || num2 > 0) ? $" harvestRetryIn={num:F0}s fails={num2}" : "") : $" harvest=IN-FLIGHT ({Time.realtimeSinceStartup - _harvestInFlightSince:F0}s)"); stringBuilder.AppendLine($"[EFFIGY] owner '{item.OwnerUid}' species '{item.Species}' tier={item.Tier} " + "stance=" + (item.StancePassive ? "passive" : "engaged") + " anchor=" + text + " body=" + text2 + text3); EffigyPin effigyPin = (((Object)(object)binding?.Body != (Object)null) ? ((Component)binding.Body).GetComponent() : null); stringBuilder.AppendLine("[EFFIGY] " + PositionFragment(item, binding, (Object)(object)effigyPin != (Object)null)); string text4 = ProxyPets.DriveFragment(item.OwnerUid); if (text4 != null) { stringBuilder.AppendLine("[EFFIGY] " + text4); } if ((Object)(object)effigyPin != (Object)null) { stringBuilder.AppendLine("[EFFIGY] " + effigyPin.DumpFragment()); } else if ((Object)(object)binding?.Body != (Object)null) { EffigySwingMirror component = ((Component)binding.Body).GetComponent(); if ((Object)(object)component != (Object)null) { stringBuilder.AppendLine("[EFFIGY] swing: attackParams=" + component.AttackParamsFragment + " " + $"swings={component.Swings} rpcSwings={AnchorAttackMirror.RpcSwings}"); } } } if (PhotonNetwork.inRoom && !PhotonNetwork.isNonMasterClientInRoom) { RecordRow val = null; bool flag = _ownedUid != null && _store != null && _store.TryGet(_ownedUid, ref val); string arg = ""; if (flag) { string arg2 = default(string); int num3 = default(int); NetProtocol.ParseEffigySet(val.Payload, ref arg2, ref num3); string text5 = $" owner '{_ownedUid}' species '{arg2}' tier={num3} "; StateMirror ownedStance = _ownedStance; arg = text5 + "stance=" + (NetProtocol.ParseStancePassive((ownedStance != null) ? ownedStance.LastPayload : null) ? "passive" : "engaged"); } stringBuilder.AppendLine($"[EFFIGY] owned-pet latch: sent={flag}{arg}"); } return stringBuilder.ToString().TrimEnd(Array.Empty()); } } public sealed class EquipmentSlotSpec { public string SlotId = "armor"; public string Label = "Armor"; public Func Accepts = (Item _) => false; public Func Wear = () => WearPolicy.Default; } public sealed class CompanionEquipment { internal sealed class DurabilityStamp : MonoBehaviour { public float Ratio = 1f; private float _dieAt = -1f; private void Update() { if (_dieAt < 0f) { _dieAt = Time.unscaledTime + 10f; } Item component = ((Component)this).GetComponent(); if ((Object)(object)component == (Object)null || Time.unscaledTime > _dieAt) { if ((Object)(object)component != (Object)null) { Debug.LogWarning((object)("[CKEQUIP] DurabilityStamp gave up after 10s on '" + ((Object)component).name + "' — " + $"the re-minted item keeps FULL durability instead of ratio {Ratio:0.##} (free-repair class; report).")); } Object.Destroy((Object)(object)this); } else if (component.m_initialized) { if (component.MaxDurability > 0) { component.SetDurabilityRatio(Ratio); } Object.Destroy((Object)(object)this); } } } public const string Tag = "[CKEQUIP]"; private static readonly List> _live = new List>(); private readonly CompanionHost _host; private readonly string _ownerLabel; public EquipmentSlotSpec Slot { get; } public EquipState State { get; private set; } public string EquippedKey { get; private set; } = ""; public bool HasEquipped => State.ItemId != 0; private static bool Enabled { get { if (CkConfig.Equipment.EnableCompanionEquipment != null) { return CkConfig.Equipment.EnableCompanionEquipment.Value; } return true; } } public event Action Changed; public CompanionEquipment(CompanionHost host, EquipmentSlotSpec slot, string ownerLabel) { _host = host; Slot = slot ?? new EquipmentSlotSpec(); _ownerLabel = ownerLabel ?? "?"; lock (_live) { for (int num = _live.Count - 1; num >= 0; num--) { if (!_live[num].TryGetTarget(out var _)) { _live.RemoveAt(num); } } _live.Add(new WeakReference(this)); } } public bool TryEquip(string key, Item item, out string refusal) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) refusal = null; if (!Enabled) { refusal = "Companion equipment is disabled."; return false; } if ((Object)(object)item == (Object)null) { refusal = "That item is gone."; return false; } if (HasEquipped) { refusal = "A piece is already equipped — unequip it first."; return false; } if (Slot.Accepts != null && !Slot.Accepts(item)) { refusal = "That does not fit this companion."; return false; } float num = ((item.MaxDurability > 0) ? ((float)item.MaxDurability) : 100f); float num2 = ((!item.m_initialized || item.MaxDurability <= 0) ? num : Mathf.Clamp(item.CurrentDurability, 0f, num)); ConsumeResult val = Inventories.ConsumeOne(item, 1); if (!((ConsumeResult)(ref val)).Consumed) { refusal = "Could not take the item from the inventory."; CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogWarning((object)("[CKEQUIP] equip consume MISMATCH on '" + item.Name + "' (" + ((ConsumeResult)(ref val)).Describe() + ") — refused, nothing equipped.")); } } return false; } State = new EquipState { ItemId = item.ItemID, Durability = num2, MaxDurability = num }; EquippedKey = key ?? ""; CompanionHost host2 = _host; if (host2 != null) { ModLog log2 = host2.Log; if (log2 != null) { log2.LogMessage((object)string.Format("{0} equipped '{1}' (id {2}, {3:0.#}/{4:0.#}) on {5}.", "[CKEQUIP]", item.Name, State.ItemId, num2, num, _ownerLabel)); } } this.Changed?.Invoke(); return true; } public bool TryUnequip(Character receiver, out string refusal) { return TryUnequip(receiver, null, out refusal); } public bool TryUnequip(Character receiver, ItemContainer preferred, out string refusal) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) refusal = null; if (!HasEquipped) { refusal = "Nothing is equipped."; return false; } if (!Enabled) { refusal = "Companion equipment is disabled."; return false; } Item val = Mint(State, receiver, preferred); if ((Object)(object)val == (Object)null) { refusal = "No room to return the item — it stays equipped."; return false; } CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogMessage((object)("[CKEQUIP] unequipped '" + val.Name + "' from " + _ownerLabel + " " + $"({State.Durability:0.#}/{State.MaxDurability:0.#} restored onto the returned item).")); } } State = default(EquipState); EquippedKey = ""; this.Changed?.Invoke(); return true; } public Item ReturnOnBondEnd(Character receiver) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) if (!HasEquipped) { return null; } Item val = Mint(State, receiver); if ((Object)(object)val == (Object)null) { CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogWarning((object)(string.Format("{0} bond end — could NOT re-mint equipped item {1} ", "[CKEQUIP]", State.ItemId) + "(no receiver/inventory); the piece STAYS equipped and its state is kept, so a re-adopted bond still has it. Return deferred.")); } } return null; } CompanionHost host2 = _host; if (host2 != null) { ModLog log2 = host2.Log; if (log2 != null) { log2.LogMessage((object)("[CKEQUIP] bond end — equipped item returned to the owner " + $"('{val.Name}', {State.Durability:0.#}/{State.MaxDurability:0.#}).")); } } State = default(EquipState); EquippedKey = ""; this.Changed?.Invoke(); return val; } private Item Mint(EquipState s, Character receiver, ItemContainer preferred = null) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)receiver == (Object)null || (Object)(object)receiver.Inventory == (Object)null) { return null; } ItemContainer pouch = receiver.Inventory.Pouch; Bag equippedBag = receiver.Inventory.EquippedBag; ItemContainer val = (ItemContainer)(((Object)(object)preferred != (Object)null) ? preferred : (((Object)(object)pouch != (Object)null) ? ((object)pouch) : ((object)(((Object)(object)equippedBag != (Object)null) ? equippedBag.Container : null)))); if ((Object)(object)val == (Object)null) { return null; } Item val2 = (((Object)(object)ItemManager.Instance != (Object)null) ? ItemManager.Instance.GenerateItemNetwork(s.ItemId) : null); if ((Object)(object)val2 == (Object)null) { return null; } val2.ChangeParent(((Component)val).transform); if (val2.MaxDurability > 0) { if (val2.m_initialized) { val2.SetDurabilityRatio(((EquipState)(ref s)).Ratio); } else { DurabilityStamp durabilityStamp = ((Component)val2).gameObject.AddComponent(); durabilityStamp.Ratio = ((EquipState)(ref s)).Ratio; } } return val2; } public static int CountHeld(Character player, int itemId, IEnumerable extra = null) { int num = 0; if ((Object)(object)player != (Object)null) { foreach (Item item in Inventories.All(player)) { if ((Object)(object)item != (Object)null && item.ItemID == itemId) { num += Math.Max(1, item.RemainingAmount); } } } if (extra != null) { foreach (Item item2 in extra) { if ((Object)(object)item2 != (Object)null && item2.ItemID == itemId) { num += Math.Max(1, item2.RemainingAmount); } } } return num; } public bool TryRepair(Character player, int reagentId, int reagentCost, string reagentName, out string refusal, IEnumerable extraReagents = null) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Expected I4, but got Unknown //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) refusal = null; if (!Enabled) { refusal = "Companion equipment is disabled."; return false; } RepairVerdict val = EquipmentRepair.Evaluate(State, CountHeld(player, reagentId, extraReagents), reagentCost); switch (val - 1) { case 2: refusal = "Nothing is equipped to repair."; return false; case 0: refusal = "It is already in perfect repair."; return false; case 1: refusal = $"You need {reagentCost}x {reagentName} to repair it."; return false; default: { int num = ConsumeFrom(player, reagentId, reagentCost, extraReagents); if (num < reagentCost) { CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogWarning((object)(string.Format("{0} repair consumed {1}/{2} of reagent {3} ", "[CKEQUIP]", num, reagentCost, reagentId) + "(read-back mismatch — BUG-CHOWNOTCONSUMED class; repairing anyway).")); } } } State = EquipmentRepair.Repaired(State); CompanionHost host2 = _host; if (host2 != null) { ModLog log2 = host2.Log; if (log2 != null) { log2.LogMessage((object)(string.Format("{0} repaired {1}'s '{2}' to {3:0.#} ", "[CKEQUIP]", _ownerLabel, EquippedKey, State.MaxDurability) + $"({reagentCost}x {reagentName} consumed).")); } } this.Changed?.Invoke(); return true; } } } public static int ConsumeFrom(Character player, int itemId, int count, IEnumerable extra = null) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) int num = 0; List list = new List(); if ((Object)(object)player != (Object)null) { foreach (Item item in Inventories.All(player)) { if ((Object)(object)item != (Object)null && item.ItemID == itemId) { list.Add(item); } } } if (extra != null) { foreach (Item item2 in extra) { if ((Object)(object)item2 != (Object)null && item2.ItemID == itemId) { list.Add(item2); } } } foreach (Item item3 in list) { while (num < count && (Object)(object)item3 != (Object)null && !item3.DestroyWanted) { ConsumeResult val = Inventories.ConsumeOne(item3, 1); if (!((ConsumeResult)(ref val)).Consumed) { return num; } num++; if (item3.RemainingAmount <= 0) { break; } } if (num >= count) { break; } } return num; } public void OnDamaged(float preMitigationTotal) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || !HasEquipped) { return; } EquipState state = State; if (((EquipState)(ref state)).IsBroken) { return; } EquipState val = EquipmentWear.AfterHit(State, preMitigationTotal, Slot.Wear()); if (val.Durability == State.Durability) { return; } bool isBroken = ((EquipState)(ref val)).IsBroken; State = val; if (isBroken) { CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogMessage((object)("[CKEQUIP] " + _ownerLabel + "'s '" + EquippedKey + "' BROKE — it grants nothing until repaired.")); } } } this.Changed?.Invoke(); } public void OnDamagedRaw(float durabilityLoss) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || !HasEquipped) { return; } EquipState state = State; if (((EquipState)(ref state)).IsBroken || durabilityLoss <= 0f) { return; } EquipState state2 = State; float num = state2.Durability - durabilityLoss; state2.Durability = ((num < 0f || float.IsNaN(num)) ? 0f : num); bool isBroken = ((EquipState)(ref state2)).IsBroken; State = state2; if (isBroken) { CompanionHost host = _host; if (host != null) { ModLog log = host.Log; if (log != null) { log.LogMessage((object)("[CKEQUIP] " + _ownerLabel + "'s '" + EquippedKey + "' BROKE — it grants nothing until repaired.")); } } } this.Changed?.Invoke(); } public string PackState(Action warn = null) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) return EquipCodec.Pack(EquippedKey, State, warn); } public void RestoreState(string cell, Action warn = null) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) string equippedKey = default(string); EquipState state = default(EquipState); if (EquipCodec.TryParse(cell, ref equippedKey, ref state, warn)) { State = state; EquippedKey = equippedKey; } else { State = default(EquipState); EquippedKey = ""; } this.Changed?.Invoke(); } public string Describe() { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) if (!HasEquipped) { return Slot.Label + ": (empty)"; } string text = $"{Slot.Label}: '{EquippedKey}' id {State.ItemId} {State.Durability:0.#}/{State.MaxDurability:0.#}"; EquipState state = State; return text + (((EquipState)(ref state)).IsBroken ? " [BROKEN]" : ""); } public static string Dump() { List list = new List(); lock (_live) { for (int num = _live.Count - 1; num >= 0; num--) { if (!_live[num].TryGetTarget(out var target)) { _live.RemoveAt(num); } else { list.Add(" " + target._ownerLabel + " — " + target.Describe()); } } } bool enabled = Enabled; return string.Format("{0} EnableCompanionEquipment={1}; {2} live aggregate(s)", "[CKEQUIP]", enabled, list.Count) + ((list.Count > 0) ? (":\n" + string.Join("\n", list.ToArray())) : "."); } } public sealed class CompanionHost { private sealed class TaggedSettings : ICompanionSettings { private readonly ICompanionSettings _inner; private readonly CompanionHost _host; public float AttackDamage => _inner.AttackDamage; public float AttackInterval => _inner.AttackInterval; public float AggroRange => _inner.AggroRange; public float AttackRange => _inner.AttackRange; public float CombatLeashDistance => _inner.CombatLeashDistance; public bool AssistOnOwnerHit => _inner.AssistOnOwnerHit; public float OwnerFocusRange => _inner.OwnerFocusRange; public float LeashDistance => _inner.LeashDistance; public float CatchUpSpeed => _inner.CatchUpSpeed; public float DisengageRunHomeSeconds => _inner.DisengageRunHomeSeconds; public bool AttackVocals => _inner.AttackVocals; public float StationRingFraction => _inner.StationRingFraction; public float StationLineAngleDeg => _inner.StationLineAngleDeg; public float StationRestationMeters => _inner.StationRestationMeters; public float StationRestationSeconds => _inner.StationRestationSeconds; public float StationArriveMeters => _inner.StationArriveMeters; public int StationMaxRestations => _inner.StationMaxRestations; public float StationFarMeters => _inner.StationFarMeters; public float StationProgressMeters => _inner.StationProgressMeters; public float StationEnemyFastMetersPerSecond => _inner.StationEnemyFastMetersPerSecond; public bool AnchorInvisible => _inner.AnchorInvisible; public bool AnchorShowHealthBar => _inner.AnchorShowHealthBar; public bool AnchorLinkSummonSlot => _inner.AnchorLinkSummonSlot; public bool AnchorHideSummonIcon => _inner.AnchorHideSummonIcon; public float AnchorLeashDistance => _inner.AnchorLeashDistance; public float AnchorRespawnSeconds => _inner.AnchorRespawnSeconds; public bool AnchorDealsDamage => _inner.AnchorDealsDamage; public float CritHealthFraction => _inner.CritHealthFraction; public float CritRearmFraction => _inner.CritRearmFraction; public bool SpeciesVoice => _inner.SpeciesVoice; public AnchorGlueMode GlueMode => _inner.GlueMode; public float GlueOffsetBehind => _inner.GlueOffsetBehind; public bool UnifyTargets => _inner.UnifyTargets; public string GhostPrefabName => _inner.GhostPrefabName; public AnchorCollisionMode AnchorPlayerCollision => _inner.AnchorPlayerCollision; public bool AnchorEnabled => _inner.AnchorEnabled; public bool SuppressLeashWarp => _inner.SuppressLeashWarp; public BodilessAnchorPolicy BodilessAnchor => _inner.BodilessAnchor; public float ModelYawOffset => _inner.ModelYawOffset; public bool SlopeTiltEnabled => _inner.SlopeTiltEnabled; public float LoafDistanceMin => _inner.LoafDistanceMin; public float LoafDistanceMax => _inner.LoafDistanceMax; public float LoafRepickDistance => _inner.LoafRepickDistance; public string LogTagSuffix => _inner.LogTagSuffix ?? _host.TagSuffix; public TaggedSettings(ICompanionSettings inner, CompanionHost host) { _inner = inner; _host = host; } } private static readonly List s_registered = new List(); public string Guid { get; } public string Name { get; } public ModLog Log { get; } public ICompanionSettings Settings { get; } public string TagSuffix { get; } public string CombatTag { get; } public static CompanionHost KitHost { get; private set; } public static CompanionHost LocalDefault { get { lock (s_registered) { if (s_registered.Count > 0) { return s_registered[0]; } } return KitHost; } } private CompanionHost(string guid, string name, ModLog log, ICompanionSettings settings, string tagSuffix, string combatTag) { Guid = guid ?? ""; Name = name ?? Guid; Log = log; TagSuffix = tagSuffix; CombatTag = (string.IsNullOrEmpty(combatTag) ? "COMBAT" : combatTag); ICompanionSettings companionSettings2; if (!(settings is TaggedSettings)) { ICompanionSettings companionSettings = new TaggedSettings(settings, this); companionSettings2 = companionSettings; } else { companionSettings2 = settings; } Settings = companionSettings2; } public static CompanionHost Create(string guid, string name, ModLog log, ICompanionSettings settings, string tagSuffix = null, string combatTag = null) { CompanionHost companionHost = new CompanionHost(guid, name, log, settings ?? new CompanionSettingsDefaults(), tagSuffix ?? HostTag.Derive(guid ?? name), combatTag); lock (s_registered) { if (!s_registered.Contains(companionHost)) { s_registered.Add(companionHost); } } return companionHost; } public static CompanionHost Create(string guid, string name, ManualLogSource log, ICompanionSettings settings, string tagSuffix = null, string combatTag = null) { return Create(guid, name, ModLog.Ungated(log), settings, tagSuffix, combatTag); } internal static void SetKitHost(ModLog log) { KitHost = new CompanionHost("cobalt.companionkit", "CompanionKit", log, new CompanionSettingsDefaults(), "", null); } internal static bool AnyWantsSummonIconHidden() { lock (s_registered) { if (s_registered.Count == 0) { return KitHost?.Settings.AnchorHideSummonIcon ?? true; } for (int i = 0; i < s_registered.Count; i++) { if (s_registered[i].Settings.AnchorHideSummonIcon) { return true; } } return false; } } } public sealed class CompanionTicker { public const float DefaultIntervalSeconds = 2f; private readonly Func _interval; private float _last; public float Interval => _interval(); public bool Due => Time.time - _last >= Interval; public CompanionTicker(Func intervalSeconds = null) { _interval = intervalSeconds ?? ((Func)(() => 2f)); } public float Advance() { float result = Time.time - _last; _last = Time.time; return result; } public void Prime() { _last = Time.time; } public void Poke() { _last = 0f; } public bool Run(Companion companion, Character player, MonoBehaviour host, Action applyAttributes) { if (!Due) { return false; } Advance(); if (companion == null || (Object)(object)player == (Object)null) { return true; } companion.Tick(player, host); applyAttributes?.Invoke(); return true; } } public static class CompanionCandidate { public static bool IsBeast(Character c) { if ((Object)(object)c != (Object)null) { return c.UseLegacyVisual; } return false; } public static bool IsHumanoid(Character c) { if ((Object)(object)c != (Object)null) { return !c.UseLegacyVisual; } return false; } } public interface ICompanionNetMirror { bool SyncTarget(Character target); bool SyncStance(bool passive); void ReportHit(Character target); bool ReportSwing(int type); void InvalidateTarget(); void InvalidateStance(); void SyncPosition(Vector3 position, float yawDeg); void InvalidatePosition(); } public sealed class CompanionPersistenceSpec where TState : class { public CompanionHost Host; public MonoBehaviour Runner; public string Noun = "companion"; public object WaitKey; public CompanionSaveStore Store; public Func LocalPlayer; public Func HasLive; public Func LiveBody; public Func LiveOwnerUid; public Func LiveSpeciesId; public Action TeardownLive; public Action OnSceneReset; public Action OnSessionEnd; public Action OnPlayerReadyFirst; public Action OnBeforeLoad; public Func SavedIdentityError; public Action AdoptSaved; public Action OnInSessionArrival; public Action OnAfterLoad; public BodyAcquisition Acquisition; } public sealed class CompanionPersistence where TState : class { private readonly CompanionPersistenceSpec _spec; private ModLog Log => _spec.Host.Log; public CompanionPersistence(CompanionPersistenceSpec spec) { if (spec == null) { throw new ArgumentNullException("spec"); } if (spec.Host == null || (Object)(object)spec.Runner == (Object)null || spec.Store == null || spec.LocalPlayer == null || spec.HasLive == null || spec.TeardownLive == null) { throw new ArgumentException("CompanionPersistenceSpec: Host/Runner/Store/LocalPlayer/HasLive/TeardownLive are required."); } _spec = spec; } public void OnSceneLoaded(Scene scene, LoadSceneMode mode) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) try { OnSceneLoadedCore(scene, mode); } catch (Exception arg) { Log.LogError((object)($"[PERSIST] scene-loaded handler threw for '{((Scene)(ref scene)).name}' (mode={mode}) — this load's " + $"{_spec.Noun} re-form/teardown did not complete: {arg}")); } } private void OnSceneLoadedCore(Scene scene, LoadSceneMode mode) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Invalid comparison between Unknown and I4 SceneAction val = ReformFlow.Classify((int)mode == 1, ((Scene)(ref scene)).name); if ((int)val == 0) { return; } _spec.OnSceneReset?.Invoke(); if ((int)val == 1) { _spec.Runner.StartCoroutine(GameplayWait(((Scene)(ref scene)).name)); } else if ((int)val == 2) { _spec.OnSessionEnd?.Invoke(); if (_spec.HasLive()) { Log.LogMessage((object)("[PERSIST] main menu loaded — tearing down live " + _spec.Noun + " '" + _spec.LiveSpeciesId?.Invoke() + "' (owner " + ReformFlow.Uid4(_spec.LiveOwnerUid?.Invoke()) + "); save file untouched.")); _spec.TeardownLive(_spec.LocalPlayer()); } } } private IEnumerator GameplayWait(string scene) { return Lifecycle.WhenPlayerReady((Func)(() => _spec.LocalPlayer()), (Action)delegate(Character player) { PlayerReady(scene, player); }, (Action)delegate { Log.LogMessage((object)("[PERSIST] player not ready in '" + scene + "'.")); }, 30f, _spec.WaitKey ?? this); } private void PlayerReady(string scene, Character player) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) _spec.OnPlayerReadyFirst?.Invoke(scene, player); string text = UID.op_Implicit(player.UID); if (_spec.HasLive() && ReformFlow.OwnerMismatch(_spec.LiveOwnerUid?.Invoke(), text)) { Log.LogMessage((object)("[PERSIST] owner mismatch: tearing down " + _spec.Noun + " owned by " + ReformFlow.Uid4(_spec.LiveOwnerUid?.Invoke()) + " before loading " + ReformFlow.Uid4(text) + ".")); _spec.TeardownLive(player); } _spec.OnBeforeLoad?.Invoke(scene, player); if (!_spec.HasLive()) { TState val = _spec.Store.Load(player); if (val != null) { string text2 = _spec.SavedIdentityError?.Invoke(val, text); if (text2 != null) { Log.LogWarning((object)("[PERSIST] " + text2)); } else { _spec.AdoptSaved?.Invoke(val, player); } } } else { _spec.OnInSessionArrival?.Invoke(scene, player); } _spec.OnAfterLoad?.Invoke(scene, player); if (_spec.Acquisition != null && ReformFlow.ShouldKickMaintain(_spec.HasLive(), (Object)(object)_spec.LiveBody?.Invoke() != (Object)null, _spec.Acquisition.Active)) { _spec.Acquisition.Kick(); } } } public sealed class PetFxRequest { public string SpellKey; public int Slot; public bool Persist; public string SpeciesId; public string OwnerUid; public int CastItemId; } public static class CompanionPetFx { public const string SetVerb = "ck.petfx.set"; public const string ClearVerb = "ck.petfx.clear"; public const string CastVerb = "ck.petfx.cast"; private const float RefreshSeconds = 30f; private const float WatchdogSeconds = 90f; private const float FailRetrySeconds = 2f; private const float TickSeconds = 2f; private static readonly PetFxLedger _ledger = new PetFxLedger(); private static readonly PetFxReportBook _guestReports = new PetFxReportBook(); private static ReplicatedStore _store; private static FireAndForgetChannel _cast; private static readonly Dictionary _retryAt = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary _slotNames = new Dictionary(); private static float _nextTickAt; private static bool _resolverMissingWarned; private static bool _resolverThrewWarned; private static bool _bodySeamThrewWarned; private static bool _hostSeamThrewWarned; private static bool _flourishSeamThrewWarned; private static readonly HashSet _flourishTriggerWarned = new HashSet(StringComparer.Ordinal); public static Func Resolver; public static Func LocalPetBody; public static Func StatusHostOfOwner; public static Func FlourishTrigger; private static ModLog Log => CompanionRuntime.Log; private static bool Enabled => CkConfig.PetFx.EnablePetFx?.Value ?? true; private static double Now => Time.unscaledTime; public static bool PetFxEnabled => Enabled; internal static void Init() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Expected O, but got Unknown //IL_0050: Expected O, but got Unknown _store = NetBus.RegisterStore("petfx", new StoreOptions { Authority = (StoreAuthority)1, RefreshSeconds = 30f, FlushOnPeerReady = false, ClearOnRoomChange = true, Verbs = new StoreVerbs { Announce = "ck.petfx.set", Release = "ck.petfx.clear" } }); _store.OnSet += OnRecordSet; _store.OnCleared += OnRecordCleared; _cast = NetBus.RegisterFireAndForget("ck.petfx.cast", "ck.proxy.petfx.cast", ProxyPets.AuthorizePetOwner, ApplyCastWire, () => Enabled); Net.OnRoomChanged += OnRoomChanged; NetBus.SubscribePeerSceneReady(FlushTo); } private static void OnRecordSet(string key, string payload, RecordMeta meta) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (Enabled) { string text = default(string); int num = default(int); RecordKey.TryParse(key, ref text, ref num); PetFxRow val = default(PetFxRow); string spellKey = default(string); if (string.IsNullOrEmpty(text)) { NetBus.CountDrop("ck.petfx.set", "empty-identity"); } else if (meta.IsRefresh && _ledger.TryGet(text, num, ref val)) { _ledger.Set(text, num, val.Signature, val.MaxSeconds, Now); } else if (!NetProtocol.TryParsePetFxSet(payload, ref spellKey)) { NetBus.CountDrop("ck.petfx.set", "unparseable"); } else { Apply(text, num, spellKey, meta.IsRefresh); } } } private static void OnRecordCleared(string key, string reason, RecordMeta meta) { string ownerUid = default(string); int slot = default(int); RecordKey.TryParse(key, ref ownerUid, ref slot); DetachRow(ownerUid, slot, string.IsNullOrEmpty(reason) ? "clear message" : reason); } private static bool ApplyCastWire(string ownerUid, string payload, string verb, out string relayPayload) { relayPayload = null; string text = default(string); int num = default(int); if (!NetProtocol.TryParsePetFxCast(payload, ref text, ref num)) { NetBus.CountDrop(verb, "unparseable"); return false; } relayPayload = NetProtocol.BuildPetFxCast(text, num); ApplyCast(ownerUid, text, num, verb); return true; } private static CompanionBody TargetBody(string ownerUid, out string kind) { kind = "none"; if (string.IsNullOrEmpty(ownerUid)) { return null; } Func localPetBody = LocalPetBody; if (localPetBody != null) { CompanionBody companionBody = null; try { companionBody = localPetBody(ownerUid); } catch (Exception ex) { if (!_bodySeamThrewWarned) { _bodySeamThrewWarned = true; Log.LogWarning((object)("[PETFX] the LocalPetBody seam threw (warned once): " + ex.Message)); } } if ((Object)(object)companionBody != (Object)null) { kind = "local-real"; return companionBody; } } if (CompanionEffigy.TryGetBody(ownerUid, out var body)) { kind = "effigy"; return body; } return null; } private static FxRecipe ResolveRecipe(string spellKey, int slot, bool persist, string speciesId, string ownerUid, int castItemId = 0) { FxRecipe fxRecipe = CompanionAura.ResolveRecipe(new PetFxRequest { SpellKey = (spellKey ?? ""), Slot = slot, Persist = persist, SpeciesId = (speciesId ?? ""), OwnerUid = (ownerUid ?? ""), CastItemId = castItemId }); if (fxRecipe != null) { return fxRecipe; } if (CompanionAura.IsAuraKey(spellKey)) { return null; } Func resolver = Resolver; if (resolver == null) { if (!_resolverMissingWarned) { _resolverMissingWarned = true; Log.LogWarning((object)"[PETFX] no Resolver installed — every pet-FX message drops as no-recipe until the consumer sets CompanionPetFx.Resolver. (Warned once.)"); } return null; } try { return resolver(new PetFxRequest { SpellKey = (spellKey ?? ""), Slot = slot, Persist = persist, SpeciesId = (speciesId ?? ""), OwnerUid = (ownerUid ?? ""), CastItemId = castItemId }); } catch (Exception ex) { if (!_resolverThrewWarned) { _resolverThrewWarned = true; Log.LogWarning((object)("[PETFX] the Resolver seam threw (treated as no-recipe; warned once): " + ex)); } return null; } } private static Character StatusHostFor(string ownerUid) { Func statusHostOfOwner = StatusHostOfOwner; if (statusHostOfOwner == null) { return null; } try { return statusHostOfOwner(ownerUid); } catch (Exception ex) { if (!_hostSeamThrewWarned) { _hostSeamThrewWarned = true; Log.LogWarning((object)("[PETFX] the StatusHostOfOwner seam threw (treated as null; warned once): " + ex.Message)); } return null; } } private static void Apply(string ownerUid, int slot, string spellKey, bool quiet) { //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Invalid comparison between Unknown and I4 //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Invalid comparison between Unknown and I4 if (!Enabled) { return; } string key = RecordKey.Compose(ownerUid, slot); if (_retryAt.TryGetValue(key, out var value) && Now < value) { return; } string kind; CompanionBody companionBody = TargetBody(ownerUid, out kind); if ((Object)(object)companionBody == (Object)null) { if (!quiet) { NetBus.CountDrop("ck.petfx.set", "no-body"); } return; } FxRecipe fxRecipe = ResolveRecipe(spellKey, slot, persist: true, companionBody.SpeciesId, ownerUid); if (fxRecipe == null) { if (!quiet) { NetBus.CountDrop("ck.petfx.set", "no-recipe"); } return; } FxApplyDecision val = _ledger.Set(ownerUid, slot, fxRecipe.Signature, 90.0, Now); if ((int)val != 0) { if ((int)val == 2) { BodyFx.Detach(((Component)companionBody).transform, slot); } GameObject val2 = BodyFx.AttachPersistent(fxRecipe, ((Component)companionBody).transform, slot, StatusHostFor(ownerUid)); if ((Object)(object)val2 == (Object)null) { _ledger.Detach(ownerUid, slot); _retryAt[key] = Now + 2.0; NetBus.CountDrop("ck.petfx.set", "apply-failed"); return; } _retryAt.Remove(key); Log.LogMessage((object)("[PETFX] " + (((int)val == 2) ? "rebuilt" : "attached") + " " + $"'{spellKey}' on owner '{ownerUid}' slot {slot} ({kind} body#{companionBody.BodyId}).")); } } private static void DetachRow(string ownerUid, int slot, string reason) { if (!string.IsNullOrEmpty(ownerUid)) { _retryAt.Remove(RecordKey.Compose(ownerUid, slot)); bool flag = _ledger.Detach(ownerUid, slot); string kind; CompanionBody companionBody = TargetBody(ownerUid, out kind); bool flag2 = (Object)(object)companionBody != (Object)null && BodyFx.Detach(((Component)companionBody).transform, slot); if (flag || flag2) { Log.LogMessage((object)$"[PETFX] clear: owner '{ownerUid}' slot {slot} ({reason})."); } } } private static void ApplyCast(string ownerUid, string spellKey, int castItemId, string verb) { if (!Enabled) { return; } string kind; CompanionBody companionBody = TargetBody(ownerUid, out kind); if ((Object)(object)companionBody == (Object)null) { NetBus.CountDrop(verb, "no-body"); return; } PlayFlourishTrigger(companionBody, spellKey); FxRecipe fxRecipe = ResolveRecipe(spellKey, 0, persist: false, companionBody.SpeciesId, ownerUid, castItemId); if (fxRecipe == null) { NetBus.CountDrop(verb, "no-recipe"); return; } Character binder = CompanionRuntime.LocalPlayer(); string sourceName; GameObject val = BodyFx.PlayOneShot(fxRecipe, ((Component)companionBody).transform, binder, out sourceName); if ((Object)(object)val == (Object)null) { NetBus.CountDrop(verb, "play-failed"); return; } string lastPlayBindings = BodyFx.LastPlayBindings; Log.LogMessage((object)("[PETFX] " + BodyFxDescribe.Played(spellKey, verb, ((Object)companionBody).name, sourceName) + ((lastPlayBindings.Length > 0) ? (" | " + lastPlayBindings) : ""))); } private static void PlayFlourishTrigger(CompanionBody body, string spellKey) { //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Invalid comparison between Unknown and I4 Func flourishTrigger = FlourishTrigger; if (flourishTrigger == null) { return; } string text; try { text = flourishTrigger(body.SpeciesId ?? "", spellKey ?? ""); } catch (Exception ex) { if (!_flourishSeamThrewWarned) { _flourishSeamThrewWarned = true; Log.LogWarning((object)("[PETFX] the FlourishTrigger seam threw (treated as animate-nothing; warned once): " + ex.Message)); } return; } if (string.IsNullOrEmpty(text)) { return; } Animator val = (((Object)(object)body._anim != (Object)null) ? body._anim : ((Component)body).GetComponent()); if ((Object)(object)val == (Object)null) { return; } bool flag = false; AnimatorControllerParameter[] parameters = val.parameters; foreach (AnimatorControllerParameter val2 in parameters) { if ((int)val2.type == 9 && val2.name == text) { flag = true; break; } } if (!flag) { if (_flourishTriggerWarned.Add(body.SpeciesId + "|" + text)) { Log.LogWarning((object)("[PETFX] species '" + body.SpeciesId + "': flourish trigger '" + text + "' is not on this body's Animator — animating nothing (warned once per (species, trigger); `animdump` on a live body lists the real parameters).")); } } else { val.SetTrigger(text); } } public static void SyncOwnedFx(string ownerUid, string spellKey, int slot, bool active) { //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Invalid comparison between Unknown and I4 if (!Enabled || _store == null || string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(spellKey) || slot < 0) { return; } string text = RecordKey.Compose(ownerUid, slot); RecordRow val2 = default(RecordRow); if (PhotonNetwork.isNonMasterClientInRoom) { if (active) { Apply(ownerUid, slot, spellKey, quiet: true); } else { DetachRow(ownerUid, slot, "owner reports inactive"); } PetFxReportStep val = (active ? _guestReports.DecideActive(text, spellKey, Now) : _guestReports.DecideInactive(text)); if ((int)val == 1 && ProxyPets.ReportFx(ownerUid, spellKey, slot, active)) { if (active) { _guestReports.OnLandedActive(text, spellKey, Now); } else { _guestReports.OnLandedInactive(text); } } } else if (active) { _store.Announce(text, NetProtocol.BuildPetFxSet(spellKey), ""); } else if (_store.TryGet(text, ref val2)) { _store.Release(text, "fx ended"); } else { DetachRow(ownerUid, slot, "owner reports inactive"); } } public static void RearmAll(string reason) { _retryAt.Clear(); List list = _ledger.RowsSnapshot(); foreach (PetFxRow item in list) { _ledger.Detach(item.OwnerUid, item.Slot); string kind; CompanionBody companionBody = TargetBody(item.OwnerUid, out kind); if ((Object)(object)companionBody != (Object)null) { BodyFx.Detach(((Component)companionBody).transform, item.Slot); } } if (list.Count > 0) { Log.LogMessage((object)($"[PETFX] re-armed {list.Count} row(s) ({reason}) — the reconcile " + "re-resolves each within one tick.")); } } public static void Flourish(string ownerUid, string spellKey, int castItemId = 0) { TryFlourish(ownerUid, spellKey, castItemId); } public static bool TryFlourish(string ownerUid, string spellKey, int castItemId = 0) { if (!Enabled || string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(spellKey)) { return false; } if (_cast != null) { return _cast.TryFire(ownerUid, NetProtocol.BuildPetFxCast(spellKey, castItemId)); } return false; } internal static void MasterOnProxyFx(string ownerUid, string spellKey, int slot, bool active) { if (!PhotonNetwork.isNonMasterClientInRoom && _store != null && Enabled && !string.IsNullOrEmpty(ownerUid)) { string text = RecordKey.Compose(ownerUid, slot); RecordRow val = default(RecordRow); if (active) { _store.Announce(text, NetProtocol.BuildPetFxSet(spellKey), ""); } else if (_store.TryGet(text, ref val)) { _store.Release(text, "owner reports inactive"); } } } internal static void MasterOnProxyTeardown(string ownerUid, string reason) { if (PhotonNetwork.isNonMasterClientInRoom || _store == null || string.IsNullOrEmpty(ownerUid)) { return; } string a = default(string); int num = default(int); foreach (RecordRow item in _store.RowsSnapshot()) { if (RecordKey.TryParse(item.Key, ref a, ref num) && string.Equals(a, ownerUid, StringComparison.Ordinal)) { _store.Release(item.Key, "owner teardown: " + reason); } } foreach (PetFxRow item2 in _ledger.RowsSnapshot()) { if (string.Equals(item2.OwnerUid, ownerUid, StringComparison.Ordinal)) { DetachRow(item2.OwnerUid, item2.Slot, "owner teardown: " + reason); } } } internal static void OnEffigyBodyDown(string ownerUid, CompanionBody body) { if (string.IsNullOrEmpty(ownerUid)) { return; } if ((Object)(object)body != (Object)null) { BodyFx.DetachAll(((Component)body).transform); } int num = 0; foreach (PetFxRow item in _ledger.RowsSnapshot()) { if (string.Equals(item.OwnerUid, ownerUid, StringComparison.Ordinal)) { _retryAt.Remove(RecordKey.Compose(item.OwnerUid, item.Slot)); if (_ledger.Detach(item.OwnerUid, item.Slot)) { num++; } } } if (num > 0) { Log.LogMessage((object)($"[PETFX] owner '{ownerUid}': effigy body down — re-armed {num} FX row(s); " + "the reconcile re-attaches on the rebuilt body.")); } } private static void FlushTo(int actor) { if (PhotonNetwork.inRoom && !PhotonNetwork.isNonMasterClientInRoom && _store != null) { int num = _store.FlushTo(actor, (Action)null); if (num > 0) { Log.LogMessage((object)$"[PETFX] flushed {num} pet-FX record(s) to actor {actor} (peer-ready — set is idempotent)."); } } } private static void OnRoomChanged(RoomChange change) { _guestReports.Reset(); _retryAt.Clear(); if (_ledger.Count == 0) { return; } foreach (PetFxRow item in _ledger.RowsSnapshot()) { DetachRow(item.OwnerUid, item.Slot, "room changed"); } } internal static void Tick() { if (Time.unscaledTime < _nextTickAt) { return; } _nextTickAt = Time.unscaledTime + 2f; if (!Enabled) { if (_ledger.Count <= 0) { return; } { foreach (PetFxRow item3 in _ledger.RowsSnapshot()) { DetachRow(item3.OwnerUid, item3.Slot, "kill-switch off"); } return; } } foreach (var item4 in _ledger.Expired(Now)) { string item = item4.Item1; int item2 = item4.Item2; string text = RecordKey.Compose(item, item2); string text2 = $"fx watchdog expired (no set heard for {90f:F0}s)"; if (_store == null || !_store.ClearLocal(text, text2)) { DetachRow(item, item2, text2); } } foreach (PetFxRow item5 in _ledger.RowsSnapshot()) { string kind; CompanionBody companionBody = TargetBody(item5.OwnerUid, out kind); if ((Object)(object)companionBody == (Object)null || !HasClone(((Component)companionBody).transform, item5.Slot)) { _ledger.Detach(item5.OwnerUid, item5.Slot); } } if (_store == null) { return; } string text3 = default(string); int num = default(int); PetFxRow val = default(PetFxRow); string spellKey = default(string); foreach (RecordRow item6 in _store.RowsSnapshot()) { if (RecordKey.TryParse(item6.Key, ref text3, ref num) && !_ledger.TryGet(text3, num, ref val) && NetProtocol.TryParsePetFxSet(item6.Payload, ref spellKey)) { Apply(text3, num, spellKey, quiet: true); } } } private static string SlotName(int slot) { if (!_slotNames.TryGetValue(slot, out var value)) { value = (_slotNames[slot] = "CK_BodyFx_" + slot.ToString(CultureInfo.InvariantCulture)); } return value; } private static bool HasClone(Transform body, int slot) { string text = SlotName(slot); for (int num = body.childCount - 1; num >= 0; num--) { Transform child = body.GetChild(num); if ((Object)(object)child != (Object)null && ((Object)child).name == text) { return true; } } return false; } public static bool TryDescribeRow(string ownerUid, int slot, out string oneLine) { if (string.IsNullOrEmpty(ownerUid)) { oneLine = "no row (no owner uid)"; return false; } PetFxRow val = default(PetFxRow); if (_ledger.TryGet(ownerUid, slot, ref val)) { string kind; CompanionBody companionBody = TargetBody(ownerUid, out kind); bool flag = (Object)(object)companionBody != (Object)null && HasClone(((Component)companionBody).transform, slot); oneLine = $"attached '{val.Signature}' age {val.AgeAt(Now):0.#}s " + "(" + kind + " body, clone " + (flag ? "present" : "MISSING") + ")"; return true; } RecordRow val2 = default(RecordRow); if (_store != null && _store.TryGet(RecordKey.Compose(ownerUid, slot), ref val2)) { string text = default(string); NetProtocol.TryParsePetFxSet(val2.Payload, ref text); oneLine = "store row '" + text + "' not applied yet (no body/recipe this instant)"; return false; } oneLine = "no row"; return false; } public static string Dump() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine($"[PETFX] enabled={Enabled} storeRows={((_store != null) ? _store.Count : 0)} " + string.Format("ledgerRows={0} resolver={1} ", _ledger.Count, (Resolver != null) ? "set" : "NONE") + "localPetBody=" + ((LocalPetBody != null) ? "set" : "NONE") + " statusHost=" + ((StatusHostOfOwner != null) ? "set" : "none") + " " + $"guestReports={_guestReports.Count} retryPaced={_retryAt.Count}"); if (_store != null) { string text = default(string); int num = default(int); string text2 = default(string); PetFxRow val = default(PetFxRow); foreach (RecordRow item in _store.RowsSnapshot()) { RecordKey.TryParse(item.Key, ref text, ref num); NetProtocol.TryParsePetFxSet(item.Payload, ref text2); string kind; CompanionBody companionBody = TargetBody(text, out kind); bool flag = _ledger.TryGet(text, num, ref val); string arg = (((Object)(object)companionBody != (Object)null && HasClone(((Component)companionBody).transform, num)) ? "present" : "MISSING"); stringBuilder.AppendLine("[PETFX] row '" + item.Key + "' spell '" + text2 + "' target=" + kind + " " + string.Format("applied={0}{1} clone={2}", flag, flag ? $" age={val.AgeAt(Now):F0}s" : "", arg)); } } RecordRow val2 = default(RecordRow); foreach (PetFxRow item2 in _ledger.RowsSnapshot()) { if (_store == null || !_store.TryGet(RecordKey.Compose(item2.OwnerUid, item2.Slot), ref val2)) { stringBuilder.AppendLine($"[PETFX] local-only row owner '{item2.OwnerUid}' slot {item2.Slot} " + $"sig '{item2.Signature}' age={item2.AgeAt(Now):F0}s"); } } return stringBuilder.ToString().TrimEnd(Array.Empty()); } } public static class CompanionPlayerFx { public const string CastVerb = "ck.playerfx.cast"; public static Func Resolver; private static bool _resolverMissingWarned; private static bool _resolverThrewWarned; private static FireAndForgetChannel _cast; private static ModLog Log => CompanionRuntime.Log; private static bool Enabled => CkConfig.PetFx.EnablePetFx?.Value ?? true; internal static void Init() { _cast = NetBus.RegisterFireAndForget("ck.playerfx.cast", "ck.proxy.playerfx.cast", ProxyPets.AuthorizePlayerOwner, ApplyWire, () => Enabled); } public static void Flourish(string ownerUid, string spellKey, int castItemId = 0) { TryFlourish(ownerUid, spellKey, castItemId); } public static bool TryFlourish(string ownerUid, string spellKey, int castItemId = 0) { if (!Enabled || string.IsNullOrEmpty(ownerUid) || string.IsNullOrEmpty(spellKey)) { return false; } if (_cast != null) { return _cast.TryFire(ownerUid, NetProtocol.BuildPetFxCast(spellKey, castItemId)); } return false; } private static bool ApplyWire(string ownerUid, string payload, string verb, out string relayPayload) { relayPayload = null; string text = default(string); int num = default(int); if (!NetProtocol.TryParsePetFxCast(payload, ref text, ref num)) { NetBus.CountDrop(verb, "unparseable"); return false; } relayPayload = NetProtocol.BuildPetFxCast(text, num); ApplyLocal(ownerUid, text, num, verb); return true; } private static void ApplyLocal(string ownerUid, string spellKey, int castItemId, string verb) { if (!Enabled) { return; } Character val = CompanionRuntime.FindCharacter(ownerUid); if ((Object)(object)val == (Object)null) { NetBus.CountDrop(verb, "no-body"); return; } FxRecipe fxRecipe = ResolveRecipe(spellKey, ownerUid, castItemId); if (fxRecipe == null) { NetBus.CountDrop(verb, "no-recipe"); return; } string sourceName; GameObject val2 = BodyFx.PlayOneShot(fxRecipe, ((Component)val).transform, val, out sourceName); if ((Object)(object)val2 == (Object)null) { NetBus.CountDrop(verb, "play-failed"); return; } string lastPlayBindings = BodyFx.LastPlayBindings; Log.LogMessage((object)("[PLAYERFX] " + BodyFxDescribe.Played(spellKey, verb, ((Object)val).name, sourceName) + ((lastPlayBindings.Length > 0) ? (" | " + lastPlayBindings) : ""))); } private static FxRecipe ResolveRecipe(string spellKey, string ownerUid, int castItemId) { Func resolver = Resolver; if (resolver == null) { if (!_resolverMissingWarned) { _resolverMissingWarned = true; Log.LogWarning((object)"[PLAYERFX] no Resolver installed — every player-FX message drops as no-recipe until the consumer sets CompanionPlayerFx.Resolver. (Warned once.)"); } return null; } try { return resolver(new PetFxRequest { SpellKey = (spellKey ?? ""), Slot = 0, Persist = false, SpeciesId = "", OwnerUid = (ownerUid ?? ""), CastItemId = castItemId }); } catch (Exception ex) { if (!_resolverThrewWarned) { _resolverThrewWarned = true; Log.LogWarning((object)("[PLAYERFX] the Resolver seam threw (treated as no-recipe; warned once): " + ex)); } return null; } } } public enum ProjectileOriginKind { None, FxSlot, ShooterBone, AboveHead } public enum ProjectileDamagePolicy { CallerOwns, SourceNative } public sealed class ProjectileRecipe { public string Key; public string Label = ""; public Func SkillItemId; public string ProjectileFilter = ""; public int OriginFxSlot = -1; public string OriginBone = ""; public Vector3 AboveHeadOffset = new Vector3(0f, 1.2f, 0f); public ProjectileDamagePolicy Damage; } public static class CompanionProjectile { public sealed class FireResult { public bool Launched; public ProjectileOriginKind Origin; public Vector3 Muzzle; public string Reason = ""; public ProjectileCapture.RangedAttackRig Rig; public override string ToString() { if (!Launched) { return "not launched (" + Reason + ")"; } return string.Format("launched from {0} at {1}", Origin, ((Vector3)(ref Muzzle)).ToString("F1")); } } public const string Tag = "[PETPROJ]"; private static readonly Dictionary _recipes = new Dictionary(StringComparer.Ordinal); private static readonly Dictionary _rigs = new Dictionary(StringComparer.Ordinal); private static readonly HashSet _nativePolicyWarned = new HashSet(StringComparer.Ordinal); public static IEnumerable Keys => _recipes.Keys; public static void Register(ProjectileRecipe recipe) { if (recipe != null && !string.IsNullOrEmpty(recipe.Key)) { bool flag = _recipes.ContainsKey(recipe.Key); _recipes[recipe.Key] = recipe; if (flag) { DropRigs(recipe.Key); } CompanionRuntime.Log.LogMessage((object)("[PETPROJ] " + (flag ? "re-registered" : "registered") + " recipe '" + recipe.Key + "'" + (string.IsNullOrEmpty(recipe.Label) ? "" : (" (" + recipe.Label + ")")) + ": " + $"originFxSlot={recipe.OriginFxSlot}, originBone='{recipe.OriginBone}', " + $"filter='{recipe.ProjectileFilter}', damage={recipe.Damage}.")); } } public static bool IsRegistered(string key) { if (key != null) { return _recipes.ContainsKey(key); } return false; } public static ProjectileRecipe Get(string key) { if (key == null || !_recipes.TryGetValue(key, out var value)) { return null; } return value; } public static ProjectileOriginKind TryResolveOrigin(ProjectileRecipe recipe, CompanionBody body, out Vector3 pos) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0096: Unknown result type (might be due to invalid IL or missing references) pos = Vector3.zero; if (recipe == null || (Object)(object)body == (Object)null) { return ProjectileOriginKind.None; } Transform transform = ((Component)body).transform; if (recipe.OriginFxSlot >= 0) { Transform val = FindFxSlot(transform, recipe.OriginFxSlot); if ((Object)(object)val != (Object)null) { pos = val.position; return ProjectileOriginKind.FxSlot; } } if (!string.IsNullOrEmpty(recipe.OriginBone)) { ShooterTransform[] componentsInChildren = ((Component)body).GetComponentsInChildren(true); foreach (ShooterTransform val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name == recipe.OriginBone) { pos = ((Component)val2).transform.position; return ProjectileOriginKind.ShooterBone; } } } pos = transform.position + recipe.AboveHeadOffset; return ProjectileOriginKind.AboveHead; } private static Transform FindFxSlot(Transform body, int slot) { string text = "CK_BodyFx_" + slot.ToString(CultureInfo.InvariantCulture); for (int num = body.childCount - 1; num >= 0; num--) { Transform child = body.GetChild(num); if ((Object)(object)child != (Object)null && ((Object)child).name == text) { return child; } } return null; } private static string RigKey(string key, CompanionBody body) { return key + "|" + ((Object)body).GetInstanceID().ToString(CultureInfo.InvariantCulture); } private static void DropRigs(string key) { List list = new List(); foreach (KeyValuePair rig in _rigs) { if (rig.Key.StartsWith(key + "|", StringComparison.Ordinal)) { list.Add(rig.Key); } } foreach (string item in list) { ProjectileCapture.RangedAttackRig rangedAttackRig = _rigs[item]; if (rangedAttackRig != null && (Object)(object)rangedAttackRig.Root != (Object)null) { Object.Destroy((Object)(object)rangedAttackRig.Root); } _rigs.Remove(item); } } public static ProjectileCapture.RangedAttackRig EnsureRig(ProjectileRecipe recipe, CompanionBody body, out string reason) { //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown //IL_0121: Unknown result type (might be due to invalid IL or missing references) reason = ""; if (recipe == null || (Object)(object)body == (Object)null) { reason = "no recipe/body"; return null; } string key = RigKey(recipe.Key, body); if (_rigs.TryGetValue(key, out var value)) { if (value != null && (Object)(object)value.Root != (Object)null && value.Ready) { return value; } _rigs.Remove(key); } int num = 0; try { num = ((recipe.SkillItemId != null) ? recipe.SkillItemId() : 0); } catch (Exception ex) { reason = "source id provider threw: " + ex.Message; return null; } if (num <= 0) { reason = "no source skill id yet (undiscovered / not configured)"; return null; } GameObject val = new GameObject("CK_ProjRigHolder"); val.SetActive(false); Object.DontDestroyOnLoad((Object)(object)val); ProjectileCapture.RangedAttackRig rangedAttackRig = ProjectileCapture.FromSkillPrefab(num, recipe.ProjectileFilter, val.transform); if (rangedAttackRig == null || (Object)(object)rangedAttackRig.Root == (Object)null) { Object.Destroy((Object)(object)val); reason = $"skill prefab {num} carried no capturable ShootProjectile (see the [BOLT] lines)"; return null; } rangedAttackRig.Root.transform.SetParent(((Component)body).transform, false); rangedAttackRig.Root.transform.localPosition = Vector3.zero; rangedAttackRig.Root.SetActive(true); Object.Destroy((Object)(object)val); _rigs[key] = rangedAttackRig; CompanionRuntime.Log.LogMessage((object)(string.Format("{0} '{1}': rig built from skill prefab {2} ", "[PETPROJ]", recipe.Key, num) + $"(projectile '{rangedAttackRig.ProjectileName}', lifespan {rangedAttackRig.ProjectileLifespan:F1}s) and attached to the body.")); return rangedAttackRig; } public static FireResult Fire(string key, CompanionBody body, Character owner, Character target, Func isValidEnemy = null, Character extraFriendly = null) { //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) FireResult fireResult = new FireResult(); ProjectileRecipe projectileRecipe = Get(key); if (projectileRecipe == null) { fireResult.Reason = "no recipe registered under '" + key + "'"; return fireResult; } if ((Object)(object)body == (Object)null) { fireResult.Reason = "the companion has no body"; return fireResult; } if ((Object)(object)owner == (Object)null) { fireResult.Reason = "no owner Character for the faction snapshot"; return fireResult; } if ((Object)(object)target == (Object)null) { fireResult.Reason = "no target"; return fireResult; } if (projectileRecipe.Damage == ProjectileDamagePolicy.SourceNative && _nativePolicyWarned.Add(projectileRecipe.Key)) { CompanionRuntime.Log.LogWarning((object)("[PETPROJ] '" + projectileRecipe.Key + "' asks for SourceNative damage, which ProjectileCapture does not support (it neutralizes every pooled bolt's payload at Setup, by contract — that strip is what keeps the SHARED pool from double-dipping). Downgraded to CallerOwns: the bolt is the visual, the caller's own hit is the damage.")); } string reason; ProjectileCapture.RangedAttackRig rangedAttackRig = EnsureRig(projectileRecipe, body, out reason); if (rangedAttackRig == null) { fireResult.Reason = reason; return fireResult; } fireResult.Origin = TryResolveOrigin(projectileRecipe, body, out var pos); if (fireResult.Origin == ProjectileOriginKind.None) { fireResult.Reason = "origin unresolvable (no body)"; return fireResult; } Vector3 val = target.CenterPosition - pos; Vector3 aim = ((Vector3)(ref val)).normalized; List list = new List(); ProjectileCapture.AddColliders(list, (Component)(object)body); ProjectileCapture.AddColliders(list, (Component)(object)owner); ProjectileCapture.AddColliders(list, (Component)(object)extraFriendly); pos = (fireResult.Muzzle = ClearMuzzle(pos, target, list, ref aim)); if (!rangedAttackRig.EnsureSetup(owner)) { fireResult.Reason = "rig setup failed (see the [BOLT] lines)"; return fireResult; } Character ignoreCharacter = (((Object)(object)extraFriendly != (Object)null) ? extraFriendly : owner); if (!rangedAttackRig.Fire(target, pos, aim, isValidEnemy, list, ignoreCharacter)) { fireResult.Reason = "nothing launched (see the [BOLT] lines)"; return fireResult; } fireResult.Launched = true; fireResult.Rig = rangedAttackRig; CompanionRuntime.Log.LogMessage((object)string.Format("{0} '{1}' fired at '{2}' — {3}.", "[PETPROJ]", projectileRecipe.Key, target.Name, fireResult)); return fireResult; } private static Vector3 ClearMuzzle(Vector3 muzzle, Character target, List friendlies, ref Vector3 aim) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) if (((Vector3)(ref aim)).sqrMagnitude < 1E-06f || friendlies == null || friendlies.Count == 0) { return muzzle; } if (!BlockedByFriendly(muzzle, aim, friendlies, out var who)) { return muzzle; } float num2 = default(float); float num3 = default(float); float num4 = default(float); for (int i = 0; i < 2; i++) { float num = ((i == 0) ? 1f : (-1f)) * 0.8f; RangedSpecial.MuzzleClearanceOffset(aim.x, aim.y, aim.z, num, 0.4f, ref num2, ref num3, ref num4); Vector3 val = muzzle + new Vector3(num2, num3, num4); Vector3 val2 = target.CenterPosition - val; Vector3 normalized = ((Vector3)(ref val2)).normalized; if (!(((Vector3)(ref normalized)).sqrMagnitude < 1E-06f) && !BlockedByFriendly(val, normalized, friendlies, out var _)) { CompanionRuntime.Log.LogMessage((object)("[PETPROJ] muzzle clearance: '" + who + "' was in the way — stepped aside.")); aim = normalized; return val; } } CompanionRuntime.Log.LogMessage((object)("[PETPROJ] muzzle clearance: '" + who + "' blocks and neither sidestep clears it — firing anyway; the ignored-character slot / HitEnemiesOnly / collider pairing carry the shot.")); return muzzle; } private static bool BlockedByFriendly(Vector3 muzzle, Vector3 aim, List friendlies, out string who) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) who = null; RaycastHit[] array; try { array = Physics.RaycastAll(muzzle, aim, 3.5f, -1, (QueryTriggerInteraction)1); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[PETPROJ] muzzle clearance raycast threw: " + ex.Message + " — skipping the check.")); return false; } float num = float.MaxValue; RaycastHit[] array2 = array; for (int i = 0; i < array2.Length; i++) { RaycastHit val = array2[i]; if (!((Object)(object)((RaycastHit)(ref val)).collider == (Object)null) && friendlies.Contains(((RaycastHit)(ref val)).collider) && RangedSpecial.MuzzleBlocked(true, ((RaycastHit)(ref val)).distance, 3.5f) && !(((RaycastHit)(ref val)).distance >= num)) { num = ((RaycastHit)(ref val)).distance; who = ((Object)((RaycastHit)(ref val)).collider).name; } } return who != null; } public static string Dump() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine(string.Format("{0} {1} recipe(s) registered, {2} cached rig(s).", "[PETPROJ]", _recipes.Count, _rigs.Count)); foreach (KeyValuePair recipe in _recipes) { ProjectileRecipe value = recipe.Value; int num = 0; try { num = ((value.SkillItemId != null) ? value.SkillItemId() : 0); } catch { num = -1; } stringBuilder.AppendLine("[PETPROJ] '" + recipe.Key + "'" + (string.IsNullOrEmpty(value.Label) ? "" : (" (" + value.Label + ")")) + ": sourceSkillId=" + ((num > 0) ? num.ToString() : ((num == 0) ? "NONE (undiscovered/unconfigured)" : "provider threw")) + ", " + $"filter='{value.ProjectileFilter}', originFxSlot={value.OriginFxSlot}, originBone='{value.OriginBone}', " + string.Format("aboveHeadOffset={0}, damage={1}.", ((Vector3)(ref value.AboveHeadOffset)).ToString("F2"), value.Damage)); } foreach (KeyValuePair rig in _rigs) { ProjectileCapture.RangedAttackRig value2 = rig.Value; stringBuilder.AppendLine("[PETPROJ] rig '" + rig.Key + "': " + ((value2 == null || (Object)(object)value2.Root == (Object)null) ? "STALE (root gone — rebuilt on next fire)" : ($"projectile='{value2.ProjectileName}', ready={value2.Ready}, pool={value2.PoolSize}, " + "owner='" + (((Object)(object)value2.SetupOwner != (Object)null) ? value2.SetupOwner.Name : "not set up") + "'"))); } return stringBuilder.ToString().TrimEnd(Array.Empty()); } } public static class CompanionRuntime { private static ModLog s_legacyLog; private static ICompanionSettings s_legacySettings; public static ModLog Log { get { ModLog log = s_legacyLog; if (log == null) { CompanionHost kitHost = CompanionHost.KitHost; if (kitHost == null) { return null; } log = kitHost.Log; } return log; } [Obsolete("K1 (2026-07-26): the process-global logger is retired — create a CompanionHost (CompanionHost.Create) in your Awake and thread it through the Companion aggregate. Shim honored for one wave.")] set { s_legacyLog = value; } } [Obsolete("K1 (2026-07-26): pass an ICompanionSettings/CompanionHost explicitly (Companion(host), CompanionAnchor(host), ProxyPets.Configure(host)). Reads fall back to the first registered host's settings; the setter is honored for one wave.")] public static ICompanionSettings DefaultSettings { get { ICompanionSettings settings = s_legacySettings; if (settings == null) { CompanionHost localDefault = CompanionHost.LocalDefault; if (localDefault == null) { return null; } settings = localDefault.Settings; } return settings; } set { s_legacySettings = value; } } internal static ICompanionSettings Fallback { get { ICompanionSettings settings = s_legacySettings; if (settings == null) { CompanionHost localDefault = CompanionHost.LocalDefault; if (localDefault == null) { return null; } settings = localDefault.Settings; } return settings; } } public static bool IsSanePosition(Vector3 p) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) return WorldPosition.IsSane(p.x, p.y, p.z); } public static Character LocalPlayer() { return Lifecycle.FirstLocalCharacterOrNull(); } public static Character FindCharacter(string uid) { if (string.IsNullOrEmpty(uid)) { return null; } CharacterManager instance = CharacterManager.Instance; if (!((Object)(object)instance != (Object)null)) { return null; } return instance.GetCharacter(uid); } public static string Tag(string name, ICompanionSettings cfg) { string text = cfg?.LogTagSuffix; if (!string.IsNullOrEmpty(text)) { return "[" + name + "/" + text + "]"; } return "[" + name + "]"; } } public sealed class CompanionSaveStore where TState : class { private readonly CompanionHost _host; private readonly string _prefix; private readonly Func _encode; private readonly Func _decode; private ModLog Log => _host.Log; public CompanionSaveStore(CompanionHost host, string filePrefix, Func encode, Func decode) { if (host == null) { throw new ArgumentNullException("host"); } if (encode == null) { throw new ArgumentNullException("encode"); } if (decode == null) { throw new ArgumentNullException("decode"); } _host = host; _prefix = filePrefix ?? ""; _encode = encode; _decode = decode; } public string PathFor(Character player) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) return PathForUid(((Object)(object)player != (Object)null) ? UID.op_Implicit(player.UID) : ""); } public string PathForUid(string uid) { return Path.Combine(Paths.ConfigPath, SaveNaming.FileName(_prefix, uid ?? "")); } public void Save(Character player, TState state) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) Save(((Object)(object)player != (Object)null) ? UID.op_Implicit(player.UID) : "", state); } public void Save(string uid, TState state) { if (string.IsNullOrEmpty(uid)) { Log.LogWarning((object)("[SAVE] refusing a write with no owner uid — it would land in the shared '" + Path.GetFileName(PathForUid("")) + "' and bleed between characters. The caller must pass the owner uid it holds (Save(uid, state)) when the player Character may already be destroyed.")); return; } string text = PathForUid(uid); try { WriteAtomic(text, _encode(state)); } catch (Exception ex) { Log.LogWarning((object)("[SAVE] write failed for uid " + ReformFlow.Uid4(uid) + " ('" + text + "'): " + ex.Message)); } } public static void WriteAtomic(string path, string content) { string text = path + ".tmp"; File.WriteAllText(text, content); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } public void Clear(Character player) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) ClearUid(((Object)(object)player != (Object)null) ? UID.op_Implicit(player.UID) : ""); } public void ClearUid(string uid) { if (string.IsNullOrEmpty(uid)) { Log.LogWarning((object)("[SAVE] clear REFUSED — no owner uid; deleting '" + Path.GetFileName(PathForUid("")) + "' would remove the ownerless global record and leave the real per-character save in place.")); return; } try { string text = PathForUid(uid); if (File.Exists(text)) { File.Delete(text); } string path = text + ".tmp"; if (File.Exists(path)) { File.Delete(path); } } catch (Exception ex) { Log.LogWarning((object)("[SAVE] clear failed for '" + PathForUid(uid) + "': " + ex.Message)); } } public TState Load(Character player) { try { string text = PathFor(player); string text2 = text + ".tmp"; if (File.Exists(text2)) { Log.LogWarning((object)("[SAVE] stale temp file '" + text2 + "' found — a previous write may have been interrupted; ignoring it.")); try { File.Delete(text2); } catch { } } if (!File.Exists(text)) { Log.LogMessage((object)("[SAVE] no save file at '" + text + "' — starting with no bond.")); return null; } TState val = _decode(File.ReadAllText(text), text); if (val == null) { Log.LogWarning((object)("[SAVE] '" + text + "' exists but decoded to nothing — treating as no bond.")); } return val; } catch (Exception ex) { Log.LogWarning((object)("[SAVE] read failed for '" + PathFor(player) + "': " + ex.Message)); return null; } } } public interface ICompanionSceneStep { IEnumerator Run(CompanionSceneCtx ctx); } public sealed class CompanionSceneCtx { public Character Player; public Companion Companion; public string SceneName; internal readonly List Cleanup = new List(); public string Tag => "[CKSCENE:" + SceneName + "]"; public void Defer(Action undo) { if (undo != null) { Cleanup.Add(undo); } } } public sealed class CompanionSceneDef { public readonly string Name; public readonly Func Enabled; public readonly IReadOnlyList Steps; public CompanionSceneDef(string name, Func enabled, params ICompanionSceneStep[] steps) { Name = name; Enabled = enabled; Steps = steps; } } public static class CompanionSceneRunner { private static bool _running; public static void Run(Character player, Companion companion, MonoBehaviour host, CompanionSceneDef scene, Action onComplete) { if (onComplete == null) { return; } try { if (scene == null || !SafeEnabled(scene) || (Object)(object)player == (Object)null || companion == null || (Object)(object)companion.Body == (Object)null || (Object)(object)host == (Object)null || _running) { onComplete(); return; } host.StartCoroutine(Play(scene, new CompanionSceneCtx { Player = player, Companion = companion, SceneName = scene.Name }, onComplete)); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[CKSCENE:" + scene?.Name + "] failed to start (applying plain): " + ex)); onComplete(); } } private static bool SafeEnabled(CompanionSceneDef scene) { try { return scene.Enabled == null || scene.Enabled(); } catch { return false; } } private static IEnumerator Play(CompanionSceneDef scene, CompanionSceneCtx ctx, Action onComplete) { _running = true; try { foreach (ICompanionSceneStep step in scene.Steps) { yield return step.Run(ctx); } } finally { try { onComplete(); } catch (Exception ex) { CompanionRuntime.Log.LogError((object)(ctx.Tag + " completion callback failed: " + ex)); } for (int num = ctx.Cleanup.Count - 1; num >= 0; num--) { try { ctx.Cleanup[num](); } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)(ctx.Tag + " scene cleanup failed: " + ex2)); } } _running = false; } } } public sealed class CompanionMoveToStep : ICompanionSceneStep { private readonly float _ahead; private readonly float _arrive; private readonly float _timeout; private readonly float _settle; public CompanionMoveToStep(float aheadMeters, float arriveDistance, float timeoutSeconds, float settleSeconds = 0f) { _ahead = aheadMeters; _arrive = arriveDistance; _timeout = timeoutSeconds; _settle = settleSeconds; } public IEnumerator Run(CompanionSceneCtx ctx) { if (PhotonNetwork.isNonMasterClientInRoom) { yield break; } CompanionBody body = ctx.Companion.Body; if ((Object)(object)body == (Object)null) { yield break; } Vector3 position = ((Component)ctx.Player).transform.position; Vector3 forward = ((Component)ctx.Player).transform.forward; double num = default(double); double num2 = default(double); CompanionSceneMath.SpotAhead((double)position.x, (double)position.z, (double)forward.x, (double)forward.z, (double)_ahead, ref num, ref num2); Vector3 spot = new Vector3((float)num, position.y, (float)num2); if (NavProbe.SampleAtFeet(spot, 1.5f, out var pos)) { spot = pos; } GameObject spotGo = new GameObject("CK_SceneSpot"); spotGo.transform.position = spot; Transform prevAnchorFollow = ctx.Companion.Anchor?.GetFollowTarget(); ctx.Defer(delegate { if ((Object)(object)body != (Object)null && body.SceneSpot == spotGo.transform) { body.SceneSpot = null; } ctx.Companion.Anchor?.SetFollowTarget(prevAnchorFollow); Object.Destroy((Object)(object)spotGo); }); body.SceneSpot = spotGo.transform; ctx.Companion.Anchor?.SetFollowTarget(spotGo.transform); CompanionRuntime.Log.LogMessage((object)string.Format("{0} companion commanded to the spot ({1}) — awaiting arrival (timeout {2:F0}s).", ctx.Tag, ((Vector3)(ref spot)).ToString("F1"), _timeout)); float deadline = Time.unscaledTime + _timeout; while (Time.unscaledTime < deadline) { if ((Object)(object)body == (Object)null) { yield break; } Vector3 position2 = ((Component)body).transform.position; if (CompanionSceneMath.Arrived((double)position2.x, (double)position2.z, (double)spot.x, (double)spot.z, (double)_arrive)) { CompanionRuntime.Log.LogMessage((object)$"{ctx.Tag} companion arrived — settling {_settle:F1}s, then holding the mark until the scene concludes."); float settleUntil = Time.unscaledTime + _settle; while (Time.unscaledTime < settleUntil) { yield return null; } yield break; } yield return null; } CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " arrival timeout — continuing the scene anyway (anti-wedge).")); } } public sealed class PlayerCastStep : ICompanionSceneStep { private const float CastWedgeTimeoutSeconds = 6f; private readonly Func _castName; public PlayerCastStep(Func castName) { _castName = castName; } public IEnumerator Run(CompanionSceneCtx ctx) { if (!Play(ctx)) { yield break; } Character player = ctx.Player; yield return null; if (!((Object)(object)player == (Object)null) && player.IsCasting) { float deadline = Time.unscaledTime + 6f; while ((Object)(object)player != (Object)null && player.IsCasting && Time.unscaledTime < deadline) { yield return null; } if ((Object)(object)player != (Object)null && player.IsCasting) { CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " cast still running at the wedge timeout — continuing the scene anyway.")); } } } private bool Play(CompanionSceneCtx ctx) { //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0136: Unknown result type (might be due to invalid IL or missing references) try { Character player = ctx.Player; string text = _castName?.Invoke()?.Trim(); if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(text)) { return false; } if (!Enum.TryParse(text, ignoreCase: true, out SpellCastType result)) { CompanionRuntime.Log.LogWarning((object)(ctx.Tag + " unknown SpellCastType '" + text + "' — skipping the animation.")); return false; } if (player.IsCasting) { CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " anim skipped: already casting.")); return false; } if (!player.InLocomotion) { CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " anim skipped: not in locomotion.")); return false; } if (!player.NextIsLocomotion) { CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " anim skipped: leaving locomotion.")); return false; } if (player.PreparingToSleep) { CompanionRuntime.Log.LogMessage((object)(ctx.Tag + " anim skipped: preparing to sleep.")); return false; } player.CastSpell(result, ((Component)player).gameObject, (SpellCastModifier)0, 1, -1f); CompanionRuntime.Log.LogMessage((object)$"{ctx.Tag} played '{result}' on the player — awaiting completion."); return true; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)(ctx.Tag + " animation failed (scene unaffected): " + ex)); return false; } } } public interface ICompanionSettings { float AttackDamage { get; } float AttackInterval { get; } float AggroRange { get; } float AttackRange { get; } float CombatLeashDistance { get; } bool AssistOnOwnerHit { get; } float OwnerFocusRange { get; } float LeashDistance { get; } float CatchUpSpeed { get; } float DisengageRunHomeSeconds { get; } bool AttackVocals { get; } float StationRingFraction { get; } float StationLineAngleDeg { get; } float StationRestationMeters { get; } float StationRestationSeconds { get; } float StationArriveMeters { get; } int StationMaxRestations { get; } float StationFarMeters { get; } float StationProgressMeters { get; } float StationEnemyFastMetersPerSecond { get; } bool AnchorInvisible { get; } bool AnchorShowHealthBar { get; } bool AnchorLinkSummonSlot { get; } bool AnchorHideSummonIcon { get; } float AnchorLeashDistance { get; } float AnchorRespawnSeconds { get; } bool AnchorDealsDamage { get; } float CritHealthFraction { get; } float CritRearmFraction { get; } bool SpeciesVoice { get; } AnchorGlueMode GlueMode { get; } float GlueOffsetBehind { get; } bool UnifyTargets { get; } string GhostPrefabName { get; } AnchorCollisionMode AnchorPlayerCollision { get; } bool AnchorEnabled { get; } bool SuppressLeashWarp { get; } BodilessAnchorPolicy BodilessAnchor { get; } float ModelYawOffset { get; } bool SlopeTiltEnabled { get; } float LoafDistanceMin { get; } float LoafDistanceMax { get; } float LoafRepickDistance { get; } string LogTagSuffix { get; } } public class CompanionSettingsDefaults : ICompanionSettings { public virtual float AttackDamage => 25f; public virtual float AttackInterval => 1.4f; public virtual float AggroRange => 12f; public virtual float AttackRange => 2.6f; public virtual float CombatLeashDistance => 60f; public virtual bool AssistOnOwnerHit => true; public virtual float OwnerFocusRange => 60f; public virtual float DisengageRunHomeSeconds => 12f; public virtual float LeashDistance => 30f; public virtual float CatchUpSpeed => 8f; public virtual bool AttackVocals => false; public virtual float StationRingFraction => 0.6f; public virtual float StationLineAngleDeg => 20f; public virtual float StationRestationMeters => 3f; public virtual float StationRestationSeconds => 2f; public virtual float StationArriveMeters => 0.8f; public virtual int StationMaxRestations => 6; public virtual float StationFarMeters => 3f; public virtual float StationProgressMeters => 1f; public virtual float StationEnemyFastMetersPerSecond => 0f; public virtual bool AnchorInvisible => true; public virtual bool AnchorShowHealthBar => false; public virtual bool AnchorLinkSummonSlot => false; public virtual bool AnchorHideSummonIcon => true; public virtual float AnchorLeashDistance => 20f; public virtual float AnchorRespawnSeconds => 60f; public virtual bool AnchorDealsDamage => false; public virtual float CritHealthFraction => 0.2f; public virtual float CritRearmFraction => 0.5f; public virtual bool SpeciesVoice => true; public virtual AnchorGlueMode GlueMode => (AnchorGlueMode)2; public virtual float GlueOffsetBehind => 0.3f; public virtual bool UnifyTargets => true; public virtual string GhostPrefabName => "NewGhostOneHandedAlly"; public virtual AnchorCollisionMode AnchorPlayerCollision => (AnchorCollisionMode)1; public virtual bool AnchorEnabled => true; public virtual bool SuppressLeashWarp => false; public virtual BodilessAnchorPolicy BodilessAnchor => (BodilessAnchorPolicy)0; public virtual float ModelYawOffset => 0f; public virtual bool SlopeTiltEnabled => false; public virtual bool AllowsBackpedal => false; public virtual float LoafDistanceMin => 2f; public virtual float LoafDistanceMax => 0f; public virtual float LoafRepickDistance => 3f; public virtual string LogTagSuffix => null; } public static class ConsumerContract { internal readonly struct Declaration { public readonly string Plugin; public readonly int BuiltAgainst; public Declaration(string plugin, int builtAgainst) { Plugin = plugin; BuiltAgainst = builtAgainst; } } private static readonly List s_declared = new List(); public static void Declare(string pluginName, int builtAgainst) { if (string.IsNullOrEmpty(pluginName)) { return; } for (int i = 0; i < s_declared.Count; i++) { if (s_declared[i].Plugin == pluginName) { return; } } s_declared.Add(new Declaration(pluginName, builtAgainst)); if (builtAgainst == 2) { ModLog log = Plugin.Log; if (log != null) { log.LogMessage((object)("[CONTRACT] '" + pluginName + "' built against CompanionKit consumer " + $"contract {builtAgainst} (running {2}) — match.")); } } else if (builtAgainst > 2) { ModLog log2 = Plugin.Log; if (log2 != null) { log2.LogError((object)("[CONTRACT] VERSION SKEW (consumer AHEAD): '" + pluginName + "' was built " + $"against CompanionKit consumer contract {builtAgainst}, but this CompanionKit " + string.Format("{0} only provides {1}. CompanionKit is the STALE ", "0.4.20", 2) + "half here — update it to match '" + pluginName + "'. Expect missing seams and, in the worst case, a consumer that fails to load at all.")); } } else { ModLog log3 = Plugin.Log; if (log3 != null) { log3.LogError((object)("[CONTRACT] VERSION SKEW: '" + pluginName + "' was built against CompanionKit " + string.Format("consumer contract {0}, but this CompanionKit {1} requires ", builtAgainst, "0.4.20") + $"{2}. The two were published from different builds. Expect companion " + "features that need a consumer-side registration to be silently absent or to misbehave. Update '" + pluginName + "' and CompanionKit together, from the same release.")); } } } public static string Describe() { if (s_declared.Count == 0) { return string.Format("[CONTRACT] CompanionKit {0} contract {1} — ", "0.4.20", 2) + "no consumer has declared. Either no consumer plugin is installed, or an installed one predates the contract / omits its ConsumerContract.Declare call (see this kit's README). Harmless on its own; it means the version handshake cannot check anything."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(string.Format("[CONTRACT] CompanionKit {0} contract {1}; ", "0.4.20", 2) + $"{s_declared.Count} consumer(s) declared:"); foreach (Declaration item in s_declared) { stringBuilder.Append($"\n[CONTRACT] {item.Plugin} built against {item.BuiltAgainst}" + ((item.BuiltAgainst < 2) ? " <-- STALE" : "")); } return stringBuilder.ToString(); } } public static class DonorHarvest { public static int CyclesThisSession => DonorHarvest.CyclesThisSession; public static Dictionary> DonorScenes => DonorHarvest.DonorScenes; public static Dictionary DonorPins => DonorHarvest.DonorPins; public static string PinFor(string tableKey) { return DonorHarvest.PinFor(tableKey); } public static string IdentityFor(string tableKey, Character donor) { return DonorHarvest.IdentityFor(tableKey, donor); } public static bool TryGetDonorScenes(string speciesId, out List sceneNames, out string searchTerm) { return DonorHarvest.TryGetDonorScenes(speciesId, ref sceneNames, ref searchTerm); } public static bool TryGetExpeditionScenes(string speciesId, out List sceneNames, out string searchTerm) { return DonorHarvest.TryGetExpeditionScenes(speciesId, ref sceneNames, ref searchTerm); } public static string ResolveBuildScene(string want, out List similar) { return DonorHarvest.ResolveBuildScene(want, ref similar); } public static Character FindLiveInScene(Scene scene, string creatureName) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return DonorHarvest.FindLiveInScene(scene, creatureName); } public static Character FindLiveInScene(Scene scene, string creatureName, out int rank) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) return DonorHarvest.FindLiveInScene(scene, creatureName, ref rank); } public static bool RemoveFromAudioRegistries(AmbienceSound a) { return DonorHarvest.RemoveFromAudioRegistries(a); } public static void PruneDeadSoundPlayers() { DonorHarvest.PruneDeadSoundPlayers(); } public static void AuditAudioRegistries(string context) { DonorHarvest.AuditAudioRegistries(context); } public static IEnumerator HarvestChain(List sceneNames, string creatureName, Func use, Action onResult) { return DonorHarvest.HarvestChain(sceneNames, creatureName, use, onResult); } public static IEnumerator Harvest(string sceneName, string creatureName, Func use, Action onResult) { return DonorHarvest.Harvest(sceneName, creatureName, use, onResult); } public static IEnumerator HarvestScene(string sceneName, string label, Func useScene, Action onResult) { return DonorHarvest.HarvestScene(sceneName, label, useScene, onResult); } public static IEnumerator HarvestChain(List sceneNames, string creatureName, Character player, Action onBody, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { return DonorHarvest.HarvestChain(sceneNames, creatureName, (Func)((Character src) => HarvestPuppet(src, player, rangedProjectileFilter, rangedSkillPrefabId)), (Action)delegate(object r) { onBody((CompanionBody)r); }); } public static IEnumerator Harvest(string sceneName, string creatureName, Character player, Action onBody, string rangedProjectileFilter = null, int rangedSkillPrefabId = 0) { return DonorHarvest.Harvest(sceneName, creatureName, (Func)((Character src) => HarvestPuppet(src, player, rangedProjectileFilter, rangedSkillPrefabId)), (Action)delegate(object r) { onBody((CompanionBody)r); }); } private static CompanionBody HarvestPuppet(Character src, Character player, string rangedProjectileFilter, int rangedSkillPrefabId) { CompanionBody companionBody = BodyFactory.BuildPuppet(src, player, consume: false, rangedProjectileFilter, rangedSkillPrefabId); if ((Object)(object)companionBody != (Object)null) { companionBody.Origin = "harvest"; } return companionBody; } } public static class DonorPhotonGuard { public static bool WindowActive => DonorPhotonGuard.WindowActive; public static string ActiveDonorScene => DonorPhotonGuard.ActiveDonorScene; public static void DumpRegistry() { DonorPhotonGuard.DumpRegistry(); } } public static class SkeletonRig { public sealed class Report { internal readonly Report Inner; public bool Healthy => Inner.Healthy; public string Summary => Inner.Summary; internal Report(Report inner) { Inner = inner; } } public static bool RepairEnabled => SkeletonRig.RepairEnabled; public static Report Audit(GameObject donor, GameObject clone, Action warn) { return new Report(SkeletonRig.Audit(donor, clone, warn)); } public static void LogForensics(Report rig, GameObject donor, Action warn) { SkeletonRig.LogForensics(rig.Inner, donor, warn); } public static string Repair(GameObject donor, GameObject clone, Report rig, Action warn) { return SkeletonRig.Repair(donor, clone, rig.Inner, warn); } public static string Census(GameObject go) { return SkeletonRig.Census(go); } } public static class RagdollRig { public static string RestoreJoints(GameObject donor, GameObject clone, Action warn) { return RagdollRig.RestoreJoints(donor, clone, warn); } } public static class TerrainGuard { public static Func UnloadModeProvider { get { return TerrainGuard.UnloadModeProvider; } set { TerrainGuard.UnloadModeProvider = value; } } public static Func UnloadEveryNProvider { get { return TerrainGuard.UnloadEveryNProvider; } set { TerrainGuard.UnloadEveryNProvider = value; } } public static Func FlushAfterPurgeProvider { get { return TerrainGuard.FlushAfterPurgeProvider; } set { TerrainGuard.FlushAfterPurgeProvider = value; } } public static void Dump(string context) { TerrainGuard.Dump(context); } public static void RepairNow(string context) { TerrainGuard.RepairNow(context); } } public static class DonorVerbs { public static void RegisterAll(CommandRegistry c) { DonorVerbs.RegisterAll(c); } } public sealed class EffigyPin : MonoBehaviour { private const float PinDeadBand = 1.5f; private const float GlideExitBand = 0.2f; private const float FaceSlerpRate = 6f; private const float BreadcrumbSeconds = 5f; private Func _anchor; private float _yaw; private float _yOffset; private ICompanionSettings _cfg; private Animator _anim; private LocoRig _loco; private NavMeshAgent _agent; private RigStabilizer _rig; private EffigySwingMirror _swing; private readonly MovementGate _moveGate = new MovementGate(); private Vector3 _faceDir = Vector3.forward; private float _animF; private float _animS; private Vector3 _lastPos; private bool _primed; private bool _anchorMissing; private float _breadcrumbAt; private float _lastCorr; private float _lastSpeed; private string _lastLag = "n/a"; private PinMove _lastMove; private bool _gliding; private SlopeTilt _slope; private Quaternion _flatRot = Quaternion.identity; private static float PinLerp => Mathf.Clamp(CkConfig.Effigy.PinLerp?.Value ?? 1f, 0.05f, 1f); private static float PinCatchUpSpeed => Mathf.Clamp(CkConfig.Effigy.PinCatchUpSpeed?.Value ?? 12f, 1f, 50f); private static float PinSnapDistance => Mathf.Clamp(CkConfig.Effigy.PinSnapDistance?.Value ?? 12f, 5f, 100f); private string Tag => CompanionRuntime.Tag("PIN", _cfg ?? CompanionRuntime.Fallback); internal void Setup(CompanionBody body, Func anchorResolver, float yaw, ICompanionSettings cfg) { //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01cd: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dd: Unknown result type (might be due to invalid IL or missing references) _anchor = anchorResolver; _yaw = yaw; _cfg = cfg; if ((Object)(object)body != (Object)null) { ((Behaviour)body).enabled = false; } _agent = ((Component)this).GetComponent(); _yOffset = (((Object)(object)body != (Object)null && body.HumanoidAgent) ? body.AgentBaseOffset : (((Object)(object)_agent != (Object)null) ? _agent.baseOffset : 0f)); if ((Object)(object)_agent != (Object)null) { _agent.updatePosition = false; _agent.updateRotation = false; ((Behaviour)_agent).enabled = false; } _anim = ((Component)this).GetComponent(); CompanionBody.InitAnimForPuppet(_anim, ((Component)this).gameObject); _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)("[PETRIG] locomotion drive on pinned '" + ((Object)((Component)this).gameObject).name + "': " + _loco.Describe() + ".")); } _swing = ((Component)this).GetComponent(); _rig = ((Component)this).GetComponent(); if ((Object)(object)_rig != (Object)null) { _rig.SpeedSource = () => _moveGate.SmoothedSpeed; } _slope = new SlopeTilt(((Component)this).transform); _slope.Calibrate(((Component)this).GetComponentInChildren(), _agent); _flatRot = ((Component)this).transform.rotation; Character val = ((_anchor != null) ? _anchor() : null); if ((Object)(object)val != (Object)null) { ((Component)this).transform.position = ((Component)val).transform.position + Vector3.up * _yOffset; } _lastPos = ((Component)this).transform.position; _primed = true; CompanionRuntime.Log.LogMessage((object)(Tag + " pinned to anchor viewID=" + CompanionAnchor.ViewIdOf(val) + " " + $"(yaw={_yaw:F0}, yOffset={_yOffset:F2}, agent neutralized) — no independent navigation.")); } private void LateUpdate() { //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Invalid comparison between Unknown and I4 //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Invalid comparison between Unknown and I4 //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Invalid comparison between Unknown and I4 //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_01b3: Unknown result type (might be due to invalid IL or missing references) //IL_01b8: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01ac: Unknown result type (might be due to invalid IL or missing references) //IL_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_01fc: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Unknown result type (might be due to invalid IL or missing references) //IL_0205: Unknown result type (might be due to invalid IL or missing references) //IL_0304: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0321: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_0225: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_0244: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024e: Unknown result type (might be due to invalid IL or missing references) //IL_037d: Unknown result type (might be due to invalid IL or missing references) //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0285: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_0297: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_0334: Unknown result type (might be due to invalid IL or missing references) //IL_0337: Unknown result type (might be due to invalid IL or missing references) //IL_033c: Unknown result type (might be due to invalid IL or missing references) //IL_0341: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_0345: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_034e: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_03df: Unknown result type (might be due to invalid IL or missing references) //IL_03e1: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_02c8: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02df: Unknown result type (might be due to invalid IL or missing references) //IL_02ea: Unknown result type (might be due to invalid IL or missing references) //IL_02f4: Unknown result type (might be due to invalid IL or missing references) //IL_03ed: Unknown result type (might be due to invalid IL or missing references) //IL_03f4: Unknown result type (might be due to invalid IL or missing references) //IL_0415: Unknown result type (might be due to invalid IL or missing references) //IL_041a: Unknown result type (might be due to invalid IL or missing references) //IL_0423: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_0589: Unknown result type (might be due to invalid IL or missing references) //IL_058b: Unknown result type (might be due to invalid IL or missing references) Character val = ((_anchor != null) ? _anchor() : null); Transform val2 = (((Object)(object)val != (Object)null && val.Alive) ? ((Component)val).transform : null); if ((Object)(object)val2 == (Object)null) { if (!_anchorMissing) { _anchorMissing = true; CompanionRuntime.Log.LogMessage((object)(Tag + " anchor transform went missing — holding position, animator idle (will resume when the anchor replica returns).")); } Idle(); if ((Object)(object)_rig != (Object)null) { _rig.PinTick(); } return; } if (_anchorMissing) { _anchorMissing = false; _moveGate.Reset(); _lastPos = ((Component)this).transform.position; CompanionRuntime.Log.LogMessage((object)(Tag + " anchor replica returned (viewID=" + CompanionAnchor.ViewIdOf(val) + ") — pin resumed.")); } Vector3 val3 = val2.position + Vector3.up * _yOffset; Vector3 position = ((Component)this).transform.position; float num = Vector3.Distance(position, val3); PinStep val4 = EffigyPinMath.CatchUpStep(num, _gliding ? 0.2f : 1.5f, PinCatchUpSpeed, PinSnapDistance, Time.deltaTime); _lastMove = val4.Mode; _gliding = (int)val4.Mode == 1; if ((int)val4.Mode == 2) { ((Component)this).transform.position = val3; } else if ((int)val4.Mode == 1) { ((Component)this).transform.position = Vector3.MoveTowards(position, val3, val4.MaxStep); } else { float pinLerp = PinLerp; ((Component)this).transform.position = ((pinLerp >= 1f) ? val3 : Vector3.Lerp(position, val3, pinLerp)); } Vector3 forward = val2.forward; float num2 = default(float); float num3 = default(float); if (EffigyPinMath.HorizontalFacing(forward.x, forward.z, ref num2, ref num3)) { Vector3 val5 = default(Vector3); ((Vector3)(ref val5))..ctor(num2, 0f, num3); Vector3 val6 = Vector3.Slerp(_faceDir, val5, Time.deltaTime * 6f); _faceDir = ((Vector3)(ref val6)).normalized; } if (((Vector3)(ref _faceDir)).sqrMagnitude > 0.01f) { Quaternion val7 = Quaternion.LookRotation(_faceDir, Vector3.up) * Quaternion.Inverse(Quaternion.Euler(0f, _yaw, 0f)); if (_slope == null || !CkConfig.Slope.EnableSlopeTilt.Value || _cfg == null || !_cfg.SlopeTiltEnabled) { ((Component)this).transform.rotation = Quaternion.RotateTowards(((Component)this).transform.rotation, val7, 540f * Time.deltaTime); _flatRot = ((Component)this).transform.rotation; } else { _flatRot = Quaternion.RotateTowards(_flatRot, val7, 540f * Time.deltaTime); ((Component)this).transform.rotation = _slope.Apply(_flatRot, ((Component)this).transform.position, Time.deltaTime); } } Vector3 position2 = ((Component)this).transform.position; float deltaTime = Time.deltaTime; float num4 = 0f; bool flag = false; Vector3 val8 = Vector3.zero; if (_primed && deltaTime > 0f) { Vector3 val9 = position2 - _lastPos; val8 = val9; num4 = EffigyPinMath.HorizontalSpeed(val9.x, val9.z, deltaTime); flag = _moveGate.Evaluate(val9.x, val9.z, deltaTime, true); } _lastPos = position2; _primed = true; if (_loco == null) { _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); } bool flag2 = _cfg is CompanionSettingsDefaults companionSettingsDefaults && companionSettingsDefaults.AllowsBackpedal; Verdict val10 = StrafeCapability.Decide(_loco.HasSide, flag2, 0f, false); if (val10.FeedSide) { Blend val11 = LocoFrame.ProjectDisplacement(val8.x, val8.z, deltaTime, _faceDir.x, _faceDir.z, flag); _animF = LocoFrame.SmoothToward(_animF, val11.Forward, deltaTime, 1.5f, 0.5f); _animS = LocoFrame.SmoothToward(_animS, val11.Side, deltaTime, 1.5f, 0.5f); _loco.Drive(flag, _animF, _animS); } else { _loco.Drive(flag, _moveGate.SmoothedSpeed); } if ((Object)(object)_rig != (Object)null) { _rig.PinTick(); } _lastCorr = num; _lastSpeed = num4; if (Time.time - _breadcrumbAt > 5f) { _breadcrumbAt = Time.time; string on = "none"; float value; float num5 = ((_loco != null && _loco.ReadForward(out value, out on)) ? value : (-99f)); _lastLag = LagOf(val); CompanionRuntime.Log.LogMessage((object)($"{Tag} anchor viewID={CompanionAnchor.ViewIdOf(val)} corr={num:F2}m " + $"speed={num4:F2} mF={num5:F2}@{on} moving={flag} anchorMissing={_anchorMissing} lag={_lastLag} " + $"move={val4.Mode}")); } } private static string LagOf(Character a) { //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)a == (Object)null) { return "n/a"; } PhotonView photonView = ((MonoBehaviour)a).photonView; if ((Object)(object)photonView == (Object)null || photonView.isMine) { return "local"; } return Vector3.Distance(((Component)a).transform.position, a.WantedPosition).ToString("F2") + "m"; } catch { return "n/a"; } } private void Idle() { if (_loco == null) { _loco = LocoRig.Resolve(_anim, ((Component)this).gameObject); } _loco.Idle(); } internal string DumpFragment() { //IL_00e2: Unknown result type (might be due to invalid IL or missing references) Character val = ((_anchor != null) ? _anchor() : null); return "pin: anchor=" + (((Object)(object)val != (Object)null && val.Alive) ? ("viewID=" + CompanionAnchor.ViewIdOf(val)) : "MISSING") + " " + $"corr={_lastCorr:F1}m speed={_lastSpeed:F1} smoothed={_moveGate.SmoothedSpeed:F2} " + string.Format("yOffset={0:F2} attackParams={1} ", _yOffset, ((Object)(object)_swing != (Object)null) ? _swing.AttackParamsFragment : "unprobed") + string.Format("lag={0} move={1} ", ((Object)(object)val != (Object)null) ? LagOf(val) : "n/a", _lastMove) + $"catchUp={PinCatchUpSpeed:F1}m/s snapAt={PinSnapDistance:F0}m deadBand={1.5f:F1}m " + $"netLerp={AnchorReplicaDress.NetLerpSpeed:F2} netMove={AnchorReplicaDress.NetMoveSpeed:F2} " + $"swings={(((Object)(object)_swing != (Object)null) ? _swing.Swings : 0)} rpcSwings={AnchorAttackMirror.RpcSwings}"; } } public sealed class EffigySwingMirror : MonoBehaviour { private ICompanionSettings _cfg; private string _tagName = "SWING"; private Animator _anim; private bool _attackProbed; private bool _hasAttack1; private bool _hasAttack2; private bool _spellNoted; private int _swings; private bool _swingLogged; private string Tag => CompanionRuntime.Tag(_tagName, _cfg ?? CompanionRuntime.Fallback); internal int Swings => _swings; internal string AttackParamsFragment { get { if (!_attackProbed) { return "unprobed"; } return $"A1={_hasAttack1} A2={_hasAttack2}"; } } internal void Setup(ICompanionSettings cfg, string tagName) { _cfg = cfg; if (!string.IsNullOrEmpty(tagName)) { _tagName = tagName; } _anim = ((Component)this).GetComponent(); } internal void Mirror(int type) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 _swings++; if ((Object)(object)_anim == (Object)null) { return; } if (!_attackProbed) { AnimatorControllerParameter[] parameters = _anim.parameters; if (parameters != null && parameters.Length != 0) { _attackProbed = true; AnimatorControllerParameter[] array = parameters; foreach (AnimatorControllerParameter val in array) { if ((int)val.type == 9) { if (val.name == "Attack1") { _hasAttack1 = true; } else if (val.name == "Attack2") { _hasAttack2 = true; } } } } } if (SwingVocabulary.IsSpellShaped(type)) { if (!_spellNoted) { _spellNoted = true; CompanionRuntime.Log.LogMessage((object)($"{Tag} swing type={type} (spell-shaped) — not mirrorable " + "onto a beast rig; noting once.")); } return; } string text = SwingVocabulary.TriggerFor(type); bool flag = ((text == "Attack1") ? _hasAttack1 : _hasAttack2); if (flag) { _anim.SetTrigger(text); } if (!_swingLogged) { _swingLogged = true; CompanionRuntime.Log.LogMessage((object)($"{Tag} swing type={type} mirrored → {text} (hasParam={flag}). " + "Further swings on this body are silent — 'effigydump' carries the running count.")); } } } public static class EngagementHygiene { public const string Tag = "[COMBATFIX]"; private static float _sweepAt; public static bool CrashGuardInstalled => HostilityCheckerGuard.Installed; public static bool Remove(Character owner, Character other) { if ((Object)(object)owner == (Object)null || (Object)(object)other == (Object)null) { return false; } List list = Engaged(owner); bool result = false; if (list != null && list.Contains(other)) { try { owner.RemoveCombatEngagement(other); result = true; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] RemoveCombatEngagement('" + NameOf(other) + "') on '" + NameOf(owner) + "' threw (" + ex.GetType().Name + ": " + ex.Message + ") — removing the entry directly instead.")); int num = list.IndexOf(other); if (num >= 0) { list.RemoveAt(num); result = true; } if (list.Count == 0) { ExitCombat(owner); } } } int num2 = PruneCheckingFor(owner, other); if (num2 > 0) { CompanionRuntime.Log.LogMessage((object)(string.Format("{0} pruned {1} stale checking row(s) for '{2}' from '{3}' ", "[COMBATFIX]", num2, NameOf(other), NameOf(owner)) + "— vanilla's StopCheckingAIHostility had already missed them (desynced parallel lists).")); } return result; } public static int SweepStale(Character owner, string reason) { if ((Object)(object)owner == (Object)null) { return 0; } List list = Engaged(owner); if (list == null) { return 0; } int num = 0; for (int num2 = list.Count - 1; num2 >= 0; num2--) { Character val = list[num2]; if (HostilityRepair.IsStale((Object)(object)val != (Object)null, SafeAlive(val))) { list.RemoveAt(num2); num++; } } int num3 = PruneCheckingStale(owner, list); if (num > 0 && list.Count == 0) { ExitCombat(owner); } if (num > 0 || num3 > 0) { CompanionRuntime.Log.LogMessage((object)(string.Format("{0} swept '{1}' ({2}): dropped {3} dead/destroyed engagement(s) ", "[COMBATFIX]", NameOf(owner), reason, num) + $"and {num3} stale checking row(s) — InCombat={SafeInCombat(owner)}, engaged={list.Count}, {DescribeChecker(owner)}.")); } return num; } public static int RepairChecker(Character owner, string reason) { if ((Object)(object)owner == (Object)null) { return 0; } string arg = DescribeChecker(owner); bool flag = owner.m_hostilityCheckCoroutine != null; if (flag) { try { ((MonoBehaviour)owner).StopCoroutine(owner.m_hostilityCheckCoroutine); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] StopCoroutine on the wedged checker threw (" + ex.GetType().Name + ": " + ex.Message + ") — clearing the field anyway.")); } } owner.m_hostilityCheckCoroutine = null; ClearParallel(owner); int num = 0; if (PhotonNetwork.isMasterClient) { List list = Engaged(owner); List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; int num2 = 0; while (list != null && num2 < list.Count) { Character val = list[num2]; if (!((Object)(object)val == (Object)null) && SafeAlive(val) && val.IsAI) { CharacterControl characterControl = val.CharacterControl; CharacterAI val2 = (CharacterAI)(object)((characterControl is CharacterAI) ? characterControl : null); if (!((Object)(object)val2 == (Object)null) && (hostilesCurrentlyChecking == null || !hostilesCurrentlyChecking.Contains(val2))) { try { owner.StartCheckingAiHostility(val2); num++; } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] re-arming the checker for '" + NameOf(val) + "' threw (" + ex2.GetType().Name + ": " + ex2.Message + ") — skipped.")); NormalizeParallel(owner); } } } num2++; } } NormalizeParallel(owner); CompanionRuntime.Log.LogMessage((object)("[COMBATFIX] re-armed the hostility checker on '" + NameOf(owner) + "' (" + reason + "): coroutine was " + (flag ? "NON-NULL (wedged — no engagement could drain)" : "null (idle)") + "; " + $"{arg} -> {DescribeChecker(owner)}; watching {num} engaged AI again.")); return num; } public static bool FullClear(Character owner, string reason) { if ((Object)(object)owner == (Object)null) { return false; } string text = $"InCombat={SafeInCombat(owner)} engaged={EngagedCount(owner)} {DescribeChecker(owner)}"; try { owner.ClearHostility(); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] ClearHostility on '" + NameOf(owner) + "' threw (" + ex.GetType().Name + ": " + ex.Message + ") — clearing by hand.")); ClearParallel(owner); Engaged(owner)?.Clear(); if (owner.m_hostilityCheckCoroutine != null) { try { ((MonoBehaviour)owner).StopCoroutine(owner.m_hostilityCheckCoroutine); } catch { } owner.m_hostilityCheckCoroutine = null; } ExitCombat(owner); } CompanionRuntime.Log.LogMessage((object)("[COMBATFIX] full clear on '" + NameOf(owner) + "' (" + reason + "): " + text + " -> " + $"InCombat={SafeInCombat(owner)} engaged={EngagedCount(owner)} {DescribeChecker(owner)}.")); return true; } public static int ReleaseLocksOn(Character target) { if ((Object)(object)target == (Object)null) { return 0; } int num = 0; try { DictionaryExt val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.Characters : null); if (val == null) { return 0; } for (int i = 0; i < val.Count; i++) { Character val2 = val.Values[i]; if (!((Object)(object)val2 == (Object)null) && val2.IsAI && SafeAlive(val2) && val2 != target) { CharacterAI component = ((Component)val2).GetComponent(); if (!((Object)(object)component == (Object)null) && !((Object)(object)component.TargetingSystem == (Object)null) && !((Object)(object)component.TargetingSystem.LockedCharacter != (Object)(object)target)) { component.TargetingSystem.SetLockingPoint((LockingPoint)null); num++; } } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] releasing locks on '" + NameOf(target) + "' threw (" + ex.GetType().Name + ": " + ex.Message + ").")); } if (num > 0) { CompanionRuntime.Log.LogMessage((object)(string.Format("{0} released {1} AI lock(s) on '{2}' before it left the world ", "[COMBATFIX]", num, NameOf(target)) + "— a lock whose target is destroyed never ends its holder's fight.")); } return num; } public static bool RemoveStaleAt(Character owner, int index) { if ((Object)(object)owner == (Object)null) { return false; } List list = Engaged(owner); if (list == null || index < 0 || index >= list.Count) { return false; } list.RemoveAt(index); PruneCheckingStale(owner, list); return true; } public static string DescribeChecker(Character owner) { if ((Object)(object)owner == (Object)null) { return "coroutine=? checking=?"; } try { List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; bool flag = owner.m_hostilityCheckCoroutine != null; int num = hostilesCurrentlyChecking?.Count ?? (-1); string text = (flag ? HostilityCheckerGuard.DescribeLiveness(owner, coroutineLive: true) : "null"); if (flag && num == 0) { text += " [nothing being checked — this handle can only be dead]"; } return $"coroutine={text} checking={num}"; } catch { return "coroutine= checking="; } } public static List OwnersToMaintain() { List list = new List(); bool flag; try { flag = PhotonNetwork.isMasterClient; } catch { flag = true; } if (!flag) { Character val = CompanionRuntime.LocalPlayer(); if ((Object)(object)val != (Object)null) { list.Add(val); } return list; } try { DictionaryExt val2 = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.Characters : null); if (val2 != null) { for (int i = 0; i < val2.Count; i++) { Character val3 = val2.Values[i]; if ((Object)(object)val3 != (Object)null && !val3.IsAI) { list.Add(val3); } } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] enumerating engagement owners threw (" + ex.GetType().Name + ": " + ex.Message + ") — falling back to the local player.")); } if (list.Count == 0) { Character val4 = CompanionRuntime.LocalPlayer(); if ((Object)(object)val4 != (Object)null) { list.Add(val4); } } return list; } public static int SweepStaleAll(string reason) { int num = 0; List list = OwnersToMaintain(); for (int i = 0; i < list.Count; i++) { num += SweepStale(list[i], reason); } return num; } public static int RemoveFromAll(Character other, string reason) { if ((Object)(object)other == (Object)null) { return 0; } int num = 0; List list = OwnersToMaintain(); for (int i = 0; i < list.Count; i++) { if (Remove(list[i], other)) { num++; } } if (num > 0) { CompanionRuntime.Log.LogMessage((object)string.Format("{0} removed '{1}' from {2} character(s)' engagement ({3}).", "[COMBATFIX]", NameOf(other), num, reason)); } return num; } public static IEnumerable DescribeCheckedHostiles(Character owner) { if ((Object)(object)owner == (Object)null) { yield break; } List checking = owner.m_hostilesCurrentlyChecking; List unaware = owner.m_timeUnawarePerHostile; List wereAware = owner.m_HostilesWereAware; List engaged = Engaged(owner); if (checking != null) { for (int i = 0; i < checking.Count; i++) { CharacterAI val = checking[i]; Character val2 = (((Object)(object)val != (Object)null) ? ((CharacterControl)val).Character : null); string text = ((unaware != null && i < unaware.Count) ? unaware[i].ToString("F1") : "?"); string text2 = ((wereAware != null && i < wereAware.Count) ? wereAware[i].ToString() : "?"); bool flag = (Object)(object)val2 != (Object)null && engaged != null && engaged.Contains(val2); yield return $"[{i}] '{NameOf(val2)}' alive={SafeAlive(val2)} unaware={text}s/15 wasAware={text2} inEngaged={flag}"; } } } internal static void Tick() { if (CkConfig.Combat.EnableEngagementSweep == null || !CkConfig.Combat.EnableEngagementSweep.Value) { return; } float unscaledTime = Time.unscaledTime; float num = Mathf.Max(1f, (CkConfig.Combat.EngagementSweepSeconds != null) ? CkConfig.Combat.EngagementSweepSeconds.Value : 5f); if (unscaledTime - _sweepAt < num) { return; } _sweepAt = unscaledTime; if (PhotonNetwork.isNonMasterClientInRoom) { return; } List list = OwnersToMaintain(); for (int i = 0; i < list.Count; i++) { Character val = list[i]; if (!((Object)(object)val == (Object)null) && SafeInCombat(val)) { SweepStale(val, "periodic safety net"); HostilityCheckerGuard.Watchdog(val, EngagedCount(val)); } } } private static List Engaged(Character owner) { try { return owner.EngagedCharacters; } catch { return null; } } private static int EngagedCount(Character owner) { return Engaged(owner)?.Count ?? (-1); } private static bool SafeInCombat(Character c) { try { return (Object)(object)c != (Object)null && c.InCombat; } catch { return false; } } internal static bool SafeAlive(Character c) { try { return (Object)(object)c != (Object)null && c.Alive; } catch { return false; } } private static string NameOf(Character c) { try { return ((Object)(object)c == (Object)null) ? "" : c.Name; } catch { return ""; } } private static void ExitCombat(Character owner) { try { owner.m_lastDealers?.Clear(); } catch { } try { if ((Object)(object)Global.CombatManager != (Object)null) { Global.CombatManager.RemoveCombatCharacter(owner); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] RemoveCombatCharacter('" + NameOf(owner) + "') threw (" + ex.GetType().Name + ": " + ex.Message + ") — the engaged list is empty but the combat manager may still hold it.")); } } private static void RemoveAtParallel(Character owner, int i) { List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; List timeUnawarePerHostile = owner.m_timeUnawarePerHostile; List hostilesWereAware = owner.m_HostilesWereAware; if (hostilesCurrentlyChecking != null && i < hostilesCurrentlyChecking.Count) { hostilesCurrentlyChecking.RemoveAt(i); } if (timeUnawarePerHostile != null && i < timeUnawarePerHostile.Count) { timeUnawarePerHostile.RemoveAt(i); } if (hostilesWereAware != null && i < hostilesWereAware.Count) { hostilesWereAware.RemoveAt(i); } } private static void NormalizeParallel(Character owner) { try { List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; if (hostilesCurrentlyChecking != null) { int count = hostilesCurrentlyChecking.Count; int num = Trim(owner.m_timeUnawarePerHostile, count) + Trim(owner.m_HostilesWereAware, count); if (num > 0) { CompanionRuntime.Log.LogWarning((object)(string.Format("{0} normalized {1} orphaned parallel row(s) on '{2}' back to ", "[COMBATFIX]", num, NameOf(owner)) + $"checking={count} — StartCheckingAiHostility had added a timer without its matching awareness flag " + "(a throw between its two Adds), which would have skewed every later index read for the session.")); } } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] normalizing the parallel lists on '" + NameOf(owner) + "' threw (" + ex.GetType().Name + ": " + ex.Message + ").")); } } private static int Trim(List list, int target) { if (list == null) { return 0; } int num = HostilityRepair.OverhangToTrim(list.Count, target); if (num > 0) { list.RemoveRange(target, num); } return num; } private static void ClearParallel(Character owner) { try { owner.m_hostilesCurrentlyChecking?.Clear(); owner.m_timeUnawarePerHostile?.Clear(); owner.m_HostilesWereAware?.Clear(); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] clearing the parallel checking lists on '" + NameOf(owner) + "' threw (" + ex.GetType().Name + ": " + ex.Message + ").")); } } private static int PruneCheckingFor(Character owner, Character target) { List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; if (hostilesCurrentlyChecking == null || (Object)(object)target == (Object)null) { return 0; } int num = 0; for (int num2 = hostilesCurrentlyChecking.Count - 1; num2 >= 0; num2--) { CharacterAI val = hostilesCurrentlyChecking[num2]; Character val2 = null; try { val2 = (((Object)(object)val != (Object)null) ? ((CharacterControl)val).Character : null); } catch { } if (val2 == target) { RemoveAtParallel(owner, num2); num++; } } return num; } private static int PruneCheckingStale(Character owner, List engaged) { List hostilesCurrentlyChecking = owner.m_hostilesCurrentlyChecking; if (hostilesCurrentlyChecking == null) { return 0; } int num = 0; for (int num2 = hostilesCurrentlyChecking.Count - 1; num2 >= 0; num2--) { CharacterAI val = hostilesCurrentlyChecking[num2]; Character val2 = null; try { val2 = (((Object)(object)val != (Object)null) ? ((CharacterControl)val).Character : null); } catch { } if ((Object)(object)val == (Object)null || HostilityRepair.IsStale((Object)(object)val2 != (Object)null, SafeAlive(val2)) || engaged == null || !engaged.Contains(val2)) { RemoveAtParallel(owner, num2); num++; } } if (num > 0 && hostilesCurrentlyChecking.Count == 0 && owner.m_hostilityCheckCoroutine != null) { try { ((MonoBehaviour)owner).StopCoroutine(owner.m_hostilityCheckCoroutine); } catch { } owner.m_hostilityCheckCoroutine = null; } return num; } } public static class ExpeditionOrchestrator { public static Func ActiveSpeciesProvider { get { return ExpeditionOrchestrator.ActiveSpeciesProvider; } set { ExpeditionOrchestrator.ActiveSpeciesProvider = value; } } public static bool AutoWarmDeferred { get { return ExpeditionOrchestrator.AutoWarmDeferred; } set { ExpeditionOrchestrator.AutoWarmDeferred = value; } } public static void RegisterAlwaysWarmSpecies(string species) { ExpeditionOrchestrator.RegisterAlwaysWarmSpecies(species); } public static void RunVerb(string[] parts) { ExpeditionOrchestrator.RunVerb(parts); } public static bool BeginTrip(string scene, Action onDone) { return ExpeditionOrchestrator.BeginTrip(scene, onDone); } public static void OpportunisticCapture(string scene) { ExpeditionOrchestrator.OpportunisticCapture(scene); } public static void AutoWarm() { ExpeditionOrchestrator.AutoWarm(); } public static void ForgetMisses() { ExpeditionOrchestrator.ForgetMisses(); } } public static class ExpeditionHarvest { public static bool InProgress => ExpeditionHarvest.InProgress; public static bool LastTripEndedHome => ExpeditionHarvest.LastTripEndedHome; public static string Status() { return ExpeditionHarvest.Status(); } public static string ForceReset() { return ExpeditionHarvest.ForceReset(); } } public delegate bool FireAndForgetApply(string uid, string payload, string verb, out string relayPayload); public sealed class FireAndForgetChannel { private readonly Func _authorize; private readonly FireAndForgetApply _apply; private readonly Func _enabled; public string CastVerb { get; } public string ProxyVerb { get; } private bool Enabled { get { if (_enabled != null) { return _enabled(); } return true; } } internal FireAndForgetChannel(string castVerb, string proxyVerb, Func authorize, FireAndForgetApply apply, Func enabled) { CastVerb = castVerb; ProxyVerb = proxyVerb; _authorize = authorize; _apply = apply; _enabled = enabled; } public void Fire(string uid, string payload) { TryFire(uid, payload); } public bool TryFire(string uid, string payload) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 FireAndForgetDecision val = FireAndForgetLadder.OnFire(Enabled, string.IsNullOrEmpty(uid), PhotonNetwork.inRoom); if ((int)((FireAndForgetDecision)(ref val)).Action == 0) { return false; } _apply(uid, payload, CastVerb, out var _); if ((int)((FireAndForgetDecision)(ref val)).Action != 3) { return true; } if (PhotonNetwork.isNonMasterClientInRoom) { NetBus.SendToMaster(ProxyVerb, uid, payload); } else { NetBus.SendToOthers(CastVerb, uid, payload); } return true; } internal void OnCast(NetBus.NetMessage msg) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Invalid comparison between Unknown and I4 //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 bool flag = string.IsNullOrEmpty(msg.OwnerUid); FireAndForgetDecision val = FireAndForgetLadder.OnCastReceived(Enabled, msg.SenderIsMaster, msg.SenderIsSelf, flag, !flag && CompanionEffigy.IsLocalOwner(msg.OwnerUid)); FireAndForgetAction action = ((FireAndForgetDecision)(ref val)).Action; if ((int)action != 1) { if (action - 2 <= 1) { _apply(msg.OwnerUid, msg.Payload, CastVerb, out var _); } } else { NetBus.CountDrop(CastVerb, ((FireAndForgetDecision)(ref val)).Reason); } } internal void OnProxy(NetBus.NetMessage msg) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected I4, but got Unknown bool isMasterClient = PhotonNetwork.isMasterClient; bool flag = string.IsNullOrEmpty(msg.OwnerUid); string text = null; if (isMasterClient && Enabled && !flag && _authorize != null) { text = _authorize(msg); } FireAndForgetDecision val = FireAndForgetLadder.OnProxyReceived(Enabled, isMasterClient, flag, text); FireAndForgetAction action = ((FireAndForgetDecision)(ref val)).Action; switch (action - 1) { case 0: NetBus.CountDrop(ProxyVerb, ((FireAndForgetDecision)(ref val)).Reason); break; case 1: { _apply(msg.OwnerUid, msg.Payload, ProxyVerb, out var _); break; } case 2: { if (_apply(msg.OwnerUid, msg.Payload, ProxyVerb, out var relayPayload)) { NetBus.SendToOthers(CastVerb, msg.OwnerUid, relayPayload ?? msg.Payload); } break; } } } } internal static class GhostStandIn { private static Character _ghostPrefab; private static bool _ghostSearched; private static float _missWarnAt = -999f; internal static Character FindGhostPrefab() { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Invalid comparison between Unknown and I4 if (_ghostSearched && (Object)(object)_ghostPrefab != (Object)null) { return _ghostPrefab; } if (_ghostSearched) { _ghostSearched = false; CompanionRuntime.Log.LogWarning((object)"[GHOST] the cached stand-in prefab was unloaded (Resources.UnloadUnusedAssets on a scene load) — re-searching."); } Character val = null; Character val2 = null; Character[] array = Resources.FindObjectsOfTypeAll(); foreach (Character val3 in array) { if ((Object)(object)val3 == (Object)null || (Object)(object)((Component)val3).gameObject == (Object)null) { continue; } string text = ((Object)((Component)val3).gameObject).name.ToLowerInvariant(); if (!text.Contains("ghost") && !text.Contains("spirit") && !text.Contains("spectral")) { continue; } Scene scene = ((Component)val3).gameObject.scene; if (!((Scene)(ref scene)).IsValid()) { CompanionRuntime.Log.LogMessage((object)$"[GHOST] candidate '{((Object)((Component)val3).gameObject).name}' faction={val3.Faction} legacyVisual={val3.UseLegacyVisual}"); if ((Object)(object)val2 == (Object)null) { val2 = val3; } if ((Object)(object)val == (Object)null && (int)val3.Faction == 1) { val = val3; } } } _ghostPrefab = (((Object)(object)val != (Object)null) ? val : val2); if ((Object)(object)_ghostPrefab == (Object)null) { if (Time.unscaledTime - _missWarnAt > 30f) { _missWarnAt = Time.unscaledTime; CompanionRuntime.Log.LogWarning((object)"[GHOST] no ghost/spirit prefab found in Resources (stand-in unavailable) — will retry."); } return null; } _ghostSearched = true; CompanionRuntime.Log.LogMessage((object)("[GHOST] using '" + ((Object)((Component)_ghostPrefab).gameObject).name + "' as the stand-in body.")); return _ghostPrefab; } internal static Character SpawnGhostActive(Character player) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) Character val = FindGhostPrefab(); if ((Object)(object)val == (Object)null) { return null; } Vector3 val2 = ((Component)player).transform.position + ((Component)player).transform.forward * 2f; if (NavProbe.SampleAtFeet(val2, 2f, out var pos) || NavProbe.SampleAtFeet(((Component)player).transform.position, 1.5f, out pos)) { val2 = pos; } GameObject val3 = Object.Instantiate(((Component)val).gameObject, val2, ((Component)player).transform.rotation); Character component = val3.GetComponent(); NeutralizeNetworking(val3); CompanionRuntime.Log.LogMessage((object)$"[GHOST] spawned active '{((Object)val3).name}' (Character={(Object)(object)component != (Object)null}); waiting for visuals."); return component; } private static void NeutralizeNetworking(GameObject go) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) ViewFacts val = Views.Neutralize(go, "ghost stand-in pre-init"); CompanionRuntime.Log.LogMessage((object)("[GHOST] networking neutralized pre-init: " + ViewHygiene.Describe(ref val) + " — group 254 send-blocked.")); } internal static bool GhostVisualReady(Character ghost) { if ((Object)(object)ghost != (Object)null && (Object)(object)ghost.Visuals != (Object)null) { return ghost.Visuals.DefaultVisualsInitialized; } return false; } internal static void NudgeGhostActive(Character ghost) { if (!((Object)(object)ghost == (Object)null)) { ghost.DisableAfterInit = false; if (!((Component)ghost).gameObject.activeSelf) { ((Component)ghost).gameObject.SetActive(true); } } } internal static bool ForceGhostVisuals(Character ghost) { //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) //IL_012a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ghost == (Object)null) { return false; } GameObject gameObject = ((Component)ghost).gameObject; int num = gameObject.GetComponentsInChildren(true).Length; bool flag = (Object)(object)ghost.Visuals == (Object)null; bool flag2 = ghost.VisualData == null; bool flag3 = (Object)(object)ghost.CharacterVisualsPrefab == (Object)null; CompanionRuntime.Log.LogMessage((object)($"[GHOST] force: activeSelf={gameObject.activeSelf} startInit={ghost.m_startInitDone} visualsNull={flag} visualDataNull={flag2} prefabNull={flag3} renderers={num}" + " defVisInit=" + (((Object)(object)ghost.Visuals != (Object)null && ghost.Visuals.DefaultVisualsInitialized) ? "T" : "F"))); string arg = "not reached"; try { CharacterVisuals val = ghost.Visuals; if ((Object)(object)val == (Object)null && !flag3) { Transform val2 = Object.Instantiate(ghost.CharacterVisualsPrefab, gameObject.transform, false); ((Object)val2).name = ((Object)ghost.CharacterVisualsPrefab).name; val2.localPosition = Vector3.zero; val2.localRotation = Quaternion.identity; val2.localScale = Vector3.one; val = (ghost.m_visualsHolder = ((Component)val2).GetComponent()); } if ((Object)(object)val != (Object)null) { val.m_character = ghost; if (ghost.VisualData == null) { arg = "SKIPPED:VisualData null"; CompanionRuntime.Log.LogWarning((object)"[GHOST] VisualData is null — can't build default visuals (would NRE)."); } else if (val.DefaultVisualsInitialized) { arg = "SKIPPED:already initialized"; } else { val.InitDefaultVisuals(); arg = (val.DefaultVisualsInitialized ? "called:ok" : "called:still uninitialized"); } Animator component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { component.Rebind(); } } } catch (Exception ex) { arg = "threw"; CompanionRuntime.Log.LogWarning((object)("[GHOST] force-visuals failed: " + ex.Message)); } int num2 = gameObject.GetComponentsInChildren(true).Length; CompanionRuntime.Log.LogMessage((object)$"[GHOST] force result: renderers {num}->{num2} initDefaultVisuals={arg}"); return num2 > num; } internal static CompanionBody FinishGhostPuppet(Character ghost, Character player) { //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)ghost == (Object)null) { return null; } GameObject gameObject = ((Component)ghost).gameObject; try { Character val = default(Character); if (CharacterManager.Instance.Characters.TryGetValue(UID.op_Implicit(ghost.UID), ref val) && val == ghost) { CharacterManager.Instance.Characters.Remove(UID.op_Implicit(ghost.UID)); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[GHOST] CharacterManager deregistration threw: " + ex.Message)); } Views.Neutralize(gameObject, "ghost finish"); BodyFactory.DestroyImmediateAll(gameObject); CharacterAI[] componentsInChildren = gameObject.GetComponentsInChildren(true); foreach (CharacterAI val2 in componentsInChildren) { if ((Object)(object)val2 != (Object)null) { try { val2.m_aiStatesRoot = null; } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[GHOST] m_aiStatesRoot clear threw: " + ex2.Message)); } Object.DestroyImmediate((Object)(object)val2); } } BodyFactory.DestroyImmediateAll(gameObject); BodyFactory.DestroyImmediateAll(gameObject); Character[] componentsInChildren2 = gameObject.GetComponentsInChildren(true); foreach (Character val3 in componentsInChildren2) { if (!((Object)(object)val3 == (Object)null)) { try { Object.DestroyImmediate((Object)(object)val3); } catch (Exception ex3) { CompanionRuntime.Log.LogError((object)("[GHOST] Character destroy threw — this is a LEGAL DestroyImmediate context (the plugin's poll loop), so the throw is REAL, not a refusal — most likely the Character.OnDestroy Bug-23 family. The husk stays; disabling it. " + ex3.Message)); ((Behaviour)val3).enabled = false; Object.Destroy((Object)(object)val3); } } } Character[] componentsInChildren3 = gameObject.GetComponentsInChildren(true); foreach (Character val4 in componentsInChildren3) { if (!((Object)(object)val4 == (Object)null)) { CompanionRuntime.Log.LogError((object)"[GHOST] a Character survived the ghost strip with no throw — a RequireComponent dependent (CharacterBarManager is the game's only [RequireComponent(Character)]) likely refused the destroy, which fails in any context, logs, and never throws. Disabled now; a deferred Destroy is queued but may be refused for the same dependency reason — the VerifyClean/census lines are the record, not this queue."); ((Behaviour)val4).enabled = false; Object.Destroy((Object)(object)val4); } } Views.VerifyClean(gameObject, "ghost finish", "[GHOST]"); if ((Object)(object)gameObject.GetComponent() == (Object)null) { gameObject.AddComponent(); } CompanionBody companionBody = BodyFactory.FinishPuppet(gameObject, "Spirit", player, null, consumedClone: false, "ghost"); if ((Object)(object)companionBody != (Object)null) { companionBody.YawOffset = 0f; companionBody.HumanoidAgent = true; companionBody.AgentBaseOffset = BodyFactory.ComputeAgentBaseOffset(gameObject); RigStabilizer component = gameObject.GetComponent(); if ((Object)(object)component != (Object)null) { component.HumanoidMode = true; } } return companionBody; } } internal static class HostilityCheckerGuard { private const string IteratorPrefix = ""; private static readonly List _pending = new List(); private static readonly Dictionary _lastRepairAt = new Dictionary(); private static readonly Dictionary _lastMoveNextAt = new Dictionary(); private static readonly Dictionary _firstWatchedAt = new Dictionary(); private static float _watchdogWarnAt = -1f; private static FieldInfo _thisField; private static Type _iteratorType; private static int _crashes; private static float _stormWarnAt = -1f; private static bool _installed; internal static bool Installed => _installed; internal static void Install(string harmonyId) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown MethodInfo methodInfo = FindMoveNext(); if (methodInfo == null) { CompanionRuntime.Log.LogWarning((object)"[COMBATFIX] could not find Character's 'd__NNN' iterator — the hostility-checker crash guard is NOT installed. A crash in vanilla's WaitForHostilityEnd will still wedge engagement removal for the session (the periodic sweep and 'combatclear' remain as fallbacks)."); return; } new Harmony(harmonyId).Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(HostilityCheckerGuard), "Finalizer", (Type[])null, (Type[])null)), (HarmonyMethod)null); _installed = true; CompanionRuntime.Log.LogMessage((object)("[COMBATFIX] hostility-checker crash guard installed on " + _iteratorType.Name + ".MoveNext (this=' " + ((_thisField != null) ? _thisField.Name : "") + "').")); } private static MethodInfo FindMoveNext() { Type[] nestedTypes = typeof(Character).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type in nestedTypes) { if (!type.Name.StartsWith("", StringComparison.Ordinal)) { continue; } MethodInfo method = type.GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method == null) { continue; } _iteratorType = type; FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(Character)) { _thisField = fieldInfo; break; } } return method; } return null; } private static Exception Finalizer(Exception __exception, object __instance) { Character val = OwnerOf(__instance); if ((Object)(object)val != (Object)null) { _lastMoveNextAt[val] = Time.time; _firstWatchedAt.Remove(val); } if (__exception == null) { return null; } _crashes++; CompanionRuntime.Log.LogError((object)("[COMBATFIX] vanilla hostility checker crashed (" + __exception.GetType().Name + ": " + __exception.Message + ") — repairing so engagement can keep draining")); CompanionRuntime.Log.LogWarning((object)(string.Format("{0} crash #{1} owner='{2}' ", "[COMBATFIX]", _crashes, ((Object)(object)val != (Object)null) ? SafeName(val) : "") + EngagementHygiene.DescribeChecker(val) + "\n" + __exception.StackTrace)); if ((Object)(object)val != (Object)null && !_pending.Contains(val)) { _pending.Add(val); } else if ((Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[COMBATFIX] the crashed checker's owning Character could not be read off the iterator — the periodic sweep will still drop stale entries, but the coroutine stays wedged until a zone change."); } return null; } private static Character OwnerOf(object iterator) { if (iterator == null) { return null; } try { if (_thisField != null) { object? value = _thisField.GetValue(iterator); return (Character)((value is Character) ? value : null); } FieldInfo[] fields = iterator.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { if (fieldInfo.FieldType == typeof(Character)) { _thisField = fieldInfo; object? value2 = fieldInfo.GetValue(iterator); return (Character)((value2 is Character) ? value2 : null); } } } catch { } return null; } private static string SafeName(Character c) { try { return c.Name; } catch { return ""; } } internal static string DescribeLiveness(Character owner, bool coroutineLive) { if ((Object)(object)owner == (Object)null || !coroutineLive) { return null; } if (!_installed) { return "non-null (liveness unknown — the crash guard is NOT installed this boot)"; } if (!_lastMoveNextAt.TryGetValue(owner, out var value)) { return "non-null (liveness unknown — no MoveNext observed yet)"; } float num = Time.time - value; if (!(num >= 2f)) { return $"non-null, last tick {num:F1}s ago"; } return $"non-null but SILENT for {num:F1}s — WEDGED (vanilla ticks it every 0.1s)"; } internal static void Watchdog(Character owner, int engagedCount) { if ((Object)(object)owner == (Object)null) { return; } bool flag; int num; try { flag = owner.m_hostilityCheckCoroutine != null; num = ((owner.m_hostilesCurrentlyChecking != null) ? owner.m_hostilesCurrentlyChecking.Count : 0); } catch { return; } if (!flag || engagedCount <= 0) { _firstWatchedAt.Remove(owner); return; } float time = Time.time; string arg; if (!_installed) { if (num != 0) { return; } arg = "a non-null handle with an EMPTY checking list (the crash guard is not installed, so this is the only readable wedge shape)"; } else { float num2 = SignalReference(owner, time); if (!HostilityRepair.CheckerWedged(true, engagedCount, time, num2, 2f)) { return; } arg = $"no MoveNext for {time - num2:F1}s (vanilla ticks it every 0.1s)"; } float value; float num3 = (_lastRepairAt.TryGetValue(owner, out value) ? value : (-1f)); if (HostilityRepair.RepairDue(Time.unscaledTime, num3, 5f)) { _lastRepairAt[owner] = Time.unscaledTime; _lastMoveNextAt[owner] = time; _firstWatchedAt.Remove(owner); if (time - _watchdogWarnAt > 10f || _watchdogWarnAt < 0f) { _watchdogWarnAt = time; CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] WATCHDOG: '" + SafeName(owner) + "' holds a hostility-checker handle " + $"with {engagedCount} engagement(s) that can never drain — {arg}. Repairing. (This is the path that heals a wedge " + "whose crash was never observed: one that predates this build, or a boot where the crash guard failed to install.)")); } EngagementHygiene.SweepStale(owner, "checker watchdog"); EngagementHygiene.RepairChecker(owner, "checker watchdog"); } } private static float SignalReference(Character owner, float now) { if (_lastMoveNextAt.TryGetValue(owner, out var value)) { return value; } if (_firstWatchedAt.TryGetValue(owner, out var value2)) { return value2; } _firstWatchedAt[owner] = now; return now; } internal static void Tick() { if (_pending.Count == 0) { return; } float unscaledTime = Time.unscaledTime; for (int num = _pending.Count - 1; num >= 0; num--) { Character val = _pending[num]; if ((Object)(object)val == (Object)null) { _pending.RemoveAt(num); } else { float value; float num2 = (_lastRepairAt.TryGetValue(val, out value) ? value : (-1f)); if (!HostilityRepair.RepairDue(unscaledTime, num2, 5f)) { if (unscaledTime - _stormWarnAt > 10f) { _stormWarnAt = unscaledTime; CompanionRuntime.Log.LogWarning((object)("[COMBATFIX] the hostility checker RE-TRIPPED on '" + SafeName(val) + "' within " + $"{5f:F0}s of the last repair — throttling the repair (re-arming it is what " + "throws, so a repair-per-crash would be a storm). Something is destroying checked hostiles without booking them out.")); } } else { _lastRepairAt[val] = unscaledTime; _pending.RemoveAt(num); EngagementHygiene.SweepStale(val, "hostility-checker crash"); EngagementHygiene.RepairChecker(val, "hostility-checker crash"); } } } PruneDeadKeys(); } private static void PruneDeadKeys() { PruneDeadKeys(_lastRepairAt); PruneDeadKeys(_lastMoveNextAt); PruneDeadKeys(_firstWatchedAt); } private static void PruneDeadKeys(Dictionary map) { if (map.Count == 0) { return; } List list = null; foreach (KeyValuePair item in map) { if ((Object)(object)item.Key == (Object)null) { (list ?? (list = new List())).Add(item.Key); } } if (list == null) { return; } foreach (Character item2 in list) { map.Remove(item2); } } } internal sealed class LocoRig { private readonly Animator[] _anims; private readonly bool[] _isSkin; private readonly bool[] _probed; private readonly bool[] _hasMoving; private readonly bool[] _hasForward; private readonly bool[] _hasSide; public bool HasSide { get { for (int i = 0; i < _anims.Length; i++) { if (!((Object)(object)_anims[i] == (Object)null)) { if (!_probed[i]) { Probe(i); } if (_hasSide[i]) { return true; } } } return false; } } private LocoRig(Animator[] anims, bool[] isSkin) { _anims = anims; _isSkin = isSkin; _probed = new bool[anims.Length]; _hasMoving = new bool[anims.Length]; _hasForward = new bool[anims.Length]; _hasSide = new bool[anims.Length]; for (int i = 0; i < anims.Length; i++) { Probe(i); } } private void Probe(int i) { Animator val = _anims[i]; if (!((Object)(object)val == (Object)null) && AnimParamLatch.IsAnswer(val.parameterCount)) { _hasMoving[i] = CompanionBody.HasParam(val, "IsMoving"); _hasForward[i] = CompanionBody.HasParam(val, "moveForward"); _hasSide[i] = CompanionBody.HasParam(val, "moveSide"); _probed[i] = true; } } public static LocoRig Resolve(Animator owned, GameObject go) { List list = new List(); List list2 = new List(); if ((Object)(object)owned != (Object)null) { list.Add(owned); list2.Add(item: false); } foreach (Animator item in CompanionBody.CollectSkinAnimators(go)) { if (!((Object)(object)item == (Object)null) && !((Object)(object)item == (Object)(object)owned) && !list.Contains(item)) { list.Add(item); list2.Add(item: true); } } return new LocoRig(list.ToArray(), list2.ToArray()); } public void Drive(bool moving, float speed) { Drive(moving, EffigyPinMath.AnimForward(speed, moving), 0f); } public void Drive(bool moving, float forward, float side) { for (int i = 0; i < _anims.Length; i++) { Animator val = _anims[i]; if (!((Object)(object)val == (Object)null)) { if (!_probed[i]) { Probe(i); } if (_hasMoving[i]) { val.SetBool("IsMoving", moving); } if (_hasForward[i]) { val.SetFloat("moveForward", forward); } if (_hasSide[i]) { val.SetFloat("moveSide", side); } } } } public bool ReadSide(out float value, out string on) { for (int i = 0; i < _anims.Length; i++) { Animator val = _anims[i]; if (!((Object)(object)val == (Object)null)) { if (!_probed[i]) { Probe(i); } if (_hasSide[i]) { value = val.GetFloat("moveSide"); on = ((Object)((Component)val).gameObject).name; return true; } } } value = 0f; on = ((_anims.Length == 0) ? "none" : "no-moveSide"); return false; } public void Idle() { Drive(moving: false, 0f); } public bool ReadForward(out float value, out string on) { for (int i = 0; i < _anims.Length; i++) { Animator val = _anims[i]; if (!((Object)(object)val == (Object)null)) { if (!_probed[i]) { Probe(i); } if (_hasForward[i]) { value = val.GetFloat("moveForward"); on = ((Object)((Component)val).gameObject).name; return true; } } } value = 0f; on = ((_anims.Length == 0) ? "none" : "no-moveForward"); return false; } public string Describe() { if (_anims.Length == 0) { return "none"; } StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < _anims.Length; i++) { if (i > 0) { stringBuilder.Append(", "); } Animator val = _anims[i]; stringBuilder.Append(((Object)(object)val == (Object)null) ? "" : ((Object)((Component)val).gameObject).name).Append("(ctrl=").Append(((Object)(object)val != (Object)null && (Object)(object)val.runtimeAnimatorController != (Object)null) ? ((Object)val.runtimeAnimatorController).name : "none") .Append(_isSkin[i] ? ", SKIN" : ", owned") .Append(_probed[i] ? "" : ", params-pending") .Append(')'); } return stringBuilder.ToString(); } } public abstract class MenuPanelBase : MenuPanel { public MenuScreens ScreenId; public string LogTag = "[MENUTAB]"; public ModLog LogSource; public override void StartInit() { ((MenuPanel)this).StartInit(); if ((Object)(object)base.m_parentPanelHolder != (Object)null) { return; } Transform parent = ((Component)this).transform.parent; while ((Object)(object)parent != (Object)null) { MenuPanelHolder component = ((Component)parent).GetComponent(); if (!((Object)(object)component == (Object)null)) { base.m_parentPanelHolder = component; component.RegisterChildMenu((MenuPanel)(object)this); ModLog logSource = LogSource; if (logSource != null) { logSource.LogMessage((object)(LogTag + " panel holder wired manually: '" + ((Object)component).name + "'.")); } return; } parent = parent.parent; } ModLog logSource2 = LogSource; if (logSource2 != null) { logSource2.LogWarning((object)(LogTag + " no MenuPanelHolder above the panel — the window's auto-hide will close it on tab switch. Parent chain: " + ParentChain())); } } protected string ParentChain() { StringBuilder stringBuilder = new StringBuilder(128); Transform parent = ((Component)this).transform.parent; while ((Object)(object)parent != (Object)null) { stringBuilder.Append(((Object)parent).name).Append(" < "); parent = parent.parent; } if (stringBuilder.Length <= 0) { return "(none)"; } return stringBuilder.ToString(0, stringBuilder.Length - 3); } } public sealed class MenuTabSpec { public string Label; public Type PanelType; public ModLog Log; public string Tag = "[MENUTAB]"; public string PanelObjectName = "MenuPanel"; public string TabObjectName = "MenuTab"; } public static class MenuTabInjector { private static readonly Dictionary> _screens = new Dictionary>(); public static bool Inject(CharacterUI ui, MenuTabSpec spec) { //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: Expected O, but got Unknown //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ui == (Object)null || spec == null) { return false; } Dictionary dictionary = PerUi(spec); if (dictionary.ContainsKey(ui)) { return false; } MenuPanel[] menus = ui.m_menus; Type[] menuTypes = ui.MenuTypes; MenuTab[] menuTabs = ui.m_menuTabs; if (menus == null || menuTypes == null || menuTabs == null || (Object)(object)ui.m_tabHolder == (Object)null) { ModLog log = spec.Log; if (log != null) { log.LogWarning((object)($"{spec.Tag} CharacterUI not injectable (menus={menus != null} " + $"types={menuTypes != null} tabs={menuTabs != null} tabHolder={(Object)(object)ui.m_tabHolder != (Object)null}) — skipped.")); } return false; } int num = Mathf.Max(menus.Length, menuTypes.Length); MenuScreens val = (MenuScreens)num; MenuPanel[] array = (MenuPanel[])(object)new MenuPanel[num + 1]; Array.Copy(menus, array, menus.Length); ui.m_menus = array; Type[] array2 = new Type[num + 1]; Array.Copy(menuTypes, array2, menuTypes.Length); array2[num] = spec.PanelType; ui.MenuTypes = array2; MenuPanelBase menuPanelBase = CreatePanel(ui, val, spec); ui.m_menus[num] = (MenuPanel)(object)menuPanelBase; UIMenuTab val2 = PickDonorTab(menuTabs); if ((Object)(object)val2 == (Object)null) { ModLog log2 = spec.Log; if (log2 != null) { log2.LogWarning((object)(spec.Tag + " no donor tab found to clone — panel exists but no tab button.")); } dictionary[ui] = val; return true; } UIMenuTab tab = CloneTab(val2, val, spec); MenuTab[] array3 = (MenuTab[])(object)new MenuTab[menuTabs.Length + 1]; Array.Copy(menuTabs, array3, menuTabs.Length); array3[menuTabs.Length] = new MenuTab { Tab = tab, TabName = spec.Label }; ui.m_menuTabs = array3; dictionary[ui] = val; ModLog log3 = spec.Log; if (log3 != null) { log3.LogMessage((object)($"{spec.Tag} {spec.Label} tab injected: screen={num}, donor='{((Object)val2).name}', " + $"menus {menus.Length}->{num + 1}, tabs {menuTabs.Length}->{array3.Length}.")); } return true; } catch (Exception ex) { if (spec != null) { ModLog log4 = spec.Log; if (log4 != null) { log4.LogError((object)(spec?.Tag + " injection failed: " + ex)); } } return false; } } public static void OnShowMenu(CharacterUI ui, MenuScreens menu, MenuTabSpec spec) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) try { if ((Object)(object)ui != (Object)null && spec != null && TryGetScreen(ui, spec, out var screen) && menu == screen && (Object)(object)ui.m_lblSectionName != (Object)null) { ui.m_lblSectionName.text = spec.Label; } } catch (Exception ex) { if (spec != null) { ModLog log = spec.Log; if (log != null) { log.LogWarning((object)(spec?.Tag + " header pin failed: " + ex.Message)); } } } } public static bool TryGetScreen(CharacterUI ui, MenuTabSpec spec, out MenuScreens screen) { screen = (MenuScreens)0; if ((Object)(object)ui != (Object)null && spec != null) { return PerUi(spec).TryGetValue(ui, out screen); } return false; } public static bool ShowFor(Character player, MenuTabSpec spec) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) CharacterUI val = (((Object)(object)player != (Object)null) ? player.CharacterUI : null); if (!TryGetScreen(val, spec, out var screen)) { return false; } val.ShowMenu(screen); return true; } private static Dictionary PerUi(MenuTabSpec spec) { if (!_screens.TryGetValue(spec, out var value)) { value = (_screens[spec] = new Dictionary()); } return value; } private static MenuPanelBase CreatePanel(CharacterUI ui, MenuScreens screen, MenuTabSpec spec) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected O, but got Unknown //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0150: Unknown result type (might be due to invalid IL or missing references) SkillMenu componentInChildren = ((Component)ui).GetComponentInChildren(true); Transform val = (((Object)(object)componentInChildren != (Object)null) ? ((Component)componentInChildren).transform.parent : ((Transform)ui.m_tabHolder).parent); GameObject val2 = new GameObject(spec.PanelObjectName, new Type[2] { typeof(RectTransform), typeof(CanvasGroup) }); val2.SetActive(false); RectTransform val3 = (RectTransform)val2.transform; ((Transform)val3).SetParent(val, false); if ((Object)(object)componentInChildren != (Object)null) { RectTransform val4 = (RectTransform)((Component)componentInChildren).transform; val3.anchorMin = val4.anchorMin; val3.anchorMax = val4.anchorMax; val3.pivot = val4.pivot; val3.anchoredPosition = val4.anchoredPosition; val3.sizeDelta = val4.sizeDelta; ((Transform)val3).localScale = ((Transform)val4).localScale; } else { val3.anchorMin = Vector2.zero; val3.anchorMax = Vector2.one; Vector2 offsetMin = (val3.offsetMax = Vector2.zero); val3.offsetMin = offsetMin; } MenuPanelBase menuPanelBase = (MenuPanelBase)(object)val2.AddComponent(spec.PanelType); menuPanelBase.ScreenId = screen; menuPanelBase.LogTag = spec.Tag; menuPanelBase.LogSource = spec.Log; ((MenuPanel)menuPanelBase).RegisterToCharUI = false; ((MenuPanel)menuPanelBase).NonConcurrentMenus = (MenuScreens[])(object)new MenuScreens[0]; ((Panel)menuPanelBase).BehaviourOnStart = (Behaviour)2; ((UIElement)menuPanelBase).SetCharacterUI(ui); return menuPanelBase; } private static UIMenuTab PickDonorTab(MenuTab[] tabs) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Invalid comparison between Unknown and I4 UIMenuTab val = null; foreach (MenuTab val2 in tabs) { if (val2 != null && !((Object)(object)val2.Tab == (Object)null)) { if ((Object)(object)val == (Object)null) { val = val2.Tab; } if ((int)val2.Tab.LinkedMenuID == 7) { return val2.Tab; } } } return val; } private static UIMenuTab CloneTab(UIMenuTab donor, MenuScreens screen, MenuTabSpec spec) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) GameObject val = Object.Instantiate(((Component)donor).gameObject, ((Component)donor).transform.parent); ((Object)val).name = spec.TabObjectName; UIMenuTab component = val.GetComponent(); component.LinkedMenuID = screen; int num = 0; Text[] componentsInChildren = val.GetComponentsInChildren(true); foreach (Text val2 in componentsInChildren) { Component[] components = ((Component)val2).GetComponents(); foreach (Component val3 in components) { if ((Object)(object)val3 != (Object)null && (Object)(object)val3 != (Object)(object)val2 && ((object)val3).GetType().Name.IndexOf("Localize", StringComparison.OrdinalIgnoreCase) >= 0) { Object.Destroy((Object)(object)val3); } } val2.text = spec.Label; num++; } component.Init(); component.OnMenuDisplayed((MenuScreens)24); ModLog log = spec.Log; if (log != null) { log.LogMessage((object)$"{spec.Tag} tab cloned from '{((Object)donor).name}' (labels relabeled: {num})."); } return component; } } public static class NavProbe { public static bool SampleAtFeet(Vector3 reference, float radius, out Vector3 pos) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) NavMeshHit val = default(NavMeshHit); if (NavMesh.SamplePosition(reference, ref val, radius, -1)) { pos = ((NavMeshHit)(ref val)).position; return true; } pos = reference; return false; } } public static class NetBus { public struct NetMessage { public string Verb; public string OwnerUid; public string Payload; public int SenderActorId; public bool SenderIsMaster; public bool SenderIsSelf; } public const string ChannelId = "ck"; public const string LogTag = "CKNET"; private static NetChannel _channel; private static readonly Dictionary> _handshakeFragments = new Dictionary>(); private static readonly List> _peerLostHandlers = new List>(); private static readonly List> _peerReadyHandlers = new List>(); private static readonly List> _peerSceneReadyHandlers = new List>(); public static bool Attached => Net.Attached; private static NetChannel Channel => _channel ?? (_channel = EnsureChannel()); private static void Fan(List> handlers, string name, int actor) { Action[] array = handlers.ToArray(); Action[] array2 = array; foreach (Action action in array2) { try { action(actor); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)string.Format("[{0}] a {1} subscriber threw (actor {2}): {3}", "CKNET", name, actor, ex)); } } } private static NetChannel EnsureChannel() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown ChannelOptions val = new ChannelOptions(); val.LogTag = "CKNET"; val.HelloExtension = () => NetProtocol.BuildHandshake((IReadOnlyDictionary)LocalFragments()); val.QuietVerbs = new string[1] { "ck.proxy.pos" }; NetChannel val2 = Net.RegisterChannel("ck", "0.4.20", val); val2.OnPeerReady += OnPeerReady; val2.OnPeerReady += delegate(PeerInfo info) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) Fan(_peerReadyHandlers, "peer-ready", info.Actor); }; val2.OnPeerSceneReady += delegate(PeerInfo info) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) Fan(_peerSceneReadyHandlers, "peer-scene-ready", info.Actor); }; val2.OnPeerExtensionChanged += OnPeerExtensionChanged; val2.OnPeerLost += delegate(PeerInfo info) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) Fan(_peerLostHandlers, "peer-lost", info.Actor); }; return val2; } internal static void StartAttach(MonoBehaviour host) { NetChannel channel = Channel; } internal static void SubscribePeerLost(Action handler) { if (handler != null) { NetChannel channel = Channel; _peerLostHandlers.Add(handler); } } internal static void SubscribePeerReady(Action handler) { if (handler != null) { NetChannel channel = Channel; _peerReadyHandlers.Add(handler); } } internal static void SubscribePeerSceneReady(Action handler) { if (handler != null) { NetChannel channel = Channel; _peerSceneReadyHandlers.Add(handler); } } public static void Register(string verb, Action handler) { Register(verb, handler, (HandlerRole)0); } public static void Register(string verb, Action handler, HandlerRole role) { //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(verb) && handler != null) { Channel.Register(verb, (Action)delegate(NetMessage nk) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) handler(Convert(nk)); }, role); } } private static NetMessage Convert(NetMessage nk) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) return new NetMessage { Verb = nk.Verb, OwnerUid = nk.Extra, Payload = nk.Payload, SenderActorId = nk.SenderActor, SenderIsMaster = nk.SenderIsMaster, SenderIsSelf = nk.SenderIsSelf }; } public static void RegisterHandshakeFragment(string key, Func value) { if (!string.IsNullOrEmpty(key) && value != null) { _handshakeFragments[key] = value; } } public static bool SendToMaster(string verb, string ownerUid, string payload) { return Channel.SendToMaster(verb, payload ?? "", ownerUid ?? ""); } public static bool SendToOthers(string verb, string ownerUid, string payload) { return Channel.SendToOthers(verb, payload ?? "", ownerUid ?? ""); } public static bool SendToAll(string verb, string ownerUid, string payload) { return Channel.SendToAll(verb, payload ?? "", ownerUid ?? ""); } public static bool SendToPlayer(PhotonPlayer player, string verb, string ownerUid, string payload) { if (player != null) { return Channel.SendToPlayer(player, verb, payload ?? "", ownerUid ?? ""); } return false; } public static bool SendToActor(int actorId, string verb, string ownerUid, string payload) { return Channel.SendToActor(actorId, verb, payload ?? "", ownerUid ?? ""); } public static void CountDrop(string verb, string reason) { Channel.CountDrop(verb ?? "?", reason ?? "?"); } public static StateMirror Mirror(string verb, MirrorTarget target, Func build, MirrorOptions options = null) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) return Channel.Mirror(verb, target, build, (MirrorOptions)(((object)options) ?? ((object)new MirrorOptions()))); } public static ReplicatedStore RegisterStore(string name, StoreOptions options) { return Channel.RegisterStore(name, options); } public static bool SendRequest(string verb, string ownerUid, string payload, float timeoutSeconds, Action onResult) { return Channel.SendRequest(verb, payload ?? "", timeoutSeconds, onResult, ownerUid ?? "", true); } public static void RegisterRequestHandler(string verb, HandlerRole role, Action> handler) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(verb) && handler != null) { Channel.RegisterRequestHandler(verb, role, (Action>)delegate(NetMessage nk, Action reply) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) handler.Invoke(Convert(nk), reply); }); } } public static FireAndForgetChannel RegisterFireAndForget(string castVerb, string proxyVerb, Func authorize, FireAndForgetApply apply, Func enabled = null) { if (string.IsNullOrEmpty(castVerb) || string.IsNullOrEmpty(proxyVerb) || apply == null) { throw new ArgumentException("RegisterFireAndForget needs a cast verb, a proxy verb and an apply seam."); } FireAndForgetChannel fireAndForgetChannel = new FireAndForgetChannel(castVerb, proxyVerb, authorize, apply, enabled); Register(castVerb, fireAndForgetChannel.OnCast, (HandlerRole)12); Register(proxyVerb, fireAndForgetChannel.OnProxy, (HandlerRole)1); return fireAndForgetChannel; } public static bool TryGetPendingRequest(string verb, out string token, out float secondsLeft) { return Channel.TryGetPendingRequest(verb, ref token, ref secondsLeft); } public static string CountersSummary() { return Net.Dump(); } public static string Dump() { return Net.Dump(); } private static Dictionary LocalFragments() { Dictionary dictionary = new Dictionary { ["ck.version"] = "0.4.20" }; foreach (KeyValuePair> handshakeFragment in _handshakeFragments) { string value; try { value = handshakeFragment.Value() ?? ""; } catch (Exception ex) { value = "provider-threw:" + ex.Message; } dictionary[handshakeFragment.Key] = value; } return dictionary; } private static void OnPeerReady(PeerInfo info) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LogHandshakeDiff(info, midSession: false); } private static void OnPeerExtensionChanged(PeerInfo info) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) LogHandshakeDiff(info, midSession: true); } private static void LogHandshakeDiff(PeerInfo info, bool midSession) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) string text = (midSession ? " (MID-SESSION extension change — a table retune on that box)" : ""); int num = default(int); Dictionary dictionary = default(Dictionary); if (!NetProtocol.TryParseHandshake(info.Extension, ref num, ref dictionary)) { CompanionRuntime.Log.LogWarning((object)(string.Format("[{0}] peer actor {1} sent no parseable CK fragment map ", "CKNET", info.Actor) + "('" + info.Extension + "')" + text + " — derived-id drift cannot be checked against it.")); return; } List list = NetProtocol.Diff((IReadOnlyDictionary)LocalFragments(), (IReadOnlyDictionary)dictionary, 1, num); if (list.Count == 0) { CompanionRuntime.Log.LogMessage((object)string.Format("[{0}] handshake with actor {1}: compatible (protocol v{2}){3}.", "CKNET", info.Actor, num, text)); return; } CompanionRuntime.Log.LogWarning((object)(string.Format("[{0}] handshake MISMATCH with actor {1}{2} — guest-pet features ", "CKNET", info.Actor, text) + "may misbehave between these machines (divergent tables mint divergent item ids):")); foreach (string item in list) { CompanionRuntime.Log.LogWarning((object)("[CKNET] " + item)); } } } internal static class OwnerFocusTracker { private struct Struck { public Character Victim; public float At; } private static readonly Dictionary s_byOwner = new Dictionary(); internal static void Report(Character attacker, Character victim) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)attacker == (Object)null) && !((Object)(object)victim == (Object)null) && attacker != victim && !attacker.IsAI && victim.IsAI && victim.Alive && (int)victim.Faction != 1 && !AnchorSentinel.IsAnchorUid(UID.op_Implicit(victim.UID))) { string text = UID.op_Implicit(attacker.UID); if (!string.IsNullOrEmpty(text)) { s_byOwner[text] = new Struck { Victim = victim, At = Time.time }; } } } catch { } } internal static Character Current(Character owner, float holdSeconds) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)owner == (Object)null) { return null; } string text; try { text = UID.op_Implicit(owner.UID); } catch { return null; } if (string.IsNullOrEmpty(text) || !s_byOwner.TryGetValue(text, out var value)) { return null; } if (!OwnerFocus.Active((double)value.At, (double)Time.time, holdSeconds)) { s_byOwner.Remove(text); return null; } Character victim = value.Victim; bool flag; try { flag = (Object)(object)victim != (Object)null && victim.Alive && victim.IsAI; } catch { flag = false; } if (!flag) { s_byOwner.Remove(text); return null; } return victim; } internal static void Forget(Character owner) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)owner == (Object)null) { return; } try { string text = UID.op_Implicit(owner.UID); if (!string.IsNullOrEmpty(text)) { s_byOwner.Remove(text); } } catch { } } } [HarmonyPatch(typeof(Character), "HasHit")] internal static class OwnerFocusHasHit { private static void Postfix(Character __instance, Character _target) { OwnerFocusTracker.Report(__instance, _target); } } [HarmonyPatch(typeof(Projectile), "OnProjectileHit")] internal static class OwnerFocusProjectileHit { private static void Postfix(Projectile __instance, Character _affectedCharacter) { if (!((Object)(object)__instance == (Object)null) && !((Object)(object)_affectedCharacter == (Object)null)) { OwnerFocusTracker.Report(((EffectSynchronizer)__instance).OwnerCharacter, _affectedCharacter); } } } internal static class PetFxVerbs { private const string Tag = "[PETFXOBS]"; private const string NoneWorn = "no FX clones"; internal static void RegisterAll(CommandRegistry c) { c.Register("petfxdump", "Pet spell-FX OBSERVER census on THIS machine: the petfx store rows + ledger (applied/clone state), the effigy bindings and what FX clones each body actually wears, and every live body swept for orphaned clones. The peer-side twin of BW's owner-side petfxdump; wire counters live in netbusdump.", (Action)delegate { CompanionRuntime.Log.LogMessage((object)Dump()); }); c.Register("auralist", "Companion auras: every registered aura recipe (key, slot, source, species/filter) plus its capture state and any dev force override.", (Action)delegate { CompanionRuntime.Log.LogMessage((object)CompanionAura.Dump()); }); c.Register("aura", "aura on|off|clear — dev force one registered aura on/off for the LOCAL player's companion (rides the full petfx pipeline, so MP peers see it too); 'clear' returns it to its consumer driver. Keys: auralist.", (Action)delegate(string[] args) { AuraVerb(args); }); } private static void AuraVerb(string[] args) { //IL_010c: Unknown result type (might be due to invalid IL or missing references) ModLog log = CompanionRuntime.Log; if (args == null || args.Length < 3) { log.LogMessage((object)("[AURA] usage: aura on|off|clear — registered: " + string.Join(", ", CompanionAura.Keys().ToArray()))); return; } string text = args[1]; string text2 = args[2].ToLowerInvariant(); bool? state = text2 switch { "clear" => null, "off" => false, "on" => true, _ => null, }; if (text2 != "on" && text2 != "off" && text2 != "clear") { log.LogMessage((object)"[AURA] second arg must be on|off|clear."); return; } if (!CompanionAura.SetForce(text, state)) { log.LogMessage((object)("[AURA] unknown aura '" + text + "' — registered: " + string.Join(", ", CompanionAura.Keys().ToArray()))); return; } Character val = CompanionRuntime.LocalPlayer(); string text3 = (((Object)(object)val != (Object)null) ? UID.op_Implicit(val.UID) : null); if (!string.IsNullOrEmpty(text3)) { CompanionAura.SetActive(text3, text, state == true); } log.LogMessage((object)("[AURA] '" + text + "' " + ((!state.HasValue) ? "returned to its driver" : (state.Value ? "FORCED ON" : "FORCED OFF")) + (string.IsNullOrEmpty(text3) ? " (no local player — applies at the next driver tick)" : "") + ".")); } internal static string Dump() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(CompanionPetFx.Dump()); List> list = CompanionEffigy.BoundBodiesSnapshot(); stringBuilder.Append(string.Format("\n{0} effigy bindings: {1} owner(s) with a live body on this machine.", "[PETFXOBS]", list.Count)); HashSet hashSet = new HashSet(); foreach (KeyValuePair item in list) { CompanionBody value = item.Value; if (!((Object)(object)value == (Object)null)) { hashSet.Add(value); stringBuilder.Append(string.Format("\n{0} owner '{1}': body#{2} '{3}' wears {4}", "[PETFXOBS]", item.Key, value.BodyId, value.SpeciesId, WornClones(value))); } } int num = 0; foreach (BodyCensus.Entry item2 in BodyCensus.Snapshot()) { CompanionBody body = item2.Body; if (!((Object)(object)body == (Object)null) && !hashSet.Contains(body)) { string text = WornClones(body); if (!(text == "no FX clones")) { num++; stringBuilder.Append(string.Format("\n{0} UNBOUND body#{1} '{2}' origin={3} wears {4} ", "[PETFXOBS]", body.BodyId, body.SpeciesId, body.Origin, text) + "(the owner's own pet locally — or a body the effigy layer no longer binds, which is the orphan-clone shape)"); } } } stringBuilder.Append(string.Format("\n{0} live-body sweep: {1} CompanionBody instance(s), ", "[PETFXOBS]", BodyCensus.LiveCount) + $"{num} unbound one(s) wearing FX. Wire counters (drops per verb) are netbusdump's, not " + "duplicated here."); return stringBuilder.ToString(); } private static string WornClones(CompanionBody body) { List list = new List(); Transform transform = ((Component)body).transform; for (int i = 0; i < transform.childCount; i++) { Transform child = transform.GetChild(i); if (!((Object)(object)child == (Object)null) && ((Object)child).name.StartsWith("CK_BodyFx_", StringComparison.Ordinal)) { list.Add(((Object)child).name + "(" + (((Component)child).gameObject.activeInHierarchy ? "active" : "INACTIVE") + ")"); } } if (list.Count != 0) { return string.Join(", ", list.ToArray()); } return "no FX clones"; } } public readonly struct StatStackSpec { public readonly Stat Target; public readonly string SourceId; public readonly float Value; public readonly bool Multiplier; public readonly string Label; public StatStackSpec(Stat target, string sourceId, float value, bool multiplier, string label = null) { Target = target; SourceId = sourceId; Value = value; Multiplier = multiplier; Label = label; } } public enum StatBuffChange { NoOp, Applied, Cleared } public readonly struct StatBuffResult { public readonly StatBuffChange Change; public readonly bool SamePlayer; public readonly int AppliedCount; public StatBuffResult(StatBuffChange change, bool samePlayer, int appliedCount) { Change = change; SamePlayer = samePlayer; AppliedCount = appliedCount; } } public sealed class PlayerStatBuff { private readonly bool _retainOwnerWhenEmpty; private Character _appliedTo; private readonly List _applied = new List(); public Character AppliedTo => _appliedTo; public int AppliedCount => _applied.Count; public IReadOnlyList Applied => _applied; public PlayerStatBuff(bool retainOwnerWhenEmpty = true) { _retainOwnerWhenEmpty = retainOwnerWhenEmpty; } public bool SamePlayer(Character player) { return player == _appliedTo; } public StatBuffResult Sync(Character player, List desired, Action onSkip = null) { if ((Object)(object)player == (Object)null) { return new StatBuffResult(StatBuffChange.NoOp, player == _appliedTo, _applied.Count); } bool flag = player == _appliedTo; if (flag && SameStacks(desired, _applied)) { return new StatBuffResult(StatBuffChange.NoOp, samePlayer: true, _applied.Count); } Clear(); Apply(player, desired, onSkip); return new StatBuffResult((_applied.Count > 0) ? StatBuffChange.Applied : StatBuffChange.Cleared, flag, _applied.Count); } public void Apply(Character player, List desired, Action onSkip = null) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Expected O, but got Unknown foreach (StatStackSpec item in desired) { if (item.Target == null) { onSkip?.Invoke(item); continue; } if (item.Multiplier) { item.Target.AddMultiplierStack(item.SourceId, item.Value); } else { item.Target.AddRawStack(new StatStack(item.SourceId, item.Value, (Tag[])null)); } _applied.Add(item); } if (_applied.Count > 0 || _retainOwnerWhenEmpty) { _appliedTo = player; } } public void Clear() { if ((Object)(object)_appliedTo != (Object)null) { foreach (StatStackSpec item in _applied) { if (item.Target != null) { if (item.Multiplier) { item.Target.RemoveMultiplierStack(item.SourceId); } else { item.Target.RemoveRawStack(item.SourceId); } } } } _applied.Clear(); _appliedTo = null; } private static bool SameStacks(List desired, List applied) { if (desired.Count != applied.Count) { return false; } for (int i = 0; i < desired.Count; i++) { if (desired[i].SourceId != applied[i].SourceId || desired[i].Value != applied[i].Value || desired[i].Multiplier != applied[i].Multiplier) { return false; } } return true; } public static float Purge(Stat stat, string sourceId, bool mult) { if (stat == null || string.IsNullOrEmpty(sourceId)) { return 0f; } float result = 0f; IList list = (mult ? stat.MultStack : stat.RawStack); if (list != null) { for (int i = 0; i < list.Count; i++) { if (list[i] != null && list[i].SourceID == sourceId) { result = list[i].EffectiveValue; break; } } } stat.RemoveStack(sourceId, mult); stat.Update(); return result; } public static string OurStack(Stat stat, string sourceId, bool mult) { IEnumerable enumerable = ((!mult) ? ((stat != null) ? stat.RawStack : null) : ((stat != null) ? stat.MultStack : null)); if (enumerable != null) { foreach (StatStack item in enumerable) { if (item?.SourceID == sourceId) { return $" [ours {item.EffectiveValue:+0.###;-0.###}]"; } } } return ""; } } [BepInPlugin("cobalt.companionkit", "CompanionKit", "0.4.20")] [BepInDependency("cobalt.forgekit", "0.4.10")] [BepInDependency("cobalt.donorkit", "0.1.7")] [BepInDependency("cobalt.aggrokit", "0.1.5")] [BepInDependency("cobalt.netkit", "0.2.6")] public class Plugin : BaseUnityPlugin { public const string GUID = "cobalt.companionkit"; public const string NAME = "CompanionKit"; public const string VERSION = "0.4.20"; public const string COMPAT_SINCE = "0.4.20"; public const int CONSUMER_CONTRACT = 2; internal static ModLog Log; internal static Plugin Instance; private CommandRegistry _commands; private CommandChannel _channel; private static float _combatFixWarnAt = -1f; [MethodImpl(MethodImplOptions.NoInlining)] private static void DeclareKitContracts() { KitContract.Declare("CompanionKit", "cobalt.forgekit", "0.4.10"); KitContract.Declare("CompanionKit", "cobalt.donorkit", "0.1.7"); KitContract.Declare("CompanionKit", "cobalt.aggrokit", "0.1.5"); KitContract.Declare("CompanionKit", "cobalt.netkit", "0.2.6"); } private void TryDeclareKitContracts() { try { DeclareKitContracts(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("[CONTRACT] kit handshake unavailable (" + ex.GetType().Name + ") — is ForgeKit older than this mod?")); } } internal void Awake() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Expected O, but got Unknown //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0113: Expected O, but got Unknown //IL_013b: Unknown result type (might be due to invalid IL or missing references) TryDeclareKitContracts(); Instance = this; Log = ModLog.Bind((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger); CompanionHost.SetKitHost(Log); if (Notify.Log == null) { Notify.Log = ModLog.op_Implicit(Log); } CkConfig.Equipment.Bind(((BaseUnityPlugin)this).Config); CkConfig.Effigy.Bind(((BaseUnityPlugin)this).Config); CkConfig.PetFx.Bind(((BaseUnityPlugin)this).Config); CkConfig.Diag.Bind(((BaseUnityPlugin)this).Config); CkConfig.Combat.Bind(((BaseUnityPlugin)this).Config); CkConfig.Proxy.Bind(((BaseUnityPlugin)this).Config); CkConfig.Slope.Bind(((BaseUnityPlugin)this).Config); BodyTemplateStore.StatCapturer = (Character src, object host) => AttributeCapture.From(src, host as CompanionHost); RegisterVerbs(); _channel = new CommandChannel("ck_cmd.txt", ModLog.op_Implicit(Log), _commands, 0.5f, true, true, new CatalogInfo { ModGuid = "cobalt.companionkit", ModName = "CompanionKit", ModVersion = "0.4.20", ConfigSource = () => ((BaseUnityPlugin)this).Config }); CommonVerbs.RegisterConfigVerbs(_commands, ModLog.op_Implicit(Log), (Func)(() => ((BaseUnityPlugin)this).Config), (Action)null, true); try { new Harmony("cobalt.companionkit").PatchAll(); } catch (Exception arg) { Log.LogError((object)("[CK] Harmony PatchAll FAILED — the registry/summon-icon/anchor patches are NOT" + $" installed; expect duplicate-UID and anchor-dressing misbehavior: {arg}")); } try { HostilityCheckerGuard.Install("cobalt.companionkit.combatfix"); } catch (Exception arg2) { Log.LogError((object)("[COMBATFIX] installing the hostility-checker crash guard FAILED — a crash inside vanilla's" + $" WaitForHostilityEnd will still wedge engagement removal for the session: {arg2}")); } ((MonoBehaviour)this).StartCoroutine(BodyReaper.Sweep()); NetBus.StartAttach((MonoBehaviour)(object)this); ProxyPets.Init(); ((Component)this).gameObject.AddComponent(); CompanionEffigy.Init(); CompanionPetFx.Init(); CompanionPlayerFx.Init(); Log.LogMessage((object)"CompanionKit 0.4.20 loaded."); Log.LogMessage((object)("[CK] build " + BuildStamp.Read(((object)this).GetType().Assembly) + " @ " + ((object)this).GetType().Assembly.Location)); } internal void Update() { _channel.Tick(); CompanionAnchor.SweepLease(); ProxyPets.Tick((MonoBehaviour)(object)this); CompanionEffigy.Tick((MonoBehaviour)(object)this); CompanionPetFx.Tick(); try { HostilityCheckerGuard.Tick(); EngagementHygiene.Tick(); } catch (Exception ex) { if (Time.unscaledTime - _combatFixWarnAt > 10f) { _combatFixWarnAt = Time.unscaledTime; Log.LogWarning((object)("[COMBATFIX] the per-frame engagement-hygiene tick threw (" + ex.GetType().Name + ": " + ex.Message + ") — swallowed so it cannot spam or stall Update. Repairs are skipped this frame.")); } } } private void RegisterVerbs() { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown _commands = new CommandRegistry(ModLog.op_Implicit(Log)); _commands.Register("expedition", "Round-trip to an oversized donor scene and cache body templates ('expedition '; no args = status + cache + manifest + config; host/offline only).", (Action)delegate(string[] parts) { ExpeditionOrchestrator.RunVerb(parts); }); _commands.Register("templateclear", "Clear the session body-template cache ('templateclear [all]' — default keeps prebuilt bundle bodies; logs cleared/kept).", (Action)delegate(string[] parts) { Log.LogMessage((object)BodyTemplateCache.ClearVerb(parts)); }); _commands.Register("expeditionreset", "Force the expedition guard open after a wedge (does NOT teleport anyone — use 'goto' for that).", (Action)delegate { Log.LogMessage((object)ExpeditionHarvest.ForceReset()); }); DonorVerbs.RegisterAll(_commands); _commands.Register("templateprobe", "Per cached template: species + first SkinnedMeshRenderer sharedMesh.isReadable (defensive read).", (Action)delegate { Log.LogMessage((object)BodyTemplateCache.Probe()); }); _commands.Register("proxydump", "Guest-pet proxy census: owner/species/tier/anchor-hp per proxied pet + the NetBus traffic counters (master-side; docs/guest-pets-plan.md M3).", (Action)delegate { Log.LogMessage((object)ProxyPets.Dump()); }); _commands.Register("netbusdump", "Co-op census on THIS machine (works on both sides) — delegates to NetKit's netdump: transport/attach, per-channel verbs/peers/counters/trace, the hello ledger, and the PUN-signature + unknown-view tables.", (Action)delegate { Log.LogMessage((object)NetBus.Dump()); }); _commands.Register("proxykill", "Dev: 'proxykill ' tears that guest-pet proxy row down DIRECTLY (master-side) — the ONLY way to exercise the teardown-recovery path; the guest's ~30s re-announce should resurrect the row with stats re-flowing.", (Action)delegate(string[] parts) { Log.LogMessage((object)ProxyPets.DevKill((parts.Length > 1) ? parts[1] : null)); }); _commands.Register("effigydump", "M6 effigy census on THIS machine: config flags, per-row owner/species/tier/anchor-resolution/body-rung, and the master's owned-pet latch (docs/guest-pets-plan.md §M6).", (Action)delegate { Log.LogMessage((object)CompanionEffigy.Dump()); }); _commands.Register("effigyrebind", "Dev: 'effigyrebind ' — reset that effigy row's LOCAL binding (cached anchor + body) so the next reconcile re-resolves from scratch. The surgical version of the room-rejoin recovery BUG-EFFIGYVANISH needed (2026-07-31); wire rows are kept, nothing is sent.", (Action)delegate(string[] parts) { Log.LogMessage((object)CompanionEffigy.RebindOwner((parts.Length > 1) ? parts[1] : null)); }); _commands.Register("ckreload", "Re-read cobalt.companionkit.cfg from disk (BepInEx has no file watcher — same rule as BW's reloadcfg). [Effigy] flags apply on the next reconcile tick.", (Action)delegate { ((BaseUnityPlugin)this).Config.Reload(); Log.LogMessage((object)"[CK] config reloaded from disk."); }); PetFxVerbs.RegisterAll(_commands); _commands.Register("ckequipdump", "Companion-equipment census: the [Equipment] flag + every live equipment aggregate on this machine (slot, equipped key/id, durability, broken state) — docs/pet-armor-plan.md.", (Action)delegate { Log.LogMessage((object)CompanionEquipment.Dump()); }); _commands.Register("bodycensus", "E8d-A: every live CompanionBody on this machine — id/species/origin/age/claim state/position + the agent's INTERNAL position (a transform-vs-next disagreement is the stale-agent drag-back signature). The two-body question in one dump.", (Action)delegate { Log.LogMessage((object)BodyCensus.Dump()); }); } } public static class ProjectileCapture { public sealed class RangedAttackRig { public GameObject Root; public ShootProjectile Shooter; public Transform Muzzle; public string SourcePath = ""; public string ProjectileName = ""; public float Force; public string TargetingModeName = ""; public string CastPositionName = ""; public string TransformName = ""; public int ShotCount; public float ProjectileLifespan; public float[] NativeDamage; public readonly List RemovedPayload = new List(); public string ProjectileTypeName = ""; public bool HitEnemiesOnlyArmed; private Character _setupOwner; private int _volleyId; private bool _resolved; private int _exploded; private int _fired; private Character _resolvedHit; private Func _isValidEnemy; public bool Ready { get { if ((Object)(object)Shooter != (Object)null) { return (Object)(object)Shooter.BaseProjectile != (Object)null; } return false; } } public Character SetupOwner => _setupOwner; public int PoolSize { get { if (!((Object)(object)Shooter != (Object)null) || ((Effect)Shooter).m_subEffects == null) { return 0; } return ((Effect)Shooter).m_subEffects.Length; } } public Character ResolvedHit => _resolvedHit; public VolleyState Volley => RangedSpecial.AssessVolley(_resolved, _exploded, _fired); public bool EnsureSetup(Character owner) { if (!Ready || (Object)(object)owner == (Object)null || (Object)(object)owner.TargetingSystem == (Object)null) { return false; } if (_setupOwner == owner && (Object)(object)owner != (Object)null && PoolSize > 0) { return true; } try { ((Shooter)Shooter).Setup(owner.TargetingSystem, Root.transform); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] Shooter.Setup threw (" + ex.Message + ") — rig unusable this attempt.")); return false; } _setupOwner = owner; int num = 0; HitEnemiesOnlyArmed = false; if (((Effect)Shooter).m_subEffects != null) { SubEffect[] subEffects = ((Effect)Shooter).m_subEffects; foreach (SubEffect val in subEffects) { Projectile val2 = (Projectile)(object)((val is Projectile) ? val : null); if (!((Object)(object)val2 == (Object)null)) { num++; if (ProjectileLifespan <= 0f) { ProjectileLifespan = val2.Lifespan; } if (string.IsNullOrEmpty(ProjectileTypeName)) { ProjectileTypeName = ((object)val2).GetType().Name; } if (ArmRaycastFriendlyFilter(val2)) { HitEnemiesOnlyArmed = true; } NeutralizePayload(val2); BoltHitRelay component = ((Component)val2).gameObject.GetComponent(); BoltHitRelay boltHitRelay = (((Object)(object)component != (Object)null) ? component : ((Component)val2).gameObject.AddComponent()); boltHitRelay.Rig = this; } } } CompanionRuntime.Log.LogMessage((object)($"[BOLT] rig setup: owner='{owner.Name}', pool={num} bolt(s), " + $"lifespan={ProjectileLifespan:F1}s, nativeDmg=[{ProfileString(NativeDamage)}], " + "stripped=[" + string.Join(", ", RemovedPayload) + "].")); CompanionRuntime.Log.LogMessage((object)("[BOLT] bolt class='" + ProjectileTypeName + "' hitEnemiesOnly=" + (HitEnemiesOnlyArmed ? "ARMED (raycast-path friendly filter)" : "n/a (not a RaycastProjectile, or no owner faction snapshot)") + " — this line names which friendly-fire guard is load-bearing for this species.")); return num > 0; } private static bool ArmRaycastFriendlyFilter(Projectile p) { RaycastProjectile val = (RaycastProjectile)(object)((p is RaycastProjectile) ? p : null); if ((Object)(object)val == (Object)null) { return false; } if ((Object)(object)((EffectSynchronizer)val).OwnerCharacter == (Object)null || (Object)(object)((EffectSynchronizer)val).OwnerCharacter.TargetingSystem == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[BOLT] RaycastProjectile has no resolvable OwnerCharacter/TargetingSystem — NOT arming HitEnemiesOnly (it would NRE in the engine's hit loop); the ignored-character slot and the muzzle sidestep carry this volley."); return false; } val.HitEnemiesOnly = true; return true; } private void NeutralizePayload(Projectile p) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Expected I4, but got Unknown Effect[] componentsInChildren = ((Component)p).GetComponentsInChildren(true); foreach (Effect val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } PunctualDamage val2 = (PunctualDamage)(object)((val is PunctualDamage) ? val : null); if ((Object)(object)val2 != (Object)null && val2.Damages != null && NativeDamage == null) { NativeDamage = new float[9]; DamageType[] damages = val2.Damages; foreach (DamageType val3 in damages) { int num = (int)val3.Type; if (num >= 0 && num < NativeDamage.Length) { NativeDamage[num] += val3.Damage; } } } string item = ((object)val).GetType().Name + (((Object)(object)val2 != (Object)null && val2.Damages != null) ? ("(" + ProfileString(NativeDamage) + ")") : ""); if (!RemovedPayload.Contains(item)) { RemovedPayload.Add(item); } Object.DestroyImmediate((Object)(object)val); } } public bool Fire(Character targetChar, Vector3 muzzlePos, Vector3 aimDir, Func isValidEnemy, IList ignoreColliders = null, Character ignoreCharacter = null) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) if (!Ready || PoolSize == 0) { return false; } Projectile[] array = Pool(); if (array == null) { return false; } Muzzle.position = muzzlePos; if (((Vector3)(ref aimDir)).sqrMagnitude > 0.0001f) { Muzzle.rotation = Quaternion.LookRotation(aimDir); } ((Shooter)Shooter).OverrideCastPos = Muzzle; _volleyId++; _resolved = false; _exploded = 0; _fired = 0; _resolvedHit = null; _isValidEnemy = isValidEnemy; float[] array2 = new float[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = (((Object)(object)array[i] != (Object)null) ? array[i].m_lastShootTime : 0f); } try { object[] array3 = null; ((Effect)Shooter).ProcessAffectInfos(targetChar, muzzlePos, aimDir, ref array3); ((Effect)Shooter).ActivateLocally(_setupOwner, array3); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] launch threw: " + ex.Message)); return false; } int num = 0; for (int j = 0; j < array.Length; j++) { if (!((Object)(object)array[j] == (Object)null) && RangedSpecial.WasShot(array2[j], array[j].m_lastShootTime)) { BoltHitRelay component = ((Component)array[j]).GetComponent(); if ((Object)(object)component != (Object)null) { component.VolleyId = _volleyId; } num += IgnoreFriendlyCollisions(array[j], ignoreColliders); if ((Object)(object)ignoreCharacter != (Object)null) { array[j].m_ignoredCharacter = ignoreCharacter; } _fired++; } } if (_fired == 0) { CompanionRuntime.Log.LogWarning((object)"[BOLT] activation ran but no pool bolt started flying — nothing launched."); return false; } CompanionRuntime.Log.LogMessage((object)($"[BOLT] launched {_fired} bolt(s) at " + "'" + (((Object)(object)targetChar != (Object)null) ? targetChar.Name : "no-target") + "' from " + ((Vector3)(ref muzzlePos)).ToString("F1") + ".")); if (ignoreColliders != null && ignoreColliders.Count > 0) { CompanionRuntime.Log.LogMessage((object)($"[BOLT] friendly pass-through: {num} collider pair(s) ignored " + $"across {_fired} bolt(s) ({ignoreColliders.Count} friendly collider(s) offered), " + "ignoredChar='" + (((Object)(object)ignoreCharacter != (Object)null) ? ignoreCharacter.Name : "none") + "', " + $"class='{ProjectileTypeName}' hitEnemiesOnly={HitEnemiesOnlyArmed}. " + "(Pairing binds simulation contacts only — a RaycastProjectile is carried by the last two.)")); } return true; } private static int IgnoreFriendlyCollisions(Projectile bolt, IList ignore) { if ((Object)(object)bolt == (Object)null || ignore == null || ignore.Count == 0) { return 0; } int num = 0; Collider[] componentsInChildren; try { componentsInChildren = ((Component)bolt).GetComponentsInChildren(true); } catch { return 0; } Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((Object)(object)val == (Object)null || !val.enabled || !((Component)val).gameObject.activeInHierarchy) { continue; } for (int j = 0; j < ignore.Count; j++) { Collider val2 = ignore[j]; if (!((Object)(object)val2 == (Object)null) && val2 != val && val2.enabled && ((Component)val2).gameObject.activeInHierarchy) { try { Physics.IgnoreCollision(val, val2, true); num++; } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] IgnoreCollision pairing threw: " + ex.Message)); } } } } return num; } internal void HandleExplode(int volleyId, object[] infos) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Invalid comparison between Unknown and I4 if (volleyId != _volleyId) { return; } Character val = (Character)((infos != null && infos.Length != 0) ? /*isinst with value type is only supported in some contexts*/: null); _exploded++; bool flag = (Object)(object)val != (Object)null && (_isValidEnemy == null || _isValidEnemy(val)); ImpactOutcome val2 = RangedSpecial.ResolveImpact(_resolved, (Object)(object)val != (Object)null, flag); if ((int)val2 != 0) { if ((int)val2 == 2) { CompanionRuntime.Log.LogMessage((object)("[BOLT] impact: " + (((Object)(object)val != (Object)null) ? ("invalid target '" + val.Name + "'") : "no character") + " " + $"({_exploded}/{_fired} exploded).")); } } else { _resolved = true; _resolvedHit = val; CompanionRuntime.Log.LogMessage((object)$"[BOLT] impact: hit '{val.Name}' ({_exploded}/{_fired} exploded)."); } } private Projectile[] Pool() { if ((Object)(object)Shooter == (Object)null || ((Effect)Shooter).m_subEffects == null) { return null; } return ((Effect)Shooter).m_subEffects as Projectile[]; } public static string ProfileString(float[] profile) { if (profile == null) { return "none"; } List list = new List(); for (int i = 0; i < profile.Length; i++) { if (profile[i] > 0f) { list.Add($"{(object)(Types)i}={profile[i]:F0}"); } } if (list.Count <= 0) { return "zero"; } return string.Join(" ", list); } } public sealed class BoltHitRelay : MonoBehaviour { public RangedAttackRig Rig; public int VolleyId = -1; private void OnExplodeDone(object[] infos) { Rig?.HandleExplode(VolleyId, infos); } } public static List Census(Character src, out ShootProjectile[] shooters) { return Census(((Object)(object)src != (Object)null) ? ((Component)src).transform : null, ((Object)(object)src != (Object)null) ? src.Name : "?", out shooters); } public static List Census(Transform root, string srcLabel, out ShootProjectile[] shooters) { //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) shooters = (ShootProjectile[])(object)new ShootProjectile[0]; if ((Object)(object)root == (Object)null) { return new List(); } try { shooters = ((Component)root).GetComponentsInChildren(true); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] shooter census threw: " + ex.Message)); return new List(); } List list = new List(shooters.Length); ShootProjectile[] array = shooters; foreach (ShootProjectile val in array) { string text = (((Object)(object)val != (Object)null && (Object)(object)val.BaseProjectile != (Object)null) ? ((Object)val.BaseProjectile).name : null); string text2 = (((Object)(object)val != (Object)null) ? PathOf(((Component)val).transform, root) : "?"); list.Add(new ShooterCandidate(text, text2)); CompanionRuntime.Log.LogMessage((object)("[BOLT] candidate: '" + text2 + "' projectile='" + (text ?? "NONE") + "' " + $"force={val.ProjectileForce:F0} mode={val.TargetingMode} cast={((Shooter)val).CastPosition} " + $"shots={((val.ProjectileShots != null) ? val.ProjectileShots.Length : 0)} muzzleBone='{((Shooter)val).TransformName}'.")); } if (list.Count == 0) { CompanionRuntime.Log.LogMessage((object)("[BOLT] census: '" + srcLabel + "' carries no ShootProjectile.")); } return list; } private static List CensusSkillPrefab(int skillPrefabId, out ShootProjectile[] shooters, out string label) { shooters = (ShootProjectile[])(object)new ShootProjectile[0]; label = $"skill prefab {skillPrefabId}"; Item val = null; try { ResourcesPrefabManager instance = ResourcesPrefabManager.Instance; val = ((instance != null) ? instance.GetItemPrefab(skillPrefabId) : null); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)$"[BOLT] skill-prefab lookup threw for {skillPrefabId}: {ex.Message}"); } if ((Object)(object)val == (Object)null) { CompanionRuntime.Log.LogWarning((object)($"[BOLT] skill-prefab fallback: no Item prefab for ItemID {skillPrefabId} " + "(not registered / wrong id — check the SpeciesSpecialAttacks row's ProjectileSkillId).")); return new List(); } label = $"skill prefab '{val.Name}' ({skillPrefabId})"; return Census(((Component)val).transform, label, out shooters); } public static RangedAttackRig From(Character src, string projectileFilter, Transform inactiveHolder, int skillPrefabId = 0) { //IL_015c: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ed: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)src == (Object)null) { return null; } string label = src.Name; ShootProjectile[] shooters; List list = Census(src, out shooters); int num = RangedSpecial.ChooseShooter((IReadOnlyList)list, projectileFilter); if (num < 0 && skillPrefabId > 0) { CompanionRuntime.Log.LogMessage((object)("[BOLT] '" + label + "': live census had no capturable shooter — " + $"falling back to skill prefab {skillPrefabId} (frozen donor / dormant template).")); list = CensusSkillPrefab(skillPrefabId, out shooters, out label); num = RangedSpecial.ChooseShooter((IReadOnlyList)list, projectileFilter); } if (num < 0) { CompanionRuntime.Log.LogWarning((object)("[BOLT] '" + src.Name + "': no capturable ShootProjectile " + string.Format("({0} candidate(s), filter='{1}', skillPrefab={2}) ", shooters.Length, projectileFilter ?? "none", (skillPrefabId > 0) ? skillPrefabId.ToString() : "none") + "— the species will use the melee special.")); return null; } if (!string.IsNullOrEmpty(projectileFilter) && list[num].ProjectileName != null && list[num].ProjectileName.IndexOf(projectileFilter, StringComparison.OrdinalIgnoreCase) < 0 && list[num].Path.IndexOf(projectileFilter, StringComparison.OrdinalIgnoreCase) < 0) { CompanionRuntime.Log.LogWarning((object)("[BOLT] filter '" + projectileFilter + "' matched nothing — fell back to '" + list[num].ProjectileName + "' (check the table row's ProjectileFilter).")); } return BuildRig(shooters[num], list[num], label, inactiveHolder); } public static RangedAttackRig FromSkillPrefab(int skillItemId, string projectileFilter, Transform inactiveHolder) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) if (skillItemId <= 0) { return null; } ShootProjectile[] shooters; string label; List list = CensusSkillPrefab(skillItemId, out shooters, out label); int num = RangedSpecial.ChooseShooter((IReadOnlyList)list, projectileFilter); if (num < 0) { CompanionRuntime.Log.LogWarning((object)("[BOLT] " + label + ": no capturable ShootProjectile " + string.Format("({0} candidate(s), filter='{1}').", shooters.Length, projectileFilter ?? "none"))); return null; } return BuildRig(shooters[num], list[num], label, inactiveHolder); } private static RangedAttackRig BuildRig(ShootProjectile chosen, ShooterCandidate meta, string srcLabel, Transform inactiveHolder) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_0191: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0219: Expected O, but got Unknown GameObject val = null; try { val = Object.Instantiate(((Component)chosen).gameObject, inactiveHolder); ((Object)val).name = "CK_RangedRig"; Views.Neutralize(val, "ranged rig"); ShootProjectile component = val.GetComponent(); Component[] components = val.GetComponents(); foreach (Component val2 in components) { if (!(val2 is Transform) && (object)val2 != component) { try { Object.DestroyImmediate((Object)(object)val2); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] rig strip threw on " + ((object)val2).GetType().Name + ": " + ex.Message)); } } } List list = null; Component[] components2 = val.GetComponents(); foreach (Component val3 in components2) { if (!((Object)(object)val3 == (Object)null) && !(val3 is Transform) && (object)val3 != component) { (list ?? (list = new List())).Add(((object)val3).GetType().Name); } } if (list != null) { CompanionRuntime.Log.LogWarning((object)($"[BOLT] '{srcLabel}': {list.Count} component(s) survived the rig strip " + "[" + string.Join(", ", list.ToArray()) + "] — DestroyImmediate was REFUSED, not attempted-and-failed: Unity silently refuses it inside animation events, physics trigger/contact callbacks, StateMachineBehaviour callbacks and render callbacks — it logs an error, returns, and never throws, so no try/catch can see it. A live script on the rig can move or destroy it.")); } if ((Object)(object)component == (Object)null || (Object)(object)component.BaseProjectile == (Object)null) { CompanionRuntime.Log.LogWarning((object)("[BOLT] '" + srcLabel + "': the cloned rig lost its shooter/projectile ref — not capturing.")); Object.Destroy((Object)(object)val); return null; } RangedAttackRig rangedAttackRig = new RangedAttackRig { Root = val, Shooter = component, SourcePath = meta.Path, ProjectileName = meta.ProjectileName, Force = component.ProjectileForce, TargetingModeName = ((object)Unsafe.As(ref component.TargetingMode)/*cast due to .constrained prefix*/).ToString(), CastPositionName = ((object)Unsafe.As(ref ((Shooter)component).CastPosition)/*cast due to .constrained prefix*/).ToString(), TransformName = ((Shooter)component).TransformName, ShotCount = ((component.ProjectileShots != null) ? component.ProjectileShots.Length : 0), ProjectileLifespan = component.BaseProjectile.Lifespan }; GameObject val4 = new GameObject("CK_BoltMuzzle"); val4.transform.SetParent(val.transform, false); rangedAttackRig.Muzzle = val4.transform; CompanionRuntime.Log.LogMessage((object)("[BOLT] captured '" + rangedAttackRig.ProjectileName + "' from '" + srcLabel + "' " + $"(source '{rangedAttackRig.SourcePath}', {rangedAttackRig.ShotCount} shot(s), force={rangedAttackRig.Force:F0}, lifespan={rangedAttackRig.ProjectileLifespan:F1}s).")); return rangedAttackRig; } catch (Exception ex2) { CompanionRuntime.Log.LogWarning((object)("[BOLT] capture threw: " + ex2.Message)); if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } return null; } } public static void Attach(RangedAttackRig rig, CompanionBody body) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (rig != null && !((Object)(object)rig.Root == (Object)null) && !((Object)(object)body == (Object)null)) { rig.Root.transform.SetParent(((Component)body).transform, false); rig.Root.transform.localPosition = Vector3.zero; rig.Root.SetActive(true); body.RangedRig = rig; CompanionRuntime.Log.LogMessage((object)$"[BOLT] rig attached to '{body.SpeciesId}' (ready={rig.Ready})."); } } public static void AddColliders(IList into, Component who) { if (into == null || (Object)(object)who == (Object)null) { return; } string name = ((Object)who).name; Collider[] componentsInChildren; try { componentsInChildren = who.GetComponentsInChildren(true); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[BOLT] collider census threw on '" + name + "': " + ex.Message)); return; } Collider[] array = componentsInChildren; foreach (Collider val in array) { if ((Object)(object)val != (Object)null && !into.Contains(val)) { into.Add(val); } } } private static string PathOf(Transform t, Transform root) { string text = ((Object)t).name; Transform parent = t.parent; while ((Object)(object)parent != (Object)null && (Object)(object)parent != (Object)(object)root) { text = ((Object)parent).name + "/" + text; parent = parent.parent; } return text; } } internal sealed class ProxyDrive { private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; private readonly string _ownerUid; private readonly CompanionAnchor _anchor; private Vector3 _lastPos; private float _yawDeg; private float _receivedAt; private float _appliedAt; private float _snapLogAt; internal float WarnAt = -999f; private Status _was; internal ProxyDrive(string ownerUid, CompanionAnchor anchor) { _ownerUid = ownerUid; _anchor = anchor; } internal void OnReceived(float x, float y, float z, float yawDeg, float now) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) _lastPos = new Vector3(x, y, z); _yawDeg = yawDeg; _receivedAt = now; } internal bool IsLive(float now, float staleSeconds) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 if ((int)ProxyDriveState.Decide((double)now, (double)_receivedAt, staleSeconds) == 1) { return (int)ProxyDriveState.Decide((double)now, (double)_appliedAt, staleSeconds) == 1; } return false; } internal string DumpFragment(float now, float staleSeconds) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Invalid comparison between Unknown and I4 Status val = ProxyDriveState.Decide((double)now, (double)_receivedAt, staleSeconds); if ((int)val == 0) { return "drive=none"; } string text = (now - _receivedAt).ToString("F1", Inv); string text2 = ((_appliedAt > 0f) ? ((now - _appliedAt).ToString("F1", Inv) + "s") : "never"); return "drive=" + (((int)val == 1) ? "live" : "stale") + " driveAge=" + text + "s applyAge=" + text2 + " lastPos=(" + _lastPos.x.ToString("F1", Inv) + "," + _lastPos.y.ToString("F1", Inv) + "," + _lastPos.z.ToString("F1", Inv) + ")"; } internal void Tick(Character owner, float now, float dt, float staleSeconds) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Invalid comparison between Unknown and I4 //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Invalid comparison between Unknown and I4 //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Invalid comparison between Unknown and I4 //IL_0151: Unknown result type (might be due to invalid IL or missing references) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020f: Unknown result type (might be due to invalid IL or missing references) //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_022f: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0249: Unknown result type (might be due to invalid IL or missing references) //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01de: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0275: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) Status val = ProxyDriveState.Decide((double)now, (double)_receivedAt, staleSeconds); if (val != _was) { if ((int)val == 1) { CompanionRuntime.Log.LogMessage((object)("[PROXY] drive LIVE for '" + _ownerUid + "' — the guest streams its pet's position (ck.proxy.pos); the anchor stops walking and is placed on the stream (wander follow released). pt=" + PhotonNetwork.time.ToString("F1", Inv))); } else if ((int)val == 2) { CompanionRuntime.Log.LogMessage((object)("[PROXY] drive STALE for '" + _ownerUid + "' — no pos for " + (now - _receivedAt).ToString("F0", Inv) + "s; the anchor reverts to owner-follow (the shipped behavior — an old guest that never streams pos always looks like this). pt=" + PhotonNetwork.time.ToString("F1", Inv))); } _was = val; } if ((int)val == 0 || _anchor == null || !_anchor.HasLiveAnchor) { return; } if ((int)val == 2) { if ((Object)(object)owner != (Object)null && (Object)(object)_anchor.GetFollowTarget() == (Object)null) { _anchor.SetFollowTarget(((Component)owner).transform); } return; } if ((Object)(object)_anchor.GetFollowTarget() != (Object)null) { _anchor.SetFollowTarget(null); } Character current = _anchor.Current; Vector3 position = ((Component)current).transform.position; float num = _yawDeg * ((float)Math.PI / 180f); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(Mathf.Sin(num), 0f, Mathf.Cos(num)); float num2 = default(float); float num3 = default(float); float num4 = default(float); AnchorGlue.WeldPosition(_lastPos.x, _lastPos.y, _lastPos.z, val2.x, val2.z, _anchor.DriveCfg.GlueOffsetBehind, ref num2, ref num3, ref num4); if (!CompanionRuntime.IsSanePosition(position)) { Vector3 pos = default(Vector3); ((Vector3)(ref pos))..ctor(num2, num3, num4); ApplyAndStamp(pos, val2, (AnchorGlueAction)(_anchor.AgentDrivesTransform ? 3 : 2), 0f, now, snapped: true, "current pos insane — stream rescue"); return; } Step val3 = ProxyDriveMotion.Toward(position.x, position.y, position.z, num2, num3, num4, dt, 15f, 3f); Vector3 val4 = default(Vector3); ((Vector3)(ref val4))..ctor(val3.X, val3.Y, val3.Z); float sep = Vector3.Distance(position, val4); AnchorGlueAction act = (AnchorGlueAction)(_anchor.AgentDrivesTransform ? 3 : ((!val3.Snapped) ? 1 : 2)); ApplyAndStamp(val4, val2, act, sep, now, val3.Snapped, "stream jump"); } private void ApplyAndStamp(Vector3 pos, Vector3 facing, AnchorGlueAction act, float sep, float now, bool snapped, string snapWhy) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) _anchor.DriveApply(pos, facing, act, sep, logJump: false); _appliedAt = now; if (snapped && now - _snapLogAt > 2f) { _snapLogAt = now; CompanionRuntime.Log.LogMessage((object)("[PROXY] drive snap for '" + _ownerUid + "': " + ((sep > 0f) ? (sep.ToString("F1", Inv) + "m") : "placed") + " onto the stream (" + snapWhy + "). pt=" + PhotonNetwork.time.ToString("F1", Inv))); } } } internal sealed class ProxyDriveTicker : MonoBehaviour { private void LateUpdate() { ProxyPets.DriveTick(); } } public static class ProxyPets { private sealed class PetProxy { public string OwnerUid; public int OwnerActorId; public string SpeciesKey; public int LoyaltyTier; public string DisplayName = ""; public Companion Bond; public CreatureAttributes LastEff; public float LastMaxHealth; public string LastHitTargetUid; public StateMirror HpMirror; public StateMirror StatusMirror; public string MandateUid; public readonly AttackedThrottle Attacked = new AttackedThrottle(); public string LastAttackedUid; public float LastAttackedAt; public float PosLogAt; public ProxyDrive Drive; public CompanionAnchor Anchor => Bond.Anchor; } private sealed class ProxyBondSettings : ICompanionSettings { private static ICompanionSettings Inner => Host.Settings; public float AttackDamage => Inner.AttackDamage; public float AttackInterval => Inner.AttackInterval; public float AggroRange => Inner.AggroRange; public float AttackRange => Inner.AttackRange; public float CombatLeashDistance => Inner.CombatLeashDistance; public bool AssistOnOwnerHit => Inner.AssistOnOwnerHit; public float OwnerFocusRange => Inner.OwnerFocusRange; public float LeashDistance => Inner.LeashDistance; public float CatchUpSpeed => Inner.CatchUpSpeed; public float DisengageRunHomeSeconds => Inner.DisengageRunHomeSeconds; public bool AttackVocals => Inner.AttackVocals; public float StationRingFraction => Inner.StationRingFraction; public float StationLineAngleDeg => Inner.StationLineAngleDeg; public float StationRestationMeters => Inner.StationRestationMeters; public float StationRestationSeconds => Inner.StationRestationSeconds; public float StationArriveMeters => Inner.StationArriveMeters; public int StationMaxRestations => Inner.StationMaxRestations; public float StationFarMeters => Inner.StationFarMeters; public float StationProgressMeters => Inner.StationProgressMeters; public float StationEnemyFastMetersPerSecond => Inner.StationEnemyFastMetersPerSecond; public bool AnchorInvisible => Inner.AnchorInvisible; public bool AnchorShowHealthBar => Inner.AnchorShowHealthBar; public bool AnchorLinkSummonSlot => Inner.AnchorLinkSummonSlot; public bool AnchorHideSummonIcon => Inner.AnchorHideSummonIcon; public float AnchorLeashDistance => Inner.AnchorLeashDistance; public float AnchorRespawnSeconds => Inner.AnchorRespawnSeconds; public bool AnchorDealsDamage => Inner.AnchorDealsDamage; public float CritHealthFraction => Inner.CritHealthFraction; public float CritRearmFraction => Inner.CritRearmFraction; public bool SpeciesVoice => Inner.SpeciesVoice; public AnchorGlueMode GlueMode => Inner.GlueMode; public float GlueOffsetBehind => Inner.GlueOffsetBehind; public bool UnifyTargets => Inner.UnifyTargets; public string GhostPrefabName => Inner.GhostPrefabName; public AnchorCollisionMode AnchorPlayerCollision => Inner.AnchorPlayerCollision; public bool AnchorEnabled => Inner.AnchorEnabled; public bool SuppressLeashWarp => Inner.SuppressLeashWarp; public BodilessAnchorPolicy BodilessAnchor => (BodilessAnchorPolicy)1; public float ModelYawOffset => Inner.ModelYawOffset; public bool SlopeTiltEnabled => Inner.SlopeTiltEnabled; public float LoafDistanceMin => Inner.LoafDistanceMin; public float LoafDistanceMax => Inner.LoafDistanceMax; public float LoafRepickDistance => Inner.LoafRepickDistance; public string LogTagSuffix => Inner.LogTagSuffix; } public struct ProxyInfo { public string OwnerUid; public int OwnerActorId; public string SpeciesKey; public int LoyaltyTier; } private sealed class GuestMirror : ICompanionNetMirror { private readonly Func _owner; private readonly StateMirror _target; private readonly StateMirror _stance; private readonly StateMirror _pos; private readonly ProxyPosPacing _posPacing = new ProxyPosPacing(); private Character _targetWanted; private bool _stanceWanted; private Vector3 _posWanted; private float _posYawWanted; private bool _hasPos; public GuestMirror(Func owner) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Expected O, but got Unknown //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Expected O, but got Unknown _owner = owner ?? ((Func)(() => CompanionRuntime.LocalPlayer())); _target = NetBus.Mirror("ck.proxy.target", (MirrorTarget)0, delegate { //IL_0034: Unknown result type (might be due to invalid IL or missing references) if (!PhotonNetwork.isNonMasterClientInRoom || (Object)(object)_owner() == (Object)null) { return (string)null; } Character targetWanted = _targetWanted; if ((Object)(object)targetWanted == (Object)null) { return ""; } try { return UID.op_Implicit(targetWanted.UID); } catch { return (string)null; } }, new MirrorOptions { Extra = OwnerUidExtra }); _stance = NetBus.Mirror("ck.proxy.stance", (MirrorTarget)0, () => (!PhotonNetwork.isNonMasterClientInRoom || !((Object)(object)_owner() != (Object)null)) ? null : NetProtocol.BuildStance(_stanceWanted), new MirrorOptions { Extra = OwnerUidExtra }); float num = default(float); float num2 = default(float); float num3 = default(float); float num4 = default(float); _pos = NetBus.Mirror("ck.proxy.pos", (MirrorTarget)0, () => (!PhotonNetwork.isNonMasterClientInRoom || !((Object)(object)_owner() != (Object)null) || !_hasPos || !CompanionRuntime.IsSanePosition(_posWanted)) ? null : NetProtocol.BuildPos(_posWanted.x, _posWanted.y, _posWanted.z, _posYawWanted), new MirrorOptions { Quantize = (string payload) => (!NetProtocol.TryParsePos(payload, ref num, ref num2, ref num3, ref num4)) ? payload : _posPacing.DeltaKey(num, num2, num3, num4, 0.25f, 5f), Extra = OwnerUidExtra, ResendSeconds = 4f }); } private string OwnerUidExtra() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Character val = _owner(); if (val == null) { return ""; } return UID.op_Implicit(val.UID); } public bool SyncTarget(Character target) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 _targetWanted = target; return (int)_target.Tick(0) == 1; } public bool SyncStance(bool passive) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Invalid comparison between Unknown and I4 _stanceWanted = passive; return (int)_stance.Tick(0) == 1; } public void ReportHit(Character target) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Character val = _owner(); if ((Object)(object)val != (Object)null) { RequestHit(UID.op_Implicit(val.UID), target); } } public bool ReportSwing(int type) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Character val = _owner(); if ((Object)(object)val != (Object)null) { return RequestSwing(UID.op_Implicit(val.UID), type); } return false; } public void SyncPosition(Vector3 position, float yawDeg) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) _posWanted = position; _posYawWanted = yawDeg; _hasPos = true; double num = Time.unscaledTime; if (_posPacing.IntervalElapsed(num, 0.2f)) { _posPacing.OnAttempt(num); _pos.Tick(0); } } public void InvalidateTarget() { _target.Invalidate(); } public void InvalidateStance() { _stance.Invalidate(); } public void InvalidatePosition() { _pos.Invalidate(); } } private static CompanionHost s_host; public const string AnnounceVerb = "ck.proxy.announce"; public const string StatsVerb = "ck.proxy.stats"; public const string ReleaseVerb = "ck.proxy.release"; public const string DiedVerb = "ck.proxy.died"; public const string CritVerb = "ck.proxy.crit"; public const string CalmedVerb = "ck.proxy.calmed"; public const string HitVerb = "ck.proxy.hit"; public const string TargetVerb = "ck.proxy.target"; public const string SetHealthVerb = "ck.proxy.sethealth"; public const string PinnedVerb = "ck.proxy.pinned"; public const string HealthVerb = "ck.proxy.hp"; public const string HealthClearVerb = "ck.proxy.hpclear"; public const string StanceVerb = "ck.proxy.stance"; public const string RecallVerb = "ck.proxy.recall"; public const string SwingVerb = "ck.proxy.swing"; public const string AttackedVerb = "ck.proxy.attacked"; public const string PetFxVerb = "ck.proxy.petfx"; public const string PetFxCastVerb = "ck.proxy.petfx.cast"; public const string PlayerFxCastVerb = "ck.proxy.playerfx.cast"; public const string PosVerb = "ck.proxy.pos"; public const string AnchorStatusVerb = "ck.proxy.status"; private const float TickSeconds = 2f; private const float AttackedThrottleSeconds = 3f; private const float OwnerMissingTeardownSeconds = 45f; private const float AnnounceRefreshSeconds = 30f; private static readonly CultureInfo Inv = CultureInfo.InvariantCulture; private const float PosLogSeconds = 60f; private static readonly Dictionary _proxies = new Dictionary(); private static ReplicatedStore _store; private static float _nextTickAt; private static readonly HashSet _refusalLogged = new HashSet(StringComparer.Ordinal); internal static CompanionHost Host => s_host ?? CompanionHost.LocalDefault; private static float PosStaleSeconds { get { float num = ((CkConfig.Proxy.PosStaleSeconds != null) ? CkConfig.Proxy.PosStaleSeconds.Value : 10f); float num2 = 10f; if (!(num < num2)) { return num; } return num2; } } public static int Count => _proxies.Count; public static void Configure(CompanionHost host) { s_host = host; } private static string Fmt(Vector3 v) { return "(" + v.x.ToString("F1", Inv) + "," + v.y.ToString("F1", Inv) + "," + v.z.ToString("F1", Inv) + ")"; } private static string PositionFragment(PetProxy p, Character owner) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Unknown result type (might be due to invalid IL or missing references) bool flag = p.Anchor != null && p.Anchor.HasLiveAnchor; string text = (flag ? Fmt(((Component)p.Anchor.Current).transform.position) : "none"); string text2 = (((Object)(object)owner != (Object)null) ? Fmt(((Component)owner).transform.position) : "none"); string text3 = ((flag && (Object)(object)owner != (Object)null) ? Vector3.Distance(((Component)p.Anchor.Current).transform.position, ((Component)owner).transform.position).ToString("F1", Inv) : "n/a"); CharacterAI val = (flag ? p.Anchor.AI : null); string text4 = (((Object)(object)val != (Object)null && (Object)(object)val.CurrentAiState != (Object)null) ? ((object)val.CurrentAiState).GetType().Name : "?"); string text5 = ((p.Drive != null) ? p.Drive.DumpFragment(Time.unscaledTime, PosStaleSeconds) : "drive=none"); return "anchor=" + text + " owner=" + text2 + " dist=" + text3 + " ai=" + text4 + " " + text5 + " pt=" + PhotonNetwork.time.ToString("F1", Inv); } public static string DriveFragment(string ownerUid) { if (!_proxies.TryGetValue(ownerUid ?? "", out var value)) { return null; } string text = ((value.Drive != null) ? value.Drive.DumpFragment(Time.unscaledTime, PosStaleSeconds) : "drive=none"); return "proxy " + text + " pt=" + PhotonNetwork.time.ToString("F1", Inv); } internal static void DriveTick() { if (_proxies.Count == 0 || PhotonNetwork.isNonMasterClientInRoom) { return; } float unscaledTime = Time.unscaledTime; float deltaTime = Time.deltaTime; float posStaleSeconds = PosStaleSeconds; foreach (KeyValuePair proxy in _proxies) { PetProxy value = proxy.Value; if (value.Drive == null) { continue; } Character owner = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(value.OwnerUid) : null); try { value.Drive.Tick(owner, unscaledTime, deltaTime, posStaleSeconds); } catch (Exception ex) { if (unscaledTime - value.Drive.WarnAt > 10f) { value.Drive.WarnAt = unscaledTime; CompanionRuntime.Log.LogWarning((object)("[PROXY] drive tick for '" + value.OwnerUid + "' threw: " + ex.Message + " (throttled 10s/pet; the stale-apply fallback reverts this row to owner-follow)")); } } } } internal static void Init() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Expected O, but got Unknown StoreOptions val = new StoreOptions(); val.Authority = (StoreAuthority)0; val.ResolveUidOwner = ResolveUidOwner; val.RefreshSeconds = 30f; val.FlushOnPeerReady = false; val.ClearOnOwnerLost = true; val.OwnerMissingSeconds = 45f; val.ClearOnRoomChange = true; val.Verbs = new StoreVerbs { Announce = "ck.proxy.announce", Release = "ck.proxy.release" }; _store = NetBus.RegisterStore("proxy", val); _store.OnSet += OnRecordSet; _store.OnCleared += OnRecordCleared; NetBus.Register("ck.proxy.stats", OnStats, (HandlerRole)1); NetBus.Register("ck.proxy.hit", OnHit, (HandlerRole)1); NetBus.Register("ck.proxy.target", OnTarget, (HandlerRole)1); NetBus.Register("ck.proxy.sethealth", OnSetHealth, (HandlerRole)1); NetBus.Register("ck.proxy.stance", OnStance, (HandlerRole)1); NetBus.Register("ck.proxy.recall", OnRecall, (HandlerRole)1); NetBus.Register("ck.proxy.swing", OnSwing, (HandlerRole)1); NetBus.Register("ck.proxy.petfx", OnPetFx, (HandlerRole)1); NetBus.Register("ck.proxy.pos", OnPos, (HandlerRole)1); Net.OnRoomChanged += delegate { _refusalLogged.Clear(); }; } public static bool TryGetAnchorCharacter(string ownerUid, out Character anchor) { anchor = null; if (!_proxies.TryGetValue(ownerUid ?? "", out var value) || value.Anchor == null || !value.Anchor.HasLiveAnchor) { return false; } anchor = value.Anchor.Current; return true; } public static bool TryGetProxyInfo(string ownerUid, out ProxyInfo info) { info = default(ProxyInfo); if (!_proxies.TryGetValue(ownerUid ?? "", out var value)) { return false; } info = InfoOf(value); return true; } public static List ProxyInfos() { List list = new List(_proxies.Count); foreach (KeyValuePair proxy in _proxies) { list.Add(InfoOf(proxy.Value)); } return list; } private static ProxyInfo InfoOf(PetProxy p) { return new ProxyInfo { OwnerUid = p.OwnerUid, OwnerActorId = p.OwnerActorId, SpeciesKey = p.SpeciesKey, LoyaltyTier = p.LoyaltyTier }; } public static bool AuthorizeSender(string verb, NetBus.NetMessage msg) { PetProxy p; return Authorized(verb, msg, out p); } internal static string AuthorizePetOwner(NetBus.NetMessage msg) { RecordRow val = default(RecordRow); if (_store == null || !_store.TryGet(msg.OwnerUid ?? "", ref val) || !_proxies.ContainsKey(msg.OwnerUid ?? "")) { return "no-row"; } if (msg.SenderActorId != val.ActorId) { CompanionRuntime.Log.LogWarning((object)("[PROXY] [G→M] '" + msg.Verb + "' for '" + msg.OwnerUid + "' REFUSED — sender actor " + $"{msg.SenderActorId} is not the bound owner actor {val.ActorId}.")); return "wrong-sender"; } return null; } public static string AuthorizePlayerOwner(NetBus.NetMessage msg) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Invalid comparison between Unknown and I4 OwnerResolution val = ResolvePlayerUidOwner(msg.OwnerUid, msg.SenderActorId); if ((int)val == 0) { return "no-replica"; } if ((int)val != 1) { return "not-owner"; } return null; } private static bool Authorized(string verb, NetBus.NetMessage msg, out PetProxy p) { p = null; RecordRow val = default(RecordRow); if (_store == null || !_store.TryGet(msg.OwnerUid ?? "", ref val) || !_proxies.TryGetValue(msg.OwnerUid ?? "", out p)) { NetBus.CountDrop(verb, "no-row"); return false; } if (msg.SenderActorId != val.ActorId) { NetBus.CountDrop(verb, "wrong-sender"); if (!string.Equals(verb, "ck.proxy.swing", StringComparison.Ordinal) && !string.Equals(verb, "ck.proxy.pos", StringComparison.Ordinal)) { CompanionRuntime.Log.LogWarning((object)("[PROXY] [G→M] '" + verb + "' for '" + msg.OwnerUid + "' REFUSED — sender actor " + $"{msg.SenderActorId} is not the bound owner actor {val.ActorId}.")); } return false; } return true; } private static bool GuestSend(string verb, string ownerUid, string payload) { if (PhotonNetwork.isNonMasterClientInRoom && !string.IsNullOrEmpty(ownerUid)) { return NetBus.SendToMaster(verb, ownerUid, payload); } return false; } public static void RequestHit(string ownerUid, Character target) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target != (Object)null) { GuestSend("ck.proxy.hit", ownerUid, UID.op_Implicit(target.UID)); } } public static bool RequestRecall(string ownerUid) { return GuestSend("ck.proxy.recall", ownerUid, ""); } public static bool RequestSwing(string ownerUid, int type) { return GuestSend("ck.proxy.swing", ownerUid, NetProtocol.BuildSwing(type)); } internal static bool ReportFx(string ownerUid, string spellKey, int slot, bool active) { return GuestSend("ck.proxy.petfx", ownerUid, NetProtocol.BuildProxyPetFx(spellKey, slot, active)); } public static bool RequestHealFull(string ownerUid) { return GuestSend("ck.proxy.sethealth", ownerUid, "full"); } public static bool RequestDrain(string ownerUid, float amount) { return GuestSend("ck.proxy.sethealth", ownerUid, NetProtocol.BuildDrain(amount)); } public static bool RequestSetHealth(string ownerUid, float value, bool isPercent) { return GuestSend("ck.proxy.sethealth", ownerUid, isPercent ? NetProtocol.BuildSetHealthPercent(value) : NetProtocol.BuildSetHealthValue(value)); } public static ICompanionNetMirror NewGuestMirror(Func owner) { return new GuestMirror(owner); } private static OwnerResolution ResolveUidOwner(string ownerUid, int slot, int senderActor) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) return ResolveUidOwner(ownerUid, slot, senderActor, null); } private static OwnerResolution ResolveUidOwner(string ownerUid, int slot, int senderActor, string tag) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: Unknown result type (might be due to invalid IL or missing references) Character val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(ownerUid) : null); if ((Object)(object)val == (Object)null) { return (OwnerResolution)0; } PlayerSystem ownerPlayerSys = val.OwnerPlayerSys; if ((Object)(object)ownerPlayerSys == (Object)null || ownerPlayerSys.PhotonOwner == null) { return RefuseRung(ownerUid, "not-a-player-uid", tag, $"[PROXY] owner binding for '{ownerUid}' refused — not a player character (actor {senderActor})."); } if (ownerPlayerSys.IsLocalPlayer) { return RefuseRung(ownerUid, "local-player-uid", tag, "[PROXY] owner binding for '" + ownerUid + "' refused — that uid belongs to a LOCAL player on this " + $"machine (actor {senderActor} tried to claim the master's own character)."); } if (ownerPlayerSys.PhotonOwner.ID != senderActor) { return RefuseRung(ownerUid, "not-replica-owner", tag, "[PROXY] owner binding for '" + ownerUid + "' refused — its replica is owned by actor " + $"{ownerPlayerSys.PhotonOwner.ID}, not actor {senderActor}."); } return (OwnerResolution)1; } public static OwnerResolution ResolvePlayerUidOwner(string uid, int senderActor, string tag = "playerfx") { //IL_000c: Unknown result type (might be due to invalid IL or missing references) if (!string.IsNullOrEmpty(uid)) { return ResolveUidOwner(uid, 0, senderActor, tag); } return (OwnerResolution)0; } private static OwnerResolution RefuseRung(string uid, string rung, string tag, string line) { string item = ((tag == null) ? ("bind|" + rung + "|" + uid) : ("bind|" + tag + "|" + rung + "|" + uid)); if (_refusalLogged.Add(item)) { CompanionRuntime.Log.LogWarning((object)(line + " (warned once per uid per rung; the store counts further refusals)")); } return (OwnerResolution)2; } private static void OnRecordSet(string key, string payload, RecordMeta meta) { //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected O, but got Unknown //IL_010a: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Expected O, but got Unknown //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) string ownerUid = default(string); int num = default(int); RecordKey.TryParse(key, ref ownerUid, ref num); if (string.IsNullOrEmpty(ownerUid)) { return; } string text = default(string); int num2 = default(int); double num3 = default(double); string displayName = default(string); NetProtocol.ParseAnnounce(payload, ref text, ref num2, ref num3, ref displayName); if (!_proxies.TryGetValue(ownerUid, out var value)) { value = new PetProxy { OwnerUid = ownerUid, OwnerActorId = meta.SenderActor }; value.Bond = new Companion(Host, new ProxyBondSettings()); PetProxy row = value; value.Bond.CombatMandate = () => ValidateInstruction(row); PetProxy petProxy = value; Func build = () => BuildHealthPayload(row); MirrorOptions val = new MirrorOptions(); val.Quantize = QuantizeHealth; val.Extra = () => row.OwnerUid; petProxy.HpMirror = NetBus.Mirror("ck.proxy.hp", (MirrorTarget)2, build, val); value.StatusMirror = NetBus.Mirror("ck.proxy.status", (MirrorTarget)2, () => BuildStatusListPayload(row), new MirrorOptions { Extra = () => row.OwnerUid }); value.Anchor.EnableHealthPersistence(() => true); value.Anchor.SeedHealthFraction((float)num3); value.Drive = new ProxyDrive(ownerUid, value.Anchor); value.Anchor.ExternallyDriven = () => row.Drive != null && row.Drive.IsLive(Time.unscaledTime, PosStaleSeconds); value.Anchor.OnAnchorDeath = delegate { SendToOwner(ownerUid, "ck.proxy.died"); }; value.Anchor.OnAnchorCriticallyHurt = delegate { SendToOwner(ownerUid, "ck.proxy.crit"); }; value.Anchor.OnCombatEnded = delegate { SendToOwner(ownerUid, "ck.proxy.calmed"); }; _proxies[ownerUid] = value; CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] guest pet announced (owner '" + ownerUid + "', actor " + $"{meta.SenderActor}, species '{text}', tier {num2}, hp {num3 * 100.0:F0}%) — anchor will " + "spawn beside the owner's replica on the next tick (first vitals apply restores that fraction).")); } else if (meta.IsRebind) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] rebinding '" + ownerUid + "''s proxy to actor " + $"{meta.SenderActor} (previous owner actor {value.OwnerActorId} left the room — reconnect).")); value.OwnerActorId = meta.SenderActor; StateMirror hpMirror = value.HpMirror; if (hpMirror != null) { hpMirror.Invalidate(); } StateMirror statusMirror = value.StatusMirror; if (statusMirror != null) { statusMirror.Invalidate(); } } value.SpeciesKey = text; value.LoyaltyTier = num2; value.DisplayName = displayName; CompanionEffigy.MasterOnProxyAnnounce(ownerUid, text, num2, displayName); CompanionEffigy.MasterOnStance(ownerUid, value.Bond.Stance.Passive); } private static void OnRecordCleared(string key, string reason, RecordMeta meta) { string text = default(string); int num = default(int); RecordKey.TryParse(key, ref text, ref num); TeardownRow(text ?? "", reason); } private static void TeardownRow(string ownerUid, string reason) { if (_proxies.TryGetValue(ownerUid, out var value)) { SendToOwner(ownerUid, "ck.proxy.hpclear"); _proxies.Remove(ownerUid); value.Anchor.ExternallyDriven = null; value.Drive = null; try { value.Bond.Despawn(); } catch { } CompanionRuntime.Log.LogMessage((object)("[PROXY] guest pet proxy for '" + ownerUid + "' torn down (" + reason + ").")); CompanionEffigy.MasterOnProxyTeardown(ownerUid, reason); CompanionPetFx.MasterOnProxyTeardown(ownerUid, reason); } } private static void OnStats(NetBus.NetMessage msg) { if (Authorized("ck.proxy.stats", msg, out var p)) { float lastMaxHealth = default(float); string text = default(string); if (!NetProtocol.TryParseStats(msg.Payload, ref lastMaxHealth, ref text)) { NetBus.CountDrop("ck.proxy.stats", "unparseable"); return; } p.LastMaxHealth = lastMaxHealth; p.LastEff = ((text.Length > 0) ? CreatureAttributes.Parse(text) : null); ApplyStats(p); } } private static void OnHit(NetBus.NetMessage msg) { //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) if (!Authorized("ck.proxy.hit", msg, out var p)) { return; } if (p.Bond.Stance.Passive) { NetBus.CountDrop("ck.proxy.hit", "stance-passive"); return; } if (p.Anchor == null || !p.Anchor.HasLiveAnchor) { NetBus.CountDrop("ck.proxy.hit", "no-anchor"); return; } Character val = CompanionRuntime.FindCharacter(msg.Payload); if ((Object)(object)val == (Object)null) { NetBus.CountDrop("ck.proxy.hit", "target-unknown"); } else if (!val.Alive) { NetBus.CountDrop("ck.proxy.hit", "target-dead"); } else if (ValidCombatTarget("ck.proxy.hit", msg.OwnerUid, val)) { if (!string.Equals(p.LastHitTargetUid, UID.op_Implicit(val.UID), StringComparison.Ordinal)) { p.LastHitTargetUid = UID.op_Implicit(val.UID); CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] aggro replay for '" + msg.OwnerUid + "': first hit vs '" + val.Name + "' — CharHurt runs with the proxy anchor as attacker.")); } ((Component)val).SendMessage("CharHurt", (object)p.Anchor.Current, (SendMessageOptions)1); } } private static void OnTarget(NetBus.NetMessage msg) { //IL_0125: Unknown result type (might be due to invalid IL or missing references) if (!Authorized("ck.proxy.target", msg, out var p)) { return; } if (string.IsNullOrEmpty(msg.Payload)) { if (p.MandateUid != null) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] calm for '" + msg.OwnerUid + "' — instructed lock cleared.")); } p.Bond.Stance.DropCommanded(); p.MandateUid = null; p.Anchor.Calm(); return; } if (p.Bond.Stance.Passive) { NetBus.CountDrop("ck.proxy.target", "stance-passive"); return; } Character val = CompanionRuntime.FindCharacter(msg.Payload); if ((Object)(object)val != (Object)null && val.Alive) { if (ValidCombatTarget("ck.proxy.target", msg.OwnerUid, val)) { if (p.Bond.Stance.CommandedTarget != val) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] instructed target for '" + msg.OwnerUid + "' → '" + val.Name + "' — the anchor fights with a guest mandate.")); } p.Bond.Stance.CommandEngage(val); try { p.MandateUid = UID.op_Implicit(val.UID); } catch { p.MandateUid = null; } p.Anchor.UnifyLock(val); } } else { NetBus.CountDrop("ck.proxy.target", "no-target"); } } private static bool ValidCombatTarget(string verb, string ownerUid, Character target) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Invalid comparison between Unknown and I4 //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) string text = null; try { if ((int)target.Faction == 1) { text = "Player-faction (a player or an ally)"; } else if (CompanionAnchor.IsAnchor(target)) { text = "a live companion anchor on this machine"; } else if (AnchorSentinel.IsAnchorUid(UID.op_Implicit(target.UID))) { text = "a companion-anchor sentinel UID"; } } catch (Exception ex) { text = "unreadable (" + ex.GetType().Name + ")"; } if (text == null) { return true; } NetBus.CountDrop(verb, "target-refused"); if (_refusalLogged.Add(verb + "|" + UID.op_Implicit(target.UID))) { CompanionRuntime.Log.LogWarning((object)("[PROXY] [G→M] '" + verb + "' for '" + ownerUid + "' REFUSED — target '" + target.Name + "' is " + text + "; a guest may not point the proxy anchor at it. (warned once per target per verb; further refusals counted silently)")); } return false; } private static void OnStance(NetBus.NetMessage msg) { if (!Authorized("ck.proxy.stance", msg, out var p)) { return; } bool flag = NetProtocol.ParseStancePassive(msg.Payload); if (p.Bond.Stance.Passive != flag) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] stance=" + (flag ? "passive" : "engaged") + " for '" + msg.OwnerUid + "'" + (flag ? " — calming the proxied anchor; it will not fight while passive." : " — instructed combat participation resumes."))); if (flag) { p.Bond.Stance.CommandDisengage(); p.MandateUid = null; p.Anchor?.Calm(); } else { p.Bond.Stance.CommandEngage(null); } } CompanionEffigy.MasterOnStance(msg.OwnerUid, flag); } private static void OnRecall(NetBus.NetMessage msg) { if (!Authorized("ck.proxy.recall", msg, out var p)) { return; } if (p.Anchor == null || !p.Anchor.HasLiveAnchor) { NetBus.CountDrop("ck.proxy.recall", "no-anchor"); return; } Character val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(msg.OwnerUid ?? "") : null); if ((Object)(object)val == (Object)null) { NetBus.CountDrop("ck.proxy.recall", "no-owner"); return; } p.Bond.Stance.DropCommanded(); p.MandateUid = null; if (p.Anchor.Recall(val, "guest recall")) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] recall for '" + msg.OwnerUid + "' — the proxied anchor is calmed, un-instructed and placed at the owner's replica.")); } else { NetBus.CountDrop("ck.proxy.recall", "throttled-move"); } } private static void OnSwing(NetBus.NetMessage msg) { if (Authorized("ck.proxy.swing", msg, out var _)) { int type = default(int); if (!NetProtocol.TryParseSwingType(msg.Payload, ref type)) { NetBus.CountDrop("ck.proxy.swing", "unparseable"); } else { CompanionEffigy.MasterOnSwing(msg.OwnerUid, type); } } } private static void OnPetFx(NetBus.NetMessage msg) { if (Authorized("ck.proxy.petfx", msg, out var _)) { string spellKey = default(string); int slot = default(int); bool active = default(bool); if (!NetProtocol.TryParseProxyPetFx(msg.Payload, ref spellKey, ref slot, ref active)) { NetBus.CountDrop("ck.proxy.petfx", "unparseable"); } else { CompanionPetFx.MasterOnProxyFx(msg.OwnerUid, spellKey, slot, active); } } } private static void OnPos(NetBus.NetMessage msg) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 if (Authorized("ck.proxy.pos", msg, out var p)) { float x = default(float); float y = default(float); float z = default(float); float yawDeg = default(float); PosParseError val = default(PosParseError); if (!NetProtocol.TryParsePos(msg.Payload, ref x, ref y, ref z, ref yawDeg, ref val)) { NetBus.CountDrop("ck.proxy.pos", ((int)val == 2) ? "insane-pos" : "unparseable"); } else { p.Drive?.OnReceived(x, y, z, yawDeg, Time.unscaledTime); } } } private static void OnSetHealth(NetBus.NetMessage msg) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Invalid comparison between Unknown and I4 if (!Authorized("ck.proxy.sethealth", msg, out var p)) { return; } SetHealthKind val = default(SetHealthKind); float num = default(float); bool flag = default(bool); if (p.Anchor == null || !p.Anchor.HasLiveAnchor) { NetBus.CountDrop("ck.proxy.sethealth", "no-anchor"); } else if (!NetProtocol.TryParseSetHealth(msg.Payload, ref val, ref num, ref flag)) { NetBus.CountDrop("ck.proxy.sethealth", "unparseable"); } else if ((int)val == 0) { p.Anchor.Heal(); CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] healed '" + msg.OwnerUid + "''s anchor to full (guest Heal Pet).")); } else if ((int)val == 2) { if (!p.Anchor.TryGetHealth(out var current, out var max) || max <= 0f) { NetBus.CountDrop("ck.proxy.sethealth", "no-max"); return; } float num2 = (flag ? (max * num / 100f) : num); if (num2 < 1f) { num2 = 1f; } if (num2 > max) { num2 = max; } bool flag2 = p.Anchor.SetHealth(num2); CompanionRuntime.Log.LogMessage((object)("[PROXY] [G→M] sethp for '" + msg.OwnerUid + "''s anchor: " + $"{current:F0} -> {(flag2 ? num2 : current):F0}/{max:F0}" + (flag ? $" (guest asked {num:F0}%)" : " (guest asked an absolute value)") + (flag2 ? "." : " — SET FAILED (anchor died mid-call?)."))); } else if (p.Anchor.ApplyTemperatureDrain(num)) { SendToOwner(msg.OwnerUid, "ck.proxy.pinned"); } } internal static void Tick(MonoBehaviour host) { //IL_011a: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Unknown result type (might be due to invalid IL or missing references) if (_proxies.Count == 0 || Time.unscaledTime < _nextTickAt) { return; } _nextTickAt = Time.unscaledTime + 2f; if (PhotonNetwork.isNonMasterClientInRoom) { return; } foreach (KeyValuePair proxy in _proxies) { PetProxy value = proxy.Value; Character val = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(value.OwnerUid) : null); if (!((Object)(object)val == (Object)null)) { if (Time.unscaledTime - value.PosLogAt > 60f) { value.PosLogAt = Time.unscaledTime; CompanionRuntime.Log.LogMessage((object)("[PROXY] pos owner='" + value.OwnerUid + "' " + PositionFragment(value, val))); } try { value.Bond.Tick(val, host); } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[PROXY] anchor upkeep for '" + value.OwnerUid + "' threw: " + ex.Message)); } EnsureAttackReporter(value); ApplyStats(value); StateMirror hpMirror = value.HpMirror; if (hpMirror != null) { hpMirror.Tick(value.OwnerActorId); } StateMirror statusMirror = value.StatusMirror; if (statusMirror != null) { statusMirror.Tick(value.OwnerActorId); } } } } private static bool ValidateInstruction(PetProxy p) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) Character commandedTarget = p.Bond.Stance.CommandedTarget; if ((Object)(object)commandedTarget == (Object)null) { if (p.MandateUid != null) { p.MandateUid = null; CompanionRuntime.Log.LogMessage((object)("[PROXY] instructed lock for '" + p.OwnerUid + "' dropped (target dead/destroyed) — the anchor re-calms un-instructed.")); } return false; } bool flag = p.Anchor != null && p.Anchor.HasLiveAnchor; float num = (flag ? Vector3.Distance(((Component)p.Anchor.Current).transform.position, ((Component)commandedTarget).transform.position) : 0f); float num2 = (Host?.Settings)?.CombatLeashDistance ?? 40f; if (CombatSweep.ShouldDrop(true, num, num2)) { p.Bond.Stance.DropCommanded(); p.MandateUid = null; CompanionRuntime.Log.LogMessage((object)("[PROXY] instructed lock for '" + p.OwnerUid + "' dropped " + $"(beyond-leash stale at {num:F0}m) — the anchor re-calms un-instructed.")); return false; } if (flag) { p.Anchor.UnifyLock(commandedTarget); } return true; } private static void EnsureAttackReporter(PetProxy p) { if (p.Anchor != null && p.Anchor.HasLiveAnchor) { GameObject gameObject = ((Component)p.Anchor.Current).gameObject; ProxyAttackReporter proxyAttackReporter = gameObject.GetComponent(); if ((Object)(object)proxyAttackReporter == (Object)null) { proxyAttackReporter = gameObject.AddComponent(); } proxyAttackReporter.OwnerUid = p.OwnerUid; } } internal static void OnProxyAnchorAttacked(string ownerUid, Character attacker) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Invalid comparison between Unknown and I4 if (PhotonNetwork.isNonMasterClientInRoom || (Object)(object)attacker == (Object)null || !_proxies.TryGetValue(ownerUid ?? "", out var value)) { return; } bool flag; try { flag = attacker.Alive && attacker.IsAI && (int)attacker.Faction != 1 && !CompanionAnchor.IsAnchor(attacker); } catch { flag = false; } if (!flag) { return; } string text = UID.op_Implicit(attacker.UID); if (!AttackedReport.SuppressedByInstruction(text, value.MandateUid) && value.Attacked.ShouldSend(text, (double)Time.unscaledTime, 3f)) { value.LastAttackedUid = text; value.LastAttackedAt = Time.unscaledTime; if (SendToOwner(ownerUid, "ck.proxy.attacked", text)) { CompanionRuntime.Log.LogMessage((object)("[PROXY] [M→G] anchor attacked by '" + attacker.Name + "' — reported to " + $"owner '{ownerUid}' (throttle {3f:F0}s/attacker).")); } } } private static void ApplyStats(PetProxy p) { if (p.Anchor == null || !p.Anchor.HasLiveAnchor) { return; } try { p.Anchor.ApplyCreatureStats(p.LastEff); if (p.LastMaxHealth > 0f) { p.Anchor.ApplyVitals(p.LastMaxHealth); } } catch (Exception ex) { CompanionRuntime.Log.LogWarning((object)("[PROXY] stat apply for '" + p.OwnerUid + "' threw: " + ex.Message)); } } private static string BuildHealthPayload(PetProxy p) { if (p.Anchor == null || !p.Anchor.HasLiveAnchor || !p.Anchor.TryGetHealth(out var current, out var max) || max <= 0f) { return null; } return NetProtocol.BuildHealth(current, max); } private static string BuildStatusListPayload(PetProxy p) { if (p.Anchor == null || !p.Anchor.HasLiveAnchor) { return NetProtocol.BuildStatusList((IEnumerable)null); } try { StatusEffectManager statusEffectMngr = p.Anchor.Current.StatusEffectMngr; if ((Object)(object)statusEffectMngr == (Object)null || statusEffectMngr.Statuses == null) { return null; } List list = new List(); foreach (StatusEffect status in statusEffectMngr.Statuses) { if ((Object)(object)status != (Object)null) { list.Add(status.IdentifierName); } } return NetProtocol.BuildStatusList((IEnumerable)list); } catch { return null; } } private static string QuantizeHealth(string payload) { float num = default(float); float num2 = default(float); if (!NetProtocol.TryParseHealth(payload, ref num, ref num2)) { return payload; } return Mathf.RoundToInt(num).ToString(Inv) + "\t" + Mathf.RoundToInt(num2).ToString(Inv); } internal static string DevKill(string ownerUid) { if (string.IsNullOrEmpty(ownerUid)) { return "[PROXY] usage: proxykill — current rows:\n" + Dump(); } if (!_proxies.ContainsKey(ownerUid)) { return "[PROXY] no proxy row for '" + ownerUid + "' — nothing to kill.\n" + Dump(); } if (_store == null || !_store.ClearLocal(ownerUid, "dev proxykill — watch for the guest's re-announce resurrecting it")) { TeardownRow(ownerUid, "dev proxykill — watch for the guest's re-announce resurrecting it"); } return "[PROXY] killed '" + ownerUid + "''s proxy — the guest's periodic re-announce (~30s) should now rebuild it."; } private static bool SendToOwner(string ownerUid, string verb, string payload = "") { if (!_proxies.TryGetValue(ownerUid, out var value)) { return false; } return NetBus.SendToActor(value.OwnerActorId, verb, ownerUid, payload); } public static string Dump() { StringBuilder stringBuilder = new StringBuilder(); if (_proxies.Count == 0) { stringBuilder.AppendLine("[PROXY] no guest pet proxies."); } else { stringBuilder.AppendLine($"[PROXY] {_proxies.Count} guest pet prox(ies):"); foreach (KeyValuePair proxy in _proxies) { PetProxy value = proxy.Value; float current; float max; string arg = ((value.Anchor != null && value.Anchor.TryGetHealth(out current, out max)) ? $"{current:F0}/{max:F0}" : "no-anchor"); string text; try { Character commandedTarget = value.Bond.Stance.CommandedTarget; text = (((Object)(object)commandedTarget != (Object)null) ? ("'" + commandedTarget.Name + "'") : ((value.MandateUid != null) ? "destroyed" : "none")); } catch { text = "destroyed"; } string text2 = ((value.LastAttackedAt > 0f) ? $"'{value.LastAttackedUid}' {Time.unscaledTime - value.LastAttackedAt:F0}s ago" : "none"); Character owner = (((Object)(object)CharacterManager.Instance != (Object)null) ? CharacterManager.Instance.GetCharacter(value.OwnerUid) : null); stringBuilder.AppendLine($"[PROXY] owner '{value.OwnerUid}' (actor {value.OwnerActorId}) species '{value.SpeciesKey}' " + (string.IsNullOrEmpty(value.DisplayName) ? "" : ("name '" + value.DisplayName + "' ")) + string.Format("tier={0} stance={1} hp={2} ", value.LoyaltyTier, value.Bond.Stance.Passive ? "passive" : "engaged", arg) + string.Format("eff={0} maxHealth={1:F0} ", (value.LastEff != null) ? "set" : "none", value.LastMaxHealth) + "instructed=" + text + " lastAttacked=" + text2); stringBuilder.AppendLine("[PROXY] " + PositionFragment(value, owner)); } } stringBuilder.AppendLine(NetBus.CountersSummary()); return stringBuilder.ToString().TrimEnd(Array.Empty()); } } public sealed class ProxyAttackReporter : MonoBehaviour { internal string OwnerUid; private void CharHurt(Character dealer) { ProxyPets.OnProxyAnchorAttacked(OwnerUid, dealer); } } internal static class PuppetVisibility { private static bool RenderReady(Renderer r) { if ((Object)(object)r == (Object)null || !r.enabled || !((Component)r).gameObject.activeInHierarchy || r.forceRenderingOff) { return false; } if ((Object)(object)r.sharedMaterial == (Object)null) { return false; } SkinnedMeshRenderer val = (SkinnedMeshRenderer)(object)((r is SkinnedMeshRenderer) ? r : null); if ((Object)(object)val != (Object)null && (Object)(object)val.sharedMesh == (Object)null) { return false; } return true; } internal static int CountRenderReady(GameObject go) { int num = 0; if ((Object)(object)go != (Object)null) { Renderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Renderer r in componentsInChildren) { if (RenderReady(r)) { num++; } } } return num; } internal static void VisDump(GameObject go, string tag) { //IL_0201: Unknown result type (might be due to invalid IL or missing references) //IL_0206: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)go == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[VIS] dump: no puppet body."); return; } Renderer[] componentsInChildren = go.GetComponentsInChildren(true); CompanionRuntime.Log.LogMessage((object)$"[VIS] {tag}: '{((Object)go).name}' — {componentsInChildren.Length} renderer(s), {CountRenderReady(go)} draw-ready."); Renderer[] array = componentsInChildren; foreach (Renderer val in array) { if (!((Object)(object)val == (Object)null)) { SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null); string text = ""; Material[] sharedMaterials = val.sharedMaterials; for (int j = 0; j < sharedMaterials.Length; j++) { text = text + ((j > 0) ? "," : "") + (((Object)(object)sharedMaterials[j] != (Object)null) ? ((Object)sharedMaterials[j]).name : "NULL"); } string text2 = (((Object)(object)val2 == (Object)null) ? "" : (string.Format(" mesh={0} offscreenUpd={1}", ((Object)(object)val2.sharedMesh != (Object)null) ? ((Object)val2.sharedMesh).name : "NULL", val2.updateWhenOffscreen) + string.Format(" rootBone={0} bones={1}", ((Object)(object)val2.rootBone != (Object)null) ? ((Object)val2.rootBone).name : "null", (val2.bones != null) ? val2.bones.Length : 0))); ModLog log = CompanionRuntime.Log; string text3 = $"[VIS] '{((Object)((Component)val).gameObject).name}' [{((object)val).GetType().Name}] enabled={val.enabled} activeInHier={((Component)val).gameObject.activeInHierarchy}"; string text4 = $" forceOff={val.forceRenderingOff} isVisible={val.isVisible} layer={LayerMask.LayerToName(((Component)val).gameObject.layer)}"; string arg = text; Bounds bounds = val.bounds; log.LogMessage((object)(text3 + text4 + text2 + $" mats=[{arg}] boundsSize={((Bounds)(ref bounds)).size}")); } } } internal static bool VisRepair(GameObject go) { if ((Object)(object)go == (Object)null) { CompanionRuntime.Log.LogWarning((object)"[VIS] repair: no puppet body."); return false; } int num = CountRenderReady(go); Renderer[] componentsInChildren = go.GetComponentsInChildren(true); foreach (Renderer val in componentsInChildren) { if ((Object)(object)val == (Object)null) { continue; } val.enabled = true; val.forceRenderingOff = false; SkinnedMeshRenderer val2 = (SkinnedMeshRenderer)(object)((val is SkinnedMeshRenderer) ? val : null); if ((Object)(object)val2 != (Object)null) { val2.updateWhenOffscreen = true; if ((Object)(object)val2.rootBone == (Object)null && (Object)(object)val2.sharedMesh != (Object)null) { val2.rootBone = ((Component)val2).transform; CompanionRuntime.Log.LogWarning((object)("[VIS] repair: '" + ((Object)((Component)val2).gameObject).name + "' rootBone was null/destroyed — fell back to its own transform.")); } } } if (CountRenderReady(go) == 0) { Renderer val3 = null; Renderer[] componentsInChildren2 = go.GetComponentsInChildren(true); foreach (Renderer val4 in componentsInChildren2) { if (!((Object)(object)val4 == (Object)null)) { SkinnedMeshRenderer val5 = (SkinnedMeshRenderer)(object)((val4 is SkinnedMeshRenderer) ? val4 : null); if (!((Object)(object)val5 != (Object)null) || !((Object)(object)val5.sharedMesh == (Object)null)) { val3 = val4; break; } } } Transform val6 = (((Object)(object)val3 != (Object)null) ? ((Component)val3).transform : null); while ((Object)(object)val6 != (Object)null) { if (!((Component)val6).gameObject.activeSelf) { ((Component)val6).gameObject.SetActive(true); CompanionRuntime.Log.LogWarning((object)("[VIS] repair: activated inactive '" + ((Object)val6).name + "' (no renderer would draw without it).")); } val6 = (((Object)(object)val6 == (Object)(object)go.transform) ? null : val6.parent); } } int num2 = CountRenderReady(go); CompanionRuntime.Log.LogMessage((object)$"[VIS] repair: draw-ready {num} -> {num2}."); return num2 > 0; } } public class RigStabilizer : MonoBehaviour { public bool HumanoidMode; private NavMeshAgent _agent; public Func SpeedSource; private Transform _rootBone; private Vector3 _rootBonePin; private SkinnedMeshRenderer _sinkSmr; private float _sinkLog; private CompanionBody _bodyRef; private const float CalibrationTimeoutSeconds = 120f; private CompanionBody Body { get { if (!((Object)(object)_bodyRef != (Object)null)) { return _bodyRef = ((Component)this).GetComponent(); } return _bodyRef; } } private ICompanionSettings Cfg => (((Object)(object)Body != (Object)null) ? (Body.Settings ?? Body.Host?.Settings) : null) ?? CompanionRuntime.Fallback; private string TagRootbone => CompanionRuntime.Tag("ROOTBONE", Cfg); private string TagSink => CompanionRuntime.Tag("SINK", Cfg); private void Start() { _agent = ((Component)this).GetComponent(); if (!HumanoidMode) { ((MonoBehaviour)this).StartCoroutine(CalibrateRootBone()); } } private IEnumerator CalibrateRootBone() { Transform[] all = ((Component)this).GetComponentsInChildren(true); Vector3[] start = null; Vector3[] sumRoot = (Vector3[])(object)new Vector3[all.Length]; float[] maxd = new float[all.Length]; int n = 0; float t0 = Time.realtimeSinceStartup; while (n < 90) { if (Time.realtimeSinceStartup - t0 > 120f) { CompanionRuntime.Log.LogWarning((object)(TagRootbone + " calibration never sampled — pin DISARMED for this " + $"body ({n}/90 samples in {120f:F0}s: the sampled speed stayed under 1 m/s). " + "Root-motion drift correction is off for it; a visible stride blip or sink would be bug 8/9.")); yield break; } float num; if (SpeedSource == null) { if (!((Object)(object)_agent != (Object)null)) { num = 0f; } else { Vector3 velocity = _agent.velocity; num = ((Vector3)(ref velocity)).magnitude; } } else { num = SpeedSource(); } float num2 = num; if (num2 > 1f) { if (start == null) { start = all.Select((Transform val5) => (!((Object)(object)val5 != (Object)null)) ? Vector3.zero : ((Component)this).transform.InverseTransformPoint(val5.position)).ToArray(); } for (int num3 = 0; num3 < all.Length; num3++) { if (!((Object)(object)all[num3] == (Object)null)) { Vector3 val = ((Component)this).transform.InverseTransformPoint(all[num3].position); float num4 = Vector3.Distance(val, start[num3]); if (num4 > maxd[num3]) { maxd[num3] = num4; } ref Vector3 reference = ref sumRoot[num3]; reference += val; } } n++; } yield return null; } Transform val2 = null; int num5 = int.MaxValue; int num6 = -1; List list = new List(); for (int num7 = 0; num7 < all.Length; num7++) { if (!(maxd[num7] < 0.3f) && !((Object)(object)all[num7] == (Object)null)) { int num8 = 0; Transform val3 = all[num7]; while ((Object)(object)val3 != (Object)null && (Object)(object)val3 != (Object)(object)((Component)this).transform) { num8++; val3 = val3.parent; } if (list.Count < 8) { list.Add($"{((Object)all[num7]).name}(d{num8},{maxd[num7]:F1}m)"); } if (num8 < num5) { num5 = num8; val2 = all[num7]; num6 = num7; } } } if ((Object)(object)val2 != (Object)null) { _rootBone = val2; _rootBonePin = sumRoot[num6] / (float)n; Vector3 val4 = (((Object)(object)val2.parent != (Object)null) ? (Quaternion.Inverse(val2.parent.rotation) * ((Component)this).transform.up) : Vector3.up); CompanionRuntime.Log.LogMessage((object)($"{TagRootbone} auto-pinning '{((Object)val2).name}' (depth {num5}, drift {maxd[num6]:F2}m; " + "root-up in parent frame = " + ((Vector3)(ref val4)).ToString("F2") + ((Mathf.Abs(val4.y) < 0.9f) ? " — TILTED parent; the old parent-local pin would sink this rig (bugs 8/9)" : "") + "). Drifting candidates: " + string.Join(", ", list.ToArray()))); } else { CompanionRuntime.Log.LogMessage((object)(TagRootbone + " no drifting bone found (move the pet around to calibrate).")); } } public void PinTick() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_rootBone != (Object)null) { Vector3 val = ((Component)this).transform.InverseTransformPoint(_rootBone.position); _rootBone.position = ((Component)this).transform.TransformPoint(new Vector3(_rootBonePin.x, val.y, _rootBonePin.z)); } SinkCheck(); } private void SinkCheck() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) if (Time.time - _sinkLog < 3f) { return; } _sinkLog = Time.time; if (!CompanionRuntime.IsSanePosition(((Component)this).transform.position)) { return; } if ((Object)(object)_sinkSmr == (Object)null) { _sinkSmr = ((Component)this).GetComponentInChildren(); if ((Object)(object)_sinkSmr == (Object)null) { return; } } Bounds bounds = ((Renderer)_sinkSmr).bounds; float num = ((Component)this).transform.position.y - ((HumanoidMode && (Object)(object)Body != (Object)null) ? Body.AgentBaseOffset : 0f); float num2 = ((Bounds)(ref bounds)).max.y - num; float num3 = num - ((Bounds)(ref bounds)).min.y; float num4 = Mathf.Max(((Bounds)(ref bounds)).extents.x, ((Bounds)(ref bounds)).extents.z) * Mathf.Sin(Vector3.Angle(((Component)this).transform.up, Vector3.up) * ((float)Math.PI / 180f)); if (num2 < 0.25f - num4 || num3 > 1f + num4) { CompanionRuntime.Log.LogWarning((object)($"{TagSink} skinned body vs feet plane: top {num2:+0.00;-0.00}m, " + $"bottom {0f - num3:+0.00;-0.00}m (feet y={num:F2}) — body sunk relative to the " + "transform (bug 8/9 signature).")); } } } public static class SkySnapshot { public static void Log(string tag) { SkySnapshot.Log(tag, ModLog.op_Implicit(CompanionRuntime.Log)); } public static bool TryGetDisplayDesync(out Camera cam, out CameraQuality quality, out string detail) { return SkySnapshot.TryGetDisplayDesync(ref cam, ref quality, ref detail); } } public sealed class SlopeTilt { private readonly Transform _t; private float _bodyHalf = 0.75f; private Vector3 _smoothed = Vector3.up; private Vector3 _sampled = Vector3.up; private float _nextProbe; private int _lastHits; private bool _flatLatch = true; private string _loggedMode; public string LastMode { get; private set; } = "Init"; public float LastPitchDeg { get; private set; } public float LastRollDeg { get; private set; } public float CurrentTiltDeg => SlopeMath.TiltDeg(_smoothed.x, _smoothed.y, _smoothed.z); private void SetMode(string mode) { if (_loggedMode == mode) { LastMode = mode; return; } LastMode = mode; _loggedMode = mode; ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)($"[SLOPE] {mode} pitch={LastPitchDeg:F1} roll={LastRollDeg:F1} " + $"n=({_smoothed.x:F2},{_smoothed.y:F2},{_smoothed.z:F2}) hits={_lastHits} half={_bodyHalf:F2}")); } } public SlopeTilt(Transform t) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) _t = t; } public void Calibrate(SkinnedMeshRenderer smr, NavMeshAgent agent) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) float num = 0f; if ((Object)(object)smr != (Object)null) { Bounds bounds = ((Renderer)smr).bounds; Vector3 extents = ((Bounds)(ref bounds)).extents; num = Mathf.Max(extents.x, extents.z); } if (num <= 0.01f && (Object)(object)agent != (Object)null) { num = agent.radius; } _bodyHalf = Mathf.Clamp(num, 0.35f, 2.5f); } public Quaternion Apply(Quaternion flatWant, Vector3 pos, float dt) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0196: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Unknown result type (might be due to invalid IL or missing references) //IL_0237: Unknown result type (might be due to invalid IL or missing references) //IL_0239: Unknown result type (might be due to invalid IL or missing references) //IL_023e: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0268: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0259: Unknown result type (might be due to invalid IL or missing references) Vector3 val = flatWant * Vector3.forward; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 1E-06f) { LastPitchDeg = 0f; LastRollDeg = 0f; SetMode("NoHeading"); return flatWant; } ((Vector3)(ref val)).Normalize(); if (Time.time >= _nextProbe) { float num = Mathf.Clamp(CkConfig.Slope.ProbeHz.Value, 1f, 60f); _nextProbe = Time.time + 1f / num; _sampled = Probe(pos, val); _flatLatch = SlopeMath.DeadbandFlat(_flatLatch, SlopeMath.TiltDeg(_sampled.x, _sampled.y, _sampled.z), CkConfig.Slope.DeadbandDegrees.Value); if (_flatLatch) { _sampled = Vector3.up; } } float num2 = default(float); float num3 = default(float); float num4 = default(float); SlopeMath.SmoothStep(_smoothed.x, _smoothed.y, _smoothed.z, _sampled.x, _sampled.y, _sampled.z, dt, Mathf.Max(0.01f, CkConfig.Slope.SmoothingTau.Value), Mathf.Max(1f, CkConfig.Slope.MaxTurnRateDegPerSec.Value), ref num2, ref num3, ref num4); _smoothed = new Vector3(num2, num3, num4); float num5 = default(float); float num6 = default(float); float num7 = default(float); SlopeMath.ClampTilt(_smoothed.x, _smoothed.y, _smoothed.z, val.x, val.z, Mathf.Max(0f, CkConfig.Slope.MaxPitchDegrees.Value), Mathf.Max(0f, CkConfig.Slope.MaxRollDegrees.Value), ref num5, ref num6, ref num7); Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(num5, num6, num7); float lastPitchDeg = default(float); float lastRollDeg = default(float); SlopeMath.PitchRollDeg(num5, num6, num7, val.x, val.z, ref lastPitchDeg, ref lastRollDeg); LastPitchDeg = lastPitchDeg; LastRollDeg = lastRollDeg; if (SlopeMath.TiltDeg(num5, num6, num7) < 0.5f) { SetMode("Flat"); return flatWant; } Vector3 val3 = Vector3.ProjectOnPlane(flatWant * Vector3.forward, val2); if (((Vector3)(ref val3)).sqrMagnitude < 1E-06f) { SetMode("Degenerate"); return flatWant; } SetMode("Tilted"); return Quaternion.LookRotation(((Vector3)(ref val3)).normalized, val2); } private Vector3 Probe(Vector3 pos, Vector3 fwd) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) RaycastHit hit; bool flag = CastGround(pos + fwd * _bodyHalf, out hit); RaycastHit hit2; bool flag2 = CastGround(pos - fwd * _bodyHalf, out hit2); _lastHits = (flag ? 1 : 0) + (flag2 ? 1 : 0); float num = default(float); float num2 = default(float); float num3 = default(float); if (flag && flag2 && SlopeMath.NormalFromTwoPoints(((RaycastHit)(ref hit)).point.x, ((RaycastHit)(ref hit)).point.y, ((RaycastHit)(ref hit)).point.z, ((RaycastHit)(ref hit2)).point.x, ((RaycastHit)(ref hit2)).point.y, ((RaycastHit)(ref hit2)).point.z, fwd.x, fwd.z, ref num, ref num2, ref num3)) { return new Vector3(num, num2, num3); } if (flag) { return PitchOnly(((RaycastHit)(ref hit)).normal, fwd); } if (flag2) { return PitchOnly(((RaycastHit)(ref hit2)).normal, fwd); } return Vector3.up; } private static Vector3 PitchOnly(Vector3 n, Vector3 fwd) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor(fwd.z, 0f, 0f - fwd.x); Vector3 val2 = n - Vector3.Dot(n, val) * val; if (!(((Vector3)(ref val2)).sqrMagnitude < 1E-06f) && !(val2.y <= 0f)) { return ((Vector3)(ref val2)).normalized; } return Vector3.up; } private bool CastGround(Vector3 at, out RaycastHit hit) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (Physics.Raycast(at + Vector3.up * (2f + _bodyHalf), Vector3.down, ref hit, 8f + _bodyHalf, Global.LargeEnvironmentMask, (QueryTriggerInteraction)1)) { return SlopeMath.AcceptFacet(((RaycastHit)(ref hit)).normal.y); } return false; } public string Describe() { return $"{LastMode} n=({_smoothed.x:F2},{_smoothed.y:F2},{_smoothed.z:F2}) pitch={LastPitchDeg:F1} roll={LastRollDeg:F1} hits={_lastHits} half={_bodyHalf:F2}"; } } public static class StrikeJudge { public static StrikeOutcome Judge(Vector3 attackerPos, Vector3 attackerForward, Character target, float maxRange, float coneDegrees) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0125: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) JudgeInputs val = new JudgeInputs { TargetAlive = ((Object)(object)target != (Object)null && target.Alive), TargetDiedSecondsAgo = SecondsSinceDeath(target), DistanceMeters = (((Object)(object)target != (Object)null) ? Vector3.Distance(attackerPos, ((Component)target).transform.position) : float.MaxValue), MaxRangeMeters = maxRange, AngleFromAttackerForward = (((Object)(object)target != (Object)null) ? Vector3.Angle(attackerForward, ((Component)target).transform.position - attackerPos) : 180f), ConeDegrees = coneDegrees, HitboxesInactive = HitboxesAllInactive(target), Dodging = ((Object)(object)target != (Object)null && target.Dodging), Blocking = ((Object)(object)target != (Object)null && target.Blocking), AngleFromTargetForward = (((Object)(object)target != (Object)null) ? Vector3.Angle(((Component)target).transform.forward, attackerPos - ((Component)target).transform.position) : 0f), TargetHasShield = ((Object)(object)target != (Object)null && target.ShieldEquipped) }; return StrikeJudge.Judge(ref val); } public static float SecondsSinceDeath(Character target) { if ((Object)(object)target == (Object)null || target.Alive) { return 0f; } float timeOfDeath = target.TimeOfDeath; if (timeOfDeath <= 0f) { return 0f; } return Mathf.Max(0.0001f, Time.time - timeOfDeath); } public static bool HitboxesAllInactive(Character target) { Hitbox[] array = (((Object)(object)target != (Object)null) ? target.Hitboxes : null); if (array == null || array.Length == 0) { return false; } Hitbox[] array2 = array; foreach (Hitbox val in array2) { if ((Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy) { return false; } } return true; } public static void PlayBlock(Character target, MonoBehaviour source, float damage, Vector3 dir, float angleFromTargetForward, Character dealer, Action log = null) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)target == (Object)null || (Object)(object)source == (Object)null) { return; } float num = Mathf.Sign(Vector3.Dot(Vector3.Cross(((Component)target).transform.forward, dir), Vector3.up)); try { target.ReceiveBlock(source, damage, dir, angleFromTargetForward, num, dealer, 1f); } catch (Exception ex) { log?.Invoke("ReceiveBlock FX skipped (" + ex.GetType().Name + ": " + ex.Message + ") — the block still dealt nothing."); } } } public static class SummonIconGuard { [HarmonyPatch(typeof(StatusEffectPanel), "Update")] internal static class StatusEffectPanel_Update { private static void Postfix(StatusEffectPanel __instance) { try { Character localCharacter = ((UIElement)__instance).LocalCharacter; Character val = (((Object)(object)localCharacter != (Object)null) ? localCharacter.CurrentSummon : null); if ((Object)(object)val == (Object)null || !CompanionAnchor.ShouldHideSummonIcon(val) || __instance.m_statusIcons == null || !__instance.m_statusIcons.TryGetValue("SummonGhost", out var value) || !((Object)(object)value != (Object)null) || !((Component)value).gameObject.activeSelf) { return; } ((Component)value).gameObject.SetActive(false); if (_loggedFor != val) { _loggedFor = val; ModLog log = CompanionRuntime.Log; if (log != null) { log.LogMessage((object)"[ANCHOR] hid the vanilla 'Summoned Ghost' HUD icon for the summon-linked anchor (Bug 35; [Anchor] HideSummonIcon=false restores it — and the CombatHUD NRE that rides it)."); } } } catch (Exception ex) { ModLog log2 = CompanionRuntime.Log; if (log2 != null) { log2.LogWarning((object)("[ANCHOR] summon-icon hide failed: " + ex.Message)); } } } } [HarmonyPatch(typeof(CharacterStatusEffectsDisplay), "UpdateDisplay")] internal static class CharacterStatusEffectsDisplay_UpdateDisplay { private static void Postfix(CharacterStatusEffectsDisplay __instance) { try { Character localCharacter = ((UIElement)__instance).LocalCharacter; Character val = (((Object)(object)localCharacter != (Object)null) ? localCharacter.CurrentSummon : null); if (!((Object)(object)val == (Object)null) && CompanionAnchor.ShouldHideSummonIcon(val)) { StatusEffectDetailDisplay summonGhostStatusDisplay = __instance.m_summonGhostStatusDisplay; if ((Object)(object)summonGhostStatusDisplay != (Object)null && ((UIElement)summonGhostStatusDisplay).IsDisplayed) { ((UIElement)summonGhostStatusDisplay).Hide(); } } } catch (Exception ex) { ModLog log = CompanionRuntime.Log; if (log != null) { log.LogWarning((object)("[ANCHOR] summon-row hide failed: " + ex.Message)); } } } } internal const string IconKey = "SummonGhost"; private static Character _loggedFor; public static string Describe(Character player) { try { if ((Object)(object)player == (Object)null) { return "summon: no player."; } Character currentSummon = player.CurrentSummon; if ((Object)(object)currentSummon == (Object)null) { return "summon: none (no icon possible)."; } bool flag = CompanionAnchor.IsAnchor(currentSummon); bool flag2 = CompanionAnchor.ShouldHideSummonIcon(currentSummon); string text = "panel not found"; StatusEffectPanel val = (((Object)(object)player.CharacterUI != (Object)null) ? ((Component)player.CharacterUI).GetComponentInChildren(true) : null); if ((Object)(object)val != (Object)null) { text = ((val.m_statusIcons == null || !val.m_statusIcons.TryGetValue("SummonGhost", out var value) || !((Object)(object)value != (Object)null)) ? "absent (never synthesized)" : (((Component)value).gameObject.activeSelf ? "VISIBLE" : "hidden")); } return $"summon: '{currentSummon.Name}' alive={!currentSummon.IsDead}, isAnchor={flag}, hideGate={flag2}, SummonGhost icon: {text}."; } catch (Exception ex) { return "summon: describe failed (" + ex.Message + ")."; } } }